diff --git a/doc/source/changelog/14.test.md b/doc/source/changelog/14.test.md new file mode 100644 index 0000000..7050acc --- /dev/null +++ b/doc/source/changelog/14.test.md @@ -0,0 +1 @@ +PyLumerical Integration Testing diff --git a/tests/conftest.py b/tests/conftest.py index 203df09..d7fb4af 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -68,3 +68,23 @@ def setup_fdtd_with_groups(): yield fdtd print("\n--> Teardown") fdtd.close() + + +@pytest.fixture(scope="module") +def setup_device(): + """Set up and tear down DEVICE.""" + print("\n--> Setup") + device = lumapi.DEVICE(hide=True) + yield device + print("\n--> Teardown") + device.close() + + +@pytest.fixture(scope="module") +def setup_mode(): + """Set up and tear down MODE.""" + print("\n--> Setup") + mode = lumapi.MODE(hide=True) + yield mode + print("\n--> Teardown") + mode.close() diff --git a/tests/integration/test_device_object_property_access.py b/tests/integration/test_device_object_property_access.py new file mode 100644 index 0000000..59e9684 --- /dev/null +++ b/tests/integration/test_device_object_property_access.py @@ -0,0 +1,256 @@ +# Copyright (C) 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Test DEVICE Simulation Object Property Access Syntax. + +- Test 01: test single level property access +- Test 02: test nested property access +- Test 03: test parent children object access with materials +- Test 04: test parent children object access with geometries +- Test 05: test parent children object access with solver objects +- Test 06: test ordered property initialization +- Test 07: test constructor initialization properties +- Test 08: test constructor initialization kwargs +- Test 09: test partial path access +- Test 10: test disabled property initialization fails +""" + +import collections + +import ansys.api.lumerical.lumapi as lumapi +import ansys.lumerical.core.autodiscovery as autodiscovery + +base_install_path = autodiscovery.locate_lumerical_install() +lumapi.InteropPaths.setLumericalInstallPath(base_install_path) + + +class TestSimulationObjectPropertyAccessSyntax: + """Test Simulation Object Property Access Syntax.""" + + def test_single_level_property_access(self, setup_device): + """Test 01: test single level property access.""" + device = setup_device + device.addrect() + device.setnamed("::model::geometry::rectangle", "x span", 10.1e-6) + expected = device.getnamed("::model::geometry::rectangle", "x span") + actual = device.getObjectById("::model::geometry::rectangle")["x span"] + + assert actual == expected + + device.deleteall() + + def test_nested_property_access(self, setup_device): + """Test 02: test nested property access.""" + device = setup_device + alloy_name = "AlInP (Aluminium Indium Phosphide)" + device.addmodelmaterial() + device.addmaterialproperties("CT", alloy_name) + device.setnamed( + "::model::materials::New Material::AlInP (Aluminium Indium Phosphide)", "electronic.gamma.mup.high field.monotonic.beta.bowing", 3.14e-6 + ) + expected = device.getnamed( + "::model::materials::New Material::AlInP (Aluminium Indium Phosphide)", "electronic.gamma.mup.high field.monotonic.beta.bowing" + ) + actual = device.getObjectById("::model::materials::New Material::AlInP (Aluminium Indium Phosphide)") + actual = actual["electronic"]["gamma"]["mup"]["high field"]["monotonic"]["beta"]["bowing"] + + assert actual == expected + + device.deleteall() + + def test_parent_children_object_access_with_materials(self, setup_device): + """Test 03: test parent children object access with materials.""" + device = setup_device + device.addmodelmaterial() + device.setnamed("New Material", "name", "electrothermal material") + device.addmaterialproperties("CT", "Air") + device.select("::model::materials::electrothermal material") + device.addmaterialproperties("HT", "InAlAs (Indium Aluminium Arsenide)") + + assert ( + device.getObjectById( + ("::model::materials::electrothermal material" + "::InAlAs (Indium Aluminium Arsenide)" + "::AlAs (Aluminium Arsenide)") + ).getParent()["name"] + == "InAlAs (Indium Aluminium Arsenide)" + ) + assert device.getObjectById("::model::materials::electrothermal material").getParent()["name"] == "materials" + + children_names = [ + c["name"] + for c in device.getObjectById(("::model::materials::electrothermal material" + "::InAlAs (Indium Aluminium Arsenide)")).getChildren() + ] + + assert "InAs (Indium Arsenide)" in children_names and "AlAs (Aluminium Arsenide)" in children_names + + device.addmodelmaterial() + device.setnamed("New Material", "name", "optical material") + device.addmaterialproperties("EM", "Vacuum") + + assert device.getObjectById("::model::materials::optical material::Vacuum").getParent()["name"] == "optical material" + assert device.getObjectById("::model::materials::optical material").getChildren()[0]["name"] == "Vacuum" + + device.deleteall() + + def test_parent_children_object_access_with_geometries(self, setup_device): + """Test 04: test parent children object access with geometries.""" + device = setup_device + device.addstructuregroup() + device.setnamed("structure group", "name", "sg0") + for j in range(1, 5): + device.addstructuregroup() + device.setnamed("structure group", "name", "sg" + str(j)) + device.select("sg" + str(j - 1)) + device.addtogroup("sg" + str(j)) + + obj = device.getObjectById("::model::geometry") + obj.getChildren() + + assert device.getObjectById("::model::geometry::sg4::sg3::sg2").getParent().getParent()["name"] == "sg4" + assert device.getObjectById("::model::geometry::sg4::sg3::sg2").getChildren()[0].getChildren()[0]["name"] == "sg0" + + device.deleteall() + + def test_parent_children_object_access_with_solver_objects(self, setup_device): + """Test 05: test parent children object access with solver objects.""" + device = setup_device + device.addchargesolver() + device.setnamed("::model::CHARGE", "temperature dependence", "coupled") + device.addtemperaturebc() + device.addelectricalcontact() + device.addgroup() + device.setnamed("::model::CHARGE::new group", "name", "container group") + device.addtemperaturemonitor() + device.addtogroup("container group") + children_names = [c["name"] for c in device.getObjectById("::model::CHARGE::boundary conditions").getChildren()] + + assert "temperature" in children_names and "electrical" in children_names + assert device.getObjectById("::model::CHARGE::boundary conditions::electrical").getParent()["name"] == "boundary conditions" + assert device.getObjectById("::model::CHARGE::boundary conditions").getParent()["name"] == "CHARGE" + assert device.getObjectById("::model::CHARGE::container group").getParent()["name"] == "CHARGE" + assert device.getObjectById("::model::CHARGE::container group").getChildren()[0]["name"] == "monitor" + assert device.getObjectById("::model::CHARGE::container group::monitor").getParent()["name"] == "container group" + + device.deleteall() + + def test_ordered_property_initialization(self, setup_device): + """Test 06: test ordered property initialization.""" + device = setup_device + + props = collections.OrderedDict() + props["first axis"] = "x" + props["rotation 1"] = 90 + ring = device.addring(properties=props) + + assert ring["rotation 1"] == 90 + + device.deleteall() + + def test_constructor_initialization_props(self, setup_device): + """Test 07: test constructor initialization properties.""" + device = setup_device + + prop = collections.OrderedDict() + prop["name"] = "material" + device.addmodelmaterial(properties=prop) + + props = collections.OrderedDict() + props["x"] = 100e-6 + props["y"] = 200e-6 + props["z"] = 300e-6 + props["first axis"] = "x" + props["second axis"] = "y" + props["third axis"] = "z" + props["rotation 1"] = 45 + props["rotation 2"] = 90 + props["rotation 3"] = 120 + props["x span"] = 1e-6 + props["y span"] = 2e-6 + props["z span"] = 3e-6 + props["material"] = "material" + rect = device.addrect(properties=props) + + assert rect["x"] == 100e-6 + assert rect["y"] == 200e-6 + assert rect["z"] == 300e-6 + assert rect["first axis"] == "x" + assert rect["second axis"] == "y" + assert rect["third axis"] == "z" + assert rect["rotation 1"] == 45 + assert rect["rotation 2"] == 90 + assert rect["rotation 3"] == 120 + assert rect["x span"] == 1e-6 + assert rect["y span"] == 2e-6 + assert rect["z span"] == 3e-6 + assert rect["material"] == "material" + assert rect["type"] == "Rectangle" + + device.deleteall() + + def test_constructor_initialization_kwargs(self, setup_device): + """Test 08: test constructor initialization kwargs.""" + device = setup_device + device.addmodelmaterial(name="material") + + rect = device.addrect(x=100e-6, y=200e-6, z=300e-6, x_span=1e-6, y_span=2e-6, z_span=3e-6, material="material") + + assert rect.x == 100e-6 + assert rect.y == 200e-6 + assert rect.z == 300e-6 + assert rect.x_span == 1e-6 + assert rect.y_span == 2e-6 + assert rect.z_span == 3e-6 + assert rect.material == "material" + assert rect.type == "Rectangle" + + device.deleteall() + + def test_partial_path_access(self, setup_device): + """Test 09: test partial path access.""" + device = setup_device + device.addrect(name="rect", x=100e-6, y=200e-6, z=300e-6, x_span=1e-6, y_span=2e-6, z_span=3e-6) + rect = device.getObjectById("rect") + + assert rect.x == 100e-6 + assert rect.y == 200e-6 + assert rect.z == 300e-6 + assert rect.x_span == 1e-6 + assert rect.y_span == 2e-6 + assert rect.z_span == 3e-6 + assert rect.type == "Rectangle" + + device.deleteall() + + def test_disabled_property_initialization_fails(self, setup_device): + """Test 10: test disabled property initialization fails.""" + device = setup_device + + props = collections.OrderedDict() + props["rotation 1"] = 10 + + excepted = False + try: + device.addring(properties=props) + except AttributeError: + excepted = True + assert excepted + + device.deleteall() diff --git a/tests/integration/test_device_simobject.py b/tests/integration/test_device_simobject.py new file mode 100644 index 0000000..f9ddf6a --- /dev/null +++ b/tests/integration/test_device_simobject.py @@ -0,0 +1,78 @@ +# Copyright (C) 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Test DEVICE SimObject Properties. + +- Test 01: test composition fraction property +- Test 02: test optical material property construction +- Test 03: test thermal material property construction +""" + +import ansys.api.lumerical.lumapi as lumapi +import ansys.lumerical.core.autodiscovery as autodiscovery + +base_install_path = autodiscovery.locate_lumerical_install() +lumapi.InteropPaths.setLumericalInstallPath(base_install_path) + + +class TestSimObjectProperties: + """Test SimObject Properties.""" + + def test_composition_fraction_property(self, setup_device): + """Test 01: test composition fraction property.""" + device = setup_device + device.addrect() + device.set("name", "Rect") + obj = device.getObjectById("::model::geometry::Rect") + attributes = dir(obj) + + assert not any(["." in name for name in attributes]) + + device.deleteall() + + def test_optical_material_property_construction(self, setup_device): + """Test 02: test optical material property construction.""" + device = setup_device + device.addmodelmaterial() + device.set("name", "Test") + device.addemmaterialproperty("Dielectric") + device.set("name", "Optical") + obj = device.getObjectById("::model::materials::Test::Optical") + attributes = dir(obj) + + assert "refractive index" in attributes + + device.deleteall() + + def test_thermal_material_property_construction(self, setup_device): + """Test 03: test thermal material property construction.""" + device = setup_device + device.addmodelmaterial() + device.set("name", "Test") + device.addhtmaterialproperty("Solid") + device.set("name", "Thermal") + obj = device.getObjectById("::model::materials::Test::Thermal") + attributes = dir(obj) + + assert not any(["." in name for name in attributes]) + + device.deleteall() diff --git a/tests/integration/test_fdtd_object_property_access.py b/tests/integration/test_fdtd_object_property_access.py new file mode 100644 index 0000000..503b89a --- /dev/null +++ b/tests/integration/test_fdtd_object_property_access.py @@ -0,0 +1,169 @@ +# Copyright (C) 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Test FDTD Simulation Object Property Access Syntax. + +- Test 01: test single level property access +- Test 02: test parent children object access with geometries +- Test 03: test ordered property initialization +- Test 04: test disabled property initialization fails +- Test 05: test constructor initialization properties +- Test 06: test constructor initialization kwargs +- Test 07: test partial path access +""" + +import collections + +import ansys.api.lumerical.lumapi as lumapi +import ansys.lumerical.core.autodiscovery as autodiscovery + +base_install_path = autodiscovery.locate_lumerical_install() +lumapi.InteropPaths.setLumericalInstallPath(base_install_path) + + +class TestSimulationObjectPropertyAccessSyntax: + """Test Simulation Object Property Access Syntax.""" + + def test_single_level_property_access(self, setup_fdtd): + """Test 01: test single level property access.""" + fdtd = setup_fdtd + fdtd.addrect() + fdtd.setnamed("::model::rectangle", "x span", 10.1e-6) + expected = fdtd.getnamed("::model::rectangle", "x span") + actual = fdtd.getObjectById("::model::rectangle")["x span"] + + assert actual == expected + + fdtd.deleteall() + + def test_parent_children_object_access_with_geometries(self, setup_fdtd): + """Test 02: test parent children object access with geometries.""" + fdtd = setup_fdtd + fdtd.addstructuregroup() + fdtd.setnamed("structure group", "name", "sg0") + + for j in range(1, 5): + fdtd.addstructuregroup() + fdtd.setnamed("structure group", "name", "sg" + str(j)) + fdtd.select("sg" + str(j - 1)) + fdtd.addtogroup("sg" + str(j)) + + obj = fdtd.getObjectById("::model") + obj.getChildren() + + assert fdtd.getObjectById("::model::sg4::sg3::sg2").getParent().getParent()["name"] == "sg4" + assert fdtd.getObjectById("::model::sg4::sg3::sg2").getChildren()[0].getChildren()[0]["name"] == "sg0" + + fdtd.deleteall() + + def test_ordered_property_initialization(self, setup_fdtd): + """Test 03: test ordered property initialization.""" + fdtd = setup_fdtd + props = collections.OrderedDict() + props["first axis"] = "x" + props["rotation 1"] = 90 + ring = fdtd.addring(properties=props) + + assert ring["rotation 1"] == 90 + + fdtd.deleteall() + + def test_disabled_property_initialization_fails(self, setup_fdtd): + """Test 04: test disabled property initialization fails.""" + fdtd = setup_fdtd + props = collections.OrderedDict() + props["rotation 1"] = 10 + + excepted = False + try: + fdtd.addring(properties=props) + except AttributeError: + excepted = True + assert excepted + + fdtd.deleteall() + + def test_constructor_initialization_props(self, setup_fdtd): + """Test 05: test constructor initialization properties.""" + fdtd = setup_fdtd + props = collections.OrderedDict() + props["x"] = 100 + props["y"] = 200 + props["z"] = 300 + props["first axis"] = "x" + props["second axis"] = "y" + props["third axis"] = "z" + props["rotation 1"] = 45 + props["rotation 2"] = 90 + props["rotation 3"] = 120 + props["x span"] = 1e-6 + props["y span"] = 2e-6 + props["z span"] = 3e-6 + + rect = fdtd.addrect(properties=props) + + assert rect["x"] == 100 + assert rect["y"] == 200 + assert rect["z"] == 300 + assert rect["first axis"] == "x" + assert rect["second axis"] == "y" + assert rect["third axis"] == "z" + assert rect["rotation 1"] == 45 + assert rect["rotation 2"] == 90 + assert rect["rotation 3"] == 120 + assert rect["x span"] == 1e-6 + assert rect["y span"] == 2e-6 + assert rect["z span"] == 3e-6 + assert rect["type"] == "Rectangle" + + fdtd.deleteall() + + def test_constructor_initialization_kwargs(self, setup_fdtd): + """Test 06: test constructor initialization kwargs.""" + fdtd = setup_fdtd + rect = fdtd.addrect(x=100, y=200, z=300, x_span=1e-6, y_span=2e-6, z_span=3e-6) + + assert rect.x == 100 + assert rect.y == 200 + assert rect.z == 300 + assert rect.x_span == 1e-6 + assert rect.y_span == 2e-6 + assert rect.z_span == 3e-6 + assert rect.type == "Rectangle" + + fdtd.deleteall() + + def test_partial_path_access(self, setup_fdtd): + """Test 07: test partial path access.""" + fdtd = setup_fdtd + fdtd.addrect(name="rect", x=100, y=200, z=300, x_span=1e-6, y_span=2e-6, z_span=3e-6) + rect = fdtd.getObjectById("rect") + + assert rect.x == 100 + assert rect.y == 200 + assert rect.z == 300 + assert rect.x_span == 1e-6 + assert rect.y_span == 2e-6 + assert rect.z_span == 3e-6 + assert rect.type == "Rectangle" + + fdtd.deleteall() diff --git a/tests/integration/test_fdtd_simobject.py b/tests/integration/test_fdtd_simobject.py new file mode 100644 index 0000000..889516f --- /dev/null +++ b/tests/integration/test_fdtd_simobject.py @@ -0,0 +1,176 @@ +# Copyright (C) 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Test FDTD SimObject. + +- Test 01: test SimObject construction +- Test 02: test SimObject construction with no object +- Test 03: test SimObject construction duplicate +- Test 04: test SimObject get attributes +- Test 05: test SimObject set attributes +- Test 06: test SimObject results +""" + +import numpy +import pytest + +import ansys.api.lumerical.lumapi as lumapi +import ansys.lumerical.core.autodiscovery as autodiscovery + +base_install_path = autodiscovery.locate_lumerical_install() +lumapi.InteropPaths.setLumericalInstallPath(base_install_path) + + +class TestSimObject: + """Test SimObject.""" + + def test_sim_object_construction(self, setup_fdtd_with_addfdtd): + """Test 01: test SimObject construction.""" + fdtd = setup_fdtd_with_addfdtd + obj = fdtd.getObjectById("::model::FDTD") + attributes = dir(obj) + + assert "x" in attributes + assert "y" in attributes + assert "x span" in attributes + assert "x_span" not in attributes + assert "x min bc" in attributes + assert "same settings on all boundaries" in attributes + + fdtd.deleteall() + + def test_sim_object_construction_no_object(self, setup_fdtd_with_addfdtd): + """Test 02: test SimObject construction with no object.""" + fdtd = setup_fdtd_with_addfdtd + + with pytest.raises(lumapi.LumApiError) as ex_info: + _ = fdtd.getObjectById("::model::FDT") + + assert "Object ::model::FDT not found" in str(ex_info.value) + + fdtd.deleteall() + + def test_sim_object_construction_duplicate(self, setup_fdtd): + """Test 03: test SimObject construction duplicate.""" + fdtd = setup_fdtd + fdtd.addrect() + fdtd.set("x", 100e-6) + + def add_rectangle_warning(): + with pytest.warns(UserWarning, match="Multiple objects named '::model::rectangle'."): + fdtd.addrect() + + return 1 + + assert add_rectangle_warning() == 1 + + fdtd.set("x", 10e-6) + + def get_object_by_id_warning(id, value): + with pytest.warns(UserWarning, match="Multiple objects named '::model::rectangle'."): + obj = fdtd.getObjectById(id) + + assert obj.x == value + + return 1 + + assert get_object_by_id_warning("::model::rectangle#1", 100e-6) == 1 + assert get_object_by_id_warning("::model::rectangle#2", 10e-6) == 1 + + fdtd.deleteall() + + def test_sim_object_get_attributes(self, setup_fdtd): + """Test 04: test SimObject get attributes.""" + fdtd = setup_fdtd + fdtd.addpoly() + obj = fdtd.getObjectBySelection() + fdtd.set("x", 1e-3) + assert obj.x == 1e-3 + + fdtd.set("z span", 2e-3) + assert obj.z_span == 2e-3 + + v = numpy.array([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]) + fdtd.set("vertices", v) + assert (obj.vertices == v).all() + + material = "GaAs - Palik" + fdtd.set("material", material) + assert obj.material == material + + fdtd.set("override mesh order from material database", 1) + assert obj.override_mesh_order_from_material_database == 1 + + fdtd.set("first axis", "y") + assert obj.first_axis == "y" + + fdtd.deleteall() + + def test_sim_object_set_attributes(self, setup_fdtd): + """Test 05: test SimObject set attributes.""" + fdtd = setup_fdtd + fdtd.addgaussian() + obj = fdtd.getObjectBySelection() + obj.amplitude = 2.5 + assert obj.amplitude == 2.5 + + obj.direction = "Backward" + assert obj.direction == "Backward" + + obj.optimize_for_short_pulse = False + assert not obj.optimize_for_short_pulse + + obj.override_global_source_settings = False + assert not obj.override_global_source_settings + + with pytest.raises(lumapi.LumApiError) as ex_info: + obj.bandwidth = 1 + + assert "in setnamed, the requested property 'bandwidth' is inactive" in str(ex_info.value) + + fdtd.deleteall() + + def test_sim_object_results(self, setup_fdtd): + """Test 06: test SimObject results.""" + fdtd = setup_fdtd + fdtd.addfdtd() + obj = fdtd.getObjectBySelection() + results = dir(obj.results) + assert "x" in results + assert "y" in results + assert "z" in results + assert "status" in results + + x = obj.results.x + + assert isinstance(x, numpy.ndarray) + assert len(x.shape) == 2 + + with pytest.raises(AttributeError) as ex_info: + _ = obj.results.xx + assert "'SimObjectResults' object has no attribute 'xx'" in str(ex_info.value) + + with pytest.raises(lumapi.LumApiError) as ex_info: + obj.results.y = 1 + assert "Attribute 'y' can not be set" in str(ex_info.value) + + fdtd.deleteall() diff --git a/tests/integration/test_interconnect_object_property_access.py b/tests/integration/test_interconnect_object_property_access.py new file mode 100644 index 0000000..26f5c8e --- /dev/null +++ b/tests/integration/test_interconnect_object_property_access.py @@ -0,0 +1,164 @@ +# Copyright (C) 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Test INTERCONNECT Simulation Object Property Access Syntax. + +- Test 01: test single level property access +- Test 02: test parent children object access with compound +- Test 03: test ordered property initialization +- Test 04: test disabled property initialization fails +- Test 05: test constructor initialization properties +- Test 06: test constructor initialization kwargs +- Test 07: test partial path access +""" + +import collections + +import ansys.api.lumerical.lumapi as lumapi +import ansys.lumerical.core.autodiscovery as autodiscovery + +base_install_path = autodiscovery.locate_lumerical_install() +lumapi.InteropPaths.setLumericalInstallPath(base_install_path) + + +class TestSimulationObjectPropertyAccessSyntax: + """Test Simulation Object Property Access Syntax.""" + + def test_single_level_property_access(self, setup_interconnect): + """Test 01: test single level property access.""" + intc = setup_interconnect + + new_element = intc.addelement() + new_element.name = "element" + + assert new_element["name"] == "element" + + intc.deleteall() + + def test_parent_children_object_access_with_compound(self, setup_interconnect): + """Test 02: test parent children object access with compound.""" + intc = setup_interconnect + + outer = intc.addelement(name="OuterParent") + intc.groupscope("OuterParent") + intc.addelement(name="InnerParent") + intc.addelement("Optical Amplifier", name="In Outer") + intc.groupscope("InnerParent") + inner = intc.addelement("Optical Amplifier", name="In Inner") + intc.groupscope("::Root Element") + children = outer.getChildren() + parent = inner.getParent() + + assert children[0]["name"] == parent["name"] + + intc.deleteall() + + def test_ordered_property_initialization(self, setup_interconnect): + """Test 03: test ordered property initialization.""" + intc = setup_interconnect + + props = collections.OrderedDict() + props["run diagnostic"] = True + props["diagnostic size"] = 500 + connector = intc.addelement("electrical connector", properties=props) + + assert connector["diagnostic size"] == 500 + + intc.deleteall() + + def test_disabled_property_initialization_fails(self, setup_interconnect): + """Test 04: test disabled property initialization fails.""" + intc = setup_interconnect + + excepted = False + try: + intc.addelement("electrical connector", diagnostic_size=500) + except AttributeError: + excepted = True + assert excepted + + intc.deleteall() + + def test_constructor_initialization_props(self, setup_interconnect): + """Test 05: test constructor initialization properties.""" + intc = setup_interconnect + + props = collections.OrderedDict() + props["name"] = "CWLASER" + props["annotate"] = False + props["frequency"] = 190 + props["power"] = 1 + props["linewidth"] = 1 + props["phase"] = 2.5 + props["azimuth"] = 1.3 + props["ellipticity"] = 0.5 + props["automatic seed"] = False + laser = intc.addelement("CW Laser", properties=props) + + for k in props.keys(): + assert laser[k] == props[k] + + intc.deleteall() + + def test_constructor_initialization_kwargs(self, setup_interconnect): + """Test 06: test constructor initialization kwargs.""" + intc = setup_interconnect + + laser = intc.addelement( + "CW Laser", + name="CWLASER", + annotate=False, + frequency=190, + power=1, + linewidth=1, + phase=2.5, + azimuth=1.3, + ellipticity=0.5, + automatic_seed=False, + ) + + assert laser.name == "CWLASER" + assert not laser.annotate + assert laser.frequency == 190 + assert laser.power == 1 + assert laser.linewidth == 1 + assert laser.phase == 2.5 + assert laser.azimuth == 1.3 + assert laser.ellipticity == 0.5 + assert not laser.automatic_seed + + intc.deleteall() + + def test_partial_path_access(self, setup_interconnect): + """Test 07: test partial path access.""" + intc = setup_interconnect + + intc.addelement("Waveguide coupler", name="coupler 1", x_position=0, y_position=0, coupling_coefficient_1=0.3) + coupler = intc.getObjectById("coupler 1") + + assert coupler.name == "coupler 1" + assert coupler.x_position == 0 + assert coupler.y_position == 0 + assert coupler.coupling_coefficient_1 == 0.3 + assert coupler.type == "Waveguide Coupler" + + intc.deleteall() diff --git a/tests/integration/test_mode_object_property_access.py b/tests/integration/test_mode_object_property_access.py new file mode 100644 index 0000000..cb60d00 --- /dev/null +++ b/tests/integration/test_mode_object_property_access.py @@ -0,0 +1,175 @@ +# Copyright (C) 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Test MODE Simulation Object Property Access Syntax. + +- Test 01: test single level property access +- Test 02: test parent children object access with geometries +- Test 03: test ordered property initialization +- Test 04: test disabled property initialization fails +- Test 05: test constructor initialization properties +- Test 06: test constructor initialization kwargs +- Test 07: test partial path access +""" + +import collections + +import ansys.api.lumerical.lumapi as lumapi +import ansys.lumerical.core.autodiscovery as autodiscovery + +base_install_path = autodiscovery.locate_lumerical_install() +lumapi.InteropPaths.setLumericalInstallPath(base_install_path) + + +class TestSimulationObjectPropertyAccessSyntax: + """Test Simulation Object Property Access Syntax.""" + + def test_single_level_property_access(self, setup_mode): + """Test 01: test single level property access.""" + mode = setup_mode + mode.addrect() + mode.setnamed("::model::rectangle", "x span", 10.1e-6) + expected = mode.getnamed("::model::rectangle", "x span") + actual = mode.getObjectById("::model::rectangle")["x span"] + + assert actual == expected + + mode.deleteall() + + def test_parent_children_object_access_with_geometries(self, setup_mode): + """Test 02: test parent children object access with geometries.""" + mode = setup_mode + mode.addstructuregroup() + mode.setnamed("structure group", "name", "sg0") + + for j in range(1, 5): + mode.addstructuregroup() + mode.setnamed("structure group", "name", "sg" + str(j)) + mode.select("sg" + str(j - 1)) + mode.addtogroup("sg" + str(j)) + + obj = mode.getObjectById("::model") + obj.getChildren() + + assert mode.getObjectById("::model::sg4::sg3::sg2").getParent().getParent()["name"] == "sg4" + assert mode.getObjectById("::model::sg4::sg3::sg2").getChildren()[0].getChildren()[0]["name"] == "sg0" + + mode.deleteall() + + def test_ordered_property_initialization(self, setup_mode): + """Test 03: test ordered property initialization.""" + mode = setup_mode + + props = collections.OrderedDict() + props["first axis"] = "x" + props["rotation 1"] = 90 + ring = mode.addring(properties=props) + + assert ring["rotation 1"] == 90 + + mode.deleteall() + + def test_disabled_property_initialization_fails(self, setup_mode): + """Test 04: test disabled property initialization fails.""" + mode = setup_mode + + props = collections.OrderedDict() + props["rotation 1"] = 10 + + excepted = False + + try: + mode.addring(properties=props) + except AttributeError: + excepted = True + assert excepted + + mode.deleteall() + + def test_constructor_initialization_props(self, setup_mode): + """Test 05: test constructor initialization properties.""" + mode = setup_mode + + props = collections.OrderedDict() + props["x"] = 100 + props["y"] = 200 + props["z"] = 300 + props["first axis"] = "x" + props["second axis"] = "y" + props["third axis"] = "z" + props["rotation 1"] = 45 + props["rotation 2"] = 90 + props["rotation 3"] = 120 + props["x span"] = 1e-6 + props["y span"] = 2e-6 + props["z span"] = 3e-6 + + rect = mode.addrect(properties=props) + + assert rect["x"] == 100 + assert rect["y"] == 200 + assert rect["z"] == 300 + assert rect["first axis"] == "x" + assert rect["second axis"] == "y" + assert rect["third axis"] == "z" + assert rect["rotation 1"] == 45 + assert rect["rotation 2"] == 90 + assert rect["rotation 3"] == 120 + assert rect["x span"] == 1e-6 + assert rect["y span"] == 2e-6 + assert rect["z span"] == 3e-6 + assert rect["type"] == "Rectangle" + + mode.deleteall() + + def test_constructor_initialization_kwargs(self, setup_mode): + """Test 06: test constructor initialization kwargs.""" + mode = setup_mode + + rect = mode.addrect(x=100, y=200, z=300, x_span=1e-6, y_span=2e-6, z_span=3e-6) + + assert rect.x == 100 + assert rect.y == 200 + assert rect.z == 300 + assert rect.x_span == 1e-6 + assert rect.y_span == 2e-6 + assert rect.z_span == 3e-6 + assert rect.type == "Rectangle" + + mode.deleteall() + + def test_partial_path_access(self, setup_mode): + """Test 07: test partial path access.""" + mode = setup_mode + + mode.addrect(name="rect", x=100, y=200, z=300, x_span=1e-6, y_span=2e-6, z_span=3e-6) + rect = mode.getObjectById("rect") + + assert rect.x == 100 + assert rect.y == 200 + assert rect.z == 300 + assert rect.x_span == 1e-6 + assert rect.y_span == 2e-6 + assert rect.z_span == 3e-6 + assert rect.type == "Rectangle" + + mode.deleteall()