Skip to content

Commit 7e2b950

Browse files
author
Mohamed Koubaa
committed
Write units to matml
1 parent 940612b commit 7e2b950

File tree

4 files changed

+215
-18
lines changed

4 files changed

+215
-18
lines changed

src/ansys/materials/manager/_models/_common/constant.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121
# SOFTWARE.
2222

23-
from typing import Any, List, Tuple
23+
from typing import Any, List, Optional, Tuple, Union
24+
25+
from ansys.units import Quantity
2426

2527
from ansys.materials.manager._models._common._base import _BaseModel, _FluentCore, _MapdlCore
2628
from ansys.materials.manager._models._common._exceptions import ModelValidationException
@@ -43,8 +45,9 @@ class Constant(_BaseModel):
4345
applicable_packages: SupportedPackage.MAPDL | SupportedPackage.FLUENT
4446
_name: str
4547
_value: float
48+
_unit: Optional[str]
4649

47-
def __init__(self, name: str, value: float) -> None:
50+
def __init__(self, name: str, value: Union[float, Quantity]) -> None:
4851
"""
4952
Create a constant property value.
5053
@@ -55,11 +58,16 @@ def __init__(self, name: str, value: float) -> None:
5558
----------
5659
name: str
5760
Name of the property to model as a constant.
58-
value: float
59-
Value of the constant property.
61+
value: Union[float, Quantity]
62+
Value of the constant property or a quantity if a unit is attached
6063
"""
6164
self._name = name
62-
self._value = value
65+
if isinstance(value, float):
66+
self._value = value
67+
self._unit = None
68+
elif isinstance(value, Quantity):
69+
self._value = value.value
70+
self._unit = value.unit
6371

6472
@property
6573
def name(self) -> str:
@@ -75,6 +83,22 @@ def value(self) -> float:
7583
def value(self, value: float) -> None:
7684
self._value = value
7785

86+
@property
87+
def unit(self) -> Optional[str]:
88+
"""Optional unit of the constant."""
89+
return self._unit
90+
91+
@unit.setter
92+
def unit(self, value: Optional[str]) -> None:
93+
self._unit = value
94+
95+
def __repr__(self) -> str:
96+
"""Get the string representation of the constant."""
97+
unit = self.unit
98+
if unit is None:
99+
return f"Constant('{self.name}', {self.value})"
100+
return f"Constant('{self.name}', {self.value}, '{self.unit}')"
101+
78102
def write_model(self, material: "Material", pyansys_session: Any) -> None:
79103
"""
80104
Write this model to MAPDL.
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Copyright (C) 2022 - 2025 ANSYS, Inc. and/or its affiliates.
2+
# SPDX-License-Identifier: MIT
3+
#
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in all
13+
# copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
23+
from dataclasses import dataclass
24+
from typing import Dict, Optional
25+
26+
27+
@dataclass
28+
class PropertyInfo:
29+
"""MatML property information."""
30+
31+
"""The unit category of the material property.
32+
33+
For unitless properties, this will be None
34+
For properties with units, this can be defined but is not strictly necessary.
35+
"""
36+
unit_category: Optional[str]
37+
38+
39+
property_infos: Dict[str, PropertyInfo] = {
40+
"young's modulus x direction": PropertyInfo("Stress"),
41+
"young's modulus y direction": PropertyInfo("Stress"),
42+
"young's modulus z direction": PropertyInfo("Stress"),
43+
"secant thermal expansion coefficient x direction": PropertyInfo(None),
44+
"secant thermal expansion coefficient y direction": PropertyInfo(None),
45+
"secant thermal expansion coefficient z direction": PropertyInfo(None),
46+
"instantaneous thermal expansion coefficient x direction": PropertyInfo(None),
47+
"instantaneous thermal expansion coefficient y direction": PropertyInfo(None),
48+
"instantaneous thermal expansion coefficient z direction": PropertyInfo(None),
49+
"strain reference temperature": PropertyInfo("Temperature"),
50+
"poisson's ratio xy": PropertyInfo(None),
51+
"poisson's ratio yz": PropertyInfo(None),
52+
"poisson's ratio xz": PropertyInfo(None),
53+
"shear modulus xy": PropertyInfo("Stress"),
54+
"shear modulus yz": PropertyInfo("Stress"),
55+
"shear modulus xz": PropertyInfo("Stress"),
56+
"coefficient of friction": PropertyInfo(None),
57+
"density": PropertyInfo("Density"),
58+
"specific heat capacity": PropertyInfo("Specific Heat Capacity"),
59+
"enthalpy": PropertyInfo("MAPDL Enthalpy"),
60+
"thermal conductivity x direction": PropertyInfo("Thermal Conductivity"),
61+
"thermal conductivity y direction": PropertyInfo("Thermal Conductivity"),
62+
"thermal conductivity z direction": PropertyInfo("Thermal Conductivity"),
63+
"convection coefficient": PropertyInfo(None),
64+
"emissivity": PropertyInfo(None),
65+
"heat generation rate": PropertyInfo(None),
66+
"viscosity": PropertyInfo("Dynamic Viscosity"),
67+
"speed of sound": PropertyInfo("Velocity"),
68+
"electrical resistivity x direction": PropertyInfo("Electrical Resistivity"),
69+
"electrical resistivity y direction": PropertyInfo("Electrical Resistivity"),
70+
"electrical resistivity z direction": PropertyInfo("Electrical Resistivity"),
71+
"electric relative permittivity x direction": PropertyInfo(None),
72+
"electric relative permittivity y direction": PropertyInfo(None),
73+
"electric relative permittivity z direction": PropertyInfo(None),
74+
"magnetic relative permeability x direction": PropertyInfo(None),
75+
"magnetic relative permeability y direction": PropertyInfo(None),
76+
"magnetic relative permeability z direction": PropertyInfo(None),
77+
"magnetic coercive force x direction": PropertyInfo(None),
78+
"magnetic coercive force y direction": PropertyInfo(None),
79+
"magnetic coercive force z direction": PropertyInfo(None),
80+
"dielectric loss tangent": PropertyInfo(None),
81+
"seebeck coefficient x direction": PropertyInfo(None),
82+
"seebeck coefficient y direction": PropertyInfo(None),
83+
"seebeck coefficient z direction": PropertyInfo(None),
84+
}

src/ansys/materials/manager/util/matml/matml_from_material.py

Lines changed: 97 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,29 @@
2222

2323
"""Provides the ``MatmlWriter`` class."""
2424

25+
from dataclasses import dataclass
2526
import os
2627
from typing import BinaryIO, Dict, Optional, Sequence, Union
2728
import xml.etree.ElementTree as ET
2829

30+
import ansys.units
31+
32+
from ansys.materials.manager._models._matml.property_info import (
33+
property_infos as matml_property_infos,
34+
)
2935
from ansys.materials.manager.material import Material
3036

3137
from .matml_parser import (
3238
BULKDATA_KEY,
3339
MATERIALS_ELEMENT_KEY,
3440
MATML_DOC_KEY,
3541
METADATA_KEY,
42+
NAME_ATTR,
43+
NAME_KEY,
44+
POWER_ATTR,
45+
UNIT_KEY,
3646
UNITLESS_KEY,
47+
UNITS_KEY,
3748
WBTRANSFER_KEY,
3849
)
3950
from .matml_property_map import MATML_PROPERTY_MAP
@@ -45,6 +56,19 @@
4556
VERSION_DATE = "29.08.2016 15:02:00"
4657

4758

59+
@dataclass
60+
class MetadataParameter:
61+
"""Name of the parameter, for example pa74."""
62+
63+
name: str
64+
65+
"""The material model name."""
66+
model_name: str
67+
68+
"""The parameter value unit."""
69+
unit: Optional[str]
70+
71+
4872
class MatmlWriter:
4973
"""
5074
Exports a list of MAPDL materials to an engineering data XML file.
@@ -57,7 +81,7 @@ class MatmlWriter:
5781

