diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index 46a683c..2b35ed5 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -1,5 +1,5 @@
name: Bug Report
-description: Report a reproducible defect in public SchroSIM components (CLI/core/docs).
+description: Report a reproducible defect in public SchroSIM components (CLI/core/docs/Studio UI).
title: "[Bug]: "
labels:
- bug
@@ -24,6 +24,7 @@ body:
- CLI (schrosim-cli)
- Swift core runtime
- Rust core runtime
+ - SchroSIM Studio UI
- Configuration/examples
- Documentation
- Other public component
@@ -34,7 +35,7 @@ body:
attributes:
label: Command / Entry point
description: Include command and backend (if applicable).
- placeholder: swift run schrosim-cli run examples/runtime_default_foundry.json --backend hybrid
+ placeholder: swift run schrosim-cli run docs/examples/runtime_default_foundry.json --backend hybrid
- type: textarea
id: reproduction
attributes:
@@ -72,5 +73,5 @@ body:
options:
- label: This is not a security vulnerability disclosure.
required: true
- - label: This report only references public repo scope (CLI/core/docs), not private enterprise GUI code.
+ - label: This report only references public repo scope (CLI/core/docs/Studio UI).
required: true
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 46cda1f..39d5788 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,8 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Security Vulnerability Report
- url: https://github.com/DennisWayo/SchroSIM/security
+ url: https://github.com/Gottesman-Software/SchroSIM/security
about: Report security issues privately per SECURITY.md. Do not post exploit details publicly.
- name: Community Guidelines
- url: https://github.com/DennisWayo/SchroSIM/blob/main/CODE_OF_CONDUCT.md
+ url: https://github.com/Gottesman-Software/SchroSIM/blob/main/CODE_OF_CONDUCT.md
about: Read expected behavior and reporting guidelines.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
index 88e9584..0d65e66 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -1,5 +1,5 @@
name: Feature Request
-description: Propose an enhancement for public SchroSIM components (CLI/core/docs).
+description: Propose an enhancement for public SchroSIM components (CLI/core/docs/Studio UI).
title: "[Feature]: "
labels:
- enhancement
@@ -38,5 +38,5 @@ body:
attributes:
label: Scope Confirmation
options:
- - label: This request targets public repo scope (CLI/core/docs) and not private enterprise GUI components.
+ - label: This request targets public repo scope (CLI/core/docs/Studio UI).
required: true
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 9abc4f4..97d898d 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -5,10 +5,10 @@ Describe the change in 2-5 bullet points.
What problem does this PR solve?
## Scope
-Public scope for this repository is CLI/core/docs.
+Public scope for this repository is CLI/core/docs/Studio UI.
-- [ ] I confirm this PR does not add or modify private enterprise GUI code/assets/workflows.
-- [ ] I confirm this PR does not include enterprise-only configuration or internal enterprise paths.
+- [ ] I confirm this PR keeps public examples, config, benchmarks, and assets under `docs/`.
+- [ ] I confirm this PR does not include credentials, generated exports, build outputs, or private lab data.
## Validation
List commands run and key outputs.
diff --git a/.gitignore b/.gitignore
index e13b68a..6c718ab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,15 +27,7 @@ exports/*.json
docs/SchroSIM_ConceptNote.pdf
last_result.json
-/enterprise/*
-/enterprise/README.md
-
-/Sources/schrosim-gui/
-/core-rust/schrosim-gui/
-/core-swift/Sources/schrosim-enterprise-ui/
-/core-swift/Tests/EnterpriseUITests/
-/core-swift/Sources/schrosim-cli/trace_enterprise.swift
-/config/profiles/enterprise-default-v1.json
-/docs/ui-mockups/*enterprise*
-!/docs/ui-mockups/schrosim-enterprise-preview.gif
-/.github/workflows/*enterprise*.yml
+# SchroSIM Studio generated/local artifacts
+/studio/docs/
+/studio/.github/
+/studio/exports/
diff --git a/Package.swift b/Package.swift
index d148752..3248442 100644
--- a/Package.swift
+++ b/Package.swift
@@ -2,9 +2,9 @@
import Foundation
import PackageDescription
-let enterpriseUISourcePath = "enterprise/core-swift/Sources/schrosim-enterprise-ui"
-let enterpriseUITestsPath = "enterprise/core-swift/Tests/EnterpriseUITests"
-let hasEnterpriseUI = FileManager.default.fileExists(atPath: enterpriseUISourcePath)
+let studioUISourcePath = "core-swift/Sources/schrosim-studio"
+let studioUITestsPath = "core-swift/Tests/SchroSIMStudioTests"
+let hasStudioUI = FileManager.default.fileExists(atPath: studioUISourcePath)
var products: [Product] = [
.library(
@@ -17,11 +17,11 @@ var products: [Product] = [
),
]
-if hasEnterpriseUI {
+if hasStudioUI {
products.append(
.executable(
- name: "schrosim-enterprise-ui",
- targets: ["schrosim-enterprise-ui"]
+ name: "schrosim-studio",
+ targets: ["schrosim-studio"]
)
)
}
@@ -54,21 +54,21 @@ var targets: [Target] = [
),
]
-if hasEnterpriseUI {
+if hasStudioUI {
targets.append(
.executableTarget(
- name: "schrosim-enterprise-ui",
+ name: "schrosim-studio",
dependencies: ["SchroSIM"],
- path: enterpriseUISourcePath
+ path: studioUISourcePath
)
)
- if FileManager.default.fileExists(atPath: enterpriseUITestsPath) {
+ if FileManager.default.fileExists(atPath: studioUITestsPath) {
targets.append(
.testTarget(
- name: "EnterpriseUITests",
- dependencies: ["schrosim-enterprise-ui"],
- path: enterpriseUITestsPath
+ name: "SchroSIMStudioTests",
+ dependencies: ["schrosim-studio"],
+ path: studioUITestsPath
)
)
}
diff --git a/README.md b/README.md
index c18ea01..81670ef 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-
+
@@ -11,8 +11,8 @@
-
-
+
+
@@ -22,20 +22,19 @@ SchroSIM is a continuous-variable (CV) photonic quantum simulation stack for des
Built for students, researchers, and quantum foundry engineers, SchroSIM provides an exact-solution simulation path for tractable circuits and scalable backend options for larger workflows.
> Documentation:
-Full project documentation is in [SchroSIM Docs](https://denniswayo.github.io/SchroSIM/).
+Full project documentation is in [SchroSIM Docs](https://gottesman-software.github.io/SchroSIM/).
-> "SchroSIM core (compiler, runtime, SDK/CLI, docs, and public examples) is open source under MIT for research, education, and downstream studies.
-Enterprise-only capabilities (advanced UI, partner-specific integrations, and commercialization-focused IP) are developed in enterprise mode and are intended to live in a separate private distribution path.
-This boundary keeps reproducible scientific workflows public while allowing confidential enterprise delivery for hardware and industrial partners."
+> "SchroSIM core, Studio UI, SDK/CLI, docs, and public examples are open source under MIT for research, education, and downstream studies.
+The public boundary is reproducible scientific software: design photonic circuit studies, compile them into backend-aware runtime configuration, inspect validation evidence, and replay results from source-controlled artifacts."
-
+
-The enterprise UI is focused on:
+SchroSIM Studio is focused on:
1. project-scoped circuit workflows,
2. backend-aware execution policies,
3. analysis views for simulation and QEC outcomes.
-Status: enterprise-track active development.
+Status: open-source studio active development.
### The Need
Photonic and CV workflows need fast iteration across modeling, backend selection, and error-correction validation. In practice, teams often lose time moving between disconnected tools for design, compilation, execution, and debugging.
@@ -61,7 +60,7 @@ Use this rule of thumb:
2. use scalable/hybrid/Fock policy-targeted paths as controlled approximations for larger experiments and pre-hardware iteration.
### Design-to-Result Workflow
-1. define a circuit in project JSON (public workflow) or design it in the enterprise UI (private track),
+1. define a circuit in project JSON or design it in SchroSIM Studio,
2. compile to backend-aware intermediate/runtime configuration,
3. run static checks for operation support and constraint compatibility,
4. execute simulation on selected path (model-exact Gaussian or controlled approximation backend path),
@@ -116,15 +115,20 @@ swift run schrosim-cli --help
Python 3 is required for helper scripts and packaging workflows.
+Run the open-source Studio UI:
+```bash
+swift run schrosim-studio
+```
+
### CLI: Run Demos
Run Boson Sampling:
```bash
-schrosim run examples/runtime_default_foundry.json --backend hybrid
+schrosim run docs/examples/runtime_default_foundry.json --backend hybrid
```
Run Quantum Error Correction:
```bash
-schrosim run examples/cv/qec_single_logical_gkp_memory_mvp.json --backend hybrid
+schrosim run docs/examples/cv/qec_single_logical_gkp_memory_mvp.json --backend hybrid
```
### Optional: Run from CLion
@@ -133,7 +137,7 @@ CLion is recommended for contributor workflows (debugging, stepping through code
### Ecosystem Comparison (High-Level)
| Framework | Primary focus | Photonic scope | Execution path | Typical fit |
|---|---|---|---|---|
-| [SchroSIM](https://denniswayo.github.io/SchroSIM/) | CV photonic circuit simulation with backend-aware compilation, runtime diagnostics, and QEC-oriented workflows | Gaussian + selected non-Gaussian workflows (JSON/CLI flow) | Local classical simulation (`gaussian`, `fock`, `hybrid`; CPU/Metal where available) | Reproducible research workflows, QEC scenario studies, validation pipelines, and hardware-like prechecks |
+| [SchroSIM](https://gottesman-software.github.io/SchroSIM/) | CV photonic circuit simulation with backend-aware compilation, runtime diagnostics, and QEC-oriented workflows | Gaussian + selected non-Gaussian workflows (JSON/CLI flow) | Local classical simulation (`gaussian`, `fock`, `hybrid`; CPU/Metal where available) | Reproducible research workflows, QEC scenario studies, validation pipelines, and hardware-like prechecks |
| [Strawberry Fields](https://strawberryfields.ai/photonics/) | Full-stack Python framework for photonic quantum computing | Continuous-variable photonics, Gaussian/Fock-style workflows | Local simulators + remote execution on Xanadu photonic hardware/cloud | CV algorithm research and cloud photonic experiments |
| [Perceval](https://perceval.quandela.net/docs/v1.1/) | Linear optics quantum framework | Discrete-variable / linear-optics photonic circuits | Local simulation + remote execution on simulator/QPU platforms | Linear-optics experiment design, sampling, and cloud QPU workflows |
| [PennyLane](https://docs.pennylane.ai/) | Differentiable quantum programming and quantum ML framework | Photonics via plugins (not photonic-only core) | Local simulators + hardware integrations via devices/plugins | Hybrid quantum-classical ML workflows, gradient-based circuit research, and QEC prototyping/education workflows |
diff --git a/core-rust/README.md b/core-rust/README.md
index 1c1a25e..af7e6e6 100644
--- a/core-rust/README.md
+++ b/core-rust/README.md
@@ -4,8 +4,7 @@ This folder contains Rust crates for SchroSIM.
Current crate:
-- `schrosim-gui` — egui desktop app that invokes the Swift CLI
-- `schrosim-core` — Rust CLI runtime (`run`, `trace`) used by SwiftUI runtime-engine selection
+- `schrosim-core` — Rust CLI runtime (`run`, `trace`, `bench`, `parity`) used for runtime parity checks and benchmark workflows
- `schrosim-core` now includes:
- Gaussian simulation kernels (phase/squeeze/beam-splitter/displace/loss/thermal/measurement)
- Measurement-driven feedback displacement (`feedback_displace`) for syndrome-based correction loops
@@ -13,20 +12,15 @@ Current crate:
- Fock single-mode path (`phase`, `displace`, `inject_fock`, `inject_cat`)
- Foundry compile step parity (validation + optional injected mode-loss)
- `parity` command to compare Rust output against `schrosim-cli`
-- GUI default backend is `hybrid` (recommended enterprise routing policy)
- Compiler/contraction recommendation is sourced from CLI policy:
- compiler: `foundry-aware-ir-v1`
- contraction policy: `hybrid_auto`
-- GUI includes trace-driven live playback:
- - `Load trace` calls `schrosim-cli trace ...`
- - `Play/Pause/Stop` animates per-gate propagation frames continuously
Build/test from repo root with:
```bash
-cargo test -p schrosim-gui
-cargo run -p schrosim-gui
-cargo run -p schrosim-core -- run exports/swiftui_runtime_input.json --backend hybrid
-cargo run -p schrosim-core -- trace exports/swiftui_runtime_input.json --backend hybrid --trace-role editor
-cargo run -p schrosim-core -- parity exports/swiftui_runtime_input.json --backend hybrid --trace-role editor
+cargo test -p schrosim-core
+cargo run -p schrosim-core -- run docs/examples/foundry_loss_map.json --backend hybrid
+cargo run -p schrosim-core -- trace docs/examples/foundry_loss_map.json --backend hybrid --trace-role editor
+cargo run -p schrosim-core -- parity docs/examples/foundry_loss_map.json --backend hybrid --trace-role editor
```
diff --git a/core-rust/schrosim-core/src/main.rs b/core-rust/schrosim-core/src/main.rs
index 988fbb0..03de35b 100644
--- a/core-rust/schrosim-core/src/main.rs
+++ b/core-rust/schrosim-core/src/main.rs
@@ -97,25 +97,25 @@ struct BenchCaseDefinition {
const BENCHMARK_CORE_CASES: [BenchCaseDefinition; 5] = [
BenchCaseDefinition {
name: "basic_loss_gaussian",
- input: BenchInputSource::File("examples/basic_loss.json"),
+ input: BenchInputSource::File("docs/examples/basic_loss.json"),
requested_backend: "gaussian",
expected_backend: "gaussian",
},
BenchCaseDefinition {
name: "seeded_homodyne_gaussian",
- input: BenchInputSource::File("examples/seeded_homodyne.json"),
+ input: BenchInputSource::File("docs/examples/seeded_homodyne.json"),
requested_backend: "gaussian",
expected_backend: "gaussian",
},
BenchCaseDefinition {
name: "fock_injection_fock",
- input: BenchInputSource::File("examples/fock_injection_smoke.json"),
+ input: BenchInputSource::File("docs/examples/fock_injection_smoke.json"),
requested_backend: "fock",
expected_backend: "fock",
},
BenchCaseDefinition {
name: "fock_injection_hybrid",
- input: BenchInputSource::File("examples/fock_injection_smoke.json"),
+ input: BenchInputSource::File("docs/examples/fock_injection_smoke.json"),
requested_backend: "hybrid",
expected_backend: "fock",
},
@@ -2945,7 +2945,7 @@ fn resolve_foundry_spec(
let registry_path = options
.foundry_registry_path
.as_deref()
- .unwrap_or("config/foundry_registry.json");
+ .unwrap_or("docs/config/foundry_registry.json");
let signing_key = resolve_foundry_signing_key(options).ok_or_else(|| {
"Missing foundry signing key. Provide --foundry-key or SCHROSIM_FOUNDRY_HMAC_KEY"
.to_string()
diff --git a/core-swift/Sources/schrosim-cli/main.swift b/core-swift/Sources/schrosim-cli/main.swift
index 58dc346..c4ee9ca 100644
--- a/core-swift/Sources/schrosim-cli/main.swift
+++ b/core-swift/Sources/schrosim-cli/main.swift
@@ -496,7 +496,7 @@ enum SchemaPolicy {
static let maxSupportedVersion = 1
}
-let defaultFoundryRegistryPath = "config/foundry_registry.json"
+let defaultFoundryRegistryPath = "docs/config/foundry_registry.json"
enum RuntimeRecommendation {
static let compiler = "foundry-aware-ir-v1"
diff --git a/core-swift/Sources/schrosim-cli/trace_support.swift b/core-swift/Sources/schrosim-cli/trace_support.swift
index 43d9aab..f7faae3 100644
--- a/core-swift/Sources/schrosim-cli/trace_support.swift
+++ b/core-swift/Sources/schrosim-cli/trace_support.swift
@@ -2,7 +2,7 @@ import Foundation
let traceRBACCurrentSchemaVersion = 1
let traceArtifactCurrentSchemaVersion = 1
-let defaultTraceRBACPolicyPath = "config/trace_rbac_policy.json"
+let defaultTraceRBACPolicyPath = "docs/config/trace_rbac_policy.json"
enum TraceRBACAction: String {
case view
diff --git a/core-swift/Sources/schrosim-studio/AppModels.swift b/core-swift/Sources/schrosim-studio/AppModels.swift
new file mode 100644
index 0000000..952bd7e
--- /dev/null
+++ b/core-swift/Sources/schrosim-studio/AppModels.swift
@@ -0,0 +1,1101 @@
+import Foundation
+
+enum EnvironmentBadge: String, CaseIterable, Identifiable {
+ case dev
+ case stage
+ case prod
+
+ var id: String { rawValue }
+}
+
+enum UserRoleBadge: String, CaseIterable, Identifiable {
+ case viewer
+ case editor
+ case approver
+ case admin
+
+ var id: String { rawValue }
+}
+
+enum AppTheme: String, CaseIterable, Identifiable {
+ case white = "White"
+ case black = "Black"
+
+ var id: String { rawValue }
+}
+
+enum BackendSelection: String, CaseIterable, Identifiable {
+ case gaussian
+ case fock
+ case hybrid
+
+ var id: String { rawValue }
+
+ var title: String {
+ switch self {
+ case .gaussian:
+ return "Gaussian"
+ case .fock:
+ return "Fock"
+ case .hybrid:
+ return "Hybrid"
+ }
+ }
+}
+
+enum RuntimeEngineSelection: String, CaseIterable, Identifiable {
+ case swift
+ case rust
+
+ var id: String { rawValue }
+
+ var title: String {
+ switch self {
+ case .swift:
+ return "Swift"
+ case .rust:
+ return "Rust"
+ }
+ }
+}
+
+enum RightInspectorTab: String, CaseIterable, Identifiable {
+ case selectionInspector = "SelectionInspector"
+ case circuitConfigInspector = "CircuitConfigInspector"
+ case statusInspector = "StatusInspector"
+ case quantumStateInspector = "QuantumStateInspector"
+
+ var id: String { rawValue }
+
+ var title: String {
+ switch self {
+ case .selectionInspector:
+ return "Selection Panel"
+ case .circuitConfigInspector:
+ return "Circuit Config"
+ case .statusInspector:
+ return "Status"
+ case .quantumStateInspector:
+ return "State"
+ }
+ }
+}
+
+enum BottomTab: String, CaseIterable, Identifiable {
+ case validationConsole = "ValidationConsole"
+ case runOutputPanel = "RunOutputPanel"
+ case auditTrailPanel = "AuditTrailPanel"
+
+ var id: String { rawValue }
+
+ var title: String {
+ switch self {
+ case .validationConsole:
+ return "Validation Console"
+ case .runOutputPanel:
+ return "Run Output"
+ case .auditTrailPanel:
+ return "Audit Trail"
+ }
+ }
+}
+
+enum RuntimePhase: String, CaseIterable, Identifiable {
+ case standby
+ case compiling
+ case running
+ case finished
+
+ var id: String { rawValue }
+
+ var title: String {
+ switch self {
+ case .standby:
+ return "Standby"
+ case .compiling:
+ return "Compiling"
+ case .running:
+ return "Running"
+ case .finished:
+ return "Finished"
+ }
+ }
+}
+
+enum RuntimeParityStatus: String {
+ case match
+ case drift
+ case unmatched
+
+ var title: String {
+ switch self {
+ case .match:
+ return "MATCH"
+ case .drift:
+ return "DRIFT"
+ case .unmatched:
+ return "UNMATCHED"
+ }
+ }
+}
+
+struct RuntimeParitySummary: Hashable {
+ let status: RuntimeParityStatus
+ let selectedRuntime: RuntimeEngineSelection
+ let referenceRuntime: RuntimeEngineSelection
+ let selectedBackendVersion: String
+ let referenceBackendVersion: String
+ let meanPhotonDelta: Double?
+ let stateDeltaMax: Double?
+ let measurementDelta: Int?
+ let detail: String
+}
+
+enum StateHealthStatus: String {
+ case healthy
+ case review
+ case invalid
+ case unavailable
+
+ var title: String {
+ switch self {
+ case .healthy:
+ return "HEALTHY"
+ case .review:
+ return "REVIEW"
+ case .invalid:
+ return "INVALID"
+ case .unavailable:
+ return "UNAVAILABLE"
+ }
+ }
+}
+
+enum StateValidityStatus: String {
+ case valid
+ case partial
+ case invalid
+ case unavailable
+
+ var title: String {
+ switch self {
+ case .valid:
+ return "VALID"
+ case .partial:
+ return "PARTIAL"
+ case .invalid:
+ return "INVALID"
+ case .unavailable:
+ return "UNAVAILABLE"
+ }
+ }
+}
+
+enum StateConfidenceStatus: String {
+ case match
+ case drift
+ case unmatched
+ case unavailable
+
+ var title: String {
+ switch self {
+ case .match:
+ return "MATCH"
+ case .drift:
+ return "DRIFT"
+ case .unmatched:
+ return "UNMATCHED"
+ case .unavailable:
+ return "UNAVAILABLE"
+ }
+ }
+}
+
+enum StateRobustnessStatus: String {
+ case robust
+ case sensitive
+ case fragile
+ case unavailable
+
+ var title: String {
+ switch self {
+ case .robust:
+ return "ROBUST"
+ case .sensitive:
+ return "SENSITIVE"
+ case .fragile:
+ return "FRAGILE"
+ case .unavailable:
+ return "UNAVAILABLE"
+ }
+ }
+}
+
+struct ThresholdCheckResult: Hashable, Identifiable {
+ let key: String
+ let title: String
+ let passed: Bool
+ let value: Double?
+ let threshold: Double?
+ let comparator: String
+ let note: String?
+
+ var id: String { key }
+}
+
+struct KPIPolicyThresholds: Hashable, Decodable {
+ let minSuccessRate: Double
+ let maxMeanPhotonP95Delta: Double
+ let maxStateP95Delta: Double
+ let maxMeasurementMismatchRate: Double
+ let minMeanPhotonNumber: Double?
+ let maxMeanPhotonNumber: Double?
+ let minSqueezingDB: Double?
+ let minGaussianPurity: Double?
+ let maxFockNormalizationError: Double?
+ let maxLogicalErrorRate: Double?
+ let minSuppressionFactor: Double?
+ let minBreakEvenGain: Double?
+
+ enum CodingKeys: String, CodingKey {
+ case minSuccessRate = "min_success_rate"
+ case maxMeanPhotonP95Delta = "max_mean_photon_p95_delta"
+ case maxStateP95Delta = "max_state_p95_delta"
+ case maxMeasurementMismatchRate = "max_measurement_mismatch_rate"
+ case minMeanPhotonNumber = "min_mean_photon_number"
+ case maxMeanPhotonNumber = "max_mean_photon_number"
+ case minSqueezingDB = "min_squeezing_db"
+ case minGaussianPurity = "min_gaussian_purity"
+ case maxFockNormalizationError = "max_fock_normalization_error"
+ case maxLogicalErrorRate = "max_logical_error_rate"
+ case minSuppressionFactor = "min_suppression_factor"
+ case minBreakEvenGain = "min_break_even_gain"
+ }
+}
+
+struct KPIPolicyDefinition: Hashable, Decodable, Identifiable {
+ let id: String
+ let name: String
+ let version: Int
+ let targetRepresentation: String
+ let summary: String
+ let references: [String]
+ let thresholds: KPIPolicyThresholds
+
+ enum CodingKeys: String, CodingKey {
+ case id
+ case name
+ case version
+ case targetRepresentation = "target_representation"
+ case summary
+ case references
+ case thresholds
+ }
+}
+
+enum KPIPolicyStatus: String {
+ case pass
+ case fail
+ case unavailable
+
+ var title: String {
+ switch self {
+ case .pass:
+ return "PASS"
+ case .fail:
+ return "FAIL"
+ case .unavailable:
+ return "UNAVAILABLE"
+ }
+ }
+}
+
+struct KPIPolicyEvaluationSummary: Hashable {
+ let policyID: String
+ let policyName: String
+ let policyVersion: Int
+ let status: KPIPolicyStatus
+ let checks: [ThresholdCheckResult]
+ let recommendation: String
+ let detail: String
+}
+
+struct StateRobustnessSummary: Hashable {
+ let status: StateRobustnessStatus
+ let score: Double?
+ let sampleCount: Int
+ let successRate: Double?
+ let meanPhotonP95Delta: Double?
+ let stateP95Delta: Double?
+ let measurementMismatchRate: Double?
+ let checks: [ThresholdCheckResult]
+ let detail: String
+}
+
+struct StateHealthSummary: Hashable {
+ let status: StateHealthStatus
+ let validity: StateValidityStatus
+ let confidence: StateConfidenceStatus
+ let robustness: StateRobustnessSummary
+ let kpi: KPIPolicyEvaluationSummary
+ let checksCompleted: Int
+ let issueCount: Int
+ let errors: [String]
+ let warnings: [String]
+ let recommendation: String
+ let successRationale: String?
+ let detail: String
+}
+
+enum BosonicSignoffStatus: String {
+ case pass
+ case incomplete
+ case unavailable
+
+ var title: String {
+ switch self {
+ case .pass:
+ return "PASS"
+ case .incomplete:
+ return "INCOMPLETE"
+ case .unavailable:
+ return "UNAVAILABLE"
+ }
+ }
+}
+
+struct BosonicSignoffCheck: Hashable, Identifiable {
+ let key: String
+ let title: String
+ let passed: Bool
+ let observed: String
+ let expected: String
+ let detail: String
+
+ var id: String { key }
+}
+
+struct BosonicSignoffSummary: Hashable {
+ let status: BosonicSignoffStatus
+ let requiredChecks: Int
+ let passedChecks: Int
+ let checks: [BosonicSignoffCheck]
+ let recommendation: String
+ let detail: String
+}
+
+struct MonteCarloNoiseModel: Hashable, Codable {
+ var sampleCount: Int
+ var lossEtaSigma: Double
+ var thermalNoiseSigma: Double
+ var phaseSigmaRad: Double
+ var displacementSigma: Double
+
+ static let standard = MonteCarloNoiseModel(
+ sampleCount: 24,
+ lossEtaSigma: 0.015,
+ thermalNoiseSigma: 0.06,
+ phaseSigmaRad: 0.025,
+ displacementSigma: 0.03
+ )
+}
+
+struct DetectorCalibrationModel: Hashable, Codable {
+ var homodyneSigma: Double
+ var heterodyneSigma: Double
+ var darkCountRate: Double
+
+ static let standard = DetectorCalibrationModel(
+ homodyneSigma: 0.010,
+ heterodyneSigma: 0.015,
+ darkCountRate: 0.0005
+ )
+
+ enum CodingKeys: String, CodingKey {
+ case homodyneSigma = "homodyne_sigma"
+ case heterodyneSigma = "heterodyne_sigma"
+ case darkCountRate = "dark_count_rate"
+ }
+}
+
+struct DriftCalibrationModel: Hashable, Codable {
+ var lossEtaPerSample: Double
+ var thermalPerSample: Double
+ var phaseRadPerSample: Double
+ var displacementPerSample: Double
+
+ static let none = DriftCalibrationModel(
+ lossEtaPerSample: 0.0,
+ thermalPerSample: 0.0,
+ phaseRadPerSample: 0.0,
+ displacementPerSample: 0.0
+ )
+
+ enum CodingKeys: String, CodingKey {
+ case lossEtaPerSample = "loss_eta_per_sample"
+ case thermalPerSample = "thermal_per_sample"
+ case phaseRadPerSample = "phase_rad_per_sample"
+ case displacementPerSample = "displacement_per_sample"
+ }
+}
+
+struct CalibratedNoiseCorrelationTensors: Hashable, Codable {
+ var lossEta: [[Double]]?
+ var thermal: [[Double]]?
+ var phaseRad: [[Double]]?
+ var displacement: [[Double]]?
+
+ enum CodingKeys: String, CodingKey {
+ case lossEta = "loss_eta"
+ case thermal
+ case phaseRad = "phase_rad"
+ case displacement
+ }
+}
+
+struct CalibratedNoiseTensorProfile: Hashable, Codable {
+ var schemaVersion: Int
+ var deviceID: String
+ var deviceName: String
+ var calibratedAt: String?
+ var modeCount: Int?
+ var correlations: CalibratedNoiseCorrelationTensors
+ var detectorModel: DetectorCalibrationModel
+ var driftModel: DriftCalibrationModel
+ var notes: String?
+
+ enum CodingKeys: String, CodingKey {
+ case schemaVersion = "schema_version"
+ case deviceID = "device_id"
+ case deviceName = "device_name"
+ case calibratedAt = "calibrated_at"
+ case modeCount = "mode_count"
+ case correlations
+ case detectorModel = "detector_model"
+ case driftModel = "drift_model"
+ case notes
+ }
+
+ init(
+ schemaVersion: Int,
+ deviceID: String,
+ deviceName: String,
+ calibratedAt: String?,
+ modeCount: Int?,
+ correlations: CalibratedNoiseCorrelationTensors,
+ detectorModel: DetectorCalibrationModel,
+ driftModel: DriftCalibrationModel,
+ notes: String?
+ ) {
+ self.schemaVersion = schemaVersion
+ self.deviceID = deviceID
+ self.deviceName = deviceName
+ self.calibratedAt = calibratedAt
+ self.modeCount = modeCount
+ self.correlations = correlations
+ self.detectorModel = detectorModel
+ self.driftModel = driftModel
+ self.notes = notes
+ }
+
+ init(from decoder: Decoder) throws {
+ let container = try decoder.container(keyedBy: CodingKeys.self)
+ schemaVersion = try container.decodeIfPresent(Int.self, forKey: .schemaVersion) ?? 1
+ deviceID = try container.decodeIfPresent(String.self, forKey: .deviceID) ?? "unknown-device"
+ deviceName = try container.decodeIfPresent(String.self, forKey: .deviceName) ?? deviceID
+ calibratedAt = try container.decodeIfPresent(String.self, forKey: .calibratedAt)
+ modeCount = try container.decodeIfPresent(Int.self, forKey: .modeCount)
+ correlations = try container.decodeIfPresent(CalibratedNoiseCorrelationTensors.self, forKey: .correlations)
+ ?? CalibratedNoiseCorrelationTensors(lossEta: nil, thermal: nil, phaseRad: nil, displacement: nil)
+ detectorModel = try container.decodeIfPresent(DetectorCalibrationModel.self, forKey: .detectorModel)
+ ?? .standard
+ driftModel = try container.decodeIfPresent(DriftCalibrationModel.self, forKey: .driftModel)
+ ?? .none
+ notes = try container.decodeIfPresent(String.self, forKey: .notes)
+ }
+}
+
+enum PaletteFilter: String, CaseIterable, Identifiable {
+ case all = "All"
+ case gaussian = "Gaussian"
+ case nonGaussian = "Non-Gaussian"
+ case channels = "Channels"
+ case measurement = "Measurement"
+ case control = "Control"
+
+ var id: String { rawValue }
+}
+
+enum PaletteSection: String, CaseIterable, Identifiable {
+ case sourceAndStatePrep = "SourceAndStatePrep"
+ case gaussianOps = "GaussianOps"
+ case nonGaussianOps = "NonGaussianOps"
+ case channels = "Channels"
+ case measurements = "Measurements"
+ case classicalControl = "ClassicalControl"
+
+ var id: String { rawValue }
+}
+
+enum GateKind: String, CaseIterable, Identifiable, Codable {
+ case sourceVacuum = "source_vacuum"
+ case sourceCoherent = "source_coherent"
+ case sourceSqueezed = "source_squeezed"
+
+ case phase = "phase"
+ case squeeze = "squeeze"
+ case beamSplitter = "beam_splitter"
+ case displace = "displace"
+
+ case injectFock = "inject_fock"
+ case injectCat = "inject_cat"
+ case injectGkp = "inject_gkp"
+
+ case loss = "loss"
+ case thermalLoss = "thermal_loss"
+
+ case measureHomodyne = "measure_homodyne"
+ case measureHeterodyne = "measure_heterodyne"
+
+ case classicalControl = "if_then"
+
+ var id: String { rawValue }
+
+ var displayName: String {
+ switch self {
+ case .sourceVacuum:
+ return "Source Vacuum"
+ case .sourceCoherent:
+ return "Source Coherent"
+ case .sourceSqueezed:
+ return "Source Squeezed"
+ case .phase:
+ return "Rz"
+ case .squeeze:
+ return "Squeeze"
+ case .beamSplitter:
+ return "Beam Splitter"
+ case .displace:
+ return "Displace"
+ case .injectFock:
+ return "Inject Fock"
+ case .injectCat:
+ return "Inject Cat"
+ case .injectGkp:
+ return "Inject GKP"
+ case .loss:
+ return "Loss"
+ case .thermalLoss:
+ return "Thermal Loss"
+ case .measureHomodyne:
+ return "Measure Homodyne"
+ case .measureHeterodyne:
+ return "Measure Heterodyne"
+ case .classicalControl:
+ return "Classical Control"
+ }
+ }
+
+ var hoverDisplayName: String {
+ switch self {
+ case .phase:
+ return "Rz (Phase Rotation)"
+ default:
+ return displayName
+ }
+ }
+
+ var systemIcon: String {
+ switch self {
+ case .sourceVacuum:
+ return "circle"
+ case .sourceCoherent:
+ return "dot.scope"
+ case .sourceSqueezed:
+ return "line.3.horizontal.decrease.circle"
+ case .phase:
+ return "angle"
+ case .squeeze:
+ return "arrow.left.and.right.righttriangle.left.righttriangle.right"
+ case .beamSplitter:
+ return "arrow.triangle.branch"
+ case .displace:
+ return "move.3d"
+ case .injectFock:
+ return "square.stack.3d.up"
+ case .injectCat:
+ return "sparkles"
+ case .injectGkp:
+ return "hexagon"
+ case .loss:
+ return "drop"
+ case .thermalLoss:
+ return "flame"
+ case .measureHomodyne:
+ return "waveform.path.ecg"
+ case .measureHeterodyne:
+ return "scope"
+ case .classicalControl:
+ return "switch.2"
+ }
+ }
+
+ var runtimeUnsupportedReason: String? {
+ nil
+ }
+
+ var symbol: String {
+ switch self {
+ case .sourceVacuum:
+ return "|0>"
+ case .sourceCoherent:
+ return "a"
+ case .sourceSqueezed:
+ return "r"
+ case .phase:
+ return "Rz"
+ case .squeeze:
+ return "S"
+ case .beamSplitter:
+ return "BS"
+ case .displace:
+ return "D"
+ case .injectFock:
+ return "|n>"
+ case .injectCat:
+ return "cat"
+ case .injectGkp:
+ return "gkp"
+ case .loss:
+ return "eta"
+ case .thermalLoss:
+ return "n_th"
+ case .measureHomodyne:
+ return "x"
+ case .measureHeterodyne:
+ return "x,p"
+ case .classicalControl:
+ return "if"
+ }
+ }
+
+ var section: PaletteSection {
+ switch self {
+ case .sourceVacuum, .sourceCoherent, .sourceSqueezed:
+ return .sourceAndStatePrep
+ case .phase, .squeeze, .beamSplitter, .displace:
+ return .gaussianOps
+ case .injectFock, .injectCat, .injectGkp:
+ return .nonGaussianOps
+ case .loss, .thermalLoss:
+ return .channels
+ case .measureHomodyne, .measureHeterodyne:
+ return .measurements
+ case .classicalControl:
+ return .classicalControl
+ }
+ }
+
+ var primaryFilter: PaletteFilter {
+ switch self {
+ case .sourceVacuum, .sourceCoherent, .sourceSqueezed, .phase, .squeeze, .beamSplitter, .displace:
+ return .gaussian
+ case .injectFock, .injectCat, .injectGkp:
+ return .nonGaussian
+ case .loss, .thermalLoss:
+ return .channels
+ case .measureHomodyne, .measureHeterodyne:
+ return .measurement
+ case .classicalControl:
+ return .control
+ }
+ }
+
+ var defaultParamA: Double {
+ switch self {
+ case .sourceCoherent:
+ return 0.30
+ case .sourceSqueezed:
+ return 0.60
+ case .phase:
+ return 0.0
+ case .squeeze:
+ return 0.8
+ case .beamSplitter:
+ return 0.25
+ case .displace:
+ return 0.35
+ case .loss:
+ return 0.9
+ case .thermalLoss:
+ return 0.85
+ case .classicalControl:
+ return 0.15
+ default:
+ return 0.0
+ }
+ }
+
+ var defaultParamB: Double {
+ switch self {
+ case .sourceCoherent:
+ return 0.00
+ case .displace:
+ return 0.2
+ case .thermalLoss:
+ return 0.1
+ default:
+ return 0.0
+ }
+ }
+
+ var primaryParamLabel: String? {
+ switch self {
+ case .sourceCoherent:
+ return "q (coherent)"
+ case .sourceSqueezed:
+ return "r (source squeeze)"
+ case .phase:
+ return "theta (Rz angle)"
+ case .squeeze:
+ return "r (squeeze factor)"
+ case .beamSplitter:
+ return "theta (mixing)"
+ case .displace:
+ return "alpha"
+ case .loss:
+ return "eta"
+ case .thermalLoss:
+ return "eta"
+ case .classicalControl:
+ return "theta (if-then phase)"
+ default:
+ return nil
+ }
+ }
+
+ var secondaryParamLabel: String? {
+ switch self {
+ case .sourceCoherent:
+ return "p (coherent)"
+ case .displace:
+ return "beta"
+ case .thermalLoss:
+ return "n_th"
+ default:
+ return nil
+ }
+ }
+
+ var primaryParamRange: ClosedRange? {
+ switch self {
+ case .sourceCoherent:
+ return -2.0...2.0
+ case .sourceSqueezed, .squeeze:
+ return 0.0...1.5
+ case .phase:
+ return -Double.pi...Double.pi
+ case .beamSplitter:
+ return 0.0...(Double.pi / 2.0)
+ case .displace:
+ return -2.0...2.0
+ case .loss, .thermalLoss:
+ return 0.0...1.0
+ case .classicalControl:
+ return -Double.pi...Double.pi
+ default:
+ return nil
+ }
+ }
+
+ var secondaryParamRange: ClosedRange? {
+ switch self {
+ case .sourceCoherent, .displace:
+ return -2.0...2.0
+ case .thermalLoss:
+ return 0.0...5.0
+ default:
+ return nil
+ }
+ }
+}
+
+struct CircuitNode: Identifiable, Codable, Hashable {
+ let id: UUID
+ var kind: GateKind
+ var mode: Int
+ var slot: Int
+ var paramA: Double
+ var paramB: Double
+
+ init(id: UUID = UUID(), kind: GateKind, mode: Int, slot: Int, paramA: Double? = nil, paramB: Double? = nil) {
+ self.id = id
+ self.kind = kind
+ self.mode = mode
+ self.slot = slot
+ self.paramA = paramA ?? kind.defaultParamA
+ self.paramB = paramB ?? kind.defaultParamB
+ }
+
+ var subtitle: String {
+ switch kind {
+ case .sourceVacuum:
+ return "vacuum"
+ case .sourceCoherent:
+ return "q=\(format(paramA)) p=\(format(paramB))"
+ case .sourceSqueezed:
+ return "r=\(format(paramA))"
+ case .phase:
+ return "theta=\(format(paramA))"
+ case .squeeze:
+ return "r=\(format(paramA))"
+ case .beamSplitter:
+ return "theta=\(format(paramA))"
+ case .displace:
+ return "alpha=\(format(paramA)) beta=\(format(paramB))"
+ case .loss:
+ return "eta=\(format(paramA))"
+ case .thermalLoss:
+ return "eta=\(format(paramA)) n_th=\(format(paramB))"
+ case .classicalControl:
+ return "if m then phase=\(format(paramA))"
+ default:
+ return "default"
+ }
+ }
+
+ private func format(_ value: Double) -> String {
+ String(format: "%.2f", value)
+ }
+}
+
+struct ScientificCircuitPreset: Identifiable, Hashable {
+ let id: String
+ let title: String
+ let citation: String
+ let doiURL: String
+ let sourceURL: String
+ let mappingNote: String
+ let recommendedBackend: BackendSelection
+ let suggestedSeed: String
+ let modeCount: Int
+ let slotCount: Int
+ let nodes: [CircuitNode]
+}
+
+enum ValidationSeverity: String, Codable {
+ case info = "Info"
+ case warning = "Warning"
+ case error = "Error"
+}
+
+struct ValidationIssue: Identifiable, Codable, Hashable {
+ let id: UUID
+ let severity: ValidationSeverity
+ let code: String
+ let message: String
+ let location: String
+
+ init(
+ id: UUID = UUID(),
+ severity: ValidationSeverity,
+ code: String,
+ message: String,
+ location: String
+ ) {
+ self.id = id
+ self.severity = severity
+ self.code = code
+ self.message = message
+ self.location = location
+ }
+}
+
+struct RunProvenance: Decodable, Hashable {
+ let foundryProfileHash: String?
+ let gitSHA: String?
+ let backendVersion: String?
+
+ enum CodingKeys: String, CodingKey {
+ case foundryProfileHash = "foundry_profile_hash"
+ case gitSHA = "git_sha"
+ case backendVersion = "backend_version"
+ }
+}
+
+struct QuantumStateSnapshot: Decodable, Hashable {
+ struct FockComponent: Decodable, Hashable, Identifiable {
+ let n: Int
+ let probability: Double
+ let re: Double?
+ let im: Double?
+
+ var id: Int { n }
+ }
+
+ let backend: String?
+ let representation: String?
+ let modes: Int?
+ let cutoff: Int?
+ let mean: [Double]?
+ let covariance: [[Double]]?
+ let probabilities: [Double]?
+ let topProbabilities: [FockComponent]?
+
+ enum CodingKeys: String, CodingKey {
+ case backend
+ case representation
+ case modes
+ case cutoff
+ case mean
+ case covariance
+ case probabilities
+ case topProbabilities = "top_probabilities"
+ }
+}
+
+struct QECReportRound: Decodable, Hashable, Identifiable {
+ let round: Int?
+ let gateIndex: Int?
+ let measurementIndex: Int?
+ let sourceValueIndex: Int?
+ let mode: Int?
+ let syndromeValue: Double?
+ let decoder: String?
+ let latticeSpacing: Double?
+ let targetLatticeIndex: Int?
+ let nearestLatticeIndex: Int?
+ let nearestLatticeValue: Double?
+ let residual: Double?
+ let correctionValue: Double?
+ let appliedQ: Double?
+ let appliedP: Double?
+ let logicalPass: Bool?
+
+ var id: Int {
+ round ?? gateIndex ?? measurementIndex ?? mode ?? 0
+ }
+
+ enum CodingKeys: String, CodingKey {
+ case round
+ case gateIndex = "gate_index"
+ case measurementIndex = "measurement_index"
+ case sourceValueIndex = "source_value_index"
+ case mode
+ case syndromeValue = "syndrome_value"
+ case decoder
+ case latticeSpacing = "lattice_spacing"
+ case targetLatticeIndex = "target_lattice_index"
+ case nearestLatticeIndex = "nearest_lattice_index"
+ case nearestLatticeValue = "nearest_lattice_value"
+ case residual
+ case correctionValue = "correction_value"
+ case appliedQ = "applied_q"
+ case appliedP = "applied_p"
+ case logicalPass = "logical_pass"
+ }
+}
+
+struct QECReport: Decodable, Hashable {
+ let decoder: String?
+ let roundsExecuted: Int?
+ let logicalPassCount: Int?
+ let logicalFailCount: Int?
+ let logicalPass: Bool?
+ let logicalErrorRate: Double?
+ let physicalErrorRateProxy: Double?
+ let suppressionFactor: Double?
+ let breakEvenGain: Double?
+ let breakEvenPass: Bool?
+ let rounds: [QECReportRound]?
+
+ enum CodingKeys: String, CodingKey {
+ case decoder
+ case roundsExecuted = "rounds_executed"
+ case logicalPassCount = "logical_pass_count"
+ case logicalFailCount = "logical_fail_count"
+ case logicalPass = "logical_pass"
+ case logicalErrorRate = "logical_error_rate"
+ case physicalErrorRateProxy = "physical_error_rate_proxy"
+ case suppressionFactor = "suppression_factor"
+ case breakEvenGain = "break_even_gain"
+ case breakEvenPass = "break_even_pass"
+ case rounds
+ }
+}
+
+struct RunResponse: Decodable, Hashable {
+ let status: String
+ let backend: String?
+ let meanPhotonNumber: Double?
+ let measurementCount: Int?
+ let finalState: QuantumStateSnapshot?
+ let qec: QECReport?
+ let provenance: RunProvenance?
+ let error: String?
+
+ enum CodingKeys: String, CodingKey {
+ case status
+ case backend
+ case meanPhotonNumber = "mean_photon_number"
+ case measurementCount = "measurement_count"
+ case finalState = "final_state"
+ case qec
+ case provenance
+ case error
+ }
+}
+
+struct TraceResponse: Decodable, Hashable {
+ struct Frame: Decodable, Hashable, Identifiable {
+ let frameIndex: Int
+ let gateIndex: Int?
+ let gateType: String
+ let meanPhotonNumber: Double
+ let measurementCount: Int
+ let frameLatencyMs: Double
+
+ var id: Int { frameIndex }
+
+ enum CodingKeys: String, CodingKey {
+ case frameIndex = "frame_index"
+ case gateIndex = "gate_index"
+ case gateType = "gate_type"
+ case meanPhotonNumber = "mean_photon_number"
+ case measurementCount = "measurement_count"
+ case frameLatencyMs = "frame_latency_ms"
+ }
+ }
+
+ let status: String
+ let backend: String?
+ let traceFrameCount: Int?
+ let traceOriginalFrameCount: Int?
+ let traceDroppedFrameCount: Int?
+ let traceDownsamplingApplied: Bool?
+ let traceRingBufferApplied: Bool?
+ let traceTotalMs: Double?
+ let traceMaxFrameLatencyMs: Double?
+ let playbackSuggestedFrameMS: Int?
+ let finalState: QuantumStateSnapshot?
+ let qec: QECReport?
+ let frames: [Frame]?
+ let error: String?
+
+ enum CodingKeys: String, CodingKey {
+ case status
+ case backend
+ case traceFrameCount = "trace_frame_count"
+ case traceOriginalFrameCount = "trace_original_frame_count"
+ case traceDroppedFrameCount = "trace_dropped_frame_count"
+ case traceDownsamplingApplied = "trace_downsampling_applied"
+ case traceRingBufferApplied = "trace_ring_buffer_applied"
+ case traceTotalMs = "trace_total_ms"
+ case traceMaxFrameLatencyMs = "trace_max_frame_latency_ms"
+ case playbackSuggestedFrameMS = "playback_suggested_frame_ms"
+ case finalState = "final_state"
+ case qec
+ case frames
+ case error
+ }
+}
diff --git a/core-swift/Sources/schrosim-studio/CLIService.swift b/core-swift/Sources/schrosim-studio/CLIService.swift
new file mode 100644
index 0000000..cb12e63
--- /dev/null
+++ b/core-swift/Sources/schrosim-studio/CLIService.swift
@@ -0,0 +1,962 @@
+import Foundation
+import AppKit
+import UniformTypeIdentifiers
+
+enum CLIRunnerError: LocalizedError {
+ case failedToDecodeJSON(String)
+ case commandFailed(String)
+ case malformedOutput(String)
+ case invalidRuntimeCircuit(String)
+ case invalidCircuitDocument(String)
+ case invalidNoiseProfile(String)
+ case unsupportedSaveFormat(String)
+
+ var errorDescription: String? {
+ switch self {
+ case .failedToDecodeJSON(let message):
+ return "Failed to decode CLI JSON: \(message)"
+ case .commandFailed(let message):
+ return "CLI command failed: \(message)"
+ case .malformedOutput(let output):
+ return "Malformed CLI output: \(output)"
+ case .invalidRuntimeCircuit(let message):
+ return "Runtime circuit is invalid: \(message)"
+ case .invalidCircuitDocument(let message):
+ return "Circuit document is invalid: \(message)"
+ case .invalidNoiseProfile(let message):
+ return "Noise profile is invalid: \(message)"
+ case .unsupportedSaveFormat(let message):
+ return "Unsupported save format: \(message)"
+ }
+ }
+}
+
+enum CLIService {
+ enum CircuitSaveFormat: String, CaseIterable {
+ case pdk
+ case json
+ case pdf
+
+ var fileExtension: String { rawValue }
+
+ var displayName: String {
+ switch self {
+ case .pdk:
+ return "SchroSIM Package (.pdk)"
+ case .json:
+ return "JSON Snapshot (.json)"
+ case .pdf:
+ return "Circuit Report (.pdf)"
+ }
+ }
+
+ var contentType: UTType {
+ switch self {
+ case .pdk:
+ return UTType(filenameExtension: fileExtension, conformingTo: .data) ?? .data
+ case .json:
+ return .json
+ case .pdf:
+ return .pdf
+ }
+ }
+
+ static func fromFileExtension(_ fileExtension: String) -> CircuitSaveFormat? {
+ let normalized = fileExtension.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
+ return allCases.first { $0.fileExtension == normalized }
+ }
+ }
+
+ struct CircuitSaveDestination {
+ let url: URL
+ let format: CircuitSaveFormat
+ }
+
+ struct CircuitSnapshotDocument {
+ let nodes: [CircuitNode]
+ let modeCount: Int
+ let slotCount: Int
+ let schemaVersion: String
+ let runtimeEngine: RuntimeEngineSelection?
+ let backend: BackendSelection?
+ let seed: String?
+ let cutoff: Int?
+ let foundryProfile: String?
+ let workspace: String?
+ let project: String?
+ let environment: EnvironmentBadge?
+ let userRole: UserRoleBadge?
+ let kpiPolicyID: String?
+ let kpiPolicyVersion: Int?
+ }
+
+ static func runCircuit(
+ jsonPath: String,
+ backend: BackendSelection,
+ environment: EnvironmentBadge,
+ runtimeEngine: RuntimeEngineSelection
+ ) throws -> RunResponse {
+ var arguments: [String]
+ switch runtimeEngine {
+ case .swift:
+ arguments = ["swift", "run", "schrosim-cli", "run", jsonPath, "--backend", backend.rawValue]
+ case .rust:
+ arguments = ["cargo", "run", "-q", "-p", "schrosim-core", "--", "run", jsonPath, "--backend", backend.rawValue]
+ }
+ if environment == .prod {
+ let registryPath = try validateProdRuntimePrerequisites()
+ arguments.append(contentsOf: ["--prod", "--foundry-registry", registryPath])
+ }
+
+ let (stdout, stderr, status) = try runCommand(
+ arguments: arguments
+ )
+
+ let response: RunResponse = try decodeFromMixedOutput(stdout)
+
+ if status != 0 || response.status != "success" {
+ throw CLIRunnerError.commandFailed(response.error ?? stderr)
+ }
+
+ return response
+ }
+
+ static func loadTrace(
+ jsonPath: String,
+ backend: BackendSelection,
+ role: UserRoleBadge,
+ environment: EnvironmentBadge,
+ runtimeEngine: RuntimeEngineSelection
+ ) throws -> TraceResponse {
+ var arguments: [String]
+ switch runtimeEngine {
+ case .swift:
+ arguments = [
+ "swift",
+ "run",
+ "schrosim-cli",
+ "trace",
+ jsonPath,
+ "--backend",
+ backend.rawValue,
+ "--trace-role",
+ role.rawValue,
+ ]
+ case .rust:
+ arguments = [
+ "cargo",
+ "run",
+ "-q",
+ "-p",
+ "schrosim-core",
+ "--",
+ "trace",
+ jsonPath,
+ "--backend",
+ backend.rawValue,
+ "--trace-role",
+ role.rawValue,
+ ]
+ }
+
+ if environment == .prod {
+ let registryPath = try validateProdRuntimePrerequisites()
+ arguments.append(contentsOf: ["--prod", "--foundry-registry", registryPath])
+ }
+
+ let (stdout, stderr, status) = try runCommand(
+ arguments: arguments
+ )
+
+ let response: TraceResponse = try decodeFromMixedOutput(stdout)
+
+ if status != 0 || response.status != "success" {
+ throw CLIRunnerError.commandFailed(response.error ?? stderr)
+ }
+
+ return response
+ }
+
+ static func materializeRuntimeCircuitInput(
+ nodes: [CircuitNode],
+ modeCount: Int,
+ backend: BackendSelection,
+ seed: String,
+ cutoff: Int,
+ schemaVersion: String,
+ foundryProfile: String,
+ environment: EnvironmentBadge,
+ foundryProfileID: String?,
+ foundryProfileVersion: Int?
+ ) throws -> URL {
+ let resolvedModeCount = max(modeCount, 1)
+ let resolvedSchemaVersion = max(1, Int(schemaVersion.trimmingCharacters(in: .whitespacesAndNewlines)) ?? 1)
+ let resolvedCutoff = max(1, cutoff)
+ let resolvedSeed = UInt64(seed.trimmingCharacters(in: .whitespacesAndNewlines))
+ let gatePayload = try runtimeGatePayload(nodes: nodes, modeCount: resolvedModeCount)
+
+ var payload: [String: Any] = [
+ "schema_version": resolvedSchemaVersion,
+ "modes": resolvedModeCount,
+ "backend": backend.rawValue,
+ "cutoff": resolvedCutoff,
+ "gates": gatePayload,
+ ]
+
+ if environment == .prod {
+ let trimmedID = foundryProfileID?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
+ guard !trimmedID.isEmpty, let foundryProfileVersion, foundryProfileVersion > 0 else {
+ throw CLIRunnerError.invalidRuntimeCircuit(
+ "Prod environment requires a valid foundry profile reference (profile_id/version)."
+ )
+ }
+ payload["foundry_profile"] = [
+ "profile_id": trimmedID,
+ "version": foundryProfileVersion,
+ ]
+ } else {
+ payload["foundry"] = [
+ "name": foundryProfile,
+ "max_modes": max(4, resolvedModeCount),
+ "max_squeezing_r": 1.5,
+ "allow_non_gaussian": true,
+ "allow_measurements": true,
+ "inject_mode_loss": false,
+ "mode_loss_eta": Array(repeating: 0.995, count: resolvedModeCount),
+ ]
+ }
+
+ if let resolvedSeed {
+ payload["seed"] = resolvedSeed
+ }
+
+ if nodes.contains(where: { $0.kind == .sourceVacuum }) {
+ payload["initial_state"] = "vacuum"
+ }
+
+ let data = try JSONSerialization.data(withJSONObject: payload, options: [.prettyPrinted, .sortedKeys])
+ let exportDir = repoRootURL().appendingPathComponent("exports", isDirectory: true)
+ try FileManager.default.createDirectory(at: exportDir, withIntermediateDirectories: true)
+ let runtimeURL = exportDir.appendingPathComponent("swiftui_runtime_input.json")
+ try data.write(to: runtimeURL, options: [.atomic])
+ return runtimeURL
+ }
+
+ static func validateRuntimeCircuit(
+ nodes: [CircuitNode],
+ modeCount: Int,
+ backend: BackendSelection
+ ) throws {
+ let resolvedModeCount = max(modeCount, 1)
+ _ = try runtimeGatePayload(nodes: nodes, modeCount: resolvedModeCount)
+
+ let requiresFockPath = nodes.contains { node in
+ node.kind == .injectFock || node.kind == .injectCat
+ }
+
+ let requiresFockCompatibility = backend == .fock || (backend == .hybrid && requiresFockPath)
+ guard requiresFockCompatibility else {
+ return
+ }
+
+ var reasons: [String] = []
+ if resolvedModeCount > 1 {
+ reasons.append("Fock path supports only single-mode circuits (got \(resolvedModeCount) modes).")
+ }
+
+ let fockSupportedKinds: Set = [
+ .sourceVacuum,
+ .sourceCoherent,
+ .phase,
+ .displace,
+ .injectFock,
+ .injectCat,
+ ]
+
+ let unsupportedKinds = Set(nodes.map(\.kind)).subtracting(fockSupportedKinds)
+ if !unsupportedKinds.isEmpty {
+ let labels = unsupportedKinds
+ .map { $0.rawValue }
+ .sorted()
+ .joined(separator: ", ")
+ reasons.append(
+ "Unsupported gates for Fock path: \(labels). " +
+ "Supported: source_vacuum, source_coherent, phase, displace, inject_fock, inject_cat."
+ )
+ }
+
+ if !reasons.isEmpty {
+ throw CLIRunnerError.invalidRuntimeCircuit(reasons.joined(separator: " "))
+ }
+ }
+
+ @MainActor
+ static func promptForCircuitSaveDestination(
+ suggestedBaseName: String,
+ existingURL: URL? = nil
+ ) -> CircuitSaveDestination? {
+ let defaultFormat = existingURL
+ .flatMap { CircuitSaveFormat.fromFileExtension($0.pathExtension) } ?? .pdk
+ let panel = NSSavePanel()
+ panel.title = "Save Circuit As"
+ panel.message = "Choose file name and format."
+ panel.prompt = "Save"
+ panel.nameFieldLabel = "Name:"
+ panel.canCreateDirectories = true
+ panel.allowsOtherFileTypes = false
+ panel.isExtensionHidden = false
+ panel.showsTagField = false
+ panel.allowedContentTypes = CircuitSaveFormat.allCases.map(\.contentType)
+
+ let accessoryContainer = NSStackView()
+ accessoryContainer.orientation = .vertical
+ accessoryContainer.alignment = .leading
+ accessoryContainer.spacing = 6
+ accessoryContainer.edgeInsets = NSEdgeInsets(top: 2, left: 0, bottom: 0, right: 0)
+
+ let formatLabel = NSTextField(labelWithString: "Format")
+ let formatPopup = NSPopUpButton()
+ for format in CircuitSaveFormat.allCases {
+ formatPopup.addItem(withTitle: format.displayName)
+ formatPopup.lastItem?.representedObject = format.rawValue
+ }
+ if let defaultIndex = CircuitSaveFormat.allCases.firstIndex(of: defaultFormat) {
+ formatPopup.selectItem(at: defaultIndex)
+ }
+ formatPopup.translatesAutoresizingMaskIntoConstraints = false
+ formatPopup.widthAnchor.constraint(greaterThanOrEqualToConstant: 280).isActive = true
+
+ accessoryContainer.addArrangedSubview(formatLabel)
+ accessoryContainer.addArrangedSubview(formatPopup)
+ panel.accessoryView = accessoryContainer
+
+ if let existingURL {
+ panel.directoryURL = existingURL.deletingLastPathComponent()
+ panel.nameFieldStringValue = existingURL.lastPathComponent
+ } else {
+ panel.directoryURL = repoRootURL().appendingPathComponent("exports", isDirectory: true)
+ panel.nameFieldStringValue = "\(suggestedBaseName).\(defaultFormat.fileExtension)"
+ }
+
+ guard panel.runModal() == .OK else {
+ return nil
+ }
+
+ let selectedFormatRaw = formatPopup.selectedItem?.representedObject as? String
+ let selectedFormat = selectedFormatRaw.flatMap(CircuitSaveFormat.init(rawValue:)) ?? defaultFormat
+ let selectedURL = resolvedSavePanelURL(from: panel) ?? panel.url
+ guard let selectedURL else { return nil }
+ return CircuitSaveDestination(url: selectedURL, format: selectedFormat)
+ }
+
+ @MainActor
+ static func promptForCircuitOpenURL(existingURL: URL? = nil) -> URL? {
+ let panel = NSOpenPanel()
+ panel.title = "Open Circuit"
+ panel.message = "Open a .pdk or .json circuit snapshot."
+ panel.prompt = "Open"
+ panel.canChooseFiles = true
+ panel.canChooseDirectories = false
+ panel.allowsMultipleSelection = false
+ panel.resolvesAliases = true
+ panel.allowedContentTypes = [
+ CircuitSaveFormat.pdk.contentType,
+ CircuitSaveFormat.json.contentType,
+ ]
+
+ if let existingURL {
+ panel.directoryURL = existingURL.deletingLastPathComponent()
+ } else {
+ panel.directoryURL = repoRootURL().appendingPathComponent("exports", isDirectory: true)
+ }
+
+ return panel.runModal() == .OK ? panel.url : nil
+ }
+
+ @MainActor
+ static func promptForNoiseProfileOpenURL(existingURL: URL? = nil) -> URL? {
+ let panel = NSOpenPanel()
+ panel.title = "Import Calibrated Noise Profile"
+ panel.message = "Open a calibrated device noise profile (.json)."
+ panel.prompt = "Import"
+ panel.canChooseFiles = true
+ panel.canChooseDirectories = false
+ panel.allowsMultipleSelection = false
+ panel.resolvesAliases = true
+ panel.allowedContentTypes = [.json]
+
+ if let existingURL {
+ panel.directoryURL = existingURL.deletingLastPathComponent()
+ } else {
+ panel.directoryURL = repoRootURL().appendingPathComponent("config", isDirectory: true)
+ }
+
+ return panel.runModal() == .OK ? panel.url : nil
+ }
+
+ static func loadCalibratedNoiseProfile(from url: URL) throws -> CalibratedNoiseTensorProfile {
+ let data = try Data(contentsOf: url)
+ do {
+ return try JSONDecoder().decode(CalibratedNoiseTensorProfile.self, from: data)
+ } catch {
+ if let wrapped = try? JSONDecoder().decode(CalibratedNoiseProfileEnvelope.self, from: data) {
+ return wrapped.profile
+ }
+ throw CLIRunnerError.invalidNoiseProfile(error.localizedDescription)
+ }
+ }
+
+ @MainActor
+ static func saveCircuitDocument(
+ nodes: [CircuitNode],
+ modeCount: Int,
+ slotCount: Int,
+ metadata: [String: String],
+ to requestedURL: URL,
+ format selectedFormat: CircuitSaveFormat? = nil
+ ) throws -> URL {
+ let explicitFormat = CircuitSaveFormat.fromFileExtension(requestedURL.pathExtension)
+ let selectedContentType = UTType(filenameExtension: requestedURL.pathExtension, conformingTo: .data)
+ let resolvedFormat = selectedFormat
+ ?? explicitFormat
+ ?? CircuitSaveFormat.allCases.first(where: { $0.contentType == selectedContentType })
+ ?? .pdk
+
+ let normalizedExtension = requestedURL.pathExtension.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
+ let targetURL: URL
+ if normalizedExtension == resolvedFormat.fileExtension {
+ targetURL = requestedURL
+ } else if normalizedExtension.isEmpty {
+ targetURL = requestedURL.appendingPathExtension(resolvedFormat.fileExtension)
+ } else {
+ targetURL = requestedURL.deletingPathExtension().appendingPathExtension(resolvedFormat.fileExtension)
+ }
+
+ let exportDir = targetURL.deletingLastPathComponent()
+ try FileManager.default.createDirectory(at: exportDir, withIntermediateDirectories: true)
+
+ switch resolvedFormat {
+ case .pdk, .json:
+ let payload = circuitSnapshotPayload(
+ nodes: nodes,
+ modeCount: modeCount,
+ slotCount: slotCount,
+ metadata: metadata
+ )
+ let data = try JSONSerialization.data(withJSONObject: payload, options: [.prettyPrinted, .sortedKeys])
+ try data.write(to: targetURL, options: [.atomic])
+ case .pdf:
+ try writeCircuitReportPDF(
+ nodes: nodes,
+ modeCount: modeCount,
+ slotCount: slotCount,
+ metadata: metadata,
+ to: targetURL
+ )
+ }
+
+ return targetURL
+ }
+
+ static func loadCircuitDocument(from url: URL) throws -> CircuitSnapshotDocument {
+ let data = try Data(contentsOf: url)
+ let object = try JSONSerialization.jsonObject(with: data, options: [])
+ guard let payload = object as? [String: Any] else {
+ throw CLIRunnerError.invalidCircuitDocument("Expected a JSON object at top-level.")
+ }
+
+ guard let nodeEntries = payload["nodes"] as? [Any] else {
+ throw CLIRunnerError.invalidCircuitDocument("Missing required 'nodes' array.")
+ }
+
+ var nodes: [CircuitNode] = []
+ nodes.reserveCapacity(nodeEntries.count)
+
+ for (index, entry) in nodeEntries.enumerated() {
+ guard let nodePayload = entry as? [String: Any] else {
+ throw CLIRunnerError.invalidCircuitDocument("Node at index \(index) is not an object.")
+ }
+
+ guard
+ let kindRaw = stringValue(from: nodePayload["kind"]),
+ let kind = GateKind(rawValue: kindRaw)
+ else {
+ throw CLIRunnerError.invalidCircuitDocument("Node at index \(index) has unknown 'kind'.")
+ }
+
+ guard let mode = intValue(from: nodePayload["mode"]), mode >= 0 else {
+ throw CLIRunnerError.invalidCircuitDocument("Node at index \(index) has invalid 'mode'.")
+ }
+
+ guard let slot = intValue(from: nodePayload["slot"]), slot >= 0 else {
+ throw CLIRunnerError.invalidCircuitDocument("Node at index \(index) has invalid 'slot'.")
+ }
+
+ let paramA = doubleValue(from: nodePayload["param_a"]) ?? kind.defaultParamA
+ let paramB = doubleValue(from: nodePayload["param_b"]) ?? kind.defaultParamB
+ let id = stringValue(from: nodePayload["id"]).flatMap(UUID.init(uuidString:)) ?? UUID()
+
+ nodes.append(
+ CircuitNode(
+ id: id,
+ kind: kind,
+ mode: mode,
+ slot: slot,
+ paramA: paramA,
+ paramB: paramB
+ )
+ )
+ }
+
+ let derivedModeCount = max(1, (nodes.map(\.mode).max() ?? -1) + 1)
+ let derivedSlotCount = max(1, (nodes.map(\.slot).max() ?? -1) + 1)
+ let modeCount = max(intValue(from: payload["mode_count"]) ?? derivedModeCount, 1)
+ let slotCount = max(intValue(from: payload["slot_count"]) ?? derivedSlotCount, 1)
+
+ let context = payload["context"] as? [String: Any] ?? [:]
+ let environmentRaw = stringValue(from: context["environment"])?.lowercased()
+ ?? stringValue(from: payload["environment"])?.lowercased()
+ let userRoleRaw = stringValue(from: context["role"])?.lowercased()
+ ?? stringValue(from: payload["role"])?.lowercased()
+
+ return CircuitSnapshotDocument(
+ nodes: nodes,
+ modeCount: modeCount,
+ slotCount: slotCount,
+ schemaVersion: stringValue(from: payload["schema_version"]) ?? "1",
+ runtimeEngine: stringValue(from: payload["runtime_engine"]).flatMap(RuntimeEngineSelection.init(rawValue:)),
+ backend: stringValue(from: payload["backend"]).flatMap(BackendSelection.init(rawValue:)),
+ seed: stringValue(from: payload["seed"]),
+ cutoff: intValue(from: payload["cutoff"]),
+ foundryProfile: stringValue(from: payload["foundry_profile"]),
+ workspace: stringValue(from: context["workspace"]) ?? stringValue(from: payload["workspace"]),
+ project: stringValue(from: context["project"]) ?? stringValue(from: payload["project"]),
+ environment: environmentRaw.flatMap(EnvironmentBadge.init(rawValue:)),
+ userRole: userRoleRaw.flatMap(UserRoleBadge.init(rawValue:)),
+ kpiPolicyID: stringValue(from: context["kpi_policy_id"]) ?? stringValue(from: payload["kpi_policy_id"]),
+ kpiPolicyVersion: intValue(from: context["kpi_policy_version"]) ?? intValue(from: payload["kpi_policy_version"])
+ )
+ }
+
+ private struct CalibratedNoiseProfileEnvelope: Decodable {
+ let profile: CalibratedNoiseTensorProfile
+
+ enum CodingKeys: String, CodingKey {
+ case profile
+ case calibratedNoiseProfile = "calibrated_noise_profile"
+ case noiseProfile = "noise_profile"
+ }
+
+ init(from decoder: Decoder) throws {
+ let container = try decoder.container(keyedBy: CodingKeys.self)
+ if let direct = try container.decodeIfPresent(CalibratedNoiseTensorProfile.self, forKey: .profile) {
+ profile = direct
+ return
+ }
+ if let calibrated = try container.decodeIfPresent(CalibratedNoiseTensorProfile.self, forKey: .calibratedNoiseProfile) {
+ profile = calibrated
+ return
+ }
+ if let noise = try container.decodeIfPresent(CalibratedNoiseTensorProfile.self, forKey: .noiseProfile) {
+ profile = noise
+ return
+ }
+ throw CLIRunnerError.invalidNoiseProfile(
+ "Expected `profile`, `calibrated_noise_profile`, or `noise_profile` object."
+ )
+ }
+ }
+
+ static func exportCircuitSnapshot(nodes: [CircuitNode], metadata: [String: String]) throws -> URL {
+ var payload: [String: Any] = [
+ "schema_version": metadata["schema_version"] ?? "1",
+ "runtime_engine": metadata["runtime_engine"] ?? "swift",
+ "backend": metadata["backend"] ?? "hybrid",
+ "seed": metadata["seed"] ?? "123456789",
+ "cutoff": Int(metadata["cutoff"] ?? "20") ?? 20,
+ "foundry_profile": metadata["foundry_profile"] ?? "studio-default-v1",
+ "nodes": nodes.map { node in
+ [
+ "id": node.id.uuidString,
+ "kind": node.kind.rawValue,
+ "mode": node.mode,
+ "slot": node.slot,
+ "param_a": node.paramA,
+ "param_b": node.paramB,
+ ]
+ },
+ ]
+
+ var context: [String: Any] = [:]
+ if let workspace = metadata["workspace"], !workspace.isEmpty {
+ context["workspace"] = workspace
+ }
+ if let project = metadata["project"], !project.isEmpty {
+ context["project"] = project
+ }
+ if let environment = metadata["environment"], !environment.isEmpty {
+ context["environment"] = environment
+ }
+ if let role = metadata["role"], !role.isEmpty {
+ context["role"] = role
+ }
+ if let foundryProfileID = metadata["foundry_profile_id"], !foundryProfileID.isEmpty {
+ context["foundry_profile_id"] = foundryProfileID
+ }
+ if let foundryProfileVersion = metadata["foundry_profile_version"], !foundryProfileVersion.isEmpty {
+ context["foundry_profile_version"] = foundryProfileVersion
+ }
+ if let kpiPolicyID = metadata["kpi_policy_id"], !kpiPolicyID.isEmpty {
+ context["kpi_policy_id"] = kpiPolicyID
+ }
+ if let kpiPolicyVersion = metadata["kpi_policy_version"], !kpiPolicyVersion.isEmpty {
+ context["kpi_policy_version"] = kpiPolicyVersion
+ }
+ if !context.isEmpty {
+ payload["context"] = context
+ }
+
+ let data = try JSONSerialization.data(withJSONObject: payload, options: [.prettyPrinted, .sortedKeys])
+ let exportDir = repoRootURL().appendingPathComponent("exports", isDirectory: true)
+ try FileManager.default.createDirectory(at: exportDir, withIntermediateDirectories: true)
+ let exportURL = exportDir.appendingPathComponent("swiftui_circuit_snapshot.json")
+ try data.write(to: exportURL, options: [.atomic])
+ return exportURL
+ }
+
+ private static func runtimeGatePayload(nodes: [CircuitNode], modeCount: Int) throws -> [[String: Any]] {
+ let sortedNodes = nodes.sorted {
+ if $0.slot == $1.slot {
+ return $0.mode < $1.mode
+ }
+ return $0.slot < $1.slot
+ }
+
+ var payload: [[String: Any]] = []
+ var consumedBeamSplitters = Set()
+ var emittedMeasurementCount = 0
+
+ func validateMode(_ mode: Int, kind: GateKind) throws -> Int {
+ guard mode >= 0, mode < modeCount else {
+ throw CLIRunnerError.invalidRuntimeCircuit(
+ "Mode index \(mode) for '\(kind.displayName)' is out of bounds 0...\(max(0, modeCount - 1))."
+ )
+ }
+ return mode
+ }
+
+ for node in sortedNodes {
+ if let reason = node.kind.runtimeUnsupportedReason {
+ throw CLIRunnerError.invalidRuntimeCircuit("\(node.kind.displayName): \(reason)")
+ }
+
+ switch node.kind {
+ case .sourceVacuum:
+ continue
+ case .sourceCoherent:
+ let mode = try validateMode(node.mode, kind: node.kind)
+ payload.append(["type": "displace", "q": node.paramA, "p": node.paramB, "mode": mode])
+ case .sourceSqueezed:
+ let mode = try validateMode(node.mode, kind: node.kind)
+ payload.append(["type": "squeeze", "r": node.paramA, "mode": mode])
+ case .phase:
+ let mode = try validateMode(node.mode, kind: node.kind)
+ payload.append(["type": "phase", "theta": node.paramA, "mode": mode])
+ case .squeeze:
+ let mode = try validateMode(node.mode, kind: node.kind)
+ payload.append(["type": "squeeze", "r": node.paramA, "mode": mode])
+ case .beamSplitter:
+ if consumedBeamSplitters.contains(node.id) {
+ continue
+ }
+
+ let modeA = try validateMode(node.mode, kind: node.kind)
+ let partner = sortedNodes.first {
+ $0.id != node.id &&
+ $0.kind == .beamSplitter &&
+ $0.slot == node.slot &&
+ !consumedBeamSplitters.contains($0.id)
+ }
+
+ let modeB: Int
+ if let partner {
+ modeB = try validateMode(partner.mode, kind: partner.kind)
+ consumedBeamSplitters.insert(partner.id)
+ } else {
+ guard modeCount > 1 else {
+ throw CLIRunnerError.invalidRuntimeCircuit(
+ "Beam splitter requires at least 2 modes."
+ )
+ }
+ modeB = modeA < (modeCount - 1) ? (modeA + 1) : (modeA - 1)
+ }
+
+ consumedBeamSplitters.insert(node.id)
+
+ guard modeA != modeB else {
+ throw CLIRunnerError.invalidRuntimeCircuit(
+ "Beam splitter at mode \(modeA), slot \(node.slot) needs two different modes."
+ )
+ }
+
+ payload.append([
+ "type": "beam_splitter",
+ "theta": node.paramA,
+ "mode_a": min(modeA, modeB),
+ "mode_b": max(modeA, modeB),
+ ])
+ case .displace:
+ let mode = try validateMode(node.mode, kind: node.kind)
+ payload.append(["type": "displace", "q": node.paramA, "p": node.paramB, "mode": mode])
+ case .loss:
+ let mode = try validateMode(node.mode, kind: node.kind)
+ payload.append(["type": "loss", "eta": node.paramA, "mode": mode])
+ case .thermalLoss:
+ let mode = try validateMode(node.mode, kind: node.kind)
+ payload.append(["type": "thermal_loss", "eta": node.paramA, "n_th": node.paramB, "mode": mode])
+ case .measureHomodyne:
+ let mode = try validateMode(node.mode, kind: node.kind)
+ payload.append(["type": "measure_homodyne", "theta": node.paramA, "mode": mode])
+ emittedMeasurementCount += 1
+ case .measureHeterodyne:
+ let mode = try validateMode(node.mode, kind: node.kind)
+ payload.append(["type": "measure_heterodyne", "mode": mode])
+ emittedMeasurementCount += 1
+ case .injectFock:
+ let mode = try validateMode(node.mode, kind: node.kind)
+ payload.append(["type": "inject_fock", "mode": mode, "n": max(1, Int(node.paramA.rounded()))])
+ case .injectCat:
+ let mode = try validateMode(node.mode, kind: node.kind)
+ payload.append(["type": "inject_cat", "mode": mode, "alpha": node.paramA])
+ case .injectGkp:
+ let mode = try validateMode(node.mode, kind: node.kind)
+ payload.append(["type": "inject_gkp", "mode": mode, "delta": max(0.001, abs(node.paramA))])
+ case .classicalControl:
+ let mode = try validateMode(node.mode, kind: node.kind)
+ guard emittedMeasurementCount > 0 else {
+ throw CLIRunnerError.invalidRuntimeCircuit(
+ "Classical control requires at least one earlier measurement."
+ )
+ }
+ payload.append([
+ "type": "if_then",
+ "on": emittedMeasurementCount - 1,
+ "apply_type": "phase",
+ "apply_mode": mode,
+ "apply_theta": node.paramA,
+ ])
+ }
+ }
+
+ return payload
+ }
+
+ private static func circuitSnapshotPayload(
+ nodes: [CircuitNode],
+ modeCount: Int,
+ slotCount: Int,
+ metadata: [String: String]
+ ) -> [String: Any] {
+ var payload: [String: Any] = [
+ "document_type": "schrosim_circuit_snapshot_v1",
+ "saved_at": ISO8601DateFormatter().string(from: Date()),
+ "schema_version": metadata["schema_version"] ?? "1",
+ "runtime_engine": metadata["runtime_engine"] ?? "swift",
+ "backend": metadata["backend"] ?? "hybrid",
+ "seed": metadata["seed"] ?? "123456789",
+ "cutoff": Int(metadata["cutoff"] ?? "20") ?? 20,
+ "foundry_profile": metadata["foundry_profile"] ?? "studio-default-v1",
+ "mode_count": max(modeCount, 1),
+ "slot_count": max(slotCount, 1),
+ "nodes": nodes.map { node in
+ [
+ "id": node.id.uuidString,
+ "kind": node.kind.rawValue,
+ "mode": node.mode,
+ "slot": node.slot,
+ "param_a": node.paramA,
+ "param_b": node.paramB,
+ ]
+ },
+ ]
+
+ var context: [String: Any] = [:]
+ if let workspace = metadata["workspace"], !workspace.isEmpty {
+ context["workspace"] = workspace
+ }
+ if let project = metadata["project"], !project.isEmpty {
+ context["project"] = project
+ }
+ if let environment = metadata["environment"], !environment.isEmpty {
+ context["environment"] = environment
+ }
+ if let role = metadata["role"], !role.isEmpty {
+ context["role"] = role
+ }
+ if let foundryProfileID = metadata["foundry_profile_id"], !foundryProfileID.isEmpty {
+ context["foundry_profile_id"] = foundryProfileID
+ }
+ if let foundryProfileVersion = metadata["foundry_profile_version"], !foundryProfileVersion.isEmpty {
+ context["foundry_profile_version"] = foundryProfileVersion
+ }
+ if let kpiPolicyID = metadata["kpi_policy_id"], !kpiPolicyID.isEmpty {
+ context["kpi_policy_id"] = kpiPolicyID
+ }
+ if let kpiPolicyVersion = metadata["kpi_policy_version"], !kpiPolicyVersion.isEmpty {
+ context["kpi_policy_version"] = kpiPolicyVersion
+ }
+ if !context.isEmpty {
+ payload["context"] = context
+ }
+
+ return payload
+ }
+
+ @MainActor
+ private static func writeCircuitReportPDF(
+ nodes: [CircuitNode],
+ modeCount: Int,
+ slotCount: Int,
+ metadata: [String: String],
+ to targetURL: URL
+ ) throws {
+ let pdfData = CircuitReportPDFRenderer.render(
+ nodes: nodes,
+ modeCount: modeCount,
+ slotCount: slotCount,
+ metadata: metadata
+ )
+ try pdfData.write(to: targetURL, options: [.atomic])
+ }
+
+ private static func runCommand(arguments: [String]) throws -> (stdout: String, stderr: String, status: Int32) {
+ let process = Process()
+ process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
+ process.arguments = arguments
+ process.currentDirectoryURL = repoRootURL()
+
+ var environment = ProcessInfo.processInfo.environment
+ environment["NO_COLOR"] = "1"
+ process.environment = environment
+
+ let stdoutPipe = Pipe()
+ let stderrPipe = Pipe()
+ process.standardOutput = stdoutPipe
+ process.standardError = stderrPipe
+
+ try process.run()
+ process.waitUntilExit()
+
+ let stdoutData = stdoutPipe.fileHandleForReading.readDataToEndOfFile()
+ let stderrData = stderrPipe.fileHandleForReading.readDataToEndOfFile()
+ let stdout = String(decoding: stdoutData, as: UTF8.self)
+ let stderr = String(decoding: stderrData, as: UTF8.self)
+
+ return (stdout, stderr, process.terminationStatus)
+ }
+
+ private static func decodeFromMixedOutput(_ output: String) throws -> T {
+ guard
+ let openIndex = output.firstIndex(of: "{"),
+ let closeIndex = output.lastIndex(of: "}")
+ else {
+ throw CLIRunnerError.malformedOutput(output)
+ }
+
+ let jsonCandidate = String(output[openIndex...closeIndex])
+ let data = Data(jsonCandidate.utf8)
+ do {
+ return try JSONDecoder().decode(T.self, from: data)
+ } catch {
+ throw CLIRunnerError.failedToDecodeJSON("\(error)")
+ }
+ }
+
+ private static func intValue(from rawValue: Any?) -> Int? {
+ switch rawValue {
+ case let value as Int:
+ return value
+ case let value as Int64:
+ return Int(value)
+ case let value as Double:
+ return Int(value)
+ case let value as NSNumber:
+ return value.intValue
+ case let value as String:
+ return Int(value.trimmingCharacters(in: .whitespacesAndNewlines))
+ default:
+ return nil
+ }
+ }
+
+ private static func doubleValue(from rawValue: Any?) -> Double? {
+ switch rawValue {
+ case let value as Double:
+ return value
+ case let value as Int:
+ return Double(value)
+ case let value as NSNumber:
+ return value.doubleValue
+ case let value as String:
+ return Double(value.trimmingCharacters(in: .whitespacesAndNewlines))
+ default:
+ return nil
+ }
+ }
+
+ private static func stringValue(from rawValue: Any?) -> String? {
+ switch rawValue {
+ case let value as String:
+ let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
+ return trimmed.isEmpty ? nil : trimmed
+ case let value as NSNumber:
+ return value.stringValue
+ default:
+ return nil
+ }
+ }
+
+ private static func resolvedSavePanelURL(from panel: NSSavePanel) -> URL? {
+ let typedName = panel.nameFieldStringValue.trimmingCharacters(in: .whitespacesAndNewlines)
+ guard !typedName.isEmpty else { return panel.url }
+ let sanitizedName = typedName.replacingOccurrences(of: "/", with: "_")
+
+ if let directoryURL = panel.directoryURL {
+ return directoryURL.appendingPathComponent(sanitizedName)
+ }
+
+ guard let fallbackURL = panel.url else { return nil }
+ return fallbackURL.deletingLastPathComponent().appendingPathComponent(sanitizedName)
+ }
+
+ private static func repoRootURL() -> URL {
+ let fileURL = URL(fileURLWithPath: #filePath)
+ return fileURL
+ .deletingLastPathComponent() // schrosim-studio
+ .deletingLastPathComponent() // Sources
+ .deletingLastPathComponent() // core-swift
+ .deletingLastPathComponent() // repo root
+ }
+
+ private static func defaultFoundryRegistryPath() -> String {
+ repoRootURL().appendingPathComponent("docs/config/foundry_registry.json").path
+ }
+
+ private static func validateProdRuntimePrerequisites() throws -> String {
+ let foundrySigningKey = ProcessInfo.processInfo.environment["SCHROSIM_FOUNDRY_HMAC_KEY"]?
+ .trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
+ guard !foundrySigningKey.isEmpty else {
+ throw CLIRunnerError.invalidRuntimeCircuit(
+ "Prod mode requires SCHROSIM_FOUNDRY_HMAC_KEY for foundry signature verification."
+ )
+ }
+
+ let registryPath = defaultFoundryRegistryPath()
+ guard FileManager.default.fileExists(atPath: registryPath) else {
+ throw CLIRunnerError.invalidRuntimeCircuit(
+ "Prod mode requires foundry registry at \(registryPath)."
+ )
+ }
+ return registryPath
+ }
+}
diff --git a/core-swift/Sources/schrosim-studio/CircuitReportPDFRenderer.swift b/core-swift/Sources/schrosim-studio/CircuitReportPDFRenderer.swift
new file mode 100644
index 0000000..9f480b6
--- /dev/null
+++ b/core-swift/Sources/schrosim-studio/CircuitReportPDFRenderer.swift
@@ -0,0 +1,366 @@
+import Foundation
+import AppKit
+import SwiftUI
+
+@MainActor
+enum CircuitReportPDFRenderer {
+ static func render(
+ nodes: [CircuitNode],
+ modeCount: Int,
+ slotCount: Int,
+ metadata: [String: String]
+ ) -> Data {
+ let safeModeCount = max(modeCount, 1)
+ let safeSlotCount = max(slotCount, 1)
+ let layout = CircuitReportLayout()
+ let canvasWidth = layout.canvasWidth(slotCount: safeSlotCount)
+ let canvasHeight = layout.canvasHeight(modeCount: safeModeCount)
+ let componentLines = componentLineItems(nodes: nodes)
+ let componentHeight = min(280, CGFloat(max(componentLines.count, 1)) * 18 + 54)
+ let reportWidth = max(980, canvasWidth + 56)
+ let reportHeight = 222 + canvasHeight + componentHeight
+
+ let reportView = CircuitReportPDFView(
+ nodes: nodes,
+ modeCount: safeModeCount,
+ slotCount: safeSlotCount,
+ metadata: metadata,
+ layout: layout,
+ canvasWidth: canvasWidth,
+ canvasHeight: canvasHeight,
+ componentLines: componentLines
+ )
+ .frame(width: reportWidth, height: reportHeight, alignment: .topLeading)
+ .background(Color.white)
+
+ let hostingView = NSHostingView(rootView: reportView)
+ hostingView.frame = NSRect(x: 0, y: 0, width: reportWidth, height: reportHeight)
+ hostingView.layoutSubtreeIfNeeded()
+ return hostingView.dataWithPDF(inside: hostingView.bounds)
+ }
+
+ private static func componentLineItems(nodes: [CircuitNode]) -> [String] {
+ let sortedNodes = nodes.sorted {
+ if $0.slot == $1.slot { return $0.mode < $1.mode }
+ return $0.slot < $1.slot
+ }
+
+ guard !sortedNodes.isEmpty else {
+ return ["(empty)"]
+ }
+
+ return sortedNodes.map { node in
+ let paramAText = String(format: "%.3f", node.paramA)
+ let paramBText = String(format: "%.3f", node.paramB)
+ return "t\(node.slot) m\(node.mode) \(node.kind.rawValue) a=\(paramAText) b=\(paramBText)"
+ }
+ }
+}
+
+private struct CircuitReportLayout {
+ let laneLabelWidth: CGFloat = 110
+ let slotWidth: CGFloat = 140
+ let slotHeight: CGFloat = 68
+ let rowSpacing: CGFloat = 10
+ let columnSpacing: CGFloat = 8
+ let canvasPadding: CGFloat = 10
+ let timelineHeaderHeight: CGFloat = 24
+
+ func canvasWidth(slotCount: Int) -> CGFloat {
+ let safeSlotCount = max(slotCount, 1)
+ return canvasPadding * 2
+ + laneLabelWidth
+ + columnSpacing
+ + CGFloat(safeSlotCount) * slotWidth
+ + CGFloat(max(safeSlotCount - 1, 0)) * columnSpacing
+ }
+
+ func canvasHeight(modeCount: Int) -> CGFloat {
+ let safeModeCount = max(modeCount, 1)
+ return canvasPadding * 2
+ + timelineHeaderHeight
+ + CGFloat(safeModeCount) * slotHeight
+ + CGFloat(max(safeModeCount - 1, 0)) * rowSpacing
+ }
+}
+
+private struct CircuitReportPDFView: View {
+ let nodes: [CircuitNode]
+ let modeCount: Int
+ let slotCount: Int
+ let metadata: [String: String]
+ let layout: CircuitReportLayout
+ let canvasWidth: CGFloat
+ let canvasHeight: CGFloat
+ let componentLines: [String]
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 14) {
+ Text("SchroSIM Circuit Report")
+ .font(.system(size: 28, weight: .semibold))
+
+ HStack(alignment: .top, spacing: 36) {
+ VStack(alignment: .leading, spacing: 4) {
+ metadataLine("workspace", metadata["workspace"] ?? "n/a")
+ metadataLine("project", metadata["project"] ?? "n/a")
+ metadataLine("backend", metadata["backend"] ?? "hybrid")
+ metadataLine("foundry_profile", metadata["foundry_profile"] ?? "n/a")
+ }
+ VStack(alignment: .leading, spacing: 4) {
+ metadataLine("seed", metadata["seed"] ?? "n/a")
+ metadataLine("cutoff", metadata["cutoff"] ?? "n/a")
+ metadataLine("modes", "\(modeCount)")
+ metadataLine("slots", "\(slotCount)")
+ }
+ Spacer(minLength: 0)
+ }
+
+ CircuitReportCanvasView(
+ nodes: nodes,
+ modeCount: modeCount,
+ slotCount: slotCount,
+ layout: layout
+ )
+ .frame(width: canvasWidth, height: canvasHeight, alignment: .topLeading)
+
+ VStack(alignment: .leading, spacing: 4) {
+ Text("Placed Components (slot, mode, kind, params):")
+ .font(.system(size: 13, weight: .semibold, design: .monospaced))
+
+ ForEach(Array(componentLines.enumerated()), id: \.offset) { item in
+ Text(item.element)
+ .font(.system(size: 12, weight: .regular, design: .monospaced))
+ .foregroundStyle(.primary)
+ }
+ }
+ }
+ .padding(24)
+ }
+
+ private func metadataLine(_ key: String, _ value: String) -> some View {
+ Text("\(key): \(value)")
+ .font(.system(size: 13, weight: .regular, design: .monospaced))
+ .foregroundStyle(.primary)
+ }
+}
+
+private struct CircuitReportCanvasView: View {
+ let nodes: [CircuitNode]
+ let modeCount: Int
+ let slotCount: Int
+ let layout: CircuitReportLayout
+
+ var body: some View {
+ ZStack(alignment: .topLeading) {
+ RoundedRectangle(cornerRadius: 10)
+ .fill(Color(red: 0.965, green: 0.972, blue: 0.985))
+ .overlay(
+ RoundedRectangle(cornerRadius: 10)
+ .stroke(Color.black.opacity(0.10), lineWidth: 1)
+ )
+
+ CircuitReportTimelineView(
+ slotCount: slotCount,
+ layout: layout
+ )
+ .allowsHitTesting(false)
+
+ CircuitReportConnectorOverlayView(
+ modeCount: modeCount,
+ slotCount: slotCount,
+ nodes: nodes,
+ layout: layout
+ )
+ .allowsHitTesting(false)
+
+ VStack(alignment: .leading, spacing: layout.rowSpacing) {
+ ForEach(0..")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+ .padding(.horizontal, 6)
+ .padding(.vertical, 6)
+ .frame(width: layout.laneLabelWidth, alignment: .leading)
+
+ ForEach(0.. CircuitNode? {
+ nodes.first { $0.mode == mode && $0.slot == slot }
+ }
+}
+
+private struct CircuitReportSlotCellView: View {
+ let node: CircuitNode?
+ let size: CGSize
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 4) {
+ if let node {
+ HStack(spacing: 8) {
+ Image(systemName: node.kind.systemIcon)
+ .font(.caption.weight(.semibold))
+ .frame(width: 18)
+ Text(node.kind.displayName)
+ .font(.system(size: 12, weight: .semibold))
+ .lineLimit(1)
+ }
+
+ Text(node.subtitle)
+ .font(.system(size: 11, weight: .regular))
+ .foregroundStyle(.secondary)
+ .lineLimit(1)
+ } else {
+ VStack(spacing: 2) {
+ Text("Drop")
+ .font(.system(size: 11, weight: .semibold))
+ .foregroundStyle(.blue)
+ Text("+")
+ .font(.system(size: 16, weight: .bold))
+ .foregroundStyle(.blue)
+ }
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
+ }
+ }
+ .padding(8)
+ .frame(width: size.width, height: size.height, alignment: .leading)
+ .background(
+ RoundedRectangle(cornerRadius: 8)
+ .fill(node == nil ? Color.white.opacity(0.65) : Color.white)
+ .overlay(
+ RoundedRectangle(cornerRadius: 8)
+ .stroke(
+ node == nil ? Color.blue.opacity(0.45) : Color.black.opacity(0.18),
+ style: StrokeStyle(lineWidth: 1, dash: node == nil ? [5, 4] : [])
+ )
+ )
+ )
+ }
+}
+
+private struct CircuitReportTimelineView: View {
+ let slotCount: Int
+ let layout: CircuitReportLayout
+
+ var body: some View {
+ Canvas { context, _ in
+ let safeSlotCount = max(slotCount, 1)
+ let axisY = layout.canvasPadding + 18
+ let firstX = layout.canvasPadding + layout.laneLabelWidth + layout.columnSpacing
+ let stepX = layout.slotWidth + layout.columnSpacing
+ let lastX = firstX
+ + CGFloat(safeSlotCount) * layout.slotWidth
+ + CGFloat(max(safeSlotCount - 1, 0)) * layout.columnSpacing
+
+ var axisPath = Path()
+ axisPath.move(to: CGPoint(x: firstX, y: axisY))
+ axisPath.addLine(to: CGPoint(x: lastX, y: axisY))
+ context.stroke(axisPath, with: .color(Color.secondary.opacity(0.72)), lineWidth: 1)
+
+ for slot in 0.. 0, slotCount > 0 else { return }
+ let groupedBySlot = Dictionary(grouping: nodes) { $0.slot }
+ let baseWidth: CGFloat = 1.4
+ let laneColor = Color.black.opacity(0.50)
+ let couplerColor = Color.black.opacity(0.65)
+
+ for mode in 0.. 1 else { continue }
+ let x = slotCenterX(slot)
+ let sortedModes = slotNodes.map(\.mode).sorted()
+ let y0 = laneCenterY(sortedModes.first ?? 0)
+ let y1 = laneCenterY(sortedModes.last ?? 0)
+
+ var couplerPath = Path()
+ couplerPath.move(to: CGPoint(x: x, y: y0))
+ couplerPath.addLine(to: CGPoint(x: x, y: y1))
+ context.stroke(
+ couplerPath,
+ with: .color(couplerColor),
+ style: StrokeStyle(lineWidth: baseWidth, lineCap: .round)
+ )
+
+ for mode in Set(slotNodes.map(\.mode)) {
+ let y = laneCenterY(mode)
+ let rect = CGRect(x: x - 2.3, y: y - 2.3, width: 4.6, height: 4.6)
+ context.fill(Path(ellipseIn: rect), with: .color(Color.black.opacity(0.88)))
+ }
+ }
+ }
+ }
+
+ private var leftLaneX: CGFloat {
+ layout.canvasPadding + layout.laneLabelWidth + layout.columnSpacing + 6
+ }
+
+ private var rightLaneX: CGFloat {
+ leftLaneX
+ + CGFloat(slotCount) * layout.slotWidth
+ + CGFloat(max(slotCount - 1, 0)) * layout.columnSpacing
+ - 12
+ }
+
+ private func laneCenterY(_ mode: Int) -> CGFloat {
+ layout.canvasPadding
+ + layout.timelineHeaderHeight
+ + CGFloat(mode) * (layout.slotHeight + layout.rowSpacing)
+ + layout.slotHeight / 2
+ }
+
+ private func slotCenterX(_ slot: Int) -> CGFloat {
+ leftLaneX + CGFloat(slot) * (layout.slotWidth + layout.columnSpacing) + layout.slotWidth / 2
+ }
+}
diff --git a/core-swift/Sources/schrosim-studio/SchroSIMStudioApp.swift b/core-swift/Sources/schrosim-studio/SchroSIMStudioApp.swift
new file mode 100644
index 0000000..72cbd28
--- /dev/null
+++ b/core-swift/Sources/schrosim-studio/SchroSIMStudioApp.swift
@@ -0,0 +1,16 @@
+import SwiftUI
+
+@main
+struct SchroSIMStudioApp: App {
+ @StateObject private var store = StudioStore()
+
+ var body: some Scene {
+ WindowGroup("SchroSIM") {
+ StudioAppShellView()
+ .environmentObject(store)
+ .frame(minWidth: 1640, minHeight: 900)
+ .preferredColorScheme(store.theme == .black ? .dark : .light)
+ }
+ .defaultSize(width: 1720, height: 980)
+ }
+}
diff --git a/core-swift/Sources/schrosim-studio/StudioStore.swift b/core-swift/Sources/schrosim-studio/StudioStore.swift
new file mode 100644
index 0000000..7f966d4
--- /dev/null
+++ b/core-swift/Sources/schrosim-studio/StudioStore.swift
@@ -0,0 +1,3498 @@
+import Foundation
+import SwiftUI
+import SchroSIM
+
+@MainActor
+final class StudioStore: ObservableObject {
+ private struct WorkspaceProjectContext {
+ let workspace: String
+ let project: String
+ let defaultBackend: BackendSelection
+ let defaultSeed: String
+ let defaultCircuitPath: String
+ let devFoundryProfile: String
+ let stageFoundryProfile: String
+ let prodFoundryProfile: String
+ }
+
+ private struct FoundryProfileRef {
+ let profileID: String
+ let version: Int
+ }
+
+ private struct FoundryRegistryDocument: Decodable {
+ let profiles: [FoundryRegistryProfile]
+ }
+
+ private struct FoundryRegistryProfile: Decodable {
+ let profileID: String
+ let version: Int
+ let status: String
+ let spec: FoundryRegistrySpec
+
+ enum CodingKeys: String, CodingKey {
+ case profileID = "profile_id"
+ case version
+ case status
+ case spec
+ }
+ }
+
+ private struct FoundryRegistrySpec: Decodable {
+ let name: String
+
+ enum CodingKeys: String, CodingKey {
+ case name
+ }
+ }
+
+ private struct FoundryRegistryCatalog {
+ let allProfileNames: [String]
+ let approvedProfileNames: Set
+ let profileRefsByName: [String: FoundryProfileRef]
+ }
+
+ private struct RobustnessContext {
+ let nodes: [CircuitNode]
+ let modeCount: Int
+ let backend: BackendSelection
+ let seed: String
+ let cutoff: Int
+ let schemaVersion: String
+ let foundryProfile: String
+ let environment: EnvironmentBadge
+ let foundryProfileID: String?
+ let foundryProfileVersion: Int?
+ let runtimeEngine: RuntimeEngineSelection
+ let noiseModel: MonteCarloNoiseModel
+ let calibratedProfile: CalibratedNoiseTensorProfile?
+ }
+
+ private struct KPIPolicyCatalogDocument: Decodable {
+ let policies: [KPIPolicyDefinition]
+ }
+
+ private struct SeededGenerator: RandomNumberGenerator {
+ private var state: UInt64
+
+ init(seed: UInt64) {
+ state = seed == 0 ? 0x9E37_79B9_7F4A_7C15 : seed
+ }
+
+ mutating func next() -> UInt64 {
+ state &+= 0x9E37_79B9_7F4A_7C15
+ var z = state
+ z = (z ^ (z >> 30)) &* 0xBF58_476D_1CE4_E5B9
+ z = (z ^ (z >> 27)) &* 0x94D0_49BB_1331_11EB
+ return z ^ (z >> 31)
+ }
+ }
+
+ private static let fallbackFoundryProfiles = [
+ "studio-default-v1",
+ "studio-default-v2",
+ "experimental-lab-v1",
+ ]
+
+ private static let cvGaussianProductionPolicyID = "cv_gaussian_production_v1"
+ private static let cvQECGaussianPolicyID = "cv_qec_gaussian_v1"
+ private static let cvFockValidationPolicyID = "cv_fock_validation_v1"
+
+ private static let fallbackKPIPolicies: [KPIPolicyDefinition] = [
+ KPIPolicyDefinition(
+ id: StudioStore.cvGaussianProductionPolicyID,
+ name: "CV Gaussian Production",
+ version: 1,
+ targetRepresentation: "gaussian_phase_space",
+ summary: "Production readiness for Gaussian CV circuits using deterministic parity and calibrated-noise robustness bounds.",
+ references: [
+ "Open-source studio qualification standard: CV-GAUSS-PROD-v1"
+ ],
+ thresholds: KPIPolicyThresholds(
+ minSuccessRate: 0.95,
+ maxMeanPhotonP95Delta: 0.050,
+ maxStateP95Delta: 0.020,
+ maxMeasurementMismatchRate: 0.01,
+ minMeanPhotonNumber: 0.0,
+ maxMeanPhotonNumber: 250.0,
+ minSqueezingDB: 1.0,
+ minGaussianPurity: 0.50,
+ maxFockNormalizationError: nil,
+ maxLogicalErrorRate: nil,
+ minSuppressionFactor: nil,
+ minBreakEvenGain: nil
+ )
+ ),
+ KPIPolicyDefinition(
+ id: StudioStore.cvQECGaussianPolicyID,
+ name: "CV QEC Gaussian",
+ version: 1,
+ targetRepresentation: "gaussian_phase_space",
+ summary: "QEC policy for Gaussian/Hybrid backends focused on logical error rate, suppression against physical shift noise, and break-even gain.",
+ references: [
+ "Open-source studio qualification standard: CV-QEC-GAUSS-v1"
+ ],
+ thresholds: KPIPolicyThresholds(
+ minSuccessRate: 0.90,
+ maxMeanPhotonP95Delta: 0.120,
+ maxStateP95Delta: 0.080,
+ maxMeasurementMismatchRate: 0.05,
+ minMeanPhotonNumber: 0.0,
+ maxMeanPhotonNumber: 400.0,
+ minSqueezingDB: nil,
+ minGaussianPurity: nil,
+ maxFockNormalizationError: nil,
+ maxLogicalErrorRate: 0.34,
+ minSuppressionFactor: 1.0,
+ minBreakEvenGain: 0.0
+ )
+ ),
+ KPIPolicyDefinition(
+ id: StudioStore.cvFockValidationPolicyID,
+ name: "CV Fock Validation",
+ version: 1,
+ targetRepresentation: "fock_number_basis",
+ summary: "Validation policy for Fock-basis runs emphasizing normalization fidelity and robust runtime consistency.",
+ references: [
+ "Open-source studio qualification standard: CV-FOCK-VAL-v1"
+ ],
+ thresholds: KPIPolicyThresholds(
+ minSuccessRate: 0.95,
+ maxMeanPhotonP95Delta: 0.080,
+ maxStateP95Delta: 0.030,
+ maxMeasurementMismatchRate: 0.01,
+ minMeanPhotonNumber: 0.0,
+ maxMeanPhotonNumber: 200.0,
+ minSqueezingDB: nil,
+ minGaussianPurity: nil,
+ maxFockNormalizationError: 1e-6,
+ maxLogicalErrorRate: nil,
+ minSuppressionFactor: nil,
+ minBreakEvenGain: nil
+ )
+ ),
+ ]
+
+ private static let scientificCircuitPresetCatalog: [ScientificCircuitPreset] = [
+ ScientificCircuitPreset(
+ id: "crespi-2013-five-mode-boson-sampling",
+ title: "Crespi et al. (2013) Five-Mode Boson Sampling Chip",
+ citation: "A. Crespi et al., Integrated multimode interferometers with arbitrary designs for photonic boson sampling, Nat. Photonics 7, 545-549 (2013).",
+ doiURL: "https://doi.org/10.1038/nphoton.2013.112",
+ sourceURL: "https://www.nature.com/articles/nphoton.2013.112",
+ mappingNote: "Mapped to SchroSIM primitives (squeeze, phase, beam_splitter, loss, measurement). This is a physically-motivated surrogate layout, not a mask-exact foundry replay.",
+ recommendedBackend: .gaussian,
+ suggestedSeed: "20130545",
+ modeCount: 5,
+ slotCount: 8,
+ nodes: [
+ CircuitNode(kind: .sourceSqueezed, mode: 0, slot: 0, paramA: 0.82),
+ CircuitNode(kind: .sourceSqueezed, mode: 1, slot: 0, paramA: 0.76),
+ CircuitNode(kind: .sourceSqueezed, mode: 2, slot: 0, paramA: 0.72),
+
+ CircuitNode(kind: .beamSplitter, mode: 0, slot: 1, paramA: 0.61),
+ CircuitNode(kind: .beamSplitter, mode: 1, slot: 1, paramA: 0.61),
+ CircuitNode(kind: .beamSplitter, mode: 2, slot: 2, paramA: 0.55),
+ CircuitNode(kind: .beamSplitter, mode: 3, slot: 2, paramA: 0.55),
+
+ CircuitNode(kind: .phase, mode: 1, slot: 3, paramA: 0.21),
+ CircuitNode(kind: .phase, mode: 4, slot: 3, paramA: -0.16),
+
+ CircuitNode(kind: .beamSplitter, mode: 1, slot: 4, paramA: 0.42),
+ CircuitNode(kind: .beamSplitter, mode: 2, slot: 4, paramA: 0.42),
+ CircuitNode(kind: .beamSplitter, mode: 3, slot: 5, paramA: 0.37),
+ CircuitNode(kind: .beamSplitter, mode: 4, slot: 5, paramA: 0.37),
+
+ CircuitNode(kind: .loss, mode: 0, slot: 6, paramA: 0.93),
+ CircuitNode(kind: .loss, mode: 2, slot: 6, paramA: 0.95),
+ CircuitNode(kind: .thermalLoss, mode: 4, slot: 6, paramA: 0.91, paramB: 0.05),
+
+ CircuitNode(kind: .measureHomodyne, mode: 0, slot: 7, paramA: 0.0),
+ CircuitNode(kind: .measureHomodyne, mode: 1, slot: 7, paramA: 0.0),
+ CircuitNode(kind: .measureHeterodyne, mode: 2, slot: 7),
+ ]
+ ),
+ ScientificCircuitPreset(
+ id: "spagnolo-2014-validation-chip",
+ title: "Spagnolo et al. (2014) Boson Sampling Validation Circuit",
+ citation: "N. Spagnolo et al., Experimental validation of photonic boson sampling, Nat. Photonics 8, 615-620 (2014).",
+ doiURL: "https://doi.org/10.1038/nphoton.2014.135",
+ sourceURL: "https://www.nature.com/articles/nphoton.2014.135",
+ mappingNote: "Validation-oriented surrogate mapped to available SchroSIM gates for compile/run checks and reproducibility workflows.",
+ recommendedBackend: .gaussian,
+ suggestedSeed: "20140615",
+ modeCount: 6,
+ slotCount: 9,
+ nodes: [
+ CircuitNode(kind: .sourceSqueezed, mode: 0, slot: 0, paramA: 0.68),
+ CircuitNode(kind: .sourceSqueezed, mode: 1, slot: 0, paramA: 0.66),
+ CircuitNode(kind: .sourceSqueezed, mode: 2, slot: 0, paramA: 0.71),
+ CircuitNode(kind: .sourceSqueezed, mode: 3, slot: 0, paramA: 0.63),
+
+ CircuitNode(kind: .beamSplitter, mode: 0, slot: 1, paramA: 0.48),
+ CircuitNode(kind: .beamSplitter, mode: 1, slot: 1, paramA: 0.48),
+ CircuitNode(kind: .beamSplitter, mode: 2, slot: 2, paramA: 0.52),
+ CircuitNode(kind: .beamSplitter, mode: 3, slot: 2, paramA: 0.52),
+
+ CircuitNode(kind: .phase, mode: 1, slot: 3, paramA: 0.24),
+ CircuitNode(kind: .phase, mode: 2, slot: 3, paramA: -0.31),
+
+ CircuitNode(kind: .beamSplitter, mode: 1, slot: 4, paramA: 0.47),
+ CircuitNode(kind: .beamSplitter, mode: 2, slot: 4, paramA: 0.47),
+ CircuitNode(kind: .beamSplitter, mode: 3, slot: 5, paramA: 0.41),
+ CircuitNode(kind: .beamSplitter, mode: 4, slot: 5, paramA: 0.41),
+
+ CircuitNode(kind: .loss, mode: 0, slot: 6, paramA: 0.92),
+ CircuitNode(kind: .loss, mode: 4, slot: 6, paramA: 0.90),
+ CircuitNode(kind: .thermalLoss, mode: 5, slot: 6, paramA: 0.88, paramB: 0.08),
+
+ CircuitNode(kind: .measureHomodyne, mode: 0, slot: 7, paramA: 0.0),
+ CircuitNode(kind: .measureHeterodyne, mode: 2, slot: 7),
+ CircuitNode(kind: .measureHomodyne, mode: 4, slot: 7, paramA: 0.0),
+ CircuitNode(kind: .classicalControl, mode: 3, slot: 8, paramA: 0.20),
+ ]
+ ),
+ ScientificCircuitPreset(
+ id: "hoch-2022-reconfigurable-3d-chip",
+ title: "Hoch et al. (2022) Reconfigurable 3D Photonic Circuit",
+ citation: "F. Hoch et al., Reconfigurable continuously-coupled 3D photonic circuit for boson sampling experiments, npj Quantum Inf. 8, 55 (2022).",
+ doiURL: "https://doi.org/10.1038/s41534-022-00568-6",
+ sourceURL: "https://www.nature.com/articles/s41534-022-00568-6",
+ mappingNote: "Topology-inspired 8-mode surrogate preserving staged coupling and phase blocks using SchroSIM's current runtime gate set.",
+ recommendedBackend: .hybrid,
+ suggestedSeed: "20220568",
+ modeCount: 8,
+ slotCount: 10,
+ nodes: [
+ CircuitNode(kind: .sourceSqueezed, mode: 0, slot: 0, paramA: 0.74),
+ CircuitNode(kind: .sourceSqueezed, mode: 2, slot: 0, paramA: 0.70),
+ CircuitNode(kind: .sourceSqueezed, mode: 4, slot: 0, paramA: 0.67),
+ CircuitNode(kind: .sourceSqueezed, mode: 6, slot: 0, paramA: 0.73),
+
+ CircuitNode(kind: .beamSplitter, mode: 0, slot: 1, paramA: 0.44),
+ CircuitNode(kind: .beamSplitter, mode: 1, slot: 1, paramA: 0.44),
+ CircuitNode(kind: .beamSplitter, mode: 2, slot: 1, paramA: 0.51),
+ CircuitNode(kind: .beamSplitter, mode: 3, slot: 1, paramA: 0.51),
+ CircuitNode(kind: .beamSplitter, mode: 4, slot: 1, paramA: 0.39),
+ CircuitNode(kind: .beamSplitter, mode: 5, slot: 1, paramA: 0.39),
+ CircuitNode(kind: .beamSplitter, mode: 6, slot: 1, paramA: 0.57),
+ CircuitNode(kind: .beamSplitter, mode: 7, slot: 1, paramA: 0.57),
+
+ CircuitNode(kind: .phase, mode: 1, slot: 2, paramA: 0.18),
+ CircuitNode(kind: .phase, mode: 3, slot: 2, paramA: -0.22),
+ CircuitNode(kind: .phase, mode: 5, slot: 2, paramA: 0.11),
+ CircuitNode(kind: .phase, mode: 7, slot: 2, paramA: -0.15),
+
+ CircuitNode(kind: .beamSplitter, mode: 1, slot: 3, paramA: 0.46),
+ CircuitNode(kind: .beamSplitter, mode: 2, slot: 3, paramA: 0.46),
+ CircuitNode(kind: .beamSplitter, mode: 3, slot: 3, paramA: 0.33),
+ CircuitNode(kind: .beamSplitter, mode: 4, slot: 3, paramA: 0.33),
+ CircuitNode(kind: .beamSplitter, mode: 5, slot: 3, paramA: 0.42),
+ CircuitNode(kind: .beamSplitter, mode: 6, slot: 3, paramA: 0.42),
+
+ CircuitNode(kind: .displace, mode: 0, slot: 4, paramA: 0.12, paramB: 0.04),
+ CircuitNode(kind: .displace, mode: 7, slot: 4, paramA: -0.10, paramB: 0.02),
+
+ CircuitNode(kind: .beamSplitter, mode: 0, slot: 5, paramA: 0.36),
+ CircuitNode(kind: .beamSplitter, mode: 3, slot: 5, paramA: 0.36),
+ CircuitNode(kind: .beamSplitter, mode: 4, slot: 6, paramA: 0.43),
+ CircuitNode(kind: .beamSplitter, mode: 7, slot: 6, paramA: 0.43),
+
+ CircuitNode(kind: .loss, mode: 1, slot: 7, paramA: 0.94),
+ CircuitNode(kind: .loss, mode: 5, slot: 7, paramA: 0.93),
+ CircuitNode(kind: .thermalLoss, mode: 7, slot: 7, paramA: 0.90, paramB: 0.07),
+
+ CircuitNode(kind: .measureHomodyne, mode: 0, slot: 8, paramA: 0.0),
+ CircuitNode(kind: .measureHeterodyne, mode: 3, slot: 8),
+ CircuitNode(kind: .measureHomodyne, mode: 6, slot: 8, paramA: 0.0),
+ CircuitNode(kind: .classicalControl, mode: 2, slot: 9, paramA: -0.19),
+ ]
+ ),
+ ]
+
+ private static let workspaceProjectContexts: [WorkspaceProjectContext] = [
+ WorkspaceProjectContext(
+ workspace: "Quantum Foundry",
+ project: "Boson Sampling",
+ defaultBackend: .hybrid,
+ defaultSeed: "123456789",
+ defaultCircuitPath: "docs/examples/foundry_loss_map.json",
+ devFoundryProfile: "experimental-lab-v1",
+ stageFoundryProfile: "studio-default-v2",
+ prodFoundryProfile: "studio-default-v1"
+ ),
+ WorkspaceProjectContext(
+ workspace: "Quantum Foundry",
+ project: "Quantum Error Correction",
+ defaultBackend: .hybrid,
+ defaultSeed: "42424242",
+ defaultCircuitPath: "docs/examples/cv/qec_single_logical_gkp_memory_mvp.json",
+ devFoundryProfile: "experimental-lab-v1",
+ stageFoundryProfile: "studio-default-v2",
+ prodFoundryProfile: "studio-default-v1"
+ ),
+ WorkspaceProjectContext(
+ workspace: "Device Lab",
+ project: "Boson Sampling",
+ defaultBackend: .gaussian,
+ defaultSeed: "27182818",
+ defaultCircuitPath: "docs/examples/foundry_loss_map.json",
+ devFoundryProfile: "experimental-lab-v1",
+ stageFoundryProfile: "studio-default-v2",
+ prodFoundryProfile: "studio-default-v1"
+ ),
+ WorkspaceProjectContext(
+ workspace: "Device Lab",
+ project: "Quantum Error Correction",
+ defaultBackend: .gaussian,
+ defaultSeed: "31415926",
+ defaultCircuitPath: "docs/examples/cv/qec_single_logical_gkp_memory_mvp.json",
+ devFoundryProfile: "experimental-lab-v1",
+ stageFoundryProfile: "studio-default-v2",
+ prodFoundryProfile: "studio-default-v1"
+ ),
+ ]
+
+ @Published var workspace: String = "Quantum Foundry" {
+ didSet {
+ handleWorkspaceOrProjectChange(triggerReason: "Workspace changed to \(workspace)")
+ }
+ }
+ @Published var project: String = "Boson Sampling" {
+ didSet {
+ handleWorkspaceOrProjectChange(triggerReason: "Project changed to \(project)")
+ }
+ }
+ @Published var environment: EnvironmentBadge = .dev {
+ didSet {
+ handleEnvironmentChange()
+ }
+ }
+ @Published var userRole: UserRoleBadge = .editor {
+ didSet {
+ handleConfigurationChange(
+ oldValue: oldValue,
+ newValue: userRole,
+ reason: "Role changed to \(userRole.rawValue)",
+ revalidate: true
+ )
+ }
+ }
+ @Published var theme: AppTheme = .black
+
+ @Published var backend: BackendSelection = .hybrid {
+ didSet {
+ handleConfigurationChange(
+ oldValue: oldValue,
+ newValue: backend,
+ reason: "Backend changed to \(backend.rawValue)",
+ revalidate: true
+ )
+ if oldValue != backend {
+ _ = enforceKPIPolicyForSelectedBackend()
+ }
+ }
+ }
+ @Published var runtimeEngine: RuntimeEngineSelection = .swift {
+ didSet {
+ handleConfigurationChange(
+ oldValue: oldValue,
+ newValue: runtimeEngine,
+ reason: "Runtime engine changed to \(runtimeEngine.rawValue)",
+ revalidate: true
+ )
+ }
+ }
+ @Published var seed: String = "123456789" {
+ didSet {
+ handleConfigurationChange(
+ oldValue: oldValue,
+ newValue: seed,
+ reason: "Seed changed",
+ revalidate: false
+ )
+ }
+ }
+ @Published var cutoff: Int = 20 {
+ didSet {
+ handleConfigurationChange(
+ oldValue: oldValue,
+ newValue: cutoff,
+ reason: "Cutoff changed to \(cutoff)",
+ revalidate: false
+ )
+ }
+ }
+ @Published var schemaVersion: String = "1" {
+ didSet {
+ handleConfigurationChange(
+ oldValue: oldValue,
+ newValue: schemaVersion,
+ reason: "Schema version changed",
+ revalidate: false
+ )
+ }
+ }
+ @Published var foundryProfile: String = "studio-default-v1" {
+ didSet {
+ handleConfigurationChange(
+ oldValue: oldValue,
+ newValue: foundryProfile,
+ reason: "Foundry profile changed to \(foundryProfile)",
+ revalidate: true
+ )
+ }
+ }
+ @Published var foundryProfiles: [String] = StudioStore.fallbackFoundryProfiles
+ @Published var circuitJSONPath: String = "docs/examples/foundry_loss_map.json" {
+ didSet {
+ handleConfigurationChange(
+ oldValue: oldValue,
+ newValue: circuitJSONPath,
+ reason: "Circuit path changed",
+ revalidate: false
+ )
+ }
+ }
+ @Published var selectedScientificCircuitPresetID: String = StudioStore.scientificCircuitPresetCatalog.first?.id ?? ""
+ @Published var kpiPolicies: [KPIPolicyDefinition] = StudioStore.fallbackKPIPolicies
+ @Published var selectedKPIPolicyID: String = StudioStore.fallbackKPIPolicies.first?.id ?? "" {
+ didSet {
+ handleConfigurationChange(
+ oldValue: oldValue,
+ newValue: selectedKPIPolicyID,
+ reason: "KPI policy changed",
+ revalidate: false
+ )
+ if oldValue != selectedKPIPolicyID {
+ stateHealthSummary = nil
+ bosonicSignoffSummary = nil
+ let switchedBackend = enforceBackendForSelectedKPIPolicy()
+ if !switchedBackend {
+ validateAction()
+ }
+ }
+ }
+ }
+
+ @Published var paletteSearch: String = ""
+ @Published var paletteFilter: PaletteFilter = .all
+ @Published var showGrid: Bool = true
+ @Published var showConnectors: Bool = true
+ @Published var showPhotonFlow: Bool = false
+ @Published var showSelection: Bool = true
+ @Published var showValidation: Bool = true
+
+ @Published var modeCount: Int = 4
+ @Published var slotCount: Int = 6 {
+ didSet {
+ handleSlotCountChange(oldValue: oldValue)
+ }
+ }
+ @Published var nodes: [CircuitNode] = []
+ @Published var selectedNodeID: UUID?
+ @Published var selectedModeIndex: Int?
+
+ @Published var rightInspectorTab: RightInspectorTab = .selectionInspector
+ @Published var bottomTab: BottomTab = .validationConsole
+
+ @Published var validationIssues: [ValidationIssue] = []
+ @Published var runOutput: [String] = ["Run output panel is ready."]
+ @Published var auditTrail: [String] = ["Session started"]
+ @Published var statusText: String = "Ready"
+ @Published var isDirty: Bool = false
+ @Published var isRunning: Bool = false
+ @Published var runtimePhase: RuntimePhase = .standby
+ @Published var isAnalysisPopupPresented: Bool = false
+ @Published var isRunBlockedAlertPresented: Bool = false
+ @Published var runBlockedAlertMessage: String = "Validation failed. Check your gates."
+
+ @Published var lastRunResponse: RunResponse?
+ @Published var lastTraceResponse: TraceResponse?
+ @Published var runtimeParitySummary: RuntimeParitySummary?
+ @Published var stateHealthSummary: StateHealthSummary?
+ @Published var bosonicSignoffSummary: BosonicSignoffSummary?
+ @Published var lastExportPath: String?
+ @Published var lastSavedDocumentPath: String?
+ @Published var noiseModel: MonteCarloNoiseModel = .standard
+ @Published var useCalibratedNoiseProfile: Bool = true
+ @Published var calibratedNoiseProfile: CalibratedNoiseTensorProfile?
+ @Published var calibratedNoiseProfilePath: String?
+ @Published var showConfidenceBands: Bool = true
+ @Published var isMonteCarloRunning: Bool = false
+ @Published var monteCarloCompletedSamples: Int = 0
+ @Published var monteCarloRunResponses: [RunResponse] = []
+ @Published var monteCarloTraceResponses: [TraceResponse] = []
+ @Published var monteCarloLastRunTimestamp: Date?
+
+ private var isApplyingContext = false
+ private var lastSavedDocumentURL: URL?
+ private var approvedProdFoundryProfiles: Set = []
+ private var foundryProfileRefsByName: [String: FoundryProfileRef] = [:]
+
+ let compilerName = "foundry-aware-ir-v1"
+ let contractionPolicy = "hybrid_auto"
+
+ var workspaces: [String] {
+ Self.uniqueOrdered(Self.workspaceProjectContexts.map(\.workspace))
+ }
+
+ var projects: [String] {
+ availableProjects(for: workspace)
+ }
+
+ var scientificCircuitPresets: [ScientificCircuitPreset] {
+ Self.scientificCircuitPresetCatalog
+ }
+
+ var selectedScientificCircuitPreset: ScientificCircuitPreset? {
+ Self.scientificCircuitPresetCatalog.first { $0.id == selectedScientificCircuitPresetID }
+ }
+
+ var selectedKPIPolicy: KPIPolicyDefinition? {
+ kpiPolicies.first { $0.id == selectedKPIPolicyID } ?? kpiPolicies.first
+ }
+
+ var isBosonicSignoffRequired: Bool {
+ requiresBosonicSignoff
+ }
+
+ private let fockRuntimeSupportedKinds: Set = [
+ .sourceVacuum,
+ .sourceCoherent,
+ .phase,
+ .displace,
+ .injectFock,
+ .injectCat,
+ ]
+
+ init() {
+ nodes = [
+ CircuitNode(kind: .squeeze, mode: 0, slot: 1, paramA: 0.8, paramB: 0.0),
+ CircuitNode(kind: .beamSplitter, mode: 0, slot: 3, paramA: 0.25, paramB: 0.0),
+ CircuitNode(kind: .phase, mode: 1, slot: 0, paramA: 0.13, paramB: 0.0),
+ CircuitNode(kind: .displace, mode: 1, slot: 3, paramA: 0.35, paramB: 0.20),
+ CircuitNode(kind: .beamSplitter, mode: 2, slot: 1, paramA: 0.10, paramB: 0.0),
+ CircuitNode(kind: .phase, mode: 3, slot: 1, paramA: -0.22, paramB: 0.0),
+ ]
+ let catalog = Self.loadFoundryRegistryCatalog()
+ foundryProfiles = catalog.allProfileNames
+ approvedProdFoundryProfiles = catalog.approvedProfileNames
+ foundryProfileRefsByName = catalog.profileRefsByName
+ kpiPolicies = Self.loadKPIPolicyCatalog()
+ selectedKPIPolicyID = kpiPolicies.first?.id ?? ""
+
+ applySelectionContext(
+ triggerReason: "Initialization",
+ markDirty: false,
+ appendContextLog: false,
+ revalidate: false
+ )
+ selectedNodeID = nodes.first?.id
+ validateAction()
+ }
+
+ var globalStatusText: String {
+ let dirtyText = isDirty ? "dirty" : "clean"
+ let syncText = isDirty ? "local changes" : "synced"
+ return "\(dirtyText) | autosave: on | sync: \(syncText) | \(workspace)/\(project) | \(environment.rawValue) | \(userRole.rawValue)"
+ }
+
+ var calibratedNoiseProfileDisplayName: String {
+ guard let profile = calibratedNoiseProfile else { return "None" }
+ return "\(profile.deviceName) (\(profile.deviceID))"
+ }
+
+ var calibratedNoiseModeCoverageText: String {
+ guard let profile = calibratedNoiseProfile else { return "Coverage: n/a" }
+ let declaredModes = max(profile.modeCount ?? modeCount, 1)
+ let effectiveModes = max(modeCount, 1)
+ return "Coverage: \(min(declaredModes, effectiveModes))/\(effectiveModes) modes"
+ }
+
+ var groupedPalette: [(PaletteSection, [GateKind])] {
+ PaletteSection.allCases.compactMap { section in
+ let components = GateKind.allCases.filter { kind in
+ kind.section == section && matchesPaletteFilter(kind) && matchesSearch(kind)
+ }
+ return components.isEmpty ? nil : (section, components)
+ }
+ }
+
+ var canDeleteSelection: Bool {
+ selectedNodeID != nil || validSelectedModeIndex != nil
+ }
+
+ private func handleWorkspaceOrProjectChange(triggerReason: String) {
+ guard !isApplyingContext else { return }
+ applySelectionContext(triggerReason: triggerReason, markDirty: true, appendContextLog: true)
+ }
+
+ private func handleEnvironmentChange() {
+ guard !isApplyingContext else { return }
+ applySelectionContext(
+ triggerReason: "Environment changed to \(environment.rawValue.uppercased())",
+ markDirty: true,
+ appendContextLog: true
+ )
+ }
+
+ private var validSelectedModeIndex: Int? {
+ guard let selectedModeIndex else { return nil }
+ guard selectedModeIndex >= 0, selectedModeIndex < modeCount else { return nil }
+ return selectedModeIndex
+ }
+
+ private func handleConfigurationChange(
+ oldValue: T,
+ newValue: T,
+ reason: String,
+ revalidate: Bool
+ ) {
+ guard !isApplyingContext else { return }
+ guard oldValue != newValue else { return }
+ markDirty(reason)
+ if revalidate {
+ validateAction()
+ }
+ }
+
+ private func handleSlotCountChange(oldValue: Int) {
+ let clamped = max(1, min(slotCount, 64))
+ if slotCount != clamped {
+ slotCount = clamped
+ return
+ }
+
+ guard !isApplyingContext else { return }
+ guard oldValue != slotCount else { return }
+
+ if slotCount < oldValue {
+ nodes.removeAll { $0.slot >= slotCount }
+ if let selectedNodeID, !nodes.contains(where: { $0.id == selectedNodeID }) {
+ self.selectedNodeID = nodes.first?.id
+ self.selectedModeIndex = nodes.first?.mode
+ }
+ }
+
+ markDirty("Slot count changed to \(slotCount)")
+ validateAction()
+ }
+
+ private func availableProjects(for workspace: String) -> [String] {
+ let scopedProjects = Self.workspaceProjectContexts
+ .filter { $0.workspace == workspace }
+ .map(\.project)
+ if !scopedProjects.isEmpty {
+ return Self.uniqueOrdered(scopedProjects)
+ }
+ return Self.uniqueOrdered(Self.workspaceProjectContexts.map(\.project))
+ }
+
+ private func selectedContext() -> WorkspaceProjectContext {
+ if let exact = Self.workspaceProjectContexts.first(where: { $0.workspace == workspace && $0.project == project }) {
+ return exact
+ }
+ if let byWorkspace = Self.workspaceProjectContexts.first(where: { $0.workspace == workspace }) {
+ return byWorkspace
+ }
+ return Self.workspaceProjectContexts.first!
+ }
+
+ private func preferredFoundryProfile(
+ for context: WorkspaceProjectContext,
+ environment: EnvironmentBadge
+ ) -> String {
+ switch environment {
+ case .dev:
+ return context.devFoundryProfile
+ case .stage:
+ return context.stageFoundryProfile
+ case .prod:
+ return context.prodFoundryProfile
+ }
+ }
+
+ private func resolvedFoundryProfile(
+ preferred: String,
+ environment: EnvironmentBadge
+ ) -> String {
+ if foundryProfiles.contains(preferred) {
+ return preferred
+ }
+
+ if environment == .prod,
+ !approvedProdFoundryProfiles.isEmpty,
+ let approved = foundryProfiles.first(where: { approvedProdFoundryProfiles.contains($0) }) {
+ return approved
+ }
+
+ if let first = foundryProfiles.first {
+ return first
+ }
+
+ return preferred
+ }
+
+ private func applySelectionContext(
+ triggerReason: String,
+ markDirty shouldMarkDirty: Bool,
+ appendContextLog: Bool,
+ revalidate: Bool = true
+ ) {
+ isApplyingContext = true
+ defer { isApplyingContext = false }
+
+ let available = availableProjects(for: workspace)
+ if let firstProject = available.first, !available.contains(project) {
+ project = firstProject
+ }
+
+ let context = selectedContext()
+ if workspace != context.workspace {
+ workspace = context.workspace
+ }
+ if project != context.project {
+ project = context.project
+ }
+
+ let previousBackend = backend
+ let previousSeed = seed
+ let previousPath = circuitJSONPath
+ let previousFoundry = foundryProfile
+
+ backend = context.defaultBackend
+ seed = context.defaultSeed
+ circuitJSONPath = context.defaultCircuitPath
+ foundryProfile = resolvedFoundryProfile(
+ preferred: preferredFoundryProfile(for: context, environment: environment),
+ environment: environment
+ )
+ let policyAutoSwitched = enforceKPIPolicyForSelectedBackend()
+
+ if appendContextLog {
+ appendAudit("Context changed: workspace=\(workspace) project=\(project) environment=\(environment.rawValue)")
+ appendRunOutput(
+ "Context: workspace=\(workspace) project=\(project) environment=\(environment.rawValue) role=\(userRole.rawValue)"
+ )
+ }
+
+ let contextChanged = previousBackend != backend ||
+ previousSeed != seed ||
+ previousPath != circuitJSONPath ||
+ previousFoundry != foundryProfile ||
+ policyAutoSwitched
+
+ if shouldMarkDirty, contextChanged {
+ markDirty(triggerReason)
+ }
+
+ if revalidate {
+ validateAction()
+ }
+ }
+
+ func matchesPaletteFilter(_ kind: GateKind) -> Bool {
+ paletteFilter == .all || kind.primaryFilter == paletteFilter
+ }
+
+ func matchesSearch(_ kind: GateKind) -> Bool {
+ let query = paletteSearch.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
+ guard !query.isEmpty else { return true }
+ return kind.rawValue.contains(query) || kind.symbol.lowercased().contains(query)
+ }
+
+ func disabledReason(for kind: GateKind) -> String? {
+ if userRole == .viewer {
+ return "disabled-by-policy: viewer role"
+ }
+
+ if let runtimeUnsupportedReason = kind.runtimeUnsupportedReason {
+ return runtimeUnsupportedReason
+ }
+
+ switch backend {
+ case .gaussian:
+ if kind == .injectFock || kind == .injectCat {
+ return "disabled-by-backend: gaussian path"
+ }
+ case .fock:
+ if !fockRuntimeSupportedKinds.contains(kind) {
+ return "disabled-by-backend: fock path"
+ }
+ case .hybrid:
+ break
+ }
+
+ return nil
+ }
+
+ var runtimeEngineUnavailableReason: String? {
+ nil
+ }
+
+ private func resolveRuntimeExecutionEngine(action: String) -> RuntimeEngineSelection {
+ _ = action
+ return runtimeEngine
+ }
+
+ func nodeAt(mode: Int, slot: Int) -> CircuitNode? {
+ nodes.first { $0.mode == mode && $0.slot == slot }
+ }
+
+ func selectNode(_ node: CircuitNode?) {
+ selectedNodeID = node?.id
+ selectedModeIndex = node?.mode
+ }
+
+ func selectMode(_ mode: Int?) {
+ guard let mode else {
+ selectedModeIndex = nil
+ return
+ }
+ guard mode >= 0, mode < modeCount else { return }
+ selectedModeIndex = mode
+ selectedNodeID = nil
+ }
+
+ func handleDrop(kindRawValue: String, mode: Int, slot: Int) {
+ guard let kind = GateKind(rawValue: kindRawValue) else { return }
+ guard disabledReason(for: kind) == nil else { return }
+
+ if let index = nodes.firstIndex(where: { $0.mode == mode && $0.slot == slot }) {
+ nodes[index].kind = kind
+ nodes[index].paramA = kind.defaultParamA
+ nodes[index].paramB = kind.defaultParamB
+ selectedNodeID = nodes[index].id
+ } else {
+ let node = CircuitNode(kind: kind, mode: mode, slot: slot)
+ nodes.append(node)
+ selectedNodeID = node.id
+ }
+
+ markDirty("Updated canvas from drag/drop")
+ validateAction()
+ }
+
+ func addModeLane() {
+ modeCount += 1
+ markDirty("Added mode lane")
+ validateAction()
+ }
+
+ func addSlotColumn() {
+ guard slotCount < 64 else {
+ statusText = "Slot count already at maximum (64)."
+ return
+ }
+ slotCount += 1
+ }
+
+ func removeSlotColumn() {
+ guard slotCount > 1 else {
+ statusText = "Slot count already at minimum (1)."
+ return
+ }
+ slotCount -= 1
+ }
+
+ func deleteModeLane(_ mode: Int) {
+ guard modeCount > 1 else { return }
+ guard mode >= 0, mode < modeCount else { return }
+
+ nodes.removeAll { $0.mode == mode }
+
+ for index in nodes.indices {
+ if nodes[index].mode > mode {
+ nodes[index].mode -= 1
+ }
+ }
+
+ modeCount -= 1
+
+ if let selectedNodeID, !nodes.contains(where: { $0.id == selectedNodeID }) {
+ self.selectedNodeID = nodes.first?.id
+ }
+
+ if let selectedModeIndex {
+ if selectedModeIndex == mode {
+ self.selectedModeIndex = max(0, min(mode, modeCount - 1))
+ } else if selectedModeIndex > mode {
+ self.selectedModeIndex = max(0, min(selectedModeIndex - 1, modeCount - 1))
+ }
+ }
+
+ markDirty("Deleted mode lane \(mode)")
+ validateAction()
+ }
+
+ func deleteSelection() {
+ if let selectedNodeID, let index = nodes.firstIndex(where: { $0.id == selectedNodeID }) {
+ let removed = nodes.remove(at: index)
+ self.selectedNodeID = nil
+ self.selectedModeIndex = removed.mode
+ markDirty("Deleted \(removed.kind.displayName) at mode \(removed.mode), slot \(removed.slot)")
+ validateAction()
+ return
+ }
+
+ if let selectedModeIndex = validSelectedModeIndex {
+ deleteModeLane(selectedModeIndex)
+ return
+ }
+
+ if selectedModeIndex != nil {
+ self.selectedModeIndex = nil
+ }
+
+ statusText = "No selected component or mode to delete."
+ }
+
+ var selectedNode: CircuitNode? {
+ guard let selectedNodeID else { return nil }
+ return nodes.first(where: { $0.id == selectedNodeID })
+ }
+
+ func updateSelectedPrimaryParam(_ value: Double) {
+ guard let selectedNodeID, let idx = nodes.firstIndex(where: { $0.id == selectedNodeID }) else { return }
+ nodes[idx].paramA = value
+ markDirty("Updated gate parameter")
+ validateAction()
+ }
+
+ func updateSelectedSecondaryParam(_ value: Double) {
+ guard let selectedNodeID, let idx = nodes.firstIndex(where: { $0.id == selectedNodeID }) else { return }
+ nodes[idx].paramB = value
+ markDirty("Updated gate parameter")
+ validateAction()
+ }
+
+ func loadSelectedScientificCircuitPreset() {
+ guard let preset = selectedScientificCircuitPreset else {
+ statusText = "No scientific preset selected."
+ appendRunOutput(statusText)
+ return
+ }
+
+ isApplyingContext = true
+ modeCount = max(1, preset.modeCount)
+ slotCount = max(1, preset.slotCount)
+ nodes = preset.nodes
+ selectedNodeID = nodes.first?.id
+ selectedModeIndex = nodes.first?.mode
+ backend = preset.recommendedBackend
+ seed = preset.suggestedSeed
+ circuitJSONPath = "scientific://\(preset.id)"
+ isApplyingContext = false
+
+ clearMonteCarloResults()
+ lastRunResponse = nil
+ lastTraceResponse = nil
+ runtimeParitySummary = nil
+ stateHealthSummary = nil
+ bosonicSignoffSummary = nil
+ runtimePhase = .standby
+ rightInspectorTab = .circuitConfigInspector
+ bottomTab = .validationConsole
+
+ markDirty("Loaded scientific preset \(preset.title)")
+ statusText = "Scientific preset loaded: \(preset.title)"
+ appendRunOutput("Scientific preset loaded: \(preset.title)")
+ appendRunOutput("Citation: \(preset.citation)")
+ appendRunOutput("DOI: \(preset.doiURL)")
+ appendAudit("Loaded scientific preset \(preset.id)")
+ validateAction()
+ }
+
+ func analyzeAction() {
+ if (lastTraceResponse?.frames ?? []).isEmpty && lastRunResponse == nil {
+ statusText = "No run data yet. Run the circuit to populate analysis graphs."
+ appendRunOutput(statusText)
+ } else if (lastTraceResponse?.frames ?? []).isEmpty {
+ statusText = "Run data available; trace frames unavailable."
+ } else {
+ statusText = "Analysis ready"
+ }
+ isAnalysisPopupPresented = true
+ appendAudit("Analysis action")
+ }
+
+ func clearMonteCarloResults() {
+ monteCarloRunResponses.removeAll()
+ monteCarloTraceResponses.removeAll()
+ monteCarloCompletedSamples = 0
+ monteCarloLastRunTimestamp = nil
+ appendAudit("Cleared Monte Carlo analysis")
+ }
+
+ func importCalibratedNoiseProfile() {
+ guard let sourceURL = CLIService.promptForNoiseProfileOpenURL(
+ existingURL: calibratedNoiseProfilePath.map { URL(fileURLWithPath: $0) }
+ ) else {
+ statusText = "Calibrated profile import canceled"
+ appendRunOutput("Calibrated profile import canceled.")
+ return
+ }
+
+ do {
+ let loadedProfile = try CLIService.loadCalibratedNoiseProfile(from: sourceURL)
+ if let declaredModes = loadedProfile.modeCount, declaredModes < 1 {
+ throw CLIRunnerError.invalidNoiseProfile("`mode_count` must be >= 1 when provided.")
+ }
+ calibratedNoiseProfile = loadedProfile
+ calibratedNoiseProfilePath = sourceURL.path
+ useCalibratedNoiseProfile = true
+ clearMonteCarloResults()
+ statusText = "Calibrated profile loaded: \(loadedProfile.deviceName)"
+ appendRunOutput(
+ "Calibrated profile loaded: \(loadedProfile.deviceName) [\(loadedProfile.deviceID)] from \(sourceURL.lastPathComponent)"
+ )
+ if let warning = calibratedTensorValidationWarning(loadedProfile, modeCount: modeCount) {
+ appendRunOutput("Calibrated profile warning: \(warning)")
+ }
+ appendAudit("Imported calibrated noise profile")
+ } catch {
+ statusText = "Calibrated profile import failed: \(error.localizedDescription)"
+ appendRunOutput(statusText)
+ appendAudit("Calibrated profile import failed")
+ bottomTab = .runOutputPanel
+ }
+ }
+
+ func clearCalibratedNoiseProfile() {
+ calibratedNoiseProfile = nil
+ calibratedNoiseProfilePath = nil
+ useCalibratedNoiseProfile = false
+ clearMonteCarloResults()
+ statusText = "Calibrated profile cleared"
+ appendRunOutput("Calibrated profile cleared.")
+ appendAudit("Cleared calibrated noise profile")
+ }
+
+ func runMonteCarloAnalysis() async {
+ guard !isMonteCarloRunning else { return }
+
+ validateAction()
+ if validationIssues.contains(where: { $0.severity == .error }) {
+ statusText = "Monte Carlo blocked: fix validation errors first."
+ appendRunOutput(statusText)
+ bottomTab = .validationConsole
+ return
+ }
+
+ let runtimeEngineForExecution = resolveRuntimeExecutionEngine(action: "monte-carlo")
+
+ let requestedSamples = noiseModel.sampleCount
+ let sampleCount = max(4, min(requestedSamples, 128))
+ let profileRef = currentFoundryProfileRef()
+ let baseNodes = nodes
+ let backendValue = backend
+ let roleValue = userRole
+ let environmentValue = environment
+ let modeCountValue = modeCount
+ let seedValue = seed
+ let cutoffValue = cutoff
+ let schemaVersionValue = schemaVersion
+ let foundryProfileValue = foundryProfile
+ let profileID = profileRef?.profileID
+ let profileVersion = profileRef?.version
+ let activeCalibration = useCalibratedNoiseProfile ? calibratedNoiseProfile : nil
+
+ isMonteCarloRunning = true
+ runtimePhase = .running
+ monteCarloCompletedSamples = 0
+ monteCarloRunResponses.removeAll(keepingCapacity: true)
+ monteCarloTraceResponses.removeAll(keepingCapacity: true)
+ appendAudit("Monte Carlo analysis started (\(sampleCount) samples)")
+ appendRunOutput("Monte Carlo: started \(sampleCount) samples.")
+ appendRunOutput("Monte Carlo runtime engine: \(runtimeEngineForExecution.rawValue)")
+ if let activeCalibration {
+ appendRunOutput(
+ "Monte Carlo calibration: device=\(activeCalibration.deviceName) id=\(activeCalibration.deviceID)"
+ )
+ }
+
+ var rng: any RandomNumberGenerator = SeededGenerator(seed: resolvedBaseSeed(seedValue))
+
+ do {
+ for sampleIndex in 0.. [ValidationIssue] {
+ let prefix = issueLocationPrefix(for: node)
+ return validationIssues.filter { $0.location.hasPrefix(prefix) }
+ }
+
+ func validationSeverity(for node: CircuitNode) -> ValidationSeverity? {
+ let scoped = issues(for: node)
+ if scoped.contains(where: { $0.severity == .error }) {
+ return .error
+ }
+ if scoped.contains(where: { $0.severity == .warning }) {
+ return .warning
+ }
+ if scoped.contains(where: { $0.severity == .info }) {
+ return .info
+ }
+ return nil
+ }
+
+ func validateAction() {
+ var issues: [ValidationIssue] = []
+
+ for node in nodes {
+ let location = issueLocation(for: node)
+
+ if let reason = disabledReason(for: node.kind) {
+ issues.append(
+ ValidationIssue(
+ severity: .error,
+ code: "POLICY_DISABLED_GATE",
+ message: reason,
+ location: location
+ )
+ )
+ }
+
+ if (node.kind == .squeeze || node.kind == .sourceSqueezed) && node.paramA > 1.5 {
+ issues.append(
+ ValidationIssue(
+ severity: .warning,
+ code: "GATE_SQUEEZE_RANGE",
+ message: "Squeeze factor r should be <= 1.50",
+ location: location
+ )
+ )
+ }
+
+ if let irIssue = irValidationIssue(for: node) {
+ issues.append(
+ irIssue
+ )
+ }
+ }
+
+ if backend == .fock, modeCount > 1 {
+ issues.append(
+ ValidationIssue(
+ severity: .error,
+ code: "BACKEND_MODE_LIMIT",
+ message: "Fock backend currently supports only single-mode circuits.",
+ location: "Circuit"
+ )
+ )
+ }
+
+ let trimmedSeed = seed.trimmingCharacters(in: .whitespacesAndNewlines)
+ if !trimmedSeed.isEmpty, UInt64(trimmedSeed) == nil {
+ issues.append(
+ ValidationIssue(
+ severity: .error,
+ code: "SEED_INVALID",
+ message: "Seed must be an unsigned 64-bit integer (0...\(UInt64.max)).",
+ location: "Circuit Config Inspector"
+ )
+ )
+ }
+
+ if !projects.contains(project) {
+ issues.append(
+ ValidationIssue(
+ severity: .error,
+ code: "CONTEXT_PROJECT_INVALID",
+ message: "Project '\(project)' does not belong to workspace '\(workspace)'.",
+ location: "GlobalTopBar"
+ )
+ )
+ }
+
+ if environment == .prod {
+ if selectedKPIPolicy == nil {
+ issues.append(
+ ValidationIssue(
+ severity: .error,
+ code: "PROD_KPI_POLICY_REQUIRED",
+ message: "Prod environment requires an explicit KPI policy selection.",
+ location: "KPI Policy"
+ )
+ )
+ }
+
+ if userRole != .approver && userRole != .admin {
+ issues.append(
+ ValidationIssue(
+ severity: .error,
+ code: "PROD_ROLE_REQUIRED",
+ message: "Prod environment requires role approver or admin.",
+ location: "GlobalTopBar"
+ )
+ )
+ }
+
+ if approvedProdFoundryProfiles.isEmpty {
+ issues.append(
+ ValidationIssue(
+ severity: .warning,
+ code: "PROD_PROFILE_APPROVAL_UNAVAILABLE",
+ message: "Foundry registry approvals are unavailable; prod profile verification is degraded.",
+ location: "Foundry Profile"
+ )
+ )
+ } else if !approvedProdFoundryProfiles.contains(foundryProfile) {
+ issues.append(
+ ValidationIssue(
+ severity: .error,
+ code: "PROD_PROFILE_NOT_APPROVED",
+ message: "Foundry profile '\(foundryProfile)' is not approved for prod.",
+ location: "Foundry Profile"
+ )
+ )
+ }
+
+ if currentFoundryProfileRef() == nil {
+ issues.append(
+ ValidationIssue(
+ severity: .error,
+ code: "PROD_PROFILE_REF_MISSING",
+ message: "Foundry profile '\(foundryProfile)' is missing registry profile_id/version mapping.",
+ location: "Foundry Profile"
+ )
+ )
+ }
+ }
+
+ if environment == .stage, foundryProfile == "experimental-lab-v1" {
+ issues.append(
+ ValidationIssue(
+ severity: .warning,
+ code: "STAGE_PROFILE_EXPERIMENTAL",
+ message: "Using experimental profile in stage environment.",
+ location: "Foundry Profile"
+ )
+ )
+ }
+
+ do {
+ try CLIService.validateRuntimeCircuit(
+ nodes: nodes,
+ modeCount: modeCount,
+ backend: backend
+ )
+ } catch {
+ issues.append(
+ ValidationIssue(
+ severity: .error,
+ code: "RUNTIME_PAYLOAD_INVALID",
+ message: error.localizedDescription,
+ location: "Runtime payload"
+ )
+ )
+ }
+
+ if environment == .prod, requiresBosonicSignoff, lastRunResponse != nil {
+ if let summary = bosonicSignoffSummary, summary.status != .pass {
+ let failedTitles = summary.checks
+ .filter { !$0.passed }
+ .map(\.title)
+ .joined(separator: ", ")
+ let failedText = failedTitles.isEmpty ? "criteria pending" : failedTitles
+ issues.append(
+ ValidationIssue(
+ severity: .warning,
+ code: "BOSONIC_SIGNOFF_INCOMPLETE",
+ message: "Bosonic signoff is incomplete (\(summary.passedChecks)/\(summary.requiredChecks)): \(failedText).",
+ location: "Provenance Inspector"
+ )
+ )
+ } else if bosonicSignoffSummary == nil {
+ issues.append(
+ ValidationIssue(
+ severity: .warning,
+ code: "BOSONIC_SIGNOFF_PENDING",
+ message: "Bosonic signoff is pending. Run the circuit to generate objective signoff checks.",
+ location: "Provenance Inspector"
+ )
+ )
+ }
+ }
+
+ if issues.isEmpty {
+ issues.append(
+ ValidationIssue(
+ severity: .info,
+ code: "VALIDATION_OK",
+ message: "No validation issues detected.",
+ location: "Circuit"
+ )
+ )
+ }
+
+ validationIssues = issues
+ statusText = "Validation complete"
+ appendAudit("Validate action")
+ appendRunOutput("Validation completed (\(issues.count) issue(s)).")
+ bottomTab = .validationConsole
+ }
+
+ func compileAction() {
+ runtimePhase = .compiling
+ do {
+ let runtimeEngineForExecution = resolveRuntimeExecutionEngine(action: "compile")
+ let inputPath = try resolveRuntimeInputPath()
+ let profileRefText: String
+ if let profileRef = currentFoundryProfileRef() {
+ profileRefText = "\(profileRef.profileID):v\(profileRef.version)"
+ } else {
+ profileRefText = "n/a"
+ }
+ appendRunOutput(
+ "Context: workspace=\(workspace) project=\(project) environment=\(environment.rawValue) role=\(userRole.rawValue)"
+ )
+ appendRunOutput("Compile runtime engine: \(runtimeEngineForExecution.rawValue)")
+ appendRunOutput(
+ "Compile: backend=\(backend.rawValue) gates=\(nodes.count) schema=\(schemaVersion) foundry_profile=\(foundryProfile) profile_ref=\(profileRefText)"
+ )
+ appendRunOutput("Compile input ready: \(inputPath)")
+ statusText = "Compile completed"
+ runtimePhase = .finished
+ appendAudit("Compile action")
+ bottomTab = .runOutputPanel
+ } catch {
+ statusText = "Compile failed: \(error.localizedDescription)"
+ appendRunOutput(statusText)
+ runtimePhase = .standby
+ bottomTab = .runOutputPanel
+ }
+ }
+
+ func runAction() async {
+ validateAction()
+ if validationIssues.contains(where: { $0.severity == .error }) {
+ statusText = "Run blocked: fix validation errors first."
+ appendRunOutput(statusText)
+ presentRunBlockedAlert()
+ runtimePhase = .standby
+ bottomTab = .validationConsole
+ return
+ }
+
+ isRunning = true
+ runtimePhase = .running
+ statusText = "Running..."
+ runtimeParitySummary = nil
+ stateHealthSummary = nil
+ bosonicSignoffSummary = nil
+ appendAudit("Run action")
+
+ do {
+ let runtimeEngineForExecution = resolveRuntimeExecutionEngine(action: "run")
+ if environment == .prod, selectedKPIPolicy == nil {
+ throw CLIRunnerError.commandFailed("Prod environment requires an explicit KPI policy selection before run.")
+ }
+ let profileRef = currentFoundryProfileRef()
+ let nodesValue = nodes
+ let backendValue = backend
+ let roleValue = userRole
+ let environmentValue = environment
+ let modeCountValue = modeCount
+ let seedValue = seed
+ let cutoffValue = cutoff
+ let schemaVersionValue = schemaVersion
+ let foundryProfileValue = foundryProfile
+ let noiseModelValue = noiseModel
+ let activeCalibration = useCalibratedNoiseProfile ? calibratedNoiseProfile : nil
+ let jsonPath = try resolveRuntimeInputPath()
+ appendRunOutput(
+ "Runtime context: workspace=\(workspace) project=\(project) environment=\(environment.rawValue) role=\(userRole.rawValue)"
+ )
+ appendRunOutput("Run runtime engine: \(runtimeEngineForExecution.rawValue)")
+ appendRunOutput("Runtime input: \(jsonPath)")
+
+ let alternateRuntime = pairedRuntimeEngine(for: runtimeEngineForExecution)
+ let runResponse = try await Task.detached(priority: .userInitiated) {
+ try CLIService.runCircuit(
+ jsonPath: jsonPath,
+ backend: backendValue,
+ environment: environmentValue,
+ runtimeEngine: runtimeEngineForExecution
+ )
+ }.value
+
+ let traceResponse = try await Task.detached(priority: .userInitiated) {
+ try CLIService.loadTrace(
+ jsonPath: jsonPath,
+ backend: backendValue,
+ role: roleValue,
+ environment: environmentValue,
+ runtimeEngine: runtimeEngineForExecution
+ )
+ }.value
+
+ let alternateRunResult = await Task.detached(priority: .utility) { () -> Result in
+ do {
+ let response = try CLIService.runCircuit(
+ jsonPath: jsonPath,
+ backend: backendValue,
+ environment: environmentValue,
+ runtimeEngine: alternateRuntime
+ )
+ return .success(response)
+ } catch {
+ return .failure(error)
+ }
+ }.value
+
+ lastRunResponse = runResponse
+ lastTraceResponse = traceResponse
+ statusText = "Run completed"
+ runtimePhase = .finished
+
+ let photonText: String
+ if let value = runResponse.meanPhotonNumber {
+ photonText = String(format: "%.4f", value)
+ } else {
+ photonText = "n/a"
+ }
+
+ appendRunOutput("Run: status=success backend=\(runResponse.backend ?? "n/a") mean_photon_number=\(photonText)")
+ appendRunOutput("Trace: backend=\(traceResponse.backend ?? "n/a") frame_count=\(traceResponse.traceFrameCount ?? 0)")
+ if let representation = runResponse.finalState?.representation {
+ appendRunOutput("State: representation=\(representation)")
+ }
+
+ switch alternateRunResult {
+ case .success(let alternateRunResponse):
+ let summary = buildRuntimeParitySummary(
+ selectedRuntime: runtimeEngineForExecution,
+ selectedResponse: runResponse,
+ referenceRuntime: alternateRuntime,
+ referenceResponse: alternateRunResponse
+ )
+ runtimeParitySummary = summary
+ appendRunOutput(
+ "Runtime parity: \(summary.status.title) selected=\(summary.selectedRuntime.rawValue) reference=\(summary.referenceRuntime.rawValue) \(summary.detail)"
+ )
+ case .failure(let error):
+ runtimeParitySummary = RuntimeParitySummary(
+ status: .unmatched,
+ selectedRuntime: runtimeEngineForExecution,
+ referenceRuntime: alternateRuntime,
+ selectedBackendVersion: runResponse.provenance?.backendVersion ?? "n/a",
+ referenceBackendVersion: "unavailable",
+ meanPhotonDelta: nil,
+ stateDeltaMax: nil,
+ measurementDelta: nil,
+ detail: "Reference runtime unavailable: \(error.localizedDescription)"
+ )
+ appendRunOutput("Runtime parity: UNMATCHED reference runtime unavailable (\(alternateRuntime.rawValue)).")
+ }
+ let robustnessContext = RobustnessContext(
+ nodes: nodesValue,
+ modeCount: modeCountValue,
+ backend: backendValue,
+ seed: seedValue,
+ cutoff: cutoffValue,
+ schemaVersion: schemaVersionValue,
+ foundryProfile: foundryProfileValue,
+ environment: environmentValue,
+ foundryProfileID: profileRef?.profileID,
+ foundryProfileVersion: profileRef?.version,
+ runtimeEngine: runtimeEngineForExecution,
+ noiseModel: noiseModelValue,
+ calibratedProfile: activeCalibration
+ )
+ let kpiPolicy = selectedKPIPolicy
+
+ let healthSummary = await buildStateHealthSummary(
+ runResponse: runResponse,
+ paritySummary: runtimeParitySummary,
+ robustnessContext: robustnessContext,
+ kpiPolicy: kpiPolicy
+ )
+ stateHealthSummary = healthSummary
+ let signoffSummary = buildBosonicSignoffSummary(
+ healthSummary: healthSummary,
+ paritySummary: runtimeParitySummary
+ )
+ bosonicSignoffSummary = signoffSummary
+ appendRunOutput(
+ "State health: \(healthSummary.status.title) validity=\(healthSummary.validity.title) confidence=\(healthSummary.confidence.title) robustness=\(healthSummary.robustness.status.title) kpi=\(healthSummary.kpi.status.title) checks=\(healthSummary.checksCompleted) issues=\(healthSummary.issueCount)"
+ )
+ if let signoffSummary {
+ appendRunOutput(
+ "Bosonic signoff: \(signoffSummary.status.title) checks=\(signoffSummary.passedChecks)/\(signoffSummary.requiredChecks) \(signoffSummary.detail)"
+ )
+ }
+ bottomTab = .runOutputPanel
+ } catch {
+ statusText = "Run failed: \(error.localizedDescription)"
+ appendRunOutput(statusText)
+ runtimePhase = .standby
+ bottomTab = .runOutputPanel
+ }
+
+ isRunning = false
+ }
+
+ func exportAction() {
+ do {
+ let url = try CLIService.exportCircuitSnapshot(
+ nodes: nodes,
+ metadata: snapshotMetadata()
+ )
+ lastExportPath = url.path
+ appendRunOutput("Export: \(url.path)")
+ markClean("Export completed")
+ appendAudit("Export action")
+ bottomTab = .runOutputPanel
+ } catch {
+ statusText = "Export failed: \(error.localizedDescription)"
+ appendRunOutput(statusText)
+ }
+ }
+
+ func saveAction() {
+ if let lastSavedDocumentURL {
+ persistCircuitDocument(to: lastSavedDocumentURL, format: nil, actionName: "Save")
+ return
+ }
+ saveAsAction()
+ }
+
+ func newAction() {
+ isApplyingContext = true
+ modeCount = 4
+ slotCount = 6
+ nodes = []
+ selectedNodeID = nil
+ selectedModeIndex = nil
+ isApplyingContext = false
+
+ lastSavedDocumentURL = nil
+ lastSavedDocumentPath = nil
+ clearMonteCarloResults()
+ runtimeParitySummary = nil
+ stateHealthSummary = nil
+ bosonicSignoffSummary = nil
+ appendRunOutput("New circuit initialized.")
+ appendAudit("New action")
+ markClean("New circuit ready")
+ runtimePhase = .standby
+ validateAction()
+ bottomTab = .validationConsole
+ }
+
+ func openAction() {
+ guard let sourceURL = CLIService.promptForCircuitOpenURL(existingURL: lastSavedDocumentURL) else {
+ statusText = "Open canceled"
+ appendRunOutput("Open canceled.")
+ runtimePhase = .standby
+ return
+ }
+
+ do {
+ let document = try CLIService.loadCircuitDocument(from: sourceURL)
+ applyLoadedCircuit(document)
+ clearMonteCarloResults()
+ runtimeParitySummary = nil
+ stateHealthSummary = nil
+ bosonicSignoffSummary = nil
+ validateAction()
+ lastSavedDocumentURL = sourceURL
+ lastSavedDocumentPath = sourceURL.path
+ appendRunOutput("Open: \(sourceURL.path)")
+ appendAudit("Open action")
+ markClean("Opened circuit")
+ runtimePhase = .standby
+ bottomTab = .validationConsole
+ } catch {
+ statusText = "Open failed: \(error.localizedDescription)"
+ appendRunOutput(statusText)
+ runtimePhase = .standby
+ bottomTab = .runOutputPanel
+ }
+ }
+
+ func saveAsAction() {
+ let suggestedBaseName = suggestedCircuitDocumentBaseName()
+ guard let destination = CLIService.promptForCircuitSaveDestination(
+ suggestedBaseName: suggestedBaseName,
+ existingURL: lastSavedDocumentURL
+ ) else {
+ statusText = "Save canceled"
+ appendRunOutput("Save canceled.")
+ return
+ }
+ persistCircuitDocument(to: destination.url, format: destination.format, actionName: "Save As")
+ }
+
+ private func persistCircuitDocument(to url: URL, format: CLIService.CircuitSaveFormat?, actionName: String) {
+ do {
+ let savedURL = try CLIService.saveCircuitDocument(
+ nodes: nodes,
+ modeCount: modeCount,
+ slotCount: slotCount,
+ metadata: snapshotMetadata(),
+ to: url,
+ format: format
+ )
+ lastSavedDocumentURL = savedURL
+ lastSavedDocumentPath = savedURL.path
+ appendRunOutput("\(actionName): \(savedURL.path)")
+ markClean("\(actionName) completed")
+ appendAudit("\(actionName) action")
+ bottomTab = .runOutputPanel
+ } catch {
+ statusText = "\(actionName) failed: \(error.localizedDescription)"
+ appendRunOutput(statusText)
+ bottomTab = .runOutputPanel
+ }
+ }
+
+ private func applyLoadedCircuit(_ document: CLIService.CircuitSnapshotDocument) {
+ isApplyingContext = true
+ defer { isApplyingContext = false }
+
+ if let workspace = document.workspace, workspaces.contains(workspace) {
+ self.workspace = workspace
+ }
+
+ if let project = document.project {
+ let available = availableProjects(for: self.workspace)
+ if available.contains(project) {
+ self.project = project
+ }
+ }
+
+ if let environment = document.environment {
+ self.environment = environment
+ }
+
+ if let userRole = document.userRole {
+ self.userRole = userRole
+ }
+
+ if let backend = document.backend {
+ self.backend = backend
+ }
+
+ if let runtimeEngine = document.runtimeEngine {
+ self.runtimeEngine = runtimeEngine
+ }
+
+ if let seed = document.seed, !seed.isEmpty {
+ self.seed = seed
+ }
+
+ if let cutoff = document.cutoff {
+ self.cutoff = max(1, cutoff)
+ }
+
+ if !document.schemaVersion.isEmpty {
+ self.schemaVersion = document.schemaVersion
+ }
+
+ if let foundryProfile = document.foundryProfile, !foundryProfile.isEmpty {
+ if !foundryProfiles.contains(foundryProfile) {
+ foundryProfiles.append(foundryProfile)
+ }
+ self.foundryProfile = foundryProfile
+ }
+
+ if let policyID = document.kpiPolicyID, kpiPolicies.contains(where: { $0.id == policyID }) {
+ selectedKPIPolicyID = policyID
+ }
+
+ let nodes = document.nodes
+ let derivedModeCount = max(1, (nodes.map(\.mode).max() ?? -1) + 1)
+ let derivedSlotCount = max(1, (nodes.map(\.slot).max() ?? -1) + 1)
+
+ self.modeCount = max(document.modeCount, derivedModeCount)
+ self.slotCount = max(document.slotCount, derivedSlotCount)
+ self.nodes = nodes
+ self.selectedNodeID = nodes.first?.id
+ self.selectedModeIndex = nodes.first?.mode
+ }
+
+ private func resolveRuntimeInputPath() throws -> String {
+ if !nodes.isEmpty {
+ let profileRef = currentFoundryProfileRef()
+ let runtimeURL = try CLIService.materializeRuntimeCircuitInput(
+ nodes: nodes,
+ modeCount: modeCount,
+ backend: backend,
+ seed: seed,
+ cutoff: cutoff,
+ schemaVersion: schemaVersion,
+ foundryProfile: foundryProfile,
+ environment: environment,
+ foundryProfileID: profileRef?.profileID,
+ foundryProfileVersion: profileRef?.version
+ )
+ return runtimeURL.path
+ }
+
+ let trimmed = circuitJSONPath.trimmingCharacters(in: .whitespacesAndNewlines)
+ guard !trimmed.isEmpty else {
+ throw CLIRunnerError.invalidRuntimeCircuit(
+ "Canvas is empty and no JSON path is set. Provide Circuit JSON Path or add gates to the canvas."
+ )
+ }
+ return trimmed
+ }
+
+ func clearRunOutput() {
+ runOutput.removeAll()
+ appendAudit("Cleared run output")
+ }
+
+ private func snapshotMetadata() -> [String: String] {
+ let profileRef = currentFoundryProfileRef()
+ let kpiPolicy = selectedKPIPolicy
+ return [
+ "schema_version": schemaVersion,
+ "runtime_engine": runtimeEngine.rawValue,
+ "backend": backend.rawValue,
+ "seed": seed,
+ "cutoff": String(cutoff),
+ "foundry_profile": foundryProfile,
+ "workspace": workspace,
+ "project": project,
+ "environment": environment.rawValue,
+ "role": userRole.rawValue,
+ "foundry_profile_id": profileRef?.profileID ?? "",
+ "foundry_profile_version": profileRef.map { String($0.version) } ?? "",
+ "kpi_policy_id": kpiPolicy?.id ?? "",
+ "kpi_policy_version": kpiPolicy.map { String($0.version) } ?? "",
+ ]
+ }
+
+ private func suggestedCircuitDocumentBaseName() -> String {
+ let workspaceToken = workspace.lowercased().replacingOccurrences(of: " ", with: "_")
+ let projectToken = project.lowercased().replacingOccurrences(of: " ", with: "_")
+ return "\(workspaceToken)_\(projectToken)_circuit"
+ }
+
+ private func currentFoundryProfileRef() -> FoundryProfileRef? {
+ foundryProfileRefsByName[foundryProfile]
+ }
+
+ private static func loadFoundryRegistryCatalog() -> FoundryRegistryCatalog {
+ let fallbackCatalog = FoundryRegistryCatalog(
+ allProfileNames: fallbackFoundryProfiles,
+ approvedProfileNames: ["studio-default-v1"],
+ profileRefsByName: [:]
+ )
+
+ let registryURL = repoRootURL().appendingPathComponent("docs/config/foundry_registry.json")
+ guard let data = try? Data(contentsOf: registryURL) else {
+ return fallbackCatalog
+ }
+
+ guard let document = try? JSONDecoder().decode(FoundryRegistryDocument.self, from: data) else {
+ return fallbackCatalog
+ }
+
+ var profileNames: [String] = []
+ var approved = Set()
+ var refs: [String: FoundryProfileRef] = [:]
+
+ for profile in document.profiles {
+ let name = profile.spec.name.trimmingCharacters(in: .whitespacesAndNewlines)
+ guard !name.isEmpty else { continue }
+
+ profileNames.append(name)
+ refs[name] = FoundryProfileRef(profileID: profile.profileID, version: profile.version)
+
+ if profile.status.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == "approved" {
+ approved.insert(name)
+ }
+ }
+
+ let names = uniqueOrdered(profileNames + fallbackFoundryProfiles)
+ if approved.isEmpty {
+ approved.insert("studio-default-v1")
+ }
+
+ return FoundryRegistryCatalog(
+ allProfileNames: names,
+ approvedProfileNames: approved,
+ profileRefsByName: refs
+ )
+ }
+
+ private static func loadKPIPolicyCatalog() -> [KPIPolicyDefinition] {
+ let fallback = fallbackKPIPolicies
+ let policyURL = repoRootURL().appendingPathComponent("docs/config/kpi_policies.json")
+ guard let data = try? Data(contentsOf: policyURL) else {
+ return fallback
+ }
+
+ guard let document = try? JSONDecoder().decode(KPIPolicyCatalogDocument.self, from: data) else {
+ return fallback
+ }
+
+ let normalized = document.policies.filter { !$0.id.isEmpty && !$0.name.isEmpty }
+ return normalized.isEmpty ? fallback : normalized
+ }
+
+ private static func uniqueOrdered(_ values: [String]) -> [String] {
+ var seen = Set()
+ var ordered: [String] = []
+ ordered.reserveCapacity(values.count)
+ for value in values where seen.insert(value).inserted {
+ ordered.append(value)
+ }
+ return ordered
+ }
+
+ private static func repoRootURL() -> URL {
+ let fileURL = URL(fileURLWithPath: #filePath)
+ return fileURL
+ .deletingLastPathComponent() // schrosim-studio
+ .deletingLastPathComponent() // Sources
+ .deletingLastPathComponent() // core-swift
+ .deletingLastPathComponent() // repo root
+ }
+
+ private func markDirty(_ reason: String) {
+ // Invalidate run-derived diagnostics whenever the working configuration mutates.
+ runtimeParitySummary = nil
+ stateHealthSummary = nil
+ bosonicSignoffSummary = nil
+ isDirty = true
+ statusText = "dirty: \(reason)"
+ }
+
+ private func markClean(_ reason: String) {
+ isDirty = false
+ statusText = reason
+ }
+
+ private func appendRunOutput(_ message: String) {
+ runOutput.append(message)
+ if runOutput.count > 300 {
+ runOutput.removeFirst(runOutput.count - 300)
+ }
+ }
+
+ private func appendAudit(_ message: String) {
+ auditTrail.append(message)
+ if auditTrail.count > 300 {
+ auditTrail.removeFirst(auditTrail.count - 300)
+ }
+ }
+
+ private func issueLocationPrefix(for node: CircuitNode) -> String {
+ "Mode \(node.mode), Slot \(node.slot)"
+ }
+
+ private func issueLocation(for node: CircuitNode) -> String {
+ "\(issueLocationPrefix(for: node)) > \(node.kind.rawValue)"
+ }
+
+ private func enforceBackendForSelectedKPIPolicy() -> Bool {
+ guard let policy = selectedKPIPolicy else { return false }
+ guard let enforcedBackend = backendRequired(for: policy, currentBackend: backend) else { return false }
+
+ showValidation = true
+ bottomTab = .validationConsole
+
+ guard backend != enforcedBackend else { return false }
+
+ backend = enforcedBackend
+ statusText = "Backend auto-switched to \(enforcedBackend.title) for KPI policy '\(policy.name)'."
+ appendRunOutput(statusText)
+ appendAudit("Backend auto-switched to \(enforcedBackend.rawValue) for policy \(policy.id)")
+ return true
+ }
+
+ private func enforceKPIPolicyForSelectedBackend() -> Bool {
+ guard let policy = preferredKPIPolicy(for: backend) else { return false }
+ guard selectedKPIPolicyID != policy.id else { return false }
+
+ showValidation = true
+ bottomTab = .validationConsole
+
+ selectedKPIPolicyID = policy.id
+ statusText = "KPI policy auto-switched to '\(policy.name)' for \(backend.title) backend."
+ appendRunOutput(statusText)
+ appendAudit("KPI policy auto-switched to \(policy.id) for backend \(backend.rawValue)")
+ return true
+ }
+
+ private func backendRequired(
+ for policy: KPIPolicyDefinition,
+ currentBackend: BackendSelection
+ ) -> BackendSelection? {
+ switch policy.targetRepresentation {
+ case "fock_number_basis":
+ return .fock
+ case "gaussian_phase_space":
+ return currentBackend == .fock ? .hybrid : nil
+ default:
+ return nil
+ }
+ }
+
+ private func preferredKPIPolicy(for backend: BackendSelection) -> KPIPolicyDefinition? {
+ switch backend {
+ case .fock:
+ return findPreferredKPIPolicy(
+ preferredID: Self.cvFockValidationPolicyID,
+ targetRepresentation: "fock_number_basis"
+ )
+ case .gaussian, .hybrid:
+ if isQECPrototypeProject {
+ return findPreferredKPIPolicy(
+ preferredID: Self.cvQECGaussianPolicyID,
+ targetRepresentation: "gaussian_phase_space"
+ )
+ }
+ return findPreferredKPIPolicy(
+ preferredID: Self.cvGaussianProductionPolicyID,
+ targetRepresentation: "gaussian_phase_space"
+ )
+ }
+ }
+
+ private var isQECPrototypeProject: Bool {
+ project.trimmingCharacters(in: .whitespacesAndNewlines).localizedCaseInsensitiveCompare("Quantum Error Correction") == .orderedSame
+ }
+
+ private func findPreferredKPIPolicy(
+ preferredID: String,
+ targetRepresentation: String
+ ) -> KPIPolicyDefinition? {
+ if let exactMatch = kpiPolicies.first(where: { $0.id == preferredID }) {
+ return exactMatch
+ }
+ return kpiPolicies.first { $0.targetRepresentation == targetRepresentation }
+ }
+
+ private func presentRunBlockedAlert() {
+ let errorIssues = validationIssues.filter { $0.severity == .error }
+ if errorIssues.isEmpty {
+ runBlockedAlertMessage = "Validation failed. Check your gates."
+ } else {
+ let preview = errorIssues.prefix(4).map { "[\($0.code)] \($0.message)" }.joined(separator: "\n")
+ runBlockedAlertMessage = "Error, check your gates.\n\n\(preview)"
+ }
+ isRunBlockedAlertPresented = true
+ }
+
+ private func irValidationIssue(for node: CircuitNode) -> ValidationIssue? {
+ guard node.kind == .loss || node.kind == .thermalLoss else {
+ return nil
+ }
+
+ do {
+ let circuit = try Circuit(modes: max(1, modeCount))
+ switch node.kind {
+ case .loss:
+ try circuit.append(.loss(eta: node.paramA, mode: node.mode))
+ case .thermalLoss:
+ try circuit.append(.thermalLoss(eta: node.paramA, nTh: node.paramB, mode: node.mode))
+ default:
+ return nil
+ }
+ return nil
+ } catch let error as IRValidationError {
+ return ValidationIssue(
+ severity: .error,
+ code: "IR_VALIDATION",
+ message: error.description,
+ location: issueLocation(for: node)
+ )
+ } catch {
+ return ValidationIssue(
+ severity: .error,
+ code: "IR_VALIDATION",
+ message: String(describing: error),
+ location: issueLocation(for: node)
+ )
+ }
+ }
+
+ private func perturbNodesForMonteCarlo(
+ _ source: [CircuitNode],
+ noiseModel: MonteCarloNoiseModel,
+ calibratedProfile: CalibratedNoiseTensorProfile?,
+ sampleIndex: Int,
+ modeCount: Int,
+ rng: inout any RandomNumberGenerator
+ ) -> [CircuitNode] {
+ var perturbed = source
+ let resolvedModeCount = max(modeCount, 1)
+ let driftModel = calibratedProfile?.driftModel ?? .none
+ let driftScale = Double(max(sampleIndex, 0))
+
+ let lossNoise = sampleCorrelatedModeNoise(
+ modeCount: resolvedModeCount,
+ fallbackSigma: noiseModel.lossEtaSigma,
+ covariance: calibratedProfile?.correlations.lossEta,
+ rng: &rng
+ )
+ let thermalNoise = sampleCorrelatedModeNoise(
+ modeCount: resolvedModeCount,
+ fallbackSigma: noiseModel.thermalNoiseSigma,
+ covariance: calibratedProfile?.correlations.thermal,
+ rng: &rng
+ )
+ let phaseNoise = sampleCorrelatedModeNoise(
+ modeCount: resolvedModeCount,
+ fallbackSigma: noiseModel.phaseSigmaRad,
+ covariance: calibratedProfile?.correlations.phaseRad,
+ rng: &rng
+ )
+ let displacementNoiseA = sampleCorrelatedModeNoise(
+ modeCount: resolvedModeCount,
+ fallbackSigma: noiseModel.displacementSigma,
+ covariance: calibratedProfile?.correlations.displacement,
+ rng: &rng
+ )
+ let displacementNoiseB = sampleCorrelatedModeNoise(
+ modeCount: resolvedModeCount,
+ fallbackSigma: noiseModel.displacementSigma,
+ covariance: calibratedProfile?.correlations.displacement,
+ rng: &rng
+ )
+ let etaDrift = driftModel.lossEtaPerSample * driftScale
+ let thermalDrift = driftModel.thermalPerSample * driftScale
+ let phaseDrift = driftModel.phaseRadPerSample * driftScale
+ let displacementDrift = driftModel.displacementPerSample * driftScale
+ let detectorHomodyneSigma = calibratedProfile?.detectorModel.homodyneSigma ?? 0.0
+ let detectorHeterodyneSigma = calibratedProfile?.detectorModel.heterodyneSigma ?? 0.0
+
+ for index in perturbed.indices {
+ let mode = clamp(perturbed[index].mode, to: 0...(resolvedModeCount - 1))
+ switch perturbed[index].kind {
+ case .loss:
+ perturbed[index].paramA = clamp(
+ perturbed[index].paramA + lossNoise[mode] + etaDrift,
+ to: 0.0...1.0
+ )
+ case .thermalLoss:
+ perturbed[index].paramA = clamp(
+ perturbed[index].paramA + lossNoise[mode] + etaDrift,
+ to: 0.0...1.0
+ )
+ perturbed[index].paramB = max(
+ 0.0,
+ perturbed[index].paramB + thermalNoise[mode] + thermalDrift
+ )
+ case .phase, .beamSplitter:
+ perturbed[index].paramA += phaseNoise[mode] + phaseDrift
+ case .displace, .sourceCoherent:
+ perturbed[index].paramA += displacementNoiseA[mode] + displacementDrift
+ perturbed[index].paramB += displacementNoiseB[mode] + displacementDrift
+ case .measureHomodyne:
+ perturbed[index].paramA += sampleGaussian(sigma: detectorHomodyneSigma, rng: &rng)
+ case .measureHeterodyne:
+ // Proxy detector noise by a small phase-equivalent jitter near readout.
+ perturbed[index].paramA += sampleGaussian(sigma: detectorHeterodyneSigma, rng: &rng)
+ default:
+ continue
+ }
+ }
+
+ return perturbed
+ }
+
+ private func sampleCorrelatedModeNoise(
+ modeCount: Int,
+ fallbackSigma: Double,
+ covariance: [[Double]]?,
+ rng: inout any RandomNumberGenerator
+ ) -> [Double] {
+ let resolvedModeCount = max(modeCount, 1)
+ var result = Array(repeating: 0.0, count: resolvedModeCount)
+
+ guard fallbackSigma > 0 || covariance != nil else {
+ return result
+ }
+
+ if fallbackSigma > 0 {
+ for index in result.indices {
+ result[index] = sampleGaussian(sigma: fallbackSigma, rng: &rng)
+ }
+ }
+
+ guard
+ let covariance,
+ let normalized = normalizedCovarianceMatrix(covariance, modeCount: resolvedModeCount),
+ let factor = choleskyLowerTriangular(normalized)
+ else {
+ return result
+ }
+
+ let n = factor.count
+ var z = Array(repeating: 0.0, count: n)
+ for index in 0.. String? {
+ let effectiveModes = max(modeCount, 1)
+ var warnings: [String] = []
+ let tensors: [(String, [[Double]]?)] = [
+ ("loss_eta", profile.correlations.lossEta),
+ ("thermal", profile.correlations.thermal),
+ ("phase_rad", profile.correlations.phaseRad),
+ ("displacement", profile.correlations.displacement),
+ ]
+
+ for (name, matrix) in tensors {
+ guard let matrix else { continue }
+ guard !matrix.isEmpty else {
+ warnings.append("\(name) tensor is empty")
+ continue
+ }
+ let rows = matrix.count
+ let cols = matrix.map(\.count).max() ?? 0
+ if rows != cols {
+ warnings.append("\(name) tensor is not square (\(rows)x\(cols))")
+ continue
+ }
+ if rows < effectiveModes {
+ warnings.append("\(name) tensor coverage \(rows) < current modes \(effectiveModes)")
+ }
+ }
+
+ if warnings.isEmpty {
+ return nil
+ }
+ return warnings.joined(separator: "; ")
+ }
+
+ private func normalizedCovarianceMatrix(
+ _ covariance: [[Double]],
+ modeCount: Int
+ ) -> [[Double]]? {
+ guard !covariance.isEmpty else { return nil }
+ let rowCount = covariance.count
+ guard rowCount > 0 else { return nil }
+ let matrixSize = min(modeCount, rowCount)
+ guard matrixSize > 0 else { return nil }
+
+ for rowIndex in 0..= matrixSize else {
+ return nil
+ }
+ }
+
+ var matrix = Array(repeating: Array(repeating: 0.0, count: matrixSize), count: matrixSize)
+ for i in 0.. [[Double]]? {
+ let n = matrix.count
+ guard n > 0 else { return nil }
+
+ var lower = Array(repeating: Array(repeating: 0.0, count: n), count: n)
+ for i in 0.. 0 {
+ for k in 0.. String {
+ let base = UInt64(baseSeed.trimmingCharacters(in: .whitespacesAndNewlines)) ?? 0
+ let jitter = UInt64.random(in: 0...999_999, using: &rng)
+ let sampleOffset = UInt64(sampleIndex + 1)
+ let resolved = base &+ sampleOffset &+ jitter
+ return String(resolved)
+ }
+
+ private func resolvedBaseSeed(_ seed: String) -> UInt64 {
+ let trimmed = seed.trimmingCharacters(in: .whitespacesAndNewlines)
+ return UInt64(trimmed) ?? 1
+ }
+
+ private func sampleGaussian(
+ sigma: Double,
+ rng: inout any RandomNumberGenerator
+ ) -> Double {
+ guard sigma > 0 else { return 0.0 }
+ var u1 = Double.random(in: 0..<1, using: &rng)
+ if u1 < 1e-12 {
+ u1 = 1e-12
+ }
+ let u2 = Double.random(in: 0..<1, using: &rng)
+ let z = sqrt(-2.0 * log(u1)) * cos(2.0 * Double.pi * u2)
+ return z * sigma
+ }
+
+ private func clamp(_ value: Double, to range: ClosedRange) -> Double {
+ min(max(value, range.lowerBound), range.upperBound)
+ }
+
+ private func clamp(_ value: Int, to range: ClosedRange) -> Int {
+ min(max(value, range.lowerBound), range.upperBound)
+ }
+
+ private func pairedRuntimeEngine(for engine: RuntimeEngineSelection) -> RuntimeEngineSelection {
+ switch engine {
+ case .swift:
+ return .rust
+ case .rust:
+ return .swift
+ }
+ }
+
+ private var requiresBosonicSignoff: Bool {
+ if project.localizedCaseInsensitiveContains("boson") {
+ return true
+ }
+ if circuitJSONPath.localizedCaseInsensitiveContains("boson") {
+ return true
+ }
+ if let preset = selectedScientificCircuitPreset {
+ if preset.id.localizedCaseInsensitiveContains("boson") {
+ return true
+ }
+ if preset.title.localizedCaseInsensitiveContains("boson") {
+ return true
+ }
+ }
+ return false
+ }
+
+ private func buildBosonicSignoffSummary(
+ healthSummary: StateHealthSummary?,
+ paritySummary: RuntimeParitySummary?
+ ) -> BosonicSignoffSummary? {
+ guard requiresBosonicSignoff else {
+ return nil
+ }
+
+ let checks: [BosonicSignoffCheck] = [
+ BosonicSignoffCheck(
+ key: "physical_validity",
+ title: "Physical validity",
+ passed: healthSummary?.validity == .valid,
+ observed: healthSummary?.validity.title ?? "UNAVAILABLE",
+ expected: StateValidityStatus.valid.title,
+ detail: "Quantum state invariants and representation checks."
+ ),
+ BosonicSignoffCheck(
+ key: "numerical_confidence",
+ title: "Numerical confidence",
+ passed: healthSummary?.confidence == .match,
+ observed: healthSummary?.confidence.title ?? "UNAVAILABLE",
+ expected: StateConfidenceStatus.match.title,
+ detail: "Cross-runtime confidence from parity-equivalent outputs."
+ ),
+ BosonicSignoffCheck(
+ key: "runtime_parity",
+ title: "Runtime parity",
+ passed: paritySummary?.status == .match,
+ observed: paritySummary?.status.title ?? "UNAVAILABLE",
+ expected: RuntimeParityStatus.match.title,
+ detail: "Selected and reference runtimes must agree at strict tolerance."
+ ),
+ BosonicSignoffCheck(
+ key: "noise_robustness",
+ title: "Noise robustness",
+ passed: healthSummary?.robustness.status == .robust,
+ observed: healthSummary?.robustness.status.title ?? "UNAVAILABLE",
+ expected: StateRobustnessStatus.robust.title,
+ detail: "Calibrated Monte Carlo robustness must satisfy policy thresholds."
+ ),
+ BosonicSignoffCheck(
+ key: "kpi_policy",
+ title: "KPI policy",
+ passed: healthSummary?.kpi.status == .pass,
+ observed: healthSummary?.kpi.status.title ?? "UNAVAILABLE",
+ expected: KPIPolicyStatus.pass.title,
+ detail: "Explicit KPI contract gate for production-grade qualification."
+ ),
+ ]
+
+ let requiredChecks = checks.count
+ let passedChecks = checks.filter(\.passed).count
+
+ let status: BosonicSignoffStatus
+ if healthSummary == nil {
+ status = .unavailable
+ } else if passedChecks == requiredChecks {
+ status = .pass
+ } else {
+ status = .incomplete
+ }
+
+ let failedChecks = checks.filter { !$0.passed }
+ let detailParts = [
+ "checks=\(passedChecks)/\(requiredChecks)",
+ "failed=\(failedChecks.map(\.key).joined(separator: ","))"
+ ]
+
+ let recommendation: String
+ if status == .pass {
+ recommendation = "Bosonic signoff completed. Circuit meets objective production criteria."
+ } else if status == .unavailable {
+ recommendation = "Run required to generate state health and parity evidence for bosonic signoff."
+ } else {
+ recommendation = "Resolve failed signoff checks before production qualification."
+ }
+
+ return BosonicSignoffSummary(
+ status: status,
+ requiredChecks: requiredChecks,
+ passedChecks: passedChecks,
+ checks: checks,
+ recommendation: recommendation,
+ detail: detailParts.joined(separator: " ")
+ )
+ }
+
+ private func buildRuntimeParitySummary(
+ selectedRuntime: RuntimeEngineSelection,
+ selectedResponse: RunResponse,
+ referenceRuntime: RuntimeEngineSelection,
+ referenceResponse: RunResponse
+ ) -> RuntimeParitySummary {
+ let selectedVersion = selectedResponse.provenance?.backendVersion ?? "n/a"
+ let referenceVersion = referenceResponse.provenance?.backendVersion ?? "n/a"
+ let meanPhotonDelta = absoluteDelta(selectedResponse.meanPhotonNumber, referenceResponse.meanPhotonNumber)
+ let measurementDelta = delta(selectedResponse.measurementCount, referenceResponse.measurementCount)
+ let stateDeltaMax = maxStateDelta(selectedResponse.finalState, referenceResponse.finalState)
+
+ let matchTolerance = 1e-9
+ let driftTolerance = 1e-5
+
+ let status: RuntimeParityStatus
+ if let measurementDelta, measurementDelta != 0 {
+ status = .unmatched
+ } else {
+ let effectiveDelta = max(meanPhotonDelta ?? 0.0, stateDeltaMax ?? 0.0)
+ if effectiveDelta <= matchTolerance {
+ status = .match
+ } else if effectiveDelta <= driftTolerance {
+ status = .drift
+ } else {
+ status = .unmatched
+ }
+ }
+
+ var detailParts: [String] = []
+ if let meanPhotonDelta {
+ detailParts.append(String(format: "d_mean=%.8e", meanPhotonDelta))
+ }
+ if let stateDeltaMax {
+ detailParts.append(String(format: "d_state=%.8e", stateDeltaMax))
+ }
+ if let measurementDelta {
+ detailParts.append("d_meas=\(measurementDelta)")
+ }
+ if detailParts.isEmpty {
+ detailParts.append("No comparable metrics available")
+ }
+
+ return RuntimeParitySummary(
+ status: status,
+ selectedRuntime: selectedRuntime,
+ referenceRuntime: referenceRuntime,
+ selectedBackendVersion: selectedVersion,
+ referenceBackendVersion: referenceVersion,
+ meanPhotonDelta: meanPhotonDelta,
+ stateDeltaMax: stateDeltaMax,
+ measurementDelta: measurementDelta,
+ detail: detailParts.joined(separator: " ")
+ )
+ }
+
+ private func buildStateHealthSummary(
+ runResponse: RunResponse,
+ paritySummary: RuntimeParitySummary?,
+ robustnessContext: RobustnessContext,
+ kpiPolicy: KPIPolicyDefinition?
+ ) async -> StateHealthSummary {
+ var checksCompleted = 0
+ var errors: [String] = []
+ var warnings: [String] = []
+
+ checksCompleted += 1
+ if runResponse.status.lowercased() != "success" {
+ errors.append("Runtime returned status=\(runResponse.status)")
+ }
+
+ checksCompleted += 1
+ if let meanPhotonNumber = runResponse.meanPhotonNumber {
+ if !meanPhotonNumber.isFinite || meanPhotonNumber < -1e-9 {
+ errors.append("mean_photon_number is invalid")
+ }
+ } else {
+ warnings.append("mean_photon_number unavailable")
+ }
+
+ checksCompleted += 1
+ if let measurementCount = runResponse.measurementCount {
+ if measurementCount < 0 {
+ errors.append("measurement_count is negative")
+ }
+ } else {
+ warnings.append("measurement_count unavailable")
+ }
+
+ if let state = runResponse.finalState {
+ checksCompleted += 1
+ if state.representation?.isEmpty ?? true {
+ warnings.append("State representation unavailable")
+ }
+
+ switch state.representation {
+ case "gaussian_phase_space":
+ evaluateGaussianStateHealth(state, checksCompleted: &checksCompleted, errors: &errors, warnings: &warnings)
+ case "fock_number_basis":
+ evaluateFockStateHealth(state, checksCompleted: &checksCompleted, errors: &errors, warnings: &warnings)
+ case .some:
+ warnings.append("Representation unsupported for deep validation")
+ case .none:
+ break
+ }
+ } else {
+ warnings.append("Final state snapshot unavailable")
+ }
+
+ let validity: StateValidityStatus
+ if !errors.isEmpty {
+ validity = .invalid
+ } else if runResponse.finalState == nil {
+ validity = .unavailable
+ } else if !warnings.isEmpty {
+ validity = .partial
+ } else {
+ validity = .valid
+ }
+
+ let confidence: StateConfidenceStatus
+ if let paritySummary {
+ switch paritySummary.status {
+ case .match:
+ confidence = .match
+ case .drift:
+ confidence = .drift
+ case .unmatched:
+ confidence = .unmatched
+ }
+ } else {
+ confidence = .unavailable
+ }
+
+ let robustness = await buildStateRobustnessSummary(
+ baselineResponse: runResponse,
+ context: robustnessContext,
+ policy: kpiPolicy
+ )
+
+ let kpi = buildKPIPolicyEvaluationSummary(
+ runResponse: runResponse,
+ policy: kpiPolicy
+ )
+
+ let status: StateHealthStatus
+ switch validity {
+ case .invalid:
+ status = .invalid
+ case .unavailable:
+ status = .unavailable
+ case .partial, .valid:
+ if kpi.status == .fail || confidence == .unmatched || robustness.status == .fragile {
+ status = .review
+ } else if validity == .valid,
+ confidence == .match,
+ robustness.status == .robust,
+ kpi.status == .pass {
+ status = .healthy
+ } else {
+ status = .review
+ }
+ }
+
+ let issueCount = errors.count + warnings.count
+ var detailParts: [String] = []
+ detailParts.append("checks=\(checksCompleted)")
+ detailParts.append("errors=\(errors.count)")
+ detailParts.append("warnings=\(warnings.count)")
+ detailParts.append("robustness=\(robustness.status.title)")
+ detailParts.append("kpi=\(kpi.status.title)")
+ if let score = robustness.score {
+ detailParts.append(String(format: "robust_checks=%.1f%%", score))
+ }
+ let issuePreview = (errors + warnings).prefix(2).joined(separator: "; ")
+ if !issuePreview.isEmpty {
+ detailParts.append(issuePreview)
+ }
+
+ let recommendation = healthRecommendation(
+ validity: validity,
+ confidence: confidence,
+ robustness: robustness.status,
+ kpiStatus: kpi.status,
+ errors: errors,
+ warnings: warnings
+ )
+
+ let successRationale: String?
+ if status == .healthy {
+ successRationale = "State passed physical checks, runtime parity matched, KPI policy passed, and robustness checks stayed within policy thresholds."
+ } else if status == .review, validity == .valid, (confidence == .drift || confidence == .match) {
+ successRationale = "Core state is physically valid, but policy or robustness criteria indicate additional calibration work before production deployment."
+ } else {
+ successRationale = nil
+ }
+
+ return StateHealthSummary(
+ status: status,
+ validity: validity,
+ confidence: confidence,
+ robustness: robustness,
+ kpi: kpi,
+ checksCompleted: checksCompleted,
+ issueCount: issueCount,
+ errors: errors,
+ warnings: warnings,
+ recommendation: recommendation,
+ successRationale: successRationale,
+ detail: detailParts.joined(separator: " ")
+ )
+ }
+
+ private func buildStateRobustnessSummary(
+ baselineResponse: RunResponse,
+ context: RobustnessContext,
+ policy: KPIPolicyDefinition?
+ ) async -> StateRobustnessSummary {
+ guard let policy else {
+ return StateRobustnessSummary(
+ status: .unavailable,
+ score: nil,
+ sampleCount: 0,
+ successRate: nil,
+ meanPhotonP95Delta: nil,
+ stateP95Delta: nil,
+ measurementMismatchRate: nil,
+ checks: [],
+ detail: "Robustness probe unavailable: no KPI policy selected."
+ )
+ }
+
+ let hasConfiguredNoise = context.calibratedProfile != nil
+ || context.noiseModel.lossEtaSigma > 0
+ || context.noiseModel.thermalNoiseSigma > 0
+ || context.noiseModel.phaseSigmaRad > 0
+ || context.noiseModel.displacementSigma > 0
+
+ guard hasConfiguredNoise else {
+ return StateRobustnessSummary(
+ status: .unavailable,
+ score: nil,
+ sampleCount: 0,
+ successRate: nil,
+ meanPhotonP95Delta: nil,
+ stateP95Delta: nil,
+ measurementMismatchRate: nil,
+ checks: [],
+ detail: "Robustness probe unavailable: configure calibrated/noise model first."
+ )
+ }
+
+ guard !context.nodes.isEmpty else {
+ return StateRobustnessSummary(
+ status: .unavailable,
+ score: nil,
+ sampleCount: 0,
+ successRate: nil,
+ meanPhotonP95Delta: nil,
+ stateP95Delta: nil,
+ measurementMismatchRate: nil,
+ checks: [],
+ detail: "Robustness probe unavailable: canvas nodes required for perturbation."
+ )
+ }
+
+ let sampleCount = max(6, min(context.noiseModel.sampleCount, 24))
+ let baseSeed = resolvedBaseSeed(context.seed)
+ var rng: any RandomNumberGenerator = SeededGenerator(seed: baseSeed)
+ var successfulRuns = 0
+ var failedRuns = 0
+ var meanDeltas: [Double] = []
+ var stateDeltas: [Double] = []
+ var measurementMismatchCount = 0
+
+ meanDeltas.reserveCapacity(sampleCount)
+ stateDeltas.reserveCapacity(sampleCount)
+
+ for sampleIndex in 0.. Result in
+ do {
+ let runtimeURL = try CLIService.materializeRuntimeCircuitInput(
+ nodes: perturbedNodes,
+ modeCount: context.modeCount,
+ backend: context.backend,
+ seed: sampleSeed,
+ cutoff: context.cutoff,
+ schemaVersion: context.schemaVersion,
+ foundryProfile: context.foundryProfile,
+ environment: context.environment,
+ foundryProfileID: context.foundryProfileID,
+ foundryProfileVersion: context.foundryProfileVersion
+ )
+ let response = try CLIService.runCircuit(
+ jsonPath: runtimeURL.path,
+ backend: context.backend,
+ environment: context.environment,
+ runtimeEngine: context.runtimeEngine
+ )
+ return .success(response)
+ } catch {
+ return .failure(error)
+ }
+ }.value
+
+ switch sampleResult {
+ case .success(let response):
+ guard response.status.lowercased() == "success" else {
+ failedRuns += 1
+ continue
+ }
+ successfulRuns += 1
+
+ if let delta = absoluteDelta(response.meanPhotonNumber, baselineResponse.meanPhotonNumber),
+ delta.isFinite {
+ meanDeltas.append(delta)
+ }
+ if let delta = maxStateDelta(response.finalState, baselineResponse.finalState),
+ delta.isFinite {
+ stateDeltas.append(delta)
+ }
+ if let lhs = response.measurementCount, let rhs = baselineResponse.measurementCount, lhs != rhs {
+ measurementMismatchCount += 1
+ }
+ case .failure:
+ failedRuns += 1
+ }
+ }
+
+ let successRate = sampleCount > 0 ? Double(successfulRuns) / Double(sampleCount) : nil
+ let mismatchRate = successfulRuns > 0 ? Double(measurementMismatchCount) / Double(successfulRuns) : nil
+ let meanP95 = percentile(meanDeltas, fraction: 0.95)
+ let stateP95 = percentile(stateDeltas, fraction: 0.95)
+
+ var checks: [ThresholdCheckResult] = []
+ checks.append(
+ lowerBoundCheck(
+ key: "robust_success_rate",
+ title: "Monte Carlo success rate",
+ value: successRate,
+ minAllowed: policy.thresholds.minSuccessRate,
+ noteIfMissing: "No successful robustness samples were produced."
+ )
+ )
+ checks.append(
+ upperBoundCheck(
+ key: "robust_mean_p95",
+ title: "P95 mean photon delta",
+ value: meanP95,
+ maxAllowed: policy.thresholds.maxMeanPhotonP95Delta,
+ noteIfMissing: "mean_photon_number deltas unavailable."
+ )
+ )
+ checks.append(
+ upperBoundCheck(
+ key: "robust_state_p95",
+ title: "P95 state delta",
+ value: stateP95,
+ maxAllowed: policy.thresholds.maxStateP95Delta,
+ noteIfMissing: "state delta unavailable."
+ )
+ )
+ checks.append(
+ upperBoundCheck(
+ key: "robust_meas_mismatch",
+ title: "Measurement mismatch rate",
+ value: mismatchRate,
+ maxAllowed: policy.thresholds.maxMeasurementMismatchRate,
+ noteIfMissing: "measurement mismatch rate unavailable."
+ )
+ )
+
+ let missingRequiredMetric = checks.contains { $0.value == nil }
+ let passedCount = checks.filter(\.passed).count
+ let score = checks.isEmpty ? nil : (100.0 * Double(passedCount) / Double(checks.count))
+
+ let status: StateRobustnessStatus
+ if missingRequiredMetric {
+ status = .unavailable
+ } else if passedCount == checks.count {
+ status = .robust
+ } else if passedCount > 0 {
+ status = .sensitive
+ } else {
+ status = .fragile
+ }
+
+ var detailParts: [String] = []
+ detailParts.append(context.calibratedProfile == nil ? "model=noise" : "model=calibrated")
+ detailParts.append("policy=\(policy.id):v\(policy.version)")
+ detailParts.append("samples=\(sampleCount)")
+ detailParts.append("success=\(successfulRuns)")
+ detailParts.append("failed=\(failedRuns)")
+ if let successRate {
+ detailParts.append(String(format: "success_rate=%.3f", successRate))
+ }
+ if let meanP95 {
+ detailParts.append(String(format: "p95_d_mean=%.4e", meanP95))
+ }
+ if let stateP95 {
+ detailParts.append(String(format: "p95_d_state=%.4e", stateP95))
+ }
+ if let mismatchRate {
+ detailParts.append(String(format: "meas_mismatch=%.3f", mismatchRate))
+ }
+ if let score {
+ detailParts.append(String(format: "pass_rate=%.1f%%", score))
+ }
+
+ return StateRobustnessSummary(
+ status: status,
+ score: score,
+ sampleCount: sampleCount,
+ successRate: successRate,
+ meanPhotonP95Delta: meanP95,
+ stateP95Delta: stateP95,
+ measurementMismatchRate: mismatchRate,
+ checks: checks,
+ detail: detailParts.joined(separator: " ")
+ )
+ }
+
+ private func buildKPIPolicyEvaluationSummary(
+ runResponse: RunResponse,
+ policy: KPIPolicyDefinition?
+ ) -> KPIPolicyEvaluationSummary {
+ guard let policy else {
+ return KPIPolicyEvaluationSummary(
+ policyID: "none",
+ policyName: "No policy selected",
+ policyVersion: 0,
+ status: .unavailable,
+ checks: [],
+ recommendation: "Select a KPI policy to evaluate production readiness.",
+ detail: "KPI evaluation unavailable: no policy selected."
+ )
+ }
+
+ let representation = runResponse.finalState?.representation ?? "unavailable"
+ if policy.targetRepresentation != "any", policy.targetRepresentation != representation {
+ return KPIPolicyEvaluationSummary(
+ policyID: policy.id,
+ policyName: policy.name,
+ policyVersion: policy.version,
+ status: .unavailable,
+ checks: [],
+ recommendation: "Selected policy targets \(policy.targetRepresentation), but this run produced \(representation).",
+ detail: "KPI unavailable: representation mismatch."
+ )
+ }
+
+ let qecMetrics = qecMetricSnapshot(from: runResponse.qec)
+ var checks: [ThresholdCheckResult] = []
+ if let minPhoton = policy.thresholds.minMeanPhotonNumber {
+ checks.append(
+ lowerBoundCheck(
+ key: "kpi_min_photon",
+ title: "Mean photon number minimum",
+ value: runResponse.meanPhotonNumber,
+ minAllowed: minPhoton,
+ noteIfMissing: "mean_photon_number unavailable."
+ )
+ )
+ }
+ if let maxPhoton = policy.thresholds.maxMeanPhotonNumber {
+ checks.append(
+ upperBoundCheck(
+ key: "kpi_max_photon",
+ title: "Mean photon number maximum",
+ value: runResponse.meanPhotonNumber,
+ maxAllowed: maxPhoton,
+ noteIfMissing: "mean_photon_number unavailable."
+ )
+ )
+ }
+
+ if let minSqueezingDB = policy.thresholds.minSqueezingDB {
+ checks.append(
+ lowerBoundCheck(
+ key: "kpi_min_squeezing_db",
+ title: "Minimum mode squeezing (dB)",
+ value: runResponse.finalState.flatMap(gaussianMinModeSqueezingDB),
+ minAllowed: minSqueezingDB,
+ noteIfMissing: "Gaussian squeezing metric unavailable."
+ )
+ )
+ }
+
+ if let minPurity = policy.thresholds.minGaussianPurity {
+ checks.append(
+ lowerBoundCheck(
+ key: "kpi_min_gaussian_purity",
+ title: "Gaussian purity minimum",
+ value: runResponse.finalState.flatMap(gaussianPurity),
+ minAllowed: minPurity,
+ noteIfMissing: "Gaussian purity unavailable."
+ )
+ )
+ }
+
+ if let maxNormalizationError = policy.thresholds.maxFockNormalizationError {
+ checks.append(
+ upperBoundCheck(
+ key: "kpi_max_fock_norm_error",
+ title: "Fock normalization error",
+ value: runResponse.finalState.flatMap(fockNormalizationError),
+ maxAllowed: maxNormalizationError,
+ noteIfMissing: "Fock normalization metric unavailable."
+ )
+ )
+ }
+
+ if let maxLogicalErrorRate = policy.thresholds.maxLogicalErrorRate {
+ checks.append(
+ upperBoundCheck(
+ key: "kpi_qec_logical_error_rate",
+ title: "QEC logical error rate",
+ value: qecMetrics?.logicalErrorRate,
+ maxAllowed: maxLogicalErrorRate,
+ noteIfMissing: "QEC logical error rate unavailable."
+ )
+ )
+ }
+
+ if let minSuppressionFactor = policy.thresholds.minSuppressionFactor {
+ checks.append(
+ lowerBoundCheck(
+ key: "kpi_qec_suppression_factor",
+ title: "QEC suppression factor vs physical noise",
+ value: qecMetrics?.suppressionFactor,
+ minAllowed: minSuppressionFactor,
+ noteIfMissing: "QEC suppression factor unavailable."
+ )
+ )
+ }
+
+ if let minBreakEvenGain = policy.thresholds.minBreakEvenGain {
+ checks.append(
+ lowerBoundCheck(
+ key: "kpi_qec_break_even_gain",
+ title: "QEC break-even gain",
+ value: qecMetrics?.breakEvenGain,
+ minAllowed: minBreakEvenGain,
+ noteIfMissing: "QEC break-even gain unavailable."
+ )
+ )
+ }
+
+ let hasMissingMetric = checks.contains { $0.value == nil }
+ let passedCount = checks.filter(\.passed).count
+ let status: KPIPolicyStatus
+ if checks.isEmpty || hasMissingMetric {
+ status = .unavailable
+ } else if passedCount == checks.count {
+ status = .pass
+ } else {
+ status = .fail
+ }
+
+ let recommendation: String
+ switch status {
+ case .pass:
+ recommendation = "Circuit meets all selected KPI policy thresholds."
+ case .fail:
+ recommendation = "Adjust circuit parameters or calibration to satisfy failed KPI thresholds before production use."
+ case .unavailable:
+ recommendation = "Required KPI metrics are unavailable for this run/policy combination."
+ }
+
+ var detailParts: [String] = []
+ detailParts.append("policy=\(policy.id):v\(policy.version)")
+ detailParts.append("checks=\(checks.count)")
+ detailParts.append("passed=\(passedCount)")
+ detailParts.append("status=\(status.title)")
+ if let rounds = qecMetrics?.roundsExecuted {
+ detailParts.append("qec_rounds=\(rounds)")
+ }
+ if let logicalErrorRate = qecMetrics?.logicalErrorRate {
+ detailParts.append(String(format: "qec_ler=%.4f", logicalErrorRate))
+ }
+ if let suppressionFactor = qecMetrics?.suppressionFactor {
+ detailParts.append(String(format: "qec_suppr=%.3f", suppressionFactor))
+ }
+ if let breakEvenGain = qecMetrics?.breakEvenGain {
+ detailParts.append(String(format: "qec_break_even=%.4f", breakEvenGain))
+ }
+
+ return KPIPolicyEvaluationSummary(
+ policyID: policy.id,
+ policyName: policy.name,
+ policyVersion: policy.version,
+ status: status,
+ checks: checks,
+ recommendation: recommendation,
+ detail: detailParts.joined(separator: " ")
+ )
+ }
+
+ private struct QECMetricSnapshot {
+ let roundsExecuted: Int?
+ let logicalErrorRate: Double?
+ let physicalErrorRateProxy: Double?
+ let suppressionFactor: Double?
+ let breakEvenGain: Double?
+ }
+
+ private func qecMetricSnapshot(from report: QECReport?) -> QECMetricSnapshot? {
+ guard let report else { return nil }
+
+ let roundsExecuted = report.roundsExecuted ?? report.rounds?.count
+
+ let logicalErrorRate: Double? = {
+ if let value = finiteOrNil(report.logicalErrorRate) {
+ return value
+ }
+ guard let rounds = roundsExecuted, rounds > 0 else { return nil }
+ guard let logicalFailCount = report.logicalFailCount else { return nil }
+ return finiteOrNil(Double(logicalFailCount) / Double(rounds))
+ }()
+
+ let physicalErrorRateProxy: Double? = {
+ if let value = finiteOrNil(report.physicalErrorRateProxy) {
+ return value
+ }
+ guard let rounds = report.rounds, !rounds.isEmpty else { return nil }
+
+ var valid = 0
+ var flagged = 0
+ for round in rounds {
+ guard let syndrome = round.syndromeValue,
+ let latticeSpacing = round.latticeSpacing,
+ latticeSpacing > 0,
+ let targetIndex = round.targetLatticeIndex
+ else {
+ continue
+ }
+
+ valid += 1
+ let target = Double(targetIndex) * latticeSpacing
+ let threshold = 0.25 * latticeSpacing
+ if abs(syndrome - target) >= threshold {
+ flagged += 1
+ }
+ }
+
+ guard valid > 0 else { return nil }
+ return finiteOrNil(Double(flagged) / Double(valid))
+ }()
+
+ let suppressionFactor: Double? = {
+ if let value = finiteOrNil(report.suppressionFactor) {
+ return value
+ }
+ guard let physicalErrorRateProxy, physicalErrorRateProxy >= 0 else { return nil }
+ guard let logicalErrorRate, logicalErrorRate >= 0 else { return nil }
+ if logicalErrorRate == 0 {
+ return physicalErrorRateProxy > 0 ? 1_000_000.0 : nil
+ }
+ return finiteOrNil(physicalErrorRateProxy / logicalErrorRate)
+ }()
+
+ let breakEvenGain: Double? = {
+ if let value = finiteOrNil(report.breakEvenGain) {
+ return value
+ }
+ guard let physicalErrorRateProxy, let logicalErrorRate else { return nil }
+ return finiteOrNil(physicalErrorRateProxy - logicalErrorRate)
+ }()
+
+ return QECMetricSnapshot(
+ roundsExecuted: roundsExecuted,
+ logicalErrorRate: logicalErrorRate,
+ physicalErrorRateProxy: physicalErrorRateProxy,
+ suppressionFactor: suppressionFactor,
+ breakEvenGain: breakEvenGain
+ )
+ }
+
+ private func finiteOrNil(_ value: Double?) -> Double? {
+ guard let value, value.isFinite else { return nil }
+ return value
+ }
+
+ private func healthRecommendation(
+ validity: StateValidityStatus,
+ confidence: StateConfidenceStatus,
+ robustness: StateRobustnessStatus,
+ kpiStatus: KPIPolicyStatus,
+ errors: [String],
+ warnings: [String]
+ ) -> String {
+ if !errors.isEmpty {
+ return "Fix state validity errors first, then rerun parity and KPI/robustness checks."
+ }
+ if kpiStatus == .fail {
+ return "KPI policy failed. Tune circuit parameters and recalibrate hardware until every KPI threshold passes."
+ }
+ if confidence == .unmatched {
+ return "Runtime outputs diverge beyond tolerance; align backend support, seed/cutoff settings, and gate compatibility."
+ }
+ if robustness == .fragile {
+ return "Robustness thresholds failed under calibrated loss/noise; improve noise margin before production rollout."
+ }
+ if robustness == .sensitive {
+ return "Robustness is partially compliant; investigate failed robustness thresholds and rerun calibrated Monte Carlo."
+ }
+ if validity == .partial || !warnings.isEmpty {
+ return "Resolve remaining warnings for stronger production confidence."
+ }
+ if confidence == .drift {
+ return "Minor runtime drift detected; acceptable for R&D, but recheck precision before production promotion."
+ }
+ if kpiStatus == .unavailable {
+ return "KPI metrics are incomplete for the selected policy; ensure representation and outputs provide required observables."
+ }
+ return "State is physically valid, policy-compliant, and robust under configured calibrated noise."
+ }
+
+ private func lowerBoundCheck(
+ key: String,
+ title: String,
+ value: Double?,
+ minAllowed: Double,
+ noteIfMissing: String
+ ) -> ThresholdCheckResult {
+ guard let value, value.isFinite else {
+ return ThresholdCheckResult(
+ key: key,
+ title: title,
+ passed: false,
+ value: nil,
+ threshold: minAllowed,
+ comparator: ">=",
+ note: noteIfMissing
+ )
+ }
+
+ return ThresholdCheckResult(
+ key: key,
+ title: title,
+ passed: value >= minAllowed,
+ value: value,
+ threshold: minAllowed,
+ comparator: ">=",
+ note: nil
+ )
+ }
+
+ private func upperBoundCheck(
+ key: String,
+ title: String,
+ value: Double?,
+ maxAllowed: Double,
+ noteIfMissing: String
+ ) -> ThresholdCheckResult {
+ guard let value, value.isFinite else {
+ return ThresholdCheckResult(
+ key: key,
+ title: title,
+ passed: false,
+ value: nil,
+ threshold: maxAllowed,
+ comparator: "<=",
+ note: noteIfMissing
+ )
+ }
+
+ return ThresholdCheckResult(
+ key: key,
+ title: title,
+ passed: value <= maxAllowed,
+ value: value,
+ threshold: maxAllowed,
+ comparator: "<=",
+ note: nil
+ )
+ }
+
+ private func gaussianMinModeSqueezingDB(_ state: QuantumStateSnapshot) -> Double? {
+ guard state.representation == "gaussian_phase_space", let covariance = state.covariance else { return nil }
+ guard !covariance.isEmpty, isSquareMatrix(covariance) else { return nil }
+
+ let modeCount = covariance.count / 2
+ guard modeCount > 0 else { return nil }
+ var minSqueezingDB: Double?
+
+ for mode in 0.. Double? {
+ guard state.representation == "gaussian_phase_space", let covariance = state.covariance else { return nil }
+ guard !covariance.isEmpty, isSquareMatrix(covariance) else { return nil }
+ let dimension = covariance.count
+ var twoV = covariance
+ for row in 0.. 0, det.isFinite else { return nil }
+ let purity = 1.0 / sqrt(det)
+ return purity.isFinite ? purity : nil
+ }
+
+ private func fockNormalizationError(_ state: QuantumStateSnapshot) -> Double? {
+ guard state.representation == "fock_number_basis", let probabilities = state.probabilities else { return nil }
+ guard !probabilities.isEmpty else { return nil }
+ let total = probabilities.reduce(0.0, +)
+ return abs(total - 1.0)
+ }
+
+ private func determinant(_ matrix: [[Double]]) -> Double? {
+ guard !matrix.isEmpty, isSquareMatrix(matrix) else { return nil }
+ let n = matrix.count
+ var a = matrix
+ var sign = 1.0
+
+ for pivot in 0.. maxAbs {
+ maxAbs = value
+ maxRow = row
+ }
+ }
+ if maxAbs < 1e-18 {
+ return 0.0
+ }
+ if maxRow != pivot {
+ a.swapAt(maxRow, pivot)
+ sign *= -1.0
+ }
+
+ let pivotValue = a[pivot][pivot]
+ for row in (pivot + 1).. Double? {
+ guard !values.isEmpty else { return nil }
+ let sorted = values.sorted()
+ let clamped = min(max(fraction, 0.0), 1.0)
+ let position = clamped * Double(sorted.count - 1)
+ let lowerIndex = Int(floor(position))
+ let upperIndex = Int(ceil(position))
+ if lowerIndex == upperIndex {
+ return sorted[lowerIndex]
+ }
+ let weight = position - Double(lowerIndex)
+ return sorted[lowerIndex] * (1.0 - weight) + sorted[upperIndex] * weight
+ }
+
+ private func evaluateGaussianStateHealth(
+ _ state: QuantumStateSnapshot,
+ checksCompleted: inout Int,
+ errors: inout [String],
+ warnings: inout [String]
+ ) {
+ checksCompleted += 1
+ guard let mean = state.mean, !mean.isEmpty else {
+ errors.append("Gaussian mean vector unavailable")
+ return
+ }
+ if !allFinite(mean) {
+ errors.append("Gaussian mean has NaN/Inf")
+ }
+
+ checksCompleted += 1
+ guard let covariance = state.covariance, !covariance.isEmpty else {
+ errors.append("Gaussian covariance unavailable")
+ return
+ }
+ if !isSquareMatrix(covariance) {
+ errors.append("Gaussian covariance is not square")
+ return
+ }
+ if !allFinite(covariance.flatMap { $0 }) {
+ errors.append("Gaussian covariance has NaN/Inf")
+ return
+ }
+
+ checksCompleted += 1
+ if let modes = state.modes, modes > 0 {
+ let expectedDim = modes * 2
+ if mean.count != expectedDim {
+ warnings.append("Gaussian mean dimension mismatch")
+ }
+ if covariance.count != expectedDim {
+ warnings.append("Gaussian covariance dimension mismatch")
+ }
+ }
+
+ checksCompleted += 1
+ if covariance.indices.contains(where: { covariance[$0][$0] < -1e-9 }) {
+ errors.append("Gaussian covariance has negative diagonal entries")
+ }
+
+ checksCompleted += 1
+ let asymmetry = matrixAsymmetryMax(covariance)
+ if asymmetry > 1e-3 {
+ errors.append("Gaussian covariance asymmetry too large")
+ } else if asymmetry > 1e-6 {
+ warnings.append("Gaussian covariance asymmetry above nominal tolerance")
+ }
+ }
+
+ private func evaluateFockStateHealth(
+ _ state: QuantumStateSnapshot,
+ checksCompleted: inout Int,
+ errors: inout [String],
+ warnings: inout [String]
+ ) {
+ checksCompleted += 1
+ guard let probabilities = state.probabilities, !probabilities.isEmpty else {
+ errors.append("Fock probabilities unavailable")
+ return
+ }
+ if !allFinite(probabilities) {
+ errors.append("Fock probabilities contain NaN/Inf")
+ return
+ }
+
+ checksCompleted += 1
+ if probabilities.contains(where: { $0 < -1e-9 || $0 > 1.0 + 1e-9 }) {
+ errors.append("Fock probabilities outside [0, 1]")
+ }
+
+ checksCompleted += 1
+ let total = probabilities.reduce(0.0, +)
+ if abs(total - 1.0) > 1e-3 {
+ errors.append(String(format: "Fock normalization drift is %.3e", abs(total - 1.0)))
+ } else if abs(total - 1.0) > 1e-6 {
+ warnings.append(String(format: "Fock normalization near tolerance (%.3e)", abs(total - 1.0)))
+ }
+ }
+
+ private func allFinite(_ values: [Double]) -> Bool {
+ values.allSatisfy { $0.isFinite }
+ }
+
+ private func isSquareMatrix(_ matrix: [[Double]]) -> Bool {
+ guard !matrix.isEmpty else { return false }
+ let dimension = matrix.count
+ return matrix.allSatisfy { $0.count == dimension }
+ }
+
+ private func matrixAsymmetryMax(_ matrix: [[Double]]) -> Double {
+ guard isSquareMatrix(matrix) else { return .infinity }
+ let dimension = matrix.count
+ var maxAsymmetry = 0.0
+ for row in 0.. Double? {
+ guard let lhs, let rhs else { return nil }
+ return abs(lhs - rhs)
+ }
+
+ private func delta(_ lhs: Int?, _ rhs: Int?) -> Int? {
+ guard let lhs, let rhs else { return nil }
+ return lhs - rhs
+ }
+
+ private func maxStateDelta(_ lhs: QuantumStateSnapshot?, _ rhs: QuantumStateSnapshot?) -> Double? {
+ guard let lhs, let rhs else { return nil }
+ guard lhs.representation == rhs.representation else { return nil }
+
+ switch lhs.representation {
+ case "gaussian_phase_space":
+ guard let lhsMean = lhs.mean, let rhsMean = rhs.mean,
+ let lhsCov = lhs.covariance, let rhsCov = rhs.covariance
+ else {
+ return nil
+ }
+ let meanDelta = maxAbsDelta(lhsMean, rhsMean)
+ let covDelta = maxAbsDelta(lhsCov.flatMap { $0 }, rhsCov.flatMap { $0 })
+ return max(meanDelta ?? 0.0, covDelta ?? 0.0)
+ case "fock_number_basis":
+ guard let lhsProb = lhs.probabilities, let rhsProb = rhs.probabilities else { return nil }
+ return maxAbsDelta(lhsProb, rhsProb)
+ default:
+ return nil
+ }
+ }
+
+ private func maxAbsDelta(_ lhs: [Double], _ rhs: [Double]) -> Double? {
+ guard lhs.count == rhs.count, !lhs.isEmpty else { return nil }
+ var maxDelta = 0.0
+ for index in lhs.indices {
+ maxDelta = max(maxDelta, abs(lhs[index] - rhs[index]))
+ }
+ return maxDelta
+ }
+}
diff --git a/core-swift/Sources/schrosim-studio/StudioViews.swift b/core-swift/Sources/schrosim-studio/StudioViews.swift
new file mode 100644
index 0000000..0249000
--- /dev/null
+++ b/core-swift/Sources/schrosim-studio/StudioViews.swift
@@ -0,0 +1,4059 @@
+import SwiftUI
+import AppKit
+
+private struct ThemePalette {
+ let appBackground: Color
+ let panel: Color
+ let subpanel: Color
+ let inputBackground: Color
+ let border: Color
+ let dashedBorder: Color
+ let gridMinor: Color
+ let gridMajor: Color
+
+ static func resolve(_ theme: AppTheme) -> ThemePalette {
+ switch theme {
+ case .white:
+ return ThemePalette(
+ appBackground: Color(red: 0.945, green: 0.952, blue: 0.965),
+ panel: .white,
+ subpanel: Color(red: 0.965, green: 0.972, blue: 0.985),
+ inputBackground: .white,
+ border: Color.black.opacity(0.10),
+ dashedBorder: Color.blue.opacity(0.45),
+ gridMinor: Color.black.opacity(0.04),
+ gridMajor: Color.black.opacity(0.08)
+ )
+ case .black:
+ return ThemePalette(
+ appBackground: Color(red: 0.07, green: 0.08, blue: 0.10),
+ panel: Color(red: 0.10, green: 0.11, blue: 0.14),
+ subpanel: Color(red: 0.13, green: 0.15, blue: 0.19),
+ inputBackground: Color(red: 0.15, green: 0.17, blue: 0.21),
+ border: Color.white.opacity(0.16),
+ dashedBorder: Color.blue.opacity(0.60),
+ gridMinor: Color.white.opacity(0.05),
+ gridMajor: Color.white.opacity(0.12)
+ )
+ }
+ }
+}
+
+struct StudioAppShellView: View {
+ @EnvironmentObject private var store: StudioStore
+ private var palette: ThemePalette { .resolve(store.theme) }
+
+ var body: some View {
+ VStack(spacing: 0) {
+ GlobalTopBarView()
+ Divider()
+
+ HSplitView {
+ LeftRailView()
+ .frame(minWidth: 220, idealWidth: 280, maxWidth: 340)
+ .layoutPriority(1)
+
+ CenterWorkspaceView()
+ .frame(minWidth: 720, maxWidth: .infinity, maxHeight: .infinity)
+ .layoutPriority(0)
+
+ RightInspectorView()
+ .frame(minWidth: 320, idealWidth: 380, maxWidth: 460)
+ .layoutPriority(2)
+ }
+ .frame(minHeight: 420)
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
+
+ Divider()
+ BottomPanelView()
+ .frame(height: 220)
+ }
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
+ .font(.system(size: 13, weight: .regular, design: .default))
+ .background(palette.appBackground.ignoresSafeArea())
+ .sheet(isPresented: $store.isAnalysisPopupPresented) {
+ AnalysisPopupWindowView()
+ .environmentObject(store)
+ .frame(minWidth: 1160, minHeight: 760)
+ }
+ .alert("Error, check your gates.", isPresented: $store.isRunBlockedAlertPresented) {
+ Button("OK", role: .cancel) {}
+ } message: {
+ Text(store.runBlockedAlertMessage)
+ }
+ }
+}
+
+private struct GlobalTopBarView: View {
+ @EnvironmentObject private var store: StudioStore
+ @State private var availableWidth: CGFloat = 0
+ private var palette: ThemePalette { .resolve(store.theme) }
+ private let horizontalInset: CGFloat = 14
+
+ var body: some View {
+ ScrollView(.horizontal, showsIndicators: false) {
+ VStack(alignment: .leading, spacing: 10) {
+ HStack(spacing: 10) {
+ Label("SchroSIM Intelligence", systemImage: "square.grid.2x2")
+ .font(.title3.weight(.semibold))
+ Text("| Photonic Quantum Circuit Simulator")
+ .font(.headline)
+ .foregroundStyle(.secondary)
+ Spacer()
+ Picker("Theme", selection: $store.theme) {
+ ForEach(AppTheme.allCases) { theme in
+ Text(theme.rawValue).tag(theme)
+ }
+ }
+ .pickerStyle(.segmented)
+ .frame(width: 170)
+ .labelsHidden()
+
+ HStack(spacing: 8) {
+ Button("New") { store.newAction() }
+ .keyboardShortcut("n", modifiers: [.command])
+ Button("Open") { store.openAction() }
+ .keyboardShortcut("o", modifiers: [.command])
+ Button("Save") { store.saveAction() }
+ .keyboardShortcut("s", modifiers: [.command])
+ Button("Save As") { store.saveAsAction() }
+ .keyboardShortcut("s", modifiers: [.command, .shift])
+ }
+ }
+
+ HStack(alignment: .bottom, spacing: 12) {
+ LabeledContentBlock(title: "Workspace") {
+ Picker("Workspace", selection: $store.workspace) {
+ ForEach(store.workspaces, id: \.self) { value in
+ Text(value).tag(value)
+ }
+ }
+ .pickerStyle(.menu)
+ .labelsHidden()
+ .frame(width: 210, alignment: .leading)
+ }
+
+ LabeledContentBlock(title: "Project") {
+ Picker("Project", selection: $store.project) {
+ ForEach(store.projects, id: \.self) { value in
+ Text(value).tag(value)
+ }
+ }
+ .pickerStyle(.menu)
+ .labelsHidden()
+ .frame(width: 210, alignment: .leading)
+ }
+
+ LabeledContentBlock(title: "Environment") {
+ Picker("Environment", selection: $store.environment) {
+ ForEach(EnvironmentBadge.allCases) { value in
+ Text(value.rawValue.uppercased()).tag(value)
+ }
+ }
+ .pickerStyle(.menu)
+ .labelsHidden()
+ .frame(width: 120, alignment: .leading)
+ }
+
+ LabeledContentBlock(title: "Role") {
+ Picker("Role", selection: $store.userRole) {
+ ForEach(UserRoleBadge.allCases) { role in
+ Text(role.rawValue).tag(role)
+ }
+ }
+ .pickerStyle(.menu)
+ .labelsHidden()
+ .frame(width: 105, alignment: .leading)
+ }
+
+ Spacer(minLength: 0)
+
+ HStack(spacing: 8) {
+ Button("Validate") { store.validateAction() }
+ Button("Compile") { store.compileAction() }
+ Button("Run") { Task { await store.runAction() } }
+ .buttonStyle(.borderedProminent)
+ .disabled(store.isRunning)
+ Button("Analysis") { store.analyzeAction() }
+ .buttonStyle(.bordered)
+ Button("Delete") { store.deleteSelection() }
+ .buttonStyle(.bordered)
+ .keyboardShortcut(.delete, modifiers: [])
+ .disabled(!store.canDeleteSelection)
+ Button("Export") { store.exportAction() }
+ RuntimePhaseIndicatorView()
+ .environmentObject(store)
+ .padding(.leading, 6)
+ }
+ .frame(maxWidth: .infinity, alignment: .trailing)
+ .padding(.trailing, 2)
+ }
+ }
+ .frame(minWidth: max(0, availableWidth - (horizontalInset * 2)), alignment: .leading)
+ }
+ .padding(.horizontal, horizontalInset)
+ .padding(.vertical, 10)
+ .background(
+ GeometryReader { proxy in
+ Color.clear
+ .onAppear {
+ availableWidth = proxy.size.width
+ }
+ .onChange(of: proxy.size.width) { newValue in
+ availableWidth = newValue
+ }
+ }
+ )
+ .background(palette.panel)
+ }
+}
+
+private struct RuntimePhaseIndicatorView: View {
+ @EnvironmentObject private var store: StudioStore
+
+ private var phase: RuntimePhase {
+ store.runtimePhase
+ }
+
+ private var isFlashing: Bool {
+ phase == .compiling || phase == .running
+ }
+
+ private var phaseColor: Color {
+ switch phase {
+ case .standby:
+ return Color.gray
+ case .compiling:
+ return Color.orange
+ case .running:
+ return Color.blue
+ case .finished:
+ return Color.green
+ }
+ }
+
+ var body: some View {
+ HStack(spacing: 7) {
+ TimelineView(.animation(minimumInterval: 0.12, paused: !isFlashing)) { timeline in
+ let now = timeline.date.timeIntervalSinceReferenceDate
+ let oscillation = (sin(now * 7.0) + 1.0) * 0.5
+ let opacity = isFlashing ? (0.35 + oscillation * 0.65) : 1.0
+ let scale = isFlashing ? (0.90 + oscillation * 0.20) : 1.0
+
+ Circle()
+ .fill(phaseColor)
+ .frame(width: 9, height: 9)
+ .scaleEffect(scale)
+ .opacity(opacity)
+ }
+ .frame(width: 11, height: 11)
+
+ Text(phase.title)
+ .font(.caption.weight(.semibold))
+ .foregroundStyle(.primary)
+ }
+ .padding(.horizontal, 8)
+ .padding(.vertical, 4)
+ .background(
+ RoundedRectangle(cornerRadius: 8)
+ .fill(phaseColor.opacity(0.12))
+ )
+ .overlay(
+ RoundedRectangle(cornerRadius: 8)
+ .stroke(phaseColor.opacity(0.30), lineWidth: 1)
+ )
+ .help("Runtime state: \(phase.title)")
+ }
+}
+
+private struct LeftRailView: View {
+ @EnvironmentObject private var store: StudioStore
+ @State private var hoveredKind: GateKind?
+ private var palette: ThemePalette { .resolve(store.theme) }
+ private var iconCoverageText: String {
+ let total = GateKind.allCases.count
+ let mapped = GateKind.allCases.filter { !$0.systemIcon.isEmpty }.count
+ return "Icons \(mapped)/\(total)"
+ }
+ private var categorizedKinds: [(section: PaletteSection, items: [GateKind])] {
+ PaletteSection.allCases.compactMap { section in
+ let items = GateKind.allCases.filter { kind in
+ kind.section == section && store.matchesSearch(kind) && store.matchesPaletteFilter(kind)
+ }
+ return items.isEmpty ? nil : (section, items)
+ }
+ }
+
+ private func sectionTitle(_ section: PaletteSection) -> String {
+ switch section {
+ case .sourceAndStatePrep:
+ return "Source & State Prep"
+ case .gaussianOps:
+ return "Gaussian Ops"
+ case .nonGaussianOps:
+ return "Non-Gaussian Ops"
+ case .channels:
+ return "Channels"
+ case .measurements:
+ return "Measurements"
+ case .classicalControl:
+ return "Control"
+ }
+ }
+
+ private var primaryPaletteFilters: [PaletteFilter] {
+ [.all, .gaussian, .nonGaussian, .channels]
+ }
+
+ private var secondaryPaletteFilters: [PaletteFilter] {
+ [.measurement, .control]
+ }
+
+ private func paletteFilterChip(_ filter: PaletteFilter) -> some View {
+ let isSelected = filter == store.paletteFilter
+ return Button(filter.rawValue) {
+ store.paletteFilter = filter
+ }
+ .buttonStyle(.plain)
+ .font(.caption.weight(.semibold))
+ .foregroundStyle(isSelected ? Color.white : Color.primary)
+ .padding(.horizontal, 10)
+ .padding(.vertical, 5)
+ .background(
+ Capsule()
+ .fill(isSelected ? Color.accentColor : palette.inputBackground)
+ .overlay(
+ Capsule()
+ .stroke(palette.border, lineWidth: isSelected ? 0 : 1)
+ )
+ )
+ }
+
+ var body: some View {
+ CardSection {
+ VStack(alignment: .leading, spacing: 10) {
+ HStack(spacing: 6) {
+ Image(systemName: "magnifyingglass")
+ .foregroundStyle(.secondary)
+ TextField("Search", text: $store.paletteSearch)
+ .textFieldStyle(.plain)
+ .font(.caption)
+ }
+ .padding(.horizontal, 8)
+ .padding(.vertical, 7)
+ .background(
+ RoundedRectangle(cornerRadius: 8)
+ .fill(palette.inputBackground)
+ .overlay(
+ RoundedRectangle(cornerRadius: 8)
+ .stroke(palette.border, lineWidth: 1)
+ )
+ )
+
+ VStack(alignment: .leading, spacing: 6) {
+ HStack(spacing: 6) {
+ ForEach(primaryPaletteFilters) { filter in
+ paletteFilterChip(filter)
+ }
+ }
+
+ HStack(spacing: 6) {
+ ForEach(secondaryPaletteFilters) { filter in
+ paletteFilterChip(filter)
+ }
+ }
+ }
+ .padding(.vertical, 2)
+
+ ScrollView {
+ VStack(alignment: .leading, spacing: 10) {
+ ForEach(categorizedKinds, id: \.section) { group in
+ VStack(alignment: .leading, spacing: 8) {
+ HStack {
+ Text(sectionTitle(group.section))
+ .font(.caption.weight(.semibold))
+ .foregroundStyle(.secondary)
+ Spacer()
+ Text("\(group.items.count)")
+ .font(.caption2.monospacedDigit())
+ .padding(.horizontal, 6)
+ .padding(.vertical, 2)
+ .background(
+ Capsule()
+ .fill(palette.inputBackground)
+ )
+ }
+
+ LazyVGrid(
+ columns: [GridItem(.adaptive(minimum: 44, maximum: 44), spacing: 8)],
+ alignment: .leading,
+ spacing: 8
+ ) {
+ ForEach(group.items) { kind in
+ PaletteIconCell(
+ kind: kind,
+ disabledReason: store.disabledReason(for: kind),
+ theme: store.theme
+ ) { isHovering in
+ if isHovering {
+ hoveredKind = kind
+ } else if hoveredKind == kind {
+ hoveredKind = nil
+ }
+ }
+ }
+ }
+ }
+ .padding(8)
+ .background(
+ RoundedRectangle(cornerRadius: 10)
+ .fill(palette.subpanel)
+ .overlay(
+ RoundedRectangle(cornerRadius: 10)
+ .stroke(palette.border, lineWidth: 1)
+ )
+ )
+ }
+ }
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .padding(.vertical, 4)
+ }
+
+ Text(hoveredKind?.hoverDisplayName ?? "Hover icons for descriptions")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ .multilineTextAlignment(.leading)
+ .frame(maxWidth: .infinity, minHeight: 32)
+
+ Text(iconCoverageText)
+ .font(.caption2)
+ .foregroundStyle(.secondary)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
+ }
+ }
+}
+
+private struct PaletteIconCell: View {
+ let kind: GateKind
+ let disabledReason: String?
+ let theme: AppTheme
+ let onHoverState: (Bool) -> Void
+ private var palette: ThemePalette { .resolve(theme) }
+
+ var body: some View {
+ let content = VStack(spacing: 4) {
+ Image(systemName: kind.systemIcon)
+ .font(.system(size: 16, weight: .semibold))
+ Text(kind.symbol)
+ .font(.system(size: 10, weight: .semibold, design: .monospaced))
+ .lineLimit(1)
+ .minimumScaleFactor(0.7)
+ }
+ .frame(width: 44, height: 44)
+ .background(
+ RoundedRectangle(cornerRadius: 10)
+ .fill(palette.inputBackground)
+ .overlay(
+ RoundedRectangle(cornerRadius: 10)
+ .stroke(
+ disabledReason == nil ? Color.blue.opacity(0.50) : Color.gray.opacity(0.35),
+ style: StrokeStyle(lineWidth: 1, dash: disabledReason == nil ? [5, 3] : [])
+ )
+ )
+ )
+ .opacity(disabledReason == nil ? 1.0 : 0.45)
+ .help(disabledReason ?? kind.hoverDisplayName)
+ .onHover { isHovering in
+ onHoverState(isHovering)
+ }
+
+ if disabledReason == nil {
+ content.draggable(kind.rawValue)
+ } else {
+ content
+ }
+ }
+}
+
+private struct CenterWorkspaceView: View {
+ @EnvironmentObject private var store: StudioStore
+ @State private var zoom: CGFloat = 1.0
+ @State private var committedZoom: CGFloat = 1.0
+ @State private var panOffset: CGSize = .zero
+ @State private var committedPanOffset: CGSize = .zero
+ private var palette: ThemePalette { .resolve(store.theme) }
+ private let slotWidth: CGFloat = 140
+ private let slotHeight: CGFloat = 68
+ private let laneLabelWidth: CGFloat = 110
+ private let rowSpacing: CGFloat = 10
+ private let columnSpacing: CGFloat = 8
+ private let canvasPadding: CGFloat = 10
+ private let timelineHeaderHeight: CGFloat = 24
+ private let minZoom: CGFloat = 0.6
+ private let maxZoom: CGFloat = 2.4
+
+ var body: some View {
+ CardSection(title: "WorkSpace") {
+ VStack(alignment: .leading, spacing: 10) {
+ HStack(spacing: 14) {
+ Toggle("Grid", isOn: $store.showGrid)
+ Toggle("Connector", isOn: $store.showConnectors)
+ Toggle("Photon Live", isOn: $store.showPhotonFlow)
+ Toggle("Selection", isOn: $store.showSelection)
+ Toggle("Validation", isOn: $store.showValidation)
+ Spacer()
+ Text("Zoom \(Int(zoom * 100))%")
+ .font(.caption.monospacedDigit())
+ .foregroundStyle(.secondary)
+ Button("Reset View") {
+ resetView()
+ }
+ .buttonStyle(.bordered)
+ }
+ .toggleStyle(.checkbox)
+ .font(.caption)
+
+ GeometryReader { proxy in
+ let viewportSize = proxy.size
+ let canvasSize = contentSize(for: viewportSize)
+ let laneTopInset = canvasPadding + timelineHeaderHeight
+
+ ZStack(alignment: .topLeading) {
+ RoundedRectangle(cornerRadius: 10)
+ .fill(palette.subpanel)
+ .overlay(
+ RoundedRectangle(cornerRadius: 10)
+ .stroke(palette.border, lineWidth: 1)
+ )
+
+ if store.showGrid {
+ InfiniteMetricGridBackground(
+ theme: store.theme,
+ zoom: zoom,
+ panOffset: panOffset
+ )
+ .clipShape(RoundedRectangle(cornerRadius: 10))
+ .allowsHitTesting(false)
+ }
+
+ ZStack(alignment: .topLeading) {
+ SlotTimelineReferenceView(
+ slotCount: store.slotCount,
+ laneLabelWidth: laneLabelWidth,
+ slotWidth: slotWidth,
+ columnSpacing: columnSpacing,
+ leadingInset: canvasPadding,
+ topInset: canvasPadding
+ )
+
+ if store.showConnectors || store.showPhotonFlow {
+ ConnectorOverlayView(
+ modeCount: store.modeCount,
+ slotCount: store.slotCount,
+ laneLabelWidth: laneLabelWidth,
+ slotWidth: slotWidth,
+ slotHeight: slotHeight,
+ leadingInset: canvasPadding,
+ topInset: laneTopInset,
+ rowSpacing: rowSpacing,
+ columnSpacing: columnSpacing,
+ nodes: store.nodes,
+ zoom: zoom,
+ showConnectors: store.showConnectors,
+ showPhotonFlow: store.showPhotonFlow
+ )
+ }
+
+ if store.showPhotonFlow {
+ PhotonMechanicalGateOverlayView(
+ modeCount: store.modeCount,
+ slotCount: store.slotCount,
+ laneLabelWidth: laneLabelWidth,
+ slotWidth: slotWidth,
+ slotHeight: slotHeight,
+ topInset: laneTopInset,
+ rowSpacing: rowSpacing,
+ columnSpacing: columnSpacing,
+ leadingInset: canvasPadding,
+ nodes: store.nodes,
+ zoom: zoom
+ )
+ }
+
+ VStack(alignment: .leading, spacing: rowSpacing) {
+ ForEach(0..")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+ .padding(.horizontal, 6)
+ .padding(.vertical, 6)
+ .frame(width: laneLabelWidth, alignment: .leading)
+ .background(
+ RoundedRectangle(cornerRadius: 8)
+ .fill(
+ (store.selectedModeIndex == mode && store.selectedNodeID == nil)
+ ? Color.accentColor.opacity(0.18)
+ : Color.clear
+ )
+ )
+ .overlay(
+ RoundedRectangle(cornerRadius: 8)
+ .stroke(
+ (store.selectedModeIndex == mode && store.selectedNodeID == nil)
+ ? Color.accentColor.opacity(0.70)
+ : Color.clear,
+ lineWidth: 1
+ )
+ )
+ .contentShape(RoundedRectangle(cornerRadius: 8))
+ .onTapGesture {
+ store.selectMode(mode)
+ }
+
+ if store.showPhotonFlow {
+ Color.clear
+ .frame(width: slotTrackWidth, height: slotHeight)
+ } else {
+ ForEach(0.. CGSize {
+ let lanesWidth = laneLabelWidth
+ + columnSpacing
+ + CGFloat(store.slotCount) * slotWidth
+ + CGFloat(max(store.slotCount - 1, 0)) * columnSpacing
+ + canvasPadding * 2
+ let lanesHeight = CGFloat(store.modeCount) * slotHeight
+ + CGFloat(max(store.modeCount - 1, 0)) * rowSpacing
+ + timelineHeaderHeight
+ + 84
+ + canvasPadding * 2
+ return CGSize(
+ width: max(viewport.width, lanesWidth),
+ height: max(viewport.height, lanesHeight)
+ )
+ }
+
+ private var slotTrackWidth: CGFloat {
+ CGFloat(store.slotCount) * slotWidth
+ + CGFloat(max(store.slotCount - 1, 0)) * columnSpacing
+ }
+
+ private func resetView() {
+ committedZoom = 1.0
+ zoom = 1.0
+ committedPanOffset = .zero
+ panOffset = .zero
+ }
+
+ private func clampedZoom(_ proposed: CGFloat) -> CGFloat {
+ min(max(proposed, minZoom), maxZoom)
+ }
+
+ private func clampedOffset(
+ _ proposed: CGSize,
+ viewportSize: CGSize,
+ canvasSize: CGSize,
+ zoomScale: CGFloat
+ ) -> CGSize {
+ let contentWidth = canvasSize.width * zoomScale
+ let contentHeight = canvasSize.height * zoomScale
+ let slack: CGFloat = 96
+ let overflowX = max(0, contentWidth - viewportSize.width)
+ let overflowY = max(0, contentHeight - viewportSize.height)
+ let minX = -overflowX - slack
+ let maxX = slack
+ let minY = -overflowY - slack
+ let maxY = slack
+ return CGSize(
+ width: min(max(proposed.width, minX), maxX),
+ height: min(max(proposed.height, minY), maxY)
+ )
+ }
+
+ private func normalizeView(viewportSize: CGSize, canvasSize: CGSize) {
+ let normalized = clampedOffset(
+ committedPanOffset,
+ viewportSize: viewportSize,
+ canvasSize: canvasSize,
+ zoomScale: committedZoom
+ )
+ committedPanOffset = normalized
+ panOffset = normalized
+ }
+
+ private func panGesture(viewportSize: CGSize, canvasSize: CGSize) -> some Gesture {
+ DragGesture(minimumDistance: 4)
+ .onChanged { value in
+ let proposed = CGSize(
+ width: committedPanOffset.width + value.translation.width,
+ height: committedPanOffset.height + value.translation.height
+ )
+ panOffset = clampedOffset(
+ proposed,
+ viewportSize: viewportSize,
+ canvasSize: canvasSize,
+ zoomScale: zoom
+ )
+ }
+ .onEnded { value in
+ let proposed = CGSize(
+ width: committedPanOffset.width + value.translation.width,
+ height: committedPanOffset.height + value.translation.height
+ )
+ committedPanOffset = clampedOffset(
+ proposed,
+ viewportSize: viewportSize,
+ canvasSize: canvasSize,
+ zoomScale: zoom
+ )
+ panOffset = committedPanOffset
+ }
+ }
+
+ private func zoomGesture(viewportSize: CGSize, canvasSize: CGSize) -> some Gesture {
+ MagnificationGesture()
+ .onChanged { value in
+ let nextZoom = clampedZoom(committedZoom * value)
+ zoom = nextZoom
+ panOffset = clampedOffset(
+ panOffset,
+ viewportSize: viewportSize,
+ canvasSize: canvasSize,
+ zoomScale: nextZoom
+ )
+ }
+ .onEnded { value in
+ committedZoom = clampedZoom(committedZoom * value)
+ zoom = committedZoom
+ committedPanOffset = clampedOffset(
+ panOffset,
+ viewportSize: viewportSize,
+ canvasSize: canvasSize,
+ zoomScale: committedZoom
+ )
+ panOffset = committedPanOffset
+ }
+ }
+
+ private func zoomWithMouseWheel(
+ deltaY: CGFloat,
+ cursorPosition: CGPoint,
+ viewportSize: CGSize,
+ canvasSize: CGSize
+ ) {
+ guard deltaY != 0 else { return }
+
+ let multiplier = exp(deltaY * 0.01)
+ let nextZoom = clampedZoom(committedZoom * multiplier)
+ let zoomFactor = nextZoom / max(committedZoom, 0.0001)
+
+ let nextOffset = CGSize(
+ width: cursorPosition.x - (cursorPosition.x - committedPanOffset.width) * zoomFactor,
+ height: cursorPosition.y - (cursorPosition.y - committedPanOffset.height) * zoomFactor
+ )
+
+ committedZoom = nextZoom
+ zoom = nextZoom
+ committedPanOffset = clampedOffset(
+ nextOffset,
+ viewportSize: viewportSize,
+ canvasSize: canvasSize,
+ zoomScale: nextZoom
+ )
+ panOffset = committedPanOffset
+ }
+}
+
+private struct MouseWheelMonitor: NSViewRepresentable {
+ let onScroll: (CGFloat, CGPoint) -> Void
+
+ func makeNSView(context: Context) -> TrackingView {
+ let view = TrackingView()
+ view.onScroll = onScroll
+ return view
+ }
+
+ func updateNSView(_ nsView: TrackingView, context: Context) {
+ nsView.onScroll = onScroll
+ }
+
+ final class TrackingView: NSView {
+ var onScroll: ((CGFloat, CGPoint) -> Void)?
+ private var monitor: Any?
+
+ override func viewDidMoveToWindow() {
+ super.viewDidMoveToWindow()
+ installMonitorIfNeeded()
+ }
+
+ override func removeFromSuperview() {
+ super.removeFromSuperview()
+ removeMonitorIfNeeded()
+ }
+
+ deinit {
+ removeMonitorIfNeeded()
+ }
+
+ private func installMonitorIfNeeded() {
+ guard monitor == nil else { return }
+
+ monitor = NSEvent.addLocalMonitorForEvents(matching: [.scrollWheel]) { [weak self] event in
+ guard let self else { return event }
+ let location = self.convert(event.locationInWindow, from: nil)
+ guard self.bounds.contains(location) else { return event }
+ self.onScroll?(event.scrollingDeltaY, location)
+ return nil
+ }
+ }
+
+ private func removeMonitorIfNeeded() {
+ guard let monitor else { return }
+ NSEvent.removeMonitor(monitor)
+ self.monitor = nil
+ }
+ }
+}
+
+private struct CircuitSlotCellView: View {
+ @EnvironmentObject private var store: StudioStore
+ let mode: Int
+ let slot: Int
+
+ @State private var isTargeted = false
+ private var palette: ThemePalette { .resolve(store.theme) }
+ private var node: CircuitNode? {
+ store.nodeAt(mode: mode, slot: slot)
+ }
+ private var isSelected: Bool {
+ store.showSelection && node?.id == store.selectedNodeID
+ }
+ private var validationColor: Color? {
+ guard store.showValidation, let node else { return nil }
+ switch store.validationSeverity(for: node) {
+ case .error:
+ return .red
+ case .warning:
+ return .orange
+ case .info:
+ return .blue
+ case .none:
+ break
+ }
+ return nil
+ }
+
+ var body: some View {
+ ZStack(alignment: .topTrailing) {
+ VStack(alignment: .leading, spacing: 4) {
+ if let node {
+ HStack(spacing: 8) {
+ Image(systemName: node.kind.systemIcon)
+ .font(.caption.weight(.semibold))
+ .frame(width: 18)
+ Text(node.kind.displayName)
+ .font(.subheadline.weight(.semibold))
+ .lineLimit(1)
+ }
+
+ Text(node.subtitle)
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ .lineLimit(1)
+ } else {
+ VStack(spacing: 2) {
+ Text("Drop")
+ .font(.caption.weight(.semibold))
+ .foregroundStyle(.blue)
+ Text("+")
+ .font(.headline.weight(.bold))
+ .foregroundStyle(.blue)
+ }
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
+ }
+ }
+ .padding(8)
+ .frame(width: 140, height: 68, alignment: .leading)
+ .background(
+ RoundedRectangle(cornerRadius: 8)
+ .fill(node == nil ? palette.subpanel : palette.inputBackground)
+ .overlay(
+ RoundedRectangle(cornerRadius: 8)
+ .stroke(
+ isSelected ? Color.accentColor :
+ (isTargeted ? Color.blue : (validationColor ?? palette.border)),
+ style: StrokeStyle(
+ lineWidth: isSelected || validationColor != nil ? 2 : 1,
+ dash: node == nil ? [5, 4] : []
+ )
+ )
+ )
+ )
+
+ if let validationColor {
+ Image(systemName: "exclamationmark.circle.fill")
+ .font(.caption)
+ .foregroundStyle(validationColor)
+ .padding(5)
+ }
+ }
+ .contentShape(RoundedRectangle(cornerRadius: 8))
+ .onTapGesture {
+ store.selectNode(node)
+ }
+ .dropDestination(for: String.self) { items, _ in
+ guard let first = items.first else { return false }
+ store.handleDrop(kindRawValue: first, mode: mode, slot: slot)
+ return true
+ } isTargeted: { targeted in
+ isTargeted = targeted
+ }
+ }
+}
+
+private struct ConnectorOverlayView: View {
+ let modeCount: Int
+ let slotCount: Int
+ let laneLabelWidth: CGFloat
+ let slotWidth: CGFloat
+ let slotHeight: CGFloat
+ let leadingInset: CGFloat
+ let topInset: CGFloat
+ let rowSpacing: CGFloat
+ let columnSpacing: CGFloat
+ let nodes: [CircuitNode]
+ let zoom: CGFloat
+ let showConnectors: Bool
+ let showPhotonFlow: Bool
+
+ var body: some View {
+ TimelineView(.animation(minimumInterval: 1.0 / 30.0, paused: !showPhotonFlow)) { timeline in
+ let t = timeline.date.timeIntervalSinceReferenceDate
+
+ Canvas { context, _ in
+ guard modeCount > 0, slotCount > 0 else { return }
+
+ let startX = leftLaneX
+ let endX = rightLaneX
+ let baseWidth = max(0.9, 1.8 / max(zoom, 0.01))
+ let strong = Color.black.opacity(0.62)
+ let medium = Color.black.opacity(0.48)
+ let groupedBySlot = Dictionary(grouping: nodes) { $0.slot }
+ let laneLength = max(endX - startX, 1)
+
+ if showConnectors {
+ for mode in 0.. 1 else { continue }
+ let x = slotCenterX(slot)
+ let sortedModes = slotNodes.map(\.mode).sorted()
+
+ for idx in 0..<(sortedModes.count - 1) {
+ let m0 = sortedModes[idx]
+ let m1 = sortedModes[idx + 1]
+ var verticalPath = Path()
+ verticalPath.move(to: CGPoint(x: x, y: laneCenterY(m0)))
+ verticalPath.addLine(to: CGPoint(x: x, y: laneCenterY(m1)))
+ context.stroke(
+ verticalPath,
+ with: .color(strong),
+ style: StrokeStyle(lineWidth: baseWidth, lineCap: .round, lineJoin: .round)
+ )
+ }
+ }
+
+ let nodeLaneSlotKeys = Set(nodes.map { "\($0.mode):\($0.slot)" })
+ let dotRadius = max(1.4, 2.6 / max(zoom, 0.01))
+ for mode in 0.. 1 else { continue }
+ let x = slotCenterX(slot)
+ let sortedModes = slotNodes.map(\.mode).sorted()
+ let y0 = laneCenterY(sortedModes.first ?? 0)
+ let y1 = laneCenterY(sortedModes.last ?? 0)
+
+ var coupler = Path()
+ coupler.move(to: CGPoint(x: x, y: y0))
+ coupler.addLine(to: CGPoint(x: x, y: y1))
+ context.stroke(
+ coupler,
+ with: .color(Color.white.opacity(0.55)),
+ style: StrokeStyle(lineWidth: max(0.9, baseWidth), lineCap: .round, lineJoin: .round)
+ )
+ }
+
+ // Static checkpoints along slots (green for idle, cyan for active node positions).
+ for mode in 0.. 1 else { continue }
+ let x = slotCenterX(slot)
+ let sortedModes = slotNodes.map(\.mode).sorted()
+
+ for idx in 0..<(sortedModes.count - 1) {
+ let m0 = sortedModes[idx]
+ let m1 = sortedModes[idx + 1]
+ let y0 = laneCenterY(m0)
+ let y1 = laneCenterY(m1)
+ let travel = CGFloat((t * 0.25 + Double(slot) * 0.13).truncatingRemainder(dividingBy: 1))
+ let y = y0 + (y1 - y0) * travel
+
+ let couplingRect = CGRect(
+ x: x - pulseRadius * 0.9,
+ y: y - pulseRadius * 0.9,
+ width: pulseRadius * 1.8,
+ height: pulseRadius * 1.8
+ )
+ context.fill(Path(ellipseIn: couplingRect), with: .color(Color.green.opacity(0.9)))
+ }
+ }
+ }
+ }
+ }
+ .allowsHitTesting(false)
+ }
+
+ private var leftLaneX: CGFloat {
+ leadingInset + laneLabelWidth + columnSpacing + 6
+ }
+
+ private var rightLaneX: CGFloat {
+ leftLaneX + CGFloat(slotCount) * slotWidth + CGFloat(max(slotCount - 1, 0)) * columnSpacing - 12
+ }
+
+ private func laneCenterY(_ mode: Int) -> CGFloat {
+ topInset + CGFloat(mode) * (slotHeight + rowSpacing) + slotHeight / 2
+ }
+
+ private func slotCenterX(_ slot: Int) -> CGFloat {
+ leftLaneX + CGFloat(slot) * (slotWidth + columnSpacing) + slotWidth / 2
+ }
+}
+
+private struct PhotonMechanicalGateOverlayView: View {
+ let modeCount: Int
+ let slotCount: Int
+ let laneLabelWidth: CGFloat
+ let slotWidth: CGFloat
+ let slotHeight: CGFloat
+ let topInset: CGFloat
+ let rowSpacing: CGFloat
+ let columnSpacing: CGFloat
+ let leadingInset: CGFloat
+ let nodes: [CircuitNode]
+ let zoom: CGFloat
+
+ var body: some View {
+ let markerScale = max(0.75, min(1.35, 1.0 / max(zoom, 0.01)))
+
+ ZStack(alignment: .topLeading) {
+ Canvas { context, _ in
+ guard modeCount > 0, slotCount > 0 else { return }
+
+ let groupedBySlot = Dictionary(grouping: nodes) { $0.slot }
+ let mechanicLineWidth = max(0.8, 1.4 / max(zoom, 0.01))
+
+ for mode in 0.. 1 else { continue }
+ let x = slotCenterX(slot)
+ let sortedModes = slotNodes.map(\.mode).sorted()
+ let y0 = laneCenterY(sortedModes.first ?? 0)
+ let y1 = laneCenterY(sortedModes.last ?? 0)
+
+ var couplingPath = Path()
+ couplingPath.move(to: CGPoint(x: x, y: y0))
+ couplingPath.addLine(to: CGPoint(x: x, y: y1))
+ context.stroke(
+ couplingPath,
+ with: .color(Color.cyan.opacity(0.35)),
+ style: StrokeStyle(lineWidth: mechanicLineWidth * 1.1, lineCap: .round)
+ )
+
+ let jointRadius = 3.2 * markerScale
+ for mode in Set(slotNodes.map(\.mode)) {
+ let y = laneCenterY(mode)
+ let rect = CGRect(
+ x: x - jointRadius,
+ y: y - jointRadius,
+ width: jointRadius * 2,
+ height: jointRadius * 2
+ )
+ context.fill(Path(ellipseIn: rect), with: .color(Color.cyan.opacity(0.88)))
+ }
+ }
+ }
+
+ ForEach(visibleNodes) { node in
+ photonNodeIcon(for: node, markerScale: markerScale)
+ .position(x: slotCenterX(node.slot), y: laneCenterY(node.mode))
+ }
+ }
+ .allowsHitTesting(false)
+ }
+
+ private var visibleNodes: [CircuitNode] {
+ nodes.filter { node in
+ node.mode >= 0 && node.mode < modeCount && node.slot >= 0 && node.slot < slotCount
+ }
+ }
+
+ private func photonNodeIcon(for node: CircuitNode, markerScale: CGFloat) -> some View {
+ let tint = color(for: node.kind)
+ let badgeDiameter = max(14.0, 18.0 * markerScale)
+ let iconSize = max(9.0, 11.5 * markerScale)
+ let symbolName = node.kind.systemIcon.isEmpty ? "questionmark.circle" : node.kind.systemIcon
+
+ return ZStack {
+ Circle()
+ .fill(Color.black.opacity(0.58))
+
+ Circle()
+ .stroke(tint.opacity(0.85), lineWidth: max(1.0, 1.3 * markerScale))
+
+ Image(systemName: symbolName)
+ .font(.system(size: iconSize, weight: .semibold))
+ .symbolRenderingMode(.monochrome)
+ .foregroundStyle(.white)
+ }
+ .frame(width: badgeDiameter, height: badgeDiameter)
+ .shadow(color: tint.opacity(0.26), radius: max(1.0, 2.0 * markerScale))
+ .help(node.kind.hoverDisplayName)
+ }
+
+ private func color(for kind: GateKind) -> Color {
+ switch kind.primaryFilter {
+ case .gaussian:
+ return Color.cyan.opacity(0.94)
+ case .nonGaussian:
+ return Color.green.opacity(0.90)
+ case .channels:
+ return Color.orange.opacity(0.92)
+ case .measurement:
+ return Color.red.opacity(0.92)
+ case .control:
+ return Color.gray.opacity(0.90)
+ case .all:
+ return Color.cyan.opacity(0.95)
+ }
+ }
+
+ private var leftSlotX: CGFloat {
+ leadingInset + laneLabelWidth + columnSpacing + slotWidth / 2
+ }
+
+ private var rightSlotX: CGFloat {
+ leftSlotX + CGFloat(max(slotCount - 1, 0)) * (slotWidth + columnSpacing)
+ }
+
+ private func laneCenterY(_ mode: Int) -> CGFloat {
+ topInset + CGFloat(mode) * (slotHeight + rowSpacing) + slotHeight / 2
+ }
+
+ private func slotCenterX(_ slot: Int) -> CGFloat {
+ leftSlotX + CGFloat(slot) * (slotWidth + columnSpacing)
+ }
+}
+
+private struct InfiniteMetricGridBackground: View {
+ let theme: AppTheme
+ let zoom: CGFloat
+ let panOffset: CGSize
+ private var palette: ThemePalette { .resolve(theme) }
+
+ var body: some View {
+ GeometryReader { _ in
+ Canvas { context, size in
+ let minorStep = max(8, 24 * zoom)
+ let majorStep = minorStep * 5
+ let startMinorX = positiveModulo(panOffset.width, by: minorStep)
+ let startMinorY = positiveModulo(panOffset.height, by: minorStep)
+ let startMajorX = positiveModulo(panOffset.width, by: majorStep)
+ let startMajorY = positiveModulo(panOffset.height, by: majorStep)
+
+ var x = startMinorX
+ while x <= size.width + minorStep {
+ let px = pixelAligned(x)
+ var path = Path()
+ path.move(to: CGPoint(x: px, y: 0))
+ path.addLine(to: CGPoint(x: px, y: size.height))
+ context.stroke(path, with: .color(palette.gridMinor), lineWidth: 0.8)
+ x += minorStep
+ }
+
+ var y = startMinorY
+ while y <= size.height + minorStep {
+ let py = pixelAligned(y)
+ var path = Path()
+ path.move(to: CGPoint(x: 0, y: py))
+ path.addLine(to: CGPoint(x: size.width, y: py))
+ context.stroke(path, with: .color(palette.gridMinor), lineWidth: 0.8)
+ y += minorStep
+ }
+
+ x = startMajorX
+ while x <= size.width + majorStep {
+ let px = pixelAligned(x)
+ var path = Path()
+ path.move(to: CGPoint(x: px, y: 0))
+ path.addLine(to: CGPoint(x: px, y: size.height))
+ context.stroke(path, with: .color(palette.gridMajor), lineWidth: 1.0)
+ x += majorStep
+ }
+
+ y = startMajorY
+ while y <= size.height + majorStep {
+ let py = pixelAligned(y)
+ var path = Path()
+ path.move(to: CGPoint(x: 0, y: py))
+ path.addLine(to: CGPoint(x: size.width, y: py))
+ context.stroke(path, with: .color(palette.gridMajor), lineWidth: 1.0)
+ y += majorStep
+ }
+ }
+ }
+ }
+
+ private func positiveModulo(_ value: CGFloat, by modulus: CGFloat) -> CGFloat {
+ guard modulus > 0 else { return 0 }
+ let result = value.truncatingRemainder(dividingBy: modulus)
+ return result >= 0 ? result : (result + modulus)
+ }
+
+ private func pixelAligned(_ value: CGFloat) -> CGFloat {
+ floor(value) + 0.5
+ }
+}
+
+private struct MinimapView: View {
+ let modeCount: Int
+ let slotCount: Int
+ let nodes: [CircuitNode]
+ let theme: AppTheme
+ private var palette: ThemePalette { .resolve(theme) }
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 4) {
+ Text("Minimap")
+ .font(.caption.weight(.semibold))
+ .foregroundStyle(.secondary)
+
+ Canvas { context, size in
+ let safeModes = max(modeCount, 1)
+ let safeSlots = max(slotCount, 1)
+ let cellWidth = size.width / CGFloat(safeSlots)
+ let cellHeight = size.height / CGFloat(safeModes)
+
+ let bg = Path(CGRect(origin: .zero, size: size))
+ context.fill(bg, with: .color(palette.subpanel))
+
+ for mode in 0...safeModes {
+ let y = CGFloat(mode) * cellHeight
+ var path = Path()
+ path.move(to: CGPoint(x: 0, y: y))
+ path.addLine(to: CGPoint(x: size.width, y: y))
+ context.stroke(path, with: .color(palette.gridMinor), lineWidth: 0.8)
+ }
+
+ for slot in 0...safeSlots {
+ let x = CGFloat(slot) * cellWidth
+ var path = Path()
+ path.move(to: CGPoint(x: x, y: 0))
+ path.addLine(to: CGPoint(x: x, y: size.height))
+ context.stroke(path, with: .color(palette.gridMinor), lineWidth: 0.8)
+ }
+
+ for node in nodes {
+ guard node.mode >= 0, node.mode < safeModes, node.slot >= 0, node.slot < safeSlots else { continue }
+ let rect = CGRect(
+ x: CGFloat(node.slot) * cellWidth + 1,
+ y: CGFloat(node.mode) * cellHeight + 1,
+ width: max(2, cellWidth - 2),
+ height: max(2, cellHeight - 2)
+ )
+ context.fill(
+ Path(roundedRect: rect, cornerRadius: 2),
+ with: .color(Color.accentColor.opacity(0.75))
+ )
+ }
+ }
+ .clipShape(RoundedRectangle(cornerRadius: 8))
+ .overlay(
+ RoundedRectangle(cornerRadius: 8)
+ .stroke(palette.border, lineWidth: 1)
+ )
+ }
+ }
+}
+
+private struct SlotTimelineReferenceView: View {
+ let slotCount: Int
+ let laneLabelWidth: CGFloat
+ let slotWidth: CGFloat
+ let columnSpacing: CGFloat
+ let leadingInset: CGFloat
+ let topInset: CGFloat
+
+ var body: some View {
+ let safeSlotCount = max(slotCount, 1)
+
+ Canvas { context, _ in
+ let axisY = topInset + 18
+ let firstX = leadingInset + laneLabelWidth + columnSpacing
+ let stepX = slotWidth + columnSpacing
+ let lastX = firstX
+ + CGFloat(safeSlotCount) * slotWidth
+ + CGFloat(max(safeSlotCount - 1, 0)) * columnSpacing
+
+ var axisPath = Path()
+ axisPath.move(to: CGPoint(x: firstX, y: axisY))
+ axisPath.addLine(to: CGPoint(x: lastX, y: axisY))
+ context.stroke(axisPath, with: .color(Color.secondary.opacity(0.70)), lineWidth: 1)
+
+ for slot in 0.. Color {
+ switch severity {
+ case .info:
+ return .blue
+ case .warning:
+ return .orange
+ case .error:
+ return .red
+ }
+ }
+}
+
+private struct CircuitConfigInspectorView: View {
+ @EnvironmentObject private var store: StudioStore
+ private var palette: ThemePalette { .resolve(store.theme) }
+ private var seedBinding: Binding {
+ Binding(
+ get: { store.seed },
+ set: { newValue in
+ store.seed = newValue.filter(\.isNumber)
+ }
+ )
+ }
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 8) {
+ VStack(alignment: .leading, spacing: 6) {
+ Text("Scientific Circuit Presets")
+ .font(.caption.weight(.semibold))
+ .foregroundStyle(.secondary)
+ Picker("Scientific Circuit Presets", selection: $store.selectedScientificCircuitPresetID) {
+ ForEach(store.scientificCircuitPresets) { preset in
+ Text(preset.title).tag(preset.id)
+ }
+ }
+ .pickerStyle(.menu)
+ .labelsHidden()
+
+ HStack(spacing: 8) {
+ Button("Load Preset") {
+ store.loadSelectedScientificCircuitPreset()
+ }
+ .buttonStyle(.borderedProminent)
+
+ if let preset = store.selectedScientificCircuitPreset {
+ Text("Backend: \(preset.recommendedBackend.title) Seed: \(preset.suggestedSeed)")
+ .font(.caption2.monospacedDigit())
+ .foregroundStyle(.secondary)
+ }
+ }
+
+ if let preset = store.selectedScientificCircuitPreset {
+ Text(preset.citation)
+ .font(.caption2)
+ .foregroundStyle(.secondary)
+ .fixedSize(horizontal: false, vertical: true)
+
+ Text(preset.mappingNote)
+ .font(.caption2)
+ .foregroundStyle(.secondary)
+ .fixedSize(horizontal: false, vertical: true)
+
+ HStack(spacing: 10) {
+ if let doiURL = URL(string: preset.doiURL) {
+ Link("DOI", destination: doiURL)
+ .font(.caption)
+ }
+ if let sourceURL = URL(string: preset.sourceURL) {
+ Link("Source", destination: sourceURL)
+ .font(.caption)
+ }
+ }
+ }
+ }
+
+ LabeledRuntimeEnginePicker(title: "Runtime Engine", selection: $store.runtimeEngine)
+ if let runtimeEngineUnavailableReason = store.runtimeEngineUnavailableReason {
+ Text(runtimeEngineUnavailableReason)
+ .font(.caption2)
+ .foregroundStyle(.secondary)
+ }
+
+ LabeledPicker(title: "Backend Selector", selection: $store.backend)
+ VStack(alignment: .leading, spacing: 4) {
+ Text("Seed Control")
+ .font(.caption.weight(.semibold))
+ .foregroundStyle(.secondary)
+ HStack(spacing: 8) {
+ TextField("Seed Control", text: seedBinding)
+ .textFieldStyle(.roundedBorder)
+ .font(.caption.monospaced())
+ Button("Random") {
+ store.seed = String(UInt64.random(in: 1...UInt64.max))
+ }
+ .buttonStyle(.bordered)
+ }
+ }
+
+ VStack(alignment: .leading, spacing: 4) {
+ Text("Cutoff Control")
+ .font(.caption.weight(.semibold))
+ .foregroundStyle(.secondary)
+ Stepper(value: $store.cutoff, in: 1...128) {
+ Text("\(store.cutoff)")
+ }
+ }
+
+ VStack(alignment: .leading, spacing: 4) {
+ Text("Slot Count")
+ .font(.caption.weight(.semibold))
+ .foregroundStyle(.secondary)
+ Stepper(value: $store.slotCount, in: 1...64) {
+ Text("\(store.slotCount)")
+ }
+ }
+
+ LabeledField(title: "Schema Version", text: $store.schemaVersion)
+
+ VStack(alignment: .leading, spacing: 4) {
+ Text("Foundry Profile")
+ .font(.caption.weight(.semibold))
+ .foregroundStyle(.secondary)
+ Picker("Foundry Profile Selector", selection: $store.foundryProfile) {
+ ForEach(store.foundryProfiles, id: \.self) { profile in
+ Text(profile).tag(profile)
+ }
+ }
+ .pickerStyle(.menu)
+ .labelsHidden()
+ }
+
+ VStack(alignment: .leading, spacing: 4) {
+ Text("KPI Policy")
+ .font(.caption.weight(.semibold))
+ .foregroundStyle(.secondary)
+ Picker("KPI Policy Selector", selection: $store.selectedKPIPolicyID) {
+ ForEach(store.kpiPolicies) { policy in
+ Text("\(policy.name) (v\(policy.version))").tag(policy.id)
+ }
+ }
+ .pickerStyle(.menu)
+ .labelsHidden()
+
+ if let policy = store.selectedKPIPolicy {
+ Text(policy.summary)
+ .font(.caption2)
+ .foregroundStyle(.secondary)
+ .fixedSize(horizontal: false, vertical: true)
+ Text("Target: \(policy.targetRepresentation)")
+ .font(.caption2.monospaced())
+ .foregroundStyle(.secondary)
+ Text(
+ "Robustness thresholds success>=\(String(format: "%.3f", policy.thresholds.minSuccessRate)) p95_d_mean<=\(String(format: "%.3e", policy.thresholds.maxMeanPhotonP95Delta)) p95_d_state<=\(String(format: "%.3e", policy.thresholds.maxStateP95Delta)) mismatch<=\(String(format: "%.3f", policy.thresholds.maxMeasurementMismatchRate))"
+ )
+ .font(.caption2.monospacedDigit())
+ .foregroundStyle(.secondary)
+ if policy.thresholds.maxLogicalErrorRate != nil
+ || policy.thresholds.minSuppressionFactor != nil
+ || policy.thresholds.minBreakEvenGain != nil {
+ Text(
+ "QEC thresholds ler<=\(policy.thresholds.maxLogicalErrorRate.map { String(format: "%.4f", $0) } ?? "n/a") suppression>=\(policy.thresholds.minSuppressionFactor.map { String(format: "%.3f", $0) } ?? "n/a") break_even>=\(policy.thresholds.minBreakEvenGain.map { String(format: "%.4f", $0) } ?? "n/a")"
+ )
+ .font(.caption2.monospacedDigit())
+ .foregroundStyle(.secondary)
+ }
+ }
+ }
+
+ LabeledField(title: "Circuit JSON Path", text: $store.circuitJSONPath)
+ }
+ .padding(10)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .background(
+ RoundedRectangle(cornerRadius: 10)
+ .fill(palette.subpanel)
+ )
+ }
+}
+
+private struct StatusInspectorView: View {
+ @EnvironmentObject private var store: StudioStore
+ private var palette: ThemePalette { .resolve(store.theme) }
+ private var syncStateText: String { store.isDirty ? "local changes" : "synced" }
+ private var cleanlinessText: String { store.isDirty ? "dirty" : "clean" }
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 8) {
+ Text("Status")
+ .font(.headline)
+
+ VStack(alignment: .leading, spacing: 4) {
+ KeyValueRow(title: "State", value: cleanlinessText)
+ KeyValueRow(title: "Autosave", value: "on")
+ KeyValueRow(title: "Sync", value: syncStateText)
+ KeyValueRow(title: "Workspace", value: store.workspace)
+ KeyValueRow(title: "Project", value: store.project)
+ KeyValueRow(title: "Environment", value: store.environment.rawValue.uppercased())
+ KeyValueRow(title: "Role", value: store.userRole.rawValue)
+ }
+ .padding(.horizontal, 10)
+ .padding(.vertical, 7)
+ .background(
+ RoundedRectangle(cornerRadius: 8)
+ .fill(palette.inputBackground)
+ .overlay(
+ RoundedRectangle(cornerRadius: 8)
+ .stroke(palette.border, lineWidth: 1)
+ )
+ )
+
+ if let savedPath = store.lastSavedDocumentPath, !savedPath.isEmpty {
+ KeyValueRow(title: "Last Save", value: savedPath)
+ }
+ }
+ .padding(10)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .background(
+ RoundedRectangle(cornerRadius: 10)
+ .fill(palette.subpanel)
+ )
+ }
+}
+
+private struct QuantumStateInspectorView: View {
+ @EnvironmentObject private var store: StudioStore
+ private var palette: ThemePalette { .resolve(store.theme) }
+
+ private var state: QuantumStateSnapshot? {
+ store.lastRunResponse?.finalState ?? store.lastTraceResponse?.finalState
+ }
+
+ private var backendLabel: String {
+ state?.backend ?? store.lastRunResponse?.backend ?? store.lastTraceResponse?.backend ?? "n/a"
+ }
+
+ private var representationLabel: String {
+ state?.representation ?? "n/a"
+ }
+
+ private var measurementCountLabel: String {
+ if let measurementCount = store.lastRunResponse?.measurementCount {
+ return "\(measurementCount)"
+ }
+ if let measurementCount = store.lastTraceResponse?.frames?.last?.measurementCount {
+ return "\(measurementCount)"
+ }
+ return "n/a"
+ }
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 8) {
+ Text("Quantum State")
+ .font(.headline)
+
+ if let state {
+ VStack(alignment: .leading, spacing: 4) {
+ KeyValueRow(title: "Backend", value: backendLabel)
+ KeyValueRow(title: "Representation", value: representationLabel)
+ KeyValueRow(title: "Modes", value: state.modes.map(String.init) ?? "n/a")
+ KeyValueRow(title: "Cutoff", value: state.cutoff.map(String.init) ?? "n/a")
+ KeyValueRow(title: "Measurements", value: measurementCountLabel)
+ }
+ .padding(.horizontal, 10)
+ .padding(.vertical, 7)
+ .background(
+ RoundedRectangle(cornerRadius: 8)
+ .fill(palette.inputBackground)
+ .overlay(
+ RoundedRectangle(cornerRadius: 8)
+ .stroke(palette.border, lineWidth: 1)
+ )
+ )
+
+ if let mean = state.mean, !mean.isEmpty {
+ VStack(alignment: .leading, spacing: 4) {
+ Text("Mean (q,p)")
+ .font(.caption.weight(.semibold))
+ .foregroundStyle(.secondary)
+ Text(formattedMeanByMode(mean))
+ .font(.system(.caption, design: .monospaced))
+ .textSelection(.enabled)
+ .padding(8)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .background(
+ RoundedRectangle(cornerRadius: 8)
+ .fill(palette.inputBackground)
+ .overlay(
+ RoundedRectangle(cornerRadius: 8)
+ .stroke(palette.border, lineWidth: 1)
+ )
+ )
+ }
+ }
+
+ if let covariance = state.covariance, !covariance.isEmpty {
+ VStack(alignment: .leading, spacing: 4) {
+ Text("Covariance")
+ .font(.caption.weight(.semibold))
+ .foregroundStyle(.secondary)
+ ScrollView(.horizontal, showsIndicators: true) {
+ Text(formattedMatrix(covariance))
+ .font(.system(.caption2, design: .monospaced))
+ .textSelection(.enabled)
+ .padding(8)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
+ .background(
+ RoundedRectangle(cornerRadius: 8)
+ .fill(palette.inputBackground)
+ .overlay(
+ RoundedRectangle(cornerRadius: 8)
+ .stroke(palette.border, lineWidth: 1)
+ )
+ )
+ }
+ }
+
+ if let topProbabilities = state.topProbabilities, !topProbabilities.isEmpty {
+ VStack(alignment: .leading, spacing: 4) {
+ Text("Top Fock Components")
+ .font(.caption.weight(.semibold))
+ .foregroundStyle(.secondary)
+ ForEach(topProbabilities) { component in
+ KeyValueRow(
+ title: "n=\(component.n)",
+ value: String(format: "%.6f", component.probability)
+ )
+ }
+ if let probabilities = state.probabilities, !probabilities.isEmpty {
+ KeyValueRow(
+ title: "Total Probability",
+ value: String(format: "%.6f", probabilities.reduce(0.0, +))
+ )
+ }
+ }
+ .padding(.horizontal, 10)
+ .padding(.vertical, 7)
+ .background(
+ RoundedRectangle(cornerRadius: 8)
+ .fill(palette.inputBackground)
+ .overlay(
+ RoundedRectangle(cornerRadius: 8)
+ .stroke(palette.border, lineWidth: 1)
+ )
+ )
+ }
+ } else {
+ Text("No state snapshot yet. Run the circuit to capture quantum state.")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+ }
+ .padding(10)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .background(
+ RoundedRectangle(cornerRadius: 10)
+ .fill(palette.subpanel)
+ )
+ }
+
+ private func formattedMeanByMode(_ mean: [Double]) -> String {
+ let modeCount = mean.count / 2
+ guard modeCount > 0 else { return "n/a" }
+
+ var lines: [String] = []
+ lines.reserveCapacity(modeCount)
+ for mode in 0.. String {
+ matrix
+ .map { row in row.map(format).joined(separator: " ") }
+ .joined(separator: "\n")
+ }
+
+ private func format(_ value: Double) -> String {
+ String(format: "% .5f", value)
+ }
+}
+
+private enum ProvenanceDetailSheet: String, Identifiable {
+ case stateHealth
+ case validity
+ case bosonicSignoff
+ case parity
+ case kpi
+
+ var id: String { rawValue }
+}
+
+private struct ProvenanceInspectorView: View {
+ @EnvironmentObject private var store: StudioStore
+ @State private var activeSheet: ProvenanceDetailSheet?
+ private var palette: ThemePalette { .resolve(store.theme) }
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 8) {
+ Text("Provenance Inspector")
+ .font(.headline)
+
+ KeyValueRow(
+ title: "Foundry Hash",
+ value: store.lastRunResponse?.provenance?.foundryProfileHash ?? "n/a"
+ )
+ KeyValueRow(
+ title: "Git SHA",
+ value: store.lastRunResponse?.provenance?.gitSHA ?? "n/a"
+ )
+ KeyValueRow(
+ title: "Backend Version",
+ value: store.lastRunResponse?.provenance?.backendVersion ?? "n/a"
+ )
+ if let health = store.stateHealthSummary {
+ HStack(spacing: 8) {
+ Text("State Health")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ Spacer()
+ Button {
+ activeSheet = .stateHealth
+ } label: {
+ Text(health.status.title)
+ .font(.caption2.weight(.semibold))
+ .foregroundStyle(.white)
+ .padding(.horizontal, 8)
+ .padding(.vertical, 4)
+ .background(
+ Capsule()
+ .fill(color(for: health.status))
+ )
+ }
+ .buttonStyle(.plain)
+ }
+ HStack {
+ Text("Physical Validity")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ Spacer()
+ Button(health.validity.title) {
+ activeSheet = .validity
+ }
+ .buttonStyle(.plain)
+ .font(.caption.monospaced())
+ }
+ KeyValueRow(title: "Numerical Confidence", value: health.confidence.title)
+ KeyValueRow(title: "Noise Robustness", value: robustnessLabel(health.robustness))
+ HStack {
+ Text("KPI Policy")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ Spacer()
+ Button(health.kpi.status.title) {
+ activeSheet = .kpi
+ }
+ .buttonStyle(.plain)
+ .font(.caption.monospaced())
+ }
+ Text(health.detail)
+ .font(.caption2.monospacedDigit())
+ .foregroundStyle(.secondary)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
+ if store.isBosonicSignoffRequired {
+ HStack(spacing: 8) {
+ Text("Bosonic Signoff")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ Spacer()
+ Button {
+ activeSheet = .bosonicSignoff
+ } label: {
+ Text(store.bosonicSignoffSummary?.status.title ?? "PENDING")
+ .font(.caption2.weight(.semibold))
+ .foregroundStyle(.white)
+ .padding(.horizontal, 8)
+ .padding(.vertical, 4)
+ .background(
+ Capsule()
+ .fill(color(for: store.bosonicSignoffSummary?.status ?? .unavailable))
+ )
+ }
+ .buttonStyle(.plain)
+ }
+ if let signoff = store.bosonicSignoffSummary {
+ KeyValueRow(
+ title: "Signoff Checks",
+ value: "\(signoff.passedChecks)/\(signoff.requiredChecks)"
+ )
+ Text(signoff.detail)
+ .font(.caption2.monospacedDigit())
+ .foregroundStyle(.secondary)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
+ }
+ if let parity = store.runtimeParitySummary {
+ HStack(spacing: 8) {
+ Text("Runtime Parity")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ Spacer()
+ Button {
+ activeSheet = .parity
+ } label: {
+ Text(parity.status.title)
+ .font(.caption2.weight(.semibold))
+ .foregroundStyle(.white)
+ .padding(.horizontal, 8)
+ .padding(.vertical, 4)
+ .background(
+ Capsule()
+ .fill(color(for: parity.status))
+ )
+ }
+ .buttonStyle(.plain)
+ }
+ KeyValueRow(
+ title: "Compared",
+ value: "\(parity.selectedRuntime.rawValue) (\(parity.selectedBackendVersion)) vs \(parity.referenceRuntime.rawValue) (\(parity.referenceBackendVersion))"
+ )
+ Text(parity.detail)
+ .font(.caption2.monospacedDigit())
+ .foregroundStyle(.secondary)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
+ KeyValueRow(
+ title: "Signature Status",
+ value: store.lastRunResponse?.provenance?.foundryProfileHash == nil ? "Pending" : "Verified"
+ )
+ }
+ .padding(10)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .background(
+ RoundedRectangle(cornerRadius: 10)
+ .fill(palette.subpanel)
+ )
+ .sheet(item: $activeSheet) { sheet in
+ ProvenanceDetailPopupView(sheet: sheet)
+ .environmentObject(store)
+ .frame(minWidth: 620, minHeight: 420)
+ }
+ }
+
+ private func robustnessLabel(_ robustness: StateRobustnessSummary) -> String {
+ guard let score = robustness.score else { return robustness.status.title }
+ return "\(robustness.status.title) (\(String(format: "%.1f", score)))"
+ }
+
+ private func color(for status: RuntimeParityStatus) -> Color {
+ switch status {
+ case .match:
+ return .green
+ case .drift:
+ return .orange
+ case .unmatched:
+ return .indigo
+ }
+ }
+
+ private func color(for status: StateHealthStatus) -> Color {
+ switch status {
+ case .healthy:
+ return .green
+ case .review:
+ return .orange
+ case .invalid:
+ return .purple
+ case .unavailable:
+ return .gray
+ }
+ }
+
+ private func color(for status: BosonicSignoffStatus) -> Color {
+ switch status {
+ case .pass:
+ return .green
+ case .incomplete:
+ return .orange
+ case .unavailable:
+ return .gray
+ }
+ }
+}
+
+private struct ProvenanceDetailPopupView: View {
+ @EnvironmentObject private var store: StudioStore
+ @Environment(\.dismiss) private var dismiss
+ let sheet: ProvenanceDetailSheet
+ private var palette: ThemePalette { .resolve(store.theme) }
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 10) {
+ HStack {
+ Text(title)
+ .font(.title3.weight(.semibold))
+ Spacer()
+ Button("Close") { dismiss() }
+ .buttonStyle(.bordered)
+ }
+
+ ScrollView {
+ VStack(alignment: .leading, spacing: 8) {
+ switch sheet {
+ case .stateHealth:
+ stateHealthContent
+ case .validity:
+ validityContent
+ case .bosonicSignoff:
+ bosonicSignoffContent
+ case .parity:
+ parityContent
+ case .kpi:
+ kpiContent
+ }
+ }
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
+ }
+ .padding(12)
+ .background(palette.panel)
+ }
+
+ private var title: String {
+ switch sheet {
+ case .stateHealth:
+ return "State Health Details"
+ case .validity:
+ return "Physical Validity Details"
+ case .bosonicSignoff:
+ return "Bosonic Signoff Details"
+ case .parity:
+ return "Runtime Parity Details"
+ case .kpi:
+ return "KPI Policy Details"
+ }
+ }
+
+ @ViewBuilder
+ private var stateHealthContent: some View {
+ if let health = store.stateHealthSummary {
+ KeyValueRow(title: "State Health", value: health.status.title)
+ KeyValueRow(title: "Physical Validity", value: health.validity.title)
+ KeyValueRow(title: "Numerical Confidence", value: health.confidence.title)
+ KeyValueRow(title: "Noise Robustness", value: health.robustness.status.title)
+ KeyValueRow(title: "KPI Policy", value: "\(health.kpi.policyName) v\(health.kpi.policyVersion)")
+ KeyValueRow(title: "KPI Status", value: health.kpi.status.title)
+ if let score = health.robustness.score {
+ KeyValueRow(title: "Robustness Pass Rate", value: String(format: "%.1f%%", score))
+ }
+ KeyValueRow(title: "Checks", value: "\(health.checksCompleted)")
+ KeyValueRow(title: "Issues", value: "\(health.issueCount)")
+ Text("Recommendation: \(health.recommendation)")
+ .font(.caption)
+ if let rationale = health.successRationale {
+ Text("Why successful: \(rationale)")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+ Text("Robustness: \(health.robustness.detail)")
+ .font(.caption2.monospacedDigit())
+ .foregroundStyle(.secondary)
+ ForEach(health.robustness.checks) { check in
+ PolicyCheckRow(check: check)
+ }
+ Text("Summary: \(health.detail)")
+ .font(.caption2.monospacedDigit())
+ .foregroundStyle(.secondary)
+ } else {
+ Text("No state health data yet. Run the circuit to generate diagnostics.")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+ }
+
+ @ViewBuilder
+ private var validityContent: some View {
+ if let health = store.stateHealthSummary {
+ KeyValueRow(title: "Physical Validity", value: health.validity.title)
+ KeyValueRow(title: "Checks", value: "\(health.checksCompleted)")
+ KeyValueRow(title: "Errors", value: "\(health.errors.count)")
+ KeyValueRow(title: "Warnings", value: "\(health.warnings.count)")
+
+ if !health.errors.isEmpty {
+ Text("Errors")
+ .font(.caption.weight(.semibold))
+ ForEach(Array(health.errors.prefix(12).enumerated()), id: \.offset) { index, issue in
+ Text("\(index + 1). \(issue)")
+ .font(.caption)
+ }
+ }
+ if !health.warnings.isEmpty {
+ Text("Warnings")
+ .font(.caption.weight(.semibold))
+ ForEach(Array(health.warnings.prefix(12).enumerated()), id: \.offset) { index, issue in
+ Text("\(index + 1). \(issue)")
+ .font(.caption)
+ }
+ }
+ if health.errors.isEmpty, health.warnings.isEmpty {
+ Text("All physical checks passed for this run.")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+ } else {
+ Text("No validity data yet. Run the circuit to evaluate physical checks.")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+ }
+
+ @ViewBuilder
+ private var bosonicSignoffContent: some View {
+ if let signoff = store.bosonicSignoffSummary {
+ KeyValueRow(title: "Status", value: signoff.status.title)
+ KeyValueRow(title: "Completed Checks", value: "\(signoff.passedChecks)/\(signoff.requiredChecks)")
+ Text("Recommendation: \(signoff.recommendation)")
+ .font(.caption)
+ Text("Summary: \(signoff.detail)")
+ .font(.caption2.monospacedDigit())
+ .foregroundStyle(.secondary)
+
+ ForEach(signoff.checks) { check in
+ VStack(alignment: .leading, spacing: 2) {
+ HStack(spacing: 8) {
+ Image(systemName: check.passed ? "checkmark.circle.fill" : "exclamationmark.triangle.fill")
+ .foregroundStyle(check.passed ? Color.green : Color.orange)
+ Text(check.title)
+ .font(.caption.weight(.semibold))
+ Spacer()
+ Text(check.observed)
+ .font(.caption.monospaced())
+ Text("->")
+ .font(.caption2)
+ .foregroundStyle(.secondary)
+ Text(check.expected)
+ .font(.caption.monospaced())
+ }
+ Text(check.detail)
+ .font(.caption2)
+ .foregroundStyle(.secondary)
+ }
+ .padding(.vertical, 2)
+ }
+ } else {
+ Text("No bosonic signoff data yet. Run the circuit to evaluate objective signoff checks.")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+ }
+
+ @ViewBuilder
+ private var parityContent: some View {
+ if let parity = store.runtimeParitySummary {
+ KeyValueRow(title: "Status", value: parity.status.title)
+ KeyValueRow(
+ title: "Compared",
+ value: "\(parity.selectedRuntime.rawValue) (\(parity.selectedBackendVersion)) vs \(parity.referenceRuntime.rawValue) (\(parity.referenceBackendVersion))"
+ )
+ if let meanDelta = parity.meanPhotonDelta {
+ KeyValueRow(title: "Delta Mean Photon", value: String(format: "%.8e", meanDelta))
+ }
+ if let stateDelta = parity.stateDeltaMax {
+ KeyValueRow(title: "Delta State Max", value: String(format: "%.8e", stateDelta))
+ }
+ if let measurementDelta = parity.measurementDelta {
+ KeyValueRow(title: "Delta Measurements", value: "\(measurementDelta)")
+ }
+ Text("Detail: \(parity.detail)")
+ .font(.caption2.monospacedDigit())
+ .foregroundStyle(.secondary)
+ Text("Recommendation: \(parityRecommendation(parity.status))")
+ .font(.caption)
+ } else {
+ Text("No runtime parity data yet. Run the circuit to compare runtimes.")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+ }
+
+ @ViewBuilder
+ private var kpiContent: some View {
+ if let health = store.stateHealthSummary {
+ KeyValueRow(title: "Policy", value: "\(health.kpi.policyName) (\(health.kpi.policyID))")
+ KeyValueRow(title: "Version", value: "v\(health.kpi.policyVersion)")
+ KeyValueRow(title: "Status", value: health.kpi.status.title)
+ Text("Recommendation: \(health.kpi.recommendation)")
+ .font(.caption)
+ Text("Summary: \(health.kpi.detail)")
+ .font(.caption2.monospacedDigit())
+ .foregroundStyle(.secondary)
+ ForEach(health.kpi.checks) { check in
+ PolicyCheckRow(check: check)
+ }
+ if health.kpi.checks.isEmpty {
+ Text("No KPI checks were produced for this run/policy pair.")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+ } else {
+ Text("No KPI data yet. Run the circuit to evaluate policy checks.")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+ }
+
+ private func parityRecommendation(_ status: RuntimeParityStatus) -> String {
+ switch status {
+ case .match:
+ return "Both runtimes agree within strict tolerance. This run is numerically consistent."
+ case .drift:
+ return "Small drift detected. Validate cutoff/seed configuration and track trends across multiple runs."
+ case .unmatched:
+ return "Runtimes diverged beyond tolerance. Review backend support, state payload compatibility, and gate-level constraints."
+ }
+ }
+}
+
+private struct PolicyCheckRow: View {
+ let check: ThresholdCheckResult
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 2) {
+ HStack(spacing: 8) {
+ Text(check.title)
+ .font(.caption)
+ Spacer()
+ Text(check.passed ? "PASS" : "FAIL")
+ .font(.caption2.weight(.semibold))
+ .foregroundStyle(check.passed ? .green : .orange)
+ }
+ HStack(spacing: 6) {
+ Text("value: \(metricText(check.value))")
+ Text("\(check.comparator)")
+ Text("threshold: \(metricText(check.threshold))")
+ }
+ .font(.caption2.monospacedDigit())
+ .foregroundStyle(.secondary)
+ if let note = check.note, !note.isEmpty {
+ Text(note)
+ .font(.caption2)
+ .foregroundStyle(.secondary)
+ }
+ }
+ .padding(.vertical, 2)
+ }
+
+ private func metricText(_ value: Double?) -> String {
+ guard let value, value.isFinite else { return "n/a" }
+ return String(format: "%.6e", value)
+ }
+}
+
+private struct BottomPanelView: View {
+ @EnvironmentObject private var store: StudioStore
+ private var palette: ThemePalette { .resolve(store.theme) }
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 10) {
+ Picker("BottomTabs", selection: $store.bottomTab) {
+ ForEach(BottomTab.allCases) { tab in
+ Text(tab.title).tag(tab)
+ }
+ }
+ .pickerStyle(.segmented)
+ .labelsHidden()
+ .frame(maxWidth: 720)
+
+ ZStack {
+ switch store.bottomTab {
+ case .validationConsole:
+ ValidationConsoleView()
+ case .runOutputPanel:
+ RunOutputPanelView()
+ case .auditTrailPanel:
+ AuditTrailPanelView()
+ }
+ }
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
+ }
+ .padding(12)
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
+ .background(palette.panel)
+ }
+}
+
+private struct ValidationConsoleView: View {
+ @EnvironmentObject private var store: StudioStore
+ private var palette: ThemePalette { .resolve(store.theme) }
+
+ var body: some View {
+ ScrollView {
+ VStack(alignment: .leading, spacing: 6) {
+ ForEach(store.validationIssues) { issue in
+ HStack(spacing: 10) {
+ Text(issue.severity.rawValue)
+ .font(.caption.weight(.semibold))
+ .foregroundStyle(color(for: issue.severity))
+ .frame(width: 64, alignment: .leading)
+ Text(issue.code)
+ .font(.caption.monospaced())
+ .frame(width: 170, alignment: .leading)
+ Text(issue.message)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ Text(issue.location)
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+ .padding(.vertical, 4)
+ Divider()
+ }
+ }
+ .padding(8)
+ }
+ .background(
+ RoundedRectangle(cornerRadius: 10)
+ .fill(palette.subpanel)
+ )
+ }
+
+ private func color(for severity: ValidationSeverity) -> Color {
+ switch severity {
+ case .info:
+ return .blue
+ case .warning:
+ return .orange
+ case .error:
+ return .red
+ }
+ }
+}
+
+private struct RunOutputPanelView: View {
+ @EnvironmentObject private var store: StudioStore
+ private var palette: ThemePalette { .resolve(store.theme) }
+
+ var body: some View {
+ ScrollView {
+ VStack(alignment: .leading, spacing: 4) {
+ Text("Status: \(store.statusText)")
+ .font(.subheadline.weight(.semibold))
+ if let export = store.lastExportPath {
+ Text("Last export: \(export)")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+ Divider()
+ ForEach(Array(store.runOutput.enumerated()), id: \.offset) { _, line in
+ Text(line)
+ .font(.system(.caption, design: .monospaced))
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
+ }
+ .padding(8)
+ }
+ .background(
+ RoundedRectangle(cornerRadius: 10)
+ .fill(palette.subpanel)
+ )
+ }
+}
+
+private struct MetricsPanelView: View {
+ @EnvironmentObject private var store: StudioStore
+
+ private var gaussianCount: Int {
+ store.nodes.filter { $0.kind.primaryFilter == .gaussian }.count
+ }
+
+ private var nonGaussianCount: Int {
+ store.nodes.filter { $0.kind.primaryFilter == .nonGaussian }.count
+ }
+
+ private var depth: Int {
+ (store.nodes.map(\.slot).max() ?? -1) + 1
+ }
+
+ var body: some View {
+ HStack(spacing: 10) {
+ MetricCard(title: "Gate Count", value: "\(store.nodes.count)", subtitle: "Gaussian: \(gaussianCount) Non-Gaussian: \(nonGaussianCount)")
+ MetricCard(title: "Depth", value: "\(depth)", subtitle: "\(store.slotCount) slots")
+ MetricCard(title: "Modes", value: "\(store.modeCount)", subtitle: "Active")
+ MetricCard(title: "Est. Runtime", value: String(format: "%.2f s", 0.25 + Double(store.nodes.count) * 0.08), subtitle: "\(store.backend.title) backend")
+ }
+ }
+}
+
+private struct ConfidenceBandSeries {
+ let lower: [Double]
+ let mean: [Double]
+ let upper: [Double]
+}
+
+private struct AnalysisPanelView: View {
+ @EnvironmentObject private var store: StudioStore
+ var includeRunOutput: Bool = false
+ private var palette: ThemePalette { .resolve(store.theme) }
+
+ private var totalGateCount: Int {
+ store.nodes.count
+ }
+
+ private var circuitDepth: Int {
+ (store.nodes.map(\.slot).max() ?? -1) + 1
+ }
+
+ private var frames: [TraceResponse.Frame] {
+ store.lastTraceResponse?.frames ?? []
+ }
+
+ private var photonSeries: [Double] {
+ frames.map(\.meanPhotonNumber)
+ }
+
+ private var latencySeries: [Double] {
+ frames.map(\.frameLatencyMs)
+ }
+
+ private var measurementSeries: [Double] {
+ frames.map { Double($0.measurementCount) }
+ }
+
+ private var photonDeltaSeries: [Double] {
+ guard let first = photonSeries.first else { return [] }
+ var deltas: [Double] = [0.0]
+ deltas.reserveCapacity(photonSeries.count)
+ var previous = first
+ for value in photonSeries.dropFirst() {
+ deltas.append(value - previous)
+ previous = value
+ }
+ return deltas
+ }
+
+ private var cumulativeLatencySeries: [Double] {
+ var running = 0.0
+ return latencySeries.map { latency in
+ running += latency
+ return running
+ }
+ }
+
+ private var qecReport: QECReport? {
+ store.lastRunResponse?.qec ?? store.lastTraceResponse?.qec
+ }
+
+ private var qecRounds: [QECReportRound] {
+ qecReport?.rounds ?? []
+ }
+
+ private var qecSyndromeSeries: [Double] {
+ qecRounds.compactMap(\.syndromeValue)
+ }
+
+ private var qecResidualSeries: [Double] {
+ qecRounds.compactMap(\.residual)
+ }
+
+ private var isQECProject: Bool {
+ store.project.trimmingCharacters(in: .whitespacesAndNewlines).localizedCaseInsensitiveCompare("Quantum Error Correction") == .orderedSame
+ }
+
+ private var monteCarloFrameMatrix: [[TraceResponse.Frame]] {
+ store.monteCarloTraceResponses.compactMap(\.frames).filter { !$0.isEmpty }
+ }
+
+ private var monteCarloFinalPhotonSeries: [Double] {
+ store.monteCarloTraceResponses.compactMap { $0.frames?.last?.meanPhotonNumber }
+ }
+
+ private var monteCarloTraceTotalSeries: [Double] {
+ store.monteCarloTraceResponses.compactMap(\.traceTotalMs)
+ }
+
+ private var monteCarloPeakLatencySeries: [Double] {
+ store.monteCarloTraceResponses.compactMap { response in
+ response.frames?.map(\.frameLatencyMs).max()
+ }
+ }
+
+ private var photonBand: ConfidenceBandSeries? {
+ store.showConfidenceBands ? confidenceBand { $0.meanPhotonNumber } : nil
+ }
+
+ private var latencyBand: ConfidenceBandSeries? {
+ store.showConfidenceBands ? confidenceBand { $0.frameLatencyMs } : nil
+ }
+
+ private var measurementBand: ConfidenceBandSeries? {
+ store.showConfidenceBands ? confidenceBand { Double($0.measurementCount) } : nil
+ }
+
+ private var gateHistogram: [(String, Int)] {
+ let counts = Dictionary(grouping: frames.filter { $0.gateType != "initial" }, by: \.gateType)
+ .mapValues(\.count)
+ return counts
+ .sorted { lhs, rhs in
+ if lhs.value == rhs.value {
+ return lhs.key < rhs.key
+ }
+ return lhs.value > rhs.value
+ }
+ .prefix(8)
+ .map { ($0.key, $0.value) }
+ }
+
+ private var monteCarloSummaryText: String? {
+ guard !store.monteCarloTraceResponses.isEmpty else { return nil }
+ let done = store.monteCarloCompletedSamples
+ let total = store.noiseModel.sampleCount
+ return "Monte Carlo samples: \(done)/\(total)"
+ }
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 10) {
+ NoiseModelPanelView()
+ .environmentObject(store)
+
+ if frames.isEmpty {
+ Text("Run a circuit to populate analysis graphs.")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .padding(10)
+ .background(
+ RoundedRectangle(cornerRadius: 10)
+ .fill(palette.subpanel)
+ )
+ qecAnalysisSection()
+ } else {
+ HStack(spacing: 10) {
+ MetricCard(
+ title: "Total Gates",
+ value: "\(totalGateCount)",
+ subtitle: "Circuit nodes"
+ )
+ MetricCard(
+ title: "Depth",
+ value: "\(circuitDepth)",
+ subtitle: "Slot layers"
+ )
+ MetricCard(
+ title: "Trace Frames",
+ value: "\(frames.count)",
+ subtitle: "Rendered"
+ )
+ MetricCard(
+ title: "Mean Photon",
+ value: String(format: "%.4f", store.lastRunResponse?.meanPhotonNumber ?? photonSeries.last ?? 0.0),
+ subtitle: "Final estimate"
+ )
+ MetricCard(
+ title: "Trace Time",
+ value: String(format: "%.2f ms", store.lastTraceResponse?.traceTotalMs ?? 0.0),
+ subtitle: "Total execution"
+ )
+ }
+
+ if let monteCarloSummaryText {
+ Text(monteCarloSummaryText)
+ .font(.caption.monospacedDigit())
+ .foregroundStyle(.secondary)
+ }
+ qecAnalysisSection()
+
+ SparklineCard(
+ title: "Photon Number vs Frame",
+ values: photonSeries,
+ stroke: .blue,
+ confidenceBand: photonBand,
+ xAxisLabel: "Frame Index",
+ yAxisLabel: "Mean Photon Number"
+ )
+
+ SparklineCard(
+ title: "Photon Delta per Frame",
+ values: photonDeltaSeries,
+ stroke: .cyan,
+ confidenceBand: nil,
+ xAxisLabel: "Frame Index",
+ yAxisLabel: "Delta Mean Photon Number"
+ )
+
+ SparklineCard(
+ title: "Frame Latency (ms)",
+ values: latencySeries,
+ stroke: .orange,
+ confidenceBand: latencyBand,
+ xAxisLabel: "Frame Index",
+ yAxisLabel: "Latency (ms)"
+ )
+
+ SparklineCard(
+ title: "Cumulative Trace Time (ms)",
+ values: cumulativeLatencySeries,
+ stroke: .mint,
+ confidenceBand: nil,
+ xAxisLabel: "Frame Index",
+ yAxisLabel: "Cumulative Time (ms)"
+ )
+
+ SparklineCard(
+ title: "Measurement Count Progression",
+ values: measurementSeries,
+ stroke: .purple,
+ confidenceBand: measurementBand,
+ xAxisLabel: "Frame Index",
+ yAxisLabel: "Measurement Count"
+ )
+
+ if monteCarloFinalPhotonSeries.count > 1 {
+ SparklineCard(
+ title: "Monte Carlo Final Mean Photon",
+ values: monteCarloFinalPhotonSeries,
+ stroke: .green,
+ confidenceBand: nil,
+ xAxisLabel: "Sample Index",
+ yAxisLabel: "Final Mean Photon"
+ )
+ }
+
+ if monteCarloTraceTotalSeries.count > 1 {
+ SparklineCard(
+ title: "Monte Carlo Trace Time (ms)",
+ values: monteCarloTraceTotalSeries,
+ stroke: .red,
+ confidenceBand: nil,
+ xAxisLabel: "Sample Index",
+ yAxisLabel: "Trace Time (ms)"
+ )
+ }
+
+ if monteCarloPeakLatencySeries.count > 1 {
+ SparklineCard(
+ title: "Monte Carlo Peak Frame Latency",
+ values: monteCarloPeakLatencySeries,
+ stroke: .indigo,
+ confidenceBand: nil,
+ xAxisLabel: "Sample Index",
+ yAxisLabel: "Peak Latency (ms)"
+ )
+ }
+
+ GateHistogramCard(items: gateHistogram)
+
+ if includeRunOutput {
+ RunOutputSummaryCard(lines: store.runOutput, statusText: store.statusText)
+ }
+ }
+ }
+ .padding(8)
+ .background(
+ RoundedRectangle(cornerRadius: 10)
+ .fill(palette.subpanel)
+ )
+ }
+
+ private func confidenceBand(metric: (TraceResponse.Frame) -> Double) -> ConfidenceBandSeries? {
+ let matrix = monteCarloFrameMatrix
+ guard !matrix.isEmpty else { return nil }
+ let frameCount = matrix.map { $0.count }.min() ?? 0
+ guard frameCount > 0 else { return nil }
+
+ var lower: [Double] = []
+ var mean: [Double] = []
+ var upper: [Double] = []
+ lower.reserveCapacity(frameCount)
+ mean.reserveCapacity(frameCount)
+ upper.reserveCapacity(frameCount)
+
+ for frameIndex in 0.. Double {
+ guard !sortedValues.isEmpty else { return 0.0 }
+ let clamped = min(max(fraction, 0.0), 1.0)
+ let position = clamped * Double(sortedValues.count - 1)
+ let lowerIndex = Int(floor(position))
+ let upperIndex = Int(ceil(position))
+ if lowerIndex == upperIndex {
+ return sortedValues[lowerIndex]
+ }
+ let weight = position - Double(lowerIndex)
+ return sortedValues[lowerIndex] * (1.0 - weight) + sortedValues[upperIndex] * weight
+ }
+
+ private func metricText(_ value: Double?) -> String {
+ guard let value, value.isFinite else { return "n/a" }
+ return String(format: "%.6f", value)
+ }
+
+ @ViewBuilder
+ private func qecAnalysisSection() -> some View {
+ if let qecReport {
+ HStack(spacing: 10) {
+ MetricCard(
+ title: "QEC Rounds",
+ value: "\(qecReport.roundsExecuted ?? qecRounds.count)",
+ subtitle: "Decoder \(qecReport.decoder ?? "n/a")"
+ )
+ MetricCard(
+ title: "Logical Error Rate",
+ value: metricText(qecReport.logicalErrorRate),
+ subtitle: "fail \(qecReport.logicalFailCount ?? 0) / pass \(qecReport.logicalPassCount ?? 0)"
+ )
+ MetricCard(
+ title: "Suppression Factor",
+ value: metricText(qecReport.suppressionFactor),
+ subtitle: "vs physical noise proxy"
+ )
+ MetricCard(
+ title: "Break-even Gain",
+ value: metricText(qecReport.breakEvenGain),
+ subtitle: (qecReport.breakEvenPass == true) ? "pass" : "review"
+ )
+ }
+
+ QECSyndromeRoundCard(report: qecReport)
+ QECLatticeCheckerCard(report: qecReport)
+
+ if qecSyndromeSeries.count > 1 {
+ SparklineCard(
+ title: "QEC Syndrome by Round",
+ values: qecSyndromeSeries,
+ stroke: .pink,
+ confidenceBand: nil,
+ xAxisLabel: "Round Index",
+ yAxisLabel: "Syndrome Value"
+ )
+ }
+
+ if qecResidualSeries.count > 1 {
+ SparklineCard(
+ title: "QEC Residual by Round",
+ values: qecResidualSeries,
+ stroke: .teal,
+ confidenceBand: nil,
+ xAxisLabel: "Round Index",
+ yAxisLabel: "Residual"
+ )
+ }
+ } else if isQECProject {
+ VStack(alignment: .leading, spacing: 6) {
+ Text("QEC Analysis")
+ .font(.caption.weight(.semibold))
+ Text("No QEC payload found for this run. Use a circuit with `gkp_decode_displace` on Gaussian/Hybrid backend, then run again.")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+ .padding(10)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .background(
+ RoundedRectangle(cornerRadius: 10)
+ .fill(palette.subpanel)
+ )
+ }
+ }
+}
+
+private struct NoiseModelPanelView: View {
+ @EnvironmentObject private var store: StudioStore
+ private var palette: ThemePalette { .resolve(store.theme) }
+
+ private var sampleCountBinding: Binding {
+ Binding(
+ get: { store.noiseModel.sampleCount },
+ set: { store.noiseModel.sampleCount = max(4, min($0, 128)) }
+ )
+ }
+
+ private var lossSigmaBinding: Binding {
+ Binding(
+ get: { store.noiseModel.lossEtaSigma },
+ set: { store.noiseModel.lossEtaSigma = min(max($0, 0.0), 0.20) }
+ )
+ }
+
+ private var thermalSigmaBinding: Binding {
+ Binding(
+ get: { store.noiseModel.thermalNoiseSigma },
+ set: { store.noiseModel.thermalNoiseSigma = min(max($0, 0.0), 1.0) }
+ )
+ }
+
+ private var phaseSigmaBinding: Binding {
+ Binding(
+ get: { store.noiseModel.phaseSigmaRad },
+ set: { store.noiseModel.phaseSigmaRad = min(max($0, 0.0), 0.30) }
+ )
+ }
+
+ private var displacementSigmaBinding: Binding {
+ Binding(
+ get: { store.noiseModel.displacementSigma },
+ set: { store.noiseModel.displacementSigma = min(max($0, 0.0), 0.30) }
+ )
+ }
+
+ private var detectorSummaryText: String {
+ guard let profile = store.calibratedNoiseProfile else { return "Detector: n/a" }
+ return "Detector homodyne sigma \(format(profile.detectorModel.homodyneSigma)) heterodyne sigma \(format(profile.detectorModel.heterodyneSigma))"
+ }
+
+ private var driftSummaryText: String {
+ guard let profile = store.calibratedNoiseProfile else { return "Drift: n/a" }
+ return "Drift/sample deta \(format(profile.driftModel.lossEtaPerSample)) dn_th \(format(profile.driftModel.thermalPerSample)) dtheta \(format(profile.driftModel.phaseRadPerSample)) ddisp \(format(profile.driftModel.displacementPerSample))"
+ }
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 8) {
+ HStack {
+ Text("Noise Model")
+ .font(.caption.weight(.semibold))
+ Spacer()
+ Toggle("Calibrated", isOn: $store.useCalibratedNoiseProfile)
+ .toggleStyle(.switch)
+ .disabled(store.calibratedNoiseProfile == nil)
+ .font(.caption)
+ Toggle("Bands", isOn: $store.showConfidenceBands)
+ .toggleStyle(.switch)
+ .font(.caption)
+ if store.isMonteCarloRunning {
+ ProgressView()
+ .progressViewStyle(.circular)
+ .controlSize(.small)
+ }
+ }
+
+ HStack(spacing: 8) {
+ Button {
+ store.importCalibratedNoiseProfile()
+ } label: {
+ Label("Import Tensor", systemImage: "square.and.arrow.down")
+ }
+ .buttonStyle(.bordered)
+ .disabled(store.isMonteCarloRunning)
+
+ Button {
+ store.clearCalibratedNoiseProfile()
+ } label: {
+ Label("Clear Tensor", systemImage: "trash")
+ }
+ .buttonStyle(.bordered)
+ .disabled(store.calibratedNoiseProfile == nil || store.isMonteCarloRunning)
+ }
+
+ VStack(alignment: .leading, spacing: 2) {
+ Text("Profile: \(store.calibratedNoiseProfileDisplayName)")
+ .font(.caption.monospaced())
+ Text(store.calibratedNoiseModeCoverageText)
+ .font(.caption2.monospacedDigit())
+ .foregroundStyle(.secondary)
+ Text(detectorSummaryText)
+ .font(.caption2.monospacedDigit())
+ .foregroundStyle(.secondary)
+ Text(driftSummaryText)
+ .font(.caption2.monospacedDigit())
+ .foregroundStyle(.secondary)
+ }
+
+ HStack(spacing: 10) {
+ Stepper(value: sampleCountBinding, in: 4...128) {
+ Text("Samples \(store.noiseModel.sampleCount)")
+ .font(.caption.monospacedDigit())
+ }
+ .frame(maxWidth: 220, alignment: .leading)
+
+ Button("Run Monte Carlo") {
+ Task { await store.runMonteCarloAnalysis() }
+ }
+ .buttonStyle(.borderedProminent)
+ .disabled(store.isMonteCarloRunning)
+
+ Button("Reset") {
+ store.clearMonteCarloResults()
+ }
+ .buttonStyle(.bordered)
+ .disabled(store.isMonteCarloRunning)
+ }
+
+ VStack(alignment: .leading, spacing: 6) {
+ NoiseSliderRow(
+ title: "Loss eta sigma",
+ value: lossSigmaBinding,
+ range: 0.0...0.20
+ )
+ NoiseSliderRow(
+ title: "Thermal noise sigma",
+ value: thermalSigmaBinding,
+ range: 0.0...1.0
+ )
+ NoiseSliderRow(
+ title: "Phase sigma (rad)",
+ value: phaseSigmaBinding,
+ range: 0.0...0.30
+ )
+ NoiseSliderRow(
+ title: "Displacement sigma",
+ value: displacementSigmaBinding,
+ range: 0.0...0.30
+ )
+ }
+ }
+ .padding(10)
+ .background(
+ RoundedRectangle(cornerRadius: 10)
+ .fill(palette.subpanel)
+ )
+ }
+
+ private func format(_ value: Double) -> String {
+ String(format: "%.5f", value)
+ }
+}
+
+private struct NoiseSliderRow: View {
+ let title: String
+ @Binding var value: Double
+ let range: ClosedRange
+
+ var body: some View {
+ HStack(spacing: 8) {
+ Text(title)
+ .font(.caption)
+ .frame(width: 170, alignment: .leading)
+ Slider(value: $value, in: range)
+ Text(String(format: "%.4f", value))
+ .font(.caption.monospacedDigit())
+ .frame(width: 62, alignment: .trailing)
+ }
+ }
+}
+
+private struct AnalysisPopupWindowView: View {
+ @EnvironmentObject private var store: StudioStore
+ @Environment(\.dismiss) private var dismiss
+ private var palette: ThemePalette { .resolve(store.theme) }
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 10) {
+ HStack {
+ VStack(alignment: .leading, spacing: 2) {
+ Text("Run Analysis")
+ .font(.title3.weight(.semibold))
+ Text("Merged metrics, traces, and run output")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+ Spacer()
+ Button("Close") { dismiss() }
+ .buttonStyle(.bordered)
+ }
+
+ ScrollView {
+ AnalysisPanelView(includeRunOutput: true)
+ .environmentObject(store)
+ .padding(.trailing, 2)
+ }
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
+ .background(palette.panel)
+ }
+ .padding(12)
+ .background(palette.panel)
+ }
+}
+
+private struct SparklineCard: View {
+ @EnvironmentObject private var store: StudioStore
+ let title: String
+ let values: [Double]
+ let stroke: Color
+ let confidenceBand: ConfidenceBandSeries?
+ let xAxisLabel: String
+ let yAxisLabel: String
+ private var palette: ThemePalette { .resolve(store.theme) }
+ private let chartHeight: CGFloat = 120
+ private let yScaleWidth: CGFloat = 84
+
+ private var plotCount: Int {
+ guard let confidenceBand else { return values.count }
+ return min(values.count, confidenceBand.lower.count, confidenceBand.mean.count, confidenceBand.upper.count)
+ }
+
+ private var plottedValues: [Double] {
+ Array(values.prefix(plotCount))
+ }
+
+ private var plottedBand: ConfidenceBandSeries? {
+ guard let confidenceBand else { return nil }
+ return ConfidenceBandSeries(
+ lower: Array(confidenceBand.lower.prefix(plotCount)),
+ mean: Array(confidenceBand.mean.prefix(plotCount)),
+ upper: Array(confidenceBand.upper.prefix(plotCount))
+ )
+ }
+
+ private var stats: (min: Double, max: Double) {
+ guard let first = plottedValues.first else { return (0, 0) }
+ var minValue = first
+ var maxValue = first
+ for value in plottedValues.dropFirst() {
+ minValue = min(minValue, value)
+ maxValue = max(maxValue, value)
+ }
+ return (minValue, maxValue)
+ }
+
+ private var chartMinMax: (min: Double, max: Double) {
+ var chartMin = stats.min
+ var chartMax = stats.max
+ if let plottedBand {
+ for value in plottedBand.lower {
+ chartMin = min(chartMin, value)
+ chartMax = max(chartMax, value)
+ }
+ for value in plottedBand.upper {
+ chartMin = min(chartMin, value)
+ chartMax = max(chartMax, value)
+ }
+ }
+ return (chartMin, chartMax)
+ }
+
+ private var yScaleValues: [Double] {
+ if plottedValues.isEmpty {
+ return [1.0, 0.5, 0.0]
+ }
+
+ if abs(chartMinMax.max - chartMinMax.min) < 1e-9 {
+ let delta = max(abs(chartMinMax.max) * 0.1, 0.1)
+ return [chartMinMax.max + delta, chartMinMax.max, chartMinMax.max - delta]
+ }
+
+ let midpoint = (chartMinMax.max + chartMinMax.min) * 0.5
+ return [chartMinMax.max, midpoint, chartMinMax.min]
+ }
+
+ private var xScaleIndices: [Int] {
+ let last = max(plotCount - 1, 0)
+ return [0, last / 2, last]
+ }
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 6) {
+ HStack {
+ Text(title)
+ .font(.caption.weight(.semibold))
+ Spacer()
+ Text("min \(format(stats.min)) max \(format(stats.max))")
+ .font(.caption2.monospacedDigit())
+ .foregroundStyle(.secondary)
+ }
+
+ HStack(alignment: .top, spacing: 8) {
+ VStack(alignment: .trailing, spacing: 4) {
+ Text(yAxisLabel)
+ .font(.caption2)
+ .foregroundStyle(.secondary)
+ .frame(maxWidth: .infinity, alignment: .trailing)
+
+ VStack(alignment: .trailing) {
+ Text(format(yScaleValues[0]))
+ Spacer(minLength: 0)
+ Text(format(yScaleValues[1]))
+ Spacer(minLength: 0)
+ Text(format(yScaleValues[2]))
+ }
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .trailing)
+ }
+ .font(.caption2.monospacedDigit())
+ .foregroundStyle(.secondary)
+ .frame(width: yScaleWidth, height: chartHeight, alignment: .trailing)
+
+ VStack(alignment: .leading, spacing: 4) {
+ Sparkline(
+ values: plottedValues,
+ stroke: stroke,
+ yScaleValues: yScaleValues,
+ xScaleIndices: xScaleIndices,
+ confidenceBand: plottedBand
+ )
+ .frame(height: chartHeight)
+ .background(
+ RoundedRectangle(cornerRadius: 8)
+ .fill(palette.inputBackground)
+ .overlay(
+ RoundedRectangle(cornerRadius: 8)
+ .stroke(palette.border, lineWidth: 1)
+ )
+ )
+
+ HStack(spacing: 0) {
+ Text("\(xScaleIndices[0])")
+ Spacer(minLength: 0)
+ Text("\(xScaleIndices[1])")
+ Spacer(minLength: 0)
+ Text("\(xScaleIndices[2])")
+ }
+ .font(.caption2.monospacedDigit())
+ .foregroundStyle(.secondary)
+ .padding(.horizontal, 2)
+
+ Text(xAxisLabel)
+ .font(.caption2)
+ .foregroundStyle(.secondary)
+ }
+ .frame(maxWidth: .infinity)
+ }
+ }
+ .padding(10)
+ .background(
+ RoundedRectangle(cornerRadius: 10)
+ .fill(palette.subpanel)
+ )
+ }
+
+ private func format(_ value: Double) -> String {
+ String(format: "%.4f", value)
+ }
+}
+
+private struct Sparkline: View {
+ let values: [Double]
+ let stroke: Color
+ let yScaleValues: [Double]
+ let xScaleIndices: [Int]
+ let confidenceBand: ConfidenceBandSeries?
+
+ var body: some View {
+ GeometryReader { _ in
+ Canvas { context, size in
+ guard !values.isEmpty else { return }
+ let bandLower = confidenceBand?.lower ?? []
+ let bandUpper = confidenceBand?.upper ?? []
+ let minValue = min(values.min() ?? 0, bandLower.min() ?? (values.min() ?? 0))
+ let maxValue = max(values.max() ?? 0, bandUpper.max() ?? (values.max() ?? 0))
+ let yRange = max(maxValue - minValue, 1e-9)
+ let xStep = values.count > 1 ? size.width / CGFloat(values.count - 1) : 0
+
+ func yPosition(for value: Double) -> CGFloat {
+ let normalized = (value - minValue) / yRange
+ return size.height - CGFloat(normalized) * size.height
+ }
+
+ for yScale in yScaleValues {
+ let y = yPosition(for: yScale)
+ var gridPath = Path()
+ gridPath.move(to: CGPoint(x: 0, y: y))
+ gridPath.addLine(to: CGPoint(x: size.width, y: y))
+ context.stroke(
+ gridPath,
+ with: .color(.secondary.opacity(0.20)),
+ style: StrokeStyle(lineWidth: 1)
+ )
+ }
+
+ for xScale in xScaleIndices {
+ let x = CGFloat(xScale) * xStep
+ var gridPath = Path()
+ gridPath.move(to: CGPoint(x: x, y: 0))
+ gridPath.addLine(to: CGPoint(x: x, y: size.height))
+ context.stroke(
+ gridPath,
+ with: .color(.secondary.opacity(0.16)),
+ style: StrokeStyle(lineWidth: 1)
+ )
+ }
+
+ if let confidenceBand,
+ confidenceBand.lower.count == values.count,
+ confidenceBand.upper.count == values.count {
+ var bandPath = Path()
+ bandPath.move(to: CGPoint(x: 0, y: yPosition(for: confidenceBand.upper[0])))
+ for idx in confidenceBand.upper.indices.dropFirst() {
+ bandPath.addLine(to: CGPoint(x: CGFloat(idx) * xStep, y: yPosition(for: confidenceBand.upper[idx])))
+ }
+ for idx in confidenceBand.lower.indices.reversed() {
+ bandPath.addLine(to: CGPoint(x: CGFloat(idx) * xStep, y: yPosition(for: confidenceBand.lower[idx])))
+ }
+ bandPath.closeSubpath()
+
+ context.fill(
+ bandPath,
+ with: .color(stroke.opacity(0.16))
+ )
+
+ if confidenceBand.mean.count == values.count {
+ var meanPath = Path()
+ meanPath.move(to: CGPoint(x: 0, y: yPosition(for: confidenceBand.mean[0])))
+ for idx in confidenceBand.mean.indices.dropFirst() {
+ meanPath.addLine(to: CGPoint(x: CGFloat(idx) * xStep, y: yPosition(for: confidenceBand.mean[idx])))
+ }
+ context.stroke(
+ meanPath,
+ with: .color(stroke.opacity(0.55)),
+ style: StrokeStyle(lineWidth: 1, lineCap: .round, lineJoin: .round, dash: [4, 3])
+ )
+ }
+ }
+
+ var path = Path()
+ path.move(to: CGPoint(x: 0, y: yPosition(for: values[0])))
+ for idx in values.indices.dropFirst() {
+ path.addLine(to: CGPoint(x: CGFloat(idx) * xStep, y: yPosition(for: values[idx])))
+ }
+
+ context.stroke(
+ path,
+ with: .color(stroke),
+ style: StrokeStyle(lineWidth: 2, lineCap: .round, lineJoin: .round)
+ )
+
+ if let last = values.last {
+ let center = CGPoint(x: size.width, y: yPosition(for: last))
+ let markerRect = CGRect(x: center.x - 3, y: center.y - 3, width: 6, height: 6)
+ context.fill(Path(ellipseIn: markerRect), with: .color(stroke))
+ }
+ }
+ }
+ }
+}
+
+private struct QECSyndromeRoundCard: View {
+ @EnvironmentObject private var store: StudioStore
+ let report: QECReport
+ private var palette: ThemePalette { .resolve(store.theme) }
+
+ private var rounds: [QECReportRound] {
+ report.rounds ?? []
+ }
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 6) {
+ Text("QEC Syndrome Log")
+ .font(.caption.weight(.semibold))
+ Text("X-axis: Round Index Y-axis: Syndrome Value")
+ .font(.caption2)
+ .foregroundStyle(.secondary)
+
+ if rounds.isEmpty {
+ Text("No syndrome rounds available in this run.")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ } else {
+ HStack(spacing: 8) {
+ Text("Round")
+ .frame(width: 48, alignment: .leading)
+ Text("Syndrome")
+ .frame(width: 108, alignment: .trailing)
+ Text("Residual")
+ .frame(width: 108, alignment: .trailing)
+ Text("Lattice")
+ .frame(width: 72, alignment: .trailing)
+ Text("LER")
+ .frame(width: 70, alignment: .trailing)
+ Text("Status")
+ .frame(width: 56, alignment: .trailing)
+ }
+ .font(.caption2.weight(.semibold))
+ .foregroundStyle(.secondary)
+
+ ForEach(Array(rounds.enumerated()), id: \.offset) { index, round in
+ HStack(spacing: 8) {
+ Text("r\(round.round ?? (index + 1))")
+ .frame(width: 48, alignment: .leading)
+ Text(metricText(round.syndromeValue))
+ .frame(width: 108, alignment: .trailing)
+ Text(metricText(round.residual))
+ .frame(width: 108, alignment: .trailing)
+ Text("\(round.nearestLatticeIndex ?? 0)")
+ .frame(width: 72, alignment: .trailing)
+ Text(metricText(cumulativeLER(at: index)))
+ .frame(width: 70, alignment: .trailing)
+ Text((round.logicalPass == true) ? "PASS" : "FAIL")
+ .frame(width: 56, alignment: .trailing)
+ .foregroundStyle((round.logicalPass == true) ? Color.green : Color.orange)
+ }
+ .font(.caption2.monospacedDigit())
+ }
+ }
+ }
+ .padding(10)
+ .background(
+ RoundedRectangle(cornerRadius: 10)
+ .fill(palette.subpanel)
+ )
+ }
+
+ private func cumulativeLER(at index: Int) -> Double? {
+ guard index >= 0, index < rounds.count else { return nil }
+ var failCount = 0
+ var observed = 0
+ for offset in 0...index {
+ guard let logicalPass = rounds[offset].logicalPass else { continue }
+ observed += 1
+ if !logicalPass {
+ failCount += 1
+ }
+ }
+ guard observed > 0 else { return nil }
+ return Double(failCount) / Double(observed)
+ }
+
+ private func metricText(_ value: Double?) -> String {
+ guard let value, value.isFinite else { return "n/a" }
+ return String(format: "%.5f", value)
+ }
+}
+
+private struct QECLatticeCheckerCard: View {
+ @EnvironmentObject private var store: StudioStore
+ let report: QECReport
+ private var palette: ThemePalette { .resolve(store.theme) }
+
+ private struct Point: Identifiable {
+ let id: Int
+ let round: Int
+ let syndrome: Double
+ let target: Double
+ let spacing: Double
+ let pass: Bool
+ }
+
+ private var points: [Point] {
+ let rounds = report.rounds ?? []
+ var extracted: [Point] = []
+ extracted.reserveCapacity(rounds.count)
+ for (index, round) in rounds.enumerated() {
+ guard let syndrome = round.syndromeValue, syndrome.isFinite else { continue }
+ guard let spacing = round.latticeSpacing, spacing.isFinite, spacing > 0 else { continue }
+ guard let targetIndex = round.targetLatticeIndex else { continue }
+ let logicalPass = round.logicalPass ?? false
+ extracted.append(
+ Point(
+ id: index,
+ round: round.round ?? (index + 1),
+ syndrome: syndrome,
+ target: Double(targetIndex) * spacing,
+ spacing: spacing,
+ pass: logicalPass
+ )
+ )
+ }
+ return extracted.sorted { $0.round < $1.round }
+ }
+
+ private var baseSpacing: Double? {
+ points.first?.spacing
+ }
+
+ private var baseTarget: Double? {
+ points.first?.target
+ }
+
+ private var domain: (min: Double, max: Double)? {
+ guard !points.isEmpty else { return nil }
+ let values = points.map(\.syndrome) + points.map(\.target)
+ guard let minValue = values.min(), let maxValue = values.max() else { return nil }
+ let spacing = baseSpacing ?? 1.0
+ let span = maxValue - minValue
+ let padding = max(0.2, max(0.6 * spacing, 0.12 * span))
+ return (minValue - padding, maxValue + padding)
+ }
+
+ private var latticeCenters: [Double] {
+ guard let spacing = baseSpacing, let domain else { return [] }
+ let minIndex = Int(floor(domain.min / spacing))
+ let maxIndex = Int(ceil(domain.max / spacing))
+ guard minIndex <= maxIndex else { return [] }
+ let clampedMin = max(minIndex, -128)
+ let clampedMax = min(maxIndex, 128)
+ if clampedMin > clampedMax { return [] }
+ return (clampedMin...clampedMax).map { Double($0) * spacing }
+ }
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 6) {
+ Text("QEC Lattice Checker")
+ .font(.caption.weight(.semibold))
+ Text("X-axis: Lattice Coordinate Y-axis: Round Index")
+ .font(.caption2)
+ .foregroundStyle(.secondary)
+
+ if points.isEmpty {
+ Text("Load and run a QEC circuit to visualize lattice/syndrome positions.")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ } else {
+ GeometryReader { proxy in
+ Canvas { context, size in
+ guard let domain else { return }
+ let width = max(size.width, 1)
+ let top: CGFloat = 8
+ let bottom: CGFloat = size.height - 8
+ let height = max(bottom - top, 1)
+ let scale = max(domain.max - domain.min, 1e-9)
+
+ func x(_ value: Double) -> CGFloat {
+ CGFloat((value - domain.min) / scale) * width
+ }
+
+ func y(_ index: Int) -> CGFloat {
+ if points.count <= 1 { return top + (height / 2) }
+ return top + (CGFloat(index) * height / CGFloat(points.count - 1))
+ }
+
+ if let spacing = baseSpacing, let target = baseTarget {
+ let threshold = 0.25 * spacing
+ let left = x(target - threshold)
+ let right = x(target + threshold)
+ let rect = CGRect(
+ x: min(left, right),
+ y: top,
+ width: abs(right - left),
+ height: height
+ )
+ context.fill(Path(rect), with: .color(Color.green.opacity(0.10)))
+ }
+
+ for center in latticeCenters {
+ var line = Path()
+ let xPos = x(center)
+ line.move(to: CGPoint(x: xPos, y: top))
+ line.addLine(to: CGPoint(x: xPos, y: bottom))
+ context.stroke(
+ line,
+ with: .color(Color.secondary.opacity(0.35)),
+ style: StrokeStyle(lineWidth: 1)
+ )
+ }
+
+ if let target = baseTarget {
+ var targetLine = Path()
+ let xPos = x(target)
+ targetLine.move(to: CGPoint(x: xPos, y: top))
+ targetLine.addLine(to: CGPoint(x: xPos, y: bottom))
+ context.stroke(
+ targetLine,
+ with: .color(Color.blue.opacity(0.85)),
+ style: StrokeStyle(lineWidth: 2)
+ )
+ }
+
+ if let first = points.first {
+ var trajectory = Path()
+ trajectory.move(to: CGPoint(x: x(first.syndrome), y: y(0)))
+ for (index, point) in points.enumerated().dropFirst() {
+ trajectory.addLine(to: CGPoint(x: x(point.syndrome), y: y(index)))
+ }
+ context.stroke(
+ trajectory,
+ with: .color(Color.pink.opacity(0.75)),
+ style: StrokeStyle(lineWidth: 1.8, lineCap: .round, lineJoin: .round)
+ )
+ }
+
+ for (index, point) in points.enumerated() {
+ let center = CGPoint(x: x(point.syndrome), y: y(index))
+ let marker = CGRect(x: center.x - 3.0, y: center.y - 3.0, width: 6.0, height: 6.0)
+ context.fill(
+ Path(ellipseIn: marker),
+ with: .color(point.pass ? Color.green : Color.orange)
+ )
+ }
+ }
+ }
+ .frame(height: 165)
+ .background(
+ RoundedRectangle(cornerRadius: 8)
+ .fill(palette.inputBackground)
+ .overlay(
+ RoundedRectangle(cornerRadius: 8)
+ .stroke(palette.border, lineWidth: 1)
+ )
+ )
+
+ HStack(spacing: 12) {
+ Text("target=\(metricText(baseTarget))")
+ Text("spacing=\(metricText(baseSpacing))")
+ if let domain {
+ Text("domain=[\(metricText(domain.min)), \(metricText(domain.max))]")
+ }
+ }
+ .font(.caption2.monospacedDigit())
+ .foregroundStyle(.secondary)
+ }
+ }
+ .padding(10)
+ .background(
+ RoundedRectangle(cornerRadius: 10)
+ .fill(palette.subpanel)
+ )
+ }
+
+ private func metricText(_ value: Double?) -> String {
+ guard let value, value.isFinite else { return "n/a" }
+ return String(format: "%.5f", value)
+ }
+}
+
+private struct GateHistogramCard: View {
+ @EnvironmentObject private var store: StudioStore
+ let items: [(String, Int)]
+ private var palette: ThemePalette { .resolve(store.theme) }
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 6) {
+ Text("Gate Frequency")
+ .font(.caption.weight(.semibold))
+ Text("X-axis: Gate Type Y-axis: Frequency")
+ .font(.caption2)
+ .foregroundStyle(.secondary)
+ if items.isEmpty {
+ Text("No gate frames available.")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ } else {
+ let maxCount = items.map { $0.1 }.max() ?? 1
+ ForEach(items, id: \.0) { gateType, count in
+ HStack(spacing: 8) {
+ Text(gateType)
+ .font(.caption.monospaced())
+ .frame(width: 120, alignment: .leading)
+ GeometryReader { proxy in
+ let width = proxy.size.width * CGFloat(count) / CGFloat(maxCount)
+ RoundedRectangle(cornerRadius: 4)
+ .fill(Color.cyan.opacity(0.75))
+ .frame(width: max(2, width), height: 12, alignment: .leading)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
+ .frame(height: 12)
+ Text("\(count)")
+ .font(.caption.monospacedDigit())
+ .frame(width: 36, alignment: .trailing)
+ }
+ .frame(height: 16)
+ }
+ }
+ }
+ .padding(10)
+ .background(
+ RoundedRectangle(cornerRadius: 10)
+ .fill(palette.subpanel)
+ )
+ }
+}
+
+private struct RunOutputSummaryCard: View {
+ @EnvironmentObject private var store: StudioStore
+ let lines: [String]
+ let statusText: String
+ private var palette: ThemePalette { .resolve(store.theme) }
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 8) {
+ Text("Run Output")
+ .font(.caption.weight(.semibold))
+ Text("Status: \(statusText)")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+
+ VStack(alignment: .leading, spacing: 4) {
+ ForEach(Array(lines.enumerated()), id: \.offset) { _, line in
+ Text(line)
+ .font(.system(.caption, design: .monospaced))
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
+ }
+ .padding(8)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .background(
+ RoundedRectangle(cornerRadius: 8)
+ .fill(palette.inputBackground)
+ .overlay(
+ RoundedRectangle(cornerRadius: 8)
+ .stroke(palette.border, lineWidth: 1)
+ )
+ )
+ }
+ .padding(10)
+ .background(
+ RoundedRectangle(cornerRadius: 10)
+ .fill(palette.subpanel)
+ )
+ }
+}
+
+private struct MetricCard: View {
+ @EnvironmentObject private var store: StudioStore
+ let title: String
+ let value: String
+ let subtitle: String
+ private var palette: ThemePalette { .resolve(store.theme) }
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 8) {
+ Text(title)
+ .font(.caption.weight(.semibold))
+ .foregroundStyle(.secondary)
+ Text(value)
+ .font(.title2.weight(.bold))
+ Text(subtitle)
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .padding(10)
+ .background(
+ RoundedRectangle(cornerRadius: 10)
+ .fill(palette.subpanel)
+ )
+ }
+}
+
+private struct AuditTrailPanelView: View {
+ @EnvironmentObject private var store: StudioStore
+ private var palette: ThemePalette { .resolve(store.theme) }
+
+ var body: some View {
+ ScrollView {
+ VStack(alignment: .leading, spacing: 4) {
+ ForEach(Array(store.auditTrail.enumerated().reversed()), id: \.offset) { _, line in
+ Text(line)
+ .font(.system(.caption, design: .monospaced))
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
+ }
+ .padding(8)
+ }
+ .background(
+ RoundedRectangle(cornerRadius: 10)
+ .fill(palette.subpanel)
+ )
+ }
+}
+
+private struct CardSection: View {
+ @EnvironmentObject private var store: StudioStore
+ var title: String? = nil
+ @ViewBuilder var content: Content
+ private var palette: ThemePalette { .resolve(store.theme) }
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 10) {
+ if let title {
+ Text(title)
+ .font(.title3.weight(.semibold))
+ }
+ content
+ }
+ .padding(10)
+ .frame(maxHeight: .infinity, alignment: .top)
+ .background(
+ RoundedRectangle(cornerRadius: 12)
+ .fill(palette.panel)
+ .overlay(
+ RoundedRectangle(cornerRadius: 12)
+ .stroke(palette.border, lineWidth: 1)
+ )
+ .shadow(color: Color.black.opacity(0.04), radius: 6, x: 0, y: 2)
+ )
+ .padding(8)
+ }
+}
+
+private struct LabeledContentBlock: View {
+ let title: String
+ @ViewBuilder var content: Content
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 4) {
+ Text(title)
+ .font(.caption.weight(.semibold))
+ .foregroundStyle(.secondary)
+ .lineLimit(1)
+ content
+ }
+ }
+}
+
+private struct KeyValueRow: View {
+ let title: String
+ let value: String
+
+ var body: some View {
+ HStack {
+ Text(title)
+ .foregroundStyle(.secondary)
+ Spacer()
+ Text(value)
+ .font(.caption.monospaced())
+ .lineLimit(1)
+ .truncationMode(.middle)
+ }
+ .font(.caption)
+ }
+}
+
+private struct LabeledField: View {
+ let title: String
+ @Binding var text: String
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 4) {
+ Text(title)
+ .font(.caption.weight(.semibold))
+ .foregroundStyle(.secondary)
+ TextField(title, text: $text)
+ .textFieldStyle(.roundedBorder)
+ .font(.caption)
+ }
+ }
+}
+
+private struct LabeledPicker: View {
+ let title: String
+ @Binding var selection: BackendSelection
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 4) {
+ Text(title)
+ .font(.caption.weight(.semibold))
+ .foregroundStyle(.secondary)
+ Picker(title, selection: $selection) {
+ ForEach(BackendSelection.allCases) { backend in
+ Text(backend.title).tag(backend)
+ }
+ }
+ .pickerStyle(.segmented)
+ .labelsHidden()
+ }
+ }
+}
+
+private struct LabeledRuntimeEnginePicker: View {
+ let title: String
+ @Binding var selection: RuntimeEngineSelection
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 4) {
+ Text(title)
+ .font(.caption.weight(.semibold))
+ .foregroundStyle(.secondary)
+ Picker(title, selection: $selection) {
+ ForEach(RuntimeEngineSelection.allCases) { runtimeEngine in
+ Text(runtimeEngine.title).tag(runtimeEngine)
+ }
+ }
+ .pickerStyle(.segmented)
+ .labelsHidden()
+ }
+ }
+}
+
+private struct NumericParameterEditor: View {
+ let title: String
+ @Binding var value: Double
+ let suggestedRange: ClosedRange?
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 4) {
+ Text(title)
+ .font(.caption)
+ HStack(spacing: 8) {
+ TextField(
+ title,
+ value: $value,
+ format: .number.precision(.fractionLength(0...6))
+ )
+ .textFieldStyle(.roundedBorder)
+ .font(.caption.monospaced())
+ .frame(width: 120)
+
+ if let suggestedRange {
+ Slider(value: $value, in: suggestedRange)
+ }
+ }
+ if let suggestedRange {
+ Text("Suggested range: \(format(suggestedRange.lowerBound)) ... \(format(suggestedRange.upperBound))")
+ .font(.caption2.monospacedDigit())
+ .foregroundStyle(.secondary)
+ } else {
+ Text(format(value))
+ .font(.caption2.monospacedDigit())
+ .foregroundStyle(.secondary)
+ }
+ }
+ }
+
+ private func format(_ value: Double) -> String {
+ String(format: "%.6f", value)
+ }
+}
diff --git a/core-swift/Tests/SchroSIMStudioTests/StudioStoreQECTests.swift b/core-swift/Tests/SchroSIMStudioTests/StudioStoreQECTests.swift
new file mode 100644
index 0000000..4c1c493
--- /dev/null
+++ b/core-swift/Tests/SchroSIMStudioTests/StudioStoreQECTests.swift
@@ -0,0 +1,35 @@
+import XCTest
+@testable import schrosim_studio
+
+@MainActor
+final class StudioStoreQECTests: XCTestCase {
+ func testQECProjectAutoBindsPolicyToBackend() {
+ let store = StudioStore()
+
+ store.workspace = "Quantum Foundry"
+ store.project = "Quantum Error Correction"
+ XCTAssertEqual(store.backend, .hybrid)
+ XCTAssertEqual(store.selectedKPIPolicy?.id, "cv_qec_gaussian_v1")
+
+ store.backend = .fock
+ XCTAssertEqual(store.selectedKPIPolicy?.id, "cv_fock_validation_v1")
+
+ store.backend = .gaussian
+ XCTAssertEqual(store.selectedKPIPolicy?.id, "cv_qec_gaussian_v1")
+
+ store.project = "Boson Sampling"
+ XCTAssertEqual(store.selectedKPIPolicy?.id, "cv_gaussian_production_v1")
+ }
+
+ func testQECPolicyThresholdsLoadedFromCatalog() {
+ let store = StudioStore()
+ guard let policy = store.kpiPolicies.first(where: { $0.id == "cv_qec_gaussian_v1" }) else {
+ XCTFail("Expected cv_qec_gaussian_v1 policy in KPI catalog")
+ return
+ }
+
+ XCTAssertEqual(policy.thresholds.maxLogicalErrorRate ?? -1.0, 0.34, accuracy: 1e-12)
+ XCTAssertEqual(policy.thresholds.minSuppressionFactor ?? -1.0, 1.0, accuracy: 1e-12)
+ XCTAssertEqual(policy.thresholds.minBreakEvenGain ?? -1.0, 0.0, accuracy: 1e-12)
+ }
+}
diff --git a/core-swift/Tests/SchroSIMTests/unit/backend_regression_tests.swift b/core-swift/Tests/SchroSIMTests/unit/backend_regression_tests.swift
index 2b09f9a..e4f2ec3 100644
--- a/core-swift/Tests/SchroSIMTests/unit/backend_regression_tests.swift
+++ b/core-swift/Tests/SchroSIMTests/unit/backend_regression_tests.swift
@@ -15,7 +15,7 @@ final class BackendRegressionTests: XCTestCase {
XCTAssertTrue(approxEqual(gaussian.mean, hybrid.mean, tol: 1e-12))
XCTAssertTrue(LA.approxEqual(gaussian.cov, hybrid.cov, tol: 1e-12))
- // Reference value from examples/foundry_loss_map.json circuit.
+ // Reference value from docs/examples/foundry_loss_map.json circuit.
XCTAssertEqual(meanPhotonNumber(gaussian), 0.5647815555903229, accuracy: 1e-12)
}
diff --git a/docs/01-overview.md b/docs/01-overview.md
index 3c54595..2f1b190 100644
--- a/docs/01-overview.md
+++ b/docs/01-overview.md
@@ -7,6 +7,7 @@ SchroSIM is a continuous-variable photonic quantum simulation stack with:
- a Swift core simulation SDK,
- a Swift CLI (`schrosim-cli`) for execution and tracing,
- a Rust runtime (`schrosim-core`) for parity/benchmark workflows and runtime operations.
+- SchroSIM Studio, an open-source SwiftUI surface for circuit design, validation evidence, and replay-oriented analysis.
## Who It Is For
@@ -16,8 +17,8 @@ SchroSIM is a continuous-variable photonic quantum simulation stack with:
## Product Boundary
-- Public repo: CLI/core/docs.
-- Enterprise-specific materials: isolated under `enterprise/`.
+- Public repo: CLI/core/docs/Studio UI.
+- Public examples, config, benchmarks, and assets are kept under `docs/`.
## Why This Exists
diff --git a/docs/02-quickstart.md b/docs/02-quickstart.md
index 69663cb..0e173a3 100644
--- a/docs/02-quickstart.md
+++ b/docs/02-quickstart.md
@@ -23,8 +23,8 @@ Python is required for helper scripts, but there are currently no third-party Py
## First Demo Runs
```bash
-schrosim run examples/runtime_default_foundry.json --backend hybrid
-schrosim run examples/cv/qec_single_logical_gkp_memory_mvp.json --backend hybrid
+schrosim run docs/examples/runtime_default_foundry.json --backend hybrid
+schrosim run docs/examples/cv/qec_single_logical_gkp_memory_mvp.json --backend hybrid
```
## Optional Contributor Workflow (CLion)
diff --git a/docs/03-architecture.md b/docs/03-architecture.md
index 00a6ff7..934218a 100644
--- a/docs/03-architecture.md
+++ b/docs/03-architecture.md
@@ -17,9 +17,9 @@ JSON Circuit Input
- Swift CLI: `core-swift/Sources/schrosim-cli`
- Rust runtime CLI: `core-rust/schrosim-core`
- Config and governance:
- - `config/foundry_registry.json`
- - `config/trace_rbac_policy.json`
- - `config/kpi_policies.json`
+ - `docs/config/foundry_registry.json`
+ - `docs/config/trace_rbac_policy.json`
+ - `docs/config/kpi_policies.json`
## Public Module Map
diff --git a/docs/04-circuit-ir-and-schema.md b/docs/04-circuit-ir-and-schema.md
index d3c3ffc..55d3cdc 100644
--- a/docs/04-circuit-ir-and-schema.md
+++ b/docs/04-circuit-ir-and-schema.md
@@ -41,6 +41,6 @@ Primary IR sources:
## Example Inputs
-- `examples/runtime_default_foundry.json`
-- `examples/fock_injection_smoke.json`
-- `examples/cv/qec_single_logical_gkp_memory_mvp.json`
+- `docs/examples/runtime_default_foundry.json`
+- `docs/examples/fock_injection_smoke.json`
+- `docs/examples/cv/qec_single_logical_gkp_memory_mvp.json`
diff --git a/docs/05-compiler-and-foundry.md b/docs/05-compiler-and-foundry.md
index 11c1433..b247e64 100644
--- a/docs/05-compiler-and-foundry.md
+++ b/docs/05-compiler-and-foundry.md
@@ -27,7 +27,7 @@ Reference implementation:
Registry-backed production profiles:
-- `config/foundry_registry.json`
+- `docs/config/foundry_registry.json`
- `foundry-admin add-draft`
- `foundry-admin promote` (approval/signing)
diff --git a/docs/08-tracing-observability-reproducibility.md b/docs/08-tracing-observability-reproducibility.md
index 9b1db57..2e908bd 100644
--- a/docs/08-tracing-observability-reproducibility.md
+++ b/docs/08-tracing-observability-reproducibility.md
@@ -18,7 +18,7 @@ Trace flows support:
Policies:
-- `config/trace_rbac_policy.json`
+- `docs/config/trace_rbac_policy.json`
## Determinism Contract
diff --git a/docs/09-validation-and-benchmarks.md b/docs/09-validation-and-benchmarks.md
index 3217b4c..7309f4e 100644
--- a/docs/09-validation-and-benchmarks.md
+++ b/docs/09-validation-and-benchmarks.md
@@ -37,9 +37,9 @@ bash scripts/compute-benchmark-scaling.sh ...
## Baseline Artifacts
-- `benchmarks/schrosim-core-baseline.json`
-- `benchmarks/schrosim-core-scaling-baseline.json`
-- `benchmarks/compute-scaling-baseline.json`
+- `docs/benchmarks/schrosim-core-baseline.json`
+- `docs/benchmarks/schrosim-core-scaling-baseline.json`
+- `docs/benchmarks/compute-scaling-baseline.json`
## Benchmark Interpretation Guide
@@ -83,6 +83,6 @@ Recommended gate order:
1. `bash scripts/cv-validation-suite.sh --runtime both`
2. `bash scripts/trace-slo-check.sh --max-gates 512 --max-frame-ms 50`
3. `bash scripts/compute-benchmark.sh ... --max-p95-ms `
-4. `bash scripts/compute-benchmark-scaling.sh --baseline benchmarks/compute-scaling-baseline.json --max-regression-pct 180`
+4. `bash scripts/compute-benchmark-scaling.sh --baseline docs/benchmarks/compute-scaling-baseline.json --max-regression-pct 180`
If step 4 fails on routing mismatch, treat it as a policy/routing regression, not a raw latency fluctuation.
diff --git a/docs/12-worked-examples.md b/docs/12-worked-examples.md
index 95d35ea..1b7848f 100644
--- a/docs/12-worked-examples.md
+++ b/docs/12-worked-examples.md
@@ -2,22 +2,22 @@
## Example 1: Runtime Default Foundry
-- Input: `examples/runtime_default_foundry.json`
+- Input: `docs/examples/runtime_default_foundry.json`
- Goal: minimal end-to-end run path.
## Example 2: Fock Injection Smoke
-- Input: `examples/fock_injection_smoke.json`
+- Input: `docs/examples/fock_injection_smoke.json`
- Goal: demonstrate backend routing to Fock-compatible execution.
## Example 3: Foundry Loss Map
-- Input: `examples/foundry_loss_map.json`
+- Input: `docs/examples/foundry_loss_map.json`
- Goal: foundry validation and injected mode-loss behavior.
## Example 4: GKP QEC Memory MVP
-- Input: `examples/cv/qec_single_logical_gkp_memory_mvp.json`
+- Input: `docs/examples/cv/qec_single_logical_gkp_memory_mvp.json`
- Goal: measurement-driven decode/correction loop and QEC metrics.
## Example Playbook (Command + Expected Result)
@@ -27,7 +27,7 @@
Command:
```bash
-swift run schrosim-cli run examples/runtime_default_foundry.json
+swift run schrosim-cli run docs/examples/runtime_default_foundry.json
```
Expected backend resolution:
@@ -52,7 +52,7 @@ Interpretation:
Command:
```bash
-swift run schrosim-cli run examples/fock_injection_smoke.json
+swift run schrosim-cli run docs/examples/fock_injection_smoke.json
```
Expected backend resolution:
@@ -76,7 +76,7 @@ Interpretation:
Command:
```bash
-swift run schrosim-cli run examples/foundry_loss_map.json
+swift run schrosim-cli run docs/examples/foundry_loss_map.json
```
Expected backend resolution:
@@ -100,7 +100,7 @@ Interpretation:
Command:
```bash
-swift run schrosim-cli run examples/cv/qec_single_logical_gkp_memory_mvp.json
+swift run schrosim-cli run docs/examples/cv/qec_single_logical_gkp_memory_mvp.json
```
Expected backend resolution:
diff --git a/docs/13-developer-guide.md b/docs/13-developer-guide.md
index 1a412b0..6ad3487 100644
--- a/docs/13-developer-guide.md
+++ b/docs/13-developer-guide.md
@@ -5,8 +5,9 @@
- Swift package manifest: `Package.swift`
- Rust workspace manifest: `Cargo.toml`
- Swift core and CLI: `core-swift/Sources/*`
+- SchroSIM Studio UI: `core-swift/Sources/schrosim-studio/*`
- Rust runtime: `core-rust/schrosim-core/*`
-- Scripts and baselines: `scripts/*`, `benchmarks/*`
+- Scripts and baselines: `scripts/*`, `docs/benchmarks/*`
## Build and Test
@@ -31,9 +32,9 @@ See detailed guide:
## Contribution and Security Policies
-- [CONTRIBUTING.md](https://github.com/DennisWayo/SchroSIM/blob/master/CONTRIBUTING.md)
-- [CODE_OF_CONDUCT.md](https://github.com/DennisWayo/SchroSIM/blob/master/CODE_OF_CONDUCT.md)
-- [SECURITY.md](https://github.com/DennisWayo/SchroSIM/blob/master/SECURITY.md)
+- [CONTRIBUTING.md](https://github.com/Gottesman-Software/SchroSIM/blob/master/CONTRIBUTING.md)
+- [CODE_OF_CONDUCT.md](https://github.com/Gottesman-Software/SchroSIM/blob/master/CODE_OF_CONDUCT.md)
+- [SECURITY.md](https://github.com/Gottesman-Software/SchroSIM/blob/master/SECURITY.md)
## Public Release Process (Current)
@@ -50,8 +51,8 @@ Until full automation is added, use this manual flow for public releases:
- Swift CLI version constant in `core-swift/Sources/schrosim-cli/main.swift`
- Rust CLI version constant in `core-rust/schrosim-core/src/main.rs`
4. Public-scope hygiene:
- - ensure `enterprise/` private material remains ignored,
- - ensure README/docs do not depend on enterprise-only assets.
+ - ensure public UI source remains in `core-swift/Sources/schrosim-studio/`,
+ - ensure public examples, config, benchmarks, and assets live under `docs/`.
5. Tag and publish:
- create release tag (`vX.Y.Z`),
- publish release notes summarizing API/CLI changes, validation coverage, and known limitations.
diff --git a/docs/14-schrosim-studio.md b/docs/14-schrosim-studio.md
new file mode 100644
index 0000000..2d146b0
--- /dev/null
+++ b/docs/14-schrosim-studio.md
@@ -0,0 +1,27 @@
+# SchroSIM Studio
+
+SchroSIM Studio is the open-source UI surface for building and inspecting photonic circuit studies before execution through the SchroSIM core runtime.
+
+## What It Covers
+
+- Project-scoped circuit layouts and reusable presets.
+- Backend-aware execution policy selection.
+- Foundry-profile and runtime validation evidence.
+- QEC-oriented result views, diagnostics, and replay summaries.
+- Export paths that keep circuit, runtime, and report artifacts inspectable.
+
+## Run From Source
+
+```bash
+swift run schrosim-studio
+```
+
+The Studio target is defined in the root Swift package and uses the same public SchroSIM core library as the CLI.
+
+## Source Layout
+
+- `core-swift/Sources/schrosim-studio/` contains the SwiftUI app.
+- `core-swift/Tests/SchroSIMStudioTests/` contains Studio-focused Swift tests.
+- `docs/config/profiles/` contains public sample profiles used by the UI.
+
+Generated exports and local workflow state are intentionally not tracked.
diff --git a/docs/DEBUGGING.md b/docs/DEBUGGING.md
index 67844e4..f5cb723 100644
--- a/docs/DEBUGGING.md
+++ b/docs/DEBUGGING.md
@@ -1,78 +1,56 @@
# SchroSIM Debugging Guide
-## 1) Debug Rust GUI in CLion
-
-### Prerequisites
-- Open the repo root (`SchroSIM`) in CLion.
-- Use the Cargo project at `Cargo.toml` (workspace root).
-
-### Run configuration
-1. Create a new `Cargo` run configuration:
- - `Command`: `run`
- - `Package`: `schrosim-gui`
- - `Working directory`: `$ProjectFileDir$`
-2. Add environment variables:
- - `RUST_BACKTRACE=1` (also enforced via `.cargo/config.toml`)
- - Optional for Swift attach flow: `SCHROSIM_WAIT_SWIFT_DEBUGGER=1`
-3. Start with `Debug` (not `Run`).
-
-### Breakpoints to set
-- `core-rust/schrosim-gui/src/main.rs` at `fn run_cli(...)`
-- `core-rust/schrosim-gui/src/main.rs` at `fn parse_cli_output(...)` (JSON parsing logic)
-- `core-rust/schrosim-gui/src/main.rs` inside `impl eframe::App for SchroSimApp` -> `fn update(...)`
-
-### Verify breakpoints are hit
-1. Launch debugger in CLion.
-2. In GUI, click `Run circuit`.
-3. Confirm debugger stops in `update`, then in `run_cli`, then in `parse_cli_output`.
-
-## 2) Attach Xcode Debugger to Swift CLI Spawned by Rust GUI
-
-### Enable wait mode
-- Start Rust GUI from CLion with:
- - `SCHROSIM_WAIT_SWIFT_DEBUGGER=1`
-
-When GUI invokes CLI, `schrosim-cli` is launched with `--wait-debugger` and pauses itself via `SIGSTOP`.
-
-### Attach from Xcode
-1. Open Xcode.
-2. Choose `Debug` -> `Attach to Process by PID or Name...`.
-3. Enter `schrosim-cli` and attach.
-4. Press `Continue` in Xcode to resume the paused CLI process.
-
-### Swift breakpoints
-- `core-swift/Sources/schrosim-cli/main.swift` at `waitForDebuggerIfRequested(...)`
-- `core-swift/Sources/schrosim-cli/main.swift` at JSON decode line in `handleRun(...)`
-- `core-swift/Sources/schrosim-cli/main.swift` at `emitJSON(...)`
-
-### Verify
-1. With Rust GUI running under CLion debug, click `Run circuit`.
-2. Attach Xcode to `schrosim-cli`.
-3. Confirm Swift breakpoints trigger, then Rust returns to `parse_cli_output`.
-
-## 3) Rust Workspace Layout
-
-Rust is configured as a workspace at repo root, with crates in `core-rust/`:
-
-```text
-Cargo.toml # workspace + dev profile (debug symbols on)
-core-rust/schrosim-gui/ # member crate
+## Swift CLI
+
+Use the CLI path for reproducible debugging and CI parity:
+
+```bash
+swift run schrosim-cli run docs/examples/runtime_default_foundry.json --backend hybrid
+swift run schrosim-cli trace-stream docs/examples/foundry_loss_map.json --backend hybrid --stream-format ndjson
+```
+
+Useful breakpoints:
+
+- `core-swift/Sources/schrosim-cli/main.swift` at input parsing and command dispatch.
+- `core-swift/Sources/SchroSIM/src/compiler/ir/circuit_ir.swift` for circuit validation.
+- `core-swift/Sources/SchroSIM/src/compiler/foundry/foundry_spec.swift` for foundry policy checks.
+- `core-swift/Sources/SchroSIM/src/core/simulator.swift` for backend execution.
+
+## SwiftUI Studio
+
+Run the public Studio UI from the root package:
+
+```bash
+swift run schrosim-studio
```
-To add more Rust crates later, create a crate directory and add it to `members` in root `Cargo.toml`.
+Useful breakpoints:
+
+- `core-swift/Sources/schrosim-studio/SchroSIMStudioApp.swift` for app launch.
+- `core-swift/Sources/schrosim-studio/StudioStore.swift` for workspace state, validation, and replay logic.
+- `core-swift/Sources/schrosim-studio/CLIService.swift` for CLI execution boundaries.
+- `core-swift/Sources/schrosim-studio/StudioViews.swift` for UI interactions.
-## 4) Fix `swift test` / XCTest in CLion
+## Tests
-If `swift test` fails with `no such module 'XCTest'`, CLion is typically using only Command Line Tools (`/Library/Developer/CommandLineTools`).
+Run the Swift test suite:
+
+```bash
+swift test
+```
+
+Run the Rust runtime tests:
+
+```bash
+cargo test -p schrosim-core
+```
-### Required setup
+## CLion and Xcode Notes
-1. Install full Xcode (App Store or Developer download).
-2. In CLion, use the shared run config: `Swift Test (core-swift)`.
-3. If your Xcode path differs, edit `DEVELOPER_DIR` in that run config.
+If `swift test` fails with `no such module 'XCTest'`, CLion is usually using only Command Line Tools rather than full Xcode.
-Shared assets in repo:
-- Script: `scripts/swift-test.sh`
-- Run config: `.run/Swift Test (core-swift).run.xml`
+Recommended setup:
-The script auto-selects `Xcode.app` or `Xcode-beta.app`, sets `SDKROOT=macosx`, and runs `swift test`.
+- Install full Xcode.
+- Use `scripts/swift-test.sh` for local test runs when IDE environment variables are inconsistent.
+- Keep CLI commands as the reference path for bug reports and CI reproduction.
diff --git a/docs/README.md b/docs/README.md
index 4ea7ace..b000fad 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -17,11 +17,12 @@ This directory is the working documentation set for SchroSIM.
11. [Mathematical Proofs](11-mathematical-proofs.md)
12. [Worked Examples](12-worked-examples.md)
13. [Developer Guide](13-developer-guide.md)
+14. [SchroSIM Studio](14-schrosim-studio.md)
## Scope Notes
-- Public repo scope: CLI/core/docs workflows.
-- Enterprise-only material is kept under `enterprise/`.
+- Public repo scope: CLI/core/docs/Studio UI workflows.
+- Public examples, config, benchmarks, and assets live under `docs/`.
- Existing debug playbook: [DEBUGGING.md](DEBUGGING.md).
## Suggested Reading Order
diff --git a/assets/schrosim-logo.png b/docs/assets/schrosim-logo.png
similarity index 100%
rename from assets/schrosim-logo.png
rename to docs/assets/schrosim-logo.png
diff --git a/benchmarks/compute-scaling-baseline.json b/docs/benchmarks/compute-scaling-baseline.json
similarity index 100%
rename from benchmarks/compute-scaling-baseline.json
rename to docs/benchmarks/compute-scaling-baseline.json
diff --git a/benchmarks/schrosim-core-baseline.json b/docs/benchmarks/schrosim-core-baseline.json
similarity index 100%
rename from benchmarks/schrosim-core-baseline.json
rename to docs/benchmarks/schrosim-core-baseline.json
diff --git a/benchmarks/schrosim-core-scaling-baseline.json b/docs/benchmarks/schrosim-core-scaling-baseline.json
similarity index 100%
rename from benchmarks/schrosim-core-scaling-baseline.json
rename to docs/benchmarks/schrosim-core-scaling-baseline.json
diff --git a/config/foundry_registry.json b/docs/config/foundry_registry.json
similarity index 91%
rename from config/foundry_registry.json
rename to docs/config/foundry_registry.json
index b8bd0cf..6ed8bd0 100644
--- a/config/foundry_registry.json
+++ b/docs/config/foundry_registry.json
@@ -18,7 +18,7 @@
"timestamp" : "2026-04-24T09:56:31.282Z"
}
],
- "profile_id" : "enterprise-default",
+ "profile_id" : "studio-default",
"signature" : "3b16e307c522022e23757e677a89e42d17e90ea5d039c0c837c1d740d776c471",
"spec" : {
"allow_measurements" : true,
@@ -29,7 +29,7 @@
"mode_loss_eta" : [
0.995
],
- "name" : "enterprise-default-v1"
+ "name" : "studio-default-v1"
},
"status" : "approved",
"valid_from" : "2026-04-24T09:56:18.108Z",
diff --git a/config/kpi_policies.json b/docs/config/kpi_policies.json
similarity index 91%
rename from config/kpi_policies.json
rename to docs/config/kpi_policies.json
index a353b62..f519965 100644
--- a/config/kpi_policies.json
+++ b/docs/config/kpi_policies.json
@@ -7,7 +7,7 @@
"target_representation": "gaussian_phase_space",
"summary": "Production readiness policy for Gaussian CV circuits using deterministic parity plus calibrated-noise robustness thresholds.",
"references": [
- "Internal enterprise qualification standard: CV-GAUSS-PROD-v1"
+ "Open-source studio qualification standard: CV-GAUSS-PROD-v1"
],
"thresholds": {
"min_success_rate": 0.95,
@@ -27,7 +27,7 @@
"target_representation": "gaussian_phase_space",
"summary": "QEC qualification policy for Gaussian/Hybrid backends using logical error rate, suppression factor against physical shift noise, and break-even gain.",
"references": [
- "Internal enterprise qualification standard: CV-QEC-GAUSS-v1"
+ "Open-source studio qualification standard: CV-QEC-GAUSS-v1"
],
"thresholds": {
"min_success_rate": 0.90,
@@ -48,7 +48,7 @@
"target_representation": "fock_number_basis",
"summary": "Validation policy for Fock-basis runs emphasizing normalization fidelity and robust runtime consistency.",
"references": [
- "Internal enterprise qualification standard: CV-FOCK-VAL-v1"
+ "Open-source studio qualification standard: CV-FOCK-VAL-v1"
],
"thresholds": {
"min_success_rate": 0.95,
diff --git a/docs/config/profiles/studio-default-v1.json b/docs/config/profiles/studio-default-v1.json
new file mode 100644
index 0000000..0030534
--- /dev/null
+++ b/docs/config/profiles/studio-default-v1.json
@@ -0,0 +1,9 @@
+{
+ "name": "studio-default-v1",
+ "max_modes": 64,
+ "max_squeezing_r": 1.2,
+ "allow_non_gaussian": true,
+ "allow_measurements": true,
+ "inject_mode_loss": true,
+ "mode_loss_eta": [0.995]
+}
diff --git a/config/sample_calibrated_noise_profile.json b/docs/config/sample_calibrated_noise_profile.json
similarity index 92%
rename from config/sample_calibrated_noise_profile.json
rename to docs/config/sample_calibrated_noise_profile.json
index e69ded2..66841f4 100644
--- a/config/sample_calibrated_noise_profile.json
+++ b/docs/config/sample_calibrated_noise_profile.json
@@ -41,5 +41,5 @@
"phase_rad_per_sample": 0.00018,
"displacement_per_sample": 0.00012
},
- "notes": "Sample calibrated tensor profile for SchroSIM enterprise UI Monte Carlo analysis."
+ "notes": "Sample calibrated tensor profile for SchroSIM Studio Monte Carlo analysis."
}
diff --git a/config/trace_rbac_policy.json b/docs/config/trace_rbac_policy.json
similarity index 100%
rename from config/trace_rbac_policy.json
rename to docs/config/trace_rbac_policy.json
diff --git a/examples/basic_loss.json b/docs/examples/basic_loss.json
similarity index 100%
rename from examples/basic_loss.json
rename to docs/examples/basic_loss.json
diff --git a/examples/cv/foundry_validation_single_mode_fock_response.json b/docs/examples/cv/foundry_validation_single_mode_fock_response.json
similarity index 100%
rename from examples/cv/foundry_validation_single_mode_fock_response.json
rename to docs/examples/cv/foundry_validation_single_mode_fock_response.json
diff --git a/examples/cv/foundry_validation_single_mode_squeezing.json b/docs/examples/cv/foundry_validation_single_mode_squeezing.json
similarity index 100%
rename from examples/cv/foundry_validation_single_mode_squeezing.json
rename to docs/examples/cv/foundry_validation_single_mode_squeezing.json
diff --git a/examples/cv/foundry_validation_thermal_heterodyne.json b/docs/examples/cv/foundry_validation_thermal_heterodyne.json
similarity index 100%
rename from examples/cv/foundry_validation_thermal_heterodyne.json
rename to docs/examples/cv/foundry_validation_thermal_heterodyne.json
diff --git a/examples/cv/foundry_validation_two_mode_epr_loss.json b/docs/examples/cv/foundry_validation_two_mode_epr_loss.json
similarity index 100%
rename from examples/cv/foundry_validation_two_mode_epr_loss.json
rename to docs/examples/cv/foundry_validation_two_mode_epr_loss.json
diff --git a/examples/cv/loss_channel.swift b/docs/examples/cv/loss_channel.swift
similarity index 100%
rename from examples/cv/loss_channel.swift
rename to docs/examples/cv/loss_channel.swift
diff --git a/examples/cv/qec_single_logical_gkp_memory_mvp.json b/docs/examples/cv/qec_single_logical_gkp_memory_mvp.json
similarity index 100%
rename from examples/cv/qec_single_logical_gkp_memory_mvp.json
rename to docs/examples/cv/qec_single_logical_gkp_memory_mvp.json
diff --git a/examples/fock_injection_smoke.json b/docs/examples/fock_injection_smoke.json
similarity index 100%
rename from examples/fock_injection_smoke.json
rename to docs/examples/fock_injection_smoke.json
diff --git a/examples/foundry_loss_map.json b/docs/examples/foundry_loss_map.json
similarity index 100%
rename from examples/foundry_loss_map.json
rename to docs/examples/foundry_loss_map.json
diff --git a/examples/foundry_registry_ref.json b/docs/examples/foundry_registry_ref.json
similarity index 83%
rename from examples/foundry_registry_ref.json
rename to docs/examples/foundry_registry_ref.json
index 8f25eb7..5f563f7 100644
--- a/examples/foundry_registry_ref.json
+++ b/docs/examples/foundry_registry_ref.json
@@ -4,7 +4,7 @@
"modes": 1,
"backend": "gaussian",
"foundry_profile": {
- "profile_id": "enterprise-default",
+ "profile_id": "studio-default",
"version": 1
},
"gates": [
diff --git a/examples/runtime_default_foundry.json b/docs/examples/runtime_default_foundry.json
similarity index 100%
rename from examples/runtime_default_foundry.json
rename to docs/examples/runtime_default_foundry.json
diff --git a/examples/seeded_homodyne.json b/docs/examples/seeded_homodyne.json
similarity index 100%
rename from examples/seeded_homodyne.json
rename to docs/examples/seeded_homodyne.json
diff --git a/docs/ui-mockups/schrosim-enterprise-preview.gif b/docs/ui-mockups/schrosim-studio-preview.gif
similarity index 100%
rename from docs/ui-mockups/schrosim-enterprise-preview.gif
rename to docs/ui-mockups/schrosim-studio-preview.gif
diff --git a/mkdocs.yml b/mkdocs.yml
index 65f82f5..c653cd1 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -1,12 +1,12 @@
site_name: SchroSIM
site_description: Continuous-variable photonic quantum simulation stack
-site_url: https://denniswayo.github.io/SchroSIM/
+site_url: https://gottesman-software.github.io/SchroSIM/
docs_dir: docs
exclude_docs: README.md
-repo_url: https://github.com/DennisWayo/SchroSIM
-repo_name: DennisWayo/SchroSIM
+repo_url: https://github.com/Gottesman-Software/SchroSIM
+repo_name: Gottesman-Software/SchroSIM
edit_uri: edit/master/docs/
theme:
@@ -32,4 +32,5 @@ nav:
- Mathematical Proofs: 11-mathematical-proofs.md
- Developer:
- Worked Examples: 12-worked-examples.md
+ - SchroSIM Studio: 14-schrosim-studio.md
- Developer Guide: 13-developer-guide.md
diff --git a/pyproject.toml b/pyproject.toml
index fd60ca9..411df36 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -38,10 +38,10 @@ classifiers = [
dependencies = []
[project.urls]
-Homepage = "https://github.com/DennisWayo/SchroSIM"
-Documentation = "https://denniswayo.github.io/SchroSIM/"
-Repository = "https://github.com/DennisWayo/SchroSIM"
-Issues = "https://github.com/DennisWayo/SchroSIM/issues"
+Homepage = "https://github.com/Gottesman-Software/SchroSIM"
+Documentation = "https://gottesman-software.github.io/SchroSIM/"
+Repository = "https://github.com/Gottesman-Software/SchroSIM"
+Issues = "https://github.com/Gottesman-Software/SchroSIM/issues"
[project.scripts]
schrosim = "schrosim.cli:main"
diff --git a/scripts/cv-validation-suite.sh b/scripts/cv-validation-suite.sh
index 6afed65..11d88ce 100755
--- a/scripts/cv-validation-suite.sh
+++ b/scripts/cv-validation-suite.sh
@@ -23,10 +23,10 @@ if [[ "$RUNTIME" != "swift" && "$RUNTIME" != "rust" && "$RUNTIME" != "both" ]];
fi
CASES=(
- "examples/cv/foundry_validation_single_mode_squeezing.json"
- "examples/cv/foundry_validation_two_mode_epr_loss.json"
- "examples/cv/foundry_validation_thermal_heterodyne.json"
- "examples/cv/foundry_validation_single_mode_fock_response.json"
+ "docs/examples/cv/foundry_validation_single_mode_squeezing.json"
+ "docs/examples/cv/foundry_validation_two_mode_epr_loss.json"
+ "docs/examples/cv/foundry_validation_thermal_heterodyne.json"
+ "docs/examples/cv/foundry_validation_single_mode_fock_response.json"
)
extract_summary() {
diff --git a/studio/README.md b/studio/README.md
new file mode 100644
index 0000000..4dc2bff
--- /dev/null
+++ b/studio/README.md
@@ -0,0 +1,16 @@
+# SchroSIM Studio
+
+SchroSIM Studio is the public research UI for designing, inspecting, and replaying SchroSIM photonic circuit studies before running them through the CLI/core runtime.
+
+The open-source SwiftUI implementation lives in:
+
+- `core-swift/Sources/schrosim-studio/`
+- `core-swift/Tests/SchroSIMStudioTests/`
+
+Local planning notes under `studio/docs/` are intentionally ignored.
+
+Run Studio from the root package:
+
+```bash
+swift run schrosim-studio
+```