-
Notifications
You must be signed in to change notification settings - Fork 73
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement simulation object validation checks
- Loading branch information
Showing
7 changed files
with
302 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
119 changes: 119 additions & 0 deletions
119
klayout_package/python/kqcircuits/simulations/export/simulation_validate.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
# This code is part of KQCircuits | ||
# Copyright (C) 2024 IQM Finland Oy | ||
# | ||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public | ||
# License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later | ||
# version. | ||
# | ||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied | ||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License along with this program. If not, see | ||
# https://www.gnu.org/licenses/gpl-3.0.html. | ||
# | ||
# The software distribution should follow IQM trademark policy for open-source software | ||
# (meetiqm.com/iqm-open-source-trademark-policy). IQM welcomes contributions to the code. | ||
# Please see our contribution agreements for individuals (meetiqm.com/iqm-individual-contributor-license-agreement) | ||
# and organizations (meetiqm.com/iqm-organization-contributor-license-agreement). | ||
from kqcircuits.simulations.export.ansys.ansys_solution import ( | ||
AnsysEigenmodeSolution, | ||
AnsysCurrentSolution, | ||
AnsysVoltageSolution, | ||
AnsysHfssSolution, | ||
) | ||
from kqcircuits.simulations.export.elmer.elmer_solution import ElmerVectorHelmholtzSolution, ElmerCapacitanceSolution | ||
|
||
|
||
class ValidateSim: | ||
"""Validation class that contains consistency checks.""" | ||
|
||
def has_no_ports_when_required(self, simulation, solution): | ||
"""Validation check: ensures that a simulation object has ports when the solution type requires it. | ||
Args: | ||
simulations: A Simulation object. | ||
solutions: A Solution object. | ||
Raises: | ||
Errors when validation criteria are not met. | ||
""" | ||
port_names = get_port_names(simulation) | ||
sim_name = simulation.name | ||
if not port_names and type(solution) in [ | ||
AnsysHfssSolution, | ||
AnsysVoltageSolution, | ||
AnsysCurrentSolution, | ||
ElmerVectorHelmholtzSolution, | ||
ElmerCapacitanceSolution, | ||
]: | ||
raise ValidateSimError( | ||
f"Simulation '{sim_name}' has no ports assigned. This is incompatible with {type(solution)}", | ||
validation_type=self.has_no_ports_when_required.__name__, | ||
) | ||
|
||
def has_edgeport_when_forbidden(self, simulation, solution): | ||
"""Validation check: ensure that if at least one "EdgePort" is present, some solution types can't be chosen. | ||
Args: | ||
simulations: A Simulation object. | ||
solutions: A Solution object. | ||
Raises: | ||
Errors when validation criteria are not met. | ||
""" | ||
port_names = get_port_names(simulation) | ||
sim_name = simulation.name | ||
if "EdgePort" in port_names and type(solution) in [ | ||
AnsysEigenmodeSolution, | ||
AnsysVoltageSolution, | ||
AnsysCurrentSolution, | ||
]: | ||
raise ValidateSimError( | ||
f"Simulation '{sim_name}' has at least one 'EdgePort'. This is incompatible with {type(solution)}", | ||
validation_type=self.has_edgeport_when_forbidden.__name__, | ||
) | ||
|
||
def flux_integration_layer_exists_if_needed(self, simulation, solution): | ||
"""Validation check related to the presence of layers and magnetic flux integration. | ||
Args: | ||
simulation: A Simulation object. | ||
Raises: | ||
Errors when validation criteria are not met. | ||
""" | ||
sim_name = simulation.name | ||
has_integrate_flux = hasattr(solution, "integrate_magnetic_flux") | ||
integrate_flux = solution.integrate_magnetic_flux if has_integrate_flux else False | ||
|
||
# Ensures that a layer with thickness == 0 and a material != "pec" | ||
# exists in the setup when "integrate_magnetic_flux" is True. | ||
if integrate_flux: | ||
layers = simulation.layers | ||
has_flux_integration_layer = False | ||
for layer in layers.values(): | ||
if layer.get("thickness", -1) == 0 and layer.get("material", "") != "pec": | ||
has_flux_integration_layer = True | ||
break | ||
if not has_flux_integration_layer: | ||
raise ValidateSimError( | ||
f"Simulation '{sim_name}' has 'integrate_magnetic_flux = True' | ||
but the integration layer is missing.", | ||
validation_type=self.flux_integration_layer_exists_if_needed.__name__, | ||
) | ||
|
||
|
||
def get_port_names(simulation): | ||
"""Helper function that returns a list of port names in a Simulation object. | ||
Args: | ||
simulation: A Simulation object. | ||
Returns: | ||
port_names: A list of names related to the ports present in simulation. | ||
""" | ||
port_list = simulation.ports | ||
port_names = [] | ||
for port in port_list: | ||
port_names.append(type(port).__name__) | ||
return port_names | ||
|
||
|
||
class ValidateSimError(Exception): | ||
"""Custom exception class for specific error handling.""" | ||
|
||
def __init__(self, message, validation_type=None): | ||
super().__init__(message) | ||
self.validation_type = validation_type |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
157 changes: 157 additions & 0 deletions
157
tests/simulations/simulation_validate/test_simulation_validate.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
# This code is part of KQCircuits | ||
# Copyright (C) 2024 IQM Finland Oy | ||
# | ||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public | ||
# License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later | ||
# version. | ||
# | ||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied | ||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License along with this program. If not, see | ||
# https://www.gnu.org/licenses/gpl-3.0.html. | ||
# | ||
# The software distribution should follow IQM trademark policy for open-source software | ||
# (meetiqm.com/iqm-open-source-trademark-policy). IQM welcomes contributions to the code. | ||
# Please see our contribution agreements for individuals (meetiqm.com/iqm-individual-contributor-license-agreement) | ||
# and organizations (meetiqm.com/iqm-organization-contributor-license-agreement). | ||
|
||
import pytest | ||
from kqcircuits.simulations.export.simulation_validate import ValidateSim | ||
from kqcircuits.simulations.simulation import Simulation | ||
from kqcircuits.simulations.port import InternalPort, EdgePort | ||
from kqcircuits.simulations.export.ansys.ansys_solution import ( | ||
AnsysCurrentSolution, | ||
AnsysHfssSolution, | ||
AnsysEigenmodeSolution, | ||
AnsysVoltageSolution, | ||
) | ||
from kqcircuits.simulations.export.elmer.elmer_solution import ( | ||
ElmerVectorHelmholtzSolution, | ||
ElmerCapacitanceSolution, | ||
) | ||
from kqcircuits.simulations.export.simulation_validate import ValidateSimError | ||
|
||
|
||
@pytest.fixture | ||
def mock_simulation(layout): | ||
simulation = Simulation(layout) | ||
simulation.name = "test_sim" | ||
return simulation | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"solution", | ||
[ | ||
AnsysCurrentSolution(), | ||
AnsysHfssSolution(), | ||
AnsysVoltageSolution(), | ||
ElmerVectorHelmholtzSolution(), | ||
ElmerCapacitanceSolution(), | ||
], | ||
) | ||
def test_has_no_ports_when_required(mock_simulation, solution): | ||
ports = [InternalPort(0, [0, 0, 1, 1])] | ||
mock_simulation.ports = ports | ||
validator = ValidateSim() | ||
validator.has_no_ports_when_required(mock_simulation, solution) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"solution", | ||
[ | ||
AnsysCurrentSolution(), | ||
AnsysHfssSolution(), | ||
AnsysVoltageSolution(), | ||
ElmerVectorHelmholtzSolution(), | ||
ElmerCapacitanceSolution(), | ||
], | ||
) | ||
def test_raise_no_port_error_when_required(mock_simulation, solution): | ||
validator = ValidateSim() | ||
with pytest.raises(ValidateSimError) as expected_error: | ||
validator.has_no_ports_when_required(mock_simulation, solution) | ||
assert expected_error.value.validation_type == "has_no_ports_when_required" | ||
|
||
|
||
@pytest.mark.parametrize("solution", [AnsysEigenmodeSolution(), AnsysVoltageSolution(), AnsysCurrentSolution()]) | ||
def test_has_edgeport_when_forbidden(mock_simulation, solution): | ||
ports = [InternalPort(0, [0, 0, 1, 1])] | ||
mock_simulation.ports = ports | ||
validator = ValidateSim() | ||
validator.has_edgeport_when_forbidden(mock_simulation, solution) | ||
|
||
|
||
@pytest.mark.parametrize("solution", [AnsysEigenmodeSolution(), AnsysVoltageSolution(), AnsysCurrentSolution()]) | ||
def test_raise_edgeport_error_when_forbidden(mock_simulation, solution): | ||
ports = [EdgePort(0, [0, 0, 1, 1])] | ||
mock_simulation.ports = ports | ||
validator = ValidateSim() | ||
with pytest.raises(ValidateSimError) as expected_error: | ||
validator.has_edgeport_when_forbidden(mock_simulation, solution) | ||
assert expected_error.value.validation_type == "has_edgeport_when_forbidden" | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"solution", [AnsysHfssSolution(), AnsysEigenmodeSolution(), AnsysCurrentSolution(), AnsysVoltageSolution()] | ||
) | ||
def test_flux_integration_layer_exists_if_needed_passes_if_no_layers(mock_simulation, solution): | ||
validator = ValidateSim() | ||
validator.flux_integration_layer_exists_if_needed(mock_simulation, solution) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"solution", [AnsysHfssSolution(), AnsysEigenmodeSolution(), AnsysCurrentSolution(), AnsysVoltageSolution()] | ||
) | ||
def test_flux_integration_layer_exists_if_needed_passes_if_has_needed_layer(mock_simulation, solution): | ||
validator = ValidateSim() | ||
mock_simulation.layers["flux_integration_layer"] = {"z": 0.0, "thickness": 0.0, "material": "non-pec"} | ||
solution.integrate_magnetic_flux = True | ||
validator.flux_integration_layer_exists_if_needed(mock_simulation, solution) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"solution", [AnsysHfssSolution(), AnsysEigenmodeSolution(), AnsysCurrentSolution(), AnsysVoltageSolution()] | ||
) | ||
def test_flux_integration_layer_exists_if_needed_passes_if_has_needed_layer_and_others(mock_simulation, solution): | ||
validator = ValidateSim() | ||
mock_simulation.layers["flux_integration_layer"] = {"z": 0.0, "thickness": 0.0, "material": "non-pec"} | ||
mock_simulation.layers["thick_non_pec"] = {"z": 0.0, "thickness": 0.1, "material": "non-pec"} | ||
mock_simulation.layers["sheet_pec"] = {"z": 0.0, "thickness": 0.0, "material": "pec"} | ||
solution.integrate_magnetic_flux = True | ||
validator.flux_integration_layer_exists_if_needed(mock_simulation, solution) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"solution", [AnsysHfssSolution(), AnsysEigenmodeSolution(), AnsysCurrentSolution(), AnsysVoltageSolution()] | ||
) | ||
def test_flux_integration_layer_passes_if_not_integrating_flux(mock_simulation, solution): | ||
validator = ValidateSim() | ||
solution.integrate_magnetic_flux = False | ||
mock_simulation.layers["thick_non_pec"] = {"z": 0.0, "thickness": 0.1, "material": "non-pec"} | ||
mock_simulation.layers["sheet_pec"] = {"z": 0.0, "thickness": 0.0, "material": "pec"} | ||
validator.flux_integration_layer_exists_if_needed(mock_simulation, solution) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"solution", [AnsysHfssSolution(), AnsysEigenmodeSolution(), AnsysCurrentSolution(), AnsysVoltageSolution()] | ||
) | ||
def test_raise_flux_integration_error_if_has_no_needed_layer(mock_simulation, solution): | ||
validator = ValidateSim() | ||
solution.integrate_magnetic_flux = True | ||
with pytest.raises(ValidateSimError) as expected_error: | ||
validator.flux_integration_layer_exists_if_needed(mock_simulation, solution) | ||
assert expected_error.value.validation_type == "flux_integration_layer_exists_if_needed" | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"solution", [AnsysHfssSolution(), AnsysEigenmodeSolution(), AnsysCurrentSolution(), AnsysVoltageSolution()] | ||
) | ||
def test_raise_flux_integration_error_if_has_no_needed_layer_and_others(mock_simulation, solution): | ||
validator = ValidateSim() | ||
solution.integrate_magnetic_flux = True | ||
mock_simulation.layers["thick_non_pec"] = {"z": 0.0, "thickness": 0.1, "material": "non-pec"} | ||
mock_simulation.layers["sheet_pec"] = {"z": 0.0, "thickness": 0.0, "material": "pec"} | ||
with pytest.raises(ValidateSimError) as expected_error: | ||
validator.flux_integration_layer_exists_if_needed(mock_simulation, solution) | ||
assert expected_error.value.validation_type == "flux_integration_layer_exists_if_needed" |