1use std::mem::Discriminant;
5
6use super::{Circuit, Instruction, SmallVec};
7use crate::error::{PrismError, Result};
8use crate::gates::Gate;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct ParamLink {
13 pub instruction: usize,
15 pub slot: usize,
17}
18
19#[derive(Debug, Clone, Default, PartialEq, Eq)]
30pub struct Parameters {
31 links: Vec<ParamLink>,
32 num_slots: usize,
33 names: Vec<String>,
36 shape: Vec<(Discriminant<Gate>, SmallVec<[usize; 4]>)>,
40}
41
42impl Parameters {
43 pub fn new(num_slots: usize) -> Self {
45 Self {
46 num_slots,
47 ..Default::default()
48 }
49 }
50
51 pub fn from_links(links: Vec<ParamLink>, num_slots: usize) -> Self {
53 Self {
54 links,
55 num_slots,
56 ..Default::default()
57 }
58 }
59
60 pub fn with_names<I, S>(mut self, names: I) -> Self
66 where
67 I: IntoIterator<Item = S>,
68 S: Into<String>,
69 {
70 let names: Vec<String> = names.into_iter().map(Into::into).collect();
71 assert_eq!(
72 names.len(),
73 self.num_slots,
74 "expected {} slot names, got {}",
75 self.num_slots,
76 names.len()
77 );
78 self.names = names;
79 self
80 }
81
82 pub fn name_of(&self, slot: usize) -> Option<&str> {
84 self.names.get(slot).map(String::as_str)
85 }
86
87 pub fn slot_of(&self, name: &str) -> Option<usize> {
89 self.names.iter().position(|n| n == name)
90 }
91
92 pub fn pinned_to(mut self, circuit: &Circuit) -> Self {
96 self.shape = self
97 .links
98 .iter()
99 .filter_map(|link| match circuit.instructions.get(link.instruction) {
100 Some(Instruction::Gate { gate, targets }) => {
101 Some((std::mem::discriminant(gate), targets.clone()))
102 }
103 _ => None,
104 })
105 .collect();
106 if self.shape.len() != self.links.len() {
107 self.shape.clear();
108 }
109 self
110 }
111
112 pub fn all_rotations(circuit: &Circuit) -> Self {
115 let mut links = Vec::new();
116 for (i, inst) in circuit.instructions.iter().enumerate() {
117 if let Instruction::Gate { gate, .. } = inst {
118 if gate.pauli_generator().is_some() {
119 links.push(ParamLink {
120 instruction: i,
121 slot: links.len(),
122 });
123 }
124 }
125 }
126 let num_slots = links.len();
127 Self {
128 links,
129 num_slots,
130 ..Default::default()
131 }
132 .pinned_to(circuit)
133 }
134
135 pub(super) fn link_growing(&mut self, instruction: usize, slot: usize) {
138 self.num_slots = self.num_slots.max(slot + 1);
139 self.links.push(ParamLink { instruction, slot });
140 }
141
142 pub fn link(&mut self, instruction: usize, slot: usize) {
149 assert!(
150 slot < self.num_slots,
151 "slot {} out of bounds (parameter set declares {} slots)",
152 slot,
153 self.num_slots
154 );
155 self.links.push(ParamLink { instruction, slot });
156 }
157
158 pub fn links(&self) -> &[ParamLink] {
159 &self.links
160 }
161
162 pub fn num_slots(&self) -> usize {
164 self.num_slots
165 }
166
167 pub fn is_empty(&self) -> bool {
169 self.links.is_empty()
170 }
171
172 pub fn validate(&self, circuit: &Circuit) -> Result<()> {
184 let n = circuit.instructions.len();
185 for link in &self.links {
186 if link.instruction >= n {
187 return Err(PrismError::InvalidParameter {
188 message: format!(
189 "parameter link references instruction {} but the circuit has {n} instructions",
190 link.instruction
191 ),
192 });
193 }
194 match &circuit.instructions[link.instruction] {
195 Instruction::Gate { gate, .. } if gate.pauli_generator().is_some() => {}
196 Instruction::Gate { gate, .. } => {
197 return Err(PrismError::InvalidParameter {
198 message: format!(
199 "instruction {} (`{}`) carries no bindable angle; bindable gates are rx, ry, rz, rzz, p, pauli_rot",
200 link.instruction,
201 gate.name()
202 ),
203 });
204 }
205 _ => {
206 return Err(PrismError::InvalidParameter {
207 message: format!(
208 "parameter link references instruction {} which is not a gate",
209 link.instruction
210 ),
211 });
212 }
213 }
214 }
215
216 if !self.shape.is_empty() {
217 for (link, (kind, targets)) in self.links.iter().zip(&self.shape) {
218 let Instruction::Gate { gate, targets: at } =
219 &circuit.instructions[link.instruction]
220 else {
221 unreachable!("link validated as a gate above")
222 };
223 if std::mem::discriminant(gate) != *kind || at.as_slice() != targets.as_slice() {
224 return Err(PrismError::InvalidParameter {
225 message: format!(
226 "instruction {} no longer holds the gate this parameter set was built against; the circuit was edited after the links were recorded",
227 link.instruction
228 ),
229 });
230 }
231 }
232 }
233
234 Ok(())
235 }
236
237 pub fn unread_slots(&self) -> Vec<usize> {
240 let mut used = vec![false; self.num_slots];
241 for link in &self.links {
242 used[link.slot] = true;
243 }
244 (0..self.num_slots).filter(|s| !used[*s]).collect()
245 }
246
247 pub fn bind(&self, template: &Circuit, values: &[f64]) -> Result<Circuit> {
254 let mut out = template.clone();
255 self.bind_into(template, values, &mut out)?;
256 Ok(out)
257 }
258
259 pub fn bind_into(&self, template: &Circuit, values: &[f64], out: &mut Circuit) -> Result<()> {
267 self.check_values(values)?;
268 self.validate(template)?;
269
270 out.num_qubits = template.num_qubits;
271 out.num_classical_bits = template.num_classical_bits;
272 out.instructions.clone_from(&template.instructions);
273 self.write_angles(out, values);
274 Ok(())
275 }
276
277 pub(crate) fn check_values(&self, values: &[f64]) -> Result<()> {
283 if values.len() != self.num_slots {
284 return Err(PrismError::InvalidParameter {
285 message: format!(
286 "expected {} parameter values, got {}",
287 self.num_slots,
288 values.len()
289 ),
290 });
291 }
292 if let Some(i) = values.iter().position(|v| !v.is_finite()) {
293 return Err(PrismError::InvalidParameter {
294 message: format!(
295 "parameter value {i} is {}, expected a finite angle",
296 values[i]
297 ),
298 });
299 }
300 Ok(())
301 }
302
303 pub(crate) fn write_angles(&self, out: &mut Circuit, values: &[f64]) {
306 for link in &self.links {
307 *angle_mut(&mut out.instructions[link.instruction]) = values[link.slot];
308 }
309 }
310
311 pub fn values(&self, circuit: &Circuit) -> Result<Vec<f64>> {
317 self.validate(circuit)?;
318 let mut out = vec![0.0; self.num_slots];
319 for link in &self.links {
320 out[link.slot] = angle_of(&circuit.instructions[link.instruction]);
321 }
322 Ok(out)
323 }
324}
325
326pub(crate) fn angle_of(instruction: &Instruction) -> f64 {
328 match instruction {
329 Instruction::Gate {
330 gate: Gate::Rx(t) | Gate::Ry(t) | Gate::Rz(t) | Gate::Rzz(t) | Gate::P(t),
331 ..
332 } => *t,
333 Instruction::Gate {
334 gate: Gate::PauliRot(data),
335 ..
336 } => data.theta(),
337 _ => unreachable!("parameter link validated as bindable"),
338 }
339}
340
341pub(crate) fn angle_mut(instruction: &mut Instruction) -> &mut f64 {
342 match instruction {
343 Instruction::Gate {
344 gate: Gate::Rx(t) | Gate::Ry(t) | Gate::Rz(t) | Gate::Rzz(t) | Gate::P(t),
345 ..
346 } => t,
347 Instruction::Gate {
348 gate: Gate::PauliRot(data),
349 ..
350 } => &mut data.theta,
351 _ => unreachable!("parameter link validated as bindable"),
352 }
353}
354
355#[cfg(test)]
356mod tests {
357 use super::*;
358
359 fn two_rotations() -> Circuit {
360 let mut c = Circuit::new(2, 0);
361 c.add_gate(Gate::Rx(0.1), &[0]);
362 c.add_gate(Gate::Cx, &[0, 1]);
363 c.add_gate(Gate::Rz(0.2), &[1]);
364 c
365 }
366
367 #[test]
368 fn all_rotations_declares_one_slot_per_gate() {
369 let p = Parameters::all_rotations(&two_rotations());
370 assert_eq!(p.num_slots(), 2);
371 assert_eq!(p.links().len(), 2);
372 assert_eq!(p.links()[1].instruction, 2);
373 }
374
375 #[test]
376 fn bind_writes_angles_and_leaves_structure() {
377 let template = two_rotations();
378 let p = Parameters::all_rotations(&template);
379 let bound = p.bind(&template, &[1.5, 2.5]).unwrap();
380 assert_eq!(bound.instructions.len(), 3);
381 assert!(matches!(
382 bound.instructions[0],
383 Instruction::Gate {
384 gate: Gate::Rx(t),
385 ..
386 } if t == 1.5
387 ));
388 assert!(matches!(
389 bound.instructions[2],
390 Instruction::Gate {
391 gate: Gate::Rz(t),
392 ..
393 } if t == 2.5
394 ));
395 }
396
397 #[test]
398 fn shared_slot_writes_every_linked_gate() {
399 let template = two_rotations();
400 let mut p = Parameters::new(1);
401 p.link(0, 0);
402 p.link(2, 0);
403 let bound = p.bind(&template, &[0.75]).unwrap();
404 assert_eq!(super::angle_of(&bound.instructions[0]), 0.75);
405 assert_eq!(super::angle_of(&bound.instructions[2]), 0.75);
406 }
407
408 #[test]
409 fn wrong_arity_is_an_error() {
410 let template = two_rotations();
411 let p = Parameters::all_rotations(&template);
412 assert!(p.bind(&template, &[1.0]).is_err());
413 assert!(p.bind(&template, &[1.0, 2.0, 3.0]).is_err());
414 }
415
416 #[test]
417 fn non_finite_value_is_an_error() {
418 let template = two_rotations();
419 let p = Parameters::all_rotations(&template);
420 assert!(p.bind(&template, &[f64::NAN, 0.0]).is_err());
421 assert!(p.bind(&template, &[0.0, f64::INFINITY]).is_err());
422 }
423
424 #[test]
425 fn link_past_end_is_an_error() {
426 let template = two_rotations();
427 let mut p = Parameters::new(1);
428 p.link(99, 0);
429 assert!(p.bind(&template, &[0.5]).is_err());
430 }
431
432 #[test]
433 fn link_to_non_bindable_gate_is_an_error() {
434 let template = two_rotations();
435 let mut p = Parameters::new(1);
436 p.link(1, 0);
437 assert!(p.bind(&template, &[0.5]).is_err());
438 }
439
440 #[test]
441 fn slot_no_gate_reads_is_accepted_and_reported() {
442 let template = two_rotations();
443 let mut p = Parameters::new(2);
444 p.link(0, 0);
445 assert!(p.bind(&template, &[0.5, 0.5]).is_ok());
446 assert_eq!(p.unread_slots(), vec![1]);
447 }
448
449 #[test]
450 #[should_panic(expected = "out of bounds")]
451 fn slot_past_declared_count_panics() {
452 let mut p = Parameters::new(1);
453 p.link(0, 4);
454 }
455
456 #[test]
457 fn values_round_trip_through_bind() {
458 let template = two_rotations();
459 let p = Parameters::all_rotations(&template);
460 let bound = p.bind(&template, &[0.3, 0.4]).unwrap();
461 assert_eq!(p.values(&bound).unwrap(), vec![0.3, 0.4]);
462 }
463
464 #[test]
470 fn angle_sites_agree_on_every_gate() {
471 use crate::circuit::plan::write_angle;
472 use crate::gates::PauliRotData;
473 use crate::sim::unified_pauli::PauliAxis;
474
475 let h = Gate::H.matrix_2x2();
476 let gates = [
477 Gate::Rx(0.1),
478 Gate::Ry(0.2),
479 Gate::Rz(0.3),
480 Gate::P(0.4),
481 Gate::Rzz(0.5),
482 Gate::PauliRot(Box::new(PauliRotData {
483 theta: 0.6,
484 axes: vec![PauliAxis::X, PauliAxis::Z],
485 })),
486 Gate::Id,
487 Gate::H,
488 Gate::T,
489 Gate::SX,
490 Gate::Cx,
491 Gate::Cz,
492 Gate::Swap,
493 Gate::Cu(Box::new(h)),
494 Gate::Fused(Box::new(h)),
495 Gate::Fused2q(Box::new(Gate::Cx.matrix_4x4())),
496 Gate::QftBlock { start: 0, num: 2 },
497 ];
498 for gate in gates {
499 let targets: SmallVec<[usize; 4]> = (0..gate.num_qubits()).collect();
500 let mut inst = Instruction::Gate {
501 gate: gate.clone(),
502 targets,
503 };
504 let bindable = gate.pauli_generator().is_some();
505 assert_eq!(write_angle(&mut inst, 0, 1.25), bindable, "{gate}");
506 if bindable {
507 assert_eq!(angle_of(&inst), 1.25, "{gate}");
508 *angle_mut(&mut inst) = 2.5;
509 assert_eq!(angle_of(&inst), 2.5, "{gate}");
510 }
511 }
512 }
513}