|
| 1 | +// Copyright (c) Microsoft Corporation. |
| 2 | +// Licensed under the MIT License. |
| 3 | + |
| 4 | +export GeneratorIndex; |
| 5 | +export GeneratorSystem; |
| 6 | +export EvolutionGenerator; |
| 7 | +export HTermToGenIdx; |
| 8 | +export HTermsToGenSys; |
| 9 | +export HTermsToGenIdx; |
| 10 | +export MultiplexOperationsFromGenerator; |
| 11 | + |
| 12 | +import Std.Math.*; |
| 13 | +import Std.Arrays.*; |
| 14 | + |
| 15 | +/// # Summary |
| 16 | +/// Represents a single primitive term in the set of all dynamical generators, e.g. |
| 17 | +/// Hermitian operators, for which there exists a map from that generator |
| 18 | +/// to time-evolution by that generator, through an evolution set function. |
| 19 | +/// |
| 20 | +/// The first element |
| 21 | +/// (Int[], Double[]) is indexes that single term -- For instance, the Pauli string |
| 22 | +/// XXY with coefficient 0.5 would be indexed by ([1,1,2], [0.5]). Alternatively, |
| 23 | +/// Hamiltonians parameterized by a continuous variable, such as X cos φ + Y sin φ, |
| 24 | +/// might for instance be represented by ([], [φ]). The second |
| 25 | +/// element indexes the subsystem on which the generator acts on. |
| 26 | +/// |
| 27 | +/// # Remarks |
| 28 | +/// > [!WARNING] |
| 29 | +/// > The interpretation of an `GeneratorIndex` is not defined except |
| 30 | +/// > with reference to a particular set of generators. |
| 31 | +/// |
| 32 | +/// # Example |
| 33 | +/// Using Pauli evolution set, the operator |
| 34 | +/// $\pi X_2 X_5 Y_9$ is represented as: |
| 35 | +/// ```qsharp |
| 36 | +/// let index = new GeneratorIndex { |
| 37 | +/// Term = ([1, 1, 2], [PI()]), |
| 38 | +/// Subsystem = [2, 5, 9] |
| 39 | +/// }; |
| 40 | +/// ``` |
| 41 | +struct GeneratorIndex { |
| 42 | + Term : (Int[], Double[]), |
| 43 | + Subsystem : Int[], |
| 44 | +} |
| 45 | + |
| 46 | +/// # Summary |
| 47 | +/// Represents a collection of `GeneratorIndex`es. |
| 48 | +/// |
| 49 | +/// We iterate over this |
| 50 | +/// collection using a single-index integer, and the size of the |
| 51 | +/// collection is assumed to be known. |
| 52 | +struct GeneratorSystem { |
| 53 | + NumEntries : Int, |
| 54 | + EntryAt : (Int -> GeneratorIndex) |
| 55 | +} |
| 56 | + |
| 57 | +/// # Summary |
| 58 | +/// Represents a dynamical generator as a set of simulatable gates and |
| 59 | +/// an expansion in terms of that basis. |
| 60 | +/// |
| 61 | +/// Last parameter for number of terms. |
| 62 | +struct EvolutionGenerator { |
| 63 | + EvolutionSet : GeneratorIndex -> (Double, Qubit[]) => Unit is Adj + Ctl, |
| 64 | + System : GeneratorSystem |
| 65 | +} |
| 66 | + |
| 67 | +/// # Summary |
| 68 | +/// Converts a Hamiltonian term to a GeneratorIndex. |
| 69 | +/// |
| 70 | +/// # Input |
| 71 | +/// ## term |
| 72 | +/// Input data in `(Int[], Double[])` format. |
| 73 | +/// ## termType |
| 74 | +/// Additional information added to GeneratorIndex. |
| 75 | +/// |
| 76 | +/// # Output |
| 77 | +/// A GeneratorIndex representing a Hamiltonian term represented by `term`, |
| 78 | +/// together with additional information added by `termType`. |
| 79 | +function HTermToGenIdx(term : (Int[], Double[]), termType : Int[]) : GeneratorIndex { |
| 80 | + let (idxFermions, coeff) = term; |
| 81 | + new GeneratorIndex { Term = (termType, coeff), Subsystem = idxFermions } |
| 82 | +} |
| 83 | + |
| 84 | + |
| 85 | +/// # Summary |
| 86 | +/// Converts an index to a Hamiltonian term in `(Int[], Double[])[]` data format to a GeneratorIndex. |
| 87 | +/// |
| 88 | +/// # Input |
| 89 | +/// ## data |
| 90 | +/// Input data in `(Int[], Double[])[]` format. |
| 91 | +/// ## termType |
| 92 | +/// Additional information added to GeneratorIndex. |
| 93 | +/// ## idx |
| 94 | +/// Index to a term of the Hamiltonian |
| 95 | +/// |
| 96 | +/// # Output |
| 97 | +/// A GeneratorIndex representing a Hamiltonian term represented by `data[idx]`, |
| 98 | +/// together with additional information added by `termType`. |
| 99 | +function HTermsToGenIdx(data : (Int[], Double[])[], termType : Int[], idx : Int) : GeneratorIndex { |
| 100 | + return HTermToGenIdx(data[idx], termType); |
| 101 | +} |
| 102 | + |
| 103 | + |
| 104 | +/// # Summary |
| 105 | +/// Converts a Hamiltonian in `(Int[], Double[])[]` data format to a GeneratorSystem. |
| 106 | +/// |
| 107 | +/// # Input |
| 108 | +/// ## data |
| 109 | +/// Input data in `(Int[], Double[])[]` format. |
| 110 | +/// ## termType |
| 111 | +/// Additional information added to GeneratorIndex. |
| 112 | +/// |
| 113 | +/// # Output |
| 114 | +/// A GeneratorSystem representing a Hamiltonian represented by the input `data`. |
| 115 | +function HTermsToGenSys(data : (Int[], Double[])[], termType : Int[]) : GeneratorSystem { |
| 116 | + new GeneratorSystem { NumEntries = Length(data), EntryAt = HTermsToGenIdx(data, termType, _) } |
| 117 | +} |
| 118 | + |
| 119 | + |
| 120 | +/// # Summary |
| 121 | +/// Applies a multiply-controlled unitary operation $U$ that applies a |
| 122 | +/// unitary $V_j$ when controlled by n-qubit number state $\ket{j}$. |
| 123 | +/// |
| 124 | +/// $U = \sum^{N-1}_{j=0}\ket{j}\bra{j}\otimes V_j$. |
| 125 | +/// |
| 126 | +/// # Input |
| 127 | +/// ## unitaryGenerator |
| 128 | +/// A tuple where the first element `Int` is the number of unitaries $N$, |
| 129 | +/// and the second element `(Int -> ('T => () is Adj + Ctl))` |
| 130 | +/// is a function that takes an integer $j$ in $[0,N-1]$ and outputs the unitary |
| 131 | +/// operation $V_j$. |
| 132 | +/// |
| 133 | +/// ## index |
| 134 | +/// $n$-qubit control register that encodes number states $\ket{j}$ in |
| 135 | +/// little-endian format. |
| 136 | +/// |
| 137 | +/// ## target |
| 138 | +/// Generic qubit register that $V_j$ acts on. |
| 139 | +/// |
| 140 | +/// # Remarks |
| 141 | +/// `coefficients` will be padded with identity elements if |
| 142 | +/// fewer than $2^n$ are specified. This implementation uses |
| 143 | +/// $n-1$ auxiliary qubits. |
| 144 | +/// |
| 145 | +/// # References |
| 146 | +/// - [ *Andrew M. Childs, Dmitri Maslov, Yunseong Nam, Neil J. Ross, Yuan Su*, |
| 147 | +/// arXiv:1711.10980](https://arxiv.org/abs/1711.10980) |
| 148 | +operation MultiplexOperationsFromGenerator<'T>(unitaryGenerator : (Int, (Int -> ('T => Unit is Adj + Ctl))), index : Qubit[], target : 'T) : Unit is Ctl + Adj { |
| 149 | + let (nUnitaries, unitaryFunction) = unitaryGenerator; |
| 150 | + let unitaryGeneratorWithOffset = (nUnitaries, 0, unitaryFunction); |
| 151 | + if Length(index) == 0 { |
| 152 | + fail "MultiplexOperations failed. Number of index qubits must be greater than 0."; |
| 153 | + } |
| 154 | + if nUnitaries > 0 { |
| 155 | + let auxiliary = []; |
| 156 | + Adjoint MultiplexOperationsFromGeneratorImpl(unitaryGeneratorWithOffset, auxiliary, index, target); |
| 157 | + } |
| 158 | +} |
| 159 | + |
| 160 | +/// # Summary |
| 161 | +/// Implementation step of `MultiplexOperationsFromGenerator`. |
| 162 | +operation MultiplexOperationsFromGeneratorImpl<'T>(unitaryGenerator : (Int, Int, (Int -> ('T => Unit is Adj + Ctl))), auxiliary : Qubit[], index : Qubit[], target : 'T) : Unit { |
| 163 | + body (...) { |
| 164 | + let nIndex = Length(index); |
| 165 | + let nStates = 2^nIndex; |
| 166 | + |
| 167 | + let (nUnitaries, unitaryOffset, unitaryFunction) = unitaryGenerator; |
| 168 | + |
| 169 | + let nUnitariesLeft = MinI(nUnitaries, nStates / 2); |
| 170 | + let nUnitariesRight = MinI(nUnitaries, nStates); |
| 171 | + |
| 172 | + let leftUnitaries = (nUnitariesLeft, unitaryOffset, unitaryFunction); |
| 173 | + let rightUnitaries = (nUnitariesRight - nUnitariesLeft, unitaryOffset + nUnitariesLeft, unitaryFunction); |
| 174 | + |
| 175 | + let newControls = Most(index); |
| 176 | + |
| 177 | + if nUnitaries > 0 { |
| 178 | + if Length(auxiliary) == 1 and nIndex == 0 { |
| 179 | + // Termination case |
| 180 | + |
| 181 | + (Controlled Adjoint (unitaryFunction(unitaryOffset)))(auxiliary, target); |
| 182 | + } elif Length(auxiliary) == 0 and nIndex >= 1 { |
| 183 | + // Start case |
| 184 | + let newauxiliary = Tail(index); |
| 185 | + if nUnitariesRight > 0 { |
| 186 | + MultiplexOperationsFromGeneratorImpl(rightUnitaries, [newauxiliary], newControls, target); |
| 187 | + } |
| 188 | + within { |
| 189 | + X(newauxiliary); |
| 190 | + } apply { |
| 191 | + MultiplexOperationsFromGeneratorImpl(leftUnitaries, [newauxiliary], newControls, target); |
| 192 | + } |
| 193 | + } else { |
| 194 | + // Recursion that reduces nIndex by 1 and sets Length(auxiliary) to 1. |
| 195 | + let controls = [Tail(index)] + auxiliary; |
| 196 | + use newauxiliary = Qubit(); |
| 197 | + use andauxiliary = Qubit[MaxI(0, Length(controls) - 2)]; |
| 198 | + within { |
| 199 | + if Length(controls) == 0 { |
| 200 | + X(newauxiliary); |
| 201 | + } elif Length(controls) == 1 { |
| 202 | + CNOT(Head(controls), newauxiliary); |
| 203 | + } else { |
| 204 | + let controls1 = controls[0..0] + andauxiliary; |
| 205 | + let controls2 = Rest(controls); |
| 206 | + let targets = andauxiliary + [newauxiliary]; |
| 207 | + for i in IndexRange(controls1) { |
| 208 | + AND(controls1[i], controls2[i], targets[i]); |
| 209 | + } |
| 210 | + } |
| 211 | + |
| 212 | + } apply { |
| 213 | + if nUnitariesRight > 0 { |
| 214 | + MultiplexOperationsFromGeneratorImpl(rightUnitaries, [newauxiliary], newControls, target); |
| 215 | + } |
| 216 | + within { |
| 217 | + (Controlled X)(auxiliary, newauxiliary); |
| 218 | + } apply { |
| 219 | + MultiplexOperationsFromGeneratorImpl(leftUnitaries, [newauxiliary], newControls, target); |
| 220 | + } |
| 221 | + } |
| 222 | + } |
| 223 | + } |
| 224 | + } |
| 225 | + adjoint auto; |
| 226 | + controlled (controlRegister, ...) { |
| 227 | + MultiplexOperationsFromGeneratorImpl(unitaryGenerator, auxiliary + controlRegister, index, target); |
| 228 | + } |
| 229 | + controlled adjoint auto; |
| 230 | +} |
0 commit comments