5882
_materials: Sequence[Material]
5983
_metadata_property_sets: Dict
60-
_metadata_parameters: Dict
84+
_metadata_parameters: Dict[str, MetadataParameter]
6185

6286
def __init__(self, materials: Sequence[Material]):
6387
"""Construct a Matml writer."""
@@ -68,18 +92,22 @@ def __init__(self, materials: Sequence[Material]):
6892
def _add_parameters(self, property_element: ET.Element, material: Material, parameters: Dict):
6993
# add the parameters of a property set to the tree
7094
for mat_key, matml_key in parameters.items():
95+
model = material.get_model_by_name(mat_key)
96+
assert len(model) == 1
97+
model = model[0]
98+
7199
if matml_key in self._metadata_parameters.keys():
72100
para_key = self._metadata_parameters[matml_key]
73101
else:
74102
index = len(self._metadata_parameters) + 1
75-
para_key = f"pa{index}"
103+
para_key = MetadataParameter(f"pa{index}", model.name, model.unit)
76104
self._metadata_parameters[matml_key] = para_key
77105

78106
param_element = ET.SubElement(
79-
property_element, "ParameterValue", {"format": "float", "parameter": para_key}
107+
property_element, "ParameterValue", {"format": "float", "parameter": para_key.name}
80108
)
81109
data_element = ET.SubElement(param_element, "Data")
82-
data_element.text = str(material.get_model_by_name(mat_key)[0].value)
110+
data_element.text = str(model.value)
83111
qualifier_element = ET.SubElement(param_element, "Qualifier", {"name": "Variable Type"})
84112
qualifier_element.text = "Dependent"
85113

@@ -95,6 +123,7 @@ def _add_property_set(
95123
# check if at least one parameter is specified (case-insensitive)
96124
# and build a map from material to Matml properties
97125
available_mat_properties = [model.name.lower() for model in material.models]
126+
# print(available_mat_properties)
98127
property_set_parameters = {item: item for item in parameter_map["properties"]}
99128
for key, mapped_properties in parameter_map["mappings"].items():
100129
property_set_parameters.update({item: key for item in mapped_properties})
@@ -157,7 +186,53 @@ def _add_materials(self, materials_element: ET.Element):
157186
behavior,
158187
)
159188

189+
def _write_unit(self, prop_element: ET.Element, value: MetadataParameter):
190+
"""Write unit in one of the following valid forms.
191+
192+
If there is a unit:
193+
<Units name="Density">
194+
<Unit>
195+
<Name>kg</Name>
196+
</Unit>
197+
<Unit power="-3">
198+
<Name>m</Name>
199+
</Unit>
200+
</Units>
201+
202+
If there is a unit for a property with no unit category defined:
203+
<Units>
204+
<Unit>
205+
<Name>kg</Name>
206+
</Unit>
207+
<Unit power="-3">
208+
<Name>m</Name>
209+
</Unit>
210+
</Units>
211+
If there is no unit:
212+
<Unitless />
213+
"""
214+
if value.unit is None:
215+
ET.SubElement(prop_element, UNITLESS_KEY)
216+
else:
217+
unit_tokens = value.unit.split(" ")
218+
matml_property_info = matml_property_infos[value.model_name]
219+
unit_category = matml_property_info.unit_category
220+
unit_name_dict = {}
221+
if unit_category is not None:
222+
unit_name_dict[NAME_ATTR] = unit_category
223+
units_element = ET.SubElement(prop_element, UNITS_KEY, unit_name_dict)
224+
for unit_token in unit_tokens:
225+
_, term, exp = ansys.units.unit._filter_unit_term(unit_token)
226+
power = int(exp)
227+
power_dict = {}
228+
if power != 1:
229+
power_dict[POWER_ATTR] = str(power)
230+
unit_element = ET.SubElement(units_element, UNIT_KEY, power_dict)
231+
unit_name_element = ET.SubElement(unit_element, NAME_KEY)
232+
unit_name_element.text = term
233+
160234
def _add_metadata(self, metadata_element: ET.Element):
235+
161236
# add the metadata to the XML tree
162237
for key, value in self._metadata_property_sets.items():
163238
prop_element = ET.SubElement(metadata_element, "PropertyDetails", {"id": value})
@@ -166,23 +241,32 @@ def _add_metadata(self, metadata_element: ET.Element):
166241
name_element.text = key
167242

168243
for key, value in self._metadata_parameters.items():
169-
prop_element = ET.SubElement(metadata_element, "ParameterDetails", {"id": value})
170-
ET.SubElement(prop_element, UNITLESS_KEY)
244+
prop_element = ET.SubElement(metadata_element, "ParameterDetails", {"id": value.name})
245+
171246
name_element = ET.SubElement(prop_element, "Name")
172247
name_element.text = key
173248

249+
self._write_unit(prop_element, value)
250+
174251
def _add_transfer_ids(self, root: ET.Element) -> None:
175252
# add the WB transfer IDs to the XML tree
176253
wb_transfer_element = ET.SubElement(root, WBTRANSFER_KEY)
177254
materials_element = ET.SubElement(wb_transfer_element, MATERIALS_ELEMENT_KEY)
255+
any_uuid = False
178256
for mat in self._materials:
179-
mat_element = ET.SubElement(materials_element, "Material")
180-
name_element = ET.SubElement(mat_element, "Name")
181-
name_element.text = mat.name
182-
transfer_element = ET.SubElement(mat_element, "DataTransferID")
183-
transfer_element.text = mat.uuid
184-
185-
def _to_etree(self) -> ET.ElementTree:
257+
if len(mat.uuid) > 0:
258+
any_uuid = True
259+
mat_element = ET.SubElement(materials_element, "Material")
260+
name_element = ET.SubElement(mat_element, "Name")
261+
name_element.text = mat.name
262+
transfer_element = ET.SubElement(mat_element, "DataTransferID")
263+
transfer_element.text = mat.uuid
264+
if not any_uuid:
265+
root.remove(wb_transfer_element)
266+
267+
def _to_etree(
268+
self,
269+
) -> ET.ElementTree:
186270
root = ET.Element(ROOT_ELEMENT)
187271
tree = ET.ElementTree(root)
188272

src/ansys/materials/manager/util/matml/matml_parser.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@
3434
MATML_DOC_KEY = "MatML_Doc"
3535
METADATA_KEY = "Metadata"
3636
BULKDATA_KEY = "BulkDetails"
37+
NAME_ATTR = "name"
38+
NAME_KEY = "Name"
39+
POWER_ATTR = "power"
40+
UNIT_KEY = "Unit"
41+
UNITS_KEY = "Units"
3742
UNITLESS_KEY = "Unitless"
3843
BEHAVIOR_KEY = "Behavior"
3944
WBTRANSFER_KEY = "ANSYSWBTransferData"

0 commit comments

Comments
 (0)