"""Chip serialization, deserialization, and structural cloning.
These helpers turn a :class:`~quchip.chip.chip.Chip` into a JSON-safe
dict and back (via the device / coupling registries), and produce
isolated structural clones suitable for sweep evaluation.
Cloning is structural, not numerical: devices are copied fresh,
couplings are rebound to the cloned device instances, and control
equipment — when attached — is cloned and reconnected. Clones keep the
chip-specific backend selection so sweeps run on the same backend as
the original.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Literal, cast
from quchip.approximations import Approximation
from quchip.chip.coupling_base import BaseCoupling
from quchip.control.equipment import ControlEquipment
from quchip.devices.base import BaseDevice
if TYPE_CHECKING:
from quchip.chip.chip import Chip
def _serialize_frame(raw_frame: Any) -> str | float | dict[str, float]:
"""Normalize a chip frame spec into a JSON-safe value."""
if isinstance(raw_frame, dict):
return {str(k): float(v) for k, v in raw_frame.items()}
if isinstance(raw_frame, (int, float)):
return float(raw_frame)
return raw_frame
[docs]
def serialize_chip(chip: "Chip") -> dict[str, Any]:
"""Serialize chip topology into a JSON-safe dictionary.
Captures devices, couplings, baths, frame, approximation strategy, and, if
present — the control equipment wiring. The chip label (if any) is
included verbatim. Backend identity is *not* serialized;
deserialization uses the process default backend unless changed
afterwards.
"""
data: dict[str, Any] = {
"label": chip.label,
"frame": _serialize_frame(chip.frame),
"approximation": chip.approximation.to_dict(),
"basis": chip.basis,
"devices": [device.to_dict() for device in chip.devices],
"couplings": [coupling.to_dict() for coupling in chip.couplings],
}
if chip.baths:
data["baths"] = [bath.to_dict() for bath in chip.baths]
if chip.ports:
data["ports"] = [port.to_dict() for port in chip.ports]
if chip.control_equipment is not None:
data["control_equipment"] = chip.control_equipment.to_dict()
return data
[docs]
def deserialize_chip(data: dict[str, Any]) -> "Chip":
"""Reconstruct a chip from :func:`serialize_chip` output.
Device and coupling classes are resolved through their shared
:class:`~quchip.utils.registry.Registrable` registries (via
:meth:`BaseDevice.from_dict` / :meth:`BaseCoupling.from_dict`), which
are populated at subclass-definition time, so any extension module must
be imported before deserialization.
"""
from quchip.chip.chip import Chip
allowed = {
"label",
"frame",
"approximation",
"basis",
"devices",
"couplings",
"baths",
"ports",
"control_equipment",
}
unknown = set(data) - allowed
if unknown:
raise TypeError(f"Unsupported serialized Chip fields: {sorted(unknown)}")
if "approximation" not in data:
raise TypeError("Serialized Chip payload is missing required field 'approximation'.")
approximation = Approximation.from_dict(data["approximation"])
devices = [BaseDevice.from_dict(d) for d in data.get("devices", [])]
device_map = {device.label: device for device in devices}
couplings: list[BaseCoupling] = []
for cd in data.get("couplings", []):
if "rwa" in cd:
raise TypeError("Unsupported serialized coupling fields: ['rwa']")
couplings.append(
BaseCoupling.from_dict(cd, device_map[cd["device_a_label"]], device_map[cd["device_b_label"]])
)
coupling_map = {coupling.label: coupling for coupling in couplings}
from quchip.chip.baths import Bath
baths = [Bath.from_dict(bd) for bd in data.get("baths", [])]
from quchip.chip.ports import Port
ports = [Port.from_dict(pd) for pd in data.get("ports", [])]
control_equipment = (
ControlEquipment.from_dict(data["control_equipment"], device_map, coupling_map)
if "control_equipment" in data
else None
)
chip = Chip(
devices=devices,
couplings=couplings or None,
control_equipment=None,
label=data.get("label"),
frame=data.get("frame", "lab"),
approximation=approximation,
basis=cast(Literal["native", "eigen"], data.get("basis", "native")),
baths=baths or None,
ports=ports or None,
)
if control_equipment is not None:
chip.connect(control_equipment)
return chip
[docs]
def clone_chip(chip: "Chip") -> "Chip":
"""Isolated structural clone suitable for sweep evaluation.
Devices are copied and decoupled from their original drive wiring;
couplings are rebound to the cloned device instances. Control
equipment, when present, is cloned and reconnected so the clone's
drives target the clone's devices — not the originals.
"""
from quchip.chip.chip import Chip
devices = [device.copy() for device in chip.devices]
device_map = {device.label: device for device in devices}
couplings = [coupling.copy(device_map) for coupling in chip.couplings]
frame = dict(chip.frame) if isinstance(chip.frame, dict) else chip.frame
cloned = Chip(
devices=devices,
couplings=couplings or None,
control_equipment=None,
label=chip.label,
frame=frame,
approximation=chip.approximation,
basis=chip.basis,
backend=chip._backend,
baths=[bath.copy() for bath in chip.baths] or None,
ports=[port.copy() for port in chip.ports] or None,
)
if chip.control_equipment is not None:
cloned.connect(chip.control_equipment.copy(device_map, cloned.coupling_map))
return cloned