Local Hybrid Simulation¶
Run the complete ideal, physical-noise, and parameter-sweep example:
python3 examples/user/complete_physical_hybrid_demo.py \
--output artifacts/complete_physical_hybrid_report.html
HybridProgram owns typed Digital and Analog payloads. LocalBackend.run() is the only normal submission entrypoint:
from cascaqit import HybridProgram, LocalBackend, SimulationOptions
program = (
HybridProgram("demo")
.digital("prepare", circuit)
.analog("evolve", ahs_program)
.digital("correct", correction)
.measure_all()
)
job = LocalBackend(seed=7).run(
program,
shots=32,
options=SimulationOptions(method="auto", max_memory_bytes=2_000_000_000),
)
result = job.result()
print(result.counts)
print(result.metadata["simulation_resource_estimate"])
Payloads are snapshotted when attached, so later mutation of the original builder does not change the program. analyze(), compile(), parameters, execution_builder(), and to_ir() remain available on the same object for advanced workflows.
Parameters And Sweeps¶
Parameters declared by Circuit and AHSProgram are collected automatically. Identical names share one declaration; conflicting declarations fail before execution. Targets are generated for every owning block.
from cascaqit import ParameterScan
single = backend.run(program, params={"theta": 0.2}, shots=32)
scan = ParameterScan.explicit(
scan_id="scan.theta",
points=({"theta": 0.1}, {"theta": 0.4}),
)
sweep_job = backend.run(
program,
sweep=scan,
shots=32,
options=SimulationOptions(workers="auto", seed=7),
failure_policy="continue_on_error",
)
sweep_result = sweep_job.result()
print(sweep_result.counts)
print(sweep_result.metadata["scan_resource_plan"])
params and sweep are mutually exclusive. Sweep size is limited by the same memory budget as a single simulation rather than a fixed point count. continue_on_error uses a memory-bounded worker pool, while strict fail_fast runs serially so a point marked not_run was never started. Both policies preserve scan-index result order, derive an independent seed from the root seed for each point, cache terminal results, and expose partial outcomes. Use execution_builder(parameterized=True) only when custom initial state or direct Bundle control is required.
Adaptive Accuracy And State Lineage¶
Run the solver comparison example:
python3 examples/user/adaptive_solver_and_lineage.py
For an Analog or Hybrid state-vector, subspace, or density-matrix run, integrator="auto" selects the adaptive DOP853 integrator. Set it explicitly when the numerical policy is part of the experiment:
result = backend.run(
program,
shots=32,
options=SimulationOptions(
integrator="adaptive_dop853",
rtol=1e-9,
atol=1e-11,
max_steps=10_000,
),
).result()
print(result.execution_config())
print(result.solver_evidence())
print(result.state_transitions())
The solver stops at waveform knots and site-addressing frame times before continuing with a new segment. solver_evidence() reports accepted steps, function evaluations, segment count, step-size range, termination reason, and whether the requested tolerances were actually applied. SciPy does not expose DOP853's internal rejection count, so CASCAQit reports it as unavailable instead of estimating it.
state_transitions() returns one canonical transition for each quantum block. Digital transitions keep logical time unchanged; Analog transitions advance it by their duration. Adjacent output/input state hashes must match, and the first and last hashes match the top-level initial/final references. Terminal measurement is a trace event rather than a fictitious state transition.
Digital-only gate execution has no time integrator. Trajectory execution remains fixed-step because jump probabilities depend on the time slices; an explicit adaptive trajectory request fails during planning. fixed_step_krylov remains available for controlled comparisons and records tolerance_applied=False.
Physical Noise¶
Executable noise APIs are part of the compact root API:
from cascaqit import NoiseChannel, NoiseModel, SimulationOptions
noise = NoiseModel(
"noise.demo",
(
NoiseChannel.preparation(0.01),
NoiseChannel.dephasing(0.2),
NoiseChannel.gate(0.01),
NoiseChannel.atom_loss(0.02, targets=("q1",)),
NoiseChannel.readout(0.02, p10=0.03),
),
)
result = backend.run(
program,
params={"theta": 0.2},
noise=noise,
shots=256,
options=SimulationOptions(trajectories=256, seed=7),
).result()
print(result.counts)
print(result.observables)
print(result.metadata["noise_report"])
Without atom loss, method="auto" prefers exact density-matrix evolution when the resource budget permits and otherwise uses trajectories. Atom loss requires trajectories because the density state has no vacuum-occupation axis. The ordered NoiseReport distinguishes physical state evolution from readout-only measurement modification. The same canonical model can be passed with sweep=...; every point receives an independent derived seed and returns its own noise report, state chain, counts, observables, and confidence interval. Sweep concurrency owns the worker budget, so each trajectory item uses one internal worker and does not create a nested pool.
Current Boundary¶
SimulationPlanner has no fixed product site or sweep-point limit. It selects a state representation and integrator from program semantics and a default 80% host-memory budget, then rejects unsupported or over-budget work before allocating a large array or creating a scan Job. Adaptive DOP853 workspace is part of that estimate. Sweep metadata records the requested and selected workers, per-run peak estimate, retained-result buffer, total estimate, budget, root seed, and worker-selection reasons. result.resource_usage() records observed wall time, process high-water RSS, the pre-run RSS baseline, incremental RSS, planner estimate, and estimate error when the process high-water mark actually grows. A baseline-dominated run reports no estimate error rather than claiming zero memory. For parallel scans, the aggregate owns scan concurrency and root seed; each item records one kernel worker and a schedule-independent derived seed. The aggregate owns RSS and each item explicitly reports owned_by_parent_scan; item wall time remains available.
The scalable engine applies Digital gates by tensor axes and evolves Analog blocks with a shared matrix-free Hamiltonian used by adaptive DOP853 or fixed-step Krylov propagation. D-A-D Hybrid blocks share one state and expose the canonical transition/reference chain. Verified Apple M4 reference runs cover 24-qubit Digital, 18-site ideal Analog/Hybrid, 10-site exact density, and 16-site/256-trajectory noisy Hybrid workloads. These are reference points, not fixed maxima.
HybridProgramIR and LocalExecutionPlan are serialization and planning artifacts, not direct Backend.run() inputs. The local path does not access hardware, cloud services, credentials, or the network.