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