Skip to content

Commit 4aac8c3

Browse files
replace problem 4
1 parent 9f3a5d3 commit 4aac8c3

4 files changed

Lines changed: 465 additions & 221 deletions

File tree

examples/challenge_suite/evaluate_11.py

Lines changed: 139 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -2,106 +2,177 @@
22
Evaluation for Challenge Suite Problem 11.
33
44
The evaluator dynamically imports a solution module, consumes only the NumPy
5-
result dictionary returned by run_solution, and prints compact validation output.
5+
result dictionary returned by run_solution, and computes an exact sparse
6+
ground-state reference for the same spin-1 chain outside the timed solution run.
67
"""
78

89
import argparse
910
import importlib
1011
import time
12+
from functools import lru_cache
1113

1214
import numpy as np
15+
from scipy.sparse import csr_matrix, eye, kron
16+
from scipy.sparse.linalg import eigsh
17+
18+
DIM = 3
19+
SQRT2 = np.sqrt(2.0)
20+
21+
SX = (
22+
np.array([[0.0, 1.0, 0.0], [1.0, 0.0, 1.0], [0.0, 1.0, 0.0]], dtype=np.complex128)
23+
/ SQRT2
24+
)
25+
SY = (
26+
np.array(
27+
[[0.0, -1.0j, 0.0], [1.0j, 0.0, -1.0j], [0.0, 1.0j, 0.0]],
28+
dtype=np.complex128,
29+
)
30+
/ SQRT2
31+
)
32+
SZ = np.diag([1.0, 0.0, -1.0]).astype(np.complex128)
33+
SZ2 = np.diag([1.0, 0.0, 1.0]).astype(np.complex128)
34+
DOT_BOND = np.kron(SX, SX) + np.kron(SY, SY) + np.kron(SZ, SZ)
35+
DOT_BOND_SQUARED = DOT_BOND @ DOT_BOND
36+
MS = np.array([1.0, 0.0, -1.0], dtype=np.float64)
37+
MIDDLE_VALUES = np.array([-1.0, 1.0, -1.0], dtype=np.float64)
1338

1439
DEFAULT_CONFIG = {
15-
"n_qubits": 20,
16-
"n_field_params": 2,
17-
"n_layers": 6,
18-
"max_steps": 300,
19-
"learning_rate": 0.01,
20-
"initial_parameter_scale": 0.02,
21-
"readout_penalty_weight": 0.05,
22-
"seed": 2037,
23-
"final_response_mse_tolerance": 1e-9,
40+
"n_sites": 12,
41+
"n_layers": 5,
42+
"beta": 0.20,
43+
"single_ion_anisotropy": 0.15,
44+
"max_steps": 500,
45+
"learning_rate": 0.03,
46+
"initial_parameter_scale": 0.05,
47+
"seed": 2041,
48+
"minimum_energy_improvement": 5e-3,
49+
"maximum_energy_density_gap": 0.12,
50+
"maximum_string_order_mae": 0.12,
2451
}
2552

2653

27-
def sensor_positions(config):
28-
return np.linspace(-1.0, 1.0, config["n_qubits"], dtype=np.float64)
54+
def string_pairs(config):
55+
n_sites = config["n_sites"]
56+
return tuple((i, n_sites - 1 - i) for i in range(3))
57+
58+
59+
@lru_cache(maxsize=None)
60+
def identity_shell(n_sites):
61+
return eye(DIM**n_sites, dtype=np.complex128, format="csr")
62+
63+
64+
def embed_one_site_operator(op, site, n_sites):
65+
return kron(
66+
kron(identity_shell(site), csr_matrix(op), format="csr"),
67+
identity_shell(n_sites - site - 1),
68+
format="csr",
69+
)
2970

3071

31-
def target_response_matrix(config):
32-
x_sites = sensor_positions(config)
33-
return np.stack([np.ones(config["n_qubits"]), x_sites], axis=1)
72+
def embed_two_site_operator(op, left, n_sites):
73+
return kron(
74+
kron(identity_shell(left), csr_matrix(op), format="csr"),
75+
identity_shell(n_sites - left - 2),
76+
format="csr",
77+
)
3478

3579

36-
def parameter_count(config):
37-
n_qubits = config["n_qubits"]
38-
count = 0
39-
for layer in range(config["n_layers"]):
40-
count += 2 * n_qubits
41-
count += len(range(layer % 2, n_qubits - 1, 2))
42-
return count
80+
def build_hamiltonian(config):
81+
n_sites = config["n_sites"]
82+
dim = DIM**n_sites
83+
hamiltonian = csr_matrix((dim, dim), dtype=np.complex128)
84+
bond_term = DOT_BOND + config["beta"] * DOT_BOND_SQUARED
85+
for left in range(n_sites - 1):
86+
hamiltonian += embed_two_site_operator(bond_term, left, n_sites)
87+
onsite_prefactor = config["single_ion_anisotropy"]
88+
for site in range(n_sites):
89+
hamiltonian += onsite_prefactor * embed_one_site_operator(SZ2, site, n_sites)
90+
return hamiltonian
91+
92+
93+
@lru_cache(maxsize=None)
94+
def basis_digits(n_sites):
95+
dim = DIM**n_sites
96+
digits = np.empty((dim, n_sites), dtype=np.int8)
97+
values = np.arange(dim, dtype=np.int64)
98+
for site in range(n_sites - 1, -1, -1):
99+
digits[:, site] = values % DIM
100+
values //= DIM
101+
return digits
102+
103+
104+
def string_order_from_state(state, config, pair):
105+
i, j = pair
106+
digits = basis_digits(config["n_sites"])
107+
weights = MS[digits[:, i]] * MS[digits[:, j]]
108+
for site in range(i + 1, j):
109+
weights *= MIDDLE_VALUES[digits[:, site]]
110+
probabilities = np.abs(state) ** 2
111+
return float(np.sum(probabilities * weights))
112+
113+
114+
def exact_reference(config):
115+
hamiltonian = build_hamiltonian(config)
116+
value, vector = eigsh(hamiltonian, k=1, which="SA", tol=1e-8, maxiter=400)
117+
ground = vector[:, 0]
118+
energy_density = float(np.real(value[0])) / config["n_sites"]
119+
strings = [
120+
string_order_from_state(ground, config, pair) for pair in string_pairs(config)
121+
]
122+
return energy_density, np.asarray(strings, dtype=np.float64)
43123

44124

45125
def evaluate(solution_module, config):
46126
module = importlib.import_module(solution_module)
47127

128+
start = time.perf_counter()
129+
exact_energy_density, exact_strings = exact_reference(config)
130+
exact_elapsed = time.perf_counter() - start
131+
48132
start = time.perf_counter()
49133
results = module.run_solution(config)
50134
elapsed = time.perf_counter() - start
51135

52-
loss_history = np.asarray(results["loss_history"], dtype=float)
53-
response_mse_history = np.asarray(results["response_mse_history"], dtype=float)
54-
readout_penalty_history = np.asarray(
55-
results["readout_penalty_history"], dtype=float
56-
)
57-
response = np.asarray(results["final_response_matrix"], dtype=float)
58-
exact_target = target_response_matrix(config)
59-
zero_readouts = np.asarray(results["final_zero_field_readouts"], dtype=float)
60-
61-
final_response_mse = float(np.mean((response - exact_target) ** 2))
62-
final_readout_penalty = float(np.mean(zero_readouts**2))
63-
final_loss = (
64-
final_response_mse + config["readout_penalty_weight"] * final_readout_penalty
136+
pairs = string_pairs(config)
137+
energy_history = np.asarray(results["energy_density_history"], dtype=float)
138+
final_energy_density = float(
139+
np.asarray(results["final_energy_density"], dtype=float)
65140
)
141+
final_strings = np.asarray(results["final_string_orders"], dtype=float)
142+
string_mae = float(np.mean(np.abs(final_strings - exact_strings)))
143+
improvement = float(energy_history[0] - final_energy_density)
144+
gap = float(final_energy_density - exact_energy_density)
66145

67-
finite_arrays = [
68-
loss_history,
69-
response_mse_history,
70-
readout_penalty_history,
71-
response,
72-
zero_readouts,
73-
]
74146
criteria = {
75-
"loss history shape": loss_history.shape == (config["max_steps"],),
76-
"response mse history shape": response_mse_history.shape
77-
== (config["max_steps"],),
78-
"readout penalty history shape": readout_penalty_history.shape
79-
== (config["max_steps"],),
80-
"response shape": response.shape
81-
== (config["n_qubits"], config["n_field_params"]),
82-
"zero readout shape": zero_readouts.shape == (config["n_qubits"],),
83-
"response mse improves": final_response_mse < float(response_mse_history[0]),
84-
"final response mse <= tolerance": final_response_mse
85-
<= config["final_response_mse_tolerance"],
86-
"loss improves": final_loss < float(loss_history[0]),
87-
"returned arrays finite": all(np.all(np.isfinite(a)) for a in finite_arrays),
147+
"energy history shape": energy_history.shape == (config["max_steps"],),
148+
"final string shape": final_strings.shape == (len(pairs),),
149+
"energy improves": improvement >= config["minimum_energy_improvement"],
150+
"returned arrays finite": np.all(np.isfinite(energy_history))
151+
and np.isfinite(final_energy_density)
152+
and np.all(np.isfinite(final_strings)),
153+
"energy gap threshold": gap <= config["maximum_energy_density_gap"],
154+
"string-order mae threshold": string_mae <= config["maximum_string_order_mae"],
88155
}
89156

90157
print("Challenge 11 evaluation")
91158
print(f"Solution module: {solution_module}")
92159
print(f"End-to-end solution time: {elapsed:.2f}s")
93-
print(f"Qubits: {config['n_qubits']}")
160+
print(f"Exact reference time: {exact_elapsed:.2f}s")
161+
print(f"Sites: {config['n_sites']}")
94162
print(f"Layers: {config['n_layers']}")
95-
print(f"Physical field parameters: {config['n_field_params']}")
96-
print(f"Readout observables: {config['n_qubits']}")
97-
print(f"Trainable circuit parameters: {parameter_count(config)}")
98-
print(f"Initial response MSE: {float(response_mse_history[0]):.8e}")
99-
print(f"Final response MSE: {final_response_mse:.8e}")
100-
print(f"Final zero-field readout penalty: {final_readout_penalty:.8e}")
101-
print(f"Initial total loss: {float(loss_history[0]):.8e}")
102-
print(f"Final total loss: {final_loss:.8e}")
103-
print(f"Loss history shape: {loss_history.shape}")
104-
print(f"Final response matrix shape: {response.shape}")
163+
print(f"Steps: {config['max_steps']}")
164+
print(f"Initial energy density: {float(energy_history[0]):.10f}")
165+
print(f"Final history energy density: {float(energy_history[-1]):.10f}")
166+
print(f"Final returned energy density: {final_energy_density:.10f}")
167+
print(f"Exact ground-state density: {exact_energy_density:.10f}")
168+
print(f"Energy-density gap: {gap:.10f}")
169+
print(f"Energy improvement: {improvement:.10f}")
170+
print("String correlators (final / exact):")
171+
for pair, value, ref in zip(pairs, final_strings, exact_strings):
172+
print(f" O_string^z{pair}: {value:.10f} / {ref:.10f}")
173+
print(f"String-order MAE: {string_mae:.10f}")
174+
print(f"Energy history shape: {energy_history.shape}")
175+
print(f"Final string-order shape: {final_strings.shape}")
105176
print(f"Returned NumPy keys: {sorted(results)}")
106177
print("Passing criteria:")
107178
for name, passed in criteria.items():
@@ -113,10 +184,12 @@ def main():
113184
parser = argparse.ArgumentParser()
114185
parser.add_argument("--solution", default="solution_11")
115186
parser.add_argument("--max-steps", type=int, default=DEFAULT_CONFIG["max_steps"])
187+
parser.add_argument("--n-layers", type=int, default=DEFAULT_CONFIG["n_layers"])
116188
args = parser.parse_args()
117189

118190
config = dict(DEFAULT_CONFIG)
119191
config["max_steps"] = args.max_steps
192+
config["n_layers"] = args.n_layers
120193
evaluate(args.solution, config)
121194

122195

0 commit comments

Comments
 (0)