Skip to content

Custom Classes

If you want to simulate and predict your own geometry, you need to create three files:

  • A python file extending the BaseGeometry class 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.

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.

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.

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 time geometry.dataset is read, so each instance gets its own dataset and normalizer statistics.

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
        }
    ]
}