pymats 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
cae/__init__.py ADDED
@@ -0,0 +1,42 @@
1
+ import numpy as np
2
+ from typing import Optional, Literal
3
+ from pymats import MaterialBase
4
+ from pymats.cae.constitutive import ConstitutiveModel, IsotropicModel
5
+ from pymats.cae.stressstraincurve import StressStrainCurve, ModelConstants
6
+
7
+ class CAEModel:
8
+
9
+ material: MaterialBase
10
+
11
+ def __init__(self, material: MaterialBase) -> None:
12
+ self.material = material
13
+
14
+
15
+ def get_stress_strain_curve(self, approach: ModelConstants.Approaches) -> StressStrainCurve:
16
+ a_s = self.material.ElongationBreak.long if self.material.ElongationBreak else 0.12
17
+ if np.isclose(a_s, 0.0):
18
+ a_s = 1.2
19
+ curve = StressStrainCurve(
20
+ Model=approach,
21
+ youngs_modulus=self.material.EO,
22
+ yield_strength=self.material.SYo,
23
+ tensile_strength=self.material.STo,
24
+ elongation_at_break=a_s
25
+ )
26
+ curve.attach_material(self.material)
27
+ curve.build()
28
+ return curve
29
+
30
+
31
+ def get_constitutive_model(
32
+ self, d: Optional[np.ndarray] = None,
33
+ kinematics: Literal['full', 'axissymmetric', 'plane strain', 'plane stress'] = 'full'
34
+ ) -> ConstitutiveModel:
35
+ return IsotropicModel(
36
+ d or IsotropicModel.compute_d(
37
+ self.material.EO,
38
+ self.material.PoissonsRatio,
39
+ kinematics
40
+ ),
41
+ self.material.EO, self.material.PoissonsRatio
42
+ )
cae/constitutive.py ADDED
@@ -0,0 +1,127 @@
1
+ from typing import Literal
2
+ import numpy as np
3
+ from abc import ABC, abstractmethod
4
+
5
+
6
+ class Plasticity:
7
+
8
+ tangent_modulus: float = 203e3
9
+ @property
10
+ def H(self):
11
+ return self.tangent_modulus
12
+
13
+ def __init__(self, e: float) -> None:
14
+ # initalized with tangent modulus as youngs modulus for no plasticity
15
+ self.tangent_modulus = e
16
+
17
+
18
+
19
+ class ConstitutiveModel(ABC):
20
+
21
+ plasticity: Plasticity
22
+
23
+ _d: np.ndarray
24
+ @property
25
+ def D(self):
26
+ return self._d
27
+
28
+ @classmethod
29
+ @abstractmethod
30
+ def compute_d(cls, *args, **kwargs) -> np.ndarray:
31
+ pass
32
+
33
+ def __init__(self, d: np.ndarray, e: float, nu: float) -> None:
34
+ super().__init__()
35
+ self._d = d
36
+ self.plasticity = Plasticity(e)
37
+
38
+ def compute_elastic_stress(self, eps: np.ndarray):
39
+ return self._d @ eps
40
+
41
+ def set_plastiticity(
42
+ self,
43
+ plasticity: Literal['linear hardening' ,'perfect'],
44
+ sy: float,
45
+ st: float,
46
+ e: float,
47
+ a_s: float
48
+ ):
49
+ if plasticity == 'perfect':
50
+ self.plasticity.tangent_modulus = 0.0
51
+ elif plasticity == 'linear hardening':
52
+ eps_el = sy / e
53
+ eps_pl = a_s - eps_el
54
+ ds = st - sy
55
+ self.plasticity.tangent_modulus = ds / eps_pl
56
+
57
+ class IsotropicModel(ConstitutiveModel):
58
+
59
+ # lame parameters
60
+ mu: float
61
+ lam: float
62
+ # ##################
63
+
64
+
65
+ @classmethod
66
+ def compute_lame_parameters(cls, e, nu):
67
+ mu = e / (2 * (1 + nu))
68
+ lam = e * nu / ((1 + nu) * (1 - 2 * nu))
69
+ return mu, lam
70
+
71
+ @classmethod
72
+ def compute_axissymmetric_stiffness(cls, e: float, nu: float):
73
+ c = e / ((1 + nu) * (1 - 2 * nu))
74
+ return c * np.array([
75
+ [1 - nu, nu, nu, 0],
76
+ [nu, 1 - nu, nu, 0],
77
+ [nu, nu, 1 - nu, 0],
78
+ [0, 0, 0, (1 - 2 * nu) / 2]
79
+ ])
80
+
81
+ @classmethod
82
+ def compute_plane_strain_stiffness(cls, e: float, nu: float):
83
+ c = e / ((1 + nu) * (1 - 2 * nu))
84
+ return c * np.array([
85
+ [1 - nu, nu, 0],
86
+ [nu, 1 - nu, 0],
87
+ [0, 0, (1 - 2 * nu) / 2.0]
88
+ ])
89
+
90
+ @classmethod
91
+ def compute_plane_stress_stiffness(cls, e: float, nu: float):
92
+ c = e / (1 - nu **2)
93
+ return c * np.array([
94
+ [1, nu, 0],
95
+ [nu, 1, 0],
96
+ [0, 0, (1 - nu) / 2]
97
+ ])
98
+
99
+ @classmethod
100
+ def compute_full_stiffness(cls, e: float, nu: float):
101
+ mu, lam = cls.compute_lame_parameters(e, nu)
102
+ d = np.zeros((6, 6))
103
+ d[:3, :3] = lam
104
+ np.fill_diagonal(d[:3, :3], lam + 2 * mu)
105
+ d[3:, 3:] = np.eye(3) * mu
106
+ return d
107
+
108
+ @classmethod
109
+ def compute_d(cls, e: float, nu: float, kinematics: Literal['full', 'plane strain', 'plane stress', 'axissymmetric']) -> np.ndarray:
110
+
111
+ if kinematics == 'axissymmetric':
112
+ d = cls.compute_axissymmetric_stiffness(e, nu)
113
+ elif kinematics == 'plane strain':
114
+ d = cls.compute_plane_strain_stiffness(e, nu)
115
+ elif kinematics == 'plane stress':
116
+ d = cls.compute_plane_stress_stiffness(e, nu)
117
+ elif kinematics == 'full':
118
+ d = cls.compute_full_stiffness(e, nu)
119
+ else:
120
+ raise ValueError("Don't understand kinematic requirement!")
121
+ return d
122
+
123
+
124
+ def __init__(self, d: np.ndarray, e: float, nu: float) -> None:
125
+ super().__init__(d, e, nu)
126
+ self.mu, self.lam = self.compute_lame_parameters(e, nu)
127
+
@@ -0,0 +1,156 @@
1
+ import numpy as np
2
+ from typing import Optional
3
+
4
+ from pydantic import computed_field, PrivateAttr
5
+
6
+ from pymats.utils.log import logger
7
+ from enum import Enum
8
+ from pymats.utils.mbasemodel import MBaseModel
9
+ from pymats import MaterialBase
10
+
11
+
12
+ class ModelConstants:
13
+ class Approaches(str, Enum):
14
+ LINEARELASTIC = "linearelastic"
15
+ PERFECTPLASTICITY = "perfectplastic"
16
+ LINEARHARDENING = "linearhardening"
17
+
18
+ class StressStrainCurve(MBaseModel):
19
+
20
+ _data: np.ndarray = PrivateAttr(default=np.array((2, 2)))
21
+ _material: MaterialBase | None = PrivateAttr(default=None)
22
+ @computed_field(return_type=np.ndarray)
23
+ @property
24
+ def Data(self):
25
+ return self._data
26
+ Model: ModelConstants.Approaches
27
+
28
+ # material properties
29
+ youngs_modulus: float
30
+ yield_strength: float
31
+ tensile_strength: float
32
+ elongation_at_break: float
33
+
34
+ def attach_material(self, material: MaterialBase) -> None:
35
+ self._material = material
36
+
37
+ def get_elastic_strain(self) -> float:
38
+ if not self._material:
39
+ raise ValueError("Material not attached to stress-strain curve.")
40
+ if not np.isclose(self._material.EO, 0.0):
41
+ return self._material.SYo / self._material.EO
42
+ raise ZeroDivisionError("The Young's modulus is zero! Check material data.")
43
+
44
+ def get_ansys_model(self) -> str:
45
+ if not self._material:
46
+ raise ValueError("Material not attached to stress-strain curve.")
47
+ lines = [
48
+ "/PREP7",
49
+ f"! Material {self._material.Synopsis} properties",
50
+ f"MP, EX, {self._material.MID}, {self._material.EO}",
51
+ f"MP, PRXY, {self._material.MID}, {self._material.PoissonsRatio}",
52
+ f"MP, DENS, {self._material.MID}, {self._material.Density}",
53
+ ]
54
+
55
+ if self.Model in [ModelConstants.Approaches.LINEARHARDENING, ModelConstants.Approaches.PERFECTPLASTICITY]:
56
+ hardening_modulus = 0.0
57
+ if self.Model == ModelConstants.Approaches.LINEARHARDENING:
58
+ a_s = self._material.ElongationBreak.long if self._material.ElongationBreak else 0.12
59
+ eps_el = self.get_elastic_strain()
60
+ hardening_modulus = (self._material.STo - self._material.SYo) / (a_s - eps_el)
61
+
62
+ lines.extend([
63
+ "! Plasticity with isotropic hardening",
64
+ f"TB, PLASTIC, {self._material.MID}, 1, 1",
65
+ f"TBPT, 1, {self._material.SYo}, {hardening_modulus}"
66
+ ])
67
+
68
+ lines.append("/FINISH")
69
+ return "\n".join(lines)
70
+
71
+ def get_dyna_model(self) -> str:
72
+ if not self._material:
73
+ raise ValueError("Material not attached to stress-strain curve.")
74
+ lines = ["*KEYWORD"]
75
+ args = ["MID", "RO", "E", "PR"]
76
+ values = [self._material.MID, self._material.Density, self._material.EO, self._material.PoissonsRatio]
77
+
78
+ if self.Model == ModelConstants.Approaches.LINEARELASTIC:
79
+ lines.append("*MAT_ELASTIC")
80
+ else:
81
+ lines.append("*MAT_PIECEWISE_LINEAR_PLASTICITY")
82
+ args.extend(["SIGY", "ETAN", "FAIL", "TDEL"])
83
+ values.extend([self._material.SYo, 0.0, 0.0, 0.0])
84
+
85
+ lines.append("$ " + "\t".join(args))
86
+ lines.append("\t".join(map(str, values)))
87
+
88
+ if self.Model != ModelConstants.Approaches.LINEARELASTIC:
89
+ hardening_stress = self._material.STo if self.Model == ModelConstants.Approaches.LINEARHARDENING else self._material.SYo
90
+ lines.extend([
91
+ "$ Epsilon-Plastic Strain vs. Stress Data",
92
+ "$ EPS\tSIG",
93
+ f"0.0\t{self._material.SYo}",
94
+ f"{self._material.ElongationBreak}\t{hardening_stress}"
95
+ ])
96
+
97
+ lines.append("*END")
98
+ return "\n".join(lines)
99
+
100
+ def get_abaqus_model(self) -> str:
101
+ if not self._material:
102
+ raise ValueError("Material not attached to stress-strain curve.")
103
+ lines = [
104
+ f"*Material, NAME={self._material.Synopsis}",
105
+ "*DENSITY",
106
+ f"{self._material.Density}",
107
+ "*ELASTIC",
108
+ f"{self._material.EO}, {self._material.PoissonsRatio}"
109
+ ]
110
+
111
+ if self.Model != ModelConstants.Approaches.LINEARELASTIC:
112
+ lines.extend([
113
+ "*PLASTIC",
114
+ f"{self._material.SYo}, 0.0",
115
+ f"{self._material.STo}, {self._material.ElongationBreak}"
116
+ ])
117
+
118
+ return "\n".join(lines)
119
+
120
+ def _build_linear_elastic(self):
121
+ self._data = np.array([
122
+ [0, 0],
123
+ [self.yield_strength / self.youngs_modulus, self.yield_strength]
124
+ ])
125
+
126
+ def _build_perfect_plasticity(self):
127
+ self._data = np.array([
128
+ [0, 0],
129
+ [self.yield_strength / self.youngs_modulus, self.yield_strength],
130
+ [0.3, self.yield_strength]
131
+ ])
132
+
133
+ def _build_linear_plasticity(self):
134
+ self._data = np.array([
135
+ [0, 0],
136
+ [self.yield_strength / self.youngs_modulus, self.yield_strength],
137
+ [self.elongation_at_break, self.tensile_strength]
138
+ ])
139
+
140
+ def add_material_properties(self, *args):
141
+ self.youngs_modulus, self.yield_strength, self.tensile_strength, self.elongation_at_break = args
142
+
143
+ def reset(self):
144
+ self._data = np.zeros((2, 2))
145
+
146
+ def build(self):
147
+ try:
148
+ if self.Model == ModelConstants.Approaches.LINEARELASTIC:
149
+ self._build_linear_elastic()
150
+ elif self.Model == ModelConstants.Approaches.PERFECTPLASTICITY:
151
+ self._build_perfect_plasticity()
152
+ elif self.Model == ModelConstants.Approaches.LINEARHARDENING:
153
+ self._build_linear_plasticity()
154
+ except ZeroDivisionError:
155
+ logger.warning("Division by zero error!")
156
+ return
data/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ from typing import List
2
+ from pymats.data.loader import LoaderFactory, MaterialIndex
3
+
4
+
5
+ def get_mids(query: str | None = None) -> List[MaterialIndex]:
6
+ loader = LoaderFactory.get_loader()
7
+ return loader.get_identifiers(query=query)
data/collection.py ADDED
@@ -0,0 +1,100 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ from typing import List, Union, Set, Type
4
+ from sklearn.metrics.pairwise import cosine_similarity
5
+
6
+ from pydantic import PrivateAttr, computed_field, SerializeAsAny
7
+
8
+ from pymats.materials.asmematerial import ASMEMaterial
9
+ from pymats.materials.enmaterial import ENMaterial
10
+ from pymats.materials.materialbase import MaterialBase
11
+ from pymats.materials.constants import MaterialDataConstants
12
+ from pymats.data.loader import MaterialLoaderBase
13
+ from pymats import Material, MaterialFactory
14
+ from pymats.utils.log import logger
15
+ from pymats.config import settings
16
+ from pymats.utils.mbasemodel import MBaseModel
17
+
18
+ class MaterialCollection(MBaseModel):
19
+
20
+ CollectionName: str
21
+ IsCustomCollection: bool = False
22
+ materials: list[SerializeAsAny[MaterialBase]] = []
23
+
24
+ @classmethod
25
+ def load_from_db(
26
+ cls,
27
+ handler: MaterialLoaderBase,
28
+ collection_name: str,
29
+ identifiers: list[str] = [],
30
+ include_property_data: bool = True
31
+ ) -> "MaterialCollection":
32
+
33
+ is_custom = collection_name in settings.CustomCollectionNames
34
+
35
+ docs = handler.load_collection_materials(
36
+ collection_name,
37
+ material_names=identifiers,
38
+ include_property_data=include_property_data
39
+ )
40
+ materials = []
41
+ for doc in docs.values():
42
+ try:
43
+ materials.append(
44
+ MaterialFactory.from_data(doc, init_mech_properties=False)
45
+ )
46
+ except Exception as ex:
47
+ name = doc.get('Name', 'none')
48
+ logger.warning(f'Failed to load material {str(name)} for collection {str(collection_name)} with {str(ex)}')
49
+ continue
50
+ return cls(CollectionName=collection_name, IsCustomCollection=is_custom, materials=materials)
51
+
52
+ @computed_field(return_type=list[str])
53
+ @property
54
+ def Keys(self):
55
+ if len(self.materials) > 0:
56
+ class_t = self.materials[0].__class__
57
+ else:
58
+ class_t = Material
59
+ return list(class_t.model_fields.keys())
60
+
61
+
62
+ def is_standard_collection(self) -> bool:
63
+ """
64
+ Check if the collection is a standard collection (ASME, EN).
65
+
66
+ Returns:
67
+ bool: True if the collection is a standard collection, False otherwise.
68
+ """
69
+ return self.CollectionName in MaterialDataConstants.Collections.__members__.values()
70
+
71
+ def find_equivalents(self, material: MaterialBase, tolerance: float = 0.0):
72
+ master = material.tokenize(20 ,0)
73
+ for mat in self.materials:
74
+ try:
75
+ if mat.Identifier == material.Identifier:
76
+ continue
77
+ candidate = mat.tokenize(20, 0)
78
+ sim = cosine_similarity(master.reshape(1, -1), candidate.reshape(1, -1))
79
+ # euclidean = np.linalg.norm(master.reshape(1, -1) - candidate.reshape(1, -1))
80
+ if np.max(sim) >= tolerance:
81
+ yield (mat, np.max(sim))
82
+ except Exception as e:
83
+ logger.error(f"Failed to compare materials: {e}")
84
+ continue
85
+
86
+ def get_equivalents(self, material: MaterialBase, tolerance: float = 0.0):
87
+ equivalents = []
88
+ for mat in self.find_equivalents(material, tolerance):
89
+ material, sim = mat
90
+ equivalents.append({
91
+ "material": material.Synopsis,
92
+ "standard": material.Standards,
93
+ "score": sim
94
+ })
95
+ df = pd.DataFrame(equivalents)
96
+ df.sort_values(by="score", ascending=False, inplace=True)
97
+ df.reset_index(drop=True, inplace=True)
98
+ df = df.iloc[:10, :]
99
+ return df.to_dict(orient="records")
100
+