pub(crate) mod ir;
pub(crate) mod lowering;
pub(crate) mod parser;
use std::path::Path;
pub use self::ir::Circuit;
use self::lowering::{CircuitLoweringResult, lower_circuit_to_factored};
pub(crate) use self::parser::{parse_ticit_circuit_file, parse_ticit_circuit_text};
use crate::errors::{Result, TicitError};
use crate::factored::{
FactoredInstruction, FactoredInstructionProgram, FrameFactoredState, PendingFactoredState,
RecordDetector,
};
use crate::pending_optimizer::optimize_pending_operations;
use crate::planner::plan_factored_updates;
use crate::sampler::prepared::{ReferenceSample, Sampler, SamplerOptions};
use crate::symbolic::{SymbolicBool, SymbolicBoolEvaluationPlan, xor_bool};
#[derive(Clone, Debug)]
struct LoweredCircuit {
state: FrameFactoredState,
measurement_records: Vec<SymbolicBool>,
detectors: Vec<LoweredDetector>,
}
#[derive(Clone, Debug)]
struct LoweredDetector {
records: Vec<usize>,
discard: bool,
after_pending_operation: usize,
}
impl Circuit {
pub fn from_text(text: &str) -> Result<Self> {
parse_ticit_circuit_text(text)
}
pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
parse_ticit_circuit_file(path)
}
pub fn compile(&self, options: SamplerOptions) -> Result<Sampler> {
Sampler::new(self, options)
}
pub fn reference_sample(&self) -> Result<ReferenceSample> {
crate::sampler::prepared::circuit_reference_sample(self)
}
#[must_use]
pub fn qubit_count(&self) -> usize {
self.nqubits
}
#[must_use]
pub fn measurement_record_count(&self) -> usize {
self.nrecords
}
#[must_use]
pub fn detector_count(&self) -> usize {
self.detectors.len()
}
#[must_use]
pub fn observable_count(&self) -> usize {
self.observables
.iter()
.map(|observable| observable.index + 1)
.max()
.unwrap_or(0)
}
#[must_use]
pub fn expectation_value_count(&self) -> usize {
self.nexpvals
}
#[must_use]
pub fn has_detector_postselection(&self) -> bool {
self.detectors.iter().any(|detector| detector.discard)
}
#[must_use]
pub fn all_detectors_postselected(&self) -> bool {
!self.detectors.is_empty() && self.detectors.iter().all(|detector| detector.discard)
}
}
fn detectors_with_lowered_positions(
circuit: &Circuit,
lowered: &CircuitLoweringResult,
) -> Result<Vec<LoweredDetector>> {
circuit
.detectors
.iter()
.map(|detector| {
let counts = &lowered.instruction_pending_operation_counts;
if detector.after_instruction >= counts.len() {
return Err(TicitError::new("detector source position is out of range"));
}
Ok(LoweredDetector {
records: detector.records.clone(),
discard: detector.discard,
after_pending_operation: counts[detector.after_instruction],
})
})
.collect()
}
fn lowered_circuit(circuit: &Circuit) -> Result<LoweredCircuit> {
let lowered = lower_circuit_to_factored(circuit)?;
let detectors = detectors_with_lowered_positions(circuit, &lowered)?;
Ok(LoweredCircuit {
state: lowered.state,
measurement_records: lowered.measurement_records,
detectors,
})
}
fn detector_expression(
records: &[usize],
measurement_records: &[SymbolicBool],
) -> Result<SymbolicBool> {
let mut out = SymbolicBool::default();
for &record in records {
if record == 0 || record > measurement_records.len() {
return Err(TicitError::new(
"detector references an out-of-range measurement record",
));
}
out = xor_bool(&out, &measurement_records[record - 1]);
}
Ok(out)
}
fn instruction_checkpoint_for_pending_prefix(
program: &FactoredInstructionProgram,
pending_prefix: usize,
) -> Result<usize> {
if pending_prefix == 0 && program.pending_prefix_instruction_indices.is_empty() {
return Ok(0);
}
let Some(&checkpoint) = program
.pending_prefix_instruction_indices
.get(pending_prefix)
else {
return Err(TicitError::new(
"detector pending-operation position is out of range",
));
};
if checkpoint < 0 || checkpoint as usize > program.instructions.len() {
return Err(TicitError::new(
"detector instruction checkpoint is out of range",
));
}
Ok(checkpoint as usize)
}
fn insert_detector_events(
program: FactoredInstructionProgram,
detectors: &[LoweredDetector],
measurement_records: &[SymbolicBool],
postselection_mask: &[u8],
) -> Result<FactoredInstructionProgram> {
if detectors.is_empty() {
return Ok(program);
}
let mut events: Vec<Vec<RecordDetector>> = vec![Vec::new(); program.instructions.len() + 1];
for (idx, detector) in detectors.iter().enumerate() {
let outcome = detector_expression(&detector.records, measurement_records)?;
let instruction = RecordDetector {
outcome_plan: SymbolicBoolEvaluationPlan::new(&outcome),
outcome,
records: detector.records.iter().map(|&r| r as i32).collect(),
detector: (idx + 1) as i32,
postselect: detector.discard
|| postselection_mask.get(idx).is_some_and(|&flag| flag != 0),
};
let checkpoint =
instruction_checkpoint_for_pending_prefix(&program, detector.after_pending_operation)?;
events[checkpoint].push(instruction);
}
let mut instructions: Vec<FactoredInstruction> =
Vec::with_capacity(program.instructions.len() + detectors.len());
let mut events = events.into_iter();
let leading = events
.next()
.expect("events has instructions.len() + 1 entries");
instructions.extend(leading.into_iter().map(FactoredInstruction::from));
for (instruction, following) in program.instructions.into_iter().zip(events) {
instructions.push(instruction);
instructions.extend(following.into_iter().map(FactoredInstruction::from));
}
FactoredInstructionProgram::with_context(
program.n,
program.initial_k,
instructions,
program.max_k,
program.context,
Vec::new(),
)
}
pub(crate) fn plan_circuit(
parsed: &Circuit,
postselection_mask: &[u8],
) -> Result<FactoredInstructionProgram> {
let LoweredCircuit {
mut state,
measurement_records,
detectors,
} = lowered_circuit(parsed)?;
const GUARDED_HANDOFF_MIN_ACTIVE_TERMS: usize = 100_000;
let mut retained_state = None;
let mut allocation_guard = None;
let mut pending = if state.active_frame.terms.len() >= GUARDED_HANDOFF_MIN_ACTIVE_TERMS {
allocation_guard = Some(std::mem::take(&mut state.active_frame));
PendingFactoredState::from_frame_state(state)
} else {
let pending = PendingFactoredState::from_frame_state(state.clone());
retained_state = Some(state);
pending
};
let detector_prefixes: Vec<usize> = detectors
.iter()
.map(|detector| detector.after_pending_operation)
.collect();
let optimization = optimize_pending_operations(&mut pending, &detector_prefixes)?;
if let Some(frame) = &mut allocation_guard {
frame.release_transpose_storage();
}
let mut detectors = detectors;
for detector in &mut detectors {
let remapped = optimization
.prefix_remap
.get(detector.after_pending_operation)
.copied()
.unwrap_or(-1);
if remapped < 0 {
return Err(TicitError::new(
"detector pending-operation prefix was not preserved by optimization",
));
}
detector.after_pending_operation = remapped as usize;
}
let program = plan_factored_updates(pending)?;
let result = insert_detector_events(
program,
&detectors,
&measurement_records,
postselection_mask,
);
drop((retained_state, allocation_guard));
result
}
pub(crate) fn has_postselection(program: &FactoredInstructionProgram) -> bool {
program.instructions.iter().any(|instruction| {
matches!(instruction, FactoredInstruction::RecordDetector(detector) if detector.postselect)
})
}
#[cfg(test)]
pub(crate) fn parse_ticit_text(text: &str) -> Result<Circuit> {
Circuit::from_text(text)
}
#[cfg(test)]
pub(crate) fn parse_ticit_file(path: impl AsRef<Path>) -> Result<Circuit> {
Circuit::from_file(path)
}
#[cfg(test)]
pub(crate) fn plan_ticit_factored_program(parsed: &Circuit) -> Result<FactoredInstructionProgram> {
plan_circuit(parsed, &[])
}