oraql-contracts 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oraql/contracts/__init__.py +0 -0
- oraql/contracts/contract_examples.py +73 -0
- oraql/contracts/contracts.py +466 -0
- oraql/contracts/data/schemas/backend_request.json +93 -0
- oraql/contracts/data/schemas/backend_result.json +106 -0
- oraql/contracts/data/schemas/calibration_record.json +231 -0
- oraql/contracts/data/schemas/calibration_update.json +128 -0
- oraql/contracts/data/schemas/circuit_lowering.json +137 -0
- oraql/contracts/data/schemas/common.json +142 -0
- oraql/contracts/data/schemas/examples/BackendRequest.json +11 -0
- oraql/contracts/data/schemas/examples/BackendResult.json +32 -0
- oraql/contracts/data/schemas/examples/CalibrationRecord.json +77 -0
- oraql/contracts/data/schemas/examples/CalibrationRecord_rb87.json +210 -0
- oraql/contracts/data/schemas/examples/CalibrationUpdate.json +25 -0
- oraql/contracts/data/schemas/examples/CircuitLowering.json +49 -0
- oraql/contracts/data/schemas/examples/LogicalLowering.json +48 -0
- oraql/contracts/data/schemas/examples/NativeProgram.json +158 -0
- oraql/contracts/data/schemas/examples/PhysicalParameters.json +45 -0
- oraql/contracts/data/schemas/examples/PhysicalParameters_cs133_radnaev2024.json +517 -0
- oraql/contracts/data/schemas/examples/PhysicalParameters_rb87.json +536 -0
- oraql/contracts/data/schemas/examples/PhysicalParameters_rb87_bluvstein2024.json +538 -0
- oraql/contracts/data/schemas/examples/PhysicalParameters_rb87_evered2023.json +536 -0
- oraql/contracts/data/schemas/examples/ProgramDocument.json +34 -0
- oraql/contracts/data/schemas/logical_lowering.json +167 -0
- oraql/contracts/data/schemas/native_program.json +190 -0
- oraql/contracts/data/schemas/physical_parameters.json +155 -0
- oraql/contracts/data/schemas/program_document.json +110 -0
- oraql/contracts/resources.py +17 -0
- oraql/contracts/schema.py +28 -0
- oraql_contracts-0.1.0.dist-info/METADATA +25 -0
- oraql_contracts-0.1.0.dist-info/RECORD +33 -0
- oraql_contracts-0.1.0.dist-info/WHEEL +4 -0
- oraql_contracts-0.1.0.dist-info/licenses/LICENSE +204 -0
|
File without changes
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""One valid example instance per contract (a 2-atom Bell-pair program), numbers computed here."""
|
|
2
|
+
import math, json, hashlib
|
|
3
|
+
|
|
4
|
+
def sha(o):
|
|
5
|
+
return hashlib.sha256(json.dumps(o, sort_keys=True).encode()).hexdigest()[:16]
|
|
6
|
+
|
|
7
|
+
def examples():
|
|
8
|
+
t2, t1 = 1000.0, 5.0e6 # us, assumed values for the example only
|
|
9
|
+
t_cz = 0.4 # us, assumed
|
|
10
|
+
pauli2 = [0.0] * 15
|
|
11
|
+
pauli2[14] = 2e-3 # ZZ
|
|
12
|
+
pauli2[2] = 1e-3 # IZ
|
|
13
|
+
pauli2[11] = 1e-3 # ZI
|
|
14
|
+
phys = {
|
|
15
|
+
"schema_version": "0.1.0", "profile_id": "example", "species": "Rb87",
|
|
16
|
+
"sites": [{"id": 0, "x": 0.0, "y": 0.0, "zone": "entangling", "trap": "tweezer_array"},
|
|
17
|
+
{"id": 1, "x": 4.0, "y": 0.0, "zone": "entangling", "trap": "tweezer_array"}],
|
|
18
|
+
"parameters": [
|
|
19
|
+
{"name": "t2_us", "value": t2, "unit": "us", "source": "assumed", "valid_lo": 1.0, "valid_hi": 1e6},
|
|
20
|
+
{"name": "blockade_radius_um", "value": 8.0, "unit": "um", "source": "assumed"},
|
|
21
|
+
{"name": "c6_ghz_um6", "value": 30.0, "unit": "GHz*um^6", "source": "computed",
|
|
22
|
+
"method": "ARC 3.x, PairStateInteractions(Rubidium87, 53,0,0.5, ...).getC6perturbatively", "note": "illustrative value"},
|
|
23
|
+
]}
|
|
24
|
+
upd = {"schema_version": "0.1.0", "experiment_id": "ramsey-0001", "experiment_kind": "ramsey_t2",
|
|
25
|
+
"timestamp": "2026-10-08T12:00:00Z", "data_ref": "data/ramsey-0001.csv",
|
|
26
|
+
"fits": [{"name": "t2_us", "value": 950.0, "unit": "us", "stderr": 30.0, "reduced_chi2": 1.1}],
|
|
27
|
+
"updates": [{"name": "t2_us", "old_value": 1000.0, "new_value": 950.0, "unit": "us", "prior_weight": 0.2}]}
|
|
28
|
+
cal = {"schema_version": "0.1.0", "physical_ref": sha(phys), "cz_protocol": "time_optimal_two_pulse",
|
|
29
|
+
"generated_at": "2026-10-08T12:05:00Z",
|
|
30
|
+
"primitives": [
|
|
31
|
+
{"primitive": "rz", "arity": 1, "duration_us": 0.0, "pauli1": {"px": 0.0, "py": 0.0, "pz": 1e-5}, "p_loss": [0.0]},
|
|
32
|
+
{"primitive": "cz", "arity": 2, "duration_us": t_cz, "pauli2": {"p": pauli2},
|
|
33
|
+
"p_loss": [2e-3, 2e-3], "p_loss_corr": [1e-4, 1e-3, 0.05], "twirl_residual": 3e-4}],
|
|
34
|
+
"idle": {"t1_us": t1, "t2_us": t2, "loss_rate_per_us": 1e-7},
|
|
35
|
+
"readout": {"p_flip_0to1": 0.002, "p_flip_1to0": 0.01, "p_loss_readout": 0.003, "duration_us": 5000.0},
|
|
36
|
+
"approximations": [{"kind": "coherent_error", "bound": 3e-4, "note": "twirled, coherent part dropped"},
|
|
37
|
+
{"kind": "leakage_as_loss", "note": "Rydberg decay treated as loss"}]}
|
|
38
|
+
ops = [
|
|
39
|
+
{"op_id": 0, "name": "rx", "targets": [0], "params": [math.pi / 2], "start_us": 0.0, "duration_us": 0.5, "source_op": 0,
|
|
40
|
+
"noise": [{"kind": "pauli1", "targets": [0], "p": [0.0, 0.0, 1e-5]}], "spectators": [1]},
|
|
41
|
+
{"op_id": 1, "name": "cz", "targets": [0, 1], "start_us": 0.5, "duration_us": t_cz, "source_op": 1, "parallel_group": 0, "zone": "entangling",
|
|
42
|
+
"noise": [{"kind": "pauli2", "targets": [0, 1], "p": pauli2},
|
|
43
|
+
{"kind": "loss", "targets": [0], "p": [2e-3]}, {"kind": "loss", "targets": [1], "p": [2e-3]}]},
|
|
44
|
+
{"op_id": 2, "name": "measure", "targets": [0], "start_us": 0.9, "duration_us": 5000.0, "source_op": 2, "measure_slot": 0,
|
|
45
|
+
"noise": [{"kind": "readout_flip", "targets": [0], "p": [0.002]}, {"kind": "loss", "targets": [0], "p": [0.003]}]},
|
|
46
|
+
{"op_id": 3, "name": "measure", "targets": [1], "start_us": 0.9, "duration_us": 5000.0, "source_op": 2, "measure_slot": 1, "parallel_group": 1,
|
|
47
|
+
"noise": [{"kind": "readout_flip", "targets": [1], "p": [0.002]}, {"kind": "loss", "targets": [1], "p": [0.003]}]},
|
|
48
|
+
]
|
|
49
|
+
nat = {"schema_version": "0.1.0", "n_atoms": 2, "calibration_ref": sha(cal), "ops": ops, "total_duration_us": 5000.9, "diagnostics": []}
|
|
50
|
+
doc = {"format_version": "0.1.0", "dialect": "nadsl", "n_qubits": 2,
|
|
51
|
+
"statements": [
|
|
52
|
+
{"kind": "gate", "name": "rx", "targets": [0], "args": [math.pi / 2]},
|
|
53
|
+
{"kind": "gate", "name": "cz", "targets": [0, 1]},
|
|
54
|
+
{"kind": "measure", "name": "m", "targets": [0, 1]}],
|
|
55
|
+
"text": "rx(1.5707963267948966) 0\ncz 0 1\nm 0 1\n"}
|
|
56
|
+
low = {"schema_version": "0.1.0", "circuit_ref": sha(doc), "native_target": "gemini_one_zone", "calibration_ref": sha(cal),
|
|
57
|
+
"qubit_map": [{"circuit_qubit": 0, "atom_id": 0}, {"circuit_qubit": 1, "atom_id": 1}],
|
|
58
|
+
"lowered": [{"circuit_op": 0, "native_ops": [0], "rule": "identity", "exact": True, "feed_forward": "none"},
|
|
59
|
+
{"circuit_op": 1, "native_ops": [1], "rule": "identity", "exact": True, "feed_forward": "none"},
|
|
60
|
+
{"circuit_op": 2, "native_ops": [2, 3], "rule": "measure_all", "exact": True, "feed_forward": "none"}],
|
|
61
|
+
"scheduler": "asap", "native_program_ref": sha(nat), "diagnostics": []}
|
|
62
|
+
logical = {"schema_version": "0.1.0",
|
|
63
|
+
"blocks": [{"block_id": 0, "code": "repetition", "distance": 3, "data_qubits": [0, 1, 2], "ancilla_qubits": [3, 4],
|
|
64
|
+
"rounds": 1, "stabilizers": ["ZZI", "IZZ"]}],
|
|
65
|
+
"detectors": [{"id": 0, "meas_indices": [0]}, {"id": 1, "meas_indices": [1]}],
|
|
66
|
+
"observables": [{"id": 0, "meas_indices": [2]}], "loss_policy": "erasure_decode", "circuit_ref": sha(doc)}
|
|
67
|
+
req = {"schema_version": "0.1.0", "native_program_ref": sha(nat), "backend": "GT", "emit_mode": "python_api", "shots": 100000, "seed": 1,
|
|
68
|
+
"truncation": {"min_abs_coeff": 1e-10}}
|
|
69
|
+
res = {"schema_version": "0.1.0", "request_ref": sha(req), "backend": "GT", "outcomes": [[0, 0], [1, 1], [2, 0]], "measure_labels": [0, 1],
|
|
70
|
+
"std_error": 0.003, "lost_fraction": [0.003, 0.003],
|
|
71
|
+
"truncation_report": {"biased_low": False}, "diagnostics": []}
|
|
72
|
+
return {"PhysicalParameters": phys, "CalibrationUpdate": upd, "CalibrationRecord": cal, "NativeProgram": nat,
|
|
73
|
+
"CircuitLowering": low, "LogicalLowering": logical, "ProgramDocument": doc, "BackendRequest": req, "BackendResult": res}
|
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
"""Single source of truth for the interface contracts of the neutral-atom DSL stack.
|
|
2
|
+
|
|
3
|
+
Everything else is generated from this module:
|
|
4
|
+
- packages/oraql-contracts/src/oraql/contracts/data/schemas/*.json (JSON Schema, draft 2020-12)
|
|
5
|
+
- the contract tables in design/01_architecture.md
|
|
6
|
+
- the dependency diagram (scripts/architecture_diagram.py)
|
|
7
|
+
Run `python -m scripts.build_architecture` from the repo root to regenerate.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
|
|
12
|
+
BASE_URI = "https://nadsl.local/schemas/"
|
|
13
|
+
UNIT_NONE = "1"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class F:
|
|
18
|
+
"""One typed field. type grammar: number | integer | string | boolean | enum:a|b|c |
|
|
19
|
+
ref:Record | array<T> (T any of the above). n fixes an array length."""
|
|
20
|
+
name: str
|
|
21
|
+
type: str
|
|
22
|
+
unit: str = UNIT_NONE
|
|
23
|
+
rng: tuple | None = None
|
|
24
|
+
desc: str = ""
|
|
25
|
+
req: bool = True
|
|
26
|
+
n: int | None = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class Record:
|
|
31
|
+
name: str
|
|
32
|
+
file: str
|
|
33
|
+
desc: str
|
|
34
|
+
fields: list[F]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class Contract:
|
|
39
|
+
name: str # root record name, also the diagram node label
|
|
40
|
+
file: str # schema file
|
|
41
|
+
layers: tuple # (producer layer id, consumer layer id)
|
|
42
|
+
pair: str # interface it belongs to
|
|
43
|
+
producer: str
|
|
44
|
+
consumer: str
|
|
45
|
+
status: str # existing format / adapter / new
|
|
46
|
+
reuse: str
|
|
47
|
+
invariants: list[str]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
LAYERS = {
|
|
51
|
+
"L1": "Physical hardware and array",
|
|
52
|
+
"L2": "Pulse and calibration",
|
|
53
|
+
"L3": "Native gates and movement",
|
|
54
|
+
"L4": "Circuit",
|
|
55
|
+
"L5": "Logical / QEC",
|
|
56
|
+
"B": "PPVM backend",
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
P = (0.0, 1.0)
|
|
60
|
+
|
|
61
|
+
RECORDS: list[Record] = [
|
|
62
|
+
# ---------------------------------------------------------------- common
|
|
63
|
+
Record("Diagnostic", "common.json", "A compile or validation message. Unsupported constructs are never dropped silently.", [
|
|
64
|
+
F("code", "string", desc="stable id, for example `PPVM-UNSUPPORTED-COHERENT`"),
|
|
65
|
+
F("severity", "enum:error|warning|info"),
|
|
66
|
+
F("construct", "string", desc="name of the construct, for example `coherent_crosstalk`"),
|
|
67
|
+
F("layer", "enum:L1|L2|L3|L4|L5|B", desc="layer that raised it"),
|
|
68
|
+
F("message", "string"),
|
|
69
|
+
F("location", "string", desc="op id, statement index or parameter name", req=False),
|
|
70
|
+
F("reduction", "string", desc="approximation applied instead, when severity is warning", req=False),
|
|
71
|
+
]),
|
|
72
|
+
Record("Pauli1Probs", "common.json", "Single-qubit Pauli channel, PPVM `pauli_error(p=[px,py,pz])`.", [
|
|
73
|
+
F("px", "number", rng=P, desc="X error probability"),
|
|
74
|
+
F("py", "number", rng=P, desc="Y error probability"),
|
|
75
|
+
F("pz", "number", rng=P, desc="Z error probability"),
|
|
76
|
+
]),
|
|
77
|
+
Record("Pauli2Probs", "common.json", "Two-qubit Pauli channel, PPVM `two_qubit_pauli_error(p=[...15])`.", [
|
|
78
|
+
F("p", "array<number>", rng=P, n=15, desc="order IX IY IZ XI XX XY XZ YI YX YY YZ ZI ZX ZY ZZ, identity is 1 minus the sum"),
|
|
79
|
+
]),
|
|
80
|
+
Record("ApproximationNote", "common.json", "A recorded reduction of a physical effect to Pauli+loss.", [
|
|
81
|
+
F("kind", "enum:coherent_error|leakage|leakage_as_loss|amplitude_damping_twirl|crosstalk|motional_heating|readout_flip|state_dependent_loss|other"),
|
|
82
|
+
F("bound", "number", rng=(0.0, 1.0), desc="norm bound on what was discarded, dimensionless", req=False),
|
|
83
|
+
F("note", "string"),
|
|
84
|
+
]),
|
|
85
|
+
# ---------------------------------------------------------------- C1
|
|
86
|
+
Record("PhysicalParameters", "physical_parameters.json", "Parameters of atoms, traps, interaction and hardware. Produced by L1, consumed by L2.", [
|
|
87
|
+
F("schema_version", "string", desc="semver of this schema"),
|
|
88
|
+
F("profile_id", "string", desc="named device profile, for example `rb87_evered2023`"),
|
|
89
|
+
F("species", "string", desc="for example `Rb87`"),
|
|
90
|
+
F("sites", "array<ref:Site>", desc="atom array geometry"),
|
|
91
|
+
F("parameters", "array<ref:ParameterEntry>", desc="named parameters, names fixed in 02_physical.md"),
|
|
92
|
+
]),
|
|
93
|
+
Record("Site", "physical_parameters.json", "One trap site.", [
|
|
94
|
+
F("id", "integer", rng=(0, None), desc="atom or site index, unique"),
|
|
95
|
+
F("x", "number", unit="um", desc="x position"),
|
|
96
|
+
F("y", "number", unit="um", desc="y position"),
|
|
97
|
+
F("zone", "string", desc="storage, entangling or readout zone name"),
|
|
98
|
+
F("trap", "enum:slm|aod|tweezer_array"),
|
|
99
|
+
]),
|
|
100
|
+
Record("ParameterEntry", "physical_parameters.json", "One physical parameter with its provenance.", [
|
|
101
|
+
F("name", "string", desc="for example `t2_us`, `c6`, `blockade_radius_um`"),
|
|
102
|
+
F("value", "number", unit="per entry `unit`", desc="value in the unit stated by `unit`"),
|
|
103
|
+
F("unit", "string", desc="unit string, for example `us`, `um`, `MHz`, `rad/us*um^6`"),
|
|
104
|
+
F("source", "enum:citation|computed|assumed|calibrated", desc="the `assumed` flag is mandatory for unsourced values"),
|
|
105
|
+
F("citation", "string", desc="DOI or arXiv id, required when source is citation", req=False),
|
|
106
|
+
F("method", "string", desc="package, version and call, required when source is computed", req=False),
|
|
107
|
+
F("locator", "string", desc="figure, table or page in the cited work", req=False),
|
|
108
|
+
F("note", "string", desc="free text: derivation, bound or reason for an assumption", req=False),
|
|
109
|
+
F("uncertainty", "number", unit="per entry `unit`", rng=(0, None), desc="1 sigma", req=False),
|
|
110
|
+
F("valid_lo", "number", unit="per entry `unit`", desc="lowest physical value", req=False),
|
|
111
|
+
F("valid_hi", "number", unit="per entry `unit`", desc="highest physical value", req=False),
|
|
112
|
+
]),
|
|
113
|
+
# ---------------------------------------------------------------- C2
|
|
114
|
+
Record("CalibrationUpdate", "calibration_update.json", "Fitted result of one calibration experiment and the parameter updates it implies. Produced from L1 data, consumed by L2.", [
|
|
115
|
+
F("schema_version", "string"),
|
|
116
|
+
F("experiment_id", "string"),
|
|
117
|
+
F("experiment_kind", "enum:ramsey_t2|rabi|t1|cz_benchmark|loss_scan|readout_histogram|other"),
|
|
118
|
+
F("timestamp", "string", desc="ISO 8601, UTC"),
|
|
119
|
+
F("data_ref", "string", desc="path or URI of raw data or the plot image (QCalEval style)", req=False),
|
|
120
|
+
F("fits", "array<ref:FitResult>"),
|
|
121
|
+
F("updates", "array<ref:ParameterUpdate>"),
|
|
122
|
+
]),
|
|
123
|
+
Record("FitResult", "calibration_update.json", "One fitted quantity.", [
|
|
124
|
+
F("name", "string", desc="must match a `ParameterEntry.name`"),
|
|
125
|
+
F("value", "number", unit="per `unit`"),
|
|
126
|
+
F("unit", "string"),
|
|
127
|
+
F("stderr", "number", unit="per `unit`", rng=(0, None)),
|
|
128
|
+
F("reduced_chi2", "number", rng=(0, None), desc="goodness of fit", req=False),
|
|
129
|
+
]),
|
|
130
|
+
Record("ParameterUpdate", "calibration_update.json", "Change applied to a `PhysicalParameters` entry.", [
|
|
131
|
+
F("name", "string"),
|
|
132
|
+
F("old_value", "number", unit="per `unit`"),
|
|
133
|
+
F("new_value", "number", unit="per `unit`"),
|
|
134
|
+
F("unit", "string"),
|
|
135
|
+
F("prior_weight", "number", rng=P, desc="weight of the old value in a Bayesian blend"),
|
|
136
|
+
]),
|
|
137
|
+
# ---------------------------------------------------------------- C3
|
|
138
|
+
Record("CalibrationRecord", "calibration_record.json", "Pauli+loss channels per native primitive, exactly as PPVM consumes them. Produced by L2, consumed by L3.", [
|
|
139
|
+
F("schema_version", "string"),
|
|
140
|
+
F("physical_ref", "string", desc="sha256 of the `PhysicalParameters` document it was derived from"),
|
|
141
|
+
F("cz_protocol", "string", desc="pluggable CZ implementation id, default `time_optimal_two_pulse`"),
|
|
142
|
+
F("generated_at", "string", desc="ISO 8601, UTC"),
|
|
143
|
+
F("primitives", "array<ref:PrimitiveChannel>"),
|
|
144
|
+
F("idle", "ref:IdleModel", desc="rates for spectators and waiting atoms"),
|
|
145
|
+
F("readout", "ref:ReadoutModel"),
|
|
146
|
+
F("approximations", "array<ref:ApproximationNote>", desc="everything the twirl discarded"),
|
|
147
|
+
]),
|
|
148
|
+
Record("PrimitiveChannel", "calibration_record.json", "Noise attached to one native primitive.", [
|
|
149
|
+
F("primitive", "enum:rz|rx|ry|r|global_r|cz|move|reset", desc="native primitive name"),
|
|
150
|
+
F("arity", "integer", rng=(1, 2)),
|
|
151
|
+
F("duration_us", "number", unit="us", rng=(0, None)),
|
|
152
|
+
F("pauli1", "ref:Pauli1Probs", desc="present when arity is 1, per target qubit", req=False),
|
|
153
|
+
F("pauli2", "ref:Pauli2Probs", desc="present when arity is 2", req=False),
|
|
154
|
+
F("p_loss", "array<number>", rng=P, desc="independent loss probability per target, length equals arity"),
|
|
155
|
+
F("p_loss_corr", "array<number>", rng=P, n=3, desc="optional [p0,p1,p2] of `correlated_loss_channel`, arity 2 only", req=False),
|
|
156
|
+
F("p_leak", "array<number>", rng=P, desc="part of `p_loss` per target that stays trapped in a non-qubit sublevel (leaked: dark to later gates until a reset, read as a classical bit); the rest is lost", req=False),
|
|
157
|
+
F("p_out_joint", "array<number>", rng=P, n=8, desc="arity 2: joint probabilities of the two atoms ending in qubit (q), leaked (k) or lost (l), cells qk ql kq kk kl lq lk ll (first letter = first target); q,q is 1 minus the sum", req=False),
|
|
158
|
+
F("p_out_alone", "array<number>", rng=P, n=2, desc="arity 2: [leaked, lost] of an atom whose partner is already gone (single-atom pulse); its sum is p2 of `p_loss_corr`", req=False),
|
|
159
|
+
F("twirl_residual", "number", rng=P, desc="norm of the discarded non-Pauli part of the simulated process", req=False),
|
|
160
|
+
]),
|
|
161
|
+
Record("IdleModel", "calibration_record.json", "Idle noise as a function of waiting time t. Consumers evaluate it at t.", [
|
|
162
|
+
F("t1_us", "number", unit="us", rng=(0, None)),
|
|
163
|
+
F("t2_us", "number", unit="us", rng=(0, None)),
|
|
164
|
+
F("loss_rate_per_us", "number", unit="1/us", rng=(0, None), desc="trap lifetime loss, p_loss(t) = 1 - exp(-rate t)"),
|
|
165
|
+
]),
|
|
166
|
+
Record("ReadoutModel", "calibration_record.json", "Measurement errors.", [
|
|
167
|
+
F("p_flip_0to1", "number", rng=P, desc="P(report 1 given 0)"),
|
|
168
|
+
F("p_flip_1to0", "number", rng=P, desc="P(report 0 given 1)"),
|
|
169
|
+
F("p_loss_readout", "number", rng=P, desc="loss during imaging"),
|
|
170
|
+
F("p_leak_reads_one", "number", rng=P, desc="probability that a leaked atom reads as 1", req=False),
|
|
171
|
+
F("duration_us", "number", unit="us", rng=(0, None)),
|
|
172
|
+
]),
|
|
173
|
+
# ---------------------------------------------------------------- C4
|
|
174
|
+
Record("NativeProgram", "native_program.json", "Scheduled native ops, each with its noise attached. Produced by L3, consumed by the backend.", [
|
|
175
|
+
F("schema_version", "string"),
|
|
176
|
+
F("n_atoms", "integer", rng=(1, 2048), desc="PPVM `MAX_N_QUBITS` is 2048"),
|
|
177
|
+
F("calibration_ref", "string", desc="sha256 of the `CalibrationRecord` used"),
|
|
178
|
+
F("ops", "array<ref:NativeOpRecord>", desc="ordered by start_us, then op_id"),
|
|
179
|
+
F("total_duration_us", "number", unit="us", rng=(0, None), desc="critical path of the schedule"),
|
|
180
|
+
F("diagnostics", "array<ref:Diagnostic>", req=False),
|
|
181
|
+
]),
|
|
182
|
+
Record("NativeOpRecord", "native_program.json", "The native-op-with-noise record.", [
|
|
183
|
+
F("op_id", "integer", rng=(0, None), desc="unique, increasing"),
|
|
184
|
+
F("name", "enum:rz|rx|ry|r|global_r|cz|move|idle|measure|reset"),
|
|
185
|
+
F("targets", "array<integer>", desc="atom ids"),
|
|
186
|
+
F("params", "array<number>", unit="rad", desc="rotation angles, or move vector in um for `move`", req=False),
|
|
187
|
+
F("start_us", "number", unit="us", rng=(0, None)),
|
|
188
|
+
F("duration_us", "number", unit="us", rng=(0, None)),
|
|
189
|
+
F("zone", "string", desc="zone in which the op acts", req=False),
|
|
190
|
+
F("parallel_group", "integer", rng=(0, None), desc="ops with equal group run simultaneously", req=False),
|
|
191
|
+
F("spectators", "array<integer>", desc="atoms that idle during this op and receive idle noise", req=False),
|
|
192
|
+
F("noise", "array<ref:NoiseChannel>", desc="channels applied after the ideal op, in order"),
|
|
193
|
+
F("source_op", "integer", rng=(-1, None), desc="index of the circuit statement it came from, -1 if inserted by the scheduler"),
|
|
194
|
+
F("measure_slot", "integer", rng=(0, None), desc="position in the measurement record, `measure` only", req=False),
|
|
195
|
+
]),
|
|
196
|
+
Record("NoiseChannel", "native_program.json", "One PPVM channel. `p` has a fixed length per kind.", [
|
|
197
|
+
F("kind", "enum:pauli1|pauli2|loss|correlated_loss|asymmetric_loss|reset_loss|amplitude_damping|readout_flip|leak|correlated_leak_loss",
|
|
198
|
+
desc="p length: pauli1 3, pauli2 15, loss 1, correlated_loss 3, asymmetric_loss 2, reset_loss 0, amplitude_damping 1, readout_flip 1, "
|
|
199
|
+
"leak 2 ([p, p_reads_one]), correlated_leak_loss 11 (the 8 cells of `p_out_joint`, the 2 of `p_out_alone`, p_reads_one). "
|
|
200
|
+
"A leaked atom skips later gates like a lost one, a reset returns it to the qubit, and its measurement gives 1 with p_reads_one"),
|
|
201
|
+
F("targets", "array<integer>", desc="atom ids, 1 or 2 depending on kind"),
|
|
202
|
+
F("p", "array<number>", rng=P, desc="probabilities, see kind"),
|
|
203
|
+
]),
|
|
204
|
+
# ---------------------------------------------------------------- C5
|
|
205
|
+
Record("CircuitLowering", "circuit_lowering.json", "Result of lowering a circuit to the native set. Produced by the L3 compiler from an L4 circuit.", [
|
|
206
|
+
F("schema_version", "string"),
|
|
207
|
+
F("circuit_ref", "string", desc="sha256 of the canonical `ProgramDocument` text"),
|
|
208
|
+
F("native_target", "string", desc="native gate set id, for example `gemini_one_zone`"),
|
|
209
|
+
F("calibration_ref", "string"),
|
|
210
|
+
F("qubit_map", "array<ref:QubitPlacement>"),
|
|
211
|
+
F("lowered", "array<ref:LoweredOp>"),
|
|
212
|
+
F("scheduler", "enum:asap|alap|layered"),
|
|
213
|
+
F("native_program_ref", "string", desc="sha256 of the resulting `NativeProgram`"),
|
|
214
|
+
F("diagnostics", "array<ref:Diagnostic>"),
|
|
215
|
+
]),
|
|
216
|
+
Record("QubitPlacement", "circuit_lowering.json", "Circuit qubit to atom.", [
|
|
217
|
+
F("circuit_qubit", "integer", rng=(0, None)),
|
|
218
|
+
F("atom_id", "integer", rng=(0, None), desc="`Site.id`"),
|
|
219
|
+
]),
|
|
220
|
+
Record("LoweredOp", "circuit_lowering.json", "Provenance of one circuit statement.", [
|
|
221
|
+
F("circuit_op", "integer", rng=(0, None), desc="statement index"),
|
|
222
|
+
F("native_ops", "array<integer>", desc="`NativeOpRecord.op_id` list"),
|
|
223
|
+
F("rule", "string", desc="rewrite rule id, for example `cx_to_h_cz_h`"),
|
|
224
|
+
F("exact", "boolean", desc="equal to the source up to global phase"),
|
|
225
|
+
F("approx_error", "number", rng=P, desc="unitary infidelity bound when not exact", req=False),
|
|
226
|
+
F("feed_forward", "enum:none|outcome|loss_flag", desc="classical condition kept on the lowered ops"),
|
|
227
|
+
]),
|
|
228
|
+
# ---------------------------------------------------------------- C6
|
|
229
|
+
Record("LogicalLowering", "logical_lowering.json", "Logical program expanded to a physical circuit. Produced by L5, consumed by L4.", [
|
|
230
|
+
F("schema_version", "string"),
|
|
231
|
+
F("blocks", "array<ref:CodeBlock>"),
|
|
232
|
+
F("detectors", "array<ref:Detector>"),
|
|
233
|
+
F("observables", "array<ref:LogicalObservable>"),
|
|
234
|
+
F("loss_policy", "enum:erasure_decode|postselect|ignore", desc="how LOST outcomes are used by the decoder"),
|
|
235
|
+
F("circuit_ref", "string", desc="sha256 of the emitted physical `ProgramDocument`"),
|
|
236
|
+
]),
|
|
237
|
+
Record("CodeBlock", "logical_lowering.json", "One code block.", [
|
|
238
|
+
F("block_id", "integer", rng=(0, None)),
|
|
239
|
+
F("code", "enum:repetition|css_4_2_2|bacon_shor|five_qubit|surface_patch|distillation_15to1|other"),
|
|
240
|
+
F("distance", "integer", rng=(1, None)),
|
|
241
|
+
F("data_qubits", "array<integer>", desc="circuit qubit ids"),
|
|
242
|
+
F("ancilla_qubits", "array<integer>", desc="circuit qubit ids"),
|
|
243
|
+
F("rounds", "integer", rng=(0, None), desc="syndrome rounds"),
|
|
244
|
+
F("stabilizers", "array<string>", desc="Pauli words over `data_qubits` positions, for example `ZZI`"),
|
|
245
|
+
]),
|
|
246
|
+
Record("Detector", "logical_lowering.json", "Parity of measurement records that is deterministic without noise (Stim `DETECTOR`).", [
|
|
247
|
+
F("id", "integer", rng=(0, None)),
|
|
248
|
+
F("meas_indices", "array<integer>", desc="absolute indices into the measurement record"),
|
|
249
|
+
F("coords", "array<number>", unit="um or round index", req=False),
|
|
250
|
+
]),
|
|
251
|
+
Record("LogicalObservable", "logical_lowering.json", "Logical observable (Stim `OBSERVABLE_INCLUDE`).", [
|
|
252
|
+
F("id", "integer", rng=(0, None)),
|
|
253
|
+
F("meas_indices", "array<integer>"),
|
|
254
|
+
]),
|
|
255
|
+
# ---------------------------------------------------------------- C7
|
|
256
|
+
Record("ProgramDocument", "program_document.json", "Structured form of a DSL program. The builder produces it, the printer renders it, the parser reads it back. Lives at L4.", [
|
|
257
|
+
F("format_version", "string"),
|
|
258
|
+
F("dialect", "enum:nadsl|stim|bloqade_squin|openqasm3", desc="dialect of `text`"),
|
|
259
|
+
F("n_qubits", "integer", rng=(1, 2048)),
|
|
260
|
+
F("statements", "array<ref:Statement>"),
|
|
261
|
+
F("text", "string", desc="canonical printed form, `parse(text)` reproduces `statements`"),
|
|
262
|
+
]),
|
|
263
|
+
Record("Statement", "program_document.json", "One statement of the program.", [
|
|
264
|
+
F("kind", "enum:gate|noise|loss|measure|reset|feed_forward|detector|observable|tick|repeat|annotation"),
|
|
265
|
+
F("name", "string", desc="lower case op name, PPVM or Stim spelling"),
|
|
266
|
+
F("targets", "array<integer>", desc="circuit qubit ids, or measurement offsets for detectors"),
|
|
267
|
+
F("args", "array<number>", unit="rad for angles, 1 for probabilities", desc="arguments", req=False),
|
|
268
|
+
F("condition", "string", desc="feed-forward predicate, for example `rec[-1]` or `lost(3)`", req=False),
|
|
269
|
+
F("body", "array<integer>", desc="statement indices inside a `repeat`", req=False),
|
|
270
|
+
F("repeat_count", "integer", rng=(1, None), req=False),
|
|
271
|
+
]),
|
|
272
|
+
# ---------------------------------------------------------------- C8
|
|
273
|
+
Record("BackendRequest", "backend_request.json", "What the backend is asked to compute. Produced by L3 or L4 drivers, consumed by the backend.", [
|
|
274
|
+
F("schema_version", "string"),
|
|
275
|
+
F("native_program_ref", "string", desc="sha256 of the `NativeProgram`"),
|
|
276
|
+
F("backend", "enum:GT|GTS|PS|LPS", desc="PPVM class"),
|
|
277
|
+
F("emit_mode", "enum:python_api|stim_text", desc="stim_text only if the program has no loss, rotations or feed-forward"),
|
|
278
|
+
F("shots", "integer", rng=(1, None), desc="sampled backends only", req=False),
|
|
279
|
+
F("seed", "integer", rng=(0, None), req=False),
|
|
280
|
+
F("observable", "string", desc="Pauli word for PS and LPS, for example `Z0Z1`", req=False),
|
|
281
|
+
F("truncation", "ref:Truncation"),
|
|
282
|
+
]),
|
|
283
|
+
Record("Truncation", "backend_request.json", "PPVM truncation settings.", [
|
|
284
|
+
F("min_abs_coeff", "number", rng=(0, None), desc="drop terms below this magnitude, PPVM default 1e-10"),
|
|
285
|
+
F("max_pauli_weight", "integer", rng=(1, None), desc="PS and LPS only", req=False),
|
|
286
|
+
F("max_loss_weight", "integer", rng=(0, None), desc="LPS only", req=False),
|
|
287
|
+
F("preserve_strings", "array<string>", desc="Pauli words never truncated, PS and LPS only", req=False),
|
|
288
|
+
]),
|
|
289
|
+
# ---------------------------------------------------------------- C9
|
|
290
|
+
Record("BackendResult", "backend_result.json", "Outcome of a backend run. Produced by the backend, consumed by L4 and L5.", [
|
|
291
|
+
F("schema_version", "string"),
|
|
292
|
+
F("request_ref", "string", desc="sha256 of the `BackendRequest`"),
|
|
293
|
+
F("backend", "enum:GT|GTS|PS|LPS"),
|
|
294
|
+
F("outcomes", "array<array<integer>>", rng=(0, 2), desc="shots x measurements, 0 ZERO, 1 ONE, 2 LOST", req=False),
|
|
295
|
+
F("measure_labels", "array<integer>", desc="`measure_slot` of each column", req=False),
|
|
296
|
+
F("expectation", "number", desc="PS and LPS: overlap with |0..0>", req=False),
|
|
297
|
+
F("std_error", "number", rng=(0, None), desc="sampled backends: standard error, PS and LPS: 0", req=False),
|
|
298
|
+
F("lost_fraction", "array<number>", rng=P, desc="per measured atom", req=False),
|
|
299
|
+
F("truncation_report", "ref:TruncationReport"),
|
|
300
|
+
F("diagnostics", "array<ref:Diagnostic>"),
|
|
301
|
+
]),
|
|
302
|
+
Record("TruncationReport", "backend_result.json", "Effect of truncation.", [
|
|
303
|
+
F("n_terms", "integer", rng=(0, None), desc="Pauli terms at the end, PS and LPS", req=False),
|
|
304
|
+
F("max_weight_seen", "integer", rng=(0, None), req=False),
|
|
305
|
+
F("biased_low", "boolean", desc="true if any truncation was active, magnitudes are then underestimated"),
|
|
306
|
+
]),
|
|
307
|
+
]
|
|
308
|
+
|
|
309
|
+
CONTRACTS: list[Contract] = [
|
|
310
|
+
Contract("PhysicalParameters", "physical_parameters.json", ("L1", "L2"), "L1-L2", "L1 parameter store (ARC, literature, assumed)", "L2 twirl and calibration",
|
|
311
|
+
"new", "values imported from ARC (Python API); Pulser `Register` JSON for `sites` import and export",
|
|
312
|
+
["Every `ParameterEntry` has a unit; `source=citation` requires `citation`; `source=assumed` is never promoted without a `CalibrationUpdate`.",
|
|
313
|
+
"`valid_lo <= value <= valid_hi` when both are given.",
|
|
314
|
+
"`Site.id` is unique and `Site` coordinates are in um."]),
|
|
315
|
+
Contract("CalibrationUpdate", "calibration_update.json", ("L1", "L2"), "L1-L2", "calibration experiment fit (data from L1)", "L2 parameter store",
|
|
316
|
+
"new", "plot and data references follow the QCalEval layout; fit result shape borrowed from Qibocal and Qiskit Experiments",
|
|
317
|
+
["`FitResult.name` matches a `ParameterEntry.name`; units agree.",
|
|
318
|
+
"`ParameterUpdate.new_value` lies in `[valid_lo, valid_hi]` of that entry, or the update is rejected with a `Diagnostic`.",
|
|
319
|
+
"Updates are append-only, `old_value` equals the value at the time."]),
|
|
320
|
+
Contract("CalibrationRecord", "calibration_record.json", ("L2", "L3"), "L2-L3", "L2 Lindblad simulation plus Pauli twirl", "L3 scheduler",
|
|
321
|
+
"adapter", "convertible to and from `GeminiNoiseModelABC` fields of bloqade-circuit (same 15-key two-qubit order as PPVM); `p` lists map one to one to PPVM `pauli_error`, `two_qubit_pauli_error`, `loss_channel`, `correlated_loss_channel`",
|
|
322
|
+
["All probabilities are in [0,1]; `px+py+pz <= 1`; the 15 entries of `pauli2.p` sum to at most 1.",
|
|
323
|
+
"Two-qubit order is IX IY IZ XI XX XY XZ YI YX YY YZ ZI ZX ZY ZZ.",
|
|
324
|
+
"If `p_loss_corr = [p0,p1,p2]` is present then `p0 + 2*p1 <= 1`.",
|
|
325
|
+
"`len(p_loss) == arity`; `p_leak`, when present, has the same length and `p_leak <= p_loss` per target.",
|
|
326
|
+
"If `p_out_joint` is present then its sum is at most 1; its cells with both atoms out sum to `p0` of `p_loss_corr`, those with one out to `2*p1`.",
|
|
327
|
+
"Pure dephasing reduces to `pz = (1 - exp(-t/t2))/2`, `px = py = 0` (checked in the 03 spec).",
|
|
328
|
+
"Every discarded effect appears in `approximations`."]),
|
|
329
|
+
Contract("NativeProgram", "native_program.json", ("L3", "B"), "L3-B", "L3 scheduler", "backend lowering",
|
|
330
|
+
"adapter", "ops map to bloqade-lanes `.sst` and QMAP `.naviz` schedules for the ideal part; noise attaches in `NoiseChannel`",
|
|
331
|
+
["Ops with the same `parallel_group` have equal `start_us` and disjoint `targets`.",
|
|
332
|
+
"No two ops on the same atom overlap in time: `start_us + duration_us <= next start_us` per atom.",
|
|
333
|
+
"`total_duration_us == max(start_us + duration_us)`.",
|
|
334
|
+
"`len(NoiseChannel.p)` equals the length stated for its `kind`; every channel is a valid distribution.",
|
|
335
|
+
"Every spectator in `spectators` is not a target of any op in the same time slice.",
|
|
336
|
+
"`measure` ops carry a unique `measure_slot`."]),
|
|
337
|
+
Contract("CircuitLowering", "circuit_lowering.json", ("L4", "L3"), "L3-L4", "L3 compiler", "L4 users and L3 scheduler",
|
|
338
|
+
"adapter", "input is a `ProgramDocument` (Stim, OpenQASM 3, Qiskit, Cirq or Bloqade squin import); output feeds bloqade-lanes or QMAP when the placement is delegated",
|
|
339
|
+
["`qubit_map` is injective.",
|
|
340
|
+
"Every circuit statement index appears in exactly one `LoweredOp`.",
|
|
341
|
+
"If `exact` is false then `approx_error` is present.",
|
|
342
|
+
"Every `native_ops` id exists in the referenced `NativeProgram`.",
|
|
343
|
+
"A statement with `feed_forward != none` keeps its classical condition on every lowered op."]),
|
|
344
|
+
Contract("LogicalLowering", "logical_lowering.json", ("L5", "L4"), "L4-L5", "L5 code library", "L4 circuit builder",
|
|
345
|
+
"adapter", "emits Stim-compatible `DETECTOR` and `OBSERVABLE_INCLUDE` semantics (Stim, tqec); decoder input is a Stim DEM for PyMatching",
|
|
346
|
+
["Data and ancilla qubit ids are disjoint within a block and across blocks.",
|
|
347
|
+
"`len(stabilizers[i]) == len(data_qubits)`; stabilizers commute.",
|
|
348
|
+
"Each detector is deterministic in the noiseless circuit.",
|
|
349
|
+
"Repetition code of distance d has d data, d-1 ancilla qubits and 2(d-1) CZ per round with a CZ-based syndrome extraction (counted in 06 spec).",
|
|
350
|
+
"`loss_policy=erasure_decode` requires the backend to return `LOST` outcomes (backend GT)."]),
|
|
351
|
+
Contract("ProgramDocument", "program_document.json", ("L4", "L3"), "L3-L4", "L4 builder or parser", "L3 compiler, printer, exporters",
|
|
352
|
+
"existing", "text dialects `stim` (Stim text, loss rejected), `openqasm3`, `bloqade_squin` import and export; the `nadsl` dialect is Stim-like text extended with loss, rotations and feed-forward",
|
|
353
|
+
["Round trip: `parse(print(doc)).statements == doc.statements` for dialect `nadsl`.",
|
|
354
|
+
"`n_qubits` bounds every target.",
|
|
355
|
+
"A `loss` statement is never exported to dialect `stim`; the exporter raises a `Diagnostic` naming the construct.",
|
|
356
|
+
"Feed-forward `condition` is `rec[-k]` or `lost(q)` and refers to an earlier measurement."]),
|
|
357
|
+
Contract("BackendRequest", "backend_request.json", ("L3", "B"), "L3-B", "driver of L3 or L4", "backend lowering",
|
|
358
|
+
"adapter", "PPVM Python API; Stim text via `StimProgram.parse` for the restricted subset",
|
|
359
|
+
["`backend=PS` or `LPS` requires `observable`; `backend=GT` or `GTS` requires `shots`.",
|
|
360
|
+
"`backend=PS` rejects any loss channel in the program; `LPS` is needed.",
|
|
361
|
+
"`emit_mode=stim_text` only if no `loss`, `r*` rotation, `u3`, `cz_block` or `feed_forward` statement exists.",
|
|
362
|
+
"`max_loss_weight` is set only for `LPS`; `max_pauli_weight` and `preserve_strings` only for `PS` or `LPS`."]),
|
|
363
|
+
Contract("BackendResult", "backend_result.json", ("B", "L4"), "L3-B", "backend", "L4 (feed-forward, analysis) and L5 (decoding)",
|
|
364
|
+
"adapter", "`outcomes` equals the PPVM `sample_stim(as_numpy=True)` array layout (0 ZERO, 1 ONE, 2 LOST); Stim measurement record layout otherwise",
|
|
365
|
+
["`outcomes` columns equal the number of `measure` ops.",
|
|
366
|
+
"`LOST` (2) is never reported as 0 or 1.",
|
|
367
|
+
"Exactly one of `outcomes` or `expectation` is present.",
|
|
368
|
+
"`truncation_report.biased_low` is true whenever `max_pauli_weight`, `max_loss_weight` or `min_abs_coeff > 0` removed a term."]),
|
|
369
|
+
]
|
|
370
|
+
|
|
371
|
+
PAIRS = [
|
|
372
|
+
("L1-L2", "L1 Physical", "L2 Pulse and calibration", ["PhysicalParameters", "CalibrationUpdate"]),
|
|
373
|
+
("L2-L3", "L2 Pulse and calibration", "L3 Native gates and movement", ["CalibrationRecord"]),
|
|
374
|
+
("L3-L4", "L3 Native gates and movement", "L4 Circuit", ["CircuitLowering", "ProgramDocument"]),
|
|
375
|
+
("L4-L5", "L4 Circuit", "L5 Logical", ["LogicalLowering"]),
|
|
376
|
+
("L3-B", "L3 Native gates and movement", "PPVM backend", ["NativeProgram", "BackendRequest", "BackendResult"]),
|
|
377
|
+
]
|
|
378
|
+
|
|
379
|
+
# ---------------------------------------------------------------------------
|
|
380
|
+
def record_by_name():
|
|
381
|
+
return {r.name: r for r in RECORDS}
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def _type_schema(f: F, rec_file: str):
|
|
385
|
+
t = f.type
|
|
386
|
+
lo, hi = f.rng if f.rng else (None, None)
|
|
387
|
+
|
|
388
|
+
def base(t, with_range):
|
|
389
|
+
if t.startswith("enum:"):
|
|
390
|
+
return {"enum": t[5:].split("|")}
|
|
391
|
+
if t.startswith("ref:"):
|
|
392
|
+
name = t[4:]
|
|
393
|
+
target = record_by_name()[name].file
|
|
394
|
+
return {"$ref": (f"#/$defs/{name}" if target == rec_file else f"{target}#/$defs/{name}")}
|
|
395
|
+
s = {"type": t}
|
|
396
|
+
if with_range and t in ("number", "integer"):
|
|
397
|
+
if lo is not None:
|
|
398
|
+
s["minimum"] = lo
|
|
399
|
+
if hi is not None:
|
|
400
|
+
s["maximum"] = hi
|
|
401
|
+
return s
|
|
402
|
+
|
|
403
|
+
if t.startswith("array<array<"):
|
|
404
|
+
inner = t[len("array<array<"):-2]
|
|
405
|
+
s = {"type": "array", "items": {"type": "array", "items": base(inner, True)}}
|
|
406
|
+
elif t.startswith("array<"):
|
|
407
|
+
inner = t[6:-1]
|
|
408
|
+
s = {"type": "array", "items": base(inner, True)}
|
|
409
|
+
if f.n is not None:
|
|
410
|
+
s["minItems"] = s["maxItems"] = f.n
|
|
411
|
+
else:
|
|
412
|
+
s = base(t, True)
|
|
413
|
+
if f.unit != UNIT_NONE:
|
|
414
|
+
s["x-unit"] = f.unit
|
|
415
|
+
if f.desc:
|
|
416
|
+
s["description"] = f.desc
|
|
417
|
+
return s
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def record_schema(rec: Record):
|
|
421
|
+
return {
|
|
422
|
+
"type": "object",
|
|
423
|
+
"description": rec.desc,
|
|
424
|
+
"properties": {f.name: _type_schema(f, rec.file) for f in rec.fields},
|
|
425
|
+
"required": [f.name for f in rec.fields if f.req],
|
|
426
|
+
"additionalProperties": False,
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def build_schemas():
|
|
431
|
+
"""Return {filename: schema dict}."""
|
|
432
|
+
files = {}
|
|
433
|
+
for rec in RECORDS:
|
|
434
|
+
files.setdefault(rec.file, []).append(rec)
|
|
435
|
+
out = {}
|
|
436
|
+
for fn, recs in files.items():
|
|
437
|
+
s = {"$schema": "https://json-schema.org/draft/2020-12/schema", "$id": BASE_URI + fn,
|
|
438
|
+
"$defs": {r.name: record_schema(r) for r in recs}}
|
|
439
|
+
if fn != "common.json":
|
|
440
|
+
s["title"] = recs[0].name
|
|
441
|
+
s["$ref"] = f"#/$defs/{recs[0].name}"
|
|
442
|
+
else:
|
|
443
|
+
s["title"] = "Shared definitions"
|
|
444
|
+
out[fn] = s
|
|
445
|
+
return out
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def type_label(f: F):
|
|
449
|
+
t = f.type.replace("enum:", "enum ").replace("ref:", "").replace("|", " / ")
|
|
450
|
+
t = t.replace("array<array<", "array of array of ").replace("array<", f"array{'[%d]' % f.n if f.n else ''} of ").replace(">", "")
|
|
451
|
+
return t
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def fmt_range(f: F):
|
|
455
|
+
if not f.rng:
|
|
456
|
+
return "-"
|
|
457
|
+
lo, hi = f.rng
|
|
458
|
+
return f"[{'-inf' if lo is None else lo}, {'inf' if hi is None else hi}]"
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def record_table(rec: Record):
|
|
462
|
+
rows = ["| Field | Type | Unit | Range | Req. | Meaning |", "|---|---|---|---|---|---|"]
|
|
463
|
+
for f in rec.fields:
|
|
464
|
+
u = "-" if f.unit == UNIT_NONE else f.unit
|
|
465
|
+
rows.append(f"| `{f.name}` | {type_label(f)} | {u} | {fmt_range(f)} | {'yes' if f.req else 'no'} | {f.desc or '-'} |")
|
|
466
|
+
return "\n".join(rows)
|