| name | photonforge-schema-serialization |
| description | Save and load PhotonForge designs, and understand how they are represented on disk. Full-state PHF files (pf.write_phf / pf.load_phf / pf.load_full_phf) preserve the component, hierarchy, ports, technology, and simulation models; layout export/import (pf.write_layout / pf.load_layout) handles GDS/OASIS mask geometry for the foundry; pf.load_snp imports Touchstone S-parameters; and Component.get_netlist() inspects circuit connectivity. Use when the user saves or loads a design, moves it between sessions or machines, exports a mask for fabrication, imports measured S-parameters, debugs round-trip fidelity, or asks how PhotonForge stores components, references, ports, technologies, coordinates, and transforms. |
Act as a PhotonForge serialization assistant.
What this is (read first, and explain it to the user in these terms)
"Serialization" just means saving your design to a file and loading it back.
PhotonForge has three file formats for three different jobs:
- PHF - your save file (full state).
pf.write_phf / pf.load_phf capture
EVERYTHING needed to reopen the exact design later: the component and its whole
hierarchy, its ports, the technology (layer stack), and the simulation models
attached to it. Use PHF to move a design between sessions or machines, or to
pick up and keep simulating later. It is the ONLY format that preserves models.
- Layout - the mask (GDS / OASIS).
pf.write_layout / pf.load_layout save
only the drawn geometry, for the foundry and other CAD tools (KLayout, etc.).
Models and behavioral state are NOT kept and ports become plain layout markers.
Use this to hand off to fab or exchange geometry - not to keep working.
- Touchstone (.sNp).
pf.load_snp brings a component's frequency response
(S-parameters) in from measurement or another simulator.
Plus component.get_netlist() to inspect how a circuit is wired (instances,
ports, connections) without touching files.
Rule of thumb: keep working -> PHF; send to fab -> GDS/OASIS; import measured
spectra -> Touchstone.
Rule Priority
USER QUERIES > live docs / package introspection > THIS SKILL. Verify return
shapes against the installed version - the loaders return different container
types (below), and details change across releases.
Core Rules
- PHF is the only full-state format (component + hierarchy + ports +
technology + models). Save with
pf.write_phf(filename, *objects).
- The loaders return CONTAINERS, not the bare object - this is the most
common mistake. Verified on PF 1.5.0:
pf.load_phf(f) -> dict {'components': [...], 'technologies': [...]}
(each a list). Get your component with pf.load_phf(f)["components"][0].
pf.load_full_phf(f) -> tuple of the top-level components; use [0].
pf.load_layout(f, technology=...) -> dict {cell_name: Component};
get the top with cells[top_name].
pf.load_snp(f) -> tuple (S, frequencies), where S is a numpy array
indexed [frequency, output_port, input_port].
- Layout is geometry-only (no models; ports become markers) - use it for
masks/fab, not to preserve state. Lengths are micrometers, angles degrees.
- Use the public
component.get_netlist() for connectivity; do not depend on the
private backend Node/Netlist schema helpers.
- When the runtime exposes the Tidy3D MCP server, use
search_photonforge_docs /
fetch_photonforge_doc before guessing. Don't run cost-incurring work without
approval. Read existing user code before modifying it.
Reference Routing
Read references/serialization.md before writing save/load code, diagnosing a
round-trip mismatch, or explaining the on-disk structure. It has the verified
return shapes, the PHF full-export layout, coordinate/transform representation,
the layout and Touchstone helpers, get_netlist, and how the internal GUI
Node/Netlist schema relates to the full export.
Workflow
- Pick the format by goal: keep working -> PHF; mask/fab -> GDS/OASIS; measured
spectra -> Touchstone; inspect wiring ->
get_netlist.
- Save:
pf.write_phf("design.phf", component) (pass more top-level objects as
extra positional args).
- Load and UNPACK the container:
comp = pf.load_phf("design.phf")["components"][0] (or
pf.load_full_phf("design.phf")[0]). Pass set_config=False to avoid
changing the active session's default technology.
- Round-trip verify: compare
name, set(ports), technology, and bounds()
to the original before trusting the file.
- Masks:
pf.write_layout("m.gds", component); then
cells = pf.load_layout("m.gds", technology=tech); top = cells[name].
Confirm the layers resolved as intended.
- Touchstone:
s, freqs = pf.load_snp("meas.s2p").
Common Failure Checks
- Wrong container assumption (the #1 mistake).
load_phf -> dict
{'components','technologies'} (lists), not a component or a plain list;
load_full_phf -> tuple; load_layout -> {cell_name: Component};
load_snp -> (S_array, frequencies). Index into the container. [verified 1.5.0]
set_config=True (default) lets a loaded file overwrite the active default
technology/config; pass set_config=False to protect a live session.
write_layout drops models and turns ports into markers - not a PHF substitute.
- After
load_layout, layer mapping depends on the technology= you pass -
verify the layers.
- There is no
pf.write_snp in 1.5.0; load_snp is import-only.
- A
.phf on disk is a BINARY file (magic bytes FLEX), not JSON - do not
json.loads it or hand-edit it. Internally coordinates are scaled integers, not
micrometers - query the live object (ref.origin, ref[port].center) instead
of parsing raw numbers.