1use crate::circuit::{
28 Circuit, CircuitError, CircuitId, ClassicalBinaryOp, ClassicalCast, ClassicalCompareOp,
29 ClassicalExpr, ClassicalExprKind, ClassicalType, ClassicalUnaryOp, ClassicalValue,
30 ClassicalVar,
31};
32use crate::device::Outcome;
33use crate::qis::QisError;
34
35#[derive(Debug, Clone, PartialEq, Eq, Hash)]
37pub enum RuntimeValue {
38 Bit(bool),
39 Bool(bool),
40 UInt { width: u32, value: u128 },
41 BitVec { width: u32, bits: Outcome },
42}
43
44impl RuntimeValue {
45 pub fn ty(&self) -> ClassicalType {
47 match self {
48 Self::Bit(_) => ClassicalType::Bit,
49 Self::Bool(_) => ClassicalType::Bool,
50 Self::UInt { width, .. } => ClassicalType::uint(*width).unwrap(),
51 Self::BitVec { width, .. } => ClassicalType::bit_vec(*width).unwrap(),
52 }
53 }
54
55 pub fn to_bitstring(&self) -> Option<String> {
57 match self {
58 Self::Bit(value) => Some(if *value { "1" } else { "0" }.to_string()),
59 Self::BitVec { width, bits } => Some(bits.to_bitstring(*width as usize)),
60 Self::Bool(_) | Self::UInt { .. } => None,
61 }
62 }
63
64 pub(crate) fn bit_vec_from_lsb(bits: &[bool]) -> Self {
69 Self::BitVec {
70 width: bits.len() as u32,
71 bits: Outcome::from_lsb_bits(bits),
72 }
73 }
74
75 fn bit_at(&self, index: u32) -> Result<bool, QisError> {
76 match self {
77 Self::UInt { width, value } if index < *width && index < 128 => {
78 Ok((value >> index) & 1 == 1)
79 }
80 Self::BitVec { width, bits } if index < *width => Ok(bits.is_one(index as usize)),
81 _ => Err(qis_unsupported(
82 "cannot read the requested bit from runtime value",
83 )),
84 }
85 }
86
87 fn bits_lsb(&self) -> Result<Vec<bool>, QisError> {
88 match self {
89 Self::Bit(value) => Ok(vec![*value]),
90 Self::BitVec { width, bits } => Ok((0..*width)
91 .map(|index| bits.is_one(index as usize))
92 .collect()),
93 Self::Bool(_) | Self::UInt { .. } => Err(qis_unsupported(
94 "runtime value cannot be interpreted as BitVec bits",
95 )),
96 }
97 }
98
99 fn as_u128(&self) -> Result<u128, QisError> {
100 match self {
101 Self::UInt { value, .. } => Ok(*value),
102 Self::BitVec { width, bits } if *width <= 128 => Ok((0..*width)
103 .fold(0u128, |value, index| {
104 value | ((bits.is_one(index as usize) as u128) << index)
105 })),
106 Self::BitVec { .. } => Err(qis_unsupported(
107 "BitVec values wider than 128 bits cannot be cast to UInt",
108 )),
109 Self::Bit(_) | Self::Bool(_) => Err(qis_unsupported(
110 "runtime value cannot be interpreted as an unsigned integer",
111 )),
112 }
113 }
114}
115
116#[derive(Debug, Clone)]
118pub struct ClassicalState {
119 circuit_id: CircuitId,
120 value_types: Vec<ClassicalType>,
121 values: Vec<Option<RuntimeValue>>,
122 var_types: Vec<ClassicalType>,
123 vars: Vec<Option<RuntimeValue>>,
124}
125
126impl ClassicalState {
127 pub(crate) fn for_circuit(circuit: &Circuit) -> Self {
128 Self {
129 circuit_id: circuit.id(),
130 value_types: circuit.classical_values().to_vec(),
131 values: vec![None; circuit.classical_values().len()],
132 var_types: circuit.classical_vars().to_vec(),
133 vars: vec![None; circuit.classical_vars().len()],
134 }
135 }
136
137 pub fn value(&self, value: ClassicalValue) -> Option<&RuntimeValue> {
142 if value.circuit_id() != self.circuit_id {
143 return None;
144 }
145 self.value_types
146 .get(value.index() as usize)
147 .filter(|ty| **ty == value.ty())?;
148 self.values.get(value.index() as usize)?.as_ref()
149 }
150
151 pub fn var(&self, var: ClassicalVar) -> Option<&RuntimeValue> {
156 if var.circuit_id() != self.circuit_id {
157 return None;
158 }
159 self.var_types
160 .get(var.index() as usize)
161 .filter(|ty| **ty == var.ty())?;
162 self.vars.get(var.index() as usize)?.as_ref()
163 }
164
165 pub(crate) fn set_value(
166 &mut self,
167 target: ClassicalValue,
168 value: RuntimeValue,
169 ) -> Result<(), QisError> {
170 if target.circuit_id() != self.circuit_id {
171 return Err(CircuitError::ForeignClassicalHandle {
172 kind: "classical value",
173 index: target.index(),
174 }
175 .into());
176 }
177 let expected = self
178 .value_types
179 .get(target.index() as usize)
180 .copied()
181 .ok_or_else(|| qis_unsupported("measurement result is not owned by this circuit"))?;
182 if expected != target.ty() || expected != value.ty() {
183 return Err(qis_unsupported(
184 "measurement result type does not match circuit IR",
185 ));
186 }
187 self.values[target.index() as usize] = Some(value);
188 Ok(())
189 }
190
191 pub(crate) fn store(
192 &mut self,
193 target: ClassicalVar,
194 expr: &ClassicalExpr,
195 ) -> Result<(), QisError> {
196 if target.circuit_id() != self.circuit_id {
197 return Err(CircuitError::ForeignClassicalHandle {
198 kind: "classical variable",
199 index: target.index(),
200 }
201 .into());
202 }
203 let expected = self
204 .var_types
205 .get(target.index() as usize)
206 .copied()
207 .ok_or_else(|| qis_unsupported("store target is not owned by this circuit"))?;
208 let value = self.evaluate(expr)?;
209 if expected != target.ty() || expected != value.ty() {
210 return Err(qis_unsupported(
211 "store value type does not match its target",
212 ));
213 }
214 self.vars[target.index() as usize] = Some(value);
215 Ok(())
216 }
217
218 pub(crate) fn rebind_to_circuit(&mut self, circuit: &Circuit) -> Result<(), QisError> {
221 if self.value_types.as_slice() != circuit.classical_values()
222 || self.var_types.as_slice() != circuit.classical_vars()
223 {
224 return Err(CircuitError::InvalidOperation(
225 "cannot rebind classical state to a circuit with different classical tables"
226 .to_string(),
227 )
228 .into());
229 }
230 self.circuit_id = circuit.id();
231 Ok(())
232 }
233
234 pub(crate) fn evaluate(&self, expr: &ClassicalExpr) -> Result<RuntimeValue, QisError> {
239 match expr.kind() {
240 ClassicalExprKind::Var(var) => self
241 .var(*var)
242 .cloned()
243 .ok_or_else(|| qis_unsupported("classical variable is read before being stored")),
244 ClassicalExprKind::Value(value) => self
245 .value(*value)
246 .cloned()
247 .ok_or_else(|| qis_unsupported("classical value is read before being produced")),
248 ClassicalExprKind::BoolLiteral(value) => Ok(RuntimeValue::Bool(*value)),
249 ClassicalExprKind::BitLiteral(value) => Ok(RuntimeValue::Bit(*value)),
250 ClassicalExprKind::UIntLiteral { width, value } => Ok(RuntimeValue::UInt {
251 width: width.get(),
252 value: *value,
253 }),
254 ClassicalExprKind::BitVecLiteral { width, value } => Ok(RuntimeValue::BitVec {
255 width: width.get(),
256 bits: Outcome::from_lsb_bits(
257 &(0..width.get())
258 .map(|index| (value >> index) & 1 == 1)
259 .collect::<Vec<_>>(),
260 ),
261 }),
262 ClassicalExprKind::Unary { op, expr } => {
263 let value = self.evaluate(expr)?;
264 match (op, value) {
265 (ClassicalUnaryOp::Not, RuntimeValue::Bit(value)) => {
266 Ok(RuntimeValue::Bit(!value))
267 }
268 (ClassicalUnaryOp::Not, RuntimeValue::Bool(value)) => {
269 Ok(RuntimeValue::Bool(!value))
270 }
271 _ => Err(qis_unsupported("invalid unary classical expression")),
272 }
273 }
274 ClassicalExprKind::Binary { op, lhs, rhs } => {
275 let lhs = self.evaluate(lhs)?;
276 let rhs = self.evaluate(rhs)?;
277 evaluate_binary(*op, lhs, rhs)
278 }
279 ClassicalExprKind::Compare { op, lhs, rhs } => {
280 let lhs = self.evaluate(lhs)?;
281 let rhs = self.evaluate(rhs)?;
282 evaluate_compare(*op, lhs, rhs)
283 }
284 ClassicalExprKind::Cast { cast, expr } => {
285 let value = self.evaluate(expr)?;
286 match (cast, value) {
287 (ClassicalCast::BitToBool, RuntimeValue::Bit(value)) => {
288 Ok(RuntimeValue::Bool(value))
289 }
290 (ClassicalCast::BitVecToUInt, RuntimeValue::BitVec { width, bits }) => {
291 Ok(RuntimeValue::UInt {
292 width,
293 value: RuntimeValue::BitVec { width, bits }.as_u128()?,
294 })
295 }
296 _ => Err(qis_unsupported("invalid classical cast")),
297 }
298 }
299 ClassicalExprKind::Select {
300 condition,
301 then_expr,
302 else_expr,
303 } => match self.evaluate(condition)? {
304 RuntimeValue::Bool(true) => self.evaluate(then_expr),
305 RuntimeValue::Bool(false) => self.evaluate(else_expr),
306 _ => Err(qis_unsupported("select condition is not a Bool")),
307 },
308 ClassicalExprKind::ExtractBit { value, index } => {
309 Ok(RuntimeValue::Bit(self.evaluate(value)?.bit_at(*index)?))
310 }
311 ClassicalExprKind::ExtractBits {
312 value,
313 offset,
314 width,
315 } => {
316 let value = self.evaluate(value)?;
317 let bits = (0..width.get())
318 .map(|index| value.bit_at(offset + index))
319 .collect::<Result<Vec<_>, _>>()?;
320 Ok(RuntimeValue::bit_vec_from_lsb(&bits))
321 }
322 ClassicalExprKind::Concat { parts } => {
323 let mut bits = Vec::new();
324 for part in parts.iter() {
325 bits.extend(self.evaluate(part)?.bits_lsb()?);
326 }
327 Ok(RuntimeValue::bit_vec_from_lsb(&bits))
328 }
329 ClassicalExprKind::PackBits { bits } => {
330 let bits = bits
331 .iter()
332 .map(|bit| match self.evaluate(bit)? {
333 RuntimeValue::Bit(value) => Ok(value),
334 _ => Err(qis_unsupported("pack_bits input is not a Bit")),
335 })
336 .collect::<Result<Vec<_>, _>>()?;
337 Ok(RuntimeValue::bit_vec_from_lsb(&bits))
338 }
339 }
340 }
341}
342
343fn evaluate_binary(
344 op: ClassicalBinaryOp,
345 lhs: RuntimeValue,
346 rhs: RuntimeValue,
347) -> Result<RuntimeValue, QisError> {
348 let apply = |lhs: bool, rhs: bool| match op {
349 ClassicalBinaryOp::And => lhs & rhs,
350 ClassicalBinaryOp::Or => lhs | rhs,
351 ClassicalBinaryOp::Xor => lhs ^ rhs,
352 };
353 match (lhs, rhs) {
354 (RuntimeValue::Bit(lhs), RuntimeValue::Bit(rhs)) => Ok(RuntimeValue::Bit(apply(lhs, rhs))),
355 (RuntimeValue::Bool(lhs), RuntimeValue::Bool(rhs)) => {
356 Ok(RuntimeValue::Bool(apply(lhs, rhs)))
357 }
358 _ => Err(qis_unsupported("invalid binary classical expression")),
359 }
360}
361
362fn evaluate_compare(
363 op: ClassicalCompareOp,
364 lhs: RuntimeValue,
365 rhs: RuntimeValue,
366) -> Result<RuntimeValue, QisError> {
367 let result = match op {
368 ClassicalCompareOp::Eq => lhs == rhs,
369 ClassicalCompareOp::Ne => lhs != rhs,
370 ClassicalCompareOp::Lt
371 | ClassicalCompareOp::Le
372 | ClassicalCompareOp::Gt
373 | ClassicalCompareOp::Ge => match (lhs, rhs) {
374 (
375 RuntimeValue::UInt {
376 width: lhs_width,
377 value: lhs,
378 },
379 RuntimeValue::UInt {
380 width: rhs_width,
381 value: rhs,
382 },
383 ) if lhs_width == rhs_width => match op {
384 ClassicalCompareOp::Lt => lhs < rhs,
385 ClassicalCompareOp::Le => lhs <= rhs,
386 ClassicalCompareOp::Gt => lhs > rhs,
387 ClassicalCompareOp::Ge => lhs >= rhs,
388 ClassicalCompareOp::Eq | ClassicalCompareOp::Ne => unreachable!(),
389 },
390 _ => {
391 return Err(qis_unsupported(
392 "ordered comparison operands are not matching UInts",
393 ));
394 }
395 },
396 };
397 Ok(RuntimeValue::Bool(result))
398}
399
400fn qis_unsupported(message: impl Into<String>) -> QisError {
401 QisError::UnsupportedOperation(message.into())
402}
403
404#[cfg(test)]
405mod tests {
406 use super::{ClassicalState, RuntimeValue};
407 use crate::circuit::{Circuit, CircuitError, ClassicalExpr, ClassicalType, Qubit};
408 use crate::qis::QisError;
409
410 #[test]
411 fn evaluates_literals_and_bit_vector_operations() {
412 let circuit = Circuit::new(0);
413 let state = ClassicalState::for_circuit(&circuit);
414 let packed = ClassicalExpr::pack_bits([
415 ClassicalExpr::bit_literal(true),
416 ClassicalExpr::bit_literal(false),
417 ClassicalExpr::bit_literal(true),
418 ])
419 .unwrap();
420
421 let value = state.evaluate(&packed).unwrap();
422 assert_eq!(value.to_bitstring().as_deref(), Some("101"));
423 }
424
425 #[test]
426 fn stores_and_reads_a_variable() {
427 let mut circuit = Circuit::new(0);
428 let var = circuit.var(ClassicalType::Bit);
429 let mut state = ClassicalState::for_circuit(&circuit);
430
431 state.store(var, &ClassicalExpr::bit_literal(true)).unwrap();
432
433 assert_eq!(state.var(var), Some(&RuntimeValue::Bit(true)));
434 }
435
436 #[test]
437 fn rejects_handles_owned_by_another_circuit() {
438 let mut owner = Circuit::new(1);
439 let owner_var = owner.var(ClassicalType::Bit);
440 let owner_value = owner.measure(Qubit::new(0)).unwrap().value();
441 let mut state = ClassicalState::for_circuit(&owner);
442 state
443 .set_value(owner_value, RuntimeValue::Bit(true))
444 .unwrap();
445 state
446 .store(owner_var, &ClassicalExpr::bit_literal(true))
447 .unwrap();
448
449 let mut other = Circuit::new(1);
450 let other_var = other.var(ClassicalType::Bit);
451 let other_value = other.measure(Qubit::new(0)).unwrap().value();
452
453 assert_eq!(state.value(other_value), None);
454 assert_eq!(state.var(other_var), None);
455 assert!(matches!(
456 state.set_value(other_value, RuntimeValue::Bit(false)),
457 Err(QisError::CircuitError(
458 CircuitError::ForeignClassicalHandle {
459 kind: "classical value",
460 ..
461 }
462 ))
463 ));
464 assert!(matches!(
465 state.store(other_var, &ClassicalExpr::bit_literal(false)),
466 Err(QisError::CircuitError(
467 CircuitError::ForeignClassicalHandle {
468 kind: "classical variable",
469 ..
470 }
471 ))
472 ));
473 }
474
475 #[test]
476 fn runtime_value_compares_by_value_and_hashes_consistently() {
477 use std::collections::hash_map::DefaultHasher;
478 use std::hash::{Hash, Hasher};
479
480 let hash_of = |value: &RuntimeValue| {
481 let mut hasher = DefaultHasher::new();
482 value.hash(&mut hasher);
483 hasher.finish()
484 };
485
486 assert_eq!(
487 RuntimeValue::UInt {
488 width: 8,
489 value: 42
490 },
491 RuntimeValue::UInt {
492 width: 8,
493 value: 42
494 }
495 );
496 assert_ne!(
497 RuntimeValue::UInt {
498 width: 8,
499 value: 42
500 },
501 RuntimeValue::UInt {
502 width: 8,
503 value: 43
504 }
505 );
506 assert_ne!(RuntimeValue::Bit(true), RuntimeValue::Bool(true),);
507
508 let bit_vec = RuntimeValue::BitVec {
509 width: 3,
510 bits: crate::device::Outcome::from_bitstring("101").unwrap(),
511 };
512 let bit_vec_again = RuntimeValue::BitVec {
513 width: 3,
514 bits: crate::device::Outcome::from_bitstring("101").unwrap(),
515 };
516 assert_eq!(bit_vec, bit_vec_again);
517 assert_eq!(hash_of(&bit_vec), hash_of(&bit_vec_again));
518 assert_eq!(
519 hash_of(&RuntimeValue::UInt {
520 width: 8,
521 value: 42
522 }),
523 hash_of(&RuntimeValue::UInt {
524 width: 8,
525 value: 42
526 })
527 );
528 }
529}