diff --git a/_unittest/example_models/syslib/Materials.amat b/_unittest/example_models/syslib/Materials.amat new file mode 100644 index 00000000000..a688590e6d9 --- /dev/null +++ b/_unittest/example_models/syslib/Materials.amat @@ -0,0 +1,123 @@ +$begin '$base_index$' + $begin 'properties' + all_levels=000000000000 + time(year=000000002021, month=000000000009, day=000000000001, hour=000000000015, min=000000000032, sec=000000000052) + version=000000000000 + $end 'properties' + $begin '$base_index$' + $index$(pos=000000318773, lin=000000011311, lvl=000000000000) + $end '$base_index$' +$end '$base_index$' +$begin 'FC-78' + $begin 'MaterialDef' + $begin 'FC-78' + CoordinateSystemType='Cartesian' + BulkOrSurfaceType=1 + $begin 'PhysicsTypes' + set('Thermal') + $end 'PhysicsTypes' + $begin 'AttachedData' + $begin 'MatAppearanceData' + property_data='appearance_data' + Red=0 + Green=128 + Blue=255 + Transparency=0.8 + $end 'MatAppearanceData' + $end 'AttachedData' + thermal_conductivity='0.062' + mass_density='1700' + specific_heat='1050' + thermal_expansion_coeffcient='0.0016' + $begin 'thermal_material_type' + property_type='ChoiceProperty' + Choice='Fluid' + $end 'thermal_material_type' + $begin 'clarity_type' + property_type='ChoiceProperty' + Choice='Transparent' + $end 'clarity_type' + molecular_mass='0.001' + viscosity='0.000462' + ModTime=1592011950 + $end 'FC-78' + $end 'MaterialDef' +$end 'FC-78' +$begin 'Polyflon CuFlon (tm)' + $begin 'AttachedData' + $begin 'MatAppearanceData' + property_data='appearance_data' + Red=230 + Green=225 + Blue=220 + $end 'MatAppearanceData' + $end 'AttachedData' + simple('permittivity', 2.1) + simple('dielectric_loss_tangent', 0.00045) + ModTime=1499970477 +$end 'Polyflon CuFlon (tm)' +$begin 'Water(@360K)' + $begin 'MaterialDef' + $begin 'Water(@360K)' + CoordinateSystemType='Cartesian' + BulkOrSurfaceType=1 + $begin 'PhysicsTypes' + set('Thermal') + $end 'PhysicsTypes' + $begin 'AttachedData' + $begin 'MatAppearanceData' + property_data='appearance_data' + Red=0 + Green=128 + Blue=255 + Transparency=0.8 + $end 'MatAppearanceData' + $end 'AttachedData' + thermal_conductivity='0.6743' + mass_density='967.4' + specific_heat='4206' + thermal_expansion_coeffcient='0.0006979' + $begin 'thermal_material_type' + property_type='ChoiceProperty' + Choice='Fluid' + $end 'thermal_material_type' + $begin 'clarity_type' + property_type='ChoiceProperty' + Choice='Transparent' + $end 'clarity_type' + material_refractive_index='1.333' + diffusivity='1.657e-007' + molecular_mass='0.018015' + viscosity='0.000324' + ModTime=1592011950 + $end 'Water(@360K)' + $end 'MaterialDef' +$end 'Water(@360K)' +$begin 'steel_stainless' + $begin 'AttachedData' + $begin 'MatAppearanceData' + property_data='appearance_data' + Red=176 + Green=154 + Blue=176 + $end 'MatAppearanceData' + $end 'AttachedData' + simple('conductivity', 1100000) + simple('thermal_conductivity', 13.8) + simple('mass_density', 8055) + simple('specific_heat', 480) + simple('youngs_modulus', 195000000000) + simple('poissons_ratio', 0.3) + simple('thermal_expansion_coeffcient', 1.08e-005) + ModTime=1499970477 +$end 'steel_stainless' +$begin '$index$' + $begin '$index$' + steel_stainless(pos=1224, lin=48, lvl=0) + 'Polyflon CuFlon (tm)'(pos=22164, lin=814, lvl=0) + 'FC-78'(pos=126130, lin=4842, lvl=0) + 'Water(@360K)'(pos=118115, lin=4556, lvl=0) + $base_index$(pos=0, lin=1, lvl=0) + $index$(pos=318773, lin=11311, lvl=0) + $end '$index$' +$end '$index$' diff --git a/_unittest/test_00_EDB.py b/_unittest/test_00_EDB.py index b50a3c43315..82877a0249a 100644 --- a/_unittest/test_00_EDB.py +++ b/_unittest/test_00_EDB.py @@ -1,10 +1,15 @@ +import builtins import json import os # Setup paths for module imports # Import required modules import sys +from unittest.mock import mock_open +from mock import MagicMock +from mock import PropertyMock +from mock import patch import pytest from pyaedt import Edb @@ -12,6 +17,7 @@ from pyaedt.edb_core.edb_data.edbvalue import EdbValue from pyaedt.edb_core.edb_data.simulation_configuration import SimulationConfiguration from pyaedt.edb_core.edb_data.sources import Source +from pyaedt.edb_core.materials import Materials from pyaedt.generic.constants import RadiationBoxType from pyaedt.generic.general_methods import check_numeric_equivalence @@ -33,6 +39,59 @@ test_subfolder = "TEDB" +MATERIALS = """ +$begin 'Polyflon CuFlon (tm)' + $begin 'AttachedData' + $begin 'MatAppearanceData' + property_data='appearance_data' + Red=230 + Green=225 + Blue=220 + $end 'MatAppearanceData' + $end 'AttachedData' + simple('permittivity', 2.1) + simple('dielectric_loss_tangent', 0.00045) + ModTime=1499970477 +$end 'Polyflon CuFlon (tm)' +$begin 'Water(@360K)' + $begin 'MaterialDef' + $begin 'Water(@360K)' + CoordinateSystemType='Cartesian' + BulkOrSurfaceType=1 + $begin 'PhysicsTypes' + set('Thermal') + $end 'PhysicsTypes' + $begin 'AttachedData' + $begin 'MatAppearanceData' + property_data='appearance_data' + Red=0 + Green=128 + Blue=255 + Transparency=0.8 + $end 'MatAppearanceData' + $end 'AttachedData' + thermal_conductivity='0.6743' + mass_density='967.4' + specific_heat='4206' + thermal_expansion_coeffcient='0.0006979' + $begin 'thermal_material_type' + property_type='ChoiceProperty' + Choice='Fluid' + $end 'thermal_material_type' + $begin 'clarity_type' + property_type='ChoiceProperty' + Choice='Transparent' + $end 'clarity_type' + material_refractive_index='1.333' + diffusivity='1.657e-007' + molecular_mass='0.018015' + viscosity='0.000324' + ModTime=1592011950 + $end 'Water(@360K)' + $end 'MaterialDef' +$end 'Water(@360K)' +""" + @pytest.fixture(scope="class") def edbapp(add_edb): @@ -2954,14 +3013,57 @@ def test_147_find_dc_shorts(self): assert len(edbapp.nets["DDR4_DM3"].find_dc_short()) == 0 edbapp.close() - def test_148_load_amat(self): - assert "Rogers RO3003 (tm)" in self.edbapp.materials.materials_in_aedt - material_file = os.path.join(self.edbapp.materials.syslib, "Materials.amat") - assert self.edbapp.materials.add_material_from_aedt("Arnold_Magnetics_N28AH_-40C") - assert "Arnold_Magnetics_N28AH_-40C" in self.edbapp.materials.materials.keys() - assert self.edbapp.materials.load_amat(material_file) + @patch("pyaedt.edb_core.materials.Materials.materials", new_callable=PropertyMock) + @patch.object(builtins, "open", new_callable=mock_open, read_data=MATERIALS) + def test_149_materials_read_materials(self, mock_file_open, mock_materials_property): + """Read materials from an AMAT file.""" + mock_materials_property.return_value = ["copper"] + materials = Materials(MagicMock()) + expected_res = { + "Polyflon CuFlon (tm)": {"permittivity": 2.1, "tangent_delta": 0.00045}, + "Water(@360K)": { + "thermal_conductivity": 0.6743, + "mass_density": 967.4, + "specific_heat": 4206, + "thermal_expansion_coeffcient": 0.0006979, + }, + } + mats = materials.read_materials("some path") + assert mats == expected_res + + def test_150_material_load_amat(self): + """Load material from an AMAT file.""" + mat_file = os.path.join(self.edbapp.base_path, "syslib", "Materials.amat") + assert self.edbapp.materials.load_amat(mat_file) material_list = list(self.edbapp.materials.materials.keys()) assert material_list assert len(material_list) > 0 assert self.edbapp.materials.materials["Rogers RO3003 (tm)"].loss_tangent == 0.0013 assert self.edbapp.materials.materials["Rogers RO3003 (tm)"].permittivity == 3.0 + + def test_151_materials_read_materials(self): + """Read materials.""" + path = os.path.join(local_path, "example_models", "syslib", "Materials.amat") + mats = self.edbapp.materials.read_materials(path) + key = "FC-78" + assert key in mats + assert mats[key]["thermal_conductivity"] == 0.062 + assert mats[key]["mass_density"] == 1700 + assert mats[key]["specific_heat"] == 1050 + assert mats[key]["thermal_expansion_coeffcient"] == 0.0016 + key = "Polyflon CuFlon (tm)" + assert key in mats + assert mats[key]["permittivity"] == 2.1 + assert mats[key]["tangent_delta"] == 0.00045 + key = "Water(@360K)" + assert key in mats + assert mats[key]["thermal_conductivity"] == 0.6743 + assert mats[key]["mass_density"] == 967.4 + assert mats[key]["specific_heat"] == 4206 + assert mats[key]["thermal_expansion_coeffcient"] == 0.0006979 + key = "steel_stainless" + assert mats[key]["conductivity"] == 1100000 + assert mats[key]["thermal_conductivity"] == 13.8 + assert mats[key]["mass_density"] == 8055 + assert mats[key]["specific_heat"] == 480 + assert mats[key]["thermal_expansion_coeffcient"] == 1.08e-005 diff --git a/doc/source/Getting_started/Troubleshooting.rst b/doc/source/Getting_started/Troubleshooting.rst index e50ad5b5598..d5fc1592811 100644 --- a/doc/source/Getting_started/Troubleshooting.rst +++ b/doc/source/Getting_started/Troubleshooting.rst @@ -135,11 +135,12 @@ Failure connecting to the gRPC server On Linux, PyAEDT may fail to initialize a new instance of the gRPC server or connect to an existing server session. This may be due to: - - Firewall - - Proxy - - Permissions - - License - - Scheduler (for example if the gRPC server was started from LSF or Slurm) + +- Firewall +- Proxy +- Permissions +- License +- Scheduler (for example if the gRPC server was started from LSF or Slurm) For issues related to use of a proxy server, you may set the following environment variable to disable the proxy server for the *localhost*. diff --git a/doc/source/conf.py b/doc/source/conf.py index 11b180ef38e..02f4d12852b 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -318,6 +318,7 @@ def setup(app): # specify the location of your github repo html_theme_options = { "github_url": "https://github.com/ansys/pyaedt", + "navigation_with_keys": False, "show_prev_next": False, "show_breadcrumbs": True, "collapse_navigation": True, diff --git a/pyaedt/edb_core/materials.py b/pyaedt/edb_core/materials.py index 154ef5d5d4b..180787bac4c 100644 --- a/pyaedt/edb_core/materials.py +++ b/pyaedt/edb_core/materials.py @@ -1,9 +1,9 @@ from __future__ import absolute_import # noreorder import difflib -import fnmatch import logging import os +import re import warnings from pyaedt import is_ironpython @@ -368,7 +368,6 @@ def __getitem__(self, item): def __init__(self, pedb): self._pedb = pedb self._syslib = os.path.join(self._pedb.base_path, "syslib") - self._personal_lib = None self._materials_in_aedt = None if not self.materials: self.add_material("air") @@ -381,7 +380,7 @@ def materials_in_aedt(self): """Retrieve the dictionary of materials available in AEDT syslib.""" if self._materials_in_aedt: return self._materials_in_aedt - self._materials_in_aedt = self._read_materials() + self._materials_in_aedt = self.read_materials(os.path.join(self._syslib, "Materials.amat")) return self._materials_in_aedt @property @@ -389,16 +388,6 @@ def syslib(self): """Retrieve the project sys library.""" return self._syslib - @property - def personallib(self): - """Get or Set the user personallib.""" - return self._personal_lib - - @personallib.setter - def personallib(self, value): - self._personal_lib = value - self._materials_in_aedt = self._read_materials() - @pyaedt_function_handler() def _edb_value(self, value): return self._pedb.edb_value(value) @@ -838,7 +827,7 @@ def get_property_by_material_name(self, property_name, material_name): @pyaedt_function_handler() def add_material_from_aedt(self, material_name): - """Add a material read from ``syslib amat`` library. + """Add a material read from a ``syslib AMAT`` library. Parameters ---------- @@ -856,116 +845,137 @@ def add_material_from_aedt(self, material_name): return False new_material = self.add_material(name=material_name) material = self.materials_in_aedt[material_name] - try: - new_material.permittivity = float(material["permittivity"]) - except (KeyError, TypeError): - pass - try: - new_material.conductivity = float(material["conductivity"]) - except (KeyError, TypeError): - pass - try: - new_material.mass_density = float(material["mass_density"]) - except (KeyError, TypeError): - pass - try: - new_material.permeability = float(material["permeability"]) - except (KeyError, TypeError): - pass - try: - new_material.loss_tangent = float(material["dielectric_loss_tangent"]) - except (KeyError, TypeError): - pass - try: - new_material.specific_heat = float(material["specific_heat"]) - except (KeyError, TypeError): - pass - try: - new_material.thermal_expansion_coefficient = float(material["thermal_expansion_coeffcient"]) - except (KeyError, TypeError): - pass + properties = [ + "permittivity", + "conductivity", + "mass_density", + "permeability", + "specific_heat", + "thermal_expansion_coefficient", + ] + for mat_prop_name, mat_prop_value in material.items(): + if mat_prop_name in properties: + setattr(new_material, mat_prop_name, mat_prop_value) + if mat_prop_name == "dielectric_loss_tangent": + new_material.loss_tangent = mat_prop_value return True @pyaedt_function_handler() def load_amat(self, amat_file): - """Load material from an amat file and add materials to Edb. + """Load materials from an AMAT file. Parameters ---------- amat_file : str - Full path to the amat file to read and add to the Edb. + Full path to the AMAT file to read and add to the Edb. + + Returns + ------- + bool """ - material_dict = self._read_materials(amat_file) + if not os.path.exists(amat_file): + self._pedb.logger.error("File path {} does not exist.".format(amat_file)) + material_dict = self.read_materials(amat_file) for material_name, material in material_dict.items(): if not material_name in list(self.materials.keys()): new_material = self.add_material(name=material_name) - try: - new_material.permittivity = float(material["permittivity"]) - except (KeyError, TypeError): - pass - try: - new_material.conductivity = float(material["conductivity"]) - except (KeyError, TypeError): - pass - try: - new_material.mass_density = float(material["mass_density"]) - except (KeyError, TypeError): - pass - try: - new_material.permeability = float(material["permeability"]) - except (KeyError, TypeError): - pass - try: - new_material.loss_tangent = float(material["dielectric_loss_tangent"]) - except (KeyError, TypeError): - pass - try: - new_material.specific_heat = float(material["specific_heat"]) - except (KeyError, TypeError): - pass - try: - new_material.thermal_expansion_coefficient = float(material["thermal_expansion_coeffcient"]) - except (KeyError, TypeError): - pass + properties = [ + "permittivity", + "conductivity", + "mass_density", + "permeability", + "specific_heat", + "thermal_expansion_coefficient", + ] + for mat_prop_name, mat_prop_value in material.items(): + if mat_prop_name in properties: + setattr(new_material, mat_prop_name, mat_prop_value) + if mat_prop_name == "tangent_delta": + new_material.loss_tangent = mat_prop_value return True + @staticmethod @pyaedt_function_handler() - def _read_materials(self, mat_file=None): - def get_mat_list(file_name, mats): - from pyaedt.generic.LoadAEDTFile import load_entire_aedt_file - - mread = load_entire_aedt_file(file_name) - for mat, mdict in mread.items(): - if mat != "$base_index$": - try: - mats[mat] = mdict["MaterialDef"][mat] - except KeyError: - mats[mat] = mdict - - if mat_file and os.path.exists(mat_file): - materials = {} - get_mat_list(mat_file, materials) - return materials - - amat_sys = [ - os.path.join(dirpath, filename) - for dirpath, _, filenames in os.walk(self.syslib) - for filename in filenames - if fnmatch.fnmatch(filename, "*.amat") + def read_materials(amat_file): + """Read materials from an AMAT file. + + Parameters + ---------- + amat_file : str + Full path to the AMAT file to read. + + Returns + ------- + dict + {material name: dict of material property to value}. + """ + + def get_line_float_value(line): + """Retrieve the float value expected in the line of an AMAT file. + The associated string is expected to follow one of the following cases: + - simple('permittivity', 12.) + - permittivity='12'. + """ + try: + return float(re.split(",|=", line)[-1].strip(")'")) + except ValueError: + return None + + res = {} + _begin_search = re.compile(r"^\$begin '(.+)'") + _end_search = re.compile(r"^\$end '(.+)'") + amat_keys = [ + "thermal_conductivity", + "permittivity", + "dielectric_loss_tangent", + "permeability", + "magnetic_loss_tangent", + "thermal_expansion_coeffcient", + "specific_heat", + "mass_density", ] - amat_personal = [] - if self.personallib: - amat_personal = [ - os.path.join(dirpath, filename) - for dirpath, _, filenames in os.walk(self.personallib) - for filename in filenames - if fnmatch.fnmatch(filename, "*.amat") - ] - materials = {} - for amat in amat_sys: - get_mat_list(amat, materials) - - if amat_personal: - for amat in amat_personal: - get_mat_list(amat, materials) - return materials + keys = [ + "thermal_conductivity", + "permittivity", + "tangent_delta", + "permeability", + "magnetic_loss_tangent", + "thermal_expansion_coeffcient", + "specific_heat", + "mass_density", + ] + + with open(amat_file, "r") as amat_fh: + raw_lines = amat_fh.read().splitlines() + mat_found = "" + for line in raw_lines: + b = _begin_search.search(line) + if b: # walk down a level + mat_found = b.group(1) + res.setdefault(mat_found, {}) + if mat_found: + for amat_key, key in zip(amat_keys, keys): + if amat_key in line: + value = get_line_float_value(line) + if value is not None: + res[mat_found][key] = value + # Extra case to avoid confusion ("conductivity" is included in "thermal_conductivity") + if "conductivity" in line and "thermal_conductivity" not in line: + value = get_line_float_value(line) + if value is not None: + res[mat_found]["conductivity"] = value + end = _end_search.search(line) + if end: + mat_found = "" + + # Clean unwanted data + try: + del res["$index$"] + except KeyError: + pass + try: + del res["$base_index$"] + except KeyError: + pass + + return res diff --git a/pyaedt/generic/LoadAEDTFile.py b/pyaedt/generic/LoadAEDTFile.py index 7c92cf447f0..fb669773701 100644 --- a/pyaedt/generic/LoadAEDTFile.py +++ b/pyaedt/generic/LoadAEDTFile.py @@ -155,7 +155,7 @@ def _decode_recognized_subkeys(sk, d): if m and m.group("SKEY1") == "simple": # extra verification. SKEY2 is with spaces, so it's not considered here. elems = _separate_list_elements(m.group("LIST1")) if elems[0] == "thermal_expansion_coeffcient": - elems[0] = "thermal_expansion_coefficient" # fix a typo in the amat files. AEDT supports both strings! + elems[0] = "thermal_expansion_coefficient" # fix a typo in the AMAT files. AEDT supports both strings! d[elems[0]] = str(elems[1]) # convert to string as it is dedicated to material props return True elif re.search(r"^\w+IDMap\(.*\)$", sk, re.IGNORECASE): # check if the format is AAKeyIDMap('10'=56802, '7'=56803) diff --git a/pyproject.toml b/pyproject.toml index 1bdb802b20a..6bd31d0a1f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,7 @@ tests = [ "matplotlib==3.8.0; python_version > '3.8'", "numpy==1.21.6; python_version <= '3.9'", "numpy==1.26.0; python_version > '3.9'", + "mock", "openpyxl==3.1.2", "osmnx", "pandas==1.3.5; python_version == '3.7'",