Skip to content
Snippets Groups Projects
Commit 14b66cf3 authored by Rahil Doshi's avatar Rahil Doshi
Browse files

Add tests for typedefs.py in test_typedefs.py, add post init functions in...

Add tests for typedefs.py in test_typedefs.py, add post init functions in typedefs.py to validate assignments after initialization, check if symbol is found in expresiion or assignment in evalf
parent 8824a70a
Branches
Tags
No related merge requests found
...@@ -62,6 +62,18 @@ class MaterialProperty: ...@@ -62,6 +62,18 @@ class MaterialProperty:
expr: sp.Expr expr: sp.Expr
assignments: List[Assignment] = field(default_factory=list) assignments: List[Assignment] = field(default_factory=list)
def __post_init__(self):
"""Validate assignments after initialization."""
if any(assignment is None for assignment in self.assignments):
raise ValueError("None assignments are not allowed")
# Validate each assignment
for assignment in self.assignments:
if not isinstance(assignment, Assignment):
raise ValueError(f"Invalid assignment type: {type(assignment)}")
if assignment.lhs is None or assignment.rhs is None or assignment.lhs_type is None:
raise ValueError("Assignment fields cannot be None")
def evalf(self, symbol: sp.Symbol, temperature: Union[float, ArrayTypes]) -> Union[float, np.ndarray]: def evalf(self, symbol: sp.Symbol, temperature: Union[float, ArrayTypes]) -> Union[float, np.ndarray]:
""" """
Evaluates the material property at specific temperature values. Evaluates the material property at specific temperature values.
...@@ -72,18 +84,44 @@ class MaterialProperty: ...@@ -72,18 +84,44 @@ class MaterialProperty:
Returns: Returns:
Union[float, np.ndarray]: The evaluated property value(s) at the given temperature(s). Union[float, np.ndarray]: The evaluated property value(s) at the given temperature(s).
Raises:
TypeError: If:
- symbol is not found in expression or assignments
- temperature contains non-numeric values
- invalid type for temperature
""" """
# Get all symbols from expression and assignments
expr_symbols = self.expr.free_symbols
assignment_symbols = set().union(*(
assignment.rhs.free_symbols
for assignment in self.assignments
if isinstance(assignment.rhs, sp.Expr)
))
all_symbols = expr_symbols.union(assignment_symbols)
# If we have symbols but the provided one isn't among them, raise TypeError
if all_symbols and symbol not in all_symbols:
raise TypeError(f"Symbol {symbol} not found in expression or assignments")
# If the expression has no symbolic variables, return it as a constant float # If the expression has no symbolic variables, return it as a constant float
if not self.expr.free_symbols: if not self.expr.free_symbols:
return float(self.expr) return float(self.expr)
# Handle array inputs
# If temperature is a numpy array, list, or tuple (ArrayTypes), evaluate the property for each temperature # If temperature is a numpy array, list, or tuple (ArrayTypes), evaluate the property for each temperature
if isinstance(temperature, get_args(ArrayTypes)): if isinstance(temperature, get_args(ArrayTypes)):
return np.array([self.evalf(symbol, t) for t in temperature]) return np.array([self.evalf(symbol, t) for t in temperature])
# Convert any numpy scalar to Python float # Convert any numpy scalar to Python float
elif isinstance(temperature, np.generic): if isinstance(temperature, np.floating):
temperature = float(temperature)
# Convert numeric types to float
try:
temperature = float(temperature) temperature = float(temperature)
except (TypeError, ValueError):
raise TypeError(f"Temperature must be numeric, got {type(temperature)}")
# Prepare substitutions for symbolic assignments # Prepare substitutions for symbolic assignments
substitutions = [(symbol, temperature)] substitutions = [(symbol, temperature)]
...@@ -98,7 +136,6 @@ class MaterialProperty: ...@@ -98,7 +136,6 @@ class MaterialProperty:
# Evaluate the material property with the substitutions # Evaluate the material property with the substitutions
result = sp.N(self.expr.subs(substitutions)) result = sp.N(self.expr.subs(substitutions))
# return float(result)
# Try to convert the result to float if possible # Try to convert the result to float if possible
try: try:
return float(result) return float(result)
......
import pytest
import numpy as np
import sympy as sp
from pymatlib.core.typedefs import Assignment, MaterialProperty, ArrayTypes, PropertyTypes
def test_assignment():
"""Test Assignment dataclass functionality."""
# Test basic assignment
x = sp.Symbol('x')
assignment = Assignment(x, 100, 'double')
assert assignment.lhs == x
assert assignment.rhs == 100
assert assignment.lhs_type == 'double'
# Test with tuple
v = sp.IndexedBase('v')
assignment = Assignment(v, (1, 2, 3), 'double[]')
assert assignment.lhs == v
assert assignment.rhs == (1, 2, 3)
assert assignment.lhs_type == 'double[]'
def test_material_property_constant():
"""Test MaterialProperty with constant values."""
# Test constant property
mp = MaterialProperty(sp.Float(405.))
assert mp.evalf(sp.Symbol('T'), 100.) == 405.0
# Test with assignments
mp.assignments.append(Assignment(sp.Symbol('A'), (100, 200), 'int'))
assert isinstance(mp.assignments, list)
assert len(mp.assignments) == 1
def test_material_property_temperature_dependent():
"""Test MaterialProperty with temperature-dependent expressions."""
T = sp.Symbol('T')
# Test linear dependency
mp = MaterialProperty(T * 100.)
assert mp.evalf(T, 2.0) == 200.0
# Test polynomial
mp = MaterialProperty(T**2 + T + 1)
assert mp.evalf(T, 2.0) == 7.0
def test_material_property_indexed():
"""Test MaterialProperty with indexed base expressions."""
T = sp.Symbol('T')
v = sp.IndexedBase('v')
i = sp.Symbol('i', integer=True)
mp = MaterialProperty(v[i])
mp.assignments.extend([
Assignment(v, (3, 6, 9), 'float'),
Assignment(i, T / 100, 'int')
])
assert mp.evalf(T, 97) == 3 # i=0
assert mp.evalf(T, 150) == 6 # i=1
def test_material_property_errors():
"""Test error handling in MaterialProperty."""
T = sp.Symbol('T')
X = sp.Symbol('X') # Different symbol
# Test evaluation with wrong symbol
mp = MaterialProperty(X * 100)
with pytest.raises(TypeError, match="Symbol T not found in expression or assignments"):
mp.evalf(T, 100.0)
# Test invalid assignments
with pytest.raises(ValueError, match="None assignments are not allowed"):
MaterialProperty(T * 100, assignments=[None])
def test_material_property_array_evaluation():
"""Test MaterialProperty evaluation with arrays."""
T = sp.Symbol('T')
mp = MaterialProperty(T * 100)
# Test with numpy array
temps = np.array([1.0, 2.0, 3.0])
result = mp.evalf(T, temps)
assert isinstance(result, np.ndarray)
assert np.allclose(result, temps * 100.0)
# Test with invalid array values
with pytest.raises(TypeError):
mp.evalf(T, np.array(['a', 'b', 'c']))
def test_material_property_piecewise():
"""Test MaterialProperty with piecewise functions."""
T = sp.Symbol('T')
mp = MaterialProperty(sp.Piecewise(
(100, T < 0),
(200, T >= 100),
(T, True)
))
assert mp.evalf(T, -10) == 100
assert mp.evalf(T, 50) == 50
assert mp.evalf(T, 150) == 200
def test_material_property_numpy_types():
"""Test MaterialProperty with numpy numeric types."""
T = sp.Symbol('T')
mp = MaterialProperty(T * 100)
assert mp.evalf(T, np.float32(1.0)) == 100.0
assert mp.evalf(T, np.float64(1.0)) == 100.0
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment