Quantum Annealing Bridge
Public module: sc_neurocore.bridges.quantum_annealing
Tier: Research bridge
Compatibility surface: 24 historical exports
Optional runtimes: SC-NeuroCore Rust engine, dimod, and D-Wave Ocean
The bridge compiles stochastic-computing (SC) network structures into
validated Ising and QUBO models. It provides deterministic classical solving,
optional native acceleration, optional D-Wave submission, analysis,
decomposition, transformations, and model export.
This is research software. It does not establish quantum advantage, certify a
minor embedding, validate a physical QPU, or claim parity with unrelated
quantum-circuit modules in sc_neurocore.quantum.
Architecture
The former 1,910-line implementation is now a 98-line compatibility façade and
nine responsibility modules. Historical imports and pickle identities remain
at sc_neurocore.bridges.quantum_annealing.
| Module |
Responsibility |
Lines |
quantum_annealing.py |
Stable exports, optional-backend observables, pickle identity |
98 |
annealing_backends.py |
Optional native, dimod, and D-Wave adapters |
139 |
annealing_models.py |
Validated Ising/QUBO value objects and backend selection |
276 |
annealing_compilers.py |
SC adjacency, bitstream, and pruning compilers |
235 |
annealing_solvers.py |
Python/native simulated annealing and D-Wave adapter |
267 |
annealing_analysis.py |
Landscape, embedding, sample, and TTS analysis |
317 |
annealing_hardware.py |
Hardware-capacity estimates and chain resolution |
203 |
annealing_transforms.py |
Schedules, gauges, and SC precision encodings |
245 |
annealing_io.py |
Deterministic export and text visualisation |
118 |
annealing_decomposition.py |
Overlapping partitioning and reconstruction |
173 |
The responsibility graph is acyclic. No responsibility module imports the
façade, and an architecture test enforces the dependency direction and size
limits.
Public surface
All 24 names are available from both the historical module and
sc_neurocore.bridges.
| Group |
Public names |
| Models |
ProblemType, QubitSpec, CouplerSpec, IsingModel, QUBOModel |
| Compilers |
SCToIsing, SCToQUBO, SCBitstreamQUBO |
| Solvers |
SimulatedAnnealer, DWaveInterface |
| Analysis |
EnergyLandscape, EmbeddingAnalyzer, SampleAggregator, TTSAnalyzer |
| Hardware and decomposition |
HardwareGraph, ChainBreakResolver, ProblemDecomposer |
| Transformations |
AnnealingSchedule, GaugeTransform, SCPrecisionEncoder |
| Export |
export_ising_json, export_qubo_json, export_bqm, visualize_ising |
Quick start
Pythonimport numpy as np
from sc_neurocore.bridges.quantum_annealing import (
SCToIsing,
SimulatedAnnealer,
)
adjacency = np.array(
[
[0.0, 0.8, -0.2],
[0.8, 0.0, 0.4],
[-0.2, 0.4, 0.0],
],
dtype=np.float64,
)
model = SCToIsing().compile(adjacency, node_labels=["a", "b", "c"])
result = SimulatedAnnealer(
n_sweeps=200,
seed=42,
backend="python",
).solve_ising(model, num_reads=10)
print(result["best_spins"], result["best_energy"])
Choose backend="python" when repeatable Python-path evidence is required.
Choose backend="rust" to require the native engine; a missing native backend
then raises a stable RuntimeError. backend="auto" may select Rust according
to the thresholds documented below.
Model contracts
IsingModel uses
Text OnlyE(s) = offset + Σᵢ hᵢsᵢ + Σᵢ<ⱼ Jᵢⱼsᵢsⱼ, sᵢ ∈ {-1, +1}
and QUBOModel uses
Text OnlyE(x) = offset + Σᵢ≤ⱼ Qᵢⱼxᵢxⱼ, xᵢ ∈ {0, 1}.
Both models canonicalise pair indices, combine reversed duplicates, remove
zero-valued terms, infer n_qubits from terms when it is omitted, and reject
non-finite values, invalid indices, out-of-bounds labels, and duplicate label
values. Missing variables preserve the historical defaults: +1 for an Ising
spin and 0 for a QUBO bit.
QUBOModel.to_ising() uses x = (s + 1) / 2 and is exactly
energy-equivalent. In particular, a diagonal term Qᵢᵢxᵢ contributes
Qᵢᵢ / 2 to both hᵢ and the offset. Off-diagonal terms contribute
Qᵢⱼ / 4 to the coupling, both incident fields, and the offset. Exhaustive
tests compare every assignment for small models.
Compilers
SC adjacency compilers
SCToIsing.compile() and SCToQUBO.compile() accept a finite, non-empty,
square matrix and optional unique labels. Directed pairs are averaged before a
single canonical coupling is emitted.
SCToIsing maps positive averaged weights to ferromagnetic (negative)
couplings and applies the configured field and coupling scales.
SCToQUBO uses negative absolute column sums on the diagonal and scaled
averaged off-diagonal weights.
Both reject malformed matrices, non-finite values, mismatched biases, and
invalid names or labels.
SC bitstream QUBOs
SCBitstreamQUBO.weight_optimization() encodes
||target - candidate_weights @ x||² for binary x. n_bits selects the first
candidate columns and must be a positive integer no greater than the available
column count; it is never silently clamped.
SCBitstreamQUBO.pruning() creates one variable for every undirected candidate
edge found in either adjacency direction. It averages the two directed
importance scores and applies a quadratic penalty for selecting exactly
max_connections. Impossible cardinalities fail before a model is returned.
SC precision encoding
SCPrecisionEncoder supports binary, unary (thermometer), and one-hot
representations of finite values clipped to [0, 1].
| Encoding |
Levels |
Qubits per value |
binary |
2**n_bits |
n_bits |
unary |
n_bits + 1 |
n_bits |
one_hot |
n_bits |
n_bits |
Array encoding requires a finite, non-empty one-dimensional array and assigns
non-overlapping global qubit indices. Decoding rejects invalid indices, values,
and multiple active one-hot bits.
Solver and backend behaviour
Simulated annealing
SimulatedAnnealer implements seeded single-spin Metropolis sweeps. The Python
and Rust paths return the same mapping-shaped contract:
best_spins: dict[int, int]
best_energy: float
energies: one finite value per returned sample
samples: list[dict[int, int]]
n_sweeps, num_reads, and backend
solve_qubo() converts through the exact Ising mapping and returns the
corresponding bit-valued contract. Native responses are validated for shape,
spin domain, energy finiteness, and aligned sample counts before they cross the
public boundary. The configured seed is forwarded to Rust.
Backend dispatch is explicit:
| Caller |
auto native threshold |
Explicit behaviour |
IsingModel.energy() |
Rust available and more than 20 qubits |
python stays Python; rust requires Rust |
SimulatedAnnealer.solve_ising() |
Rust available and more than 10 qubits |
python stays Python; rust requires Rust |
EnergyLandscape.analyze() |
Rust available and more than 100 samples |
python stays Python; rust requires Rust |
D-Wave adapter
DWaveInterface.available means only that dimod and the required Ocean SDK
classes are importable. It does not prove credentials, network access, solver
entitlement, or QPU health.
When those imports are absent, solve_ising() uses a bounded local simulated
annealing fallback with at most 20 reads and labels the result
simulated_annealing_fallback. When the SDK is installed, sampler construction,
authentication, provider, and submission failures propagate; they are not
misreported as local success. A successful QPU result is checked for a valid
spin mapping and finite best energy.
Install the optional local annealing dependencies with:
Bashpython -m pip install 'sc-neurocore[annealing]'
Real QPU access additionally requires a compatible Ocean installation and
provider credentials outside this repository.
Analysis, hardware, and decomposition
EnergyLandscape exhaustively enumerates models up to 20 qubits. Larger
models use a deterministic configurable random sample unless samples are
supplied by the caller.
EmbeddingAnalyzer reports graph density, degree, and a Pegasus-oriented
chain-size estimate. It is a capacity estimate, not a minor embedding.
SampleAggregator validates aligned samples and energies, deduplicates spin
patterns, and reports histogram and Boltzmann-weighted summaries.
TTSAnalyzer validates probabilities, times, energy samples, and solver
payloads before computing the standard cumulative-success estimate.
HardwareGraph models idealised Chimera, Pegasus, and Zephyr capacity. Its
degree-based can_embed() result is not hardware placement evidence.
ChainBreakResolver validates non-overlapping physical chains and resolves
by majority vote or local energy minimisation. The latter requires a model.
AnnealingSchedule rejects non-finite or non-positive timings and invalid
anneal fractions for linear, pause-and-quench, and reverse schedules.
GaugeTransform produces deterministic energy-equivalent Python models and
validates samples when returning them to the original spin frame.
ProblemDecomposer creates deterministic graph-aware partitions with up to
the configured overlap from connected assigned neighbours. Submodels use
local indices, while reconstruction retains an exact local-to-global index
map; it does not infer identity from optional labels.
Decomposition is a heuristic orchestration strategy. It does not guarantee a
global optimum or a favourable embedding.
Export behaviour
export_ising_json() and export_qubo_json() write sorted, deterministic UTF-8
JSON through a same-directory temporary file, flush and synchronise it, and
atomically replace the destination. A failed write removes the temporary file.
export_bqm() returns a dimod spin BQM when available and None otherwise.
visualize_ising() returns a stable human-readable text representation.
Polyglot authority
The Python bridge is the maintained orchestration authority. The maintained
native implementation is engine/src/quantum.rs, exposed through the PyO3
engine package and covered by 12 focused Rust tests.
Earlier generated files under the Rust safety registry, Go services, Julia
bridges, and Mojo kernels were not alternate implementations: the Rust safety
file returned constants, the Go functions were empty, and the Julia and Mojo
files did not build. They have been removed together with their registry
entries. No Go, Julia, Mojo, or generated-Rust parity or performance claim is
made for this bridge.
The maintained native comparison harness remains available:
Bashpython benchmarks/bench_quantum_annealing_rust_vs_python.py
Its results are machine- and load-specific; rerun it in the target release
environment before publishing a native speed claim.
Modularisation benchmark
benchmarks/bench_quantum_annealing_modularisation.py compares the committed
single-file parent (9308910a5d863ebfb338244b43d10f73f25cfbc6) with the
modular candidate. It uses 30 measured child processes after five warm-ups,
alternates variant order, pins each child with taskset, records raw samples
and load context, and binds both variants to source digests.
| Metric |
Parent median |
Modular median |
Delta |
| Cold import |
383.554 ms |
286.231 ms |
-25.37% |
| Eight-node compile |
0.0389 ms |
0.0776 ms |
+99.65% |
| Python solve |
1.8522 ms |
1.5434 ms |
-16.67% |
| Child wall time |
569.268 ms |
437.154 ms |
-23.21% |
| Maximum RSS |
41,326 KiB |
39,888 KiB |
-3.48% |
The candidate source digest is
9d5152b02377c5358a7eb678ab9b35633fbe40deefd6c0a7a8b7996978023d1a.
The workstation load average rose from 7.46 to 11.53 during capture, and CPU 0
was affinity-pinned but not exclusively isolated. These measurements are local
regression diagnostics, not release throughput or hardware claims. Repeat on
reserved isolated cores before promotion.
Reproduce the committed evidence with:
Bashpython benchmarks/bench_quantum_annealing_modularisation.py \
--baseline-root /path/to/clean-parent \
--candidate-root . \
--output benchmarks/results/local_python_quantum_annealing.json
Verification
The focused cohort is split by responsibility instead of rebuilding a test
GodFile. Its largest file is 445 lines. The linked cohort passes 250 tests with
one optional neal parity test skipped when that extra is absent. Exact-file
coverage passes 216 tests and records 100% of 1,040 statements and 442 branches
with no misses or partial branches.
BashPYTHONPATH=src:. python -m pytest \
tests/test_bridges/test_quantum_annealing_value_specs.py \
tests/test_bridges/test_quantum_annealing_ising_model.py \
tests/test_bridges/test_quantum_annealing_qubo_model.py \
tests/test_bridges/test_quantum_annealing_network_compilers.py \
tests/test_bridges/test_quantum_annealing_bitstream_qubo.py \
tests/test_bridges/test_quantum_annealing_solvers_backends.py \
tests/test_bridges/test_quantum_annealing_energy_landscape.py \
tests/test_bridges/test_quantum_annealing_embedding_analysis.py \
tests/test_bridges/test_quantum_annealing_sample_aggregation.py \
tests/test_bridges/test_quantum_annealing_time_to_solution.py \
tests/test_bridges/test_quantum_annealing_hardware_graph.py \
tests/test_bridges/test_quantum_annealing_chain_break_resolution.py \
tests/test_bridges/test_quantum_annealing_transforms_io.py \
tests/test_bridges/test_quantum_annealing_decomposition_architecture.py \
tests/test_bridges/test_quantum_annealing_neal_parity.py \
tests/test_bench_quantum_annealing_modularisation.py
cargo test --manifest-path engine/Cargo.toml quantum --lib
Coverage includes exact QUBO-to-Ising energy equivalence, backend selection and
malformed native returns, QPU fallback/error boundaries, compiler objectives,
schedule and hardware validation, atomic export cleanup, exact decomposition
index reconstruction, historical import and pickle identities, the architecture
DAG, and benchmark schema/source binding.
Limitations
- No real QPU submission is exercised by the dependency-light test cohort.
available checks imports, not credentials or provider health.
- Hardware and embedding results are conservative estimates, not placement
proofs.
- Problem decomposition is heuristic and offers no optimum guarantee.
- Optional
neal parity requires sc-neurocore[annealing].
- Benchmark evidence does not demonstrate quantum speedup or production
throughput.
Auto-rendered API
sc_neurocore.bridges.quantum_annealing
Compatibility façade for modular quantum-annealing tooling.
The bridge compiles SC network structures into validated Ising/QUBO models,
runs classical or optional native/D-Wave solvers, and exposes bounded analysis,
embedding, transformation, decomposition, and export responsibilities.
ProblemType
Bases: Enum
Quantum optimization problem type.
Source code in src/sc_neurocore/bridges/annealing_models.py
| Python |
|---|
| class ProblemType(Enum):
"""Quantum optimization problem type."""
ISING = "ising"
QUBO = "qubo"
|
QubitSpec
dataclass
Specification for one logical qubit.
Source code in src/sc_neurocore/bridges/annealing_models.py
| Python |
|---|
54
55
56
57
58
59
60
61
62
63
64
65
66
67 | @dataclass
class QubitSpec:
"""Specification for one logical qubit."""
index: int
label: str
bias: float = 0.0
def __post_init__(self) -> None:
"""Validate the qubit index, label, and bias."""
self.index = _require_index("index", self.index)
if not isinstance(self.label, str) or not self.label.strip():
raise ValueError("label must be a non-empty string")
self.bias = _require_finite("bias", self.bias)
|
__post_init__()
Validate the qubit index, label, and bias.
Source code in src/sc_neurocore/bridges/annealing_models.py
| Python |
|---|
| def __post_init__(self) -> None:
"""Validate the qubit index, label, and bias."""
self.index = _require_index("index", self.index)
if not isinstance(self.label, str) or not self.label.strip():
raise ValueError("label must be a non-empty string")
self.bias = _require_finite("bias", self.bias)
|
CouplerSpec
dataclass
Specification for one logical Ising/QUBO coupling.
Source code in src/sc_neurocore/bridges/annealing_models.py
| Python |
|---|
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86 | @dataclass
class CouplerSpec:
"""Specification for one logical Ising/QUBO coupling."""
qubit_a: int
qubit_b: int
strength: float = 0.0
def __post_init__(self) -> None:
"""Validate distinct endpoints and a finite strength."""
self.qubit_a = _require_index("qubit_a", self.qubit_a)
self.qubit_b = _require_index("qubit_b", self.qubit_b)
if self.qubit_a == self.qubit_b:
raise ValueError("coupler endpoints must be distinct")
if self.qubit_a > self.qubit_b:
self.qubit_a, self.qubit_b = self.qubit_b, self.qubit_a
self.strength = _require_finite("strength", self.strength)
|
__post_init__()
Validate distinct endpoints and a finite strength.
Source code in src/sc_neurocore/bridges/annealing_models.py
| Python |
|---|
78
79
80
81
82
83
84
85
86 | def __post_init__(self) -> None:
"""Validate distinct endpoints and a finite strength."""
self.qubit_a = _require_index("qubit_a", self.qubit_a)
self.qubit_b = _require_index("qubit_b", self.qubit_b)
if self.qubit_a == self.qubit_b:
raise ValueError("coupler endpoints must be distinct")
if self.qubit_a > self.qubit_b:
self.qubit_a, self.qubit_b = self.qubit_b, self.qubit_a
self.strength = _require_finite("strength", self.strength)
|
IsingModel
dataclass
Ising spin-glass model H = Σhᵢsᵢ + ΣJᵢⱼsᵢsⱼ.
Source code in src/sc_neurocore/bridges/annealing_models.py
| Python |
|---|
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190 | @dataclass
class IsingModel:
"""Ising spin-glass model ``H = Σhᵢsᵢ + ΣJᵢⱼsᵢsⱼ``."""
h: dict[int, float] = field(default_factory=dict)
J: dict[tuple[int, int], float] = field(default_factory=dict)
offset: float = 0.0
qubit_labels: dict[int, str] = field(default_factory=dict)
n_qubits: int = 0
source: str = ""
def __post_init__(self) -> None:
"""Normalize canonical couplings and validate model bounds."""
normalized_h: dict[int, float] = {}
highest_index = -1
for raw_index, raw_bias in self.h.items():
index = _require_index("h index", raw_index)
normalized_h[index] = _require_finite(f"h[{index}]", raw_bias)
highest_index = max(highest_index, index)
normalized_j: dict[tuple[int, int], float] = {}
for raw_pair, raw_strength in self.J.items():
if not isinstance(raw_pair, tuple) or len(raw_pair) != 2:
raise ValueError("J keys must be two-index tuples")
first = _require_index("J endpoint", raw_pair[0])
second = _require_index("J endpoint", raw_pair[1])
if first == second:
raise ValueError("Ising couplings must connect distinct qubits")
pair = (min(first, second), max(first, second))
strength = _require_finite(f"J[{pair}]", raw_strength)
normalized_j[pair] = normalized_j.get(pair, 0.0) + strength
highest_index = max(highest_index, *pair)
normalized_labels: dict[int, str] = {}
for raw_index, label in self.qubit_labels.items():
index = _require_index("qubit label index", raw_index)
if not isinstance(label, str) or not label.strip():
raise ValueError("qubit labels must be non-empty strings")
normalized_labels[index] = label
highest_index = max(highest_index, index)
if len(set(normalized_labels.values())) != len(normalized_labels):
raise ValueError("qubit labels must be unique")
if isinstance(self.n_qubits, bool) or not isinstance(self.n_qubits, int):
raise ValueError("n_qubits must be a non-negative integer")
if self.n_qubits < 0:
raise ValueError("n_qubits must be a non-negative integer")
if self.n_qubits == 0 and highest_index >= 0:
self.n_qubits = highest_index + 1
if highest_index >= self.n_qubits:
raise ValueError("model indices must be smaller than n_qubits")
if not isinstance(self.source, str):
raise ValueError("source must be a string")
self.h = normalized_h
self.J = {pair: value for pair, value in normalized_j.items() if value != 0.0}
self.qubit_labels = normalized_labels
self.offset = _require_finite("offset", self.offset)
def energy(
self,
spins: Mapping[int, int],
*,
backend: BackendChoice = "auto",
) -> float:
"""Compute energy for a partial spin assignment.
Missing spins retain the historical ``+1`` default. An explicit Rust
request fails when the native engine is unavailable; ``auto`` uses it
only for models larger than 20 qubits.
"""
selected = validate_backend_choice(backend)
for index, spin in spins.items():
_require_index("spin index", index)
if spin not in {-1, 1}:
raise ValueError("spin values must be -1 or +1")
use_rust = selected == "rust" or (
selected == "auto" and backends.HAS_RUST_QA and self.n_qubits > 20
)
if use_rust:
kernel = backends.require_rust_energy()
h_indices = list(self.h)
j_pairs = list(self.J)
return float(
kernel(
h_indices,
[self.h[index] for index in h_indices],
[pair[0] for pair in j_pairs],
[pair[1] for pair in j_pairs],
[self.J[pair] for pair in j_pairs],
[spins.get(index, 1) for index in range(self.n_qubits)],
self.offset,
)
)
energy = self.offset
for index, bias in self.h.items():
energy += bias * spins.get(index, 1)
for (first, second), strength in self.J.items():
energy += strength * spins.get(first, 1) * spins.get(second, 1)
return energy
|
__post_init__()
Normalize canonical couplings and validate model bounds.
Source code in src/sc_neurocore/bridges/annealing_models.py
| Python |
|---|
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146 | def __post_init__(self) -> None:
"""Normalize canonical couplings and validate model bounds."""
normalized_h: dict[int, float] = {}
highest_index = -1
for raw_index, raw_bias in self.h.items():
index = _require_index("h index", raw_index)
normalized_h[index] = _require_finite(f"h[{index}]", raw_bias)
highest_index = max(highest_index, index)
normalized_j: dict[tuple[int, int], float] = {}
for raw_pair, raw_strength in self.J.items():
if not isinstance(raw_pair, tuple) or len(raw_pair) != 2:
raise ValueError("J keys must be two-index tuples")
first = _require_index("J endpoint", raw_pair[0])
second = _require_index("J endpoint", raw_pair[1])
if first == second:
raise ValueError("Ising couplings must connect distinct qubits")
pair = (min(first, second), max(first, second))
strength = _require_finite(f"J[{pair}]", raw_strength)
normalized_j[pair] = normalized_j.get(pair, 0.0) + strength
highest_index = max(highest_index, *pair)
normalized_labels: dict[int, str] = {}
for raw_index, label in self.qubit_labels.items():
index = _require_index("qubit label index", raw_index)
if not isinstance(label, str) or not label.strip():
raise ValueError("qubit labels must be non-empty strings")
normalized_labels[index] = label
highest_index = max(highest_index, index)
if len(set(normalized_labels.values())) != len(normalized_labels):
raise ValueError("qubit labels must be unique")
if isinstance(self.n_qubits, bool) or not isinstance(self.n_qubits, int):
raise ValueError("n_qubits must be a non-negative integer")
if self.n_qubits < 0:
raise ValueError("n_qubits must be a non-negative integer")
if self.n_qubits == 0 and highest_index >= 0:
self.n_qubits = highest_index + 1
if highest_index >= self.n_qubits:
raise ValueError("model indices must be smaller than n_qubits")
if not isinstance(self.source, str):
raise ValueError("source must be a string")
self.h = normalized_h
self.J = {pair: value for pair, value in normalized_j.items() if value != 0.0}
self.qubit_labels = normalized_labels
self.offset = _require_finite("offset", self.offset)
|
energy(spins, *, backend='auto')
Compute energy for a partial spin assignment.
Missing spins retain the historical +1 default. An explicit Rust
request fails when the native engine is unavailable; auto uses it
only for models larger than 20 qubits.
Source code in src/sc_neurocore/bridges/annealing_models.py
| Python |
|---|
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190 | def energy(
self,
spins: Mapping[int, int],
*,
backend: BackendChoice = "auto",
) -> float:
"""Compute energy for a partial spin assignment.
Missing spins retain the historical ``+1`` default. An explicit Rust
request fails when the native engine is unavailable; ``auto`` uses it
only for models larger than 20 qubits.
"""
selected = validate_backend_choice(backend)
for index, spin in spins.items():
_require_index("spin index", index)
if spin not in {-1, 1}:
raise ValueError("spin values must be -1 or +1")
use_rust = selected == "rust" or (
selected == "auto" and backends.HAS_RUST_QA and self.n_qubits > 20
)
if use_rust:
kernel = backends.require_rust_energy()
h_indices = list(self.h)
j_pairs = list(self.J)
return float(
kernel(
h_indices,
[self.h[index] for index in h_indices],
[pair[0] for pair in j_pairs],
[pair[1] for pair in j_pairs],
[self.J[pair] for pair in j_pairs],
[spins.get(index, 1) for index in range(self.n_qubits)],
self.offset,
)
)
energy = self.offset
for index, bias in self.h.items():
energy += bias * spins.get(index, 1)
for (first, second), strength in self.J.items():
energy += strength * spins.get(first, 1) * spins.get(second, 1)
return energy
|
QUBOModel
dataclass
Quadratic unconstrained binary optimization model xᵀQx.
Source code in src/sc_neurocore/bridges/annealing_models.py
| Python |
|---|
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276 | @dataclass
class QUBOModel:
"""Quadratic unconstrained binary optimization model ``xᵀQx``."""
Q: dict[tuple[int, int], float] = field(default_factory=dict)
offset: float = 0.0
qubit_labels: dict[int, str] = field(default_factory=dict)
n_qubits: int = 0
source: str = ""
def __post_init__(self) -> None:
"""Normalize matrix keys and validate model bounds."""
normalized_q: dict[tuple[int, int], float] = {}
highest_index = -1
for raw_pair, raw_value in self.Q.items():
if not isinstance(raw_pair, tuple) or len(raw_pair) != 2:
raise ValueError("Q keys must be two-index tuples")
first = _require_index("Q index", raw_pair[0])
second = _require_index("Q index", raw_pair[1])
pair = (min(first, second), max(first, second))
value = _require_finite(f"Q[{pair}]", raw_value)
normalized_q[pair] = normalized_q.get(pair, 0.0) + value
highest_index = max(highest_index, *pair)
normalized_labels: dict[int, str] = {}
for raw_index, label in self.qubit_labels.items():
index = _require_index("qubit label index", raw_index)
if not isinstance(label, str) or not label.strip():
raise ValueError("qubit labels must be non-empty strings")
normalized_labels[index] = label
highest_index = max(highest_index, index)
if len(set(normalized_labels.values())) != len(normalized_labels):
raise ValueError("qubit labels must be unique")
if isinstance(self.n_qubits, bool) or not isinstance(self.n_qubits, int):
raise ValueError("n_qubits must be a non-negative integer")
if self.n_qubits < 0:
raise ValueError("n_qubits must be a non-negative integer")
if self.n_qubits == 0 and highest_index >= 0:
self.n_qubits = highest_index + 1
if highest_index >= self.n_qubits:
raise ValueError("model indices must be smaller than n_qubits")
if not isinstance(self.source, str):
raise ValueError("source must be a string")
self.Q = {pair: value for pair, value in normalized_q.items() if value != 0.0}
self.qubit_labels = normalized_labels
self.offset = _require_finite("offset", self.offset)
def energy(self, bits: Mapping[int, int]) -> float:
"""Compute QUBO energy for a partial binary assignment."""
for index, bit in bits.items():
_require_index("bit index", index)
if bit not in {0, 1}:
raise ValueError("bit values must be 0 or 1")
energy = self.offset
for (first, second), coefficient in self.Q.items():
energy += coefficient * bits.get(first, 0) * bits.get(second, 0)
return energy
def to_ising(self) -> IsingModel:
"""Convert QUBO to an exactly energy-equivalent Ising model."""
h: dict[int, float] = {}
couplings: dict[tuple[int, int], float] = {}
offset = self.offset
for (first, second), coefficient in self.Q.items():
if first == second:
h[first] = h.get(first, 0.0) + coefficient / 2.0
offset += coefficient / 2.0
continue
couplings[(first, second)] = couplings.get((first, second), 0.0) + coefficient / 4.0
h[first] = h.get(first, 0.0) + coefficient / 4.0
h[second] = h.get(second, 0.0) + coefficient / 4.0
offset += coefficient / 4.0
return IsingModel(
h=h,
J=couplings,
offset=offset,
qubit_labels=dict(self.qubit_labels),
n_qubits=self.n_qubits,
source=f"{self.source} (QUBO→Ising)",
)
|
__post_init__()
Normalize matrix keys and validate model bounds.
Source code in src/sc_neurocore/bridges/annealing_models.py
| Python |
|---|
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240 | def __post_init__(self) -> None:
"""Normalize matrix keys and validate model bounds."""
normalized_q: dict[tuple[int, int], float] = {}
highest_index = -1
for raw_pair, raw_value in self.Q.items():
if not isinstance(raw_pair, tuple) or len(raw_pair) != 2:
raise ValueError("Q keys must be two-index tuples")
first = _require_index("Q index", raw_pair[0])
second = _require_index("Q index", raw_pair[1])
pair = (min(first, second), max(first, second))
value = _require_finite(f"Q[{pair}]", raw_value)
normalized_q[pair] = normalized_q.get(pair, 0.0) + value
highest_index = max(highest_index, *pair)
normalized_labels: dict[int, str] = {}
for raw_index, label in self.qubit_labels.items():
index = _require_index("qubit label index", raw_index)
if not isinstance(label, str) or not label.strip():
raise ValueError("qubit labels must be non-empty strings")
normalized_labels[index] = label
highest_index = max(highest_index, index)
if len(set(normalized_labels.values())) != len(normalized_labels):
raise ValueError("qubit labels must be unique")
if isinstance(self.n_qubits, bool) or not isinstance(self.n_qubits, int):
raise ValueError("n_qubits must be a non-negative integer")
if self.n_qubits < 0:
raise ValueError("n_qubits must be a non-negative integer")
if self.n_qubits == 0 and highest_index >= 0:
self.n_qubits = highest_index + 1
if highest_index >= self.n_qubits:
raise ValueError("model indices must be smaller than n_qubits")
if not isinstance(self.source, str):
raise ValueError("source must be a string")
self.Q = {pair: value for pair, value in normalized_q.items() if value != 0.0}
self.qubit_labels = normalized_labels
self.offset = _require_finite("offset", self.offset)
|
energy(bits)
Compute QUBO energy for a partial binary assignment.
Source code in src/sc_neurocore/bridges/annealing_models.py
| Python |
|---|
242
243
244
245
246
247
248
249
250
251 | def energy(self, bits: Mapping[int, int]) -> float:
"""Compute QUBO energy for a partial binary assignment."""
for index, bit in bits.items():
_require_index("bit index", index)
if bit not in {0, 1}:
raise ValueError("bit values must be 0 or 1")
energy = self.offset
for (first, second), coefficient in self.Q.items():
energy += coefficient * bits.get(first, 0) * bits.get(second, 0)
return energy
|
to_ising()
Convert QUBO to an exactly energy-equivalent Ising model.
Source code in src/sc_neurocore/bridges/annealing_models.py
| Python |
|---|
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276 | def to_ising(self) -> IsingModel:
"""Convert QUBO to an exactly energy-equivalent Ising model."""
h: dict[int, float] = {}
couplings: dict[tuple[int, int], float] = {}
offset = self.offset
for (first, second), coefficient in self.Q.items():
if first == second:
h[first] = h.get(first, 0.0) + coefficient / 2.0
offset += coefficient / 2.0
continue
couplings[(first, second)] = couplings.get((first, second), 0.0) + coefficient / 4.0
h[first] = h.get(first, 0.0) + coefficient / 4.0
h[second] = h.get(second, 0.0) + coefficient / 4.0
offset += coefficient / 4.0
return IsingModel(
h=h,
J=couplings,
offset=offset,
qubit_labels=dict(self.qubit_labels),
n_qubits=self.n_qubits,
source=f"{self.source} (QUBO→Ising)",
)
|
SCToIsing
Compile an SC adjacency matrix into an Ising model.
Source code in src/sc_neurocore/bridges/annealing_compilers.py
| Python |
|---|
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112 | class SCToIsing:
"""Compile an SC adjacency matrix into an Ising model."""
def __init__(
self,
coupling_scale: float = 1.0,
field_scale: float = 0.1,
) -> None:
"""Configure finite coupling and local-field scales."""
self._coupling_scale = _finite_scalar("coupling_scale", coupling_scale)
self._field_scale = _finite_scalar("field_scale", field_scale)
def compile(
self,
adjacency: np.ndarray[Any, Any],
node_labels: Sequence[str] | None = None,
biases: np.ndarray[Any, Any] | None = None,
name: str = "sc_ising",
) -> IsingModel:
"""Compile a finite square weight matrix.
Directed pairs are averaged. Positive weights become ferromagnetic
couplings and negative weights become antiferromagnetic couplings.
"""
matrix = _square_matrix("adjacency", adjacency)
size = matrix.shape[0]
labels = _labels(node_labels, size)
if not isinstance(name, str) or not name.strip():
raise ValueError("name must be a non-empty string")
if biases is None:
bias_array = np.zeros(size, dtype=np.float64)
else:
bias_array = np.asarray(biases, dtype=np.float64)
if bias_array.shape != (size,):
raise ValueError("biases must contain exactly one value per node")
if not bool(np.all(np.isfinite(bias_array))):
raise ValueError("biases must contain only finite values")
fields = {index: float(bias_array[index]) * self._field_scale for index in range(size)}
couplings: dict[tuple[int, int], float] = {}
for first in range(size):
for second in range(first + 1, size):
weight = float(matrix[first, second] + matrix[second, first]) / 2.0
if abs(weight) > _ZERO_TOLERANCE:
couplings[(first, second)] = -weight * self._coupling_scale
return IsingModel(
h=fields,
J=couplings,
qubit_labels={index: label for index, label in enumerate(labels)},
n_qubits=size,
source=name,
)
|
__init__(coupling_scale=1.0, field_scale=0.1)
Configure finite coupling and local-field scales.
Source code in src/sc_neurocore/bridges/annealing_compilers.py
| Python |
|---|
| def __init__(
self,
coupling_scale: float = 1.0,
field_scale: float = 0.1,
) -> None:
"""Configure finite coupling and local-field scales."""
self._coupling_scale = _finite_scalar("coupling_scale", coupling_scale)
self._field_scale = _finite_scalar("field_scale", field_scale)
|
compile(adjacency, node_labels=None, biases=None, name='sc_ising')
Compile a finite square weight matrix.
Directed pairs are averaged. Positive weights become ferromagnetic
couplings and negative weights become antiferromagnetic couplings.
Source code in src/sc_neurocore/bridges/annealing_compilers.py
| Python |
|---|
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112 | def compile(
self,
adjacency: np.ndarray[Any, Any],
node_labels: Sequence[str] | None = None,
biases: np.ndarray[Any, Any] | None = None,
name: str = "sc_ising",
) -> IsingModel:
"""Compile a finite square weight matrix.
Directed pairs are averaged. Positive weights become ferromagnetic
couplings and negative weights become antiferromagnetic couplings.
"""
matrix = _square_matrix("adjacency", adjacency)
size = matrix.shape[0]
labels = _labels(node_labels, size)
if not isinstance(name, str) or not name.strip():
raise ValueError("name must be a non-empty string")
if biases is None:
bias_array = np.zeros(size, dtype=np.float64)
else:
bias_array = np.asarray(biases, dtype=np.float64)
if bias_array.shape != (size,):
raise ValueError("biases must contain exactly one value per node")
if not bool(np.all(np.isfinite(bias_array))):
raise ValueError("biases must contain only finite values")
fields = {index: float(bias_array[index]) * self._field_scale for index in range(size)}
couplings: dict[tuple[int, int], float] = {}
for first in range(size):
for second in range(first + 1, size):
weight = float(matrix[first, second] + matrix[second, first]) / 2.0
if abs(weight) > _ZERO_TOLERANCE:
couplings[(first, second)] = -weight * self._coupling_scale
return IsingModel(
h=fields,
J=couplings,
qubit_labels={index: label for index, label in enumerate(labels)},
n_qubits=size,
source=name,
)
|
SCToQUBO
Compile an SC adjacency matrix into a QUBO model.
Source code in src/sc_neurocore/bridges/annealing_compilers.py
| Python |
|---|
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148 | class SCToQUBO:
"""Compile an SC adjacency matrix into a QUBO model."""
def __init__(self, penalty: float = 2.0) -> None:
"""Configure a positive constraint penalty."""
self._penalty = _finite_scalar("penalty", penalty, positive=True)
def compile(
self,
adjacency: np.ndarray[Any, Any],
node_labels: Sequence[str] | None = None,
name: str = "sc_qubo",
) -> QUBOModel:
"""Compile a finite square weight matrix into canonical QUBO terms."""
matrix = _square_matrix("adjacency", adjacency)
size = matrix.shape[0]
labels = _labels(node_labels, size)
if not isinstance(name, str) or not name.strip():
raise ValueError("name must be a non-empty string")
q_matrix: dict[tuple[int, int], float] = {}
for index in range(size):
q_matrix[(index, index)] = -float(np.sum(np.abs(matrix[:, index])))
for second in range(index + 1, size):
weight = float(matrix[index, second] + matrix[second, index]) / 2.0
if abs(weight) > _ZERO_TOLERANCE:
q_matrix[(index, second)] = weight * self._penalty
return QUBOModel(
Q=q_matrix,
qubit_labels={index: label for index, label in enumerate(labels)},
n_qubits=size,
source=name,
)
|
__init__(penalty=2.0)
Configure a positive constraint penalty.
Source code in src/sc_neurocore/bridges/annealing_compilers.py
| Python |
|---|
| def __init__(self, penalty: float = 2.0) -> None:
"""Configure a positive constraint penalty."""
self._penalty = _finite_scalar("penalty", penalty, positive=True)
|
compile(adjacency, node_labels=None, name='sc_qubo')
Compile a finite square weight matrix into canonical QUBO terms.
Source code in src/sc_neurocore/bridges/annealing_compilers.py
| Python |
|---|
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148 | def compile(
self,
adjacency: np.ndarray[Any, Any],
node_labels: Sequence[str] | None = None,
name: str = "sc_qubo",
) -> QUBOModel:
"""Compile a finite square weight matrix into canonical QUBO terms."""
matrix = _square_matrix("adjacency", adjacency)
size = matrix.shape[0]
labels = _labels(node_labels, size)
if not isinstance(name, str) or not name.strip():
raise ValueError("name must be a non-empty string")
q_matrix: dict[tuple[int, int], float] = {}
for index in range(size):
q_matrix[(index, index)] = -float(np.sum(np.abs(matrix[:, index])))
for second in range(index + 1, size):
weight = float(matrix[index, second] + matrix[second, index]) / 2.0
if abs(weight) > _ZERO_TOLERANCE:
q_matrix[(index, second)] = weight * self._penalty
return QUBOModel(
Q=q_matrix,
qubit_labels={index: label for index, label in enumerate(labels)},
n_qubits=size,
source=name,
)
|
SCBitstreamQUBO
Build QUBOs for SC weight selection and connection pruning.
Source code in src/sc_neurocore/bridges/annealing_compilers.py
| Python |
|---|
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235 | class SCBitstreamQUBO:
"""Build QUBOs for SC weight selection and connection pruning."""
def __init__(self, penalty: float = 5.0) -> None:
"""Configure a positive constraint penalty."""
self._penalty = _finite_scalar("penalty", penalty, positive=True)
def weight_optimization(
self,
target_output: np.ndarray[Any, Any],
candidate_weights: np.ndarray[Any, Any],
n_bits: int = 8,
) -> QUBOModel:
"""Encode ``||target - candidate_weights @ x||²`` for binary ``x``."""
target = np.asarray(target_output, dtype=np.float64)
weights = np.asarray(candidate_weights, dtype=np.float64)
if target.ndim != 1 or target.size == 0:
raise ValueError("target_output must be a non-empty one-dimensional array")
if weights.ndim != 2 or weights.shape[0] != target.shape[0] or weights.shape[1] == 0:
raise ValueError("candidate_weights must be a non-empty matrix with one row per target")
if not bool(np.all(np.isfinite(target))) or not bool(np.all(np.isfinite(weights))):
raise ValueError("weight optimization inputs must contain only finite values")
if isinstance(n_bits, bool) or not isinstance(n_bits, int) or n_bits <= 0:
raise ValueError("n_bits must be a positive integer")
if n_bits > weights.shape[1]:
raise ValueError("n_bits cannot exceed the number of candidate columns")
selected_weights = weights[:, :n_bits]
gram = selected_weights.T @ selected_weights
correlation = selected_weights.T @ target
q_matrix: dict[tuple[int, int], float] = {}
for first in range(n_bits):
q_matrix[(first, first)] = float(gram[first, first] - 2.0 * correlation[first])
for second in range(first + 1, n_bits):
value = float(gram[first, second] + gram[second, first])
if abs(value) > _ZERO_TOLERANCE:
q_matrix[(first, second)] = value
return QUBOModel(
Q=q_matrix,
offset=float(target @ target),
n_qubits=n_bits,
source="sc_weight_optimization",
)
def pruning(
self,
adjacency: np.ndarray[Any, Any],
importance_scores: np.ndarray[Any, Any],
max_connections: int,
) -> QUBOModel:
"""Select exactly ``max_connections`` undirected candidate edges."""
matrix = _square_matrix("adjacency", adjacency)
importance = np.asarray(importance_scores, dtype=np.float64)
if importance.shape != matrix.shape or not bool(np.all(np.isfinite(importance))):
raise ValueError("importance_scores must be finite and match adjacency")
if isinstance(max_connections, bool) or not isinstance(max_connections, int):
raise ValueError("max_connections must be an integer")
edges: list[tuple[int, int]] = []
for first in range(matrix.shape[0]):
for second in range(first + 1, matrix.shape[0]):
if max(abs(matrix[first, second]), abs(matrix[second, first])) > _ZERO_TOLERANCE:
edges.append((first, second))
if max_connections < 0 or max_connections > len(edges):
raise ValueError("max_connections must be between zero and the candidate edge count")
q_matrix: dict[tuple[int, int], float] = {}
for edge_index, (first, second) in enumerate(edges):
symmetric_importance = (
float(importance[first, second] + importance[second, first]) / 2.0
)
q_matrix[(edge_index, edge_index)] = -symmetric_importance
for first in range(len(edges)):
q_matrix[(first, first)] += self._penalty * (1 - 2 * max_connections)
for second in range(first + 1, len(edges)):
q_matrix[(first, second)] = 2 * self._penalty
return QUBOModel(
Q=q_matrix,
offset=self._penalty * max_connections**2,
n_qubits=len(edges),
source="sc_pruning",
)
|
__init__(penalty=5.0)
Configure a positive constraint penalty.
Source code in src/sc_neurocore/bridges/annealing_compilers.py
| Python |
|---|
| def __init__(self, penalty: float = 5.0) -> None:
"""Configure a positive constraint penalty."""
self._penalty = _finite_scalar("penalty", penalty, positive=True)
|
weight_optimization(target_output, candidate_weights, n_bits=8)
Encode ||target - candidate_weights @ x||² for binary x.
Source code in src/sc_neurocore/bridges/annealing_compilers.py
| Python |
|---|
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194 | def weight_optimization(
self,
target_output: np.ndarray[Any, Any],
candidate_weights: np.ndarray[Any, Any],
n_bits: int = 8,
) -> QUBOModel:
"""Encode ``||target - candidate_weights @ x||²`` for binary ``x``."""
target = np.asarray(target_output, dtype=np.float64)
weights = np.asarray(candidate_weights, dtype=np.float64)
if target.ndim != 1 or target.size == 0:
raise ValueError("target_output must be a non-empty one-dimensional array")
if weights.ndim != 2 or weights.shape[0] != target.shape[0] or weights.shape[1] == 0:
raise ValueError("candidate_weights must be a non-empty matrix with one row per target")
if not bool(np.all(np.isfinite(target))) or not bool(np.all(np.isfinite(weights))):
raise ValueError("weight optimization inputs must contain only finite values")
if isinstance(n_bits, bool) or not isinstance(n_bits, int) or n_bits <= 0:
raise ValueError("n_bits must be a positive integer")
if n_bits > weights.shape[1]:
raise ValueError("n_bits cannot exceed the number of candidate columns")
selected_weights = weights[:, :n_bits]
gram = selected_weights.T @ selected_weights
correlation = selected_weights.T @ target
q_matrix: dict[tuple[int, int], float] = {}
for first in range(n_bits):
q_matrix[(first, first)] = float(gram[first, first] - 2.0 * correlation[first])
for second in range(first + 1, n_bits):
value = float(gram[first, second] + gram[second, first])
if abs(value) > _ZERO_TOLERANCE:
q_matrix[(first, second)] = value
return QUBOModel(
Q=q_matrix,
offset=float(target @ target),
n_qubits=n_bits,
source="sc_weight_optimization",
)
|
pruning(adjacency, importance_scores, max_connections)
Select exactly max_connections undirected candidate edges.
Source code in src/sc_neurocore/bridges/annealing_compilers.py
| Python |
|---|
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235 | def pruning(
self,
adjacency: np.ndarray[Any, Any],
importance_scores: np.ndarray[Any, Any],
max_connections: int,
) -> QUBOModel:
"""Select exactly ``max_connections`` undirected candidate edges."""
matrix = _square_matrix("adjacency", adjacency)
importance = np.asarray(importance_scores, dtype=np.float64)
if importance.shape != matrix.shape or not bool(np.all(np.isfinite(importance))):
raise ValueError("importance_scores must be finite and match adjacency")
if isinstance(max_connections, bool) or not isinstance(max_connections, int):
raise ValueError("max_connections must be an integer")
edges: list[tuple[int, int]] = []
for first in range(matrix.shape[0]):
for second in range(first + 1, matrix.shape[0]):
if max(abs(matrix[first, second]), abs(matrix[second, first])) > _ZERO_TOLERANCE:
edges.append((first, second))
if max_connections < 0 or max_connections > len(edges):
raise ValueError("max_connections must be between zero and the candidate edge count")
q_matrix: dict[tuple[int, int], float] = {}
for edge_index, (first, second) in enumerate(edges):
symmetric_importance = (
float(importance[first, second] + importance[second, first]) / 2.0
)
q_matrix[(edge_index, edge_index)] = -symmetric_importance
for first in range(len(edges)):
q_matrix[(first, first)] += self._penalty * (1 - 2 * max_connections)
for second in range(first + 1, len(edges)):
q_matrix[(first, second)] = 2 * self._penalty
return QUBOModel(
Q=q_matrix,
offset=self._penalty * max_connections**2,
n_qubits=len(edges),
source="sc_pruning",
)
|
SimulatedAnnealer
Metropolis simulated annealer with explicit backend selection.
Source code in src/sc_neurocore/bridges/annealing_solvers.py
| Python |
|---|
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212 | class SimulatedAnnealer:
"""Metropolis simulated annealer with explicit backend selection."""
def __init__(
self,
n_sweeps: int = 1000,
beta_start: float = 0.1,
beta_end: float = 10.0,
seed: int = 42,
*,
backend: BackendChoice = "auto",
) -> None:
"""Configure a deterministic annealer."""
self._n_sweeps = _positive_int("n_sweeps", n_sweeps)
self._beta_start = _positive_float("beta_start", beta_start)
self._beta_end = _positive_float("beta_end", beta_end)
if self._beta_end < self._beta_start:
raise ValueError("beta_end must be greater than or equal to beta_start")
if isinstance(seed, bool) or not isinstance(seed, int):
raise ValueError("seed must be an integer")
self._seed = seed
self._backend = validate_backend_choice(backend)
self._rng = np.random.default_rng(seed)
def solve_ising(self, model: IsingModel, num_reads: int = 10) -> dict[str, Any]:
"""Solve an Ising model and preserve a stable sample contract."""
if not isinstance(model, IsingModel):
raise ValueError("model must be an IsingModel")
if model.n_qubits <= 0:
raise ValueError("model must contain at least one qubit")
reads = _positive_int("num_reads", num_reads)
use_rust = self._backend == "rust" or (
self._backend == "auto" and backends.HAS_RUST_QA and model.n_qubits > 10
)
if use_rust:
return self._solve_ising_rust(model, reads)
return self._solve_ising_python(model, reads)
def _solve_ising_rust(self, model: IsingModel, num_reads: int) -> dict[str, Any]:
"""Execute and validate the native solver result."""
h_indices = list(model.h)
j_pairs = list(model.J)
raw = backends.require_rust_annealer()(
h_indices,
[model.h[index] for index in h_indices],
[pair[0] for pair in j_pairs],
[pair[1] for pair in j_pairs],
[model.J[pair] for pair in j_pairs],
model.n_qubits,
model.offset,
self._n_sweeps,
num_reads,
self._beta_start,
self._beta_end,
self._seed,
)
best_vector = _spin_sequence("best_spins", raw.get("best_spins"), model.n_qubits)
raw_samples = raw.get("samples", [])
if isinstance(raw_samples, (str, bytes)) or not isinstance(raw_samples, Sequence):
raise RuntimeError("native solver returned invalid samples")
samples: list[dict[int, int]] = []
for sample_index, sample in enumerate(raw_samples):
vector = _spin_sequence(f"samples[{sample_index}]", sample, model.n_qubits)
samples.append(dict(enumerate(vector)))
raw_energies = raw.get("energies", [])
if isinstance(raw_energies, (str, bytes)) or not isinstance(raw_energies, Sequence):
raise RuntimeError("native solver returned invalid energies")
energies = [
_finite_float(f"energies[{index}]", energy) for index, energy in enumerate(raw_energies)
]
if samples and len(samples) != len(energies):
raise RuntimeError("native solver returned mismatched samples and energies")
return {
"best_spins": dict(enumerate(best_vector)),
"best_energy": _finite_float("best_energy", raw.get("best_energy")),
"energies": energies,
"samples": samples,
"n_sweeps": self._n_sweeps,
"num_reads": num_reads,
"backend": "rust",
}
def _solve_ising_python(self, model: IsingModel, num_reads: int) -> dict[str, Any]:
"""Execute the deterministic pure-Python Metropolis solver."""
best_energy = float("inf")
best_spins: dict[int, int] = {}
all_energies: list[float] = []
all_samples: list[dict[int, int]] = []
for _ in range(num_reads):
spins = {index: int(self._rng.choice((-1, 1))) for index in range(model.n_qubits)}
energy = model.energy(spins, backend="python")
for sweep in range(self._n_sweeps):
exponent = sweep / max(self._n_sweeps - 1, 1)
beta = self._beta_start * (self._beta_end / self._beta_start) ** exponent
for qubit in range(model.n_qubits):
local_field = model.h.get(qubit, 0.0)
for (first, second), strength in model.J.items():
if first == qubit:
local_field += strength * spins.get(second, 1)
elif second == qubit:
local_field += strength * spins.get(first, 1)
delta_energy = -2.0 * spins[qubit] * local_field
if delta_energy < 0.0 or self._rng.random() < math.exp(-beta * delta_energy):
spins[qubit] *= -1
energy += delta_energy
all_energies.append(energy)
all_samples.append(dict(spins))
if energy < best_energy:
best_energy = energy
best_spins = dict(spins)
return {
"best_spins": best_spins,
"best_energy": best_energy,
"energies": all_energies,
"samples": all_samples,
"n_sweeps": self._n_sweeps,
"num_reads": num_reads,
"backend": "python",
}
def solve_qubo(self, model: QUBOModel, num_reads: int = 10) -> dict[str, Any]:
"""Convert a QUBO to Ising, solve it, and map samples back to bits."""
if not isinstance(model, QUBOModel):
raise ValueError("model must be a QUBOModel")
result = self.solve_ising(model.to_ising(), num_reads=num_reads)
best_bits = {index: (spin + 1) // 2 for index, spin in result["best_spins"].items()}
bit_samples = [
{index: (spin + 1) // 2 for index, spin in sample.items()}
for sample in result["samples"]
]
return {
"best_bits": best_bits,
"best_energy": model.energy(best_bits),
"energies": [model.energy(sample) for sample in bit_samples],
"samples": bit_samples,
"n_sweeps": self._n_sweeps,
"num_reads": num_reads,
"backend": result["backend"],
}
|
__init__(n_sweeps=1000, beta_start=0.1, beta_end=10.0, seed=42, *, backend='auto')
Configure a deterministic annealer.
Source code in src/sc_neurocore/bridges/annealing_solvers.py
| Python |
|---|
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90 | def __init__(
self,
n_sweeps: int = 1000,
beta_start: float = 0.1,
beta_end: float = 10.0,
seed: int = 42,
*,
backend: BackendChoice = "auto",
) -> None:
"""Configure a deterministic annealer."""
self._n_sweeps = _positive_int("n_sweeps", n_sweeps)
self._beta_start = _positive_float("beta_start", beta_start)
self._beta_end = _positive_float("beta_end", beta_end)
if self._beta_end < self._beta_start:
raise ValueError("beta_end must be greater than or equal to beta_start")
if isinstance(seed, bool) or not isinstance(seed, int):
raise ValueError("seed must be an integer")
self._seed = seed
self._backend = validate_backend_choice(backend)
self._rng = np.random.default_rng(seed)
|
solve_ising(model, num_reads=10)
Solve an Ising model and preserve a stable sample contract.
Source code in src/sc_neurocore/bridges/annealing_solvers.py
| Python |
|---|
92
93
94
95
96
97
98
99
100
101
102
103
104 | def solve_ising(self, model: IsingModel, num_reads: int = 10) -> dict[str, Any]:
"""Solve an Ising model and preserve a stable sample contract."""
if not isinstance(model, IsingModel):
raise ValueError("model must be an IsingModel")
if model.n_qubits <= 0:
raise ValueError("model must contain at least one qubit")
reads = _positive_int("num_reads", num_reads)
use_rust = self._backend == "rust" or (
self._backend == "auto" and backends.HAS_RUST_QA and model.n_qubits > 10
)
if use_rust:
return self._solve_ising_rust(model, reads)
return self._solve_ising_python(model, reads)
|
solve_qubo(model, num_reads=10)
Convert a QUBO to Ising, solve it, and map samples back to bits.
Source code in src/sc_neurocore/bridges/annealing_solvers.py
| Python |
|---|
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212 | def solve_qubo(self, model: QUBOModel, num_reads: int = 10) -> dict[str, Any]:
"""Convert a QUBO to Ising, solve it, and map samples back to bits."""
if not isinstance(model, QUBOModel):
raise ValueError("model must be a QUBOModel")
result = self.solve_ising(model.to_ising(), num_reads=num_reads)
best_bits = {index: (spin + 1) // 2 for index, spin in result["best_spins"].items()}
bit_samples = [
{index: (spin + 1) // 2 for index, spin in sample.items()}
for sample in result["samples"]
]
return {
"best_bits": best_bits,
"best_energy": model.energy(best_bits),
"energies": [model.energy(sample) for sample in bit_samples],
"samples": bit_samples,
"n_sweeps": self._n_sweeps,
"num_reads": num_reads,
"backend": result["backend"],
}
|
DWaveInterface
Submit validated Ising models to D-Wave or use a local fallback.
Source code in src/sc_neurocore/bridges/annealing_solvers.py
| Python |
|---|
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267 | class DWaveInterface:
"""Submit validated Ising models to D-Wave or use a local fallback."""
def __init__(
self,
chain_strength: float = _DEFAULT_CHAIN_STRENGTH,
num_reads: int = _DEFAULT_NUM_READS,
annealing_time_us: float = _DEFAULT_ANNEALING_TIME_US,
) -> None:
"""Configure QPU sampling parameters."""
self._chain_strength = _positive_float("chain_strength", chain_strength)
self._num_reads = _positive_int("num_reads", num_reads)
self._annealing_time_us = _positive_float("annealing_time_us", annealing_time_us)
@property
def available(self) -> bool:
"""Return whether both Ocean SDK components are importable."""
return backends.HAS_DWAVE and backends.HAS_DIMOD
def solve_ising(self, model: IsingModel) -> dict[str, Any]:
"""Submit to a QPU, or run a bounded local fallback when unavailable."""
if not isinstance(model, IsingModel) or model.n_qubits <= 0:
raise ValueError("model must be a non-empty IsingModel")
if not self.available:
result = SimulatedAnnealer().solve_ising(model, num_reads=min(self._num_reads, 20))
result["backend"] = "simulated_annealing_fallback"
return result
dimod_module, sampler_type, composite_type = backends.require_dwave_components()
bqm = dimod_module.BinaryQuadraticModel(model.h, model.J, model.offset, "SPIN")
sampler = composite_type(sampler_type())
response = sampler.sample(
bqm,
num_reads=self._num_reads,
chain_strength=self._chain_strength,
annealing_time=self._annealing_time_us,
)
best = getattr(response, "first", None)
sample = getattr(best, "sample", None)
energy = getattr(best, "energy", None)
if not isinstance(sample, Mapping):
raise RuntimeError("D-Wave response did not contain a best sample")
best_spins = {int(index): int(spin) for index, spin in sample.items()}
model.energy(best_spins, backend="python")
info = getattr(response, "info", {})
timing = info.get("timing", {}) if isinstance(info, Mapping) else {}
return {
"best_spins": best_spins,
"best_energy": _finite_float("best_energy", energy),
"num_reads": self._num_reads,
"backend": "dwave_qpu",
"timing": timing,
}
|
available
property
Return whether both Ocean SDK components are importable.
__init__(chain_strength=_DEFAULT_CHAIN_STRENGTH, num_reads=_DEFAULT_NUM_READS, annealing_time_us=_DEFAULT_ANNEALING_TIME_US)
Configure QPU sampling parameters.
Source code in src/sc_neurocore/bridges/annealing_solvers.py
| Python |
|---|
218
219
220
221
222
223
224
225
226
227 | def __init__(
self,
chain_strength: float = _DEFAULT_CHAIN_STRENGTH,
num_reads: int = _DEFAULT_NUM_READS,
annealing_time_us: float = _DEFAULT_ANNEALING_TIME_US,
) -> None:
"""Configure QPU sampling parameters."""
self._chain_strength = _positive_float("chain_strength", chain_strength)
self._num_reads = _positive_int("num_reads", num_reads)
self._annealing_time_us = _positive_float("annealing_time_us", annealing_time_us)
|
solve_ising(model)
Submit to a QPU, or run a bounded local fallback when unavailable.
Source code in src/sc_neurocore/bridges/annealing_solvers.py
| Python |
|---|
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267 | def solve_ising(self, model: IsingModel) -> dict[str, Any]:
"""Submit to a QPU, or run a bounded local fallback when unavailable."""
if not isinstance(model, IsingModel) or model.n_qubits <= 0:
raise ValueError("model must be a non-empty IsingModel")
if not self.available:
result = SimulatedAnnealer().solve_ising(model, num_reads=min(self._num_reads, 20))
result["backend"] = "simulated_annealing_fallback"
return result
dimod_module, sampler_type, composite_type = backends.require_dwave_components()
bqm = dimod_module.BinaryQuadraticModel(model.h, model.J, model.offset, "SPIN")
sampler = composite_type(sampler_type())
response = sampler.sample(
bqm,
num_reads=self._num_reads,
chain_strength=self._chain_strength,
annealing_time=self._annealing_time_us,
)
best = getattr(response, "first", None)
sample = getattr(best, "sample", None)
energy = getattr(best, "energy", None)
if not isinstance(sample, Mapping):
raise RuntimeError("D-Wave response did not contain a best sample")
best_spins = {int(index): int(spin) for index, spin in sample.items()}
model.energy(best_spins, backend="python")
info = getattr(response, "info", {})
timing = info.get("timing", {}) if isinstance(info, Mapping) else {}
return {
"best_spins": best_spins,
"best_energy": _finite_float("best_energy", energy),
"num_reads": self._num_reads,
"backend": "dwave_qpu",
"timing": timing,
}
|
EnergyLandscape
Compute energy statistics and spectral gaps for an Ising model.
Source code in src/sc_neurocore/bridges/annealing_analysis.py
| Python |
|---|
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145 | class EnergyLandscape:
"""Compute energy statistics and spectral gaps for an Ising model."""
def __init__(
self,
*,
backend: BackendChoice = "auto",
random_sample_count: int = 10_000,
seed: int = 42,
) -> None:
"""Configure deterministic large-model sampling and backend choice."""
self._backend = validate_backend_choice(backend)
if (
isinstance(random_sample_count, bool)
or not isinstance(random_sample_count, int)
or random_sample_count <= 0
):
raise ValueError("random_sample_count must be a positive integer")
if isinstance(seed, bool) or not isinstance(seed, int):
raise ValueError("seed must be an integer")
self._random_sample_count = random_sample_count
self._seed = seed
def analyze(
self,
model: IsingModel,
samples: Sequence[Mapping[int, int]] | None = None,
) -> dict[str, Any]:
"""Analyze exhaustive, supplied, or deterministic random samples."""
if not isinstance(model, IsingModel) or model.n_qubits <= 0:
raise ValueError("model must be a non-empty IsingModel")
if samples is None:
if model.n_qubits <= 20:
normalized_samples = self._enumerate_all(model.n_qubits)
else:
rng = np.random.default_rng(self._seed)
normalized_samples = [
{index: int(rng.choice((-1, 1))) for index in range(model.n_qubits)}
for _ in range(self._random_sample_count)
]
else:
if isinstance(samples, (str, bytes)):
raise ValueError("samples must be a sequence of spin mappings")
normalized_samples = [_validated_sample(sample) for sample in samples]
if not normalized_samples:
raise ValueError("samples must not be empty")
use_rust = self._backend == "rust" or (
self._backend == "auto" and backends.HAS_RUST_QA and len(normalized_samples) > 100
)
if use_rust:
h_indices = list(model.h)
j_pairs = list(model.J)
raw_energies = backends.require_rust_batch_energy()(
h_indices,
[model.h[index] for index in h_indices],
[pair[0] for pair in j_pairs],
[pair[1] for pair in j_pairs],
[model.J[pair] for pair in j_pairs],
[
[sample.get(index, 1) for index in range(model.n_qubits)]
for sample in normalized_samples
],
model.offset,
)
energies = [
_finite(f"energies[{index}]", value) for index, value in enumerate(raw_energies)
]
if len(energies) != len(normalized_samples):
raise RuntimeError("native batch backend returned the wrong energy count")
else:
energies = [model.energy(sample, backend="python") for sample in normalized_samples]
unique_energies = sorted(set(energies))
minimum = unique_energies[0]
spectral_gap = unique_energies[1] - unique_energies[0] if len(unique_energies) > 1 else 0.0
return {
"min_energy": minimum,
"max_energy": max(energies),
"mean_energy": float(np.mean(energies)),
"std_energy": float(np.std(energies)),
"spectral_gap": spectral_gap,
"degeneracy": energies.count(minimum),
"n_unique_energies": len(unique_energies),
"n_samples": len(normalized_samples),
}
@staticmethod
def _enumerate_all(n_qubits: int) -> list[dict[int, int]]:
"""Enumerate every configuration for at most 20 qubits."""
if isinstance(n_qubits, bool) or not isinstance(n_qubits, int) or not 0 <= n_qubits <= 20:
raise ValueError("n_qubits must be an integer between zero and 20")
return [
{index: 1 if (bits >> index) & 1 else -1 for index in range(n_qubits)}
for bits in range(2**n_qubits)
]
|
__init__(*, backend='auto', random_sample_count=10000, seed=42)
Configure deterministic large-model sampling and backend choice.
Source code in src/sc_neurocore/bridges/annealing_analysis.py
| Python |
|---|
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71 | def __init__(
self,
*,
backend: BackendChoice = "auto",
random_sample_count: int = 10_000,
seed: int = 42,
) -> None:
"""Configure deterministic large-model sampling and backend choice."""
self._backend = validate_backend_choice(backend)
if (
isinstance(random_sample_count, bool)
or not isinstance(random_sample_count, int)
or random_sample_count <= 0
):
raise ValueError("random_sample_count must be a positive integer")
if isinstance(seed, bool) or not isinstance(seed, int):
raise ValueError("seed must be an integer")
self._random_sample_count = random_sample_count
self._seed = seed
|
analyze(model, samples=None)
Analyze exhaustive, supplied, or deterministic random samples.
Source code in src/sc_neurocore/bridges/annealing_analysis.py
| Python |
|---|
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135 | def analyze(
self,
model: IsingModel,
samples: Sequence[Mapping[int, int]] | None = None,
) -> dict[str, Any]:
"""Analyze exhaustive, supplied, or deterministic random samples."""
if not isinstance(model, IsingModel) or model.n_qubits <= 0:
raise ValueError("model must be a non-empty IsingModel")
if samples is None:
if model.n_qubits <= 20:
normalized_samples = self._enumerate_all(model.n_qubits)
else:
rng = np.random.default_rng(self._seed)
normalized_samples = [
{index: int(rng.choice((-1, 1))) for index in range(model.n_qubits)}
for _ in range(self._random_sample_count)
]
else:
if isinstance(samples, (str, bytes)):
raise ValueError("samples must be a sequence of spin mappings")
normalized_samples = [_validated_sample(sample) for sample in samples]
if not normalized_samples:
raise ValueError("samples must not be empty")
use_rust = self._backend == "rust" or (
self._backend == "auto" and backends.HAS_RUST_QA and len(normalized_samples) > 100
)
if use_rust:
h_indices = list(model.h)
j_pairs = list(model.J)
raw_energies = backends.require_rust_batch_energy()(
h_indices,
[model.h[index] for index in h_indices],
[pair[0] for pair in j_pairs],
[pair[1] for pair in j_pairs],
[model.J[pair] for pair in j_pairs],
[
[sample.get(index, 1) for index in range(model.n_qubits)]
for sample in normalized_samples
],
model.offset,
)
energies = [
_finite(f"energies[{index}]", value) for index, value in enumerate(raw_energies)
]
if len(energies) != len(normalized_samples):
raise RuntimeError("native batch backend returned the wrong energy count")
else:
energies = [model.energy(sample, backend="python") for sample in normalized_samples]
unique_energies = sorted(set(energies))
minimum = unique_energies[0]
spectral_gap = unique_energies[1] - unique_energies[0] if len(unique_energies) > 1 else 0.0
return {
"min_energy": minimum,
"max_energy": max(energies),
"mean_energy": float(np.mean(energies)),
"std_energy": float(np.std(energies)),
"spectral_gap": spectral_gap,
"degeneracy": energies.count(minimum),
"n_unique_energies": len(unique_energies),
"n_samples": len(normalized_samples),
}
|
EmbeddingAnalyzer
Estimate logical-to-physical embedding requirements.
Source code in src/sc_neurocore/bridges/annealing_analysis.py
| Python |
|---|
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175 | class EmbeddingAnalyzer:
"""Estimate logical-to-physical embedding requirements."""
def analyze(self, model: IsingModel) -> dict[str, Any]:
"""Return graph density, degree, and Pegasus chain estimates."""
if not isinstance(model, IsingModel) or model.n_qubits <= 0:
raise ValueError("model must be a non-empty IsingModel")
size = model.n_qubits
coupling_count = len(model.J)
max_possible = size * (size - 1) // 2
degree = {index: 0 for index in range(size)}
for first, second in model.J:
degree[first] += 1
degree[second] += 1
max_degree = max(degree.values())
mean_degree = sum(degree.values()) / size
chain_length = max(1, math.ceil(max_degree / 15))
estimated_physical = size * chain_length
return {
"n_logical_qubits": size,
"n_couplers": coupling_count,
"density": coupling_count / max(max_possible, 1),
"max_degree": max_degree,
"mean_degree": float(mean_degree),
"min_chain_estimate": chain_length,
"estimated_physical_qubits": estimated_physical,
"pegasus_compatible": estimated_physical <= 5000,
}
|
analyze(model)
Return graph density, degree, and Pegasus chain estimates.
Source code in src/sc_neurocore/bridges/annealing_analysis.py
| Python |
|---|
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175 | def analyze(self, model: IsingModel) -> dict[str, Any]:
"""Return graph density, degree, and Pegasus chain estimates."""
if not isinstance(model, IsingModel) or model.n_qubits <= 0:
raise ValueError("model must be a non-empty IsingModel")
size = model.n_qubits
coupling_count = len(model.J)
max_possible = size * (size - 1) // 2
degree = {index: 0 for index in range(size)}
for first, second in model.J:
degree[first] += 1
degree[second] += 1
max_degree = max(degree.values())
mean_degree = sum(degree.values()) / size
chain_length = max(1, math.ceil(max_degree / 15))
estimated_physical = size * chain_length
return {
"n_logical_qubits": size,
"n_couplers": coupling_count,
"density": coupling_count / max(max_possible, 1),
"max_degree": max_degree,
"mean_degree": float(mean_degree),
"min_chain_estimate": chain_length,
"estimated_physical_qubits": estimated_physical,
"pegasus_compatible": estimated_physical <= 5000,
}
|
SampleAggregator
Deduplicate samples and compute energy-distribution statistics.
Source code in src/sc_neurocore/bridges/annealing_analysis.py
| Python |
|---|
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226 | class SampleAggregator:
"""Deduplicate samples and compute energy-distribution statistics."""
def aggregate(
self,
samples: Sequence[Mapping[int, int]],
energies: Sequence[float],
temperature: float = 1.0,
) -> dict[str, Any]:
"""Aggregate an aligned sample and energy sequence."""
if isinstance(samples, (str, bytes)) or isinstance(energies, (str, bytes)):
raise ValueError("samples and energies must be sequences")
if len(samples) != len(energies):
raise ValueError("samples and energies must have equal lengths")
if not samples:
return {"unique_samples": 0, "best": {}, "histogram": {}}
thermal = _finite("temperature", temperature)
if thermal <= 0.0:
raise ValueError("temperature must be greater than zero")
normalized_samples = [_validated_sample(sample, spins_only=False) for sample in samples]
normalized_energies = [
_finite(f"energies[{index}]", value) for index, value in enumerate(energies)
]
paired = sorted(zip(normalized_energies, normalized_samples), key=lambda item: item[0])
best_energy, best_sample = paired[0]
unique_samples = len({tuple(sorted(sample.items())) for _, sample in paired})
energy_array = np.asarray(normalized_energies, dtype=np.float64)
bin_count = max(min(20, len(set(normalized_energies))), 1)
counts, bin_edges = np.histogram(energy_array, bins=bin_count)
shifted_weights = np.exp(-(energy_array - min(normalized_energies)) / thermal)
partition = float(np.sum(shifted_weights))
ground_count = sum(1 for energy in normalized_energies if abs(energy - best_energy) < 1e-10)
return {
"unique_samples": unique_samples,
"total_samples": len(normalized_samples),
"best_sample": best_sample,
"best_energy": best_energy,
"mean_energy": float(np.mean(energy_array)),
"std_energy": float(np.std(energy_array)),
"boltzmann_avg_energy": float(np.sum(shifted_weights * energy_array) / partition),
"success_probability": ground_count / len(normalized_energies),
"gs_degeneracy": ground_count,
"histogram": {
"counts": counts.tolist(),
"bin_edges": bin_edges.tolist(),
},
}
|
aggregate(samples, energies, temperature=1.0)
Aggregate an aligned sample and energy sequence.
Source code in src/sc_neurocore/bridges/annealing_analysis.py
| Python |
|---|
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226 | def aggregate(
self,
samples: Sequence[Mapping[int, int]],
energies: Sequence[float],
temperature: float = 1.0,
) -> dict[str, Any]:
"""Aggregate an aligned sample and energy sequence."""
if isinstance(samples, (str, bytes)) or isinstance(energies, (str, bytes)):
raise ValueError("samples and energies must be sequences")
if len(samples) != len(energies):
raise ValueError("samples and energies must have equal lengths")
if not samples:
return {"unique_samples": 0, "best": {}, "histogram": {}}
thermal = _finite("temperature", temperature)
if thermal <= 0.0:
raise ValueError("temperature must be greater than zero")
normalized_samples = [_validated_sample(sample, spins_only=False) for sample in samples]
normalized_energies = [
_finite(f"energies[{index}]", value) for index, value in enumerate(energies)
]
paired = sorted(zip(normalized_energies, normalized_samples), key=lambda item: item[0])
best_energy, best_sample = paired[0]
unique_samples = len({tuple(sorted(sample.items())) for _, sample in paired})
energy_array = np.asarray(normalized_energies, dtype=np.float64)
bin_count = max(min(20, len(set(normalized_energies))), 1)
counts, bin_edges = np.histogram(energy_array, bins=bin_count)
shifted_weights = np.exp(-(energy_array - min(normalized_energies)) / thermal)
partition = float(np.sum(shifted_weights))
ground_count = sum(1 for energy in normalized_energies if abs(energy - best_energy) < 1e-10)
return {
"unique_samples": unique_samples,
"total_samples": len(normalized_samples),
"best_sample": best_sample,
"best_energy": best_energy,
"mean_energy": float(np.mean(energy_array)),
"std_energy": float(np.std(energy_array)),
"boltzmann_avg_energy": float(np.sum(shifted_weights * energy_array) / partition),
"success_probability": ground_count / len(normalized_energies),
"gs_degeneracy": ground_count,
"histogram": {
"counts": counts.tolist(),
"bin_edges": bin_edges.tolist(),
},
}
|
TTSAnalyzer
Compute time-to-solution from single-run success probability.
Source code in src/sc_neurocore/bridges/annealing_analysis.py
| Python |
|---|
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317 | class TTSAnalyzer:
"""Compute time-to-solution from single-run success probability."""
def compute(
self,
p_success: float,
t_anneal_us: float,
p_target: float = 0.99,
) -> dict[str, float]:
"""Compute the standard cumulative-success TTS metric."""
success = _finite("p_success", p_success)
target = _finite("p_target", p_target)
anneal_time = _finite("t_anneal_us", t_anneal_us)
if not 0.0 <= success <= 1.0:
raise ValueError("p_success must be between zero and one")
if not 0.0 < target < 1.0:
raise ValueError("p_target must be strictly between zero and one")
if anneal_time <= 0.0:
raise ValueError("t_anneal_us must be greater than zero")
if success == 0.0:
return {
"tts_us": float("inf"),
"tts_ms": float("inf"),
"n_runs_needed": float("inf"),
"p_success": 0.0,
"p_target": target,
}
if success == 1.0:
return {
"tts_us": anneal_time,
"tts_ms": anneal_time / 1000.0,
"n_runs_needed": 1.0,
"p_success": 1.0,
"p_target": target,
}
run_count = math.log1p(-target) / math.log1p(-success)
tts = anneal_time * run_count
return {
"tts_us": tts,
"tts_ms": tts / 1000.0,
"n_runs_needed": run_count,
"p_success": success,
"p_target": target,
}
def from_samples(
self,
energies: Sequence[float],
ground_state_energy: float,
t_anneal_us: float = 20.0,
tolerance: float = 1e-6,
p_target: float = 0.99,
) -> dict[str, float]:
"""Estimate single-run success from observed energies."""
if isinstance(energies, (str, bytes)):
raise ValueError("energies must be a sequence")
ground = _finite("ground_state_energy", ground_state_energy)
threshold = _finite("tolerance", tolerance)
if threshold <= 0.0:
raise ValueError("tolerance must be greater than zero")
normalized = [
_finite(f"energies[{index}]", energy) for index, energy in enumerate(energies)
]
ground_count = sum(1 for energy in normalized if abs(energy - ground) < threshold)
success = ground_count / len(normalized) if normalized else 0.0
return self.compute(success, t_anneal_us, p_target)
def compare_solvers(
self,
results: Mapping[str, Mapping[str, Any]],
ground_state_energy: float,
tolerance: float = 1e-6,
) -> dict[str, dict[str, float]]:
"""Compute comparable TTS rows for named solver outputs."""
comparison: dict[str, dict[str, float]] = {}
for name, data in results.items():
if not isinstance(name, str) or not name:
raise ValueError("solver names must be non-empty strings")
raw_energies = data.get("energies")
if isinstance(raw_energies, (str, bytes)) or not isinstance(raw_energies, Sequence):
raise ValueError(f"solver {name!r} must provide an energy sequence")
comparison[name] = self.from_samples(
raw_energies,
ground_state_energy,
t_anneal_us=_finite("t_anneal_us", data.get("t_anneal_us", 20.0)),
tolerance=tolerance,
)
return comparison
|
compute(p_success, t_anneal_us, p_target=0.99)
Compute the standard cumulative-success TTS metric.
Source code in src/sc_neurocore/bridges/annealing_analysis.py
| Python |
|---|
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273 | def compute(
self,
p_success: float,
t_anneal_us: float,
p_target: float = 0.99,
) -> dict[str, float]:
"""Compute the standard cumulative-success TTS metric."""
success = _finite("p_success", p_success)
target = _finite("p_target", p_target)
anneal_time = _finite("t_anneal_us", t_anneal_us)
if not 0.0 <= success <= 1.0:
raise ValueError("p_success must be between zero and one")
if not 0.0 < target < 1.0:
raise ValueError("p_target must be strictly between zero and one")
if anneal_time <= 0.0:
raise ValueError("t_anneal_us must be greater than zero")
if success == 0.0:
return {
"tts_us": float("inf"),
"tts_ms": float("inf"),
"n_runs_needed": float("inf"),
"p_success": 0.0,
"p_target": target,
}
if success == 1.0:
return {
"tts_us": anneal_time,
"tts_ms": anneal_time / 1000.0,
"n_runs_needed": 1.0,
"p_success": 1.0,
"p_target": target,
}
run_count = math.log1p(-target) / math.log1p(-success)
tts = anneal_time * run_count
return {
"tts_us": tts,
"tts_ms": tts / 1000.0,
"n_runs_needed": run_count,
"p_success": success,
"p_target": target,
}
|
from_samples(energies, ground_state_energy, t_anneal_us=20.0, tolerance=1e-06, p_target=0.99)
Estimate single-run success from observed energies.
Source code in src/sc_neurocore/bridges/annealing_analysis.py
| Python |
|---|
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295 | def from_samples(
self,
energies: Sequence[float],
ground_state_energy: float,
t_anneal_us: float = 20.0,
tolerance: float = 1e-6,
p_target: float = 0.99,
) -> dict[str, float]:
"""Estimate single-run success from observed energies."""
if isinstance(energies, (str, bytes)):
raise ValueError("energies must be a sequence")
ground = _finite("ground_state_energy", ground_state_energy)
threshold = _finite("tolerance", tolerance)
if threshold <= 0.0:
raise ValueError("tolerance must be greater than zero")
normalized = [
_finite(f"energies[{index}]", energy) for index, energy in enumerate(energies)
]
ground_count = sum(1 for energy in normalized if abs(energy - ground) < threshold)
success = ground_count / len(normalized) if normalized else 0.0
return self.compute(success, t_anneal_us, p_target)
|
compare_solvers(results, ground_state_energy, tolerance=1e-06)
Compute comparable TTS rows for named solver outputs.
Source code in src/sc_neurocore/bridges/annealing_analysis.py
| Python |
|---|
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317 | def compare_solvers(
self,
results: Mapping[str, Mapping[str, Any]],
ground_state_energy: float,
tolerance: float = 1e-6,
) -> dict[str, dict[str, float]]:
"""Compute comparable TTS rows for named solver outputs."""
comparison: dict[str, dict[str, float]] = {}
for name, data in results.items():
if not isinstance(name, str) or not name:
raise ValueError("solver names must be non-empty strings")
raw_energies = data.get("energies")
if isinstance(raw_energies, (str, bytes)) or not isinstance(raw_energies, Sequence):
raise ValueError(f"solver {name!r} must provide an energy sequence")
comparison[name] = self.from_samples(
raw_energies,
ground_state_energy,
t_anneal_us=_finite("t_anneal_us", data.get("t_anneal_us", 20.0)),
tolerance=tolerance,
)
return comparison
|
HardwareGraph
Capacity model for Chimera, Pegasus, and Zephyr topologies.
Source code in src/sc_neurocore/bridges/annealing_hardware.py
| Python |
|---|
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121 | class HardwareGraph:
"""Capacity model for Chimera, Pegasus, and Zephyr topologies."""
_TOPOLOGIES: ClassVar[dict[str, dict[str, int]]] = {
"chimera": {"connectivity": 6, "base_qubits_per_cell": 8},
"pegasus": {"connectivity": 15, "base_qubits_per_cell": 24},
"zephyr": {"connectivity": 20, "base_qubits_per_cell": 48},
}
def __init__(self, topology: str = "pegasus", size: int = 16) -> None:
"""Select a topology and a valid positive size parameter."""
if topology not in self._TOPOLOGIES:
raise ValueError(f"Unknown topology: {topology}")
if isinstance(size, bool) or not isinstance(size, int) or size <= 0:
raise ValueError("size must be a positive integer")
if topology == "pegasus" and size < 2:
raise ValueError("Pegasus size must be at least two")
self._topology = topology
self._size = size
self._props = self._TOPOLOGIES[topology]
@property
def n_physical_qubits(self) -> int:
"""Return the idealized physical-qubit capacity."""
if self._topology == "chimera":
return self._size * self._size * 8
if self._topology == "pegasus":
return 24 * self._size * (self._size - 1)
return 48 * self._size * self._size
@property
def connectivity(self) -> int:
"""Return the idealized per-qubit connectivity."""
return self._props["connectivity"]
def can_embed(self, model: IsingModel) -> dict[str, Any]:
"""Return a conservative degree-based capacity estimate."""
if not isinstance(model, IsingModel) or model.n_qubits <= 0:
raise ValueError("model must be a non-empty IsingModel")
degree = {index: 0 for index in range(model.n_qubits)}
for first, second in model.J:
degree[first] += 1
degree[second] += 1
max_degree = max(degree.values())
chain_length = max(1, math.ceil(max_degree / self.connectivity))
physical_needed = model.n_qubits * chain_length
return {
"embeddable": physical_needed <= self.n_physical_qubits,
"topology": self._topology,
"size": self._size,
"n_logical": model.n_qubits,
"n_couplers": len(model.J),
"max_degree": max_degree,
"chain_length_estimate": chain_length,
"n_physical_available": self.n_physical_qubits,
"estimated_physical_needed": physical_needed,
"utilization_pct": physical_needed / self.n_physical_qubits * 100.0,
}
|
n_physical_qubits
property
Return the idealized physical-qubit capacity.
connectivity
property
Return the idealized per-qubit connectivity.
__init__(topology='pegasus', size=16)
Select a topology and a valid positive size parameter.
Source code in src/sc_neurocore/bridges/annealing_hardware.py
| Python |
|---|
73
74
75
76
77
78
79
80
81
82
83 | def __init__(self, topology: str = "pegasus", size: int = 16) -> None:
"""Select a topology and a valid positive size parameter."""
if topology not in self._TOPOLOGIES:
raise ValueError(f"Unknown topology: {topology}")
if isinstance(size, bool) or not isinstance(size, int) or size <= 0:
raise ValueError("size must be a positive integer")
if topology == "pegasus" and size < 2:
raise ValueError("Pegasus size must be at least two")
self._topology = topology
self._size = size
self._props = self._TOPOLOGIES[topology]
|
can_embed(model)
Return a conservative degree-based capacity estimate.
Source code in src/sc_neurocore/bridges/annealing_hardware.py
| Python |
|---|
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121 | def can_embed(self, model: IsingModel) -> dict[str, Any]:
"""Return a conservative degree-based capacity estimate."""
if not isinstance(model, IsingModel) or model.n_qubits <= 0:
raise ValueError("model must be a non-empty IsingModel")
degree = {index: 0 for index in range(model.n_qubits)}
for first, second in model.J:
degree[first] += 1
degree[second] += 1
max_degree = max(degree.values())
chain_length = max(1, math.ceil(max_degree / self.connectivity))
physical_needed = model.n_qubits * chain_length
return {
"embeddable": physical_needed <= self.n_physical_qubits,
"topology": self._topology,
"size": self._size,
"n_logical": model.n_qubits,
"n_couplers": len(model.J),
"max_degree": max_degree,
"chain_length_estimate": chain_length,
"n_physical_available": self.n_physical_qubits,
"estimated_physical_needed": physical_needed,
"utilization_pct": physical_needed / self.n_physical_qubits * 100.0,
}
|
ChainBreakResolver
Resolve embedded-chain disagreement by vote or local energy search.
Source code in src/sc_neurocore/bridges/annealing_hardware.py
| Python |
|---|
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203 | class ChainBreakResolver:
"""Resolve embedded-chain disagreement by vote or local energy search."""
def __init__(self, method: str = "majority_vote") -> None:
"""Select a supported deterministic resolution method."""
if method not in {"majority_vote", "minimize_energy"}:
raise ValueError(f"Unknown method: {method}")
self._method = method
def resolve(
self,
physical_samples: Sequence[Mapping[int, int]],
chains: Mapping[int, Sequence[int]],
model: IsingModel | None = None,
) -> list[dict[int, int]]:
"""Map physical samples to logical samples and optionally refine them."""
normalized_samples = _validated_physical_samples(physical_samples)
normalized_chains = _validated_chains(chains)
if self._method == "minimize_energy" and model is None:
raise ValueError("model is required for minimize_energy resolution")
if model is not None:
if not isinstance(model, IsingModel):
raise ValueError("model must be an IsingModel")
if any(logical >= model.n_qubits for logical in normalized_chains):
raise ValueError("chain logical indices must fit within model.n_qubits")
resolved: list[dict[int, int]] = []
for sample in normalized_samples:
logical = {
logical_index: (
1
if sum(sample.get(physical_index, 1) for physical_index in physical) >= 0
else -1
)
for logical_index, physical in normalized_chains.items()
}
if self._method == "minimize_energy" and model is not None:
energy = model.energy(logical, backend="python")
for logical_index in logical:
candidate = dict(logical)
candidate[logical_index] *= -1
candidate_energy = model.energy(candidate, backend="python")
if candidate_energy < energy:
logical[logical_index] *= -1
energy = candidate_energy
resolved.append(logical)
return resolved
def analyze_breaks(
self,
physical_samples: Sequence[Mapping[int, int]],
chains: Mapping[int, Sequence[int]],
) -> dict[str, Any]:
"""Measure per-chain and aggregate break rates."""
normalized_samples = _validated_physical_samples(physical_samples)
normalized_chains = _validated_chains(chains)
total_breaks = 0
breakable_chain_count = 0
per_chain: dict[int, float] = {}
for logical_index, physical in normalized_chains.items():
if len(physical) == 1:
per_chain[logical_index] = 0.0
continue
breaks = sum(
1
for sample in normalized_samples
if len({sample.get(index, 1) for index in physical}) > 1
)
per_chain[logical_index] = (
breaks / len(normalized_samples) if normalized_samples else 0.0
)
total_breaks += breaks
breakable_chain_count += 1
opportunity_count = breakable_chain_count * len(normalized_samples)
return {
"total_breaks": total_breaks,
"break_rate": total_breaks / opportunity_count if opportunity_count else 0.0,
"per_chain": per_chain,
"n_chains": len(normalized_chains),
}
|
__init__(method='majority_vote')
Select a supported deterministic resolution method.
Source code in src/sc_neurocore/bridges/annealing_hardware.py
| Python |
|---|
| def __init__(self, method: str = "majority_vote") -> None:
"""Select a supported deterministic resolution method."""
if method not in {"majority_vote", "minimize_energy"}:
raise ValueError(f"Unknown method: {method}")
self._method = method
|
resolve(physical_samples, chains, model=None)
Map physical samples to logical samples and optionally refine them.
Source code in src/sc_neurocore/bridges/annealing_hardware.py
| Python |
|---|
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170 | def resolve(
self,
physical_samples: Sequence[Mapping[int, int]],
chains: Mapping[int, Sequence[int]],
model: IsingModel | None = None,
) -> list[dict[int, int]]:
"""Map physical samples to logical samples and optionally refine them."""
normalized_samples = _validated_physical_samples(physical_samples)
normalized_chains = _validated_chains(chains)
if self._method == "minimize_energy" and model is None:
raise ValueError("model is required for minimize_energy resolution")
if model is not None:
if not isinstance(model, IsingModel):
raise ValueError("model must be an IsingModel")
if any(logical >= model.n_qubits for logical in normalized_chains):
raise ValueError("chain logical indices must fit within model.n_qubits")
resolved: list[dict[int, int]] = []
for sample in normalized_samples:
logical = {
logical_index: (
1
if sum(sample.get(physical_index, 1) for physical_index in physical) >= 0
else -1
)
for logical_index, physical in normalized_chains.items()
}
if self._method == "minimize_energy" and model is not None:
energy = model.energy(logical, backend="python")
for logical_index in logical:
candidate = dict(logical)
candidate[logical_index] *= -1
candidate_energy = model.energy(candidate, backend="python")
if candidate_energy < energy:
logical[logical_index] *= -1
energy = candidate_energy
resolved.append(logical)
return resolved
|
analyze_breaks(physical_samples, chains)
Measure per-chain and aggregate break rates.
Source code in src/sc_neurocore/bridges/annealing_hardware.py
| Python |
|---|
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203 | def analyze_breaks(
self,
physical_samples: Sequence[Mapping[int, int]],
chains: Mapping[int, Sequence[int]],
) -> dict[str, Any]:
"""Measure per-chain and aggregate break rates."""
normalized_samples = _validated_physical_samples(physical_samples)
normalized_chains = _validated_chains(chains)
total_breaks = 0
breakable_chain_count = 0
per_chain: dict[int, float] = {}
for logical_index, physical in normalized_chains.items():
if len(physical) == 1:
per_chain[logical_index] = 0.0
continue
breaks = sum(
1
for sample in normalized_samples
if len({sample.get(index, 1) for index in physical}) > 1
)
per_chain[logical_index] = (
breaks / len(normalized_samples) if normalized_samples else 0.0
)
total_breaks += breaks
breakable_chain_count += 1
opportunity_count = breakable_chain_count * len(normalized_samples)
return {
"total_breaks": total_breaks,
"break_rate": total_breaks / opportunity_count if opportunity_count else 0.0,
"per_chain": per_chain,
"n_chains": len(normalized_chains),
}
|
ProblemDecomposer
Partition large Ising graphs into bounded overlapping subproblems.
Source code in src/sc_neurocore/bridges/annealing_decomposition.py
| Python |
|---|
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173 | class ProblemDecomposer:
"""Partition large Ising graphs into bounded overlapping subproblems."""
def __init__(
self,
max_subproblem_size: int = 64,
overlap: int = 4,
n_iterations: int = 10,
) -> None:
"""Configure maximum size, real overlap, and merge iterations."""
self._max_size = _positive_int("max_subproblem_size", max_subproblem_size)
if isinstance(overlap, bool) or not isinstance(overlap, int) or overlap < 0:
raise ValueError("overlap must be a non-negative integer")
if overlap >= self._max_size:
raise ValueError("overlap must be smaller than max_subproblem_size")
self._overlap = overlap
self._n_iterations = _positive_int("n_iterations", n_iterations)
def _partition_indices(self, model: IsingModel) -> list[list[int]]:
"""Return deterministic graph-aware partitions in global indices."""
if model.n_qubits <= self._max_size:
return [list(range(model.n_qubits))]
neighbors: dict[int, dict[int, float]] = {index: {} for index in range(model.n_qubits)}
for (first, second), strength in model.J.items():
magnitude = abs(strength)
neighbors[first][second] = magnitude
neighbors[second][first] = magnitude
remaining = set(range(model.n_qubits))
assigned: set[int] = set()
partitions: list[list[int]] = []
while remaining:
seed = min(remaining)
overlap_candidates = sorted(
(
(neighbors[seed].get(index, 0.0), index)
for index in assigned
if index in neighbors[seed]
),
key=lambda item: (-item[0], item[1]),
)
shared = [index for _, index in overlap_candidates[: self._overlap]]
partition = [*shared, seed]
remaining.remove(seed)
assigned.add(seed)
while len(partition) < self._max_size and remaining:
scored: list[tuple[float, int]] = []
for candidate in remaining:
score = max(
(neighbors[candidate].get(member, 0.0) for member in partition),
default=0.0,
)
if score > 0.0:
scored.append((score, candidate))
next_qubit = (
min(remaining)
if not scored
else min(scored, key=lambda item: (-item[0], item[1]))[1]
)
partition.append(next_qubit)
remaining.remove(next_qubit)
assigned.add(next_qubit)
partitions.append(partition)
return partitions
@staticmethod
def _submodel(model: IsingModel, indices: list[int], part_index: int) -> IsingModel:
"""Build one local-indexed model from global indices."""
local_index = {global_index: index for index, global_index in enumerate(indices)}
index_set = set(indices)
couplings: dict[tuple[int, int], float] = {}
for (first, second), strength in model.J.items():
if first in index_set and second in index_set:
local_first = local_index[first]
local_second = local_index[second]
couplings[(min(local_first, local_second), max(local_first, local_second))] = (
strength
)
return IsingModel(
h={
local_index[global_index]: model.h.get(global_index, 0.0)
for global_index in indices
},
J=couplings,
qubit_labels={
local_index[global_index]: model.qubit_labels.get(global_index, f"q{global_index}")
for global_index in indices
},
n_qubits=len(indices),
source=f"{model.source}_part{part_index}",
)
def decompose(self, model: IsingModel) -> list[IsingModel]:
"""Return bounded submodels; small inputs retain object identity."""
if not isinstance(model, IsingModel) or model.n_qubits <= 0:
raise ValueError("model must be a non-empty IsingModel")
if model.n_qubits <= self._max_size:
return [model]
return [
self._submodel(model, indices, part_index)
for part_index, indices in enumerate(self._partition_indices(model))
]
def solve_decomposed(
self,
model: IsingModel,
solver: SimulatedAnnealer | None = None,
) -> dict[str, Any]:
"""Solve submodels iteratively and reconstruct by exact global index."""
if not isinstance(model, IsingModel) or model.n_qubits <= 0:
raise ValueError("model must be a non-empty IsingModel")
active_solver = solver or SimulatedAnnealer(n_sweeps=1000, seed=42)
if not isinstance(active_solver, SimulatedAnnealer):
raise ValueError("solver must be a SimulatedAnnealer")
partitions = self._partition_indices(model)
submodels = [
model
if len(partitions) == 1 and partitions[0] == list(range(model.n_qubits))
else self._submodel(model, indices, part_index)
for part_index, indices in enumerate(partitions)
]
global_spins = {index: 1 for index in range(model.n_qubits)}
for _ in range(self._n_iterations):
for indices, submodel in zip(partitions, submodels):
result = active_solver.solve_ising(submodel, num_reads=5)
best_spins = result.get("best_spins")
if not isinstance(best_spins, Mapping):
raise RuntimeError("subproblem solver returned no best_spins mapping")
for local_index, spin in best_spins.items():
if (
isinstance(local_index, bool)
or not isinstance(local_index, int)
or not 0 <= local_index < len(indices)
or spin not in {-1, 1}
):
raise RuntimeError("subproblem solver returned an invalid spin mapping")
global_spins[indices[local_index]] = int(spin)
return {
"best_spins": global_spins,
"best_energy": model.energy(global_spins, backend="python"),
"n_partitions": len(partitions),
"n_iterations": self._n_iterations,
}
|
__init__(max_subproblem_size=64, overlap=4, n_iterations=10)
Configure maximum size, real overlap, and merge iterations.
Source code in src/sc_neurocore/bridges/annealing_decomposition.py
| Python |
|---|
30
31
32
33
34
35
36
37
38
39
40
41
42
43 | def __init__(
self,
max_subproblem_size: int = 64,
overlap: int = 4,
n_iterations: int = 10,
) -> None:
"""Configure maximum size, real overlap, and merge iterations."""
self._max_size = _positive_int("max_subproblem_size", max_subproblem_size)
if isinstance(overlap, bool) or not isinstance(overlap, int) or overlap < 0:
raise ValueError("overlap must be a non-negative integer")
if overlap >= self._max_size:
raise ValueError("overlap must be smaller than max_subproblem_size")
self._overlap = overlap
self._n_iterations = _positive_int("n_iterations", n_iterations)
|
decompose(model)
Return bounded submodels; small inputs retain object identity.
Source code in src/sc_neurocore/bridges/annealing_decomposition.py
| Python |
|---|
121
122
123
124
125
126
127
128
129
130 | def decompose(self, model: IsingModel) -> list[IsingModel]:
"""Return bounded submodels; small inputs retain object identity."""
if not isinstance(model, IsingModel) or model.n_qubits <= 0:
raise ValueError("model must be a non-empty IsingModel")
if model.n_qubits <= self._max_size:
return [model]
return [
self._submodel(model, indices, part_index)
for part_index, indices in enumerate(self._partition_indices(model))
]
|
solve_decomposed(model, solver=None)
Solve submodels iteratively and reconstruct by exact global index.
Source code in src/sc_neurocore/bridges/annealing_decomposition.py
| Python |
|---|
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173 | def solve_decomposed(
self,
model: IsingModel,
solver: SimulatedAnnealer | None = None,
) -> dict[str, Any]:
"""Solve submodels iteratively and reconstruct by exact global index."""
if not isinstance(model, IsingModel) or model.n_qubits <= 0:
raise ValueError("model must be a non-empty IsingModel")
active_solver = solver or SimulatedAnnealer(n_sweeps=1000, seed=42)
if not isinstance(active_solver, SimulatedAnnealer):
raise ValueError("solver must be a SimulatedAnnealer")
partitions = self._partition_indices(model)
submodels = [
model
if len(partitions) == 1 and partitions[0] == list(range(model.n_qubits))
else self._submodel(model, indices, part_index)
for part_index, indices in enumerate(partitions)
]
global_spins = {index: 1 for index in range(model.n_qubits)}
for _ in range(self._n_iterations):
for indices, submodel in zip(partitions, submodels):
result = active_solver.solve_ising(submodel, num_reads=5)
best_spins = result.get("best_spins")
if not isinstance(best_spins, Mapping):
raise RuntimeError("subproblem solver returned no best_spins mapping")
for local_index, spin in best_spins.items():
if (
isinstance(local_index, bool)
or not isinstance(local_index, int)
or not 0 <= local_index < len(indices)
or spin not in {-1, 1}
):
raise RuntimeError("subproblem solver returned an invalid spin mapping")
global_spins[indices[local_index]] = int(spin)
return {
"best_spins": global_spins,
"best_energy": model.energy(global_spins, backend="python"),
"n_partitions": len(partitions),
"n_iterations": self._n_iterations,
}
|
AnnealingSchedule
Build validated linear, pause, and reverse annealing schedules.
Source code in src/sc_neurocore/bridges/annealing_transforms.py
| Python |
|---|
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121 | class AnnealingSchedule:
"""Build validated linear, pause, and reverse annealing schedules."""
def __init__(self) -> None:
"""Create an empty schedule."""
self._points: list[tuple[float, float]] = []
def linear(self, duration_us: float = 20.0) -> AnnealingSchedule:
"""Configure a standard linear anneal from zero to one."""
duration = _positive_duration("duration_us", duration_us)
self._points = [(0.0, 0.0), (duration, 1.0)]
return self
def pause_and_quench(
self,
ramp_time_us: float = 5.0,
pause_at_s: float = 0.4,
pause_duration_us: float = 50.0,
quench_time_us: float = 1.0,
) -> AnnealingSchedule:
"""Ramp, hold at an intermediate fraction, then quench."""
ramp = _positive_duration("ramp_time_us", ramp_time_us)
pause = _positive_duration("pause_duration_us", pause_duration_us)
quench = _positive_duration("quench_time_us", quench_time_us)
fraction = _anneal_fraction("pause_at_s", pause_at_s)
if fraction in {0.0, 1.0}:
raise ValueError("pause_at_s must be strictly between zero and one")
self._points = [
(0.0, 0.0),
(ramp, fraction),
(ramp + pause, fraction),
(ramp + pause + quench, 1.0),
]
return self
def reverse(
self,
initial_s: float = 1.0,
reverse_to_s: float = 0.3,
ramp_time_us: float = 5.0,
hold_time_us: float = 10.0,
forward_time_us: float = 5.0,
) -> AnnealingSchedule:
"""Configure a reverse anneal followed by a forward return."""
initial = _anneal_fraction("initial_s", initial_s)
reverse_to = _anneal_fraction("reverse_to_s", reverse_to_s)
if reverse_to >= initial:
raise ValueError("reverse_to_s must be smaller than initial_s")
ramp = _positive_duration("ramp_time_us", ramp_time_us)
hold = _positive_duration("hold_time_us", hold_time_us)
forward = _positive_duration("forward_time_us", forward_time_us)
self._points = [
(0.0, initial),
(ramp, reverse_to),
(ramp + hold, reverse_to),
(ramp + hold + forward, 1.0),
]
return self
@property
def points(self) -> list[tuple[float, float]]:
"""Return a defensive copy of the schedule points."""
return list(self._points)
@property
def total_time_us(self) -> float:
"""Return zero for an empty schedule or its final timestamp."""
return self._points[-1][0] if self._points else 0.0
def to_dict(self) -> dict[str, Any]:
"""Return a D-Wave-compatible schedule payload."""
return {
"schedule": list(self._points),
"total_time_us": self.total_time_us,
"n_points": len(self._points),
}
|
points
property
Return a defensive copy of the schedule points.
total_time_us
property
Return zero for an empty schedule or its final timestamp.
__init__()
Create an empty schedule.
Source code in src/sc_neurocore/bridges/annealing_transforms.py
| Python |
|---|
| def __init__(self) -> None:
"""Create an empty schedule."""
self._points: list[tuple[float, float]] = []
|
linear(duration_us=20.0)
Configure a standard linear anneal from zero to one.
Source code in src/sc_neurocore/bridges/annealing_transforms.py
| Python |
|---|
| def linear(self, duration_us: float = 20.0) -> AnnealingSchedule:
"""Configure a standard linear anneal from zero to one."""
duration = _positive_duration("duration_us", duration_us)
self._points = [(0.0, 0.0), (duration, 1.0)]
return self
|
pause_and_quench(ramp_time_us=5.0, pause_at_s=0.4, pause_duration_us=50.0, quench_time_us=1.0)
Ramp, hold at an intermediate fraction, then quench.
Source code in src/sc_neurocore/bridges/annealing_transforms.py
| Python |
|---|
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79 | def pause_and_quench(
self,
ramp_time_us: float = 5.0,
pause_at_s: float = 0.4,
pause_duration_us: float = 50.0,
quench_time_us: float = 1.0,
) -> AnnealingSchedule:
"""Ramp, hold at an intermediate fraction, then quench."""
ramp = _positive_duration("ramp_time_us", ramp_time_us)
pause = _positive_duration("pause_duration_us", pause_duration_us)
quench = _positive_duration("quench_time_us", quench_time_us)
fraction = _anneal_fraction("pause_at_s", pause_at_s)
if fraction in {0.0, 1.0}:
raise ValueError("pause_at_s must be strictly between zero and one")
self._points = [
(0.0, 0.0),
(ramp, fraction),
(ramp + pause, fraction),
(ramp + pause + quench, 1.0),
]
return self
|
reverse(initial_s=1.0, reverse_to_s=0.3, ramp_time_us=5.0, hold_time_us=10.0, forward_time_us=5.0)
Configure a reverse anneal followed by a forward return.
Source code in src/sc_neurocore/bridges/annealing_transforms.py
| Python |
|---|
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103 | def reverse(
self,
initial_s: float = 1.0,
reverse_to_s: float = 0.3,
ramp_time_us: float = 5.0,
hold_time_us: float = 10.0,
forward_time_us: float = 5.0,
) -> AnnealingSchedule:
"""Configure a reverse anneal followed by a forward return."""
initial = _anneal_fraction("initial_s", initial_s)
reverse_to = _anneal_fraction("reverse_to_s", reverse_to_s)
if reverse_to >= initial:
raise ValueError("reverse_to_s must be smaller than initial_s")
ramp = _positive_duration("ramp_time_us", ramp_time_us)
hold = _positive_duration("hold_time_us", hold_time_us)
forward = _positive_duration("forward_time_us", forward_time_us)
self._points = [
(0.0, initial),
(ramp, reverse_to),
(ramp + hold, reverse_to),
(ramp + hold + forward, 1.0),
]
return self
|
to_dict()
Return a D-Wave-compatible schedule payload.
Source code in src/sc_neurocore/bridges/annealing_transforms.py
| Python |
|---|
115
116
117
118
119
120
121 | def to_dict(self) -> dict[str, Any]:
"""Return a D-Wave-compatible schedule payload."""
return {
"schedule": list(self._points),
"total_time_us": self.total_time_us,
"n_points": len(self._points),
}
|
Generate deterministic random spin-reversal transformations.
Source code in src/sc_neurocore/bridges/annealing_transforms.py
| Python |
|---|
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170 | class GaugeTransform:
"""Generate deterministic random spin-reversal transformations."""
def __init__(self, n_gauges: int = 10, seed: int = 42) -> None:
"""Configure a positive transform count and deterministic seed."""
if isinstance(n_gauges, bool) or not isinstance(n_gauges, int) or n_gauges <= 0:
raise ValueError("n_gauges must be a positive integer")
if isinstance(seed, bool) or not isinstance(seed, int):
raise ValueError("seed must be an integer")
self._n_gauges = n_gauges
self._rng = np.random.default_rng(seed)
def transform(self, model: IsingModel) -> list[IsingModel]:
"""Return energy-equivalent gauge-transformed model copies."""
if not isinstance(model, IsingModel) or model.n_qubits <= 0:
raise ValueError("model must be a non-empty IsingModel")
transforms: list[IsingModel] = []
for gauge_index in range(self._n_gauges):
gauge = {index: int(self._rng.choice((-1, 1))) for index in range(model.n_qubits)}
transforms.append(
IsingModel(
h={index: gauge[index] * bias for index, bias in model.h.items()},
J={
pair: gauge[pair[0]] * gauge[pair[1]] * strength
for pair, strength in model.J.items()
},
offset=model.offset,
qubit_labels=dict(model.qubit_labels),
n_qubits=model.n_qubits,
source=f"{model.source}_gauge{gauge_index}",
)
)
return transforms
def untransform_sample(
self,
sample: Mapping[int, int],
gauge: Mapping[int, int],
) -> dict[int, int]:
"""Return a transformed sample to the original spin frame."""
for mapping_name, mapping in (("sample", sample), ("gauge", gauge)):
for index, spin in mapping.items():
if isinstance(index, bool) or not isinstance(index, int) or index < 0:
raise ValueError(f"{mapping_name} indices must be non-negative integers")
if spin not in {-1, 1}:
raise ValueError(f"{mapping_name} values must be -1 or +1")
return {index: spin * gauge.get(index, 1) for index, spin in sample.items()}
|
Configure a positive transform count and deterministic seed.
Source code in src/sc_neurocore/bridges/annealing_transforms.py
| Python |
|---|
127
128
129
130
131
132
133
134 | def __init__(self, n_gauges: int = 10, seed: int = 42) -> None:
"""Configure a positive transform count and deterministic seed."""
if isinstance(n_gauges, bool) or not isinstance(n_gauges, int) or n_gauges <= 0:
raise ValueError("n_gauges must be a positive integer")
if isinstance(seed, bool) or not isinstance(seed, int):
raise ValueError("seed must be an integer")
self._n_gauges = n_gauges
self._rng = np.random.default_rng(seed)
|
Return energy-equivalent gauge-transformed model copies.
Source code in src/sc_neurocore/bridges/annealing_transforms.py
| Python |
|---|
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156 | def transform(self, model: IsingModel) -> list[IsingModel]:
"""Return energy-equivalent gauge-transformed model copies."""
if not isinstance(model, IsingModel) or model.n_qubits <= 0:
raise ValueError("model must be a non-empty IsingModel")
transforms: list[IsingModel] = []
for gauge_index in range(self._n_gauges):
gauge = {index: int(self._rng.choice((-1, 1))) for index in range(model.n_qubits)}
transforms.append(
IsingModel(
h={index: gauge[index] * bias for index, bias in model.h.items()},
J={
pair: gauge[pair[0]] * gauge[pair[1]] * strength
for pair, strength in model.J.items()
},
offset=model.offset,
qubit_labels=dict(model.qubit_labels),
n_qubits=model.n_qubits,
source=f"{model.source}_gauge{gauge_index}",
)
)
return transforms
|
Return a transformed sample to the original spin frame.
Source code in src/sc_neurocore/bridges/annealing_transforms.py
| Python |
|---|
158
159
160
161
162
163
164
165
166
167
168
169
170 | def untransform_sample(
self,
sample: Mapping[int, int],
gauge: Mapping[int, int],
) -> dict[int, int]:
"""Return a transformed sample to the original spin frame."""
for mapping_name, mapping in (("sample", sample), ("gauge", gauge)):
for index, spin in mapping.items():
if isinstance(index, bool) or not isinstance(index, int) or index < 0:
raise ValueError(f"{mapping_name} indices must be non-negative integers")
if spin not in {-1, 1}:
raise ValueError(f"{mapping_name} values must be -1 or +1")
return {index: spin * gauge.get(index, 1) for index, spin in sample.items()}
|
SCPrecisionEncoder
Encode unit-interval SC values as binary, unary, or one-hot qubits.
Source code in src/sc_neurocore/bridges/annealing_transforms.py
| Python |
|---|
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245 | class SCPrecisionEncoder:
"""Encode unit-interval SC values as binary, unary, or one-hot qubits."""
def __init__(self, encoding: str = "binary", n_bits: int = 8) -> None:
"""Select a supported encoding and positive qubit count."""
if encoding not in {"binary", "unary", "one_hot"}:
raise ValueError(f"Unknown encoding: {encoding}")
if isinstance(n_bits, bool) or not isinstance(n_bits, int) or n_bits <= 0:
raise ValueError("n_bits must be a positive integer")
self._encoding = encoding
self._n_bits = n_bits
@property
def n_levels(self) -> int:
"""Return the number of representable precision levels."""
if self._encoding == "binary":
return int(2**self._n_bits)
if self._encoding == "unary":
return self._n_bits + 1
return self._n_bits
def encode(self, sc_value: float) -> dict[int, int]:
"""Encode one finite SC value after clipping it to ``[0, 1]``."""
value = _finite("sc_value", sc_value)
clipped = max(0.0, min(1.0, value))
if self._encoding == "binary":
level = int(round(clipped * (2**self._n_bits - 1)))
return {index: (level >> index) & 1 for index in range(self._n_bits)}
if self._encoding == "unary":
one_count = int(round(clipped * self._n_bits))
return {index: 1 if index < one_count else 0 for index in range(self._n_bits)}
level = int(round(clipped * (self._n_bits - 1)))
return {index: 1 if index == level else 0 for index in range(self._n_bits)}
def decode(self, qubits: Mapping[int, int]) -> float:
"""Decode a validated partial binary qubit mapping."""
for index, bit in qubits.items():
if (
isinstance(index, bool)
or not isinstance(index, int)
or not 0 <= index < self._n_bits
):
raise ValueError("qubit indices must fit within the configured encoding")
if bit not in {0, 1}:
raise ValueError("qubit values must be binary")
if self._encoding == "binary":
level = sum(qubits.get(index, 0) << index for index in range(self._n_bits))
return float(level / (2**self._n_bits - 1))
if self._encoding == "unary":
return sum(qubits.get(index, 0) for index in range(self._n_bits)) / self._n_bits
active = [index for index in range(self._n_bits) if qubits.get(index, 0) == 1]
if len(active) > 1:
raise ValueError("one_hot decoding accepts at most one active qubit")
return active[0] / max(self._n_bits - 1, 1) if active else 0.0
def qubits_needed(self, n_sc_values: int) -> int:
"""Return the total qubits required for a non-negative value count."""
if isinstance(n_sc_values, bool) or not isinstance(n_sc_values, int) or n_sc_values < 0:
raise ValueError("n_sc_values must be a non-negative integer")
return n_sc_values * self._n_bits
def encode_array(self, values: np.ndarray[Any, Any]) -> dict[int, int]:
"""Encode a non-empty one-dimensional array into global qubit indices."""
array = np.asarray(values, dtype=np.float64)
if array.ndim != 1 or array.size == 0:
raise ValueError("values must be a non-empty one-dimensional array")
if not bool(np.all(np.isfinite(array))):
raise ValueError("values must contain only finite numbers")
result: dict[int, int] = {}
for value_index, value in enumerate(array):
for local_index, bit in self.encode(float(value)).items():
result[value_index * self._n_bits + local_index] = bit
return result
|
n_levels
property
Return the number of representable precision levels.
__init__(encoding='binary', n_bits=8)
Select a supported encoding and positive qubit count.
Source code in src/sc_neurocore/bridges/annealing_transforms.py
| Python |
|---|
176
177
178
179
180
181
182
183 | def __init__(self, encoding: str = "binary", n_bits: int = 8) -> None:
"""Select a supported encoding and positive qubit count."""
if encoding not in {"binary", "unary", "one_hot"}:
raise ValueError(f"Unknown encoding: {encoding}")
if isinstance(n_bits, bool) or not isinstance(n_bits, int) or n_bits <= 0:
raise ValueError("n_bits must be a positive integer")
self._encoding = encoding
self._n_bits = n_bits
|
encode(sc_value)
Encode one finite SC value after clipping it to [0, 1].
Source code in src/sc_neurocore/bridges/annealing_transforms.py
| Python |
|---|
194
195
196
197
198
199
200
201
202
203
204
205 | def encode(self, sc_value: float) -> dict[int, int]:
"""Encode one finite SC value after clipping it to ``[0, 1]``."""
value = _finite("sc_value", sc_value)
clipped = max(0.0, min(1.0, value))
if self._encoding == "binary":
level = int(round(clipped * (2**self._n_bits - 1)))
return {index: (level >> index) & 1 for index in range(self._n_bits)}
if self._encoding == "unary":
one_count = int(round(clipped * self._n_bits))
return {index: 1 if index < one_count else 0 for index in range(self._n_bits)}
level = int(round(clipped * (self._n_bits - 1)))
return {index: 1 if index == level else 0 for index in range(self._n_bits)}
|
decode(qubits)
Decode a validated partial binary qubit mapping.
Source code in src/sc_neurocore/bridges/annealing_transforms.py
| Python |
|---|
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226 | def decode(self, qubits: Mapping[int, int]) -> float:
"""Decode a validated partial binary qubit mapping."""
for index, bit in qubits.items():
if (
isinstance(index, bool)
or not isinstance(index, int)
or not 0 <= index < self._n_bits
):
raise ValueError("qubit indices must fit within the configured encoding")
if bit not in {0, 1}:
raise ValueError("qubit values must be binary")
if self._encoding == "binary":
level = sum(qubits.get(index, 0) << index for index in range(self._n_bits))
return float(level / (2**self._n_bits - 1))
if self._encoding == "unary":
return sum(qubits.get(index, 0) for index in range(self._n_bits)) / self._n_bits
active = [index for index in range(self._n_bits) if qubits.get(index, 0) == 1]
if len(active) > 1:
raise ValueError("one_hot decoding accepts at most one active qubit")
return active[0] / max(self._n_bits - 1, 1) if active else 0.0
|
qubits_needed(n_sc_values)
Return the total qubits required for a non-negative value count.
Source code in src/sc_neurocore/bridges/annealing_transforms.py
| Python |
|---|
| def qubits_needed(self, n_sc_values: int) -> int:
"""Return the total qubits required for a non-negative value count."""
if isinstance(n_sc_values, bool) or not isinstance(n_sc_values, int) or n_sc_values < 0:
raise ValueError("n_sc_values must be a non-negative integer")
return n_sc_values * self._n_bits
|
encode_array(values)
Encode a non-empty one-dimensional array into global qubit indices.
Source code in src/sc_neurocore/bridges/annealing_transforms.py
| Python |
|---|
234
235
236
237
238
239
240
241
242
243
244
245 | def encode_array(self, values: np.ndarray[Any, Any]) -> dict[int, int]:
"""Encode a non-empty one-dimensional array into global qubit indices."""
array = np.asarray(values, dtype=np.float64)
if array.ndim != 1 or array.size == 0:
raise ValueError("values must be a non-empty one-dimensional array")
if not bool(np.all(np.isfinite(array))):
raise ValueError("values must contain only finite numbers")
result: dict[int, int] = {}
for value_index, value in enumerate(array):
for local_index, bit in self.encode(float(value)).items():
result[value_index * self._n_bits + local_index] = bit
return result
|
export_ising_json(model, path)
Write a canonical JSON representation of an Ising model.
Source code in src/sc_neurocore/bridges/annealing_io.py
| Python |
|---|
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65 | def export_ising_json(model: IsingModel, path: str | Path) -> None:
"""Write a canonical JSON representation of an Ising model."""
if not isinstance(model, IsingModel):
raise ValueError("model must be an IsingModel")
_atomic_json_write(
path,
{
"type": "ising",
"n_qubits": model.n_qubits,
"source": model.source,
"offset": model.offset,
"h": {str(index): value for index, value in model.h.items()},
"J": {f"{first},{second}": value for (first, second), value in model.J.items()},
"qubit_labels": {str(index): label for index, label in model.qubit_labels.items()},
},
)
|
export_qubo_json(model, path)
Write a canonical JSON representation of a QUBO model.
Source code in src/sc_neurocore/bridges/annealing_io.py
| Python |
|---|
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82 | def export_qubo_json(model: QUBOModel, path: str | Path) -> None:
"""Write a canonical JSON representation of a QUBO model."""
if not isinstance(model, QUBOModel):
raise ValueError("model must be a QUBOModel")
_atomic_json_write(
path,
{
"type": "qubo",
"n_qubits": model.n_qubits,
"source": model.source,
"offset": model.offset,
"Q": {f"{first},{second}": value for (first, second), value in model.Q.items()},
"qubit_labels": {str(index): label for index, label in model.qubit_labels.items()},
},
)
|
export_bqm(model)
Return a dimod spin BQM, or None when dimod is unavailable.
Source code in src/sc_neurocore/bridges/annealing_io.py
| Python |
|---|
| def export_bqm(model: IsingModel) -> Any | None:
"""Return a dimod spin BQM, or ``None`` when dimod is unavailable."""
if not isinstance(model, IsingModel):
raise ValueError("model must be an IsingModel")
return backends.build_spin_bqm(model.h, model.J, model.offset)
|
visualize_ising(model)
Return a stable Unicode text rendering of fields and couplings.
Source code in src/sc_neurocore/bridges/annealing_io.py
| Python |
|---|
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118 | def visualize_ising(model: IsingModel) -> str:
"""Return a stable Unicode text rendering of fields and couplings."""
if not isinstance(model, IsingModel):
raise ValueError("model must be an IsingModel")
lines = [
f"┌{'=' * 50}┐",
f"│ Ising Model: {model.source:<34} │",
f"│ Qubits: {model.n_qubits:<4} Couplers: {len(model.J):<5} │",
f"│ Offset: {model.offset:<40.4f} │",
f"└{'=' * 50}┘",
"",
" Biases (h):",
]
for index in sorted(model.h):
label = model.qubit_labels.get(index, f"q{index}")
bar = "█" * min(int(abs(model.h[index]) * 20), 20)
sign = "+" if model.h[index] >= 0.0 else "-"
lines.append(f" {label:>8}: {sign}{bar:<20} ({model.h[index]:+.4f})")
lines.extend(("", " Couplings (J):"))
for first, second in sorted(model.J):
first_label = model.qubit_labels.get(first, f"q{first}")
second_label = model.qubit_labels.get(second, f"q{second}")
strength = model.J[(first, second)]
kind = "ferro" if strength < 0.0 else "anti"
lines.append(f" {first_label:>8} ─── {second_label:<8}: {strength:+.4f} [{kind}]")
return "\n".join(lines)
|