microsoft/qdk
Publicmirrored fromhttps://github.com/microsoft/qdkAvailable
samples/estimation/df-chemistry/src/df_chemistry.qs
359lines · modecode
| 1 | // Copyright (c) Microsoft Corporation. All rights reserved. |
| 2 | // Licensed under the MIT License. |
| 3 | namespace Microsoft.Quantum.Applications.Chemistry { |
| 4 | open Microsoft.Quantum.Arrays; |
| 5 | open Microsoft.Quantum.Canon; |
| 6 | open Microsoft.Quantum.Convert; |
| 7 | open Microsoft.Quantum.Diagnostics; |
| 8 | open Microsoft.Quantum.Math; |
| 9 | open Microsoft.Quantum.ResourceEstimation; |
| 10 | open Microsoft.Quantum.Unstable.Arithmetic; |
| 11 | open Microsoft.Quantum.Unstable.TableLookup; |
| 12 | |
| 13 | // ------------------------------------------ // |
| 14 | // DF chemistry (public operations and types) // |
| 15 | // ------------------------------------------ // |
| 16 | |
| 17 | /// # Summary |
| 18 | /// The description of a double-factorized Hamiltonian |
| 19 | /// |
| 20 | /// # Reference |
| 21 | /// arXiv:2007.14460, p. 9, eq. 9 |
| 22 | newtype DoubleFactorizedChemistryProblem = ( |
| 23 | // Number of orbitals (N, p. 8) |
| 24 | NumOrbitals : Int, |
| 25 | // one-body norm (ǁL⁽⁻¹⁾ǁ, p. 8, eq. 16) |
| 26 | OneBodyNorm : Double, |
| 27 | // one-body norm (¼∑ǁL⁽ʳ⁾ǁ², p. 8, eq. 16) |
| 28 | TwoBodyNorm : Double, |
| 29 | // eigenvalues in the EVD of the one-electron Hamiltonian (λₖ, p. 54, eq. 67) |
| 30 | OneBodyEigenValues : Double[], |
| 31 | // eigenvectors in the EVD of the one-electron Hamiltonian (Rₖ, p. 54, eq. 67) |
| 32 | OneBodyEigenVectors : Double[][], |
| 33 | // norms inside Λ_SH (p. 56, eq. 77) |
| 34 | Lambdas : Double[], |
| 35 | // eigenvalues in the EVDs of the two-electron Hamiltonian for all r (λₖ⁽ʳ⁾, p. 56, eq. 77) |
| 36 | TwoBodyEigenValues : Double[][], |
| 37 | // eigenvectors in the EVDs of the two-electron Hamiltonian for all r (R⁽ʳ⁾ₖ, p. 56, eq. 77) |
| 38 | TwoBodyEigenVectors : Double[][][], |
| 39 | ); |
| 40 | |
| 41 | newtype DoubleFactorizedChemistryParameters = ( |
| 42 | // Standard deviation (ΔE, p. 8, eq. 1) |
| 43 | // Typically set to 0.001 |
| 44 | StandardDeviation : Double, |
| 45 | ); |
| 46 | |
| 47 | /// # Summary |
| 48 | /// Performs quantum circuit in (p. 45, eq. 33) for double-factorized |
| 49 | /// Hamiltonian; also prepares phase gradient registers to use phase |
| 50 | /// gradient technique (p. 55) |
| 51 | operation DoubleFactorizedChemistry( |
| 52 | problem : DoubleFactorizedChemistryProblem, |
| 53 | parameters : DoubleFactorizedChemistryParameters |
| 54 | ) : Unit { |
| 55 | let constants = ComputeConstants(problem, parameters); |
| 56 | |
| 57 | use register0 = Qubit[problem::NumOrbitals]; |
| 58 | use register1 = Qubit[problem::NumOrbitals]; |
| 59 | use phaseGradientRegister = Qubit[constants::RotationAngleBitPrecision]; |
| 60 | |
| 61 | // compute number of repetitions |
| 62 | let norm = problem::OneBodyNorm + problem::TwoBodyNorm; |
| 63 | let repetitions = Ceiling((PI() * norm) / (2.0 * parameters::StandardDeviation)); |
| 64 | |
| 65 | let walkStep = MakeWalkStep(problem, constants); |
| 66 | use walkStepHelper = Qubit[walkStep::NGarbageQubits]; |
| 67 | |
| 68 | within { |
| 69 | X(phaseGradientRegister[0]); |
| 70 | Adjoint ApplyQFT(Reversed(phaseGradientRegister)); |
| 71 | } apply { |
| 72 | within { |
| 73 | RepeatEstimates(repetitions); |
| 74 | } apply { |
| 75 | walkStep::StepOp(register0, register1, phaseGradientRegister, walkStepHelper); |
| 76 | } |
| 77 | } |
| 78 | } |
| 79 | |
| 80 | // -------------------------------------------- // |
| 81 | // DF chemistry (internal operations and types) // |
| 82 | // -------------------------------------------- // |
| 83 | |
| 84 | /// # Summary |
| 85 | /// Constants that are used in the implementation of the one- and two- |
| 86 | /// electron operators, which are computed from the double factorized |
| 87 | /// problem and parameters. |
| 88 | internal newtype DoubleFactorizedChemistryConstants = ( |
| 89 | RotationAngleBitPrecision : Int, |
| 90 | StatePreparationBitPrecision : Int, |
| 91 | TargetError : Double |
| 92 | ); |
| 93 | |
| 94 | internal function ComputeConstants( |
| 95 | problem : DoubleFactorizedChemistryProblem, |
| 96 | parameters : DoubleFactorizedChemistryParameters |
| 97 | ) : DoubleFactorizedChemistryConstants { |
| 98 | let pj = 0.1; |
| 99 | let barEpsilon = Sqrt(pj); |
| 100 | let norm = problem::OneBodyNorm + problem::TwoBodyNorm; |
| 101 | let epsilon = 0.1 * parameters::StandardDeviation / norm; |
| 102 | |
| 103 | let RotationAngleBitPrecision = Ceiling(1.152 + Lg(Sqrt((IntAsDouble((problem::NumOrbitals - 1) * 8) * PI() * norm) / parameters::StandardDeviation)) + 0.5 * Lg(1.0 / barEpsilon)); |
| 104 | let StatePreparationBitPrecision = Ceiling(Lg(1.0 / epsilon) + 2.5); |
| 105 | let TargetError = 2.0^IntAsDouble(1 - StatePreparationBitPrecision); |
| 106 | |
| 107 | DoubleFactorizedChemistryConstants( |
| 108 | RotationAngleBitPrecision, |
| 109 | StatePreparationBitPrecision, |
| 110 | TargetError |
| 111 | ) |
| 112 | } |
| 113 | |
| 114 | internal newtype WalkStep = ( |
| 115 | NGarbageQubits : Int, |
| 116 | StepOp : (Qubit[], Qubit[], Qubit[], Qubit[]) => Unit |
| 117 | ); |
| 118 | |
| 119 | /// # Summary |
| 120 | /// Performs one B[H] and reflection (p. 45, eq. 33) |
| 121 | internal function MakeWalkStep( |
| 122 | problem : DoubleFactorizedChemistryProblem, |
| 123 | constants : DoubleFactorizedChemistryConstants |
| 124 | ) : WalkStep { |
| 125 | let oneElectronOperator = MakeOneElectronOperator(problem::OneBodyEigenValues, problem::OneBodyEigenVectors, constants); |
| 126 | let twoElectronOperator = MakeTwoElectronOperator(problem, constants); |
| 127 | |
| 128 | let NGarbageQubits = oneElectronOperator::NGarbageQubits + twoElectronOperator::NGarbageQubits; |
| 129 | |
| 130 | WalkStep(NGarbageQubits, WalkStepOperation(problem, oneElectronOperator, twoElectronOperator, _, _, _, _)) |
| 131 | } |
| 132 | |
| 133 | internal operation WalkStepOperation( |
| 134 | problem : DoubleFactorizedChemistryProblem, |
| 135 | oneElectronOperator : OneElectronOperator, |
| 136 | twoElectronOperator : TwoElectronOperator, |
| 137 | register0 : Qubit[], |
| 138 | register1 : Qubit[], |
| 139 | phaseGradientRegister : Qubit[], |
| 140 | helper : Qubit[] |
| 141 | ) : Unit { |
| 142 | use ctl = Qubit(); |
| 143 | |
| 144 | let helperParts = Partitioned([oneElectronOperator::NGarbageQubits, twoElectronOperator::NGarbageQubits], helper); |
| 145 | Fact(IsEmpty(Tail(helperParts)), "wrong number of helper qubits in WalkStepOperation"); |
| 146 | |
| 147 | // apply Hamiltionian |
| 148 | within { |
| 149 | PrepareSingleQubit(problem::OneBodyNorm, problem::TwoBodyNorm, ctl); |
| 150 | } apply { |
| 151 | within { X(ctl); } apply { |
| 152 | Controlled oneElectronOperator::Apply([ctl], (register0, register1, phaseGradientRegister, [], helperParts[0])); |
| 153 | } |
| 154 | Controlled twoElectronOperator::Apply([ctl], (register0, register1, phaseGradientRegister, helperParts[1])); |
| 155 | } |
| 156 | |
| 157 | // reflection |
| 158 | ReflectAboutInteger(0, helper); |
| 159 | } |
| 160 | |
| 161 | internal newtype OneElectronOperator = ( |
| 162 | NGarbageQubits : Int, |
| 163 | Apply : (Qubit[], Qubit[], Qubit[], Qubit[], Qubit[]) => Unit is Adj + Ctl |
| 164 | ); |
| 165 | |
| 166 | /// # Summary |
| 167 | /// Performs quantum circuit for one-electron operator (p. 54, eq. 70) |
| 168 | internal function MakeOneElectronOperator( |
| 169 | eigenValues : Double[], |
| 170 | eigenVectors : Double[][], |
| 171 | constants : DoubleFactorizedChemistryConstants |
| 172 | ) : OneElectronOperator { |
| 173 | let data = Mapped(eigenValue -> [eigenValue < 0.0], eigenValues); |
| 174 | let prepare = MakePrepareArbitrarySuperpositionWithData(constants::TargetError, eigenValues, data); |
| 175 | |
| 176 | OneElectronOperator( |
| 177 | prepare::NGarbageQubits, |
| 178 | OneElectronOperatorOperation(eigenVectors, constants, prepare, _, _, _, _, _) |
| 179 | ) |
| 180 | } |
| 181 | |
| 182 | internal operation OneElectronOperatorOperation( |
| 183 | eigenVectors : Double[][], |
| 184 | constants : DoubleFactorizedChemistryConstants, |
| 185 | prepare : PrepareArbitrarySuperposition, |
| 186 | register0 : Qubit[], |
| 187 | register1 : Qubit[], |
| 188 | phaseGradientRegister : Qubit[], |
| 189 | offsets : Qubit[], |
| 190 | helper : Qubit[] |
| 191 | ) : Unit is Adj + Ctl { |
| 192 | if BeginEstimateCaching("OneElectronOperator", IsEmpty(offsets) ? 0 | 1) { |
| 193 | // assertions |
| 194 | Fact(Length(helper) == prepare::NGarbageQubits, "invalid number of helper qubits in OneElectronOperatorOperation"); |
| 195 | |
| 196 | let precision = constants::RotationAngleBitPrecision; |
| 197 | let bitstrings = AllEigenVectorsAsBitString(eigenVectors, precision); |
| 198 | |
| 199 | use prepQubits = Qubit[prepare::NIndexQubits]; |
| 200 | use rotationQubits = Qubit[Length(Head(bitstrings))]; |
| 201 | use sign = Qubit(); |
| 202 | use spin = Qubit(); |
| 203 | |
| 204 | within { |
| 205 | if not IsEmpty(offsets) { |
| 206 | prepare::PrepareWithSelect(SelectWithOffset(_, offsets, _, _), prepQubits, [sign], helper); |
| 207 | } else { |
| 208 | prepare::Prepare(prepQubits, [sign], helper); |
| 209 | } |
| 210 | H(spin); |
| 211 | for i in IndexRange(register0) { |
| 212 | Controlled SWAP([spin], (register0[i], register1[i])); |
| 213 | } |
| 214 | if not IsEmpty(offsets) { |
| 215 | RippleCarryCGIncByLE(offsets, prepQubits); |
| 216 | } |
| 217 | Select(bitstrings, prepQubits, rotationQubits); |
| 218 | } apply { |
| 219 | ApplyGivensRotations(phaseGradientRegister, Chunks(precision, rotationQubits), register0); |
| 220 | Z(sign); |
| 221 | } |
| 222 | |
| 223 | EndEstimateCaching(); |
| 224 | } |
| 225 | } |
| 226 | |
| 227 | internal operation SelectWithOffset(data : Bool[][], offset : Qubit[], address : Qubit[], target : Qubit[]) : Unit is Adj + Ctl { |
| 228 | within { |
| 229 | RippleCarryCGIncByLE(offset, address); |
| 230 | } apply { |
| 231 | Select(data, address, target); |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | internal newtype TwoElectronOperator = ( |
| 236 | NGarbageQubits : Int, |
| 237 | Apply : (Qubit[], Qubit[], Qubit[], Qubit[]) => Unit is Ctl |
| 238 | ); |
| 239 | |
| 240 | /// # Summary |
| 241 | /// Performs quantum circuit for two-electron operator (p. 57, eq. 78) |
| 242 | internal function MakeTwoElectronOperator(problem : DoubleFactorizedChemistryProblem, constants : DoubleFactorizedChemistryConstants) : TwoElectronOperator { |
| 243 | let lambdaPrepare = MakePrepareArbitrarySuperposition( |
| 244 | constants::TargetError, |
| 245 | problem::Lambdas |
| 246 | ); |
| 247 | |
| 248 | let eigenValuesFlattened = Flattened(problem::TwoBodyEigenValues); |
| 249 | let eigenVectorsFlattened = Flattened(problem::TwoBodyEigenVectors); |
| 250 | let oneElectronOperator = MakeOneElectronOperator(eigenValuesFlattened, eigenVectorsFlattened, constants); |
| 251 | |
| 252 | let numGarbageQubits = lambdaPrepare::NGarbageQubits + oneElectronOperator::NGarbageQubits; |
| 253 | |
| 254 | TwoElectronOperator(numGarbageQubits, TwoElectronOperatorOperation(problem, lambdaPrepare, oneElectronOperator, _, _, _, _)) |
| 255 | } |
| 256 | |
| 257 | internal operation TwoElectronOperatorOperation( |
| 258 | problem : DoubleFactorizedChemistryProblem, |
| 259 | lambdaPrepare : PrepareArbitrarySuperposition, |
| 260 | oneElectronOperator : OneElectronOperator, |
| 261 | register0 : Qubit[], |
| 262 | register1 : Qubit[], |
| 263 | phaseGradientRegister : Qubit[], |
| 264 | helper : Qubit[] |
| 265 | ) : Unit is Ctl { |
| 266 | let helperParts = Partitioned([lambdaPrepare::NGarbageQubits], helper); |
| 267 | let dataOffsets = ComputeOffsetDataSet(Mapped(Length, problem::TwoBodyEigenValues)); |
| 268 | |
| 269 | use rankQubits = Qubit[lambdaPrepare::NIndexQubits]; |
| 270 | use offsetQubits = Qubit[Length(dataOffsets[0])]; |
| 271 | |
| 272 | within { |
| 273 | lambdaPrepare::Prepare(rankQubits, [], helperParts[0]); |
| 274 | Select(dataOffsets, rankQubits, offsetQubits); |
| 275 | oneElectronOperator::Apply(register0, register1, phaseGradientRegister, offsetQubits, helperParts[1]); |
| 276 | } apply { |
| 277 | ReflectAboutInteger(0, helperParts[1]); |
| 278 | } |
| 279 | } |
| 280 | |
| 281 | internal function ComputeOffsetDataSet(lengths : Int[]) : Bool[][] { |
| 282 | mutable currentOffset = 0; |
| 283 | mutable offsets = [0, size = Length(lengths)]; |
| 284 | |
| 285 | for i in IndexRange(lengths) { |
| 286 | set offsets w/= i <- currentOffset; |
| 287 | set currentOffset += lengths[i]; |
| 288 | } |
| 289 | |
| 290 | let offsetWidth = Ceiling(Lg(IntAsDouble(currentOffset))); |
| 291 | return Mapped(IntAsBoolArray(_, offsetWidth), offsets); |
| 292 | } |
| 293 | |
| 294 | /// # Summary |
| 295 | /// Performs quantum circuit for phase rotations (p. 54, eq. 73) |
| 296 | internal operation ApplyGivensRotations(pgr : Qubit[], rotationQubits : Qubit[][], target : Qubit[]) : Unit is Adj + Ctl { |
| 297 | within { |
| 298 | let windows = Windows(2, target); |
| 299 | for i in RangeReverse(IndexRange(rotationQubits)) { |
| 300 | let rotationControls = rotationQubits[i]; |
| 301 | let qs = windows[i]; |
| 302 | ApplyWithMajoranaCliffords(1, ApplyRotationUsingRippleCarryAddition(pgr, rotationControls, _), qs); |
| 303 | ApplyWithMajoranaCliffords(0, Adjoint ApplyRotationUsingRippleCarryAddition(pgr, rotationControls, _), qs); |
| 304 | } |
| 305 | } apply { |
| 306 | X(Head(target)); |
| 307 | Y(Head(target)); |
| 308 | } |
| 309 | } |
| 310 | |
| 311 | internal operation ApplyRotationUsingRippleCarryAddition(pgr : Qubit[], rotationQubits : Qubit[], target : Qubit) : Unit is Adj { |
| 312 | Controlled Adjoint RippleCarryCGIncByLE([target], (Reversed(Rest(rotationQubits)), pgr)); |
| 313 | } |
| 314 | |
| 315 | internal operation ApplyWithMajoranaCliffords(x : Int, op : (Qubit => Unit is Adj), qs : Qubit[]) : Unit is Adj { |
| 316 | // x is the Majorana type (which can be either 0 or 1, see p. 52, eq. 59) |
| 317 | within { |
| 318 | Adjoint S(qs[x]); |
| 319 | H(qs[x]); |
| 320 | H(qs[1 - x]); |
| 321 | CNOT(qs[1], qs[0]); |
| 322 | } apply { |
| 323 | op(qs[0]); |
| 324 | } |
| 325 | } |
| 326 | |
| 327 | internal function AllEigenVectorsAsBitString(eigenVectors : Double[][], precision : Int) : Bool[][] { |
| 328 | mutable bitstrings = []; |
| 329 | |
| 330 | let tau = 2.0 * PI(); |
| 331 | let preFactor = 2.0^IntAsDouble(precision); |
| 332 | |
| 333 | for eigenVector in eigenVectors { |
| 334 | // Computes rotation angles for Majorana operator ($\vec u$ in p. 52, eq. 55) |
| 335 | mutable result = []; |
| 336 | mutable sins = 1.0; |
| 337 | |
| 338 | for index in 0..Length(eigenVector) - 2 { |
| 339 | // We apply MinD, such that rounding errors do not lead to |
| 340 | // an argument for ArcCos which is larger than 1.0. (p. 52, eq. 56) |
| 341 | let theta = sins == 0.0 ? 0.0 | 0.5 * ArcCos(MinD(eigenVector[index] / sins, 1.0)); |
| 342 | |
| 343 | // all angles as bit string |
| 344 | let factor = theta / tau; |
| 345 | set result += Reversed(IntAsBoolArray(IsNaN(factor) ? 0 | Floor(preFactor * factor), precision)); |
| 346 | |
| 347 | set sins *= Sin(2.0 * theta); |
| 348 | } |
| 349 | |
| 350 | set bitstrings += [result]; |
| 351 | } |
| 352 | |
| 353 | bitstrings |
| 354 | } |
| 355 | |
| 356 | internal function IsNaN(value : Double) : Bool { |
| 357 | value != value |
| 358 | } |
| 359 | } |
| 360 | |