재료공학 계산 함수와 간단한 클래스 작성
이번 강의가 끝나면 다음을 할 수 있어야 한다.
다음 함수는 계산은 가능하지만 변수의 뜻을 알기 어렵다.
def f(a, b):
return a / b
변수 이름과 docstring에 물리량과 단위를 나타내면 함수의 의도가 분명해진다.
def calculate_density(mass_g, volume_cm3):
"""Calculate density in g/cm^3 from mass and volume."""
return mass_g / volume_cm3
docstring은 아래와 같이 호출하여 함수의 설명을 확인할 수 있다.
# 직접 docstring을 출력하거나
print(calculate_density.__doc__)
# 혹은 help 함수 활용
help(calculate_density)
부피가 0 이하이면 밀도를 계산할 수 없다. raise를 사용하여 잘못된 입력을 바로 알릴 수 있다.
def calculate_density(mass_g, volume_cm3):
"""Calculate density in g/cm^3 from mass and volume."""
if volume_cm3 <= 0:
raise ValueError("volume_cm3 must be positive")
return mass_g / volume_cm3
처음에는 모든 예외를 처리하려고 하기보다, 계산을 불가능하게 만드는 중요한 입력부터 검사하면 된다.
일축 선형탄성에서 응력($\sigma$)과 변형률($\varepsilon$)은
\[\sigma=E\varepsilon\]의 관계를 갖는다.
def elastic_stress(modulus_gpa, strain):
"""Return stress in GPa."""
return modulus_gpa * strain
stress_gpa = elastic_stress(200.0, 0.001)
print(stress_gpa)
입력한 탄성계수가 GPa이므로 결과도 GPa이다. 결과는 0.2 GPa, 즉 200 MPa이다.
전위 밀도($\rho$)와 항복강도($\sigma_y$)는 다음 관계를 갖는다.
\[\sigma=\alpha G b \sqrt{\rho}\]$G$: 전단계수, $b$: 버거스 벡터 크기, $\alpha$: 상수
아래 재료의 항복 강도를 계산하는 함수를 작성하면 다음과 같다.
def yield_stress(alpha, shear_modulus_gpa, burgers_vector_nm, dislocation_density_per_m2):
"""Return yield stress in GPa."""
return (
alpha
* shear_modulus_gpa
* burgers_vector_nm
* (dislocation_density_per_m2 ** 0.5)
)
| 재료 | $\alpha$ | $G$ (GPa) | $b$ (nm) | $\rho$ ($\mathrm{m^{-2}}$) |
|---|---|---|---|---|
| Al | 0.5 | 26 | 0.286 | $1\times10^{12}$ |
| Fe | 0.5 | 80 | 0.248 | $1\times10^{12}$ |
| Cu | 0.5 | 48 | 0.256 | $1\times10^{12}$ |
초기 길이 $l_0$와 현재 길이 $l$로부터 공칭변형률을 계산하면
\[e=\frac{l-l_0}{l_0}\]이다. 균일한 일축변형에서 진변형률은
\[\varepsilon=\ln(1+e)=\ln\left(\frac{l}{l_0}\right)\]이다.
import math
def engineering_strain(initial_length, final_length):
if initial_length <= 0:
raise ValueError("initial_length must be positive")
return (final_length - initial_length) / initial_length
def true_strain(initial_length, final_length):
if initial_length <= 0 or final_length <= 0:
raise ValueError("lengths must be positive")
return math.log(final_length / initial_length)
print(engineering_strain(50.0, 55.0))
print(true_strain(50.0, 55.0))
일축응력($\sigma$)가 작용할 때 분해전단응력($\tau$)은
\[\tau=\sigma\cos\phi\cos\lambda\]이다. 응력 방향과 결정면 법선 방향의 사잇각을 $\phi$, 응력 방향과 전위 슬립 방향의 사잇각을 $\lambda$로 나타내고, 각도 단위를 명확히 하기 위해 함수 이름에 degree를 나타낸다. (각은 degree 혹은 radian 단위로 나타낼 수 있다.)
import math
def resolved_shear_stress(stress_mpa, phi_deg, lambda_deg):
phi_rad = math.radians(phi_deg)
lambda_rad = math.radians(lambda_deg)
return (
stress_mpa
* math.cos(phi_rad)
* math.cos(lambda_rad)
)
tau = resolved_shear_stress(100.0, 45.0, 45.0)
print(tau)
결과는 약 50 MPa이다.
함수는 하나의 작업을 묶는 데 적합하다. 재료의 이름, 밀도, 탄성계수처럼 서로 관련된 여러 데이터와 그 데이터를 사용하는 동작을 함께 묶으려면 클래스(class)가 편리하다.
클래스는 객체를 만들기 위한 설계도이고, 클래스로 만든 각각의 실체를 객체(object) 또는 인스턴스(instance)라고 한다.
예를 들어 Al과 Fe 객체는 같은 Material 클래스로 만들 수 있지만 서로 다른 밀도와 결정구조를 가질 수 있다. 따라서 Al과 Fe에 해당하는 각자 다른 인스턴스를 만들 수 있다. 각 인스턴스는 서로 다른 속성(attribute) 값을 갖는다.
class Material:
def __init__(self, name, density):
self.name = name
self.density = density
def describe(self):
return f"{self.name}: {self.density} g/cm^3"
aluminum = Material("Aluminum", 2.70)
print(aluminum.name)
print(aluminum.describe())
각 부분의 의미는 다음과 같다.
Material: 클래스 이름__init__: 객체를 만들 때 실행되는 초기화 메서드self: 현재 객체 자신self.name, self.density: 객체의 속성(attribute)describe: 객체가 수행할 수 있는 메서드(method)aluminum: Material 클래스의 객체메서드를 정의할 때 첫 번째 매개변수로 self를 적어야 한다.
class Alloy:
def __init__(
self,
name,
tensile_strength_mpa,
density_g_cm3,
):
self.name = name
self.tensile_strength_mpa = tensile_strength_mpa
self.density_g_cm3 = density_g_cm3
def specific_strength(self):
return (
self.tensile_strength_mpa
/ self.density_g_cm3
)
def describe(self):
return (
f"{self.name}: "
f"strength={self.tensile_strength_mpa} MPa, "
f"density={self.density_g_cm3} g/cm^3"
)
alloy = Alloy("Ti-6Al-4V", 900.0, 4.4)
print(alloy.describe())
print(alloy.specific_strength())
비강도(specific strength)는 여기서 인장강도를 밀도로 나눈 비교용 값이다. 서로 같은 단위를 사용한 재료끼리 비교해야 한다.
같은 클래스로 만든 객체를 리스트에 저장하면 반복문으로 비교할 수 있다.
alloys = [
Alloy("Ni-Cu", 450.0, 8.9),
Alloy("Al-Mg", 320.0, 2.7),
Alloy("Ti-6Al-4V", 900.0, 4.4),
]
for alloy in alloys:
print(
alloy.name,
alloy.specific_strength(),
)
비강도가 가장 큰 객체는 다음과 같이 찾을 수 있다.
best = max(
alloys,
key=lambda alloy: alloy.specific_strength(),
)
print(best.name)
getattr를 사용하면 속성 이름을 문자열로 지정할 수도 있다.
property_name = "tensile_strength_mpa"
for alloy in alloys:
value = getattr(alloy, property_name)
print(alloy.name, value)
초급 단계에서는 alloy.tensile_strength_mpa처럼 속성을 직접 쓰는 것이 더 읽기 쉽다. 속성 이름을 실행 중에 선택해야 할 때 getattr가 유용하다.
예를 들어 밀도 하나를 계산하는 작업은 함수로 충분하다. 여러 합금의 이름, 밀도, 강도와 비교 동작을 함께 관리한다면 클래스가 편리하다.
Young 계수가 70 GPa이고 탄성변형률이 0.002일 때 elastic_stress 함수가 반환하는 응력을 구하라.
초기 길이가 100 mm이고 현재 길이가 102 mm일 때 공칭변형률을 구하라.
클래스와 객체의 관계를 한 문장으로 설명하라.
다음 코드에서 클래스, 객체와 속성을 각각 하나씩 쓰시오.
iron = Material("Iron", 7.87)
Material 객체 iron의 밀도를 읽는 표현을 쓰시오.
메서드를 정의할 때 첫 번째 매개변수로 일반적으로 사용하는 이름은 무엇인가?
4장 조건문과 반복문의 경도 검사 프로젝트를 확장하여, 판정 함수와 시편 클래스를 작성하시오. 함수는 공통 판정 규칙을 담당하고, 객체는 각 시편의 이름과 측정값을 함께 저장한다. 모든 측정값은 동일한 경도 척도와 시험 조건에서 얻었다고 가정한다.
classify_hardness(value)는 수치 하나를 받아 다음 판정 문자열을 반환한다. 기준은 프로그래밍 연습을 위한 가상 규격이다.
| 측정값 | 반환값 |
|---|---|
| 0 이하 | '잘못된 측정값' |
| 0 초과, 120 미만 | '기준 미달' |
| 120 이상, 150 이하 | '합격' |
| 150 초과 | '기준 초과' |
입력은 숫자라고 가정한다. 함수 안에서 출력하지 않고 return으로 판정 결과를 돌려준다.
HardnessSample 클래스를 작성한다.
__init__(self, name, hardness)에서 시편 이름과 경도를 self.name, self.hardness에 저장한다.classify(self) 메서드는 자신의 경도 값을 classify_hardness에 전달하여 판정 결과를 반환한다.측정 오류가 있는 시편도 기록으로 남기기 위해 객체 생성은 허용하되, 판정에서 오류를 표시하고 평균에서는 제외한다. 다음 호출 코드가 동작하도록 구현하시오.
samples = [
HardnessSample('A', 110),
HardnessSample('B', 130),
HardnessSample('C', 166),
HardnessSample('D', -1),
]
for sample in samples:
print(sample.name, sample.hardness, sample.classify())
위 예제에서 A는 기준 미달, B는 합격, C는 기준 초과, D는 잘못된 측정값이다. 유효한 측정값은 3개, 합격 시편은 1개이며 평균은 약 135.33이다.
추가로 다음 경우를 확인하시오.
제출물: 판정 함수, 클래스 정의, 객체 리스트를 처리하는 코드, 확인 사례별 결과, 그리고 ‘함수·속성·메서드가 각각 어떤 역할을 하는가?’에 대한 짧은 설명.