diff --git a/samples/chemistry/FermionicSwap/COPYRIGHT.txt b/samples/chemistry/FermionicSwap/COPYRIGHT.txt new file mode 100644 index 00000000000..b644aabc084 --- /dev/null +++ b/samples/chemistry/FermionicSwap/COPYRIGHT.txt @@ -0,0 +1,9 @@ +Copyright 2023 Battelle Memorial Institute + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/samples/chemistry/FermionicSwap/DISCLAIMER.txt b/samples/chemistry/FermionicSwap/DISCLAIMER.txt new file mode 100644 index 00000000000..82318b6a3d8 --- /dev/null +++ b/samples/chemistry/FermionicSwap/DISCLAIMER.txt @@ -0,0 +1,8 @@ +This material was prepared as an account of work sponsored by an agency of the United States Government. Neither the United States Government nor the United States Department of Energy, nor Battelle, nor any of their employees, nor any jurisdiction or organization that has cooperated in the development of these materials, makes any warranty, express or implied, or assumes any legal liability or responsibility for the accuracy, completeness, or usefulness or any information, apparatus, product, software, or process disclosed, or represents that its use would not infringe privately owned rights. +Reference herein to any specific commercial product, process, or service by trade name, trademark, manufacturer, or otherwise does not necessarily constitute or imply its endorsement, recommendation, or favoring by the United States Government or any agency thereof, or Battelle Memorial Institute. The views and opinions of authors expressed herein do not necessarily state or reflect those of the United States Government or any agency thereof. +PACIFIC NORTHWEST NATIONAL LABORATORY +operated by +BATTELLE +for the +UNITED STATES DEPARTMENT OF ENERGY +under Contract DE-AC05-76RL01830 diff --git a/samples/chemistry/FermionicSwap/FermionicSwap.QSharp/FermionicSwap.QSharp.csproj b/samples/chemistry/FermionicSwap/FermionicSwap.QSharp/FermionicSwap.QSharp.csproj new file mode 100644 index 00000000000..b6bec602636 --- /dev/null +++ b/samples/chemistry/FermionicSwap/FermionicSwap.QSharp/FermionicSwap.QSharp.csproj @@ -0,0 +1,11 @@ + + + + net6.0 + + + + + + + diff --git a/samples/chemistry/FermionicSwap/FermionicSwap.QSharp/FermionicSwap.qs b/samples/chemistry/FermionicSwap/FermionicSwap.QSharp/FermionicSwap.qs new file mode 100644 index 00000000000..0ec83128044 --- /dev/null +++ b/samples/chemistry/FermionicSwap/FermionicSwap.QSharp/FermionicSwap.qs @@ -0,0 +1,283 @@ +// Copyright Battelle Memorial Institute 2022. All rights reserved. + +// Q# functions for Fermionic Swap. + +// Code rationale: the code that creates swap networks uses +// dictionaries, which are not available in Q#. The eventual goal is to call +// the needed C# functions from Q#, but for now everything is driven from C#. +// These functions take a data structure produced by C# code and use it to perform +// evolution of Jordan-Wigner represented fermions using swap networks. + +namespace FermionicSwap +{ + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Chemistry.JordanWigner; + open Microsoft.Quantum.Simulation; + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Convert; + open Microsoft.Quantum.Arrays; + + /// # Summary + /// Swap two qubits and apply -1 phase if both are in occupied states. + /// Effectively permutes pairwise elements in the Jordan-Wigner ordering. + /// + /// # Input + /// ## a + /// A Qubit. + /// ## b + /// Another Qubit. Should be adjacent to the first qubit in the Jordan-Wigner ordering. + operation FermionicSwap(a : Qubit, b : Qubit) : Unit is Adj + Ctl { + SWAP(a,b); + CZ(a,b); + } + + /// # Summary + /// Apply a sequence of fermionic swaps. + /// + /// # Input + /// ## swaps : An array of pairs of qubit indices to swap, + /// ## qubits: The qubits encoding the state, in the Jordan-Wigner representation. + operation FermionicSwapLayer( swaps : (Int,Int)[], qubits : Qubit[]) : Unit is Adj + Ctl { + for (a,b) in swaps { + FermionicSwap(qubits[a],qubits[b]); + } + } + + /// # Summary + /// ## Apply Fermionic Swap Trotter steps to qubits. + /// + /// # Input + /// ## generator + /// an EvolutionGenerator which describes a Fermionic Swap Trotter step, + /// ## trotterStepSize + /// Time duration of a single Trotter step, + /// ## time + /// Total duration of evolution, + /// ## register + /// The qubits to be operated upon. + /// + /// # Remarks + /// Changes the Jordan-Wigner ordering if evolution requires an odd number + /// of time steps. + operation FermionicSwapEvolveUnderGenerator( + generator : EvolutionGenerator, + trotterStepSize : Double, + time : Double, + register : Qubit[] + ) : Unit is Adj + Ctl { + let evolveFor = (FermionicSwapSimulationAlgorithm(trotterStepSize))!; + evolveFor(time, generator, register); + } + + /// # Summary + /// Apply a single fermionic swap Trotter step. + /// + /// # Input + /// ## swapNetwork + /// The swaps to be performed. An array of arrays, one for each layer. + /// ## localEvolutions + /// Local evolutions to be performed between swap layers. Each evolution + /// is a JWOptimizedHTerms object, and each local evolution layer is an + /// array of such to keep Q# from optimizing. If Q# optimization is + /// desired, the layer may be specified as a length one array with all + /// evolutions combined in a single JWOptimizedHTerms object. + /// ## time + /// The duration of the Trotter step. + /// ## qubits + /// The qubits to be acted upon. + /// + /// # Remarks + /// Changes the Jordan-Wigner ordering. Applying again with layers reversed + /// restores the original Jordan-Wigner ordering. + operation FermionicSwapTrotterStep( + swapNetwork : (Int,Int)[][], + localEvolutions : JWOptimizedHTerms[][], + time : Double, + qubits : Qubit[]) : Unit + { + let nTerms = Length(qubits); + for i in 0 .. Length(swapNetwork) { + for ops in localEvolutions[i] { + mutable empty = true; + let (opa,opb,opc,opd) = ops!; + if Length(opa) > 0 or Length(opb) > 0 or Length(opc) > 0 or Length(opd) > 0 { + set empty = false; + } + if (not empty) { + let generatorSystem = JordanWignerGeneratorSystem(ops); + let evolutionGenerator = EvolutionGenerator(JordanWignerFermionEvolutionSet(), generatorSystem); + TrotterStep(evolutionGenerator, 1, time)(qubits); + } + } + if i < Length(swapNetwork) { + FermionicSwapLayer(swapNetwork[i], qubits); + } + } + } + + /// # Summary + /// Internal implementation of single layer for a fermionic swap + /// Hamiltonian evolution Trotter step. + /// Trotterized swap network. + /// + /// # Input + /// ## stepSize + /// Duration of a Trotter step. + /// ## time + /// Duration of the evolution. + /// ## generator + /// An EvolutionGenerator. + /// ## qubits + /// The qubits in the system to be acted upon. + operation FermionicSwapEvolutionImpl( + swapNetwork : (Int,Int)[][], + localEvolutions : JWOptimizedHTerms[][], + generatorIndex : GeneratorIndex, + time : Double, + qubits : Qubit[] + ) : Unit is Adj + Ctl{ + body (...) { + let ((indices, _), _) = generatorIndex!; + let index = indices[0]; + let gi = (index-1) / 2; + if index % 2 != 0 { + for ops in localEvolutions[gi] { + let (opa,opb,opc,opd) = ops!; + if (Length(opa) > 0 or Length(opb) > 0 or Length(opc) > 0 or Length(opd) > 0) { + let generatorSystem = JordanWignerGeneratorSystem(ops); + let evolutionGenerator = EvolutionGenerator(JordanWignerFermionEvolutionSet(), generatorSystem); + TrotterStep(evolutionGenerator, 1, time)(qubits); + } + } + } else { + FermionicSwapLayer(swapNetwork[gi], qubits); + } + } + } + + /// # Summary + /// Create an EvolutionFunction that evolves a single swap or Hamiltonian + /// layer in a swap network. An EvolutionGenerator uses these to perform + /// a Trotter step. + /// + /// # Input + /// ## swapNetwork + /// The network of layers of swaps to perform. + /// ## localEvolutions + /// Local evolutions to be performed between swap layers. + /// ## generatorIndex + /// An index indicating the swap or Hamiltonian interaction layer to evolve. + /// + /// # Output + /// An EvolutionFunction that evolves the layer. + function FermionicSwapEvolutionFunction( + swapNetwork : (Int,Int)[][], + localEvolutions : JWOptimizedHTerms[][], + generatorIndex : GeneratorIndex + ) : EvolutionUnitary { + return EvolutionUnitary(FermionicSwapEvolutionImpl(swapNetwork, localEvolutions, generatorIndex, _, _)); + } + + /// # Summary + /// Return an EvolutionSet for a swap network. + /// + /// # Input + /// ## swapNetwork + /// The fermionic swaps to be performed. + /// ## localEvolutions + /// Local evolutions to be performed between swap layers. + /// + /// # Output + /// An EvolutionSet which converts indices to layer evolutions. + function FermionicSwapEvolutionSet( + swapNetwork : (Int,Int)[][], + localEvolutions : JWOptimizedHTerms[][] + ) : EvolutionSet { + return EvolutionSet(FermionicSwapEvolutionFunction(swapNetwork, localEvolutions, _)); + } + + /// # Summary + /// Return a GeneratorSystem for a fermionic swap network + /// + /// # Inputs + /// ## Size + /// The size of the system. Should be twice the number of swap layers, plus + /// one. + /// + /// # Output + /// A GeneratorSystem. + function FermionicSwapGeneratorSystem( + size : Int + ) : GeneratorSystem { + return GeneratorSystem(size, s -> GeneratorIndex(([s], []), [])); + } + + /// # Summary + /// Internal implementation of timed evolution of a Hamiltonian through + /// Trotterized swap network. + /// + /// # Input + /// ## stepSize + /// Duration of a Trotter step. + /// ## time + /// Duration of the evolution. + /// ## generator + /// An EvolutionGenerator. + /// ## qubits + /// The qubits in the system to be acted upon. + operation FermionicSwapSimulationAlgorithmImpl( + stepSize : Double, + time : Double, + generator : EvolutionGenerator, + qubits : Qubit[] + ) : Unit is Adj + Ctl { + let (evoSet, genSys) = generator!; + let (numTerms, termDict) = genSys!; + let timeSteps = Ceiling(time / stepSize); + for i in 1..timeSteps { + let thisTime = (i + + + net6.0 + + + + + + + + + + + diff --git a/samples/chemistry/FermionicSwap/FermionicSwap.Tests.QSharp/QSharp.qs b/samples/chemistry/FermionicSwap/FermionicSwap.Tests.QSharp/QSharp.qs new file mode 100644 index 00000000000..5e174e62e78 --- /dev/null +++ b/samples/chemistry/FermionicSwap/FermionicSwap.Tests.QSharp/QSharp.qs @@ -0,0 +1,79 @@ +// Copyright Battelle Memorial Institute 2022. All rights reserved. + +// QSharp unit tests for Fermionic Swap QSharp code. +// These are tests are driven from the C# unit tests; +// See notes in FermionicSwap.qs for rationale. +namespace FermionicSwap.Tests { + open Microsoft.Quantum.Simulation; + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Chemistry; + open Microsoft.Quantum.Chemistry.JordanWigner; + open Microsoft.Quantum.Arrays; + open FermionicSwap; + + // We open the diagnostics namespace under an alias to help avoid + // conflicting with deprecation stubs in Microsoft.Quantum.Canon. + open Microsoft.Quantum.Diagnostics as Diag; + + // This test as currently written will only work for Hamiltonians with a + // single summand due to Trotter summand reordering issues. + operation SwapNetworkOneSummandTestOp(swapNetwork : (Int,Int)[][], + qsharpNetworkData : JWOptimizedHTerms[][], + qsharpHamiltonian : JWOptimizedHTerms, + numQubits : Int + ) : Unit { + let time = 1.0; + Diag.AssertOperationsEqualReferenced(numQubits, + _FixedOrderFermionicSwapTrotterStep(swapNetwork, qsharpNetworkData, time, _), + _JordanWignerApplyTrotterStep(qsharpHamiltonian, time, _ ) + ); + } + + // Perform trotter evolution with straight Jordan-Wigner evolution, and + // using Fermionic swap network. These are only the same in the + // small stepSize limit. + operation SwapNetworkEvolutionTestOp( + swapNetwork : (Int,Int)[][], + qsharpNetworkData : JWOptimizedHTerms[][], + qsharpHamiltonian : JWOptimizedHTerms, + numQubits : Int, + stepSize : Double, + time : Double + ) : Unit { + let generatorSystem = JordanWignerGeneratorSystem(qsharpHamiltonian); + let jwGenerator = EvolutionGenerator(JordanWignerFermionEvolutionSet(), generatorSystem); + let fsGenerator = FermionicSwapEvolutionGenerator(swapNetwork, qsharpNetworkData); + Diag.AssertOperationsEqualReferenced(numQubits, + FermionicSwapEvolveUnderGenerator(fsGenerator, stepSize, time, _), + _EvolveUnderGenerator(jwGenerator, stepSize, time,_ ) + ); + } + + // Copied from a QDK example + operation _EvolveUnderGenerator(generator : EvolutionGenerator, trotterStepSize : Double, time : Double, register : Qubit[]) + : Unit is Adj + Ctl { + let trotterOrder = 1; + let evolveFor = (TrotterSimulationAlgorithm(trotterStepSize, trotterOrder))!; + evolveFor(time, generator, register); + } + + + operation _FixedOrderFermionicSwapTrotterStep(swapNetwork : (Int,Int)[][], + qsharpNetworkData : JWOptimizedHTerms[][], + time : Double, qubits : Qubit[]) : Unit { + FermionicSwapTrotterStep(swapNetwork, qsharpNetworkData, time, qubits); + let empty = new JWOptimizedHTerms[][Length(swapNetwork)+1]; + FermionicSwapTrotterStep(Reversed(swapNetwork), empty, 0.0, qubits); + } + + operation _JordanWignerApplyTrotterStep (data : JWOptimizedHTerms, trotterStepSize : Double, qubits : +Qubit[]) + : Unit is Adj + Ctl { + let generatorSystem = JordanWignerGeneratorSystem(data); + let evolutionGenerator = EvolutionGenerator(JordanWignerFermionEvolutionSet(), generatorSystem); + let trotterOrder = 1; + TrotterStep(evolutionGenerator, trotterOrder, trotterStepSize)(qubits); + } +} \ No newline at end of file diff --git a/samples/chemistry/FermionicSwap/FermionicSwap.Tests/FermionicSwap.Tests.csproj b/samples/chemistry/FermionicSwap/FermionicSwap.Tests/FermionicSwap.Tests.csproj new file mode 100644 index 00000000000..d402dd57fb5 --- /dev/null +++ b/samples/chemistry/FermionicSwap/FermionicSwap.Tests/FermionicSwap.Tests.csproj @@ -0,0 +1,32 @@ + + + + net6.0 + x64 + enable + enable + + false + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/samples/chemistry/FermionicSwap/FermionicSwap.Tests/TestFermionicSwap.cs b/samples/chemistry/FermionicSwap/FermionicSwap.Tests/TestFermionicSwap.cs new file mode 100644 index 00000000000..ffc7e9b7f7f --- /dev/null +++ b/samples/chemistry/FermionicSwap/FermionicSwap.Tests/TestFermionicSwap.cs @@ -0,0 +1,703 @@ +// Copyright Battelle Memorial Institute 2022. All rights reserved. + +using Microsoft.Quantum.Chemistry.Fermion; +using Microsoft.Quantum.Chemistry.LadderOperators; +using Microsoft.Quantum.Chemistry; +using Microsoft.Quantum.Simulation.Core; +using Microsoft.Quantum.Simulation.Simulators; +using Microsoft.Quantum.Chemistry.JordanWigner; +using static FermionicSwap.FSTools; +using Microsoft.Quantum.Chemistry.QSharpFormat; +using static FermionicSwap.SwapNetwork; +using System.Linq; +using System.Collections.Immutable; +using System; + +namespace FermionicSwap.Tests +{ + + using SwapLayer = List<(int,int)>; + using OperatorLayer = List<(HermitianFermionTerm, DoubleCoeff)>; + using OperatorNetwork = List>; + + public class TestFermionicSwap +{ + + [Theory] + [MemberData(nameof(Data))] + public void TestEvenOddSwap(int[] startOrder, int[] endOrder, SwapNetwork swapNetwork) + { + var result = FSTools.EvenOddSwap(startOrder, endOrder); + Assert.True(result.Count == swapNetwork.Count, $"Need swap layers {LayersString(swapNetwork)}, but got {LayersString(result)}."); + // for each swap layer, check that the list of swaps matches the test list + foreach (var (first,second) in result.Zip(swapNetwork, (f,s)=> (f,s))) { + Assert.True(first.SequenceEqual(second), $"Swap network {LayersString(result)} should be {LayersString(swapNetwork)}."); + } + } + + public static IEnumerable Data => new List + { + // empty layer + new object[] { new int[] {}, new int[] {}, new SwapNetwork {} }, + // trivial swapping + new object[] { new int[] {0,1}, new int[] {0,1}, new SwapNetwork {} }, + // nontrivial + new object[] { new int[] {1,0}, new int[] {0,1}, new SwapNetwork {new SwapLayer {(0,1)}}}, + // site numbering does not start from zero + new object[] { new int[] {1,2}, new int[] {2,1}, new SwapNetwork {new SwapLayer {(0,1)}}}, + // three items, trivial swapping + new object[] { new int[] {0,1,2}, new int[] {0,1,2}, new SwapNetwork {}}, + // four items, trivial swapping + new object[] { new int[] {0,1,2,3}, new int[] {0,1,2,3}, new SwapNetwork {}}, + // five items, trivial swapping + new object[] { new int[] {0,1,2,3,4}, new int[] {0,1,2,3,4}, new SwapNetwork {}}, + // three items, nontrivial swapping + new object[] { new int[] {0,1,2}, new int[] {2,1,0}, new SwapNetwork { + new SwapLayer {(0,1)}, + new SwapLayer {(1,2)}, + new SwapLayer {(0,1)} + } + }, + // three items, no (even) swaps in initial swap layer + new object[] { new int[] {0,1,2}, new int[] {0,2,1}, new SwapNetwork {new SwapLayer {(1,2)}}}, + // 7 items, a single item moves in each layer + new object[] { new int[] {0,1,2,3,4,5,6}, new int[] {6,0,1,2,3,4,5}, new SwapNetwork { + new SwapLayer {(5,6)}, + new SwapLayer {(4,5)}, + new SwapLayer {(3,4)}, new SwapLayer {(2,3)}, + new SwapLayer {(1,2)}, new SwapLayer {(0,1)}, + }}, + // odd larger number of items, full reverse + new object[] { new int[] {0,1,2,3,4,5,6}, new int[] {6,5,4,3,2,1,0}, new SwapNetwork { + new SwapLayer {(0,1),(2,3),(4,5)}, + new SwapLayer {(1,2),(3,4),(5,6)}, + new SwapLayer {(0,1),(2,3),(4,5)}, + new SwapLayer {(1,2),(3,4),(5,6)}, + new SwapLayer {(0,1),(2,3),(4,5)}, + new SwapLayer {(1,2),(3,4),(5,6)}, + new SwapLayer {(0,1),(2,3),(4,5)}, + }}, + // even larger number of items, full reverse + new object[] { new int[] {0,1,2,3,4,5}, new int[] {5,4,3,2,1,0}, new SwapNetwork { + new SwapLayer {(0,1),(2,3),(4,5)}, + new SwapLayer {(1,2),(3,4)}, + new SwapLayer {(0,1),(2,3),(4,5)}, + new SwapLayer {(1,2),(3,4)}, + new SwapLayer {(0,1),(2,3),(4,5)}, + new SwapLayer {(1,2),(3,4)}, + }} + }; + + [Theory] + [MemberData(nameof(OneBodyDenseNetworkData))] + public void TestOneBodyDenseNetwork(int numSites, SwapNetwork swapNetwork) + { + var result = FSTools.OneBodyDenseNetwork(numSites); + Assert.True(result.Count == swapNetwork.Count, $"Need swap layers {LayersString(swapNetwork)}, but got {LayersString(result)}."); + // for each swap layer, check that the list of swaps matches the test list + foreach (var (first,second) in result.Zip(swapNetwork, (f,s)=> (f,s))) { + Assert.True(first.SequenceEqual(second), $"Swap network {LayersString(result)} should be {LayersString(swapNetwork)}."); + } + } + public static IEnumerable OneBodyDenseNetworkData => new List + { + // Small one body swap networks of various sizes. Small, larger odd and larger even. + new object[] {0, new SwapNetwork {}}, + new object[] {1, new SwapNetwork {}}, + new object[] {2, new SwapNetwork {new SwapLayer {(0,1)}}}, + new object[] {3, new SwapNetwork {new SwapLayer {(0,1)}, new SwapLayer {(1,2)}, new SwapLayer {(0,1)}}}, + new object[] {6, new SwapNetwork { + new SwapLayer {(0,1),(2,3),(4,5)}, + new SwapLayer {(1,2),(3,4)}, + new SwapLayer {(0,1),(2,3),(4,5)}, + new SwapLayer {(1,2),(3,4)}, + new SwapLayer {(0,1),(2,3),(4,5)}, + new SwapLayer {(1,2),(3,4)}, + }}, + new object[] {7, new SwapNetwork { + new SwapLayer {(0,1),(2,3),(4,5)}, + new SwapLayer {(1,2),(3,4),(5,6)}, + new SwapLayer {(0,1),(2,3),(4,5)}, + new SwapLayer {(1,2),(3,4),(5,6)}, + new SwapLayer {(0,1),(2,3),(4,5)}, + new SwapLayer {(1,2),(3,4),(5,6)}, + new SwapLayer {(0,1),(2,3),(4,5)}, + }}, + }; + +[Theory] + [MemberData(nameof(SpinlessTwoDHubbardNetworkData))] + public void TestSpinlessTwoDHubbardNetwork(int numM, int numN, int[] correctStartOrder, SwapNetwork swapNetwork) + { + var (startOrder, result) = FSTools.SpinlessTwoDHubbardNetwork(numM, numN); + var actualLayerCount = result.Count; + Assert.Equal(startOrder, correctStartOrder.ToList()); + Assert.True(actualLayerCount == swapNetwork.Count, + $"Got {actualLayerCount} swap layers, but needed {swapNetwork.Count}." + ); + // for each swap layer, check that the list of swaps matches the test list + foreach (var (first,second) in result.Zip(swapNetwork, (f,s)=> (f,s))) { + Assert.True(first.SequenceEqual(second), $"Swap network {LayersString(result)} should be {LayersString(swapNetwork)}."); + } + } + public static IEnumerable SpinlessTwoDHubbardNetworkData => new List + { + // Small 2D Hubbard swap networks. + new object[] {1, 1, new int[]{0}, new SwapNetwork {}}, + new object[] {1, 2, new int[] {0,1}, new SwapNetwork {}}, + new object[] {2, 1, new int[] {0,1}, new SwapNetwork {}}, + // 1 0 2 3 -> 0 1 3 2 + new object[] {2, 2, new int[] {1,0,2,3}, + new SwapNetwork {new SwapLayer{(0,1),(2,3)}}}, + // 1 0 2 3 5 4 -> 0 1 3 2 4 5 + new object[] {3, 2, new int[] {1,0,2,3,5,4}, + new SwapNetwork {new SwapLayer{(0,1),(2,3),(4,5)}}}, + // 1 0 3 2 5 4 -> 0 2 1 4 3 5 + new object[] {2,3, new int[] {1,0,3,2,5,4}, + new SwapNetwork {new SwapLayer{(0,1),(2,3),(4,5)}, + new SwapLayer{(1,2), (3,4)}}}, + // 1 0 3 2 5 4 7 6 8 -> 0 2 1 4 3 6 5 8 7 + new object[] {3, 3, new int[] {1,0,3,2,5,4,7,6,8}, + new SwapNetwork {new SwapLayer{(0,1),(2,3),(4,5),(6,7)}, + new SwapLayer{(1,2),(3,4),(5,6),(7,8)}}}, + // 1 0 4 3 2 6 5 9 8 12 7 11 10 14 13 15 -> + // 0 2 1 5 4 8 3 7 6 10 9 13 12 11 15 14 + new object[] {4, 4, new int[]{1, 0, 4, 3, 2, 6, 5, 9, 8, 12, 7, 11, 10, 14, 13, 15}, + EvenOddSwap( + new int[]{1, 0, 4, 3, 2, 6, 5, 9, 8, 12, 7, 11, 10, 14, 13, 15}, + new int[]{0, 2, 1, 5, 4, 8, 3, 7, 6, 10, 9, 13, 12, 11, 15, 14})} + }; + + [Theory] + [MemberData(nameof(ReorderedFermionTermData))] + public void TestReorderedFermionTerm(HermitianFermionTerm term, + Dictionary desiredOrder, + List correctTerm, + int coefficient) { + var newTerm = ReorderedFermionTerm(term, desiredOrder); + var result = newTerm.Sequence.Select(o => o.Index); + var resultString = String.Join(", ", result); + Assert.True(result.SequenceEqual(correctTerm), $"Incorrect order {resultString}."); + Assert.Equal(coefficient, newTerm.Coefficient); + } + + // Note: the reordered terms are returned in QDK's canonical ladder operator order. + public static IEnumerable ReorderedFermionTermData => new List + { + // Leave a correctly ordered object alone. + new object[] { + new HermitianFermionTerm(new int[] {0,1}), + PositionDictionary(new int[] {0,1}), + new List {0,1}, + 1 + }, + new object[] { + new HermitianFermionTerm(new int[] {0,1,3,2}), + PositionDictionary(new int[] {0,1,2,3}), + new List {0,1,3,2}, + 1 + }, + new object[] { + new HermitianFermionTerm(new int[] {0,5,6,4}), + PositionDictionary(new int[] {0,1,2,3,4,5,6}), + new List {0,5,6,4}, + 1 + }, + + // Permute to new positions correctly in the absence of canonical reordering + new object[] { + new HermitianFermionTerm(new int[] {0,1}), + PositionDictionary(new int[] {1,0}), + // Hermitian reordering occurs here + new List {0,1}, + 1 + }, + new object[] { + new HermitianFermionTerm(new int[] {1,3,2,0}), + PositionDictionary(new int[] {0,1,3,2}), + new List {0,3,2,1}, + 1 + }, + new object[] { + new HermitianFermionTerm(new int[] {2,4,3,1}), + PositionDictionary(new int[] {0,1,2,4,3}), + new List {1,4,3,2}, + 1 + }, + // Permute to new positions with pre/post canonical reordering + // differing by even/even permutations from given ordering + new object[] { + new HermitianFermionTerm(new int[] {0,1,2,3,4,5,6,7}), + PositionDictionary(new int[] {1,0,3,2,7,6,5,4}), + new List {0,1,2,3,7,6,5,4}, + 1 + }, + // Same, but even/odd + new object[] { + new HermitianFermionTerm(new int[] {0,1,2,3,4,5,6,7}), + PositionDictionary(new int[] {0,1,3,2,7,6,5,4}), + new List {0,1,2,3,7,6,5,4}, + -1 + }, + // Same, but odd/even + new object[] { + new HermitianFermionTerm(new int[] {0,1,2,3,4,5,7,6}), + PositionDictionary(new int[] {1,0,3,2,7,6,5,4}), + new List {0,1,2,3,7,6,5,4}, + -1 + }, + // Same, but odd/odd + new object[] { + new HermitianFermionTerm(new int[] {0,1,2,3,4,5,7,6}), + PositionDictionary(new int[] {0,1,3,2,7,6,5,4}), + new List {0,1,2,3,7,6,5,4}, + 1 + }, + }; + + [Theory] + [MemberData(nameof(ProcessNetworkLayerData))] + public void TestProcessNetworkLayer( + TermsDictionary termDict, + int[] order, + OperatorLayer correct) + { + var result = ProcessNetworkLayer(termDict, order); + Assert.True(result.Count() == correct.Count(), $"Result has {result.Count()} elements but correct result has {correct.Count()}"); + foreach (var (r,c) in result.Zip(correct)) { + Assert.True(r == c, $"{r} does not equal {c}."); + } + } + + // Note: the reordered terms are returned in QDK's canonical ladder operator order. + public static IEnumerable ProcessNetworkLayerData => new List + { + // Produce no operators if there are no terms. + new object[] { + new TermsDictionary(), + new int[] {0,1,2,3}, + new OperatorLayer {} + }, + // Produce an operator from a term + new object[] { + new TermsDictionary() {{ImmutableArray.Create(new int[] {0,1}), new OperatorLayer { + (new HermitianFermionTerm(new int[] {0,1}),3.0) + }}}, + new int[] {0,1}, + new OperatorLayer{(new HermitianFermionTerm(new int[] {0,1}),3.0)} + }, + // Produce an operator from a misordered term + new object[] { + new TermsDictionary() {{ImmutableArray.Create(new int[] {0,1}), new OperatorLayer { + (new HermitianFermionTerm(new int[] {0,1}),3.0) + }}}, + new int[] {0,1}, + new OperatorLayer{(new HermitianFermionTerm(new int[] {0,1}),3.0)} + }, + // Apply the greedy algorithm to produce multiple operators + new object[] { + new TermsDictionary() { + {ImmutableArray.Create(new int[] {0,1}), new OperatorLayer { + (new HermitianFermionTerm(new int[] {0,1}),1.0) + }}, + {ImmutableArray.Create(new int[] {3,4}), new OperatorLayer { + (new HermitianFermionTerm(new int[] {3,4}),1.0) + }}, + + }, + new int[] {0,1,2,3,4}, + new OperatorLayer{ + (new HermitianFermionTerm(new int[] {0,1}),1.0), + (new HermitianFermionTerm(new int[] {3,4}),1.0) + } + }, + // Process multiple operators with the same indices + // double check time application + new object[] { + new TermsDictionary() { + {ImmutableArray.Create(new int[] {0,1}), new OperatorLayer { + (new HermitianFermionTerm(new int[] {0,1}),1.0), + (new HermitianFermionTerm(new int[] {0,1,1,0}),2.0) + }}, + {ImmutableArray.Create(new int[] {3,4}), new OperatorLayer { + (new HermitianFermionTerm(new int[] {3,4}),3.0) + }}, + + }, + new int[] {0,1,2,3,4}, + new OperatorLayer{ + (new HermitianFermionTerm(new int[] {0,1}),1.0), + (new HermitianFermionTerm(new int[] {3,4}),3.0), + (new HermitianFermionTerm(new int[] {0,1,1,0}),2.0) + } + }, + // Handle operator overlap correctly + new object[] { + new TermsDictionary() { + {ImmutableArray.Create(new int[] {0,1}), new OperatorLayer { + (new HermitianFermionTerm(new int[] {0,1}),1.0), + (new HermitianFermionTerm(new int[] {0,1,1,0}),1.0) + }}, + {ImmutableArray.Create(new int[] {1,2,3,4}), new OperatorLayer { + (new HermitianFermionTerm(new int[] {2,4,3,1}),1.0) + }}, + {ImmutableArray.Create(new int[] {3,4}), new OperatorLayer { + (new HermitianFermionTerm(new int[] {3,4}),1.0) + }}, + + }, + new int[] {0,1,2,3,4}, + new OperatorLayer{ + (new HermitianFermionTerm(new int[] {0,1}),1.0), + (new HermitianFermionTerm(new int[] {3,4}),1.0), + (new HermitianFermionTerm(new int[] {0,1,1,0}),1.0), + (new HermitianFermionTerm(new int[] {2,4,3,1}),1.0) + } + }, + // Correctly reorder the terms + new object[] { + new TermsDictionary() { + {ImmutableArray.Create(new int[] {0,1}), new OperatorLayer { + (new HermitianFermionTerm(new int[] {0,1}),1.0), + (new HermitianFermionTerm(new int[] {0,1,1,0}),1.0) + }}, + {ImmutableArray.Create(new int[] {1,2,3,4}), new OperatorLayer { + (new HermitianFermionTerm(new int[] {2,4,3,1}),1.0) + }}, + {ImmutableArray.Create(new int[] {3,4}), new OperatorLayer { + (new HermitianFermionTerm(new int[] {3,4}),1.0) + }}, + + }, + new int[] {4,3,2,1,0}, + new OperatorLayer{ + (new HermitianFermionTerm(new int[] {1,0}),1.0), // 3,4 + (new HermitianFermionTerm(new int[] {4,3}),1.0), // 0,1 + (new HermitianFermionTerm(new int[] {0,2,3,1}),1.0), // 2,4,3,1 + (new HermitianFermionTerm(new int[] {4,3,3,4}),1.0), // 0,1,1,0 + } + } + + }; + + [Theory] + [MemberData(nameof(TrotterStepDataData))] + public void TestTrotterStepData( + FermionHamiltonian H, + SwapNetwork swapNetwork, + int[] startOrder, + OperatorNetwork correctNetwork, + int[] correctOrder + ) + { + var (operatorNetwork, endOrder) = TrotterStepData(H, swapNetwork, startOrder); + Assert.True(endOrder.SequenceEqual(correctOrder), + $"Resulting order {String.Join(", ", endOrder)} differs from correct order {String.Join(", ", correctOrder.Select(o=>o.ToString()))}."); + Assert.True(operatorNetwork.Count() == swapNetwork.Count() + 1, + $"Resulting operator network has {operatorNetwork.Count()} layers instead of {swapNetwork.Count()}."); + foreach (var (r,c) in operatorNetwork.Zip(correctNetwork)) { + Assert.True(r.SequenceEqual(c), $"Resulting layer {String.Join(", ", r)} differs from correct layer {String.Join(", ", c)}."); + } + } + + // Note: the reordered terms are returned in QDK's canonical ladder operator order. + public static IEnumerable TrotterStepDataData() { + var result = new List {}; + + // An empty Hamiltonian and swap network produce an empty operator network. + var H = new FermionHamiltonian {}; + var swapNetwork = new SwapNetwork {}; + var startOrder = new int[]{}; + var correctNetwork = new OperatorNetwork {}; + int[] correctOrder = startOrder.ToArray(); + result.Add(new object[] {H, swapNetwork, startOrder, correctNetwork, correctOrder}); + + // An empty Hamiltonian and any swap network produce an empty operator network. + H = new FermionHamiltonian {}; + swapNetwork = OneBodyDenseNetwork(3); + startOrder = new int[] {0,1,2}; + correctNetwork = new OperatorNetwork {}; + correctOrder = startOrder.Reverse().ToArray(); + result.Add(new object[] {H, swapNetwork, startOrder, correctNetwork, correctOrder}); + + // correct networks for some dense hopping term hamiltonians + var correctNetworks = new List { + // 3 sites + new OperatorNetwork { + new OperatorLayer { + (new HermitianFermionTerm(new int[] {0,1}), 1.0), + (new HermitianFermionTerm(new int[] {1,2}), 1.0) + }, + new OperatorLayer { + (new HermitianFermionTerm(new int[] {1,2}),1.0) + }, + new OperatorLayer {} + }, + // 4 sites + new OperatorNetwork { + new OperatorLayer { + (new HermitianFermionTerm(new int[] {0,1}), 1.0), + (new HermitianFermionTerm(new int[] {2,3}), 1.0), + (new HermitianFermionTerm(new int[] {1,2}), 1.0) + }, + new OperatorLayer { + (new HermitianFermionTerm(new int[] {1,2}), 1.0), + }, + new OperatorLayer { + (new HermitianFermionTerm(new int[] {0,1}), 1.0), + (new HermitianFermionTerm(new int[] {2,3}), 1.0), + }, + new OperatorLayer {} + }, + // 5 sites + new OperatorNetwork { + new OperatorLayer { + (new HermitianFermionTerm(new int[] {0,1}), 1.0), + (new HermitianFermionTerm(new int[] {2,3}), 1.0), + (new HermitianFermionTerm(new int[] {1,2}), 1.0), + (new HermitianFermionTerm(new int[] {3,4}), 1.0) + }, + new OperatorLayer { + (new HermitianFermionTerm(new int[] {1,2}), 1.0), + (new HermitianFermionTerm(new int[] {3,4}), 1.0), + }, + new OperatorLayer { + (new HermitianFermionTerm(new int[] {0,1}), 1.0), + (new HermitianFermionTerm(new int[] {2,3}), 1.0), + }, + new OperatorLayer { + (new HermitianFermionTerm(new int[] {1,2}), 1.0), + (new HermitianFermionTerm(new int[] {3,4}), 1.0), + }, + new OperatorLayer {} + } + }; + + // Dense Hopping terms on 3,4,5 site orbitals + int numSites = 3; + for (; numSites < 6; numSites++) { + H = new FermionHamiltonian {}; + for (int i = 0; i < numSites; i++) { + for (int j = i+1; j < numSites; j++) { + H.Add(new HermitianFermionTerm(new int[] {i, j}), 1.0); + } + } + swapNetwork = OneBodyDenseNetwork(numSites); + startOrder = Enumerable.Range(0,numSites).ToArray(); + correctOrder = startOrder.Reverse().ToArray(); + result.Add(new object[] {H, swapNetwork, startOrder, correctNetworks[numSites-3], correctOrder}); + } + + // 5 sites with self-interactions + numSites = 5; + H = new FermionHamiltonian {}; + for (int i = 0; i < numSites; i++) { + for (int j = i+1; j < numSites; j++) { + H.Add(new HermitianFermionTerm(new int[] {i, j}), 1.0); + } + H.Add(new HermitianFermionTerm(new int[] {i,i}), (Double)(i+1)); + } + swapNetwork = OneBodyDenseNetwork(numSites); + startOrder = Enumerable.Range(0,numSites).ToArray(); + correctNetwork = new OperatorNetwork { + new OperatorLayer { + (new HermitianFermionTerm(new int[] {0,0}), 1.0), + (new HermitianFermionTerm(new int[] {1,1}), 2.0), + (new HermitianFermionTerm(new int[] {2,2}), 3.0), + (new HermitianFermionTerm(new int[] {3,3}), 4.0), + (new HermitianFermionTerm(new int[] {4,4}), 5.0), + (new HermitianFermionTerm(new int[] {0,1}), 1.0), + (new HermitianFermionTerm(new int[] {2,3}), 1.0), + (new HermitianFermionTerm(new int[] {1,2}), 1.0), + (new HermitianFermionTerm(new int[] {3,4}), 1.0) + }, + new OperatorLayer { + (new HermitianFermionTerm(new int[] {1,2}), 1.0), + (new HermitianFermionTerm(new int[] {3,4}), 1.0), + }, + new OperatorLayer { + (new HermitianFermionTerm(new int[] {0,1}), 1.0), + (new HermitianFermionTerm(new int[] {2,3}), 1.0), + }, + new OperatorLayer { + (new HermitianFermionTerm(new int[] {1,2}), 1.0), + (new HermitianFermionTerm(new int[] {3,4}), 1.0), + }, + new OperatorLayer {} + }; + correctOrder = startOrder.Reverse().ToArray(); + result.Add(new object[] {H, swapNetwork, startOrder, correctNetwork, correctOrder}); + + // verify that weights transfer correctly + H = new FermionHamiltonian {}; + numSites = 5; + for (int i = 0; i < numSites; i++) { + for (int j = i+1; j < numSites; j++) { + H.Add(new HermitianFermionTerm(new int[] {i, j}), (double)(10*i+j)); + } + } + swapNetwork = OneBodyDenseNetwork(numSites); + startOrder = Enumerable.Range(0,numSites).ToArray(); + correctOrder = startOrder.Reverse().ToArray(); + correctNetwork = new OperatorNetwork { + new OperatorLayer { + (new HermitianFermionTerm(new int[] {0,1}), 1.0), + (new HermitianFermionTerm(new int[] {2,3}), 23.0), + (new HermitianFermionTerm(new int[] {1,2}), 12.0), + (new HermitianFermionTerm(new int[] {3,4}), 34.0) + }, + // order 10324 + new OperatorLayer { + (new HermitianFermionTerm(new int[] {1,2}), 3.0), + (new HermitianFermionTerm(new int[] {3,4}), 24.0), + }, + // order 13042 + new OperatorLayer { + (new HermitianFermionTerm(new int[] {0,1}), 13.0), + (new HermitianFermionTerm(new int[] {2,3}), 4.0), + }, + //order 31402 + new OperatorLayer { + (new HermitianFermionTerm(new int[] {1,2}), 14.0), + (new HermitianFermionTerm(new int[] {3,4}), 2.0), + }, + //order 34120 + new OperatorLayer {} + //order 43210 + }; + + result.Add(new object[] {H, swapNetwork, startOrder, correctNetwork, correctOrder}); + + return result; + } + + // Check the operation of the following functions: + // ToQSharpFormat, + // FermionicSwapTrotterStep (qsharp), + // FixedOrderFermionicSwapTrotterStep (qsharp), + // by constructing a Hamiltonian for a Trotter step and checking for + // equality with the corresponding JordanWigner trotter step. Since + // the two methods do not agree on the order in which terms are + // evaluated, which results in unequal Trotter steps in general, this + // test uses Hamiltonians consisting of single PQ terms. + [Theory] + [MemberData(nameof(OneTermHamiltonianData))] + public void TestOneTermHamiltonian( + FermionHamiltonian H, + SwapNetwork swapNetwork, + int[] startOrder + ) + { + var (opNetwork, endOrder) = TrotterStepData(H, swapNetwork, startOrder); + //we use 32 bit ints until the point of injection into q#, which requires 64 bit ints. + var qsharpSwapNetwork = swapNetwork.ToQSharpFormat(); + var qsharpData = ToQSharpFormat(opNetwork, false); + var (_,_,qsharpHamiltonian) = H.ToPauliHamiltonian().ToQSharpFormat(); + + Assert.Equal(qsharpSwapNetwork.Length+1, qsharpData.Length); + using (var qsim = new QuantumSimulator()) + { + SwapNetworkOneSummandTestOp.Run(qsim, qsharpSwapNetwork, qsharpData, qsharpHamiltonian, + (long)startOrder.Length) + .Wait(); + } + } + + public static IEnumerable OneTermHamiltonianData() { + var result = new List {}; + var numSites = 5; + for (int i = 0; i < numSites; i++) { + for (int j = i+1; j < numSites; j++) { + var H = new FermionHamiltonian {}; + H.Add(new HermitianFermionTerm(new int[] {i, j}), (double)(10*i+j)); + var swapNetwork = OneBodyDenseNetwork(numSites); + result.Add(new object[] {H, swapNetwork, Enumerable.Range(0,numSites).ToArray()}); + } + } + + return result; + } + + [Theory] + [MemberData(nameof(HamiltonianData))] + public void TestHamiltonian( + FermionHamiltonian H, + int numSites, + SwapNetwork swapNetwork, + double stepSize, + double time + ) { + var startOrder = Enumerable.Range(0,numSites).ToArray(); + var (opNetwork, endOrder) = TrotterStepData(H, swapNetwork, startOrder); + //we use 32 bit ints until the point of injection into q#, which requires 64 bit ints. + var qsharpSwapNetwork = swapNetwork.ToQSharpFormat(); + var qsharpData = ToQSharpFormat(opNetwork, false); + var (_,_,qsharpHamiltonian) = H.ToPauliHamiltonian().ToQSharpFormat(); + + Assert.Equal(qsharpSwapNetwork.Length+1, qsharpData.Length); + using (var qsim = new QuantumSimulator()) + { + SwapNetworkEvolutionTestOp.Run(qsim, qsharpSwapNetwork, qsharpData, qsharpHamiltonian, + (long)numSites, stepSize, time) + .Wait(); + } + } + + // One term hamiltonians, similar to previous test + public static IEnumerable HamiltonianData() { + var result = new List {}; + var numSites = 5; + for (int i = 0; i < numSites; i++) { + for (int j = i+1; j < numSites; j++) { + var H = new FermionHamiltonian {}; + H.Add(new HermitianFermionTerm(new int[] {i, j}), (double)(10*i+j)); + var swapNetwork = OneBodyDenseNetwork(numSites); + var stepSize = 1; + var time = 2; + result.Add(new object[] {H, numSites, swapNetwork, stepSize, time}); + } + } + // A dense Hamiltonian + var H2 = new FermionHamiltonian {}; + var swapNetwork2 = OneBodyDenseNetwork(numSites); + var stepSize2 = .00002; + var time2 = .001; + for (int i = 0; i < numSites; i++) { + for (int j = i+1; j < numSites; j++) { + H2.Add(new HermitianFermionTerm(new int[] {i, j}), (double)(10*i+j)); + } + } + result.Add(new object[] {H2, numSites, swapNetwork2, stepSize2, time2}); + + + return result; + } + + public string LayersString(SwapNetwork swaps) { + var result = "{"; + var swapsOccupied = false; + foreach (var layer in swaps) { + if (swapsOccupied) { + result += ", "; + } else { + result += "{"; + swapsOccupied = true; + } + var layerOccupied = false; + foreach (var (a,b) in layer) { + if (layerOccupied) { + result += ", "; + } else { + result += "{"; + layerOccupied = true; + } + result += $"({a}, {b})"; + } + result += "}"; + } + result += "}"; + return result; + } + + } +} \ No newline at end of file diff --git a/samples/chemistry/FermionicSwap/FermionicSwap.Tests/Usings.cs b/samples/chemistry/FermionicSwap/FermionicSwap.Tests/Usings.cs new file mode 100644 index 00000000000..c802f4480b1 --- /dev/null +++ b/samples/chemistry/FermionicSwap/FermionicSwap.Tests/Usings.cs @@ -0,0 +1 @@ +global using Xunit; diff --git a/samples/chemistry/FermionicSwap/FermionicSwap/FermionicSwap.cs b/samples/chemistry/FermionicSwap/FermionicSwap/FermionicSwap.cs new file mode 100644 index 00000000000..121353a014e --- /dev/null +++ b/samples/chemistry/FermionicSwap/FermionicSwap/FermionicSwap.cs @@ -0,0 +1,438 @@ +// Copyright Battelle Memorial Institute 2022. All rights reserved. + +using System.Linq; +using System.Collections.Immutable; + +using Microsoft.Quantum.Chemistry.Fermion; +using Microsoft.Quantum.Chemistry.LadderOperators; +using Microsoft.Quantum.Chemistry; +using Microsoft.Quantum.Simulation.Core; +using Microsoft.Quantum.Chemistry.JordanWigner; +using Microsoft.Quantum.Chemistry.QSharpFormat; +using static System.Linq.Enumerable; + +namespace FermionicSwap +{ + + using FermionOperator = LadderOperator; + using SwapLayer = List<(int,int)>; + using OperatorLayer = List<(HermitianFermionTerm,DoubleCoeff)>; + using OperatorNetwork = List>; + + public class SwapNetwork : List> + { + public QArray> ToQSharpFormat() { + return new QArray>(this.Select(item => new QArray<(Int64,Int64)>(item.Select(t=> ((Int64)t.Item1,(Int64)t.Item2)).ToArray())) + .ToArray()); + } + } + public class TermsDictionaryComparer : IEqualityComparer> + { + public bool Equals(ImmutableArray x, ImmutableArray y) + { + return x!.SequenceEqual(y!); + } + + public int GetHashCode(ImmutableArray obj) + { + return obj.Aggregate(0, (ob1,ob2) => HashCode.Combine(ob1,ob2)); + } + } + + public class TermsDictionary : Dictionary, List<(HermitianFermionTerm,DoubleCoeff)>> { + public TermsDictionary() : base(new TermsDictionaryComparer {}) {} + } + + public static class FSTools { + // fixme: What is the naming convention for a library that includes a + // single static class? + + /// # Summary + /// Returns the first swap layer of the swap network that takes + /// elements of startOrder to desiredPositions + /// + /// # Input + /// ## startOrder + /// A position-indexed array indicating the index of the site orbital corresponding to each position. + /// ## desiredPositions + /// A map from orbital sites to desired final positions + /// ## evenParity + /// True if this layer should consist of even-odd swaps, false if odd-even swaps. + /// + /// # Output + /// A tuple (nextOrder, layer) consisting of + /// ## nextOrder + /// A position indexed array indicating site orbital positioning after the swap layer is applied + /// ## layer + /// A list of mutually disjoint (n,n+1) transpositions. + private static (int[],SwapLayer) EvenOddSwapLayer(int[] startOrder, Dictionary desiredPositions, bool evenParity) { + + int start = evenParity?0:1; + var newOrder = startOrder.ToArray(); + var swaps = new SwapLayer(); + + for(int i=start;i PositionDictionary(int[] order) { + var desiredPositions = new Dictionary(); + for (int i = 0; i < order.Length; i++) + { + desiredPositions[order[i]] = i; + } + return desiredPositions; + + } + + /// # Summary + /// Return a network of swaps that converts an initial ordering of site + /// orbitals to the desired final ordering, by implementing an even-odd + /// sort algorithm. + /// + /// Even-odd sort produces a network with minimal swaps and at most one + /// more than minimal circuit depth. A greedy circuit-packing algorithm + /// will eliminate the one-extra circuit depth. + /// + /// # Input + /// ## startOrder + /// A position-indexed array of site orbital indices, indicating their + /// starting order. + /// ## endOrder + /// A position-indexed array of site orbital indices, indicating their + /// desired order + /// + /// # Output + /// A SwapNetwork describing layers of disjoint (n,n+1) transpositions + /// which convert startOrder to endOrder. + public static SwapNetwork EvenOddSwap(int[] startOrder, int[] endOrder) { + var result = new SwapNetwork(); + var thisOrder = startOrder; + + bool atLeastOnce = false; + bool done = false; + bool evenParity = true; + var desiredPositions = PositionDictionary(endOrder); + while (!done) { + var (nextOrder,swaps) = EvenOddSwapLayer(thisOrder, desiredPositions, evenParity); + thisOrder = nextOrder; + if (swaps.Count > 0) { + result.Add(swaps); + evenParity = !evenParity; + atLeastOnce = true; + } else { + if (atLeastOnce) { + done = true; + } else { + atLeastOnce = true; + evenParity = !evenParity; + } + + } + } + return result; + } + + /// # Summary + /// Return a swap network suitable for evaluating a Trotter step for a + /// one-body dense Hamiltonian. The resulting network fully reverses + /// the order of the site-orbitals. The Trotter step can be evolved + /// without using the last two of the swap layers, but we do not + /// assume that optimization here. + /// + /// # Input + /// ## numSites + /// The number of site orbitals in the Hamiltonian. + /// # Output + /// A swap network that reverses the order of the site orbitals. + public static SwapNetwork OneBodyDenseNetwork(int numSites) { + var startOrder = Range(0,numSites).ToArray(); + var endOrder = startOrder.Select(x => numSites - x-1).ToArray(); + return EvenOddSwap(startOrder, endOrder); + } + + private static List Interleave(List first, List second) { + var result = new List() {}; + for (int i = 0; i < Math.Max(first.Count(), second.Count()); i++) { + if (i < first.Count()) { + result.Add(first[i]); + } + if (i < second.Count()) { + result.Add(second[i]); + } + } + return result; + } + + private static int JWIndex (int m, int n, int numM, int numN) { + return numN * m + n; + } + + /// # Summary + /// Return an efficient swap network for a two-dimensional spinless + /// Hubbard model Hamiltiltonian, using the method + /// [described here](https://arxiv.org/abs/2001.08324). + + /// # Input + /// ## numM + /// The number of rows in the Hamiltonian interaction grid. + /// ## numM + /// The number of columns in the Hamiltonian interaction grid. + /// # Output + /// The swap network. + public static (List, SwapNetwork) SpinlessTwoDHubbardNetwork(int numM, int numN) { + var diagonals = new List>() {}; + var startOrder = new List() {}; + var endOrder = new List() {}; + if (numM > 1 && numN > 1) { + // not a trivial special case + for (int i=0; i JWIndex(j,i-j,numM,numN)).ToList()); + } + // iterate over pairs of the numM+numN-1 diagonals + for (int i=0; 2*i < numM + numN - 1; i++) { + if (2*i+1 == numM + numN - 1) { + // last "pair" of diagonals, only contains one diagonal, of length 1 + startOrder.AddRange(diagonals[2*i]); + } else { + // interleave the pair of diagonals, greater diagonal first + // until we reach the corner at the last column, then + // lesser diagonal first. + if (2*i+1 < numN) { + startOrder.AddRange(Interleave(diagonals[2*i+1], diagonals[2*i])); + } else { + startOrder.AddRange(Interleave(diagonals[2*i], diagonals[2*i+1])); + } + } + } + // iterate over pairs of the numM+numN-1 diagonals, starting at + // the second one. + endOrder.AddRange(diagonals[0]); + for (int i=0; 2*i + 1 < numM + numN -1; i++) { + if (2*i+2 == numM + numN-1) { + // last "pair" of diagonals, only contains one diagonal, of length 1 + endOrder.AddRange(diagonals[2*i+1]); + } else { + // interleave the pair of diagonals, greater diagonal first + // until we reach the corner at the last column, then + // lesser diagonal first. + if (2*i+2 < numN) { + endOrder.AddRange(Interleave(diagonals[2*i+2], diagonals[2*i+1])); + } else { + endOrder.AddRange(Interleave(diagonals[2*i+1], diagonals[2*i+2])); + } + } + } + } else { + // trivial grid size, no swapping needed + startOrder = Range(0,numM*numN).ToList(); + endOrder = Range(0,numM*numN).ToList(); + } + //endOrder = startOrder; // delete this. + return (startOrder, EvenOddSwap(startOrder.ToArray(),endOrder.ToArray())); + } + + /// # Summary + /// Return an n-body fermionic Hamiltonian term, re-indexed to be + /// evaluated in the specified Jordan-Wigner ordering. + /// + /// # Input + /// ## term + /// A Hamiltonian term (and by implication, its Hermitian conjugate). + /// ## actualPositions + /// A map from site orbital indices to positions, specifying a + /// Jordan-Wigner ordering + /// + /// # Output + /// A new Hamiltonian term, with reordered indices and possibly + /// opposite sign. + /// + /// ## Note + /// Because we return the reordered sequence as a FermionTerm, the + /// listed order of the operators will be shuffled (and the sign + /// adjusted) to match QDK's canonical order. Because it is a + /// HermitionFermionTerm, the reordered sequence gets replaced with its + /// adjoint when that results in lower the canonical ordering. + public static HermitianFermionTerm ReorderedFermionTerm(HermitianFermionTerm term, Dictionary actualPositions) { + return new HermitianFermionTerm(term.Sequence.Select(o=>new FermionOperator(o.Type, actualPositions[o.Index])), + term.Coefficient); + } + + /// # Summary + /// Return a plan which Q# can use to evaluate a Trotter step for a + /// given fermionic swap network, evolving Jordan-Wigner-reordered + /// terms between swap layers, as they become local. + /// + /// # Input + /// ## H + /// A Hamiltonian. + /// ## swapNetwork + /// The network of swaps to be applied. + /// ## startOrder + /// A position-indexed array of site orbital positions, indicating + /// the Jordan-Wigner ordering prior to any swaps being performed. + /// + /// # Output + /// A tuple (network, endOrder), consisting of the following: + /// ## network + /// A list, one layer inter than swapNetwork, containing the local + /// operators to evaluate between each swap layer. Operators are + /// ordered so that a greedy circuit-packing algorithm will produce + /// a reasonably low-depth circuit. + /// ## endOrder + /// A position-indexed list of site orbital indices, indicating the + /// Jordan-Wigner ordering after all swaps are performed. + public static (OperatorNetwork, int[] ) TrotterStepData( + FermionHamiltonian H, + SwapNetwork swapNetwork, + int[] startOrder + ) + { + var opNetwork = new OperatorNetwork {}; + var endOrder = startOrder.ToArray(); + var terms = new TermsDictionary(); + foreach (var (termType, termList) in H.Terms) { + foreach (var (term,termValue) in termList) { + var termSites = ImmutableArray.Create( + term.Sequence.OrderBy(o => o.Index).Select(o => o.Index).Distinct().ToArray() + ); + if (!terms.ContainsKey(termSites)) { + terms[termSites] = new OperatorLayer {}; + } + terms[termSites].Add((term,termValue)); + } + } + + opNetwork.Add(ProcessNetworkLayer(terms, endOrder)); + // Apply each swap layer to the ordering and add layer interactions + foreach (var layer in swapNetwork) { + foreach (var (oldpos,newpos) in layer) { + (endOrder[oldpos], endOrder[newpos]) = (endOrder[newpos], endOrder[oldpos]); + } + opNetwork.Add(ProcessNetworkLayer(terms, endOrder)); + } + return (opNetwork, endOrder); + } + + /// # Summary + /// Return a layer of operators for a network, updating the dictionary + /// of already-applied terms. + /// + /// # Input + /// ## termDict + /// A map from ordered indexed lists to lists of unapplied terms having + /// those indices. + /// ## order + /// A position-indexed array of site orbital indices, indicating the + /// current Jordan-Wigner ordering. + /// + /// # Output + /// A list of local HermetianFermionTerms to evaluate, expressed in + /// the local Jordan-Wigner ordering. + /// + /// # Side effects + /// Terms are removed from termDict as they are applied. + public static OperatorLayer ProcessNetworkLayer( + TermsDictionary termDict, + int[] order) + { + var result = new OperatorLayer {}; + bool productive = true; + while (productive) { + productive = false; + var start = 0; + var end = 1; + while (start < order.Count()) { + while (end <= order.Count()) { + var key = ImmutableArray.Create(order[start..end].OrderBy(o=>o).Distinct().ToArray()); + if (termDict.ContainsKey(key)) { + var (term,coeff) = termDict[key][0]; + result.Add((ReorderedFermionTerm(term, PositionDictionary(order[0..end])), coeff)); + termDict[key].RemoveAt(0); + if (termDict[key].Count() == 0) { + termDict.Remove(key); + } + productive = true; + // find evolutions that can occur in parallel with this one, + // then end the layer. + start = end; + } + end++; + } + start++; + end = start + 1; + } + } + return result; + } + + // using OperatorNetwork = List>; + + /// # Summary + /// Construct an array of arrays of Q# processable Pauli Hamiltonians from the swap operator network. + /// + /// # Input + /// ## network + /// The layer-sorted list of localized evolutions that occur between swap layers + /// ## gatherTerms=false + /// If true, an interaction layer consists of a list containing a + /// single Hamiltonian containing all terms. + /// If false, an interaction layer consists of a list of Hamiltonians, + /// one for each term. + /// + /// # Output + /// A QArray of QArrays of Pauli Hamiltonians in Q# format. Each inner + /// QArray represents a single interaction layer. + /// Q# format for an individual PauliHamiltonian is of type + /// (Double, Int64, JWOptimizedHTerms) + /// and consists of + /// energyOffset: The energy offset (coefficient of the identity summand) + /// nSpinOrbitals: number of spin orbitals + /// terms: QArrays of Hamlitonian terms, organized by term "shape" + + public static QArray> ToQSharpFormat(OperatorNetwork network, bool gatherLayer = false) { + var result = new List>(); + var terms = new JWOptimizedHTerms(); + foreach (var layer in network) { + var resultLayer = new List(); + var H = new FermionHamiltonian(); + foreach (var (term,coeff) in layer) { + H.Add(term,coeff); + if (!gatherLayer) { + (_, _, terms) = H.ToPauliHamiltonian().ToQSharpFormat(); + resultLayer.Add(terms); + H = new FermionHamiltonian(); + } + } + if (H.Terms.Count > 0) { + (_, _, terms) = H.ToPauliHamiltonian().ToQSharpFormat(); + } else { + terms = new JWOptimizedHTerms(); + } + resultLayer.Add(terms); + result.Add(new QArray(resultLayer)); + } + return new QArray>(result); + } + } +} \ No newline at end of file diff --git a/samples/chemistry/FermionicSwap/FermionicSwap/FermionicSwap.csproj b/samples/chemistry/FermionicSwap/FermionicSwap/FermionicSwap.csproj new file mode 100644 index 00000000000..8acc4457a6e --- /dev/null +++ b/samples/chemistry/FermionicSwap/FermionicSwap/FermionicSwap.csproj @@ -0,0 +1,15 @@ + + + + net6.0 + x64 + enable + enable + + + + + + + + diff --git a/samples/chemistry/FermionicSwap/README.md b/samples/chemistry/FermionicSwap/README.md new file mode 100644 index 00000000000..fe021668b00 --- /dev/null +++ b/samples/chemistry/FermionicSwap/README.md @@ -0,0 +1,18 @@ +--- + page_type: sample + languages: + - qsharp + - csharp + products: + - qdk + description: "This sample demonstrates implementation of a fermionic swap network library, performing data processing necessary to construct a swap network in C# and passing the resulting data to Q# code." + urlFragment: validating-quantum-mechanics + --- + # Fermionic swap network implementation. + + This sample demonstrates: + - Implementation of a C# library for constructing fermionic swap networks for Hamiltonians, expressed using QDK data structures. Currently Dense one-body hamiltonians and rectangular lattices are implemented. + - Q# code that evolves Trotterized Hamiltonians as swap networks. + - A modified version of the SimulateHubbardHamiltonian sample which uses swap networks. + + Fermionic swap networks reduce the cost of Trotterized Hamiltonian evolution by amortizing Jordan-Wigner circuit-weight costs, repeatedly permuting the Jordan-Wigner ordering and performing evolutions when they admit low-weight circuits. Fermionic swap networks are described [here](https://arxiv.org/abs/1711.04789). The network used for Hubbard hamiltonians is described [here](https://arxiv.org/abs/2001.08324). \ No newline at end of file diff --git a/samples/chemistry/FermionicSwap/SimulateHubbardHamiltonian/HubbardSimulation.qs b/samples/chemistry/FermionicSwap/SimulateHubbardHamiltonian/HubbardSimulation.qs new file mode 100644 index 00000000000..b3ae4154ed8 --- /dev/null +++ b/samples/chemistry/FermionicSwap/SimulateHubbardHamiltonian/HubbardSimulation.qs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Chemistry.Samples.FermionicSwapHubbard { + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Characterization; + open Microsoft.Quantum.Convert; + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Chemistry.JordanWigner; + open Microsoft.Quantum.Simulation; + + open FermionicSwap; + + + ////////////////////////////////////////////////////////////////////////// + // Using Trotterization ////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////// + + /// # Summary + /// We define an initial state of the Hamiltonian here. + operation HubbardHalfFillingStatePrep (nFilling : Int, qubits : Qubit[]) : Unit { + ApplyToEachCA(X, qubits[0..(nFilling / 2 - 1)]); + } + + + /// # Summary + /// We can now use Canon's phase estimation algorithms to + /// learn the ground state energy using the above simulation. + operation GetEnergy (nQubits : Int, swapNetwork : (Int,Int)[][], localEvolutions : JWOptimizedHTerms[][], nBitsPrecision : Int, trotterStepSize : Double) : (Double, Double) { + // old line + // let (nSpinOrbitals, data, notUsedInThisSample, energyShift) = localEvolutions!; + let energyShift=0.; + + // We use a Product formula, also known as `Trotterization` to + // simulate the Hamiltonian. + // old lines: + // let trotterOrder = 1; + // let (nQubits, (rescaleFactor, oracle)) = TrotterStepOracle(qSharpData, trotterStepSize, trotterOrder); + let generator = FermionicSwapEvolutionGenerator(swapNetwork, localEvolutions); + let oracle = FermionicSwapEvolveUnderGenerator(generator, trotterStepSize, 2.*trotterStepSize, _); + let statePrep = HubbardHalfFillingStatePrep(nQubits, _); + let phaseEstAlgorithm = RobustPhaseEstimation(nBitsPrecision, _, _); + let estPhase = EstimateEnergy(nQubits, statePrep, oracle, phaseEstAlgorithm); + let estEnergy = estPhase / trotterStepSize + energyShift; + return (estPhase, estEnergy); + } + +} diff --git a/samples/chemistry/FermionicSwap/SimulateHubbardHamiltonian/Program.cs b/samples/chemistry/FermionicSwap/SimulateHubbardHamiltonian/Program.cs new file mode 100644 index 00000000000..a1cb3861318 --- /dev/null +++ b/samples/chemistry/FermionicSwap/SimulateHubbardHamiltonian/Program.cs @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#region Using Statements +// We will need several different libraries in this sample. +// Here, we expose these libraries to our program using the +// C# "using" statement, similar to the Q# "open" statement. + +// We will use the data model implemented by the Quantum Development Kit chemistry +// libraries. This model defines what a fermionic Hamiltonian is, and how to +// represent Hamiltonians on disk. +using Microsoft.Quantum.Chemistry.OrbitalIntegrals; +using Microsoft.Quantum.Chemistry.Fermion; +using Microsoft.Quantum.Chemistry.QSharpFormat; + +// To perform the simulation, we'll use the full state simulator provided with +// the Quantum Development Kit. +using Microsoft.Quantum.Simulation.Simulators; + +// The System namespace provides a number of useful built-in +// types and methods that we'll use throughout this sample. +using System; + +// We use this for convenience methods for manipulating arrays. +using System.Linq; +using static FermionicSwap.FSTools; +#endregion + +namespace Microsoft.Quantum.Chemistry.Samples.FermionicSwapHubbard +{ + class Program + { + static void Main(string[] args) + { + ////////////////////////////////////////////////////////////////////////// + // Introduction ////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////// + + // In this example, we will estimate the ground state energy of + // 1D Hubbard Hamiltonian using the quantum chemistry library. + + // The 1D Hubbard model has `n` sites. Let `i` be the site index, + // `s` = 1,0 be the spin index, where 0 is up and 1 is down, `t` be the + // hopping coefficient, `u` the repulsion coefficient, and aᵢₛ the fermionic + // annihilation operator on the fermion indexed by `(i,s)`. The Hamiltonian + // of this model is + // + // H ≔ - t Σᵢ (a†ᵢₛ aᵢ₊₁ₛ + a†ᵢ₊₁ₛ aᵢₛ) + u Σᵢ a†ᵢ₀ a†ᵢ₁ aᵢ₁ aᵢ₀ + // + // Note that we use closed boundary conditions. + + #region Building the 2D-Hubbard Hamiltonian through orbital integrals + + var t = 0.2; // hopping coefficient + var u = 1.0; // repulsion coefficient + var nSites = 3; // number of sites along edge of lattice; + // Construct Hubbard Hamiltonian + var hubbardOrbitalIntegralHamiltonian = new OrbitalIntegralHamiltonian(); + + foreach (var i in Enumerable.Range(0, nSites*nSites)) + { + if ((i + 1) % nSites!= 0) { + hubbardOrbitalIntegralHamiltonian.Add(new OrbitalIntegral(new[] { i, i + 1}, -t)); + } + if (i < nSites*(nSites-1)) { + hubbardOrbitalIntegralHamiltonian.Add(new OrbitalIntegral(new[] { i, i + nSites }, -t)); + } + hubbardOrbitalIntegralHamiltonian.Add(new OrbitalIntegral(new[] { i, i, i, i }, u)); + } + + // Create fermion representation of Hamiltonian + // In this case, we use the spin-orbital to integer + // indexing convention `x = orbitalIdx + spin * nSites`; as it + // minimizes the length of Jordan–Wigner strings + var hubbardFermionHamiltonian = hubbardOrbitalIntegralHamiltonian.ToFermionHamiltonian(IndexConvention.HalfUp); + + #endregion + + + #region Estimating energies by simulating quantum phase estimation + // Create Swap network Jordan–Wigner representation of Hamiltonian + //var jordanWignerEncoding = hubbardFermionHamiltonian.ToPauliHamiltonian(); + + // Create data structure to pass to QSharp. + //var qSharpData = jordanWignerEncoding.ToQSharpFormat().Pad(); + var (hubbardStartOrder,swapNetwork) = SpinlessTwoDHubbardNetwork(nSites, nSites); + Console.WriteLine($"Debug: start order: {string.Join(',',hubbardStartOrder)}"); + var (operatorNetwork,_) = TrotterStepData(hubbardFermionHamiltonian, swapNetwork, hubbardStartOrder.ToArray()); + var qSharpData = ToQSharpFormat(operatorNetwork, false); + + Console.WriteLine($"Estimate Hubbard Hamiltonian energy:"); + // Bits of precision in phase estimation. + var bits = 7; + + // Repetitions to find minimum energy. + var reps = 5; + + // Trotter step size + var trotterStep = 0.5; + var qSharpSwapNetwork = swapNetwork.ToQSharpFormat(); + using (var qsim = new QuantumSimulator()) + { + + for (int i = 0; i < reps; i++) + { + // EstimateEnergyByTrotterization + // Name should make clear that it does it by trotterized + var (phaseEst, energyEst) = GetEnergy.Run(qsim, nSites*nSites, qSharpSwapNetwork, qSharpData, bits, trotterStep).Result; + + Console.WriteLine($"Rep #{i}: Energy estimate: {energyEst}; Phase estimate: {phaseEst}"); + } + + } + + #endregion + } + } +} diff --git a/samples/chemistry/FermionicSwap/SimulateHubbardHamiltonian/SimulateHubbardHamiltonian.csproj b/samples/chemistry/FermionicSwap/SimulateHubbardHamiltonian/SimulateHubbardHamiltonian.csproj new file mode 100644 index 00000000000..887fe793dc1 --- /dev/null +++ b/samples/chemistry/FermionicSwap/SimulateHubbardHamiltonian/SimulateHubbardHamiltonian.csproj @@ -0,0 +1,17 @@ + + + + Exe + net6.0 + + + + + + + + + + + + diff --git a/samples/chemistry/FermionicSwap/fswap.sln b/samples/chemistry/FermionicSwap/fswap.sln new file mode 100644 index 00000000000..4fce8efe41e --- /dev/null +++ b/samples/chemistry/FermionicSwap/fswap.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FermionicSwap", "FermionicSwap\FermionicSwap.csproj", "{6C79EA97-3360-45EF-A29F-A4073809649E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FermionicSwap.Tests", "FermionicSwap.Tests\FermionicSwap.Tests.csproj", "{C9C7F716-7DDF-49AB-ABEF-B284648E7FB9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimulateHubbardHamiltonian", "SimulateHubbardHamiltonian\SimulateHubbardHamiltonian.csproj", "{B3B742DB-6FFB-4D0E-8285-1AD0A9CEF42F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6C79EA97-3360-45EF-A29F-A4073809649E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6C79EA97-3360-45EF-A29F-A4073809649E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6C79EA97-3360-45EF-A29F-A4073809649E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6C79EA97-3360-45EF-A29F-A4073809649E}.Release|Any CPU.Build.0 = Release|Any CPU + {C9C7F716-7DDF-49AB-ABEF-B284648E7FB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C9C7F716-7DDF-49AB-ABEF-B284648E7FB9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C9C7F716-7DDF-49AB-ABEF-B284648E7FB9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C9C7F716-7DDF-49AB-ABEF-B284648E7FB9}.Release|Any CPU.Build.0 = Release|Any CPU + {B3B742DB-6FFB-4D0E-8285-1AD0A9CEF42F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B3B742DB-6FFB-4D0E-8285-1AD0A9CEF42F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B3B742DB-6FFB-4D0E-8285-1AD0A9CEF42F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B3B742DB-6FFB-4D0E-8285-1AD0A9CEF42F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal