Custom Classes
September 16, 2026 · View on GitHub
If you want to simulate and predict your own geometry, you need to create three files:
- A python file extending the
BaseGeometryclass and implementing the required methods. - A stackup XML file defining the layers of your geometry.
- A simulation configuration file defining the simulation parameters for Palace.
Once you have these three files, you can head over to Running ORCA to see how to run ORCA with these files.
Python Class
The Python class should be a @dataclass extending orca.BaseGeometry and must implement the following:
Required dataclass fields:
name: str— Unique identifier for the geometry (used as directory name and file prefix).stackup_xml: str— Path to the stackup XML file describing the physical layer stack.simconfig_filename: str— Path to the Palace simulation configuration file (.simcfg).input_parameter_iterator: InputParameterIterator— Defines geometry parameters and their sampling ranges.
!!! warning "Give the object-valued field a default_factory"
`input_parameter_iterator` must be declared with `field(default_factory=...)`,
as in the example below. Writing `= InputParameterIterator(...)` instead builds
**one** object shared by every instance of the class.
!!! tip "Choosing an output representation"
The `codec` passed to the dataset decides what the model regresses. `FlatReImCodec`
predicts all `N x N` entries; `UpperTriangleReImCodec` predicts only the upper
triangle and mirrors it on decode, which halves the output dimension and makes
`S = S^T` structural. Use it for reciprocal passives (any ordinary inductor or
transformer), and `FlatReImCodec` for anything non-reciprocal. Note that this
changes the ONNX output names. See `src/orca/training/README.md`.
!!! note "Basis expansions are not set on the geometry"
Engineered inputs such as a Chebyshev expansion of frequency belong to the
model, not the geometry: pass `orca.ModelTrainer(model=..., basis="chebyshev")`.
That way the expansion is tuned with the model and traced into the exported ONNX
graph. See `src/orca/training/README.md`.
Required abstract methods:
create_gds_file(name, output_path, params) -> str— Generates a GDS layout file from geometry parameters. Returns the path to the created file.create_dataset() -> BaseDataset— Builds the dataset (e.g.GeoToSParamDatasetSingleFrequency) with its output codec and normalizers, used for training. It is called once per geometry instance, the first timegeometry.datasetis read, so each instance gets its own dataset and normalizer statistics.
!!! tip "Keep the training imports inside create_dataset()"
The dataset classes and normalizers need PyTorch, which is an optional
dependency of ORCA (the `train` extra). Import them inside `create_dataset()`,
as in the example below, and your geometry can still generate layouts and run
Palace simulations in an environment without PyTorch (e.g. an HPC cluster).
The model architecture and its hyperparameters are not part of the geometry: pass them to
the training stage with orca.ModelTrainer(model=..., hyperparameters=...), or leave
hyperparameters out to let ModelTrainer tune them with Optuna over the model's own search space.
Example:
# Builds a fresh iterator per geometry instance. See the warning above:
# a bare `= InputParameterIterator(...)` default would be shared by every instance.
def _input_parameters() -> InputParameterIterator:
return InputParameterIterator(
picking_strategy="random",
frequency=[1e8, 500e8], # 1 GHz to 500 GHz
bottom_winding_diameter=[x / 10 for x in range(200, 1201, 1)], # 20.0 to 120.0 in 0.1 steps
top_winding_diameter=[x / 10 for x in range(200, 1201, 1)], # 20.0 to 120.0 in 0.1 steps
center_displacement=[x / 10 for x in range(0, 151, 1)], # 0.0 to 15.0 in 0.1 steps
bottom_linewidth=[x / 10 for x in range(20, 121, 1)], # 2.0 to 12.0 in 0.1 steps
top_linewidth=[x / 10 for x in range(20, 121, 1)], # 2.0 to 12.0 in 0.1 steps
)
@dataclass
class TransformerOcta(BaseGeometry):
"""
Represents a transformer geometry with octagonal shape.
"""
name: str = "tf_octa_c_ports"
stackup_xml: str = os.path.join(os.path.dirname(__file__), "SG13G2_nosub.xml")
simconfig_filename: str = os.path.join(os.path.dirname(__file__), "tf_octa_c_ports.simcfg")
input_parameter_iterator: InputParameterIterator = field(default_factory=_input_parameters)
def create_dataset(self) -> "BaseDataset":
# Imported here so the geometry works without PyTorch (see the tip above).
from orca.training.codecs import FlatReImCodec
from orca.training.datasets.geo_to_s_param_single_f import GeoToSParamDatasetSingleFrequency
from orca.training.normalize import MinMaxNormalizer, StandardNormalizer
return GeoToSParamDatasetSingleFrequency(
codec=FlatReImCodec(n_ports=6),
# Scale inputs with the declared parameter ranges rather than the
# min/max of whatever subset happens to be trained on.
input_normalizer=MinMaxNormalizer(self.input_parameter_iterator),
output_normalizer=StandardNormalizer(),
)
@staticmethod
def create_gds_file(name: str, output_path: str, params: dict[str, Any]) -> str:
#
# < Put your actual GDS generation code here >
#
c.write_gds(output_path, with_metadata=False)
return output_path
Reference: InputParameterIterator
InputParameterIterator defines the set of geometry parameters and how they are sampled during GDS generation.
InputParameterIterator(
picking_strategy="random", # "grid" / "uniform_grid", "step_grid", or "random"
frequency=[1e8, 500e8], # Optional: frequency range included for normalisation (not iterated)
param_a=[...], # List/range of possible values for each geometry parameter
param_b=[...],
)
Picking strategies:
| Strategy | Behaviour |
|---|---|
"grid" / "uniform_grid" | Uniform grid across all parameter combinations |
"step_grid" | Grid using explicit step sizes |
"random" | Random sampling without replacement |
Reference: Dataset Types
| Class | Description |
|---|---|
GeoToSParamDatasetSingleFrequency | One training sample per frequency point per geometry (recommended) |
GeoToSParamDataset | One training sample per geometry (full frequency sweep as a vector) |
Reference: Basis Expansions
Basis expansions widen the model's input before its first layer by appending fixed
basis functions of it. Nothing about them is learned. They belong to the model,
not the geometry, so they are chosen on the training stage — orca.ModelTrainer(model="mlp", basis="chebyshev") — and work with any architecture. Because the expansion is part
of the model, it is tuned with it and traced into the exported ONNX graph; the ONNX
input names stay the same.
| Class | Name | Description |
|---|---|---|
IdentityBasis | "identity" | Passes inputs through unchanged (the default) |
ChebyshevBasis | "chebyshev" | Appends degree Chebyshev polynomials of one column, frequency by default |
ChebyshevBasis tunes basis_degree automatically when no hyperparameters are
supplied. It sees normalized inputs and clamps to [-1, 1], so a frequency outside
the training range saturates rather than diverging.
Reference: Normalizers
Input normalizers (passed as input_normalizer to the dataset):
| Class | Description |
|---|---|
MinMaxNormalizer(input_parameter_iterator) | Min-max normalisation with the declared parameter ranges (recommended: independent of which samples are trained on, and matches the input_parameter_ranges metadata of the exported ONNX model) |
OutputMinMaxNormalizer | Min-max normalisation fitted to the min/max of the training split |
Output normalizers (passed as output_normalizer to the dataset):
| Class | Description |
|---|---|
StandardNormalizer | Z-score normalisation (zero mean, unit variance) |
OutputMinMaxNormalizer | Min-max normalisation |
Stackup XML File
There are multiple examples of stackup XML files in the src/orca/geometry/examples/ directory. Often these are sufficient to get started. You can also create your own stackup XML file or adjust one of the examples here to fit your needs.
Example:
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<Stackup schemaVersion="2.0">
<Materials>
<Material Name="Activ" Type="Conductor" Permittivity="1" DielectricLossTangent="0" Conductivity="357141.0" Color="00ff00"/>
<Material Name="Metal1" Type="Conductor" Permittivity="1" DielectricLossTangent="0" Conductivity="21640000.0" Color="39bfff"/>
<Material Name="Metal2" Type="Conductor" Permittivity="1" DielectricLossTangent="0" Conductivity="23190000.0" Color="ccccd9"/>
<Material Name="TopMetal1" Type="Conductor" Permittivity="1" DielectricLossTangent="0" Conductivity="27800000.0" Color="ffe6bf"/>
<Material Name="TopMetal2" Type="Conductor" Permittivity="1" DielectricLossTangent="0" Conductivity="30300000.0" Color="ff8000"/>
<Material Name="TopVia1" Type="Conductor" Permittivity="1" DielectricLossTangent="0" Conductivity="2191000.0" Color="ffe6bf"/>
<Material Name="Via2" Type="Conductor" Permittivity="1" DielectricLossTangent="0" Conductivity="1660000.0" Color="ff3736"/>
<Material Name="Via1" Type="Conductor" Permittivity="1" DielectricLossTangent="0" Conductivity="1660000.0" Color="ccccff"/>
<Material Name="Cont" Type="Conductor" Permittivity="1" DielectricLossTangent="0" Conductivity="2390000.0" Color="00ffff"/>
<Material Name="Passive" Type="Dielectric" Permittivity="6.6" DielectricLossTangent="0.0" Conductivity="0" Color="a0a0f0"/>
<Material Name="SiO2" Type="Dielectric" Permittivity="4.1" DielectricLossTangent="0.0" Conductivity="0" Color="fffcad"/>
<Material Name="AIR" Type="Dielectric" Permittivity="1.0" DielectricLossTangent="0.0" Conductivity="0" Color="d0d0d0"/>
<Material Name="Vmim" Type="Conductor" Permittivity="1" DielectricLossTangent="0" Conductivity="2191000.0" Color="ffe6bf"/>
<Material Name="MIM" Type="Conductor" Permittivity="1" DielectricLossTangent="0" Conductivity="500000.0" Color="e6ffbf"/>
<Material Name="LOWLOSS" Type="Conductor" Permittivity="1" DielectricLossTangent="0" Conductivity="1E10" Color="ff0000"/>
</Materials>
<ELayers LengthUnit="um">
<Dielectrics>
<Dielectric Name="AIR" Material="AIR" Thickness="200.0000" />
<Dielectric Name="Passive" Material="Passive" Thickness="0.4000" />
<Dielectric Name="SiO2" Material="SiO2" Thickness="15.7303" />
<Dielectric Name="EPI" Material="EPI" Thickness="3.7500" />
<Dielectric Name="Substrate" Material="Substrate" Thickness="180.0000" />
</Dielectrics>
<Layers>
<Substrate Offset="183.75"/>
<Layer Name="Activ" Type="conductor" Zmin="0.0000" Zmax="0.4000" Material="Activ" Layer="1" />
<Layer Name="Metal1" Type="conductor" Zmin="1.0400" Zmax="1.4600" Material="Metal1" Layer="8" />
<Layer Name="Metal2" Type="conductor" Zmin="2.0000" Zmax="2.4900" Material="Metal2" Layer="10" />
<Layer Name="TopMetal1" Type="conductor" Zmin="6.4303" Zmax="8.4303" Material="TopMetal1" Layer="126" />
<Layer Name="TopMetal2" Type="conductor" Zmin="11.2303" Zmax="14.2303" Material="TopMetal2" Layer="134" />
<Layer Name="SUBGND" Type="conductor" Zmin="-3.75" Zmax="0" Material="LOWLOSS" Layer="250" />
<Layer Name="BACKSIDEGND" Type="conductor" Zmin="-190" Zmax="-183.75" Material="LOWLOSS" Layer="251" />
<Layer Name="MIM" Type="conductor" Zmin="5.6043" Zmax="5.7540" Material="MIM" Layer="36" />
<Layer Name="Vmim" Type="via" Zmin="5.7540" Zmax="6.4303" Material="Vmim" Layer="129" />
<Layer Name="LBE" Type="dielectric" Zmin="-183.75" Zmax="0" Material="Air" Layer="157" />
</Layers>
</ELayers>
</Stackup>
Simulation Configuration File
The simulation configuration file (.simcfg) defines how to mesh and run the electromagnetic simulation in Palace.
You can either tweak existing .simcfg files from the src/orca/geometry/examples/ directory, create your own from scratch,
or use the GUI provided by setupEM to create the .simcfg file interactively.
Example:
{
"application": "setupEM",
"data_format": "1.0",
"saved_values": {
"preprocess_gds": true,
"merge_polygon_size": 0.5,
"purpose": [
0
],
"fstart": 0.0,
"fstop": 170.0,
"refined_cellsize": 2.0,
"order": 2,
"cells_per_wavelength": 20.0,
"meshsize_max": 100.0,
"adaptive_mesh_iterations": 0,
"iterative": false,
"boundary": [
"PEC",
"PEC",
"PEC",
"PEC",
"PEC",
"PEC"
],
"margin": 200.0,
"air_around": 200.0,
"ELMER_MPI_THREADS": 4,
"model_basename": "tf_octa_c_ports",
"sim_path": "/home/users/simone/OpenSource_LNA",
"fstep": 1.0
},
"ports": [
{
"portnumber": 1,
"source_layernum": 201,
"target_layername": null,
"from_layername": "Metal5",
"to_layername": "TopMetal1",
"direction": "Z",
"port_Z0": 50.0,
"voltage": 1.0
},
{
"portnumber": 2,
"source_layernum": 204,
"target_layername": null,
"from_layername": "Metal5",
"to_layername": "TopMetal1",
"direction": "Z",
"port_Z0": 50.0,
"voltage": 1.0
},
{
"portnumber": 3,
"source_layernum": 202,
"target_layername": null,
"from_layername": "Metal5",
"to_layername": "TopMetal2",
"direction": "Z",
"port_Z0": 50.0,
"voltage": 1.0
},
{
"portnumber": 4,
"source_layernum": 203,
"target_layername": null,
"from_layername": "Metal5",
"to_layername": "TopMetal2",
"direction": "Z",
"port_Z0": 50.0,
"voltage": 1.0
}
]
}