prism-q 0.6.0

PRISM-Q — Performance Rust Interoperable Simulator for Quantum
Documentation
use std::borrow::Cow;

use num_complex::Complex64;

use super::{smallvec, Circuit, Instruction, SmallVec};
use crate::gates::{BatchPhaseData, Gate, MultiFusedData};

use super::fusion::IDENTITY_EPS;

const MIN_BATCH_PHASES: usize = 2;

pub fn fuse_controlled_phases(circuit: Cow<'_, Circuit>) -> Cow<'_, Circuit> {
    if !has_batchable_phases(&circuit) {
        return circuit;
    }

    let mut output: Vec<Instruction> = Vec::with_capacity(circuit.instructions.len());
    let n = circuit.num_qubits;
    type PhaseVec = SmallVec<[(usize, Complex64); 8]>;
    let mut pending: Vec<Option<PhaseVec>> = (0..n).map(|_| None).collect();

    let flush_qubit =
        |q: usize, pending: &mut [Option<PhaseVec>], output: &mut Vec<Instruction>| {
            if let Some(phases) = pending[q].take() {
                if phases.len() >= MIN_BATCH_PHASES {
                    output.push(Instruction::Gate {
                        gate: Gate::BatchPhase(Box::new(BatchPhaseData { phases })),
                        targets: smallvec![q],
                    });
                } else {
                    for (target, phase) in phases {
                        let one = Complex64::new(1.0, 0.0);
                        let zero = Complex64::new(0.0, 0.0);
                        output.push(Instruction::Gate {
                            gate: Gate::cu([[one, zero], [zero, phase]]),
                            targets: smallvec![q, target],
                        });
                    }
                }
            }
        };

    for inst in &circuit.instructions {
        match inst {
            Instruction::Gate { gate, targets } => {
                if let Some(phase) = gate.controlled_phase() {
                    if gate.num_qubits() == 2 {
                        let control = targets[0];
                        let target = targets[1];
                        flush_qubit(target, &mut pending, &mut output);
                        match &mut pending[control] {
                            Some(v) => v.push((target, phase)),
                            slot => *slot = Some(smallvec![(target, phase)]),
                        }
                        continue;
                    }
                }
                for &q in targets.iter() {
                    flush_qubit(q, &mut pending, &mut output);
                }
                output.push(inst.clone());
            }
            Instruction::Measure { qubit, .. } | Instruction::Reset { qubit } => {
                flush_qubit(*qubit, &mut pending, &mut output);
                output.push(inst.clone());
            }
            Instruction::Barrier { qubits } => {
                for &q in qubits.iter() {
                    flush_qubit(q, &mut pending, &mut output);
                }
                output.push(inst.clone());
            }
            Instruction::Conditional { targets, .. } => {
                for &q in targets.iter() {
                    flush_qubit(q, &mut pending, &mut output);
                }
                output.push(inst.clone());
            }
        }
    }

    for q in 0..n {
        flush_qubit(q, &mut pending, &mut output);
    }

    Cow::Owned(Circuit {
        num_qubits: circuit.num_qubits,
        num_classical_bits: circuit.num_classical_bits,
        instructions: output,
    })
}

fn has_batchable_phases(circuit: &Circuit) -> bool {
    let mut has_pending = vec![false; circuit.num_qubits];
    for inst in &circuit.instructions {
        match inst {
            Instruction::Gate { gate, targets } => {
                if gate.controlled_phase().is_some() && gate.num_qubits() == 2 {
                    let control = targets[0];
                    if has_pending[control] {
                        return true;
                    }
                    has_pending[control] = true;
                    has_pending[targets[1]] = false;
                } else {
                    for &q in targets.iter() {
                        has_pending[q] = false;
                    }
                }
            }
            Instruction::Measure { qubit, .. } | Instruction::Reset { qubit } => {
                has_pending[*qubit] = false;
            }
            Instruction::Barrier { qubits } => {
                for &q in qubits {
                    has_pending[q] = false;
                }
            }
            Instruction::Conditional { targets, .. } => {
                for &q in targets.iter() {
                    has_pending[q] = false;
                }
            }
        }
    }
    false
}

fn is_batchable_1q(inst: &Instruction) -> bool {
    matches!(
        inst,
        Instruction::Gate { targets, gate, .. }
            if targets.len() == 1
                && !matches!(gate, Gate::BatchPhase(_) | Gate::MultiFused(_) | Gate::Multi2q(_) | Gate::DiagonalBatch(_))
    )
}

pub(super) fn batch_post_phase_1q(circuit: Cow<'_, Circuit>) -> Cow<'_, Circuit> {
    let mut max_run = 0usize;
    let mut run = 0usize;
    for inst in &circuit.instructions {
        if is_batchable_1q(inst) {
            run += 1;
            max_run = max_run.max(run);
        } else {
            run = 0;
        }
    }
    if max_run < 2 {
        return circuit;
    }

    let mut output: Vec<Instruction> = Vec::with_capacity(circuit.instructions.len());
    let mut pending: Vec<(usize, [[Complex64; 2]; 2])> = Vec::new();

    let flush = |pending: &mut Vec<(usize, [[Complex64; 2]; 2])>, output: &mut Vec<Instruction>| {
        if pending.len() >= 2 {
            let mut targets: SmallVec<[usize; 4]> = SmallVec::new();
            for &(q, _) in pending.iter() {
                if !targets.contains(&q) {
                    targets.push(q);
                }
            }
            targets.sort_unstable();
            let all_diagonal = pending
                .iter()
                .all(|(_, m)| m[0][1].norm() < IDENTITY_EPS && m[1][0].norm() < IDENTITY_EPS);
            output.push(Instruction::Gate {
                gate: Gate::MultiFused(Box::new(MultiFusedData {
                    gates: std::mem::take(pending),
                    all_diagonal,
                })),
                targets,
            });
        } else {
            for (q, mat) in pending.drain(..) {
                output.push(Instruction::Gate {
                    gate: Gate::Fused(Box::new(mat)),
                    targets: smallvec![q],
                });
            }
        }
    };

    for inst in &circuit.instructions {
        if is_batchable_1q(inst) {
            if let Instruction::Gate { gate, targets, .. } = inst {
                pending.push((targets[0], gate.matrix_2x2()));
            }
        } else {
            flush(&mut pending, &mut output);
            output.push(inst.clone());
        }
    }

    flush(&mut pending, &mut output);

    Cow::Owned(Circuit {
        num_qubits: circuit.num_qubits,
        num_classical_bits: circuit.num_classical_bits,
        instructions: output,
    })
}