diff --git a/.gitignore b/.gitignore index 232a25886d2..b815351418e 100644 --- a/.gitignore +++ b/.gitignore @@ -329,4 +329,3 @@ __pycache__/ *.btm.cs *.odx.cs *.xsd.cs - diff --git a/Build/build.ps1 b/Build/build.ps1 index 4cc20d6404e..1fbe7eb1950 100644 --- a/Build/build.ps1 +++ b/Build/build.ps1 @@ -40,6 +40,9 @@ Build-One 'publish' '../MachineLearning.sln' Write-Host "##[info]Build Jupyter magic library" Build-One 'publish' '../Magic.sln' +Write-Host "##[info]Build QAOA library" +Build-One 'publish' '../Qaoa.sln' + if (-not $all_ok) { throw "At least one test failed execution. Check the logs." } \ No newline at end of file diff --git a/Build/manifest.ps1 b/Build/manifest.ps1 index fe82d57e8ef..8432922ed7e 100644 --- a/Build/manifest.ps1 +++ b/Build/manifest.ps1 @@ -9,7 +9,8 @@ "Microsoft.Quantum.Standard.Visualization", "Microsoft.Quantum.Chemistry", "Microsoft.Quantum.Numerics", - "Microsoft.Quantum.MachineLearning" + "Microsoft.Quantum.MachineLearning", + "Microsoft.Quantum.Qaoa" ); Assemblies = @( ".\Standard\src\bin\$Env:BUILD_CONFIGURATION\netstandard2.1\Microsoft.Quantum.Standard.dll", @@ -18,6 +19,7 @@ ".\MachineLearning\src\bin\$Env:BUILD_CONFIGURATION\netstandard2.1\Microsoft.Quantum.MachineLearning.dll", ".\Chemistry\src\DataModel\bin\$Env:BUILD_CONFIGURATION\netstandard2.1\Microsoft.Quantum.Chemistry.DataModel.dll", ".\Chemistry\src\Runtime\bin\$Env:BUILD_CONFIGURATION\netstandard2.1\Microsoft.Quantum.Chemistry.Runtime.dll", - ".\Chemistry\src\Tools\bin\$Env:BUILD_CONFIGURATION\netcoreapp3.1\qdk-chem.dll" + ".\Chemistry\src\Tools\bin\$Env:BUILD_CONFIGURATION\netcoreapp3.1\qdk-chem.dll", + ".\Qaoa\src\bin\$Env:BUILD_CONFIGURATION\netcoreapp3.1\Microsoft.Quantum.Qaoa.dll" ) | ForEach-Object { Get-Item (Join-Path $PSScriptRoot ".." $_) }; } | Write-Output; diff --git a/Build/pack.ps1 b/Build/pack.ps1 index e4e1af0397a..7926480906b 100644 --- a/Build/pack.ps1 +++ b/Build/pack.ps1 @@ -75,6 +75,9 @@ if ($Env:ENABLE_PYTHON -eq "false") { Pack-Wheel '../Python/qsharp' } +Write-Host "##[info]Pack QAOA library" +Pack-One '../QAOA/src/Qaoa.csproj' + if (-not $all_ok) { throw "At least one test failed execution. Check the logs." } diff --git a/Build/test.ps1 b/Build/test.ps1 index 5337d4b0fba..5409ebef16a 100644 --- a/Build/test.ps1 +++ b/Build/test.ps1 @@ -40,6 +40,15 @@ Test-One '../Chemistry/tests/JupyterTests/JupyterTests.csproj' Write-Host "##[info]Testing Numerics/tests/NumericsTests.csproj" Test-One '../Numerics/tests/NumericsTests.csproj' +Write-Host "##[info]Testing Qaoa/tests/QaoaTests/QaoaTests.csproj" +Test-One '../Qaoa/tests/QaoaTests/QaoaTests.csproj' + +Write-Host "##[info]Testing Qaoa/tests/HybridQaoaTests/HybridQaoaTests.csproj" +Test-One '../Qaoa/tests/HybridQaoaTests/HybridQaoaTests.csproj' + +Write-Host "##[info]Testing Qaoa/tests/JupyterTests/JupyterTests.csproj" +Test-One '../Qaoa/tests/JupyterTests/JupyterTests.csproj' + if (-not $all_ok) { throw "At least one test failed execution. Check the logs." } diff --git a/QAOA.sln b/QAOA.sln new file mode 100644 index 00000000000..f0db189db79 --- /dev/null +++ b/QAOA.sln @@ -0,0 +1,53 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29920.165 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Qaoa", "QAOA\src\Qaoa.csproj", "{159D83BF-56A2-41B7-951B-35292F5A9F23}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HybridQaoaTests", "QAOA\tests\HybridQaoaTests\HybridQaoaTests.csproj", "{1623F038-210A-4BA2-8D4E-9AF611610829}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JupyterTests", "QAOA\tests\JupyterTests\JupyterTests.csproj", "{3C8B4AF6-5495-4F23-B7B2-634E5A667837}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QaoaTests", "QAOA\tests\QaoaTests\QaoaTests.csproj", "{208C0012-D62F-4F29-8738-394A62BBE842}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{AD73B607-0EF5-4C11-8832-22D584939780}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{80082123-4392-4F0E-8F87-26183C620693}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {159D83BF-56A2-41B7-951B-35292F5A9F23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {159D83BF-56A2-41B7-951B-35292F5A9F23}.Debug|Any CPU.Build.0 = Debug|Any CPU + {159D83BF-56A2-41B7-951B-35292F5A9F23}.Release|Any CPU.ActiveCfg = Release|Any CPU + {159D83BF-56A2-41B7-951B-35292F5A9F23}.Release|Any CPU.Build.0 = Release|Any CPU + {1623F038-210A-4BA2-8D4E-9AF611610829}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1623F038-210A-4BA2-8D4E-9AF611610829}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1623F038-210A-4BA2-8D4E-9AF611610829}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1623F038-210A-4BA2-8D4E-9AF611610829}.Release|Any CPU.Build.0 = Release|Any CPU + {3C8B4AF6-5495-4F23-B7B2-634E5A667837}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C8B4AF6-5495-4F23-B7B2-634E5A667837}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C8B4AF6-5495-4F23-B7B2-634E5A667837}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C8B4AF6-5495-4F23-B7B2-634E5A667837}.Release|Any CPU.Build.0 = Release|Any CPU + {208C0012-D62F-4F29-8738-394A62BBE842}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {208C0012-D62F-4F29-8738-394A62BBE842}.Debug|Any CPU.Build.0 = Debug|Any CPU + {208C0012-D62F-4F29-8738-394A62BBE842}.Release|Any CPU.ActiveCfg = Release|Any CPU + {208C0012-D62F-4F29-8738-394A62BBE842}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {159D83BF-56A2-41B7-951B-35292F5A9F23} = {AD73B607-0EF5-4C11-8832-22D584939780} + {1623F038-210A-4BA2-8D4E-9AF611610829} = {80082123-4392-4F0E-8F87-26183C620693} + {3C8B4AF6-5495-4F23-B7B2-634E5A667837} = {80082123-4392-4F0E-8F87-26183C620693} + {208C0012-D62F-4F29-8738-394A62BBE842} = {80082123-4392-4F0E-8F87-26183C620693} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {09481F99-E1AC-44CE-A892-7331820661DF} + EndGlobalSection +EndGlobal diff --git a/QAOA/README.md b/QAOA/README.md new file mode 100644 index 00000000000..b6bcee5a003 --- /dev/null +++ b/QAOA/README.md @@ -0,0 +1,31 @@ +# Microsoft Quantum QAOA Library + +This library provides a hybrid quantum-classical algorithm for solving optimization problems. +It includes a Q# implementation of the Quantum Approximate Optimization Algorithm ([QAOA](https://arxiv.org/abs/1411.4028)) together with a classical optimizer in C#. +The classical optimizer uses a quantum objective function to choose hopefully optimal parameters for running the QAOA. +The quantum objective function is currently evaluated on a simulator backend provided by Q#. + +## Building and testing ## + +The quantum QAOA library consists of a cross-platform project built using [.NET Core](https://docs.microsoft.com/en-us/dotnet/core/): + +- **Qaoa.csproj**: Q# and C# sources used to encode combinatorial optimization problems and run QAOA and hybrid QAOA algorithms. + +The high-level diagram of the implementation (notation comes from the [QAOA paper](https://arxiv.org/abs/1411.4028)): + +[![QAOA diagram](https://i.postimg.cc/sgryqr80/IMG-0202.jpg)](https://postimg.cc/XpQTBTnw) + +Once .NET Core is installed, you may build and run its tests by executing the following from a command line: + +```bash +dotnet test tests +``` + +For more details about creating and running tests in Q#, +see the [Testing and debugging](https://docs.microsoft.com/quantum/quantum-techniques-testinganddebugging) +section of the [developer's guide](https://docs.microsoft.com/quantum). + +Dependencies: + +1) [Q# and Microsoft Quantum Libraries](https://docs.microsoft.com/en-us/quantum/language/) +2) [C# Accord Math library](http://accord-framework.net/docs/html/N_Accord_Math.htm) diff --git a/QAOA/src/Jupyter/HybridQaoaMagic.cs b/QAOA/src/Jupyter/HybridQaoaMagic.cs new file mode 100644 index 00000000000..46ee6dde8ab --- /dev/null +++ b/QAOA/src/Jupyter/HybridQaoaMagic.cs @@ -0,0 +1,155 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Qaoa.Jupyter +{ + using System; + using System.Threading.Tasks; + using Microsoft.Jupyter.Core; + using Microsoft.Quantum.Qaoa.QaoaHybrid; + using Newtonsoft.Json; + + /// + /// This class makes it possible to run the hybrid QAOA with user-defined input parameters in languages other than C# that are supported by this mechanism. + /// + public class HybridQaoaRunMagic : MagicSymbol + { + + public HybridQaoaRunMagic() + { + this.Name = $"%qaoa.hybridqaoa.run"; + this.Documentation = new Documentation() + { + Summary = "Runs a hybrid QAOA algorithm with a classical optimizer that chooses input angles. QAOA parameters are provided as JSON input. Initial beta and gamma coefficients are provided by a user." + }; + this.Kind = SymbolKind.Magic; + this.Execute = this.Run; + } + + /// + /// List of arguments + /// + public class Arguments + { + /// + /// Number of iterations in the fidelity sampling. + /// + [JsonProperty(PropertyName = "number_of_iterations")] + public int NumberOfIterations { get; set; } + + /// + /// A parameter related to the depth of a QAOA circuit. + /// + [JsonProperty(PropertyName = "NHamiltonianApplications")] + public int NHamiltonianApplications { get; set; } + + /// + /// Description of a combinatorial problem to be solved. + /// + [JsonProperty(PropertyName = "problem_instance")] + public QaoaProblemInstance QaoaProblemInstance { get; set; } + + /// + /// Initial QAOA parameters (beta and gamma angles). + /// + [JsonProperty(PropertyName = "initial_qaoa_parameters")] + public QaoaParameters InitialQaoaParameters { get; set; } + + /// + /// Flag whether optimization should be logged into a file. + /// + [JsonProperty(PropertyName = "should_log")] + public Boolean ShouldLog { get; set; } = false; + } + + /// + /// Runs a hybrid QAOA. + /// + public async Task Run(string input, IChannel channel) + { + if (string.IsNullOrWhiteSpace(input)) + { + channel.Stderr("Please provide correct arguments"); + return ExecuteStatus.Error.ToExecutionResult(); + } + + var args = JsonConvert.DeserializeObject(input); + + var hybridQaoa = new HybridQaoa(args.NumberOfIterations, args.NHamiltonianApplications, args.QaoaProblemInstance, args.ShouldLog); + + return hybridQaoa.RunOptimization(args.InitialQaoaParameters).ToExecutionResult(); + } + } + + /// + /// This class makes it possible to run the hybrid QAOA with randomly initialized input parameters in languages other than C# that are supported by this mechanism. + /// + public class HybridQaoaWithRandomParametersRunMagic : MagicSymbol + { + + public HybridQaoaWithRandomParametersRunMagic() + { + this.Name = $"%qaoa.hybridqaoa.random.params.run"; + this.Documentation = new Documentation() + { + Summary = "Runs a hybrid QAOA algorithm with a classical optimizer that chooses input angles. QAOA parameters are provided as a JSON. Initial beta and gamma parameters are chosen randomly." + }; + this.Kind = SymbolKind.Magic; + this.Execute = this.Run; + } + + /// + /// List of arguments + /// + public class Arguments + { + /// + /// Number of iterations in the fidelity sampling. + /// + [JsonProperty(PropertyName = "number_of_iterations")] + public int NumberOfIterations { get; set; } + + /// + /// A parameter related to the depth of a QAOA circuit. + /// + [JsonProperty(PropertyName = "NHamiltonianApplications")] + public int NHamiltonianApplications { get; set; } + + /// + /// Description of a combinatorial problem to be solved. + /// + [JsonProperty(PropertyName = "problem_instance")] + public QaoaProblemInstance QaoaProblemInstance { get; set; } + + /// + /// Number of random starting points in the angles parameters spaces. + /// + [JsonProperty(PropertyName = "number_of_random_starting_points")] + public int NumberOfRandomStartingPoints { get; set; } = 1; + + /// + /// Flag whether optimization should be logged into a file. + /// + [JsonProperty(PropertyName = "should_log")] + public Boolean ShouldLog { get; set; } = false; + } + + /// + /// Runs a hybrid QAOA. + /// + public async Task Run(string input, IChannel channel) + { + if (string.IsNullOrWhiteSpace(input)) + { + channel.Stderr("Please provide correct arguments"); + return ExecuteStatus.Error.ToExecutionResult(); + } + + var args = JsonConvert.DeserializeObject(input); + + var hybridQaoa = new HybridQaoa(args.NumberOfIterations, args.NHamiltonianApplications, args.QaoaProblemInstance, args.ShouldLog); + + return hybridQaoa.RunOptimization(args.NumberOfRandomStartingPoints).ToExecutionResult(); + } + } +} diff --git a/QAOA/src/Jupyter/HybridQaoaParametersMagic.cs b/QAOA/src/Jupyter/HybridQaoaParametersMagic.cs new file mode 100644 index 00000000000..279d81dfb8b --- /dev/null +++ b/QAOA/src/Jupyter/HybridQaoaParametersMagic.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Qaoa.Jupyter +{ + using System.Threading.Tasks; + using Microsoft.Jupyter.Core; + using Microsoft.Quantum.Qaoa.QaoaHybrid; + using Newtonsoft.Json; + + /// + /// This class makes it possible to initialize hybrid QAOA input parameters in languages other than C# that are supported by this mechanism. + /// + public class HybridQaoaParametersMagic : MagicSymbol + { + public HybridQaoaParametersMagic() + { + this.Name = $"%qaoa.hybridqaoa.create.parameters"; + this.Documentation = new Documentation() + { + Summary = "Prepares a QAOA parameters object that serves as one of arguments to %qaoa.hybridqaoa.run." + }; + this.Kind = SymbolKind.Magic; + this.Execute = this.Run; + } + + /// + /// List of arguments. + /// + public class Arguments + { + /// + /// Betas QAOA coefficients. + /// + [JsonProperty(PropertyName = "beta")] + public double[] Beta { get; set; } + + /// + /// Gammas QAOA coefficients. + /// + [JsonProperty(PropertyName = "gamma")] + public double[] Gamma { get; set; } + } + + /// + /// Prepares a QaoaParameters object for a hybrid QAOA. + /// + public async Task Run(string input, IChannel channel) + { + if (string.IsNullOrWhiteSpace(input)) + { + channel.Stderr("Please provide correct arguments"); + return ExecuteStatus.Error.ToExecutionResult(); + } + + var args = JsonConvert.DeserializeObject(input); + + var qaoaParameters = new QaoaParameters(args.Beta, args.Gamma); + + return qaoaParameters.ToExecutionResult(); + } + } +} diff --git a/QAOA/src/Jupyter/HybridQaoaProblemInstanceMagic.cs b/QAOA/src/Jupyter/HybridQaoaProblemInstanceMagic.cs new file mode 100644 index 00000000000..07dcbf749cb --- /dev/null +++ b/QAOA/src/Jupyter/HybridQaoaProblemInstanceMagic.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Qaoa.Jupyter +{ + using System.Threading.Tasks; + using Microsoft.Jupyter.Core; + using Microsoft.Quantum.Qaoa.QaoaHybrid; + using Newtonsoft.Json; + + /// + /// This class makes it possible to create a problem instance for the hybrid QAOA in languages other than C# that are supported by this mechanism. + /// + public class HybridQaoaProblemInstanceMagic : MagicSymbol + { + public HybridQaoaProblemInstanceMagic() + { + this.Name = $"%qaoa.hybridqaoa.create.problem.instance"; + this.Documentation = new Documentation() + { + Summary = "Prepares a problem instance object that serves as one of arguments to %qaoa.hybridqaoa.run." + }; + this.Kind = SymbolKind.Magic; + this.Execute = this.Run; + } + + /// + /// List of arguments. + /// + public class Arguments + { + /// + /// Coefficients for one-local Hamiltonian terms. Assuming that a solution to a combinatorial optimization problem can be encoded into n bits (which then corresponds to an encoding into n qubits), this array must be of length n. The i-th coefficient in an array corresponds to the coefficient of a term \sigma_i^z. + /// + [JsonProperty(PropertyName = "one_local_hamiltonian_coefficients")] + public double[] OneLocalHamiltonianCoefficients { get; set; } + + /// + /// Coefficients for two-local Hamiltonian terms. Assuming that a solution to a combinatorial optimization problem can be encoded into n bits (which then corresponds to an encoding into n qubits), this array must be of length n^2. The (i*n+j)-th coefficient in an array corresponds to the coefficient of a term \sigma_i^z\sigma_j^z (other coefficients in an array can take any value of type double). + /// + [JsonProperty(PropertyName = "two_local_hamiltonian_coefficients")] + public double[] TwoLocalHamiltonianCoefficients { get; set; } + + /// + /// Size of the combinatorial problem in bits. + /// + [JsonProperty(PropertyName = "problem_size_in_bits")] + public int ProblemSizeInBits { get; set; } + } + + /// + /// Prepares a QaoaProblemInstance object for a hybrid QAOA. + /// + public async Task Run(string input, IChannel channel) + { + if (string.IsNullOrWhiteSpace(input)) + { + channel.Stderr("Please provide correct arguments"); + return ExecuteStatus.Error.ToExecutionResult(); + } + + var args = JsonConvert.DeserializeObject(input); + + var problemInstance = new QaoaProblemInstance(args.OneLocalHamiltonianCoefficients, args.TwoLocalHamiltonianCoefficients); + + return problemInstance.ToExecutionResult(); + } + } +} diff --git a/QAOA/src/QAOA.csproj b/QAOA/src/QAOA.csproj new file mode 100644 index 00000000000..6bece22fd8c --- /dev/null +++ b/QAOA/src/QAOA.csproj @@ -0,0 +1,15 @@ + + + + Library + netcoreapp3.1 + Microsoft.Quantum.Qaoa + + + + + + + + + diff --git a/QAOA/src/Qaoa/EvolutionOperations.qs b/QAOA/src/Qaoa/EvolutionOperations.qs new file mode 100644 index 00000000000..f79d28f8672 --- /dev/null +++ b/QAOA/src/Qaoa/EvolutionOperations.qs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Qaoa { + + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Arrays; + + /// # Summary + /// Implements a unitary based on the mixing Hamiltonian and applies it to qubits. + /// + /// # Input + /// ## qubits + /// Qubits that will be transformed by a unitary. + /// ## beta + /// Coefficent for the unitary based on the mixing Hamiltonian. + /// # References + /// This implementation in inspired by https://github.com/stephenjordan/qaoa_tsp. + operation EvolveWithMixingHamiltonian(beta: Double, qubits: Qubit[]) : Unit is Adj + Ctl { + ApplyToEachCA(R(PauliX, -2.0 * beta, _), qubits); + } + + /// # Summary + /// Implements a unitary based on the objective function Hamiltonian and applies it to qubits. + /// + /// # Input + /// ## gamma + /// Coefficent for the unitary based on the objective function Hamiltonian. + /// ## oneLocalHamiltonianCoefficients + /// Array of 1-local coefficents of the objective function Hamiltonian. Assuming that a solution to a combinatorial optimization problem can be encoded into n bits (which then corresponds to an encoding into n qubits), this array must be of length n. The i-th coefficient in an array corresponds to the coefficient of a term \sigma_i^z. + /// ## twoLocalHamiltonianCoefficients + /// Array of 2-local coefficents of the objective function Hamiltonian. Assuming that a solution to a combinatorial optimization problem can be encoded into n bits (which then corresponds to an encoding into n qubits), this array must be of length n^2. The (i*n+j)-th coefficient in an array corresponds to the coefficient of a term \sigma_i^z\sigma_j^z (other coefficients in an array can take any value of type double). + /// ## qubits + /// Qubits that will be transformed by a unitary. + /// # References + /// This implementation in inspired by https://github.com/stephenjordan/qaoa_tsp. + operation EvolveWithObjectiveHamiltonian(gamma: Double, oneLocalHamiltonianCoefficients: Double[], twoLocalHamiltonianCoefficients: Double[], qubits: Qubit[]) : Unit is Adj + Ctl{ + let numberOfQubits = Length(qubits); + for(i in 0..numberOfQubits-1) { + R(PauliZ, 2.0*gamma*oneLocalHamiltonianCoefficients[i],qubits[i]); + } + for(i in 0..numberOfQubits-1) { + for (j in i+1..numberOfQubits-1) { + RunPhaseKickback(2.0*gamma*twoLocalHamiltonianCoefficients[i*numberOfQubits+j], Subarray([i,j], qubits)); + } + } + } +} diff --git a/QAOA/src/Qaoa/PhaseKickback.qs b/QAOA/src/Qaoa/PhaseKickback.qs new file mode 100644 index 00000000000..13d19e49e5d --- /dev/null +++ b/QAOA/src/Qaoa/PhaseKickback.qs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Qaoa { + + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Measurement; + open Microsoft.Quantum.Arrays; + + /// # Summary + /// Runs a phase kickback routine on an auxiliary qubit given indices of control qubits and a phase. + /// + /// # Input + /// ## phaseExponent + /// Phase to be applied. + /// ## controlQubits + /// Qubits that are to aquire a phase. + /// ## auxiliaryQubit + /// auxiliary qubit. + operation RunPhaseKickback(phaseExponent: Double, controlQubits: Qubit[]) : Unit is Adj + Ctl { + using(auxiliaryQubit = Qubit()){ + within { + ApplyToEachCA( + CNOT(_, auxiliaryQubit), + controlQubits + ); + } apply { + R(PauliZ, phaseExponent, auxiliaryQubit); + } + } + } +} \ No newline at end of file diff --git a/QAOA/src/Qaoa/QaoaRunner.qs b/QAOA/src/Qaoa/QaoaRunner.qs new file mode 100644 index 00000000000..d2324035785 --- /dev/null +++ b/QAOA/src/Qaoa/QaoaRunner.qs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Qaoa { + + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Measurement; + open Microsoft.Quantum.Arrays; + open Microsoft.Quantum.Convert; + + + /// # Summary + /// Prepares and measures the quantum state in the QAOA. + /// + /// # Input + /// ## problemSize + /// Number of qubits. + /// ## betas + /// Vector of coefficents for the unitary based on the mixing Hamiltonian. + /// ## gammas + /// Vector of coefficents for the unitary based on the objective function Hamiltonian. + /// ## oneLocalHamiltonianCoefficients + /// Array of 1-local coefficents of the objective function Hamiltonian. Assuming that a solution to a combinatorial optimization problem can be encoded into n bits (which then corresponds to an encoding into n qubits), this array must be of length n. The i-th coefficient in an array corresponds to the coefficient of a term \sigma_i^z. + /// ## twoLocalHamiltonianCoefficients + /// Array of 2-local coefficents of the objective function Hamiltonian. Assuming that a solution to a combinatorial optimization problem can be encoded into n bits (which then corresponds to an encoding into n qubits), this array must be of length n^2. The (i*n+j)-th coefficient in an array corresponds to the coefficient of a term \sigma_i^z\sigma_j^z (other coefficients in an array can take any value of type double). + /// + /// # Output + /// Array of boolean values that represent results of measurements on the QAOA state. + /// + /// # References + /// This implementation in inspired by https://github.com/stephenjordan/qaoa_tsp. + /// + /// # Examples + /// Suppose we want to solve a MAXCUT problem on a simple weighted graph with two connected vertices. Then, problemSize = 2, the corresponding quantum Hamiltonian is \sigma_0^z\sigma_1^z and is expressed as oneLocalHamiltonianCoefficients = new Double[] {0, 0} and twoLocalHamiltonianCoefficients = new Double[]{0, 1, 0, 0}. betas and gammas are arrays of p elements each (their actual values are difficult to choose upfront and p is related to the depth of the QAOA circuit). + operation RunQaoa(problemSize: Int, betas: Double[], gammas: Double[], oneLocalHamiltonianCoefficients: Double[], twoLocalHamiltonianCoefficients: Double[]) : Bool[] { + + mutable result = new Bool[problemSize]; + using (qubits = Qubit[problemSize]) { + ApplyToEach(H, qubits); + for ((beta, gamma) in Zip(betas, gammas)) { + EvolveWithObjectiveHamiltonian(gamma, oneLocalHamiltonianCoefficients, twoLocalHamiltonianCoefficients, qubits); + EvolveWithMixingHamiltonian(beta, qubits); + } + return ResultArrayAsBoolArray(ForEach(MResetZ, qubits)); + } + } +} diff --git a/QAOA/src/QaoaHybrid/Helpers/ArrayToStringConverter.cs b/QAOA/src/QaoaHybrid/Helpers/ArrayToStringConverter.cs new file mode 100644 index 00000000000..53efdd1de75 --- /dev/null +++ b/QAOA/src/QaoaHybrid/Helpers/ArrayToStringConverter.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Qaoa.QaoaHybrid.Helpers +{ + using System.Text; + + public class ArrayToStringConverter + { + /// + /// Converts an array of bools to a boolean string. + /// + /// + /// An array of bools. + /// + /// + /// A boolean string. + /// + public static string ConvertBoolArrayToString(bool[] boolArray) + { + var sb = new StringBuilder(); + foreach (var b in boolArray) + { + sb.Append(b ? "1" : "0"); + } + + return sb.ToString(); + } + } +} diff --git a/QAOA/src/QaoaHybrid/Helpers/ModeFinder.cs b/QAOA/src/QaoaHybrid/Helpers/ModeFinder.cs new file mode 100644 index 00000000000..67f763eb43b --- /dev/null +++ b/QAOA/src/QaoaHybrid/Helpers/ModeFinder.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Qaoa.QaoaHybrid.Helpers +{ + using System.Collections.Generic; + using System.Linq; + + public class ModeFinder + { + /// + /// Return the most common boolean string from a list of boolean values. + /// + /// + /// List of boolean values. + /// + /// + /// The most common boolean string. + /// + public static bool[] FindModeInBoolList(List list) + { + var counter = new Dictionary(); + foreach (var boolString in list.Select(ArrayToStringConverter.ConvertBoolArrayToString)) + { + if (counter.ContainsKey(boolString)) + { + counter[boolString] += 1; + } + else + { + counter[boolString] = 1; + } + } + + var result = counter.Aggregate((x, y) => x.Value > y.Value ? x : y).Key; + + return result.Select(chr => chr == '1').ToArray(); + } + } +} \ No newline at end of file diff --git a/QAOA/src/QaoaHybrid/Helpers/RandomVectorGenerator.cs b/QAOA/src/QaoaHybrid/Helpers/RandomVectorGenerator.cs new file mode 100644 index 00000000000..b086ce4407c --- /dev/null +++ b/QAOA/src/QaoaHybrid/Helpers/RandomVectorGenerator.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Qaoa.QaoaHybrid.Helpers +{ + using System; + + public class RandomVectorGenerator + { + /// + /// Returns a vector of random doubles in a range from 0 to maximum. + /// + /// + /// Length of a random vector. + /// + /// + /// Maximum value of a random double. + /// + /// + /// A random vector of doubles. + /// + public static double[] GenerateRandomVector(int length, double maximumValue) + { + var rand = new Random(); + double[] randomVector = new double[length]; + for (var i = 0; i < length; i++) + { + randomVector[i] = maximumValue * rand.NextDouble(); + } + + return randomVector; + } + } +} diff --git a/QAOA/src/QaoaHybrid/HybridQaoa.cs b/QAOA/src/QaoaHybrid/HybridQaoa.cs new file mode 100644 index 00000000000..f877b424208 --- /dev/null +++ b/QAOA/src/QaoaHybrid/HybridQaoa.cs @@ -0,0 +1,264 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Threading.Tasks; +using Microsoft.Jupyter.Core; + +#nullable enable + +namespace Microsoft.Quantum.Qaoa.QaoaHybrid + +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Accord.Math.Optimization; + using Microsoft.Quantum.Qaoa; + using Microsoft.Quantum.Simulation.Core; + using Microsoft.Quantum.Simulation.Simulators; + using Microsoft.Quantum.Qaoa.QaoaHybrid.Helpers; + + /// + /// This class runs a hybrid (quantum-classical) QAOA given an instance of a combinatorial optimization problem encoded into a 2-local Hamiltonian. The classical part is used for optimizing QAOA input parameters and is implemented using the Cobyla optimizer. QAOA input parameters can be optionally specified by a user and they will be treated as a starting point for optimization. Otherwise, input parameters are initialized randomly (possibly many times, as specified by the numberOfRandomStartingPoints variable). + /// + public class HybridQaoa + { + private readonly int numberOfIterations; + private readonly int NHamiltonianApplications; + private readonly QaoaProblemInstance qaoaProblemInstance; + private readonly bool shouldLog; + private QaoaSolution solution; + private QaoaLogger? logger; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The number of iterations for sampling the average value of the objective function Hamiltonian. + /// + /// + /// A parameter related to the depth of a QAOA circuit. It corresponds to the number of times evolution operators are applied. + /// + /// + /// An object that describes a combinatorial optimization problem to be solved by the algorithm. + /// + /// + /// A flag that specifies whether a log from the hybrid QAOA should be saved in a text file. + /// + public HybridQaoa(int numberOfIterations, int nHamiltonianApplications, QaoaProblemInstance qaoaProblemInstance, bool shouldLog = false) + { + this.numberOfIterations = numberOfIterations; + this.NHamiltonianApplications = nHamiltonianApplications; + this.qaoaProblemInstance = qaoaProblemInstance; + this.shouldLog = shouldLog; + } + + /// + /// Calculates the value of the cost function based on costs provided. + /// + /// + /// A binary string. In this context it is a result that we get after measuring the QAOA state. + /// + /// + /// A list of costs for the cost function. + /// + /// + /// The value of the costfunction. + /// + public double EvaluateCostFunction(bool[] result, double[] costs) + { + double costFunctionValue = 0; + for (var i = 0; i < this.qaoaProblemInstance.ProblemSizeInBits; i++) + { + costFunctionValue += costs[i] * Convert.ToInt32(result[i]); + } + + return costFunctionValue; + } + + /// + /// Uses a quantum function to get a solution string from the QAOA that relies on the current values of beta and gamma vectors. + /// To get a reasonable estimate for the expectation value of a Hamiltonian that encodes the problem, we run the QAOA many times and calculate the expectation based on solutions obtained. + /// If the expectation of the Hamiltonian is smaller than our current best, we update our best solution to the current solution. The solution vector for the current best solution is the mode of boolean strings that we obtained from the QAOA. + /// + /// + /// Betas and gamma vectors concatenated. + /// + /// + /// The expected value of a Hamiltonian that we calculated in this run. + /// + private double CalculateObjectiveFunctionAsync(double[] concatenatedQaoaParameters) + { + var qaoaParameters = new QaoaParameters(concatenatedQaoaParameters); + + var betas = new QArray(qaoaParameters.Betas); + var gammas = new QArray(qaoaParameters.Gammas); + + var oneLocalHamiltonianCoefficients = new QArray(this.qaoaProblemInstance.OneLocalHamiltonianCoefficients); + var twoLocalHamiltonianCoefficients = new QArray(this.qaoaProblemInstance.TwoLocalHamiltonianCoefficients); + + var (hamiltonianExpectationValue, allSolutionVectors) = this.CalculateHamiltonianExpectation(betas, gammas, oneLocalHamiltonianCoefficients, twoLocalHamiltonianCoefficients).Result; + + this.UpdateBestSolution(hamiltonianExpectationValue, allSolutionVectors, qaoaParameters); + + this.logger?.LogCurrentBestSolution(betas, gammas, this.solution.SolutionHamiltonianValue, this.solution.SolutionVector); + + return hamiltonianExpectationValue; + } + + private async Task<(double HamiltonianExpectationValue, List AllSolutionVectors)> CalculateHamiltonianExpectation(QArray betas, QArray gammas, QArray oneLocalHamiltonianCoefficients, QArray twoLocalHamiltonianCoefficients) + { + var hamiltonianExpectationValue = 0.0; + var allSolutionVectors = new List(); + + using (var qsim = new QuantumSimulator()) + { + + for (var i = 0; i < this.numberOfIterations; i++) + { + var result = await RunQaoa.Run(qsim, this.qaoaProblemInstance.ProblemSizeInBits, betas, gammas, oneLocalHamiltonianCoefficients, twoLocalHamiltonianCoefficients); + allSolutionVectors.Add(result.ToArray()); + var hamiltonianValue = this.qaoaProblemInstance.EvaluateHamiltonian(result.ToArray()); + hamiltonianExpectationValue += hamiltonianValue / this.numberOfIterations; + } + } + + return (hamiltonianExpectationValue, allSolutionVectors); + } + + /// + /// Updates the currently best solution if a new solution is better. + /// + /// + /// Expectation value of a Hamiltonian. + /// + /// + /// A vector of all binary solutions that were found by a QAOA. + /// + /// + /// Betas and gamma coefficients. + /// + private void UpdateBestSolution(double hamiltonianExpectationValue, List allSolutionVectors, QaoaParameters qaoaParameters) + { + if (hamiltonianExpectationValue < this.solution.SolutionHamiltonianValue) + { + var mostProbableSolutionVectorTemp = ModeFinder.FindModeInBoolList(allSolutionVectors); + this.solution.SolutionHamiltonianValue = hamiltonianExpectationValue; + this.solution.SolutionVector = mostProbableSolutionVectorTemp; + this.solution.SolutionQaoaParameters = qaoaParameters; + } + } + + /// + /// Generates constraints for elements in beta and gamma vectors. + /// + /// + /// Generated constraints. + /// + /// + /// For the canonical choice of the mixing Hamiltonian (i.e. the sum of X operators acting on single qubits), the range of values in the beta vector is 0 <= beta_i <= PI. + /// For the objective function Hamiltonian based on Z operators, the range of values in the gamma vector is 0 <= beta_i <= 2PI. + /// + private NonlinearConstraint[] GenerateConstraints() + { + var constraints = new NonlinearConstraint[4*this.NHamiltonianApplications]; + foreach (var i in Enumerable.Range(0, this.NHamiltonianApplications).Select(x => x * 2)) + { + var gammaIndex = (2 * this.NHamiltonianApplications) + i; + constraints[i] = new NonlinearConstraint(2 * this.NHamiltonianApplications, x => x[i / 2] >= 0); + constraints[i + 1] = new NonlinearConstraint(2 * this.NHamiltonianApplications, x => x[i / 2] <= Math.PI); + constraints[gammaIndex] = new NonlinearConstraint(2 * this.NHamiltonianApplications, x => x[gammaIndex / 2] >= 0); + constraints[gammaIndex + 1] = new NonlinearConstraint(2 * this.NHamiltonianApplications, x => x[gammaIndex / 2] <= 2 * Math.PI); + } + + return constraints; + } + + /// + /// Uses a classical optimizer to change beta and gamma parameters so that the objective function is minimized. The optimization is performed some number of times to decrease the chance of getting stuck in a local minimum. + /// + /// + /// A number of times the hybrid QAOA will be ran with randomly initiated values of beta and gamma. The bigger the number, the lower the chance of getting a solution that is a local minimum. + /// + /// + /// Optimal solution to the optimization problem input by the user. + /// + /// + /// Currently used optimizer is Cobyla which is a gradient-free optimization technique. + /// The objective function Hamiltonian is based on Z operators, the range of values in the beta vector is 0 <= beta_i <= PI, the range of values in the gamma vector is 0 <= beta_i <= 2PI. + /// + public QaoaSolution RunOptimization(int numberOfRandomStartingPoints) + { + try + { + if (this.shouldLog) + { + this.logger = new QaoaLogger(); + } + + this.solution = new QaoaSolution(null, double.MaxValue, null); + + Func objectiveFunction = this.CalculateObjectiveFunctionAsync; + + var optimizerObjectiveFunction = new NonlinearObjectiveFunction(2 * this.NHamiltonianApplications, objectiveFunction); + var constraints = this.GenerateConstraints(); + var cobyla = new Cobyla(optimizerObjectiveFunction, constraints); + + for (var i = 0; i < numberOfRandomStartingPoints; i++) + { + var concatenatedQaoaParameters = new QaoaParameters(NHamiltonianApplications).ConcatenatedQaoaParameters; + var success = cobyla.Minimize(concatenatedQaoaParameters); + + this.logger?.LogSuccess(success); + } + } + finally + { + this.logger?.Dispose(); + } + + return this.solution; + } + + /// + /// Uses a classical optimizer to change beta and gamma parameters so that the objective function is minimized. Initial beta and gamma parameters are provided by a user. + /// + /// + /// User-defined initial values of beta and gamma parameters. + /// + /// Optimal solution to the optimization problem input by the user. + /// + /// + /// Currently used optimizer is Cobyla which is a gradient-free optimization technique. + /// The objective function Hamiltonian is based on Z operators, the range of values in the beta vector is 0 <= beta_i <= PI, the range of values in the gamma vector is 0 <= beta_i <= 2PI. + /// + public QaoaSolution RunOptimization(QaoaParameters qaoaParameters) + { + try + { + if (this.shouldLog) + { + this.logger = new QaoaLogger(); + } + + this.solution = new QaoaSolution(null, double.MaxValue, null); + + Func objectiveFunction = this.CalculateObjectiveFunctionAsync; + var optimizerObjectiveFunction = new NonlinearObjectiveFunction(2 * this.NHamiltonianApplications, objectiveFunction); + var constraints = this.GenerateConstraints(); + + var cobyla = new Cobyla(optimizerObjectiveFunction, constraints); + var concatenatedQaoaParameters = qaoaParameters.ConcatenatedQaoaParameters; + var success = cobyla.Minimize(concatenatedQaoaParameters); + + this.logger?.LogSuccess(success); + } + finally + { + this.logger?.Dispose(); + } + + return this.solution; + } + } +} diff --git a/QAOA/src/QaoaHybrid/QaoaLogger.cs b/QAOA/src/QaoaHybrid/QaoaLogger.cs new file mode 100644 index 00000000000..22aad669044 --- /dev/null +++ b/QAOA/src/QaoaHybrid/QaoaLogger.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Qaoa.QaoaHybrid +{ + using System; + using System.IO; + using Microsoft.Quantum.Simulation.Core; + using System.Linq; + + /// + /// This class provides a simple capability for logging intermediate steps of a hybrid QAOA to a text file. + /// + internal class QaoaLogger : IDisposable + { + private readonly StreamWriter logger; + + public QaoaLogger() + { + + logger = new StreamWriter("hybrid_qaoa_log_" + DateTime.Now.ToString("yyyy-dd-M--HH-mm-ss") + ".txt", true); + } + + /// + /// Writes current values of the best fidelity and the best solution vector to a file. + /// + /// + /// Best beta vector so far. + /// + /// + /// Best gamma vector so far. + /// + /// + /// Best value of a Hamiltonian so far. + /// + /// + /// Best solution vector that generates the above value of a Hamiltonian so far. + /// + public void LogCurrentBestSolution(QArray beta, QArray gamma, double bestHamiltonian, bool[] bestVector) + { + + logger.WriteLine("Current beta vector:"); + logger.WriteLine(beta); + logger.WriteLine("Current gamma vector:"); + logger.WriteLine(gamma); + logger.WriteLine("Current best expected value of a Hamiltonian:"); + logger.WriteLine(bestHamiltonian); + logger.WriteLine("Current best solution vector:"); + var bestSolutionVector = string.Join(", ", bestVector.Select(x => x.ToString())); + logger.WriteLine("[" + bestSolutionVector + "]"); + } + + /// + /// Writes to a file whether an optimization finished successfully. + /// + /// + /// A flag that indicates whether an optimization finished successfully. + /// + public void LogSuccess(bool success) + { + logger.WriteLine("Was optimization successful?"); + logger.WriteLine(success); + logger.WriteLine("##################################"); + } + + protected virtual void Dispose(bool disposing) + { + + if (disposing) + { + logger?.Dispose(); + } + } + + /// + /// Closes a logger. + /// + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/QAOA/src/QaoaHybrid/QaoaParameters.cs b/QAOA/src/QaoaHybrid/QaoaParameters.cs new file mode 100644 index 00000000000..fb5aa7afe0e --- /dev/null +++ b/QAOA/src/QaoaHybrid/QaoaParameters.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Qaoa.QaoaHybrid +{ + using System.Linq; + using Newtonsoft.Json; + using Microsoft.Quantum.Qaoa.QaoaHybrid.Helpers; + + public class QaoaParameters + { + public double[] Betas { get; set; } + public double[] Gammas { get; set; } + + public QaoaParameters(double[] concatenatedQaoaParameters) + { + var size = concatenatedQaoaParameters.Length; + var vectorTermsNumber = size / 2; + Betas = concatenatedQaoaParameters[0..vectorTermsNumber]; + Gammas = concatenatedQaoaParameters[vectorTermsNumber..(2 * vectorTermsNumber)]; + } + + [JsonConstructor] + public QaoaParameters(double[] betas, double[] gammas) + { + Betas = betas; + Gammas = gammas; + } + + public QaoaParameters(int p) + { + Betas = RandomVectorGenerator.GenerateRandomVector(p, System.Math.PI); + Gammas = RandomVectorGenerator.GenerateRandomVector(p, 2 * System.Math.PI); + } + + /// + /// Gets betas and gammas vectors as a concatenated vector. + /// + /// + /// Array of concatenated betas and gammas arrays. + /// + public double[] ConcatenatedQaoaParameters => + Betas.Concat(Gammas).ToArray(); + } +} diff --git a/QAOA/src/QaoaHybrid/QaoaProblemInstance.cs b/QAOA/src/QaoaHybrid/QaoaProblemInstance.cs new file mode 100644 index 00000000000..4f7e91d2b01 --- /dev/null +++ b/QAOA/src/QaoaHybrid/QaoaProblemInstance.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Qaoa.QaoaHybrid +{ + using System; + + /// + /// This class is used for storing the encoding of a combinatorial optimization problem into a Hamiltonian. Currently, a Hamiltonian with locality of up to 2 is supported. + /// + public class QaoaProblemInstance + { + public double[] OneLocalHamiltonianCoefficients { get; } + + public double[] TwoLocalHamiltonianCoefficients { get; } + + public int ProblemSizeInBits { get; } + + /// + /// Initializes a new instance of the class. + /// + /// + /// Given a problem encoding into a Hamiltonian, this field corresponds to coefficients of 1-local terms. Assuming that a solution to a combinatorial optimization problem can be encoded into n bits (which then corresponds to an encoding into n qubits), this array must be of length n. The i-th coefficient in an array corresponds to the coefficient of a term \sigma_i^z. + /// + /// + /// Given a problem encoding into a Hamiltonian, this field corresponds to coefficients of 2-local terms. Assuming that a solution to a combinatorial optimization problem can be encoded into n bits (which then corresponds to an encoding into n qubits), this array must be of length n^2. The (i*n+j)-th coefficient in an array corresponds to the coefficient of a term \sigma_i^z\sigma_j^z (other coefficients in an array can take any value of type double). + /// + public QaoaProblemInstance(double[] oneLocalHamiltonianCoefficients, double[] twoLocalHamiltonianCoefficients) + { + this.OneLocalHamiltonianCoefficients = oneLocalHamiltonianCoefficients; + this.TwoLocalHamiltonianCoefficients = twoLocalHamiltonianCoefficients; + this.ProblemSizeInBits = oneLocalHamiltonianCoefficients.Length; + } + + /// + /// Calculates the value of the objective function Hamiltonian for a binary string provided. + /// + /// + /// A binary string. In this context it is a result that we get after measuring the QAOA state. + /// + /// + /// The value of the objective function Hamiltonian. + /// + /// + /// In the binary string, 0 is mapped to 1 and 1 is mapped to -1 since (-1,1) are eigenvalues of the Z operator which is currently supported in this implementation. + /// + public double EvaluateHamiltonian(bool[] result) + { + double hamiltonianValue = 0; + for (var i = 0; i < this.ProblemSizeInBits; i++) + { + hamiltonianValue += this.OneLocalHamiltonianCoefficients[i] * (1 - (2 * Convert.ToInt32(result[i]))); + } + + for (var i = 0; i < this.ProblemSizeInBits; i++) + { + for (var j = i + 1; j < this.ProblemSizeInBits; j++) + { + hamiltonianValue += this.TwoLocalHamiltonianCoefficients[(i * this.ProblemSizeInBits) + j] * (1 - (2 * Convert.ToInt32(result[i]))) * (1 - (2 * Convert.ToInt32(result[j]))); + } + } + + return hamiltonianValue; + } + } +} diff --git a/QAOA/src/QaoaHybrid/QaoaSolution.cs b/QAOA/src/QaoaHybrid/QaoaSolution.cs new file mode 100644 index 00000000000..8c3cd1fd3bc --- /dev/null +++ b/QAOA/src/QaoaHybrid/QaoaSolution.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Qaoa.QaoaHybrid +{ + /// + /// This class stores a solution that is found by a hybrid QAOA. It includes a boolean array that encodes the solution, a corresponding expected value of the objective function Hamiltonian and corresponding beta and gamma parameters that are input to a QAOA. + /// + /// + /// Note that given the nature of a QAOA and a hybrid QAOA, a solution produced by the algorithm is not necessarily feasible and not necessarily optimal. + /// + public class QaoaSolution + { + + public bool[] SolutionVector { get; set; } + + public double SolutionHamiltonianValue { get; set; } + + public QaoaParameters SolutionQaoaParameters { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A vector that is a solution of a combinatorial optimization problem found by a hybrid QAOA. + /// + /// + /// An expected value of an objective function Hamiltonian that corresponds to the solution stored in solutionVector. + /// + /// + /// Values of beta and gamma parameters that correspond to the solution stored in solutionVector. + /// + public QaoaSolution(bool[] solutionVector, double solutionHamiltonianValue, QaoaParameters solutionQaoaParameters) + { + SolutionVector = solutionVector; + SolutionHamiltonianValue = solutionHamiltonianValue; + SolutionQaoaParameters = solutionQaoaParameters; + + } + + } +} diff --git a/QAOA/tests/HybridQaoaTests/HelpersTests/ArrayToStringConverterTests.cs b/QAOA/tests/HybridQaoaTests/HelpersTests/ArrayToStringConverterTests.cs new file mode 100644 index 00000000000..cefc50b1ddd --- /dev/null +++ b/QAOA/tests/HybridQaoaTests/HelpersTests/ArrayToStringConverterTests.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Qaoa.HybridQaoaTests +{ + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Qaoa.QaoaHybrid.Helpers; + + [TestClass] + class ArrayToStringConverterTests + { + [TestMethod] + public void ConvertBoolArrayToStringTest() + { + var boolsArray = new[] { false, false, true }; + + var expectedResult = "001"; + + var result = ArrayToStringConverter.ConvertBoolArrayToString(boolsArray); + + Assert.AreEqual(expectedResult, result, "Bool string not created correctly."); + + + } + + } +} diff --git a/QAOA/tests/HybridQaoaTests/HelpersTests/ModeFinderTests.cs b/QAOA/tests/HybridQaoaTests/HelpersTests/ModeFinderTests.cs new file mode 100644 index 00000000000..44439834284 --- /dev/null +++ b/QAOA/tests/HybridQaoaTests/HelpersTests/ModeFinderTests.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Linq; + +namespace Microsoft.Quantum.Qaoa.HybridQaoaTests +{ + using Microsoft.VisualStudio.TestTools.UnitTesting; + using System.Collections.Generic; + using Microsoft.Quantum.Qaoa.QaoaHybrid.Helpers; + + [TestClass] + public class ModeFinderTests + { + [TestMethod] + public void FindModeInBoolListTest() + { + + var listOfBools = new List + { + new[] { false, false, true }, + new[] { false, false, true }, + new[] { false, false, false }, + new[] { false, true, true } + }; + + var expectedResult = new[] {false, false, true}; + + var result = ModeFinder.FindModeInBoolList(listOfBools); + + CollectionAssert.AreEqual(expectedResult, result, "Mode bool string not found correctly."); + } + + [TestMethod] + public void FindModeInBoolListWithTieTest() + { + + var listOfBools = new List + { + new[] { false, true, true }, + new[] { false, false, true }, + new[] { false, false, false }, + new[] { false, false, true }, + new[] { false, false, false } + }; + + var expectedResult1 = new[] { false, false, true }; + var expectedResult2 = new[] { false, false, false }; + + var result = ModeFinder.FindModeInBoolList(listOfBools); + + Assert.IsTrue(expectedResult1.SequenceEqual(result) || expectedResult2.SequenceEqual(result), "Mode bool string not found correctly."); + } + } +} + diff --git a/QAOA/tests/HybridQaoaTests/HelpersTests/ProblemInstanceTests.cs b/QAOA/tests/HybridQaoaTests/HelpersTests/ProblemInstanceTests.cs new file mode 100644 index 00000000000..6483567569f --- /dev/null +++ b/QAOA/tests/HybridQaoaTests/HelpersTests/ProblemInstanceTests.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Qaoa.HybridQaoaTests +{ + using Microsoft.Quantum.Qaoa.QaoaHybrid; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + class ProblemInstanceTests + { + + + [TestMethod] + public void EvaluateHamiltonianTest() + { + var problemInstance = new QaoaProblemInstance(new double[] { 1, 2, 2, -1 }, new double[] { 5, 0, 0, 1, 1, 5, 0, 0, 3, 4, -2, -2, 8, 7, -2, 12 }); + + var result = problemInstance.EvaluateHamiltonian(new [] {false,false,true,true}); + + const int expectedResult = -1; + + Assert.AreEqual(expectedResult, result, "Hamiltonian value not calculated correctly."); + + + } + +} +} diff --git a/QAOA/tests/HybridQaoaTests/HybridQaoaTests.cs b/QAOA/tests/HybridQaoaTests/HybridQaoaTests.cs new file mode 100644 index 00000000000..38d5fdb1c6c --- /dev/null +++ b/QAOA/tests/HybridQaoaTests/HybridQaoaTests.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Qaoa.HybridQaoaTests +{ + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Microsoft.Quantum.Qaoa.QaoaHybrid; + + [TestClass] + public class ClassicalOptimizationTest + { + + [TestMethod] + public void EvaluateCostFunctionTest() + { + QaoaProblemInstance qaoaProblemInstance = new QaoaProblemInstance(new double[] { 1, 1, 1, 1 }, new double[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }); + + HybridQaoa classicalOptimization = new HybridQaoa(2, 1, qaoaProblemInstance); + + var optimizationResult = new []{false, true, false, true}; + + var result = classicalOptimization.EvaluateCostFunction(optimizationResult, new double[] { 5, 3, 2, 1 }); + + var expectedResult = 4; + + Assert.AreEqual(expectedResult, result, "Cost function not calculated correctly."); + + + } + + [TestMethod] + public void RunOptimizationRandomParamsTest() + { + var dh = new double[] { 0, 0 }; + var dJ = new double[]{ 0, 1, 0, 0}; + + var numberOfIterations = 50; + var NHamiltonianApplications = 2; + var numberOfRandomStartingPoints = 2; + + var simpleMaxCut = new QaoaProblemInstance(dh, dJ); + + var classicalOptimization = new HybridQaoa(numberOfIterations, NHamiltonianApplications, simpleMaxCut); + var optimalSolution = classicalOptimization.RunOptimization(numberOfRandomStartingPoints); + + var optimizationResult1 = new[] {false, true}; + var optimizationResult2 = new[] {true, false}; + + var result = optimalSolution.SolutionVector; + + CollectionAssert.AreEqual(result, result[0] ? optimizationResult2 : optimizationResult1, "Hybrid QAOA with random parameters produced incorrect result."); + } + + [TestMethod] + public void RunOptimizationUserParamsTest() + { + var dh = new double[] { 0, 0 }; + var dJ = new double[] { 0, 1, 0, 0 }; + + var numberOfIterations = 60; + var NHamiltonianApplications = 3; + + var simpleMaxCut = new QaoaProblemInstance(dh, dJ); + + var initialBeta = new double[] { 0, 0, 0 }; + var initialGamma = new double[] { 0, 0, 0 }; + + var qaoaParameters = new QaoaParameters(initialBeta, initialGamma); + + var classicalOptimization = new HybridQaoa(numberOfIterations, NHamiltonianApplications, simpleMaxCut); + var optimalSolution = classicalOptimization.RunOptimization(qaoaParameters); + + var optimizationResult1 = new[] { false, true }; + var optimizationResult2 = new[] { true, false }; + + var result = optimalSolution.SolutionVector; + + CollectionAssert.AreEqual(result, result[0] ? optimizationResult2 : optimizationResult1, "Hybrid QAOA with random parameters produced incorrect result."); + } + } +} diff --git a/QAOA/tests/HybridQaoaTests/HybridQaoaTests.csproj b/QAOA/tests/HybridQaoaTests/HybridQaoaTests.csproj new file mode 100644 index 00000000000..c60b2d4a6e8 --- /dev/null +++ b/QAOA/tests/HybridQaoaTests/HybridQaoaTests.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + diff --git a/QAOA/tests/JupyterTests/HybridQaoaParametersMagicTests.cs b/QAOA/tests/JupyterTests/HybridQaoaParametersMagicTests.cs new file mode 100644 index 00000000000..f178f7f0c39 --- /dev/null +++ b/QAOA/tests/JupyterTests/HybridQaoaParametersMagicTests.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Qaoa.JupyterTests +{ + using System.Threading.Tasks; + using Microsoft.Jupyter.Core; + using Newtonsoft.Json; + using Xunit; + using Microsoft.Quantum.Qaoa.QaoaHybrid; + using Microsoft.Quantum.Qaoa.Jupyter; + + public class HybridQaoaParametersMagicTests + { + public (HybridQaoaParametersMagic, MockChannel) Init() => + (new HybridQaoaParametersMagic(), new MockChannel()); + + [Fact] + public async Task HybridQaoaProblemInstance() + { + var (magic, channel) = Init(); + Assert.Equal("%qaoa.hybridqaoa.create.parameters", magic.Name); + + var beta = new double[] { 1, 2, 3 }; + var gamma = new double[] { 5, 6, 7 }; + + + var args = JsonConvert.SerializeObject(new HybridQaoaParametersMagic.Arguments + { + Beta = beta, + Gamma = gamma + }); + + var result = await magic.Run(args, channel); + var qaoaParameters = result.Output as QaoaParameters; + Assert.Equal(ExecuteStatus.Ok, result.Status); + + Assert.Equal(qaoaParameters.Betas, beta); + Assert.Equal(qaoaParameters.Gammas, gamma); + } + + } +} \ No newline at end of file diff --git a/QAOA/tests/JupyterTests/HybridQaoaProblemInstanceMagicTests.cs b/QAOA/tests/JupyterTests/HybridQaoaProblemInstanceMagicTests.cs new file mode 100644 index 00000000000..0da9b3d8983 --- /dev/null +++ b/QAOA/tests/JupyterTests/HybridQaoaProblemInstanceMagicTests.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Qaoa.JupyterTests +{ + using System.Threading.Tasks; + using Microsoft.Jupyter.Core; + using Newtonsoft.Json; + using Xunit; + using Microsoft.Quantum.Qaoa.QaoaHybrid; + using Microsoft.Quantum.Qaoa.Jupyter; + + public class HybridQaoaProblemInstanceMagicTests + { + public (HybridQaoaProblemInstanceMagic, MockChannel) Init() => + (new HybridQaoaProblemInstanceMagic(), new MockChannel()); + + [Fact] + public async Task HybridQaoaProblemInstance() + { + var (magic, channel) = Init(); + Assert.Equal("%qaoa.hybridqaoa.create.problem.instance", magic.Name); + + var oneLocalHamiltonianCoefficients = new double[] {0, 0}; + var twoLocalHamiltonianCoefficients = new double[] {0, 1, 0, 0}; + + var args = JsonConvert.SerializeObject(new HybridQaoaProblemInstanceMagic.Arguments + { + OneLocalHamiltonianCoefficients = oneLocalHamiltonianCoefficients, + TwoLocalHamiltonianCoefficients = twoLocalHamiltonianCoefficients + }); + + var result = await magic.Run(args, channel); + var problemInstance = result.Output as QaoaProblemInstance; + Assert.Equal(ExecuteStatus.Ok, result.Status); + + Assert.Equal(problemInstance.ProblemSizeInBits, oneLocalHamiltonianCoefficients.Length); + Assert.Equal(problemInstance.OneLocalHamiltonianCoefficients, oneLocalHamiltonianCoefficients); + Assert.Equal(problemInstance.TwoLocalHamiltonianCoefficients, twoLocalHamiltonianCoefficients); + } + + } +} \ No newline at end of file diff --git a/QAOA/tests/JupyterTests/HybridQaoaRunMagicTests.cs b/QAOA/tests/JupyterTests/HybridQaoaRunMagicTests.cs new file mode 100644 index 00000000000..fa600987f84 --- /dev/null +++ b/QAOA/tests/JupyterTests/HybridQaoaRunMagicTests.cs @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Qaoa.JupyterTests +{ + using System.Threading.Tasks; + using Microsoft.Jupyter.Core; + using Newtonsoft.Json; + using Microsoft.Quantum.Qaoa.QaoaHybrid; + using Xunit; + using Microsoft.Quantum.Qaoa.Jupyter; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Assert = Xunit.Assert; + + public class HybridQaoaRunMagicTests + { + public (HybridQaoaRunMagic, MockChannel) Init() => + (new HybridQaoaRunMagic(), new MockChannel()); + + [Fact] + public async Task HybridQaoaRun() + { + var (magic, channel) = Init(); + Assert.Equal("%qaoa.hybridqaoa.run", magic.Name); + + var numberOfIterations = 50; + var p = 2; + var oneLocalHamiltonianCoefficients = new double[] { 0, 0 }; + var twoLocalHamiltonianCoefficients = new double[] { 0, 1, 0, 0}; + var initialBeta = new double[] {0, 0}; + var initialGamma = new double[] { 0, 0 }; + var initialQaoaParameters = new QaoaParameters(initialBeta, initialGamma); + + var simpleMaxCut = new QaoaProblemInstance(oneLocalHamiltonianCoefficients, twoLocalHamiltonianCoefficients); + + var args = JsonConvert.SerializeObject(new HybridQaoaRunMagic.Arguments + { + NumberOfIterations = numberOfIterations, + NHamiltonianApplications = p, + QaoaProblemInstance = simpleMaxCut, + InitialQaoaParameters = initialQaoaParameters, + }); + + var result = await magic.Run(args, channel); + var optimalSolution = result.Output as QaoaSolution; + Assert.Equal(ExecuteStatus.Ok, result.Status); + var optimizationResult1 = new[] {false, true}; + var optimizationResult2 = new[] {true, false}; + + if (optimalSolution.SolutionVector[0] == false) + { + CollectionAssert.AreEqual(optimalSolution.SolutionVector, optimizationResult1, "Hybrid QAOA produced incorrect result when running magic."); + } + else + { + CollectionAssert.AreEqual(optimalSolution.SolutionVector, optimizationResult2, "Hybrid QAOA produced incorrect result when running magic."); + } + } + } + + public class HybridQaoaWithRandomParametersMagicTests + { + public (HybridQaoaWithRandomParametersRunMagic, MockChannel) Init() => + (new HybridQaoaWithRandomParametersRunMagic(), new MockChannel()); + + [Fact] + public async Task HybridQaoaRun() + { + var (magic, channel) = Init(); + Assert.Equal("%qaoa.hybridqaoa.random.params.run", magic.Name); + + var numberOfIterations = 50; + var p = 2; + var oneLocalHamiltonianCoefficients = new double[] { 0, 0 }; + var twoLocalHamiltonianCoefficients = new double[] { 0, 1, 0, 0 }; + var numberOfRandomStartingPoints = 2; + + var simpleMaxCut = new QaoaProblemInstance(oneLocalHamiltonianCoefficients, twoLocalHamiltonianCoefficients); + + var args = JsonConvert.SerializeObject(new HybridQaoaWithRandomParametersRunMagic.Arguments + { + NumberOfIterations = numberOfIterations, + NHamiltonianApplications = p, + QaoaProblemInstance = simpleMaxCut, + NumberOfRandomStartingPoints = numberOfRandomStartingPoints + }); + + var result = await magic.Run(args, channel); + var optimalSolution = result.Output as QaoaSolution; + Assert.Equal(ExecuteStatus.Ok, result.Status); + var optimizationResult1 = new[] { false, true }; + var optimizationResult2 = new[] { true, false }; + + if (optimalSolution.SolutionVector[0] == false) + { + CollectionAssert.AreEqual(optimalSolution.SolutionVector, optimizationResult1, "Hybrid QAOA produced incorrect result when running magic."); + } + else + { + CollectionAssert.AreEqual(optimalSolution.SolutionVector, optimizationResult2, "Hybrid QAOA produced incorrect result when running magic."); + } + } + } + +} \ No newline at end of file diff --git a/QAOA/tests/JupyterTests/JupyterTests.csproj b/QAOA/tests/JupyterTests/JupyterTests.csproj new file mode 100644 index 00000000000..a5482530506 --- /dev/null +++ b/QAOA/tests/JupyterTests/JupyterTests.csproj @@ -0,0 +1,26 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/QAOA/tests/JupyterTests/MockChannel.cs b/QAOA/tests/JupyterTests/MockChannel.cs new file mode 100644 index 00000000000..157dcd4f85b --- /dev/null +++ b/QAOA/tests/JupyterTests/MockChannel.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Qaoa.JupyterTests +{ + using System; + using System.Collections.Generic; + using Microsoft.Jupyter.Core; + + public class MockChannel : IChannel + { + public List errors = new List(); + public List msgs = new List(); + + public void Display(object displayable) + { + throw new NotImplementedException(); + } + + public void Stderr(string message) => errors.Add(message); + + public void Stdout(string message) => msgs.Add(message); + } +} diff --git a/QAOA/tests/QaoaTests/PhaseKickbackTests.qs b/QAOA/tests/QaoaTests/PhaseKickbackTests.qs new file mode 100644 index 00000000000..dc7f4346121 --- /dev/null +++ b/QAOA/tests/QaoaTests/PhaseKickbackTests.qs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Tests { + open Microsoft.Quantum.Logical; + open Microsoft.Quantum.Arrays; + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Qaoa; + + @Test("QuantumSimulator") + operation RunPhaseKickbackTest() : Unit { + + let phaseExponent = 0.5; + + using (register = Qubit()) { + RunPhaseKickback(phaseExponent, [register]); + AssertQubit(Zero, register); + Reset(register); + } + } + + @Test("QuantumSimulator") + operation RunPhaseKickbackOneControlQubitTest() : Unit { + + let phaseExponent = 0.5; + + let complexZero = Complex(0.685125,-0.174941); + let complexOne = Complex(0.685125,0.174941); + + using (register = Qubit()) { + H(register); + RunPhaseKickback(phaseExponent, [register]); + AssertQubitIsInStateWithinTolerance((complexZero, complexOne), register, 1E-05); + Reset(register); + } + } +} diff --git a/QAOA/tests/QaoaTests/QaoaTests.csproj b/QAOA/tests/QaoaTests/QaoaTests.csproj new file mode 100644 index 00000000000..272e2b2fb98 --- /dev/null +++ b/QAOA/tests/QaoaTests/QaoaTests.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp3.1 + false + + + + + + + + + + + + + + +