Skip to main content

tnc/builders/
circuit_builder.rs

1//! Building a tensor network from a quantum circuit.
2
3use std::marker::PhantomData;
4
5use itertools::Itertools;
6use num_complex::Complex64;
7use permutation::Permutation;
8
9use crate::{
10    tensornetwork::{
11        tensor::{CompositeTensor, EdgeIndex, LeafTensor},
12        tensordata::TensorData,
13    },
14    utils::traits::PermutationToVec,
15};
16
17/// A quantum register, i.e., an array of qubits. Similar to the Qiskit / QASM
18/// idea, quantum registers group qubits (for instance, one qreg for ancillas), and
19/// a circuit can act on multiple qregs.
20#[derive(Debug)]
21pub struct QuantumRegister<'a> {
22    base: usize,
23    size: usize,
24    phantom: PhantomData<&'a Circuit>,
25}
26
27impl QuantumRegister<'_> {
28    /// Creates a new quantum register without any associated circuit. This is mainly
29    /// for testing.
30    #[cfg(test)]
31    pub(crate) fn new(size: usize) -> Self {
32        QuantumRegister {
33            base: 0,
34            size,
35            phantom: PhantomData,
36        }
37    }
38
39    /// Returns the qubit at a given index.
40    pub fn qubit(&self, index: usize) -> Qubit<'_> {
41        assert!(index < self.size);
42        Qubit {
43            index: self.base + index,
44            phantom: PhantomData,
45        }
46    }
47
48    /// Returns an iterator over all qubits in this register.
49    pub fn qubits(&self) -> impl Iterator<Item = Qubit<'_>> {
50        (self.base..self.base + self.size).map(|i| Qubit {
51            index: i,
52            phantom: PhantomData,
53        })
54    }
55
56    /// Returns the size of the register.
57    #[inline]
58    pub fn len(&self) -> usize {
59        self.size
60    }
61
62    /// Returns whether this register is empty, i.e., doesn't contain any qubits.
63    #[inline]
64    pub fn is_empty(&self) -> bool {
65        self.size == 0
66    }
67}
68
69/// A single qubit from a quantum register.
70pub struct Qubit<'a> {
71    index: usize,
72    phantom: PhantomData<&'a Circuit>,
73}
74
75/// A struct holding a permutation to be applied to a tensor.
76#[derive(Debug, Clone)]
77pub struct Permutor {
78    target_leg_order: Vec<EdgeIndex>,
79}
80
81impl Permutor {
82    fn new(target_legs: Vec<EdgeIndex>) -> Self {
83        Self {
84            target_leg_order: target_legs,
85        }
86    }
87
88    /// Permutates the tensor according to the stored permutation.
89    pub fn apply(&self, tensor: LeafTensor) -> LeafTensor {
90        if self.is_identity() {
91            return tensor;
92        }
93
94        let (mut legs, mut bond_dims, tensordata) = tensor.into_inner();
95        let mut data = tensordata.into_data();
96
97        // Find the permutation
98        let mut perm = Self::permutation_between(&legs, &self.target_leg_order);
99
100        // Permute legs, shape and data
101        perm.apply_slice_in_place(&mut legs);
102        perm.apply_slice_in_place(&mut bond_dims);
103        data = data.permuted_axes(perm.to_vec());
104
105        LeafTensor::new_with_data(legs, bond_dims, TensorData::Matrix(data))
106    }
107
108    /// Returns whether the permutor is identity, in which case it won't have any
109    /// effect.
110    #[inline]
111    pub fn is_identity(&self) -> bool {
112        self.target_leg_order.is_empty()
113    }
114
115    /// Computes the permutation that, when applied to `given`, returns `target`.
116    /// Assumes that `given` and `target` are equal up to permutation.
117    fn permutation_between(given: &[usize], target: &[usize]) -> Permutation {
118        let given_to_sorted = permutation::sort_unstable(given);
119        let target_to_sorted = permutation::sort_unstable(target);
120        &target_to_sorted.inverse() * &given_to_sorted
121    }
122}
123
124/// A quantum circuit builder that constructs a tensor network representing a quantum
125/// circuit.
126#[derive(Debug, Default)]
127pub struct Circuit {
128    /// The last open edges on each qubit.
129    open_edges: Vec<EdgeIndex>,
130    /// The next edge to be used.
131    next_edge: usize,
132    /// The tensor network representing the circuit.
133    tensor_network: CompositeTensor,
134}
135
136impl Circuit {
137    /// The |0> state.
138    fn ket0() -> TensorData {
139        TensorData::new_from_data(&[2], vec![Complex64::ONE, Complex64::ZERO])
140    }
141
142    /// The |1> state.
143    fn ket1() -> TensorData {
144        TensorData::new_from_data(&[2], vec![Complex64::ZERO, Complex64::ONE])
145    }
146
147    /// The Z gate.
148    fn z() -> TensorData {
149        TensorData::Gate((String::from("z"), vec![], false))
150    }
151
152    /// Creates a new edge id.
153    fn new_edge(&mut self) -> usize {
154        let edge = self.next_edge;
155        self.next_edge += 1;
156        edge
157    }
158
159    /// Returns the total number of qubits allocated in this circuit.
160    ///
161    /// # Examples
162    /// ```
163    /// # use tnc::builders::circuit_builder::Circuit;
164    /// let mut circuit = Circuit::default();
165    /// let q1 = circuit.allocate_register(2);
166    /// let q2 = circuit.allocate_register(3);
167    /// assert_eq!(circuit.num_qubits(), 5);
168    /// ```
169    #[inline]
170    pub fn num_qubits(&self) -> usize {
171        self.open_edges.len()
172    }
173
174    /// Allocates a new quantum register. The qubits are initialized in the |0>
175    /// state.
176    pub fn allocate_register<'a>(&mut self, size: usize) -> QuantumRegister<'a> {
177        let previous_qubits = self.num_qubits();
178
179        self.open_edges.reserve(size);
180        self.tensor_network.reserve(size);
181        for _ in 0..size {
182            let edge = self.new_edge();
183            self.open_edges.push(edge);
184            let mut ket0 = LeafTensor::new_from_const(vec![edge], 2);
185            ket0.set_tensor_data(Self::ket0());
186            self.tensor_network.push_tensor(ket0);
187        }
188
189        QuantumRegister {
190            base: previous_qubits,
191            size,
192            phantom: PhantomData,
193        }
194    }
195
196    /// Appends a gate to the circuit on the specified qubits.
197    pub fn append_gate(&mut self, gate: TensorData, indices: &[Qubit]) {
198        assert!(
199            indices.iter().map(|q| q.index).all_unique(),
200            "Qubit arguments must be unique"
201        );
202
203        // Get the old and new edges
204        let old_edges = indices.iter().map(|q| self.open_edges[q.index]);
205        let new_edges = (0..indices.len()).map(|e| e + self.next_edge);
206        let edges = new_edges.chain(old_edges).collect_vec();
207        self.next_edge += indices.len();
208
209        // Update the open edges
210        for (q, next_edge) in indices.iter().zip(&edges[..indices.len()]) {
211            self.open_edges[q.index] = *next_edge;
212        }
213
214        // Create the new tensor
215        let mut new_tensor = LeafTensor::new_from_const(edges, 2);
216        new_tensor.set_tensor_data(gate);
217
218        // Push the new tensor to the tensors
219        self.tensor_network.push_tensor(new_tensor);
220    }
221
222    /// Converts the circuit to a tensor network that computes the amplitude for the
223    /// given bitstring.
224    ///
225    /// The bitstring can also contain wildcards `*`, in which case the tensor leg
226    /// corresponding to this qubit is left open. For every wildcard, the output
227    /// tensor will be doubled in size. In the extreme case where there's only
228    /// wildcards, the full statevector will be computed.
229    ///
230    /// Since the final tensor can end up with arbitrary permutation, a [`Permutor`]
231    /// is returned that can transpose the final tensor after contraction to the
232    /// natural order, i.e., sorted by increasing qubit number. If the bitstring
233    /// contains no wildcards, the final result is a scalar and the permutator can be
234    /// ignored.
235    pub fn into_amplitude_network(mut self, bitstring: &str) -> (CompositeTensor, Permutor) {
236        assert_eq!(bitstring.len(), self.num_qubits());
237
238        // Apply the final bras
239        self.tensor_network.reserve(bitstring.len());
240        let mut final_legs = Vec::new();
241        for (c, e) in bitstring.chars().zip(self.open_edges) {
242            let bra = match c {
243                '0' => Self::ket0(),
244                '1' => Self::ket1(),
245                '*' => {
246                    final_legs.push(e);
247                    continue; // leave this edge open
248                }
249                _ => panic!("Only 0, 1 and * are allowed in bitstring"),
250            };
251            let mut tensor = LeafTensor::new_from_const(vec![e], 2);
252            tensor.set_tensor_data(bra);
253            self.tensor_network.push_tensor(tensor);
254        }
255
256        // The contraction can re-order the legs depending on the contraction order,
257        // so we need to return the order we want the legs to be in at the end, such
258        // that the user can transpose the final tensor and has the expected order of
259        // elements.
260        let permutor = Permutor::new(final_legs);
261        (self.tensor_network, permutor)
262    }
263
264    /// Converts the circuit to a tensor network that computes the full statevector.
265    ///
266    /// Since the final tensor can end up with arbitrary permutation, a [`Permutor`]
267    /// is returned that can transpose the final tensor after contraction to the
268    /// natural order, i.e., sorted by increasing qubit number.
269    #[inline]
270    pub fn into_statevector_network(self) -> (CompositeTensor, Permutor) {
271        let qubits = self.num_qubits();
272        self.into_amplitude_network(&"*".repeat(qubits))
273    }
274
275    /// Creates the adjoint tensor of a given `tensor`. This not only modifies the
276    /// data, but also the order of legs and the bond dims vec. The legs of the new
277    /// tensor are offset by `leg_offset`.
278    fn tensor_adjoint(tensor: &LeafTensor, leg_offset: usize) -> LeafTensor {
279        // Transpose legs and shape of tensor
280        let half = tensor.legs().len() / 2;
281        let legs = tensor.legs()[half..]
282            .iter()
283            .chain(&tensor.legs()[..half])
284            .map(|l| l + leg_offset)
285            .collect();
286        let bond_dims = tensor.bond_dims()[half..]
287            .iter()
288            .chain(&tensor.bond_dims()[..half])
289            .copied()
290            .collect();
291
292        // Take actual adjoint of tensor data
293        let data = tensor.tensor_data().clone();
294        let data = data.adjoint();
295
296        LeafTensor::new_with_data(legs, bond_dims, data)
297    }
298
299    /// Converts the circuit to a tensor network that computes the expectation value
300    /// with respect to standard observables (`Z`) on all qubits.
301    ///
302    /// The tensor network is roughly twice the size of the circuit, as it needs to
303    /// compute the adjoint of the circuit as well.
304    pub fn into_expectation_value_network(mut self) -> CompositeTensor {
305        let offset = self.next_edge;
306        self.tensor_network
307            .reserve(self.tensor_network.len() + self.num_qubits());
308
309        // Add the mirrored tensor network
310        let mut adjoint_tensors = Vec::with_capacity(self.tensor_network.len());
311        for tensor in self.tensor_network.tensors() {
312            let tensor = tensor.as_leaf().unwrap();
313            let adjoint = Self::tensor_adjoint(tensor, offset);
314            adjoint_tensors.push(adjoint);
315        }
316        self.tensor_network.push_tensors(adjoint_tensors);
317
318        // Add the layer of observables
319        for e in self.open_edges {
320            let mut t = LeafTensor::new_from_const(vec![e, e + offset], 2);
321            t.set_tensor_data(Self::z());
322            self.tensor_network.push_tensor(t);
323        }
324
325        self.tensor_network
326    }
327}
328
329#[cfg(test)]
330mod tests {
331    use super::*;
332
333    use std::f64::consts::{FRAC_1_SQRT_2, FRAC_PI_3, FRAC_PI_4};
334
335    use approx::assert_abs_diff_eq;
336    use num_complex::Complex64;
337
338    use crate::{
339        contractionpath::paths::{
340            cotengrust::{Cotengrust, OptMethod},
341            ContractionPathResult, Pathfinder,
342        },
343        path,
344        tensornetwork::{contraction::contract_tensor_network, tensordata::TensorData},
345    };
346
347    fn test_permutation_between(given: &[usize], target: &[usize]) {
348        let perm = Permutor::permutation_between(given, target);
349        assert_eq!(perm.apply_slice(given), target);
350    }
351
352    #[test]
353    fn permutation_between() {
354        test_permutation_between(&[1, 2, 3, 4], &[1, 2, 3, 4]);
355        test_permutation_between(&[1, 2, 3, 4], &[4, 3, 2, 1]);
356        test_permutation_between(&[4, 3, 2, 1], &[1, 2, 3, 4]);
357        test_permutation_between(&[4, 1, 3, 2], &[2, 4, 3, 1]);
358        test_permutation_between(&[5, 1, 4, 3, 2, 6], &[1, 6, 3, 5, 2, 4]);
359    }
360
361    #[test]
362    fn hadamards_amplitude() {
363        let qubits = 5;
364        let mut circuit = Circuit::default();
365        let qr = circuit.allocate_register(qubits);
366        for q in qr.qubits() {
367            circuit.append_gate(TensorData::Gate((String::from("h"), vec![], false)), &[q]);
368        }
369        let (tensor_network, permutor) = circuit.into_amplitude_network("00000");
370        assert!(permutor.is_identity());
371
372        let mut opt = Cotengrust::new(OptMethod::Greedy);
373        let result = opt.find_path(&tensor_network);
374        let path = result.replace_path();
375
376        let result = contract_tensor_network(tensor_network, &path);
377
378        let mut tn_ref = LeafTensor::default();
379        tn_ref.set_tensor_data(TensorData::new_from_data(
380            &[],
381            vec![Complex64::new(FRAC_1_SQRT_2.powi(qubits as i32), 0.0)],
382        ));
383
384        assert_abs_diff_eq!(&result, &tn_ref);
385    }
386
387    #[test]
388    fn rx_expectation_value() {
389        let qubits = 2;
390        let mut circuit = Circuit::default();
391        let qr = circuit.allocate_register(qubits);
392        circuit.append_gate(
393            TensorData::Gate((String::from("rx"), vec![FRAC_PI_4], false)),
394            &[qr.qubit(0)],
395        );
396        circuit.append_gate(
397            TensorData::Gate((String::from("rx"), vec![FRAC_PI_3], false)),
398            &[qr.qubit(1)],
399        );
400        let tensor_network = circuit.into_expectation_value_network();
401
402        let mut opt = Cotengrust::new(OptMethod::Greedy);
403        let result = opt.find_path(&tensor_network);
404        let path = result.replace_path();
405
406        let result = contract_tensor_network(tensor_network, &path);
407
408        let mut tn_ref = LeafTensor::default();
409        tn_ref.set_tensor_data(TensorData::new_from_data(
410            &[],
411            vec![Complex64::new(FRAC_1_SQRT_2 * 0.5, 0.0)],
412        ));
413
414        assert_abs_diff_eq!(&result, &tn_ref);
415    }
416
417    #[test]
418    #[should_panic(expected = "Qubit arguments must be unique")]
419    fn duplicate_qubit_arg() {
420        let mut circuit = Circuit::default();
421        let qr = circuit.allocate_register(2);
422        circuit.append_gate(
423            TensorData::Gate((String::from("cx"), vec![], true)),
424            &[qr.qubit(1), qr.qubit(1)],
425        );
426    }
427
428    #[test]
429    fn dimension_order() {
430        let mut circuit = Circuit::default();
431        let qr = circuit.allocate_register(1);
432        circuit.append_gate(
433            TensorData::new_from_data(
434                &[2, 2],
435                vec![
436                    Complex64::new(1.0, 0.0),
437                    Complex64::new(2.0, 0.0),
438                    Complex64::new(3.0, 0.0),
439                    Complex64::new(4.0, 0.0),
440                ],
441            ),
442            &[qr.qubit(0)],
443        );
444        let (tensor_network, permutor) = circuit.into_statevector_network();
445        let result = contract_tensor_network(tensor_network, &path![(0, 1)]);
446        let result = permutor.apply(result);
447        let mut tn_ref = LeafTensor::new_from_const(vec![1], 2);
448        tn_ref.set_tensor_data(TensorData::new_from_data(
449            &[2],
450            vec![Complex64::new(1.0, 0.0), Complex64::new(3.0, 0.0)],
451        ));
452        assert_abs_diff_eq!(&result, &tn_ref);
453    }
454
455    #[test]
456    fn permute() {
457        let mut tensor = LeafTensor::new_from_const(vec![0, 1, 2], 2);
458        let data = (0..8).map(|i| Complex64::new(i as f64, 0.0)).collect();
459        tensor.set_tensor_data(TensorData::new_from_data(&[2, 2, 2], data));
460
461        let permutor = Permutor::new(vec![2, 0, 1]);
462        let permuted = permutor.apply(tensor);
463
464        let ref_data = [0, 2, 4, 6, 1, 3, 5, 7]
465            .into_iter()
466            .map(|i| Complex64::new(i as f64, 0.0))
467            .collect();
468        let mut expected = LeafTensor::new_from_const(vec![2, 0, 1], 2);
469        expected.set_tensor_data(TensorData::new_from_data(&[2, 2, 2], ref_data));
470
471        assert_abs_diff_eq!(&permuted, &expected);
472    }
473}