Skip to content

Commit

Permalink
Split AnsysSolution into subclasses
Browse files Browse the repository at this point in the history
Create dedicated solution class for each `ansys_tool` alternative, and include only relevant parameters for each solution type.

Related changes:

- Introduce `get_parameters` function in `Solution` so that ClassVar parameter `ansys_tool` ends up into json "parameters".

- Generalize `find_varied_parameters` to cover variable "parameters" keys in different json files. This is necessary because keys in "parameters" depend on solution type.

- Raise error if `export_ansys_json` is going to override json file. This is useful to make sure that user remembers to set unique names for simulation and solution classes.

- Rename AnsysEigenmodeSolution parameter "frequency" to "min_frequency", because it means minimum allowed eigenmode frequency. The change is applied in 5 export scripts.

- Rename AnsysHfssSolution parameter "hfss_capacitance_export" to "capacitance_export". No usage in export scripts.

- Remove unused export parameter `sweep_enabled` from `cpw_circle_sim.py`.

- Modernize export scripts `swissmon_fluxline_sim.py` and `swissmon_epr_sim.py` (renamed) to use solution sweep feature.

- Modernize `tls_waveguide_sim.py` to use AnsysHfssSolution.

- Change renamed export parameters in user guide
  • Loading branch information
jukkarabina committed Apr 19, 2024
1 parent 60daeca commit 03a67c6
Show file tree
Hide file tree
Showing 16 changed files with 264 additions and 156 deletions.
5 changes: 3 additions & 2 deletions docs/user_guide/simulation/simulation_features.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ The other relevant parameters and their default values are::
The capacitance matrix simulations are also available with Ansys HFSS framework, which is useful in case if only HFSS
license is available.
For the usage one must perform a HFSS S-parameter simulation as indicated above and use the export parameter
``hfss_capacitance_export=True``.
``capacitance_export=True``.
This method assumes a purely capacitive model and is valid as long as the resulting ``C_i_j`` are constant over
frequency.

Expand Down Expand Up @@ -102,7 +102,8 @@ The other relevant parameters and their default values are::

export_parameters.update({
'ansys_tool': 'eigenmode', # Determines whether to use HFSS ('hfss'), Q3D Extractor ('q3d') or HFSS eigenmode ('eigenmode')
'frequency': 5, # the lower limit for eigenfrequency.
'frequency_units': "GHz", # Units of frequency.
'min_frequency': 5, # the lower limit for eigenfrequency.
'max_delta_f': 0.1, # Maximum allowed relative difference in eigenfrequency (%).
'n_modes': 2, # Number of eigenmodes to solve.
'percent_refinement': 30, # Percentage of mesh refinement on each iteration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from typing import Optional, Union, Sequence, Tuple
from pathlib import Path

from kqcircuits.simulations.export.ansys.ansys_solution import AnsysSolution
from kqcircuits.simulations.export.ansys.ansys_solution import AnsysSolution, get_ansys_solution
from kqcircuits.simulations.export.simulation_export import (
copy_content_into_directory,
get_post_process_command_lines,
Expand Down Expand Up @@ -71,8 +71,13 @@ def export_ansys_json(simulation: Simulation, solution: AnsysSolution, path: Pat

# write .json file
json_file_path = str(path.joinpath(full_name + ".json"))
with open(json_file_path, "w") as fp:
json.dump(json_data, fp, cls=GeometryJsonEncoder, indent=4)
if not Path(json_file_path).exists():
with open(json_file_path, "w") as fp:
json.dump(json_data, fp, cls=GeometryJsonEncoder, indent=4)
else:
raise ValueError(
f"Json file '{full_name}.json' already exists. Make sure that simulations and solutions have unique names."
)

return json_file_path

Expand Down Expand Up @@ -175,13 +180,9 @@ def export_ansys(
write_commit_reference_file(path)
copy_content_into_directory(ANSYS_SCRIPT_PATHS, path, script_folder)
json_filenames = []
common_sol = None if all(isinstance(s, Sequence) for s in simulations) else get_ansys_solution(**solution_params)
for sim_sol in simulations:
if isinstance(sim_sol, Sequence):
simulation, solution = sim_sol
else:
simulation = sim_sol
solution = AnsysSolution(**solution_params)

simulation, solution = sim_sol if isinstance(sim_sol, Sequence) else (sim_sol, common_sol)
try:
json_filenames.append(export_ansys_json(simulation, solution, path))
except (IndexError, ValueError, Exception) as e: # pylint: disable=broad-except
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,93 +15,225 @@
# (meetiqm.com/developers/osstmpolicy). IQM welcomes contributions to the code. Please see our contribution agreements
# for individuals (meetiqm.com/developers/clas/individual) and organizations (meetiqm.com/developers/clas/organization).
from dataclasses import dataclass
from typing import Optional, Union, List
from typing import Optional, Union, List, ClassVar
from kqcircuits.simulations.export.solution import Solution


@dataclass
class AnsysSolution(Solution):
"""
A Base class for Ansys Solution and export parameters
A Base class for Ansys solution parameters
Args:
ansys_tool: Determines whether to use 'hfss' (s-parameters), 'q3d', 'current', 'voltage', or 'eigenmode'.
frequency_units: Units of frequency.
frequency: Frequency for mesh refinement. To set up multifrequency analysis in HFSS use list of numbers.
max_delta_s: Stopping criterion in HFSS simulation.
max_delta_e: Stopping criterion in current or voltage excitation simulation.
percent_error: Stopping criterion in Q3D simulation.
percent_refinement: Percentage of mesh refinement on each iteration.
maximum_passes: Maximum number of iterations in simulation.
minimum_passes: Minimum number of iterations in simulation.
minimum_converged_passes: Determines how many iterations have to meet the stopping criterion to stop simulation.
sweep_enabled: Determines if HFSS frequency sweep is enabled.
sweep_start: The lowest frequency in the sweep.
sweep_end: The highest frequency in the sweep.
sweep_count: Number of frequencies in the sweep.
sweep_type: choices are "interpolating", "discrete" or "fast"
max_delta_f: Maximum allowed relative difference in eigenfrequency (%). Used when ``ansys_tool`` is *eigenmode*.
n_modes: Number of eigenmodes to solve. Used when ``ansys_tool`` is 'eigenmode'.
frequency_units: Units of frequency.
mesh_size: Dictionary to determine manual mesh refinement on layers. Set key as the layer name and value as the
maximal mesh element length inside the layer.
simulation_flags: Optional export processing, given as list of strings. See Simulation Export in docs.
ansys_project_template: path to the simulation template
integrate_energies: Calculate energy integrals over each layer and save them into a file
integrate_magnetic_flux: Integrate magnetic fluxes through each non-pec sheet and save them into a file
hfss_capacitance_export: If True, the capacitance matrices are exported from HFSS simulations
"""

ansys_tool: str = "hfss" # to be ClassVar
frequency_units: str = "GHz"
frequency: Union[float, List[float]] = 5
max_delta_s: float = 0.1
max_delta_e: float = 0.1
percent_error: float = 1
ansys_tool: ClassVar[str] = ""
percent_refinement: float = 30.0
maximum_passes: int = 12
minimum_passes: int = 1
minimum_converged_passes: int = 1
sweep_enabled: bool = True
sweep_start: float = 0
sweep_end: float = 10
sweep_count: int = 101
sweep_type: str = "interpolating"
max_delta_f: float = 0.1
n_modes: int = 2
frequency_units: str = "GHz"
mesh_size: Optional[dict] = None
simulation_flags: Optional[List[str]] = None
ansys_project_template: Optional[str] = None
integrate_energies: bool = False
integrate_magnetic_flux: bool = False
hfss_capacitance_export: bool = False

def get_solution_data(self):
"""Return the solution data in dictionary form."""
return {
"solution_name": self.name,
"ansys_tool": self.ansys_tool,
"analysis_setup": {
"frequency_units": self.frequency_units,
"frequency": self.frequency,
"max_delta_s": self.max_delta_s, # stopping criterion for HFSS
"max_delta_e": self.max_delta_e, # stopping criterion for current or voltage excitation simulation
"percent_error": self.percent_error, # stopping criterion for Q3D
"percent_refinement": self.percent_refinement,
"maximum_passes": self.maximum_passes,
"minimum_passes": self.minimum_passes,
"minimum_converged_passes": self.minimum_converged_passes,
"frequency_units": self.frequency_units,
},
"mesh_size": {} if self.mesh_size is None else self.mesh_size,
"simulation_flags": [] if self.simulation_flags is None else self.simulation_flags,
"integrate_energies": self.integrate_energies,
"integrate_magnetic_flux": self.integrate_magnetic_flux,
**({} if self.ansys_project_template is None else {"ansys_project_template": self.ansys_project_template}),
}


@dataclass
class AnsysHfssSolution(AnsysSolution):
"""
Class for Ansys S-parameter (HFSS) solution parameters
Args:
frequency: Frequency for mesh refinement. To set up multifrequency analysis in HFSS use list of numbers.
max_delta_s: Stopping criterion in HFSS simulation.
sweep_enabled: Determines if HFSS frequency sweep is enabled.
sweep_start: The lowest frequency in the sweep.
sweep_end: The highest frequency in the sweep.
sweep_count: Number of frequencies in the sweep.
sweep_type: choices are "interpolating", "discrete" or "fast"
capacitance_export: If True, the capacitance matrices are exported from S-parameter simulation
"""

ansys_tool: ClassVar[str] = "hfss"
frequency: Union[float, List[float]] = 5
max_delta_s: float = 0.1
sweep_enabled: bool = True
sweep_start: float = 0
sweep_end: float = 10
sweep_count: int = 101
sweep_type: str = "interpolating"
capacitance_export: bool = False

def get_solution_data(self):
"""Return the solution data in dictionary form."""
data = super().get_solution_data()
return {
**data,
"analysis_setup": {
**data["analysis_setup"],
"frequency": self.frequency,
"max_delta_s": self.max_delta_s,
"sweep_enabled": self.sweep_enabled,
"sweep_start": self.sweep_start,
"sweep_end": self.sweep_end,
"sweep_count": self.sweep_count,
"sweep_type": self.sweep_type,
},
"capacitance_export": self.capacitance_export,
}


@dataclass
class AnsysQ3dSolution(AnsysSolution):
"""
Class for Ansys capacitance matrix (Q3D) solution parameters
Args:
frequency: Nominal solution frequency (has no effect on capacitance matrix at the moment).
percent_error: Stopping criterion in Q3D simulation.
"""

ansys_tool: ClassVar[str] = "q3d"
frequency: float = 5
percent_error: float = 1

def get_solution_data(self):
"""Return the solution data in dictionary form."""
data = super().get_solution_data()
return {
**data,
"analysis_setup": {
**data["analysis_setup"],
"frequency": self.frequency,
"percent_error": self.percent_error,
},
}


@dataclass
class AnsysEigenmodeSolution(AnsysSolution):
"""
Class for Ansys eigenmode solution parameters
Args:
min_frequency: Minimum allowed eigenmode frequency
max_delta_f: Maximum allowed relative difference in eigenfrequency (%)
n_modes: Number of eigenmodes to solve.
"""

ansys_tool: ClassVar[str] = "eigenmode"
min_frequency: float = 0.1
max_delta_f: float = 0.1
n_modes: int = 2

def get_solution_data(self):
"""Return the solution data in dictionary form."""
data = super().get_solution_data()
return {
**data,
"analysis_setup": {
**data["analysis_setup"],
"min_frequency": self.min_frequency,
"max_delta_f": self.max_delta_f,
"n_modes": self.n_modes,
},
"mesh_size": {} if self.mesh_size is None else self.mesh_size,
"simulation_flags": [] if self.simulation_flags is None else self.simulation_flags,
"integrate_energies": self.integrate_energies,
"integrate_magnetic_flux": self.integrate_magnetic_flux,
"hfss_capacitance_export": self.hfss_capacitance_export,
**({} if self.ansys_project_template is None else {"ansys_project_template": self.ansys_project_template}),
}


@dataclass
class AnsysCurrentSolution(AnsysSolution):
"""
Class for Ansys current excitation solution parameters
Args:
frequency: Frequency of alternating current excitation.
max_delta_e: Stopping criterion in current excitation simulation.
"""

ansys_tool: ClassVar[str] = "current"
frequency: float = 0.1
max_delta_e: float = 0.1

def get_solution_data(self):
"""Return the solution data in dictionary form."""
data = super().get_solution_data()
return {
**data,
"analysis_setup": {
**data["analysis_setup"],
"frequency": self.frequency,
"max_delta_e": self.max_delta_e,
},
}


@dataclass
class AnsysVoltageSolution(AnsysSolution):
"""
Class for Ansys voltage excitation solution parameters
Args:
frequency: Frequency of alternating voltage excitation.
max_delta_e: Stopping criterion in voltage excitation simulation.
"""

ansys_tool: ClassVar[str] = "voltage"
frequency: float = 5
max_delta_e: float = 0.1

def get_solution_data(self):
"""Return the solution data in dictionary form."""
data = super().get_solution_data()
return {
**data,
"analysis_setup": {
**data["analysis_setup"],
"frequency": self.frequency,
"max_delta_e": self.max_delta_e,
},
}


def get_ansys_solution(ansys_tool="hfss", **solution_params):
"""Returns an instance of AnsysSolution subclass.
Args:
ansys_tool: Determines the subclass of AnsysSolution.
solution_params: Arguments passed for AnsysSolution subclass.
"""
for c in [AnsysHfssSolution, AnsysQ3dSolution, AnsysEigenmodeSolution, AnsysCurrentSolution, AnsysVoltageSolution]:
if ansys_tool == c.ansys_tool:
return c(**solution_params)
raise ValueError(f"No AnsysSolution found for ansys_tool={ansys_tool}.")
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def get_combined_parameters(simulation, solution):
In case of common keys, 'solution.' prefix is added to Solution parameter key.
"""
sim_dict = simulation.get_parameters()
sol_dict = solution.__dict__
sol_dict = solution.get_parameters()
return {
**{k: v for k, v in sim_dict.items() if k != "name"},
**{f"solution.{k}" if k in sim_dict else k: v for k, v in sol_dict.items() if k != "name"},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@ class Solution:
"""

name: str = ""

def get_parameters(self):
"""Returns class parameters (also ClassVar parameters) in dictionary form"""
return {**{k: getattr(self, k) for k in self.__annotations__.keys()}, **self.__dict__}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def write_simulation_machine_versions_file(oDesktop):
oDesktop.RunScriptWithArguments(os.path.join(scriptpath, "import_simulation_geometry.py"), jsonfile)

# Set up capacitive PI model
if data.get("ansys_tool", "hfss") == "q3d" or data.get("hfss_capacitance_export", False):
if data.get("ansys_tool", "hfss") == "q3d" or data.get("capacitance_export", False):
oDesktop.RunScript(os.path.join(scriptpath, "create_capacitive_pi_model.py"))

# Create reports
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -731,16 +731,6 @@ def color_by_material(material, is_sheet=False):
],
)
elif ansys_tool == "q3d":
if isinstance(type(setup["frequency"]), list):
setup["frequency"] = setup["frequency"][0]
oDesktop.AddMessage(
"",
"",
0,
"Multi-frequency is not supported in Q3D. Create setup with frequency "
"{}.".format(str(setup["frequency"]) + setup["frequency_units"]),
)

oAnalysisSetup.InsertSetup(
"Matrix",
[
Expand Down Expand Up @@ -774,12 +764,10 @@ def color_by_material(material, is_sheet=False):
)
elif ansys_tool == "eigenmode":
# Create EM setup
min_freq_ghz = str(setup.get("frequency", 0.1)) + setup["frequency_units"]

setup_list = [
"NAME:Setup1",
"MinimumFrequency:=",
min_freq_ghz,
str(setup["min_frequency"]) + setup["frequency_units"],
"NumModes:=",
setup["n_modes"],
"MaxDeltaFreq:=",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ def build(self):
"percent_error": 0.1,
"maximum_passes": 20,
"minimum_passes": 15,
"sweep_enabled": False,
}
export_ansys(simulations, **export_parameters)

Expand Down
Loading

0 comments on commit 03a67c6

Please sign in to comment.