"""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
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, RWA policy, 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),
"rwa": chip.rwa,
"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.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
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", []):
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", [])]
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"),
rwa=bool(data.get("rwa", True)),
baths=baths 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,
rwa=chip.rwa,
backend=chip._backend,
baths=[bath.copy() for bath in chip.baths] or None,
)
if chip.control_equipment is not None:
cloned.connect(chip.control_equipment.copy(device_map, cloned.coupling_map))
return cloned