diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index cce8e639836..a9f6dd5fd68 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -9,15 +9,14 @@ RUN apt-get -y update && \ apt-get -y install --no-install-recommends mono-complete nuget && \ apt-get clean && rm -rf /var/lib/apt/lists/ -# Install CMake, Ninja, LLVM/Clang tools -RUN apt-get -y update && \ - apt-get install -y cmake && \ - apt-get install -y ninja-build && \ - apt-get install -y clang-11 && \ - apt-get install -y clang-tidy-11 && \ - apt-get clean && rm -rf /var/lib/apt/lists/ +RUN apt update \ + && apt-get install -y cmake \ + ninja-build \ + clang-11 \ + clang-tidy-11 \ + build-essential \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/ -# Install C/C++ build tools and libraries -RUN apt-get -y update && \ - apt-get install -y build-essential && \ - apt-get clean && rm -rf /var/lib/apt/lists/ +RUN curl https://sh.rustup.rs -sSf | sh -s -- -y +CMD [ "pwsh" ] diff --git a/.gitignore b/.gitignore index 5b33b48524e..e1cb5a9ea3f 100644 --- a/.gitignore +++ b/.gitignore @@ -339,3 +339,9 @@ ASALocalRun/ /src/Simulation/Simulators.Tests/TestProjects/QSharpExe/built /src/Simulation/Simulators.Tests/TestProjects/TargetedExe/built dbw_test + +# Jupyter caches +.ipynb_checkpoints + +# Ignore drops from building native simulators. +xplat diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000000..c12c6fd5236 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,19 @@ +# This file configures the pre-commit tool to run an initial +# suite of lightweight tests on each commit, reducing the +# probability of failing in CI. +# For more information on pre-commit, see https://pre-commit.com/. +repos: + - repo: https://github.com/doublify/pre-commit-rust + rev: v1.0 + hooks: + - id: fmt + args: ['--manifest-path', 'src/Simulation/qdk_sim_rs/Cargo.toml', '--'] + - id: cargo-check + args: ['--manifest-path', 'src/Simulation/qdk_sim_rs/Cargo.toml', '--'] + # This step runs cargo-clippy, a linting tool provided with the + # Rust toolchain. Please see https://github.com/rust-lang/rust-clippy + # and https://rust-lang.github.io/rust-clippy/master/index.html + # for more information. + - id: clippy + args: ['--manifest-path', 'src/Simulation/qdk_sim_rs/Cargo.toml', '--'] + diff --git a/bootstrap.ps1 b/bootstrap.ps1 index d8b102b7937..0ca15813a99 100644 --- a/bootstrap.ps1 +++ b/bootstrap.ps1 @@ -7,6 +7,18 @@ Push-Location (Join-Path $PSScriptRoot "build") .\prerequisites.ps1 Pop-Location +Push-Location (Join-Path $PSScriptRoot "./src/Simulation/qdk_sim_rs") + # We use dotnet-script to inject the version number into Cargo.toml, + # so we go on ahead here and restore any missing tools. + # Since that Cargo.toml is referenced by CMake lists in the QIR + # runtime, this injection has to be the first thing we do. + dotnet tool restore + dotnet script inject-version.csx -- ` + --template Cargo.toml.template ` + --out-path Cargo.toml ` + --version $Env:NUGET_VERSION; +Pop-Location + if (-not (Test-Path Env:AGENT_OS)) { if ($Env:ENABLE_NATIVE -ne "false") { Write-Host "Build release flavor of the native simulator" @@ -14,6 +26,16 @@ if (-not (Test-Path Env:AGENT_OS)) { Push-Location (Join-Path $PSScriptRoot "src/Simulation/Native") .\build-native-simulator.ps1 Pop-Location + Push-Location (Join-Path $PSScriptRoot "src/Simulation/qdk_sim_rs") + # Don't run the experimental simulator build if we're local + # and prerequisites are missing. + $IsCI = "$Env:TF_BUILD" -ne "" -or "$Env:CI" -eq "true"; + if ((Get-Command cargo -ErrorAction SilentlyContinue) -or $IsCI) { + .\build-qdk-sim-rs.ps1 + } else { + Write-Verbose "cargo was not installed, skipping qdk_sim_rs build."; + } + Pop-Location $Env:BUILD_CONFIGURATION = $null } if ($Env:ENABLE_QIRRUNTIME -ne "false") { diff --git a/build/build.ps1 b/build/build.ps1 index dec45cec756..177b5eb6a8b 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -26,6 +26,33 @@ if ($Env:ENABLE_QIRRUNTIME -ne "false") { Write-Host "Skipping build of qir runtime because ENABLE_QIRRUNTIME variable is set to: $Env:ENABLE_QIRRUNTIME" } +if ($Env:ENABLE_EXPERIMENTALSIM -ne "false") { + if (-not (Get-Command cargo -ErrorAction SilentlyContinue)) { + # Cargo was missing, so cannot build experimental simulators. + # That's fine if running locally, we'll warn the user and then skip. + # On CI, though, we should fail when the experimental simulator build + # is turned on by ENABLE_EXPERIMENTALSIM, but we can't actually + # proceed. + if ("$Env:TF_BUILD" -ne "" -or "$Env:CI" -eq "true") { + Write-Host "##[error]Experimental simulators enabled, but cargo was not installed in CI pipeline."; + } else { + Write-Warning ` + "Experimental simulators enabled, but cargo missing. " + ` + "Either install cargo, or set `$Env:ENABLE_EXPERIMENTALSIM " + ` + "to `"false`". Skipping experimental simulators."; + } + } else { + # Prerequisites are met, so let's go. + $expSim = (Join-Path $PSScriptRoot "../src/Simulation/qdk_sim_rs") + & "$expSim/build-qdk-sim-rs.ps1" + if ($LastExitCode -ne 0) { + $script:all_ok = $False + } + } +} else { + Write-Host "Skipping build of experimental simulators because ENABLE_OPENSIM variable is set to: $Env:ENABLE_OPENSIM." +} + function Build-One { param( [string]$action, diff --git a/build/pack.ps1 b/build/pack.ps1 index 7f6dbe702a4..9751b9e0047 100644 --- a/build/pack.ps1 +++ b/build/pack.ps1 @@ -6,14 +6,36 @@ $ErrorActionPreference = 'Stop' & "$PSScriptRoot/set-env.ps1" $all_ok = $True +$RepoRoot = Resolve-Path (Join-Path $PSScriptRoot ".."); + Write-Host "##[info]Copy Native simulator xplat binaries" -pushd ../src/Simulation/Native -If (-not (Test-Path 'osx')) { mkdir 'osx' } -If (-not (Test-Path 'linux')) { mkdir 'linux' } -$DROP = "$Env:DROP_NATIVE/src/Simulation/Native/build/drop" -If (Test-Path "$DROP/libMicrosoft.Quantum.Simulator.Runtime.dylib") { copy "$DROP/libMicrosoft.Quantum.Simulator.Runtime.dylib" "osx/Microsoft.Quantum.Simulator.Runtime.dll" } -If (Test-Path "$DROP/libMicrosoft.Quantum.Simulator.Runtime.so") { copy "$DROP/libMicrosoft.Quantum.Simulator.Runtime.so" "linux/Microsoft.Quantum.Simulator.Runtime.dll" } -popd +Push-Location (Join-Path $PSScriptRoot ../src/Simulation/Native) + If (-not (Test-Path 'osx')) { mkdir 'osx' } + If (-not (Test-Path 'linux')) { mkdir 'linux' } + If (-not (Test-Path 'win10')) { mkdir 'win10' } + + $DROP = "$Env:DROP_NATIVE/src/Simulation/Native/build/drop" + Write-Host "##[info]Copying Microsoft.Quantum.Simulator.Runtime files from $DROP..."; + If (Test-Path "$DROP/libMicrosoft.Quantum.Simulator.Runtime.dylib") { + Copy-Item -Verbose "$DROP/libMicrosoft.Quantum.Simulator.Runtime.dylib" "osx/Microsoft.Quantum.Simulator.Runtime.dll" + } + If (Test-Path "$DROP/libMicrosoft.Quantum.Simulator.Runtime.so") { + Copy-Item -Verbose "$DROP/libMicrosoft.Quantum.Simulator.Runtime.so" "linux/Microsoft.Quantum.Simulator.Runtime.dll" + } + + + $DROP = "$Env:DROP_NATIVE/src/Simulation/qdk_sim_rs/drop"; + Write-Host "##[info]Copying qdk_sim_rs files from $DROP..."; + if (Test-Path "$DROP/libqdk_sim.dylib") { + Copy-Item -Verbose "$DROP/libqdk_sim.dylib" "osx/Microsoft.Quantum.Experimental.Simulators.Runtime.dll" + } + if (Test-Path "$DROP/libqdk_sim.so") { + Copy-Item -Verbose "$DROP/libqdk_sim.so" "linux/Microsoft.Quantum.Experimental.Simulators.Runtime.dll" + } + if (Test-Path "$DROP/qdk_sim.dll") { + Copy-Item -Verbose "$DROP/qdk_sim.dll" "win10/Microsoft.Quantum.Experimental.Simulators.Runtime.dll" + } +Pop-Location function Pack-One() { @@ -31,7 +53,7 @@ function Pack-One() { $version = $Env:NUGET_VERSION } - nuget pack $project ` + nuget pack (Join-Path $PSScriptRoot $project) ` -OutputDirectory $Env:NUGET_OUTDIR ` -Properties Configuration=$Env:BUILD_CONFIGURATION ` -Version $version ` @@ -68,7 +90,7 @@ function Pack-Dotnet() { $version = $Env:NUGET_VERSION } - dotnet pack $project ` + dotnet pack (Join-Path $PSScriptRoot $project) ` -o $Env:NUGET_OUTDIR ` -c $Env:BUILD_CONFIGURATION ` -v detailed ` @@ -87,6 +109,49 @@ function Pack-Dotnet() { } +function Pack-Crate() { + param( + [string] + $PackageDirectory, + + [string] + $OutPath + ); + + "##[info]Packing crate at $PackageDirectory to $OutPath..." | Write-Host + + # Resolve relative to where the build script is located, + # not the PackageDirectory. + if (-not [IO.Path]::IsPathRooted($OutPath)) { + $OutPath = Resolve-Path (Join-Path $PSScriptRoot $OutPath); + } + Push-Location (Join-Path $PSScriptRoot $PackageDirectory) + cargo package; + Copy-Item -Force -Recurse (Join-Path . "target" "package") $OutPath; + Pop-Location +} + +function Pack-Wheel() { + param( + [string] + $PackageDirectory, + + [string] + $OutPath + ); + + "##[info]Packing wheel at $PackageDirectory to $OutPath..." | Write-Host + + # Resolve relative to where the build script is located, + # not the PackageDirectory. + if (-not [IO.Path]::IsPathRooted($OutPath)) { + $OutPath = Resolve-Path (Join-Path $PSScriptRoot $OutPath); + } + Push-Location (Join-Path $PSScriptRoot $PackageDirectory) + pip wheel --wheel-dir $OutPath .; + Pop-Location +} + Write-Host "##[info]Using nuget to create packages" Pack-Dotnet '../src/Azure/Azure.Quantum.Client/Microsoft.Azure.Quantum.Client.csproj' Pack-One '../src/Simulation/CSharpGeneration/Microsoft.Quantum.CSharpGeneration.fsproj' '-IncludeReferencedProjects' @@ -101,6 +166,8 @@ Pack-Dotnet '../src/Simulation/Type3Core/Microsoft.Quantum.Type3.Core.csproj' Pack-One '../src/Simulation/Simulators/Microsoft.Quantum.Simulators.nuspec' Pack-One '../src/Quantum.Development.Kit/Microsoft.Quantum.Development.Kit.nuspec' Pack-One '../src/Xunit/Microsoft.Quantum.Xunit.csproj' +Pack-Crate -PackageDirectory "../src/Simulation/qdk_sim_rs" -OutPath $Env:CRATE_OUTDIR; +Pack-Wheel -PackageDirectory "../src/Simulation/qdk_sim_rs" -OutPath $Env:WHEEL_OUTDIR; Pack-One '../src/Qir/Runtime/Microsoft.Quantum.Qir.Runtime.nuspec' -ForcePrerelease if (-not $all_ok) { diff --git a/build/set-env.ps1 b/build/set-env.ps1 index 39799bdf9f7..1173a7a0a07 100644 --- a/build/set-env.ps1 +++ b/build/set-env.ps1 @@ -9,7 +9,8 @@ If ($Env:BUILD_BUILDNUMBER -eq $null) { $Env:BUILD_BUILDNUMBER ="0.0.1.0" } If ($Env:BUILD_CONFIGURATION -eq $null) { $Env:BUILD_CONFIGURATION ="Debug" } If ($Env:BUILD_VERBOSITY -eq $null) { $Env:BUILD_VERBOSITY ="m" } If ($Env:ASSEMBLY_VERSION -eq $null) { $Env:ASSEMBLY_VERSION ="$Env:BUILD_BUILDNUMBER" } -If ($Env:NUGET_VERSION -eq $null) { $Env:NUGET_VERSION ="$Env:ASSEMBLY_VERSION-alpha" } +If ($Env:PYTHON_VERSION -eq $null) { $Env:PYTHON_VERSION = "${Env:ASSEMBLY_VERSION}a1" } +If ($Env:NUGET_VERSION -eq $null) { $Env:NUGET_VERSION ="0.0.1-alpha" } If (($Env:ENABLE_NATIVE -ne "false") -and ($Env:NATIVE_SIMULATOR -eq $null) ) { $Env:NATIVE_SIMULATOR = (Join-Path $PSScriptRoot "..\src\Simulation\Native\build\drop") @@ -20,10 +21,27 @@ if ($Env:ENABLE_QIRRUNTIME -ne "false" -and $Env:QIR_DROPS -eq $null) { } If ($Env:DROPS_DIR -eq $null) { $Env:DROPS_DIR = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot "..\drops")) } +if ($Env:DROP_NATIVE -eq $null) { + $Env:DROP_NATIVE = (Join-Path $PSScriptRoot "..") +} If ($Env:NUGET_OUTDIR -eq $null) { $Env:NUGET_OUTDIR = (Join-Path $Env:DROPS_DIR "nugets") } If (-not (Test-Path -Path $Env:NUGET_OUTDIR)) { [IO.Directory]::CreateDirectory($Env:NUGET_OUTDIR) } +If ($Env:CRATE_OUTDIR -eq $null) { $Env:CRATE_OUTDIR = (Join-Path $Env:DROPS_DIR "crates") } +If (-not (Test-Path -Path $Env:CRATE_OUTDIR)) { [IO.Directory]::CreateDirectory($Env:CRATE_OUTDIR) } + +If ($Env:WHEEL_OUTDIR -eq $null) { $Env:WHEEL_OUTDIR = (Join-Path $Env:DROPS_DIR "wheels") } +If (-not (Test-Path -Path $Env:WHEEL_OUTDIR)) { [IO.Directory]::CreateDirectory($Env:WHEEL_OUTDIR) } + If ($Env:DOCS_OUTDIR -eq $null) { $Env:DOCS_OUTDIR = (Join-Path $Env:DROPS_DIR "docs") } If (-not (Test-Path -Path $Env:DOCS_OUTDIR)) { [IO.Directory]::CreateDirectory($Env:DOCS_OUTDIR) } +Get-ChildItem @( + "Env:\DROPS_DIR", + "Env:\DROP_NATIVE", + "Env:\NUGET_OUTDIR", + "Env:\CRATE_OUTDIR", + "Env:\WHEEL_OUTDIR", + "Env:\DOCS_OUTDIR" + ) | Format-Table \ No newline at end of file diff --git a/build/steps-init.yml b/build/steps-init.yml index 28b5251b15b..42d9df112aa 100644 --- a/build/steps-init.yml +++ b/build/steps-init.yml @@ -14,6 +14,23 @@ steps: packageType: sdk version: '3.1.300' +- script: | + curl https://sh.rustup.rs -sSf | sh -s -- -y + echo "##vso[task.setvariable variable=PATH;]$PATH:$HOME/.cargo/bin" + displayName: Install rust + condition: ne( variables['Agent.OS'], 'Windows_NT' ) +- script: | + curl -sSf -o rustup-init.exe https://win.rustup.rs + rustup-init.exe -y + echo "##vso[task.setvariable variable=PATH;]%PATH%;%USERPROFILE%\.cargo\bin" + displayName: Windows install rust + condition: eq( variables['Agent.OS'], 'Windows_NT' ) + +- script: | + rustup install nightly + rustup component add rustfmt clippy + rustup component add rustfmt clippy --toolchain nightly + displayName: Enable Rust formatting and nightly options. ## # Custom pre-reqs diff --git a/build/test.ps1 b/build/test.ps1 index 974c2a61615..9109434192c 100644 --- a/build/test.ps1 +++ b/build/test.ps1 @@ -57,6 +57,17 @@ if ($Env:ENABLE_QIRRUNTIME -ne "false") { Write-Host "Skipping test of qir runtime because ENABLE_QIRRUNTIME variable is set to: $Env:ENABLE_QIRRUNTIME." } + +if ($Env:ENABLE_EXPERIMENTALSIM -ne "false") { + $expSim = (Join-Path $PSScriptRoot "../src/Simulation/qdk_sim_rs") + & "$expSim/test-qdk-sim-rs.ps1" + if ($LastExitCode -ne 0) { + $script:all_ok = $False + } +} else { + Write-Host "Skipping test of experimental simulators because ENABLE_OPENSIM variable is set to: $Env:ENABLE_OPENSIM." +} + if (-not $all_ok) { throw "At least one project failed during testing. Check the logs." } diff --git a/documentation/examples/experimental-simulators-from-python.ipynb b/documentation/examples/experimental-simulators-from-python.ipynb new file mode 100644 index 00000000000..164612e243b --- /dev/null +++ b/documentation/examples/experimental-simulators-from-python.ipynb @@ -0,0 +1,1127 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a21769ec", + "metadata": {}, + "source": [ + "# Using Experimental Simulators with Q# and Python" + ] + }, + { + "cell_type": "markdown", + "id": "a7510c5c", + "metadata": {}, + "source": [ + "The experimental simulators use the [QuTiP](https://qutip.org) library for Python to help represent noise models, so we import it here." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "7430263c", + "metadata": {}, + "outputs": [], + "source": [ + "import qutip as qt" + ] + }, + { + "cell_type": "markdown", + "id": "67abc680", + "metadata": {}, + "source": [ + "To use the experimental simulators, we start by importing Q# interoperability as normal." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "00c0728f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Preparing Q# environment...\n" + ] + } + ], + "source": [ + "import qsharp" + ] + }, + { + "cell_type": "markdown", + "id": "a83cdd60", + "metadata": {}, + "source": [ + "We can then use `qsharp.experimental.enable_noisy_simulation()` to add support for experimental simulators." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "ad41d44b", + "metadata": {}, + "outputs": [], + "source": [ + "import qsharp.experimental\n", + "qsharp.experimental.enable_noisy_simulation()" + ] + }, + { + "cell_type": "markdown", + "id": "03daa4e4", + "metadata": {}, + "source": [ + "Doing so adds the `.simulate_noise` method to Python representations of Q# callables:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "a97eefa7", + "metadata": {}, + "outputs": [], + "source": [ + "%%qsharp\n", + "\n", + "operation DumpPlus() : Unit {\n", + " use q = Qubit();\n", + " H(q);\n", + " Microsoft.Quantum.Diagnostics.DumpMachine();\n", + " X(q);\n", + " Reset(q);\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "a7cb1504", + "metadata": {}, + "outputs": [ + { + "data": { + "application/json": "{\"Data\":[0.5000000000000001,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.5000000000000001,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.5000000000000001,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.5000000000000001,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0],\"NQubits\":3}", + "text/html": [ + "\r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + "\r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + "
Mixed state
# of qubits3
State data\r\n", + " $$\r\n", + " \\left(\r\n", + " \\begin{matrix}\r\n", + " 0.5000000000000001 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i & 0.5000000000000001 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i\\\\\n", + "0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i\\\\\n", + "0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i\\\\\n", + "0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i\\\\\n", + "0.5000000000000001 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i & 0.5000000000000001 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i\\\\\n", + "0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i\\\\\n", + "0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i\\\\\n", + "0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i & 0 + 0 i\r\n", + " \\end{matrix}\r\n", + " \\right)\r\n", + " $$\r\n", + "
\r\n", + " " + ], + "text/plain": [ + "Mixed state on 3 qubits: [ [0.5000000000000001 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i, 0.5000000000000001 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i] [0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i] [0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i] [0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i] [0.5000000000000001 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i, 0.5000000000000001 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i] [0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i] [0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i] [0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i, 0 + 0 i] ]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "()" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "DumpPlus.simulate_noise()" + ] + }, + { + "cell_type": "markdown", + "id": "9e1fd1cd", + "metadata": {}, + "source": [ + "## Configuring Open Systems Noise Models" + ] + }, + { + "cell_type": "markdown", + "id": "9aa83aef", + "metadata": {}, + "source": [ + "The experimental simulators can be configured by the use of the `qsharp.config` object. For example, to change the size of the register used, we can modify the `experimental.simulators.nQubits` configuration setting:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "865fa045", + "metadata": {}, + "outputs": [], + "source": [ + "qsharp.config['experimental.simulators.nQubits'] = 1" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "ff5b88d7", + "metadata": {}, + "outputs": [ + { + "data": { + "application/json": "{\"Data\":[0.5000000000000001,0.0,0.5000000000000001,0.0,0.5000000000000001,0.0,0.5000000000000001,0.0],\"NQubits\":1}", + "text/html": [ + "\r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + "\r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + "
Mixed state
# of qubits1
State data\r\n", + " $$\r\n", + " \\left(\r\n", + " \\begin{matrix}\r\n", + " 0.5000000000000001 + 0 i & 0.5000000000000001 + 0 i\\\\\n", + "0.5000000000000001 + 0 i & 0.5000000000000001 + 0 i\r\n", + " \\end{matrix}\r\n", + " \\right)\r\n", + " $$\r\n", + "
\r\n", + " " + ], + "text/plain": [ + "Mixed state on 1 qubits: [ [0.5000000000000001 + 0 i, 0.5000000000000001 + 0 i] [0.5000000000000001 + 0 i, 0.5000000000000001 + 0 i] ]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "()" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "DumpPlus.simulate_noise()" + ] + }, + { + "cell_type": "markdown", + "id": "436a1534", + "metadata": {}, + "source": [ + "We can modify the noise model used in simulating Q# programs by using several functions in the `qsharp.experimental` module. For instance, to initialize the noise model to an ideal model (that is, with no noise), we can use `set_noise_model_by_name` or the `%noise_model --set-by-name` magic command:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "4cfc0e13", + "metadata": {}, + "outputs": [], + "source": [ + "qsharp.experimental.set_noise_model_by_name('ideal')\n", + "%noise_model --set-by-name ideal" + ] + }, + { + "cell_type": "markdown", + "id": "fab9fb78", + "metadata": {}, + "source": [ + "We can then access the noise model by using `get_noise_model`:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "422a7ee2", + "metadata": {}, + "outputs": [], + "source": [ + "noise_model = qsharp.experimental.get_noise_model()" + ] + }, + { + "cell_type": "markdown", + "id": "c3dba96a", + "metadata": {}, + "source": [ + "This noise model is represented as a Python dictionary from preparations, measurements, and gates to Python objects representing the noise in each. For example, in the ideal noise model, the `Microsoft.Quantum.Intrinsic.H` operation is simulated by a unitary matrix:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "a2774610", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True\\begin{equation*}\\left(\\begin{array}{*{11}c}0.707 & 0.707\\\\0.707 & -0.707\\\\\\end{array}\\right)\\end{equation*}" + ], + "text/plain": [ + "Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True\n", + "Qobj data =\n", + "[[ 0.70710678 0.70710678]\n", + " [ 0.70710678 -0.70710678]]" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "noise_model['h']" + ] + }, + { + "cell_type": "markdown", + "id": "81c5310c", + "metadata": {}, + "source": [ + "We can modify this to add depolarizing noise using QuTiP functions to build a depolarizing noise channel:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "b0a01d12", + "metadata": {}, + "outputs": [], + "source": [ + "I, X, Y, Z = [P.as_qobj() for P in qsharp.Pauli]" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "938accec", + "metadata": {}, + "outputs": [], + "source": [ + "def depolarizing_noise(p=1.0):\n", + " return p * qt.to_super(I) + ((1 - p) / 4) * sum(map(qt.to_super, [I, X, Y, Z]))" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "3add5aa9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "Quantum object: dims = [[[2], [2]], [[2], [2]]], shape = (4, 4), type = super, isherm = True\\begin{equation*}\\left(\\begin{array}{*{11}c}0.500 & 0.495 & 0.495 & 0.500\\\\0.495 & -0.495 & 0.495 & -0.495\\\\0.495 & 0.495 & -0.495 & -0.495\\\\0.500 & -0.495 & -0.495 & 0.500\\\\\\end{array}\\right)\\end{equation*}" + ], + "text/plain": [ + "Quantum object: dims = [[[2], [2]], [[2], [2]]], shape = (4, 4), type = super, isherm = True\n", + "Qobj data =\n", + "[[ 0.5 0.495 0.495 0.5 ]\n", + " [ 0.495 -0.495 0.495 -0.495]\n", + " [ 0.495 0.495 -0.495 -0.495]\n", + " [ 0.5 -0.495 -0.495 0.5 ]]" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "noise_model['h'] = depolarizing_noise(0.99) * qt.to_super(qt.qip.operations.hadamard_transform())\n", + "noise_model['h']" + ] + }, + { + "cell_type": "markdown", + "id": "b6dc2da9", + "metadata": {}, + "source": [ + "Once we have modified our noise model in this way, we can set it as the active noise model used in simulating Q# programs:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "23379c8b", + "metadata": {}, + "outputs": [], + "source": [ + "qsharp.experimental.set_noise_model(noise_model)" + ] + }, + { + "cell_type": "markdown", + "id": "05dbbef9", + "metadata": {}, + "source": [ + "Using this model, we no longer get the exact $|+\\rangle\\langle+|$ state, but see that our Q# program has incurred some small error due to noise in the application of `Microsoft.Quantum.Intrinsic.H`:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "f791e6f6", + "metadata": {}, + "outputs": [ + { + "data": { + "application/json": "{\"Data\":[0.5032581095356969,0.0,0.4951069263733158,0.0,0.4951069263733158,0.0,0.49667422634133085,0.0],\"NQubits\":1}", + "text/html": [ + "\r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + "\r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + "
Mixed state
# of qubits1
State data\r\n", + " $$\r\n", + " \\left(\r\n", + " \\begin{matrix}\r\n", + " 0.5032581095356969 + 0 i & 0.4951069263733158 + 0 i\\\\\n", + "0.4951069263733158 + 0 i & 0.49667422634133085 + 0 i\r\n", + " \\end{matrix}\r\n", + " \\right)\r\n", + " $$\r\n", + "
\r\n", + " " + ], + "text/plain": [ + "Mixed state on 1 qubits: [ [0.5032581095356969 + 0 i, 0.4951069263733158 + 0 i] [0.4951069263733158 + 0 i, 0.49667422634133085 + 0 i] ]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "()" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "DumpPlus.simulate_noise()" + ] + }, + { + "cell_type": "markdown", + "id": "94c70d54", + "metadata": {}, + "source": [ + "## Configuring Stabilizer Noise Models" + ] + }, + { + "cell_type": "markdown", + "id": "05d1a8f5", + "metadata": {}, + "source": [ + "We can also configure the experimental simulator to use stabilizer (_a.k.a._ CHP) simulation. This time, let's get a new noise model by using `get_noise_model_by_name`:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "a02721e7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'initial_state': {'n_qubits': 1,\n", + " 'data': {'Stabilizer': {'n_qubits': 1,\n", + " 'table': array([[ True, False, False],\n", + " [False, True, False]])}}},\n", + " 'cnot': {'n_qubits': 2, 'data': {'ChpDecomposition': [{'Cnot': [0, 1]}]}},\n", + " 'i': {'n_qubits': 1, 'data': {'Sequence': []}},\n", + " 's': {'n_qubits': 1, 'data': {'ChpDecomposition': [{'Phase': 0}]}},\n", + " 's_adj': {'n_qubits': 1, 'data': {'ChpDecomposition': [{'AdjointPhase': 0}]}},\n", + " 't': {'n_qubits': 1, 'data': 'Unsupported'},\n", + " 't_adj': {'n_qubits': 1, 'data': 'Unsupported'},\n", + " 'h': {'n_qubits': 1, 'data': {'ChpDecomposition': [{'Hadamard': 0}]}},\n", + " 'x': {'n_qubits': 1,\n", + " 'data': {'ChpDecomposition': [{'Hadamard': 0},\n", + " {'Phase': 0},\n", + " {'Phase': 0},\n", + " {'Hadamard': 0}]}},\n", + " 'y': {'n_qubits': 1,\n", + " 'data': {'ChpDecomposition': [{'AdjointPhase': 0},\n", + " {'Hadamard': 0},\n", + " {'Phase': 0},\n", + " {'Phase': 0},\n", + " {'Hadamard': 0},\n", + " {'Phase': 0}]}},\n", + " 'z': {'n_qubits': 1,\n", + " 'data': {'ChpDecomposition': [{'Phase': 0}, {'Phase': 0}]}},\n", + " 'z_meas': {'ZMeasurement': {'pr_readout_error': 0}}}" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "noise_model = qsharp.experimental.get_noise_model_by_name('ideal_stabilizer')\n", + "noise_model" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "9f7791f6", + "metadata": {}, + "outputs": [], + "source": [ + "qsharp.experimental.set_noise_model(noise_model)" + ] + }, + { + "cell_type": "markdown", + "id": "df293936", + "metadata": {}, + "source": [ + "To make the best use of stabilizer noise models, we also need to configure the simulator to start off in the stabilizer representation:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "fe0142d8", + "metadata": {}, + "outputs": [], + "source": [ + "qsharp.config['experimental.simulators.representation'] = 'stabilizer'" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "4db703f8", + "metadata": {}, + "outputs": [ + { + "data": { + "application/json": "{\"Table\":{\"SchemaVersion\":1,\"Dimensions\":[2,3],\"Data\":[false,true,false,true,false,false],\"AsArray\":[false,true,false,true,false,false]},\"Data\":[false,true,false,true,false,false],\"NQubits\":1}", + "text/html": [ + "\r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + "\r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + "
Stabilizer state
# of qubits1
State data$$\\left(\\begin{array}{c|c|c}0 & 1 & 0\\\\\n", + "\\hline\n", + "1 & 0 & 0\\end{array}\\right)$$
\r\n", + " " + ], + "text/plain": [ + "Microsoft.Quantum.Experimental.StabilizerState" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "()" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "DumpPlus.simulate_noise()" + ] + }, + { + "cell_type": "markdown", + "id": "5e340d9d", + "metadata": {}, + "source": [ + "Notably, the stabilizer representation does not support operations outside of the stabilizer formalism, such as `T` and `CCNOT`. This allows the stabilizer representation to support significantly more qubits than other representations:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "5f24b886", + "metadata": {}, + "outputs": [], + "source": [ + "qsharp.config['experimental.simulators.nQubits'] = 40" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "5f4d45b8", + "metadata": {}, + "outputs": [ + { + "data": { + "application/json": "{\"Table\":{\"SchemaVersion\":1,\"Dimensions\":[80,81],\"Data\":[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false],\"AsArray\":[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false]},\"Data\":[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false],\"NQubits\":40}", + "text/html": [ + "\r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + "\r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + "
Stabilizer state
# of qubits40
State data$$\\left(\\begin{array}{cccccccccccccccccccccccccccccccccccccccc|cccccccccccccccccccccccccccccccccccccccc|c}0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "\\hline\n", + "1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0\\end{array}\\right)$$
\r\n", + " " + ], + "text/plain": [ + "Microsoft.Quantum.Experimental.StabilizerState" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "()" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "DumpPlus.simulate_noise()" + ] + }, + { + "cell_type": "markdown", + "id": "3534390d", + "metadata": {}, + "source": [ + "For now, though, we'll turn back down the number of qubits just to make dumps easier to read!" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "f3587f4c", + "metadata": {}, + "outputs": [], + "source": [ + "qsharp.config['experimental.simulators.nQubits'] = 4" + ] + }, + { + "cell_type": "markdown", + "id": "f5e96ca9", + "metadata": {}, + "source": [ + "The visualization style for stabilizer states can be selected by using the `experimental.simulators.stabilizerStateStyle` configuration setting:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "2204f4ae", + "metadata": {}, + "outputs": [], + "source": [ + "%%qsharp\n", + "\n", + "operation DumpBellPair() : Unit {\n", + " use left = Qubit();\n", + " use right = Qubit();\n", + " within {\n", + " H(left);\n", + " CNOT(left, right);\n", + " } apply {\n", + " Microsoft.Quantum.Diagnostics.DumpMachine();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "70908857", + "metadata": {}, + "outputs": [], + "source": [ + "qsharp.config['experimental.simulators.stabilizerStateStyle'] = 'matrixWithoutDestabilizers'" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "89b437a8", + "metadata": {}, + "outputs": [ + { + "data": { + "application/json": "{\"Table\":{\"SchemaVersion\":1,\"Dimensions\":[8,9],\"Data\":[false,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false],\"AsArray\":[false,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false]},\"Data\":[false,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false],\"NQubits\":4}", + "text/html": [ + "\r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + "\r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + "
Stabilizer state
# of qubits4
State data$$\\left(\\begin{array}{cccc|cccc|c}1 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 1 & 1 & 0 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0\\\\\n", + "0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0\\end{array}\\right)$$
\r\n", + " " + ], + "text/plain": [ + "Microsoft.Quantum.Experimental.StabilizerState" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "()" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "DumpBellPair.simulate_noise()" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "40431ed1", + "metadata": {}, + "outputs": [], + "source": [ + "qsharp.config['experimental.simulators.stabilizerStateStyle'] = 'denseGroupPresentation'" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "1cc366e4", + "metadata": {}, + "outputs": [ + { + "data": { + "application/json": "{\"Table\":{\"SchemaVersion\":1,\"Dimensions\":[8,9],\"Data\":[false,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false],\"AsArray\":[false,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false]},\"Data\":[false,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false],\"NQubits\":4}", + "text/html": [ + "\r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + "\r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + "
Stabilizer state
# of qubits4
State data$$\\left\\langle XX𝟙𝟙, ZZ𝟙𝟙, 𝟙𝟙Z𝟙, -𝟙𝟙𝟙Z \\right\\rangle$$
\r\n", + " " + ], + "text/plain": [ + "Microsoft.Quantum.Experimental.StabilizerState" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "()" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "DumpBellPair.simulate_noise()" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "07439931", + "metadata": {}, + "outputs": [], + "source": [ + "qsharp.config['experimental.simulators.stabilizerStateStyle'] = 'sparseGroupPresentation'" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "7f4560f8", + "metadata": {}, + "outputs": [ + { + "data": { + "application/json": "{\"Table\":{\"SchemaVersion\":1,\"Dimensions\":[8,9],\"Data\":[false,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false],\"AsArray\":[false,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false]},\"Data\":[false,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false],\"NQubits\":4}", + "text/html": [ + "\r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + "\r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + "
Stabilizer state
# of qubits4
State data$$\\left\\langle X_{0}X_{1}, Z_{0}Z_{1}, Z_{2}, -Z_{3} \\right\\rangle$$
\r\n", + " " + ], + "text/plain": [ + "Microsoft.Quantum.Experimental.StabilizerState" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "()" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "DumpBellPair.simulate_noise()" + ] + }, + { + "cell_type": "markdown", + "id": "58b9d547", + "metadata": {}, + "source": [ + "So far, we've only used ideal stabilizer simulation, but what happens if one of our operations is followed by a mixed Pauli channel?" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "5b80e500", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "SequenceProcess(n_qubits=1, processes=[ChpDecompositionProcess(n_qubits=1, operations=[Hadamard(idx_target=0)]), MixedPauliProcess(n_qubits=1, operators=[(0.9, 'I'), (0.1, 'Z')])])" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "noise_model['h'] = qsharp.experimental.SequenceProcess(1, \n", + " [\n", + " qsharp.experimental.ChpDecompositionProcess(1, [\n", + " qsharp.experimental.Hadamard(0)\n", + " ]),\n", + " qsharp.experimental.MixedPauliProcess(1, [\n", + " (0.9, 'I'),\n", + " (0.1, 'Z')\n", + " ])\n", + " ]\n", + ")\n", + "noise_model['h']" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "94c8b146", + "metadata": {}, + "outputs": [], + "source": [ + "qsharp.experimental.set_noise_model(noise_model)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "c517bb74", + "metadata": {}, + "outputs": [ + { + "data": { + "application/json": "{\"Table\":{\"SchemaVersion\":1,\"Dimensions\":[8,9],\"Data\":[false,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false],\"AsArray\":[false,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false]},\"Data\":[false,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false],\"NQubits\":4}", + "text/html": [ + "\r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + "\r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + "
Stabilizer state
# of qubits4
State data$$\\left\\langle X_{0}X_{1}, Z_{0}Z_{1}, Z_{2}, -Z_{3} \\right\\rangle$$
\r\n", + " " + ], + "text/plain": [ + "Microsoft.Quantum.Experimental.StabilizerState" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "()" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "DumpBellPair.simulate_noise()" + ] + }, + { + "cell_type": "markdown", + "id": "a64c1a27", + "metadata": {}, + "source": [ + "## Epilogue" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "56168b7e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'iqsharp': LooseVersion ('1.0.0'),\n", + " 'Jupyter Core': LooseVersion ('1.5.0.0'),\n", + " '.NET Runtime': LooseVersion ('.NETCoreApp,Version=v3.1'),\n", + " 'qsharp': LooseVersion ('0.0.1.0a1'),\n", + " 'experimental': {'simulators': {'features': ['DEFAULT'],\n", + " 'name': 'Microsoft.Quantum.Experimental.Simulators',\n", + " 'opt_level': '3',\n", + " 'target': 'x86_64-pc-windows-msvc',\n", + " 'version': '0.17.210627752-alpha'}}}" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "qsharp.component_versions()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "85f8f2e8", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/documentation/experimental-simulators.md b/documentation/experimental-simulators.md new file mode 100644 index 00000000000..1336b286761 --- /dev/null +++ b/documentation/experimental-simulators.md @@ -0,0 +1,39 @@ +# Using the Experimental Simulators for the Quantum Development Kit + +As an experimental feature, the Quantum Development Kit provides capabilities for noisy and stabilizer simulation. This feature allows for simulating the behavior of Q# programs under the influence of noise, and for using the stabilizer representation (a.k.a. CHP simulation) with programs that only call Clifford operations. + +> For more information about the development of this feature, please see the GitHub issue at . + +Currently, the experimental simulators are supported for use with: + +- C# host programs +- Python host programs +- Q# standalone notebooks + +The experimental simulators are not yet supported by: + +- Q# standalone command-line programs +- QIR-based executables + +## Using Experimental Simulators from Python + +> ### **ⓘ** TIP +> +> See the [example on using the experimental simulators from Python](./examples/experimental-simulators-from-python.ipynb) for more details. + +Once you have the right version of the `qsharp-core` Python package installed, you can enable the use of the experimental simulators by using the `qsharp.experimental` module: + +```python +import qsharp +import qsharp.experimental +qsharp.experimental.enable_noisy_simulation() +``` + +After calling `enable_noisy_simulation()`, Q# operations imported into Python will expose a `.simulate_noise()` method that can be used to run Q# programs against the experimental simulators. + +By default, `.simulate_noise()` will assume an ideal error model (that is, no noise). To configure a particular error model, use the `qsharp.experimental.get_noise_model` and `qsharp.experimental.set_noise_model` functions to get and set the current noise model for the experimental simulators. Each error model is represented as a dictionary from intrinsic operation names to objects representing the errors in those intrinsic operations. + +For open systems simulation, error channels can be represented by [QuTiP](https://qutip.org/) `Qobj` objects encoding superoperators. + +> **Known limitation**: Currently, error channels for stabilizer simulation must be specified manually by their JSON serialization. + diff --git a/omnisharp.json b/omnisharp.json new file mode 100644 index 00000000000..d705c6415c8 --- /dev/null +++ b/omnisharp.json @@ -0,0 +1,5 @@ +{ + "script": { + "enableScriptNuGetReferences": true + } +} \ No newline at end of file diff --git a/src/Qir/Runtime/lib/QIR/CMakeLists.txt b/src/Qir/Runtime/lib/QIR/CMakeLists.txt index 9dbd2b9cd74..c9812d73d6b 100644 --- a/src/Qir/Runtime/lib/QIR/CMakeLists.txt +++ b/src/Qir/Runtime/lib/QIR/CMakeLists.txt @@ -19,6 +19,7 @@ set(rt_sup_source_files delegated.cpp strings.cpp utils.cpp + QubitManager.cpp ) # Produce object lib we'll use to create a shared lib (so/dll) later on diff --git a/src/Qir/Runtime/lib/QIR/QubitManager.cpp b/src/Qir/Runtime/lib/QIR/QubitManager.cpp new file mode 100644 index 00000000000..7e200aa5c44 --- /dev/null +++ b/src/Qir/Runtime/lib/QIR/QubitManager.cpp @@ -0,0 +1,522 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "QubitManager.hpp" +#include "QirRuntime.hpp" // For quantum__rt__fail_cstr +#include // For memcpy + +namespace Microsoft +{ +namespace Quantum +{ + +// +// Failing in case of errors +// + +[[noreturn]] static void FailNow(const char* message) +{ + quantum__rt__fail_cstr(message); +} + +static void FailIf(bool condition, const char* message) +{ + if (condition) + { + quantum__rt__fail_cstr(message); + } +} + +// +// QubitListInSharedArray +// + +CQubitManager::QubitListInSharedArray::QubitListInSharedArray( + QubitIdType startId, + QubitIdType endId, + QubitIdType* sharedQubitStatusArray): + firstElement(startId), + lastElement(endId) +{ + FailIf(startId > endId || startId < 0 || endId == MaximumQubitCapacity, + "Incorrect boundaries in the linked list initialization."); + FailIf(sharedQubitStatusArray == nullptr, "Shared status array is not provided."); + + for (QubitIdType i = startId; i < endId; i++) { + sharedQubitStatusArray[i] = i + 1; // Current element points to the next element. + } + sharedQubitStatusArray[endId] = NoneMarker; // Last element ends the chain. +} + +bool CQubitManager::QubitListInSharedArray::IsEmpty() const +{ + return firstElement == NoneMarker; +} + +void CQubitManager::QubitListInSharedArray::AddQubit(QubitIdType id, bool addToFront, QubitIdType* sharedQubitStatusArray) +{ + FailIf(id == NoneMarker, "Incorrect qubit id, cannot add it to the list."); + FailIf(sharedQubitStatusArray == nullptr, "Shared status array is not provided."); + + // If the list is empty, we initialize it with the new element. + if (IsEmpty()) + { + firstElement = id; + lastElement = id; + sharedQubitStatusArray[id] = NoneMarker; // List with a single elemenet in the chain. + return; + } + + if (addToFront) + { + sharedQubitStatusArray[id] = firstElement; // The new element will point to the former first element. + firstElement = id; // The new element is now the first in the chain. + } else + { + sharedQubitStatusArray[lastElement] = id; // The last element will point to the new element. + sharedQubitStatusArray[id] = NoneMarker; // The new element will end the chain. + lastElement = id; // The new element will be the last element in the chain. + } +} + +CQubitManager::QubitIdType CQubitManager::QubitListInSharedArray::TakeQubitFromFront(QubitIdType* sharedQubitStatusArray) +{ + FailIf(sharedQubitStatusArray == nullptr, "Shared status array is not provided."); + + // First element will be returned. It is 'NoneMarker' if the list is empty. + QubitIdType result = firstElement; + + // Advance list start to the next element if list is not empty. + if (!IsEmpty()) + { + firstElement = sharedQubitStatusArray[firstElement]; // The second element will be the first. + } + + // Drop pointer to the last element if list becomes empty. + if (IsEmpty()) + { + lastElement = NoneMarker; + } + + if (result != NoneMarker) + { + sharedQubitStatusArray[result] = AllocatedMarker; + } + + return result; +} + +void CQubitManager::QubitListInSharedArray::MoveAllQubitsFrom(QubitListInSharedArray& source, QubitIdType* sharedQubitStatusArray) +{ + FailIf(sharedQubitStatusArray == nullptr, "Shared status array is not provided."); + + // No need to do anthing if source is empty. + if (source.IsEmpty()) + { + return; + } + + if (this->IsEmpty()) + { + // If this list is empty, we'll just set it to the source list. + lastElement = source.lastElement; + } else + { + // Attach source at the beginning of the list if both lists aren't empty. + sharedQubitStatusArray[source.lastElement] = firstElement; // The last element of the source chain will point to the first element of this chain. + } + firstElement = source.firstElement; // The first element from the source chain will be the first element of this chain. + + // Remove all elements from source. + source.firstElement = NoneMarker; + source.lastElement = NoneMarker; +} + + +// +// RestrictedReuseArea +// + +CQubitManager::RestrictedReuseArea::RestrictedReuseArea( + QubitListInSharedArray freeQubits): + FreeQubitsReuseProhibited(), // Default costructor + FreeQubitsReuseAllowed(freeQubits) // Default shallow copy. +{ +} + + +// +// CRestrictedReuseAreaStack +// + +void CQubitManager::CRestrictedReuseAreaStack::PushToBack(RestrictedReuseArea area) +{ + FailIf(Count() >= std::numeric_limits::max(), "Too many nested restricted reuse areas."); + this->insert(this->end(), area); +} + +CQubitManager::RestrictedReuseArea CQubitManager::CRestrictedReuseAreaStack::PopFromBack() +{ + FailIf(this->empty(), "Cannot remove restricted reuse area from an empty set."); + RestrictedReuseArea result = this->back(); + this->pop_back(); + return result; +} + +CQubitManager::RestrictedReuseArea& CQubitManager::CRestrictedReuseAreaStack::PeekBack() +{ + return this->back(); +} + +int32_t CQubitManager::CRestrictedReuseAreaStack::Count() const +{ + // The size should never exceed int32_t. + return static_cast(this->size()); +} + +// +// CQubitManager +// + +CQubitManager::CQubitManager( + QubitIdType initialQubitCapacity, + bool mayExtendCapacity, + bool encourageReuse): + mayExtendCapacity(mayExtendCapacity), + encourageReuse(encourageReuse), + qubitCapacity(initialQubitCapacity) +{ + FailIf(qubitCapacity <= 0, "Qubit capacity must be positive."); + sharedQubitStatusArray = new QubitIdType[qubitCapacity]; + + // These objects are passed by value (copies are created) + QubitListInSharedArray FreeQubitsFresh(0, qubitCapacity - 1, sharedQubitStatusArray); + RestrictedReuseArea outermostArea(FreeQubitsFresh); + freeQubitsInAreas.PushToBack(outermostArea); + + freeQubitCount = qubitCapacity; +} + +CQubitManager::~CQubitManager() +{ + if (sharedQubitStatusArray != nullptr) + { + delete[] sharedQubitStatusArray; + sharedQubitStatusArray = nullptr; + } + // freeQubitsInAreas - direct member of the class, no need to delete. +} + +// Although it is not necessary to pass area IDs to these functions, such support may be added for extra checks. +void CQubitManager::StartRestrictedReuseArea() +{ + RestrictedReuseArea newArea; + freeQubitsInAreas.PushToBack(newArea); +} + +void CQubitManager::NextRestrictedReuseSegment() +{ + FailIf(freeQubitsInAreas.Count() <= 0, "Internal error! No reuse areas."); + FailIf(freeQubitsInAreas.Count() == 1, "NextRestrictedReuseSegment() without an active area."); + RestrictedReuseArea& currentArea = freeQubitsInAreas.PeekBack(); + // When new segment starts, reuse of all free qubits in the current area becomes prohibited. + currentArea.FreeQubitsReuseProhibited.MoveAllQubitsFrom(currentArea.FreeQubitsReuseAllowed, sharedQubitStatusArray); +} + +void CQubitManager::EndRestrictedReuseArea() +{ + FailIf(freeQubitsInAreas.Count() < 2, "EndRestrictedReuseArea() without an active area."); + RestrictedReuseArea areaAboutToEnd = freeQubitsInAreas.PopFromBack(); + RestrictedReuseArea& containingArea = freeQubitsInAreas.PeekBack(); + // When area ends, reuse of all free qubits from this area becomes allowed. + containingArea.FreeQubitsReuseAllowed.MoveAllQubitsFrom(areaAboutToEnd.FreeQubitsReuseProhibited, sharedQubitStatusArray); + containingArea.FreeQubitsReuseAllowed.MoveAllQubitsFrom(areaAboutToEnd.FreeQubitsReuseAllowed, sharedQubitStatusArray); +} + +Qubit CQubitManager::Allocate() +{ + QubitIdType newQubitId = AllocateQubitId(); + FailIf(newQubitId == NoneMarker, "Not enough qubits."); + return CreateQubitObject(newQubitId); +} + +void CQubitManager::Allocate(Qubit* qubitsToAllocate, int32_t qubitCountToAllocate) +{ + if (qubitCountToAllocate == 0) + { + return; + } + FailIf(qubitCountToAllocate < 0, "Cannot allocate negative number of qubits."); + FailIf(qubitsToAllocate == nullptr, "No array provided for qubits to be allocated."); + + // Consider optimization for initial allocation of a large array at once + for (int32_t i = 0; i < qubitCountToAllocate; i++) + { + QubitIdType newQubitId = AllocateQubitId(); + if (newQubitId == NoneMarker) + { + for (int32_t k = 0; k < i; k++) + { + Release(qubitsToAllocate[k]); + } + FailNow("Not enough qubits."); + } + qubitsToAllocate[i] = CreateQubitObject(newQubitId); + } +} + +void CQubitManager::Release(Qubit qubit) +{ + FailIf(!IsValidQubit(qubit), "Qubit is not valid."); + ReleaseQubitId(QubitToId(qubit)); + DeleteQubitObject(qubit); +} + +void CQubitManager::Release(Qubit* qubitsToRelease, int32_t qubitCountToRelease) { + if (qubitCountToRelease == 0) + { + return; + } + FailIf(qubitCountToRelease < 0, "Cannot release negative number of qubits."); + FailIf(qubitsToRelease == nullptr, "No array provided with qubits to be released."); + + for (int32_t i = 0; i < qubitCountToRelease; i++) + { + Release(qubitsToRelease[i]); + qubitsToRelease[i] = nullptr; + } +} + +Qubit CQubitManager::Borrow() +{ + // We don't support true borrowing/returning at the moment. + return Allocate(); +} + +void CQubitManager::Borrow(Qubit* qubitsToBorrow, int32_t qubitCountToBorrow) +{ + // We don't support true borrowing/returning at the moment. + return Allocate(qubitsToBorrow, qubitCountToBorrow); +} + +void CQubitManager::Return(Qubit qubit) +{ + // We don't support true borrowing/returning at the moment. + Release(qubit); +} + +void CQubitManager::Return(Qubit* qubitsToReturn, int32_t qubitCountToReturn) +{ + // We don't support true borrowing/returning at the moment. + Release(qubitsToReturn, qubitCountToReturn); +} + +void CQubitManager::Disable(Qubit qubit) +{ + FailIf(!IsValidQubit(qubit), "Qubit is not valid."); + QubitIdType id = QubitToId(qubit); + + // We can only disable explicitly allocated qubits that were not borrowed. + FailIf(!IsExplicitlyAllocatedId(id), "Cannot disable qubit that is not explicitly allocated."); + sharedQubitStatusArray[id] = DisabledMarker; + + disabledQubitCount++; + FailIf(disabledQubitCount <= 0, "Incorrect disabled qubit count."); + allocatedQubitCount--; + FailIf(allocatedQubitCount < 0, "Incorrect allocated qubit count."); +} + +void CQubitManager::Disable(Qubit* qubitsToDisable, int32_t qubitCountToDisable) +{ + if (qubitCountToDisable == 0) + { + return; + } + FailIf(qubitCountToDisable < 0, "Cannot disable negative number of qubits."); + FailIf(qubitsToDisable == nullptr, "No array provided with qubits to be disabled."); + + for (int32_t i = 0; i < qubitCountToDisable; i++) + { + Disable(qubitsToDisable[i]); + } +} + +bool CQubitManager::IsValidId(QubitIdType id) const +{ + return (id >= 0) && (id < qubitCapacity); +} + +bool CQubitManager::IsDisabledId(QubitIdType id) const +{ + return sharedQubitStatusArray[id] == DisabledMarker; +} + +bool CQubitManager::IsExplicitlyAllocatedId(QubitIdType id) const +{ + return sharedQubitStatusArray[id] == AllocatedMarker; +} + +bool CQubitManager::IsFreeId(QubitIdType id) const +{ + return sharedQubitStatusArray[id] >= 0; +} + + +bool CQubitManager::IsValidQubit(Qubit qubit) const +{ + return IsValidId(QubitToId(qubit)); +} + +bool CQubitManager::IsDisabledQubit(Qubit qubit) const +{ + return IsValidQubit(qubit) && IsDisabledId(QubitToId(qubit)); +} + +bool CQubitManager::IsExplicitlyAllocatedQubit(Qubit qubit) const +{ + return IsValidQubit(qubit) && IsExplicitlyAllocatedId(QubitToId(qubit)); +} + +bool CQubitManager::IsFreeQubitId(QubitIdType id) const +{ + return IsValidId(id) && IsFreeId(id); +} + +CQubitManager::QubitIdType CQubitManager::GetQubitId(Qubit qubit) const +{ + FailIf(!IsValidQubit(qubit), "Not a valid qubit."); + return QubitToId(qubit); +} + + +Qubit CQubitManager::CreateQubitObject(QubitIdType id) +{ + // Make sure the static_cast won't overflow: + FailIf(id < 0 || id > std::numeric_limits::max(), "Qubit id is out of range."); + intptr_t pointerSizedId = static_cast(id); + return reinterpret_cast(pointerSizedId); +} + +void CQubitManager::DeleteQubitObject(Qubit qubit) +{ + // Do nothing. By default we store qubit Id in place of a pointer to a qubit. +} + +CQubitManager::QubitIdType CQubitManager::QubitToId(Qubit qubit) const +{ + intptr_t pointerSizedId = reinterpret_cast(qubit); + // Make sure the static_cast won't overflow: + FailIf(pointerSizedId < 0 || pointerSizedId > std::numeric_limits::max(), "Qubit id is out of range."); + return static_cast(pointerSizedId); +} + +void CQubitManager::EnsureCapacity(QubitIdType requestedCapacity) +{ + FailIf(requestedCapacity <= 0, "Requested qubit capacity must be positive."); + if (requestedCapacity <= qubitCapacity) + { + return; + } + // We need to reallocate shared status array, but there's no need to adjust + // existing values (NonMarker or indexes in the array). + + // Prepare new shared status array + QubitIdType* newStatusArray = new QubitIdType[requestedCapacity]; + memcpy(newStatusArray, sharedQubitStatusArray, qubitCapacity * sizeof(newStatusArray[0])); + QubitListInSharedArray newFreeQubits(qubitCapacity, requestedCapacity - 1, newStatusArray); + + // Set new data. All fresh new qubits are added to the free qubits in the outermost area. + freeQubitCount += requestedCapacity - qubitCapacity; + FailIf(freeQubitCount <= 0, "Incorrect free qubit count."); + delete[] sharedQubitStatusArray; + sharedQubitStatusArray = newStatusArray; + qubitCapacity = requestedCapacity; + freeQubitsInAreas[0].FreeQubitsReuseAllowed.MoveAllQubitsFrom(newFreeQubits, sharedQubitStatusArray); +} + +CQubitManager::QubitIdType CQubitManager::TakeFreeQubitId() +{ + // Possible future optimization: we may store and maintain links to the next + // area with non-empty free list. Need to check amortized complexity... + + QubitIdType id = NoneMarker; + if (encourageReuse) + { + // When reuse is encouraged, we start with the innermost area + for (CRestrictedReuseAreaStack::reverse_iterator rit = freeQubitsInAreas.rbegin(); rit != freeQubitsInAreas.rend(); ++rit) + { + id = rit->FreeQubitsReuseAllowed.TakeQubitFromFront(sharedQubitStatusArray); + if (id != NoneMarker) + { + break; + } + } + } else + { + // When reuse is discouraged, we start with the outermost area + for (CRestrictedReuseAreaStack::iterator it = freeQubitsInAreas.begin(); it != freeQubitsInAreas.end(); ++it) + { + id = it->FreeQubitsReuseAllowed.TakeQubitFromFront(sharedQubitStatusArray); + if (id != NoneMarker) + { + break; + } + } + } + if (id != NoneMarker) { + FailIf(id < 0 || id >= qubitCapacity, "Internal Error: Allocated invalid qubit."); + allocatedQubitCount++; + FailIf(allocatedQubitCount <= 0, "Incorrect allocated qubit count."); + freeQubitCount--; + FailIf(freeQubitCount < 0, "Incorrect free qubit count."); + } + return id; +} + +CQubitManager::QubitIdType CQubitManager::AllocateQubitId() +{ + QubitIdType newQubitId = TakeFreeQubitId(); + if (newQubitId == NoneMarker && mayExtendCapacity) + { + QubitIdType newQubitCapacity = qubitCapacity * 2; + FailIf(newQubitCapacity <= qubitCapacity, "Cannot extend capacity."); + EnsureCapacity(newQubitCapacity); + newQubitId = TakeFreeQubitId(); + } + return newQubitId; +} + +void CQubitManager::ReleaseQubitId(QubitIdType id) +{ + FailIf(id < 0 || id >= qubitCapacity, "Internal Error: Cannot release an invalid qubit."); + if (IsDisabledId(id)) + { + // Nothing to do. Qubit will stay disabled. + return; + } + + FailIf(!IsExplicitlyAllocatedId(id), "Attempt to free qubit that has not been allocated."); + + if (mayExtendCapacity && !encourageReuse) + { + // We can extend capcity and don't want reuse => Qubits will never be reused => Discard qubit. + // We put it in its own "free" list, this list will never be found again and qubit will not be reused. + sharedQubitStatusArray[id] = NoneMarker; + } else + { + // Released qubits are added to reuse area/segment in which they were released + // (rather than area/segment where they are allocated). + // Although counterintuitive, this makes code simple. + // This is reasonable because qubits should be allocated and released in the same segment. (This is not enforced) + freeQubitsInAreas.PeekBack().FreeQubitsReuseAllowed.AddQubit(id, encourageReuse, sharedQubitStatusArray); + } + + freeQubitCount++; + FailIf(freeQubitCount <= 0, "Incorrect free qubit count."); + allocatedQubitCount--; + FailIf(allocatedQubitCount < 0, "Incorrect allocated qubit count."); +} + + +} +} diff --git a/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp b/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp index 6d9103d6779..b6da62d9443 100644 --- a/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp +++ b/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp @@ -17,6 +17,7 @@ #include "QSharpSimApi_I.hpp" #include "SimFactory.hpp" #include "OutputStream.hpp" +#include "QubitManager.hpp" #ifdef _WIN32 #include @@ -107,11 +108,13 @@ namespace Quantum const QUANTUM_SIMULATOR handle = 0; unsigned simulatorId = -1; - unsigned nextQubitId = 0; // the QuantumSimulator expects contiguous ids, starting from 0 + // the QuantumSimulator expects contiguous ids, starting from 0 + std::unique_ptr qubitManager; unsigned GetQubitId(Qubit qubit) const { - return static_cast(reinterpret_cast(qubit)); + // Qubit manager uses unsigned range of int32_t for qubit ids. + return static_cast(qubitManager->GetQubitId(qubit)); } std::vector GetQubitIds(long num, Qubit* qubits) const @@ -120,7 +123,7 @@ namespace Quantum ids.reserve(num); for (long i = 0; i < num; i++) { - ids.push_back(static_cast(reinterpret_cast(qubits[i]))); + ids.push_back(GetQubitId(qubits[i])); } return ids; } @@ -156,6 +159,7 @@ namespace Quantum typedef unsigned (*TInit)(); static TInit initSimulatorInstance = reinterpret_cast(this->GetProc("init")); + qubitManager = std::make_unique(); this->simulatorId = initSimulatorInstance(); } ~CFullstateSimulator() @@ -195,10 +199,10 @@ namespace Quantum typedef void (*TAllocateQubit)(unsigned, unsigned); static TAllocateQubit allocateQubit = reinterpret_cast(this->GetProc("allocateQubit")); - const unsigned id = this->nextQubitId; - allocateQubit(this->simulatorId, id); - this->nextQubitId++; - return reinterpret_cast(id); + Qubit q = qubitManager->Allocate(); // Allocate qubit in qubit manager. + unsigned id = GetQubitId(q); // Get its id. + allocateQubit(this->simulatorId, id); // Allocate it in the simulator. + return q; } void ReleaseQubit(Qubit q) override @@ -206,7 +210,8 @@ namespace Quantum typedef void (*TReleaseQubit)(unsigned, unsigned); static TReleaseQubit releaseQubit = reinterpret_cast(this->GetProc("release")); - releaseQubit(this->simulatorId, GetQubitId(q)); + releaseQubit(this->simulatorId, GetQubitId(q)); // Release qubit in the simulator. + qubitManager->Release(q); // Release it in the qubit manager. } Result Measure(long numBases, PauliId bases[], long numTargets, Qubit targets[]) override diff --git a/src/Qir/Runtime/public/QubitManager.hpp b/src/Qir/Runtime/public/QubitManager.hpp new file mode 100644 index 00000000000..c45a74ce292 --- /dev/null +++ b/src/Qir/Runtime/public/QubitManager.hpp @@ -0,0 +1,255 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include +#include +#include + +#include "CoreTypes.hpp" + +namespace Microsoft +{ +namespace Quantum +{ + // CQubitManager maintains mapping between user qubit objects and + // underlying qubit identifiers (Ids). When user program allocates + // a qubit, Qubit Manager decides whether to allocate a fresh id or + // reuse existing id that was previously freed. When user program + // releases a qubit, Qubit Manager tracks it as a free qubit id. + // Decision to reuse a qubit id is influenced by restricted reuse + // areas. When a qubit id is freed in one section of a restricted + // reuse area, it cannot be reused in other sections of the same area. + // True borrowing of qubits is not supported and is currently + // implemented as a plain allocation. + class QIR_SHARED_API CQubitManager + { + public: + using QubitIdType = ::int32_t; + + // We want status array to be reasonably large. + constexpr static QubitIdType DefaultQubitCapacity = 8; + + // Indexes in the status array can potentially be in range 0 .. QubitIdType.MaxValue-1. + // This gives maximum capacity as QubitIdType.MaxValue. Actual configured capacity may be less than this. + // Index equal to QubitIdType.MaxValue doesn't exist and is reserved for 'NoneMarker' - list terminator. + constexpr static QubitIdType MaximumQubitCapacity = std::numeric_limits::max(); + + public: + CQubitManager( + QubitIdType initialQubitCapacity = DefaultQubitCapacity, + bool mayExtendCapacity = true, + bool encourageReuse = true); + + // No complex scenarios for now. Don't need to support copying/moving. + CQubitManager(const CQubitManager&) = delete; + CQubitManager& operator = (const CQubitManager&) = delete; + virtual ~CQubitManager(); + + // Restricted reuse area control + void StartRestrictedReuseArea(); + void NextRestrictedReuseSegment(); + void EndRestrictedReuseArea(); + + // Allocate a qubit. Extend capacity if necessary and possible. + // Fail if the qubit cannot be allocated. + // Computation complexity is O(number of nested restricted reuse areas). + Qubit Allocate(); + // Allocate qubitCountToAllocate qubits and store them in the provided array. Extend manager capacity if necessary and possible. + // Fail without allocating any qubits if the qubits cannot be allocated. + // Caller is responsible for providing array of sufficient size to hold qubitCountToAllocate. + void Allocate(Qubit* qubitsToAllocate, int32_t qubitCountToAllocate); + + // Releases a given qubit. + void Release(Qubit qubit); + // Releases qubitCountToRelease qubits in the provided array. + // Caller is responsible for managing memory used by the array itself (i.e. delete[] array if it was dynamically allocated). + void Release(Qubit* qubitsToRelease, int32_t qubitCountToRelease); + + // Borrow (We treat borrowing as allocation currently) + Qubit Borrow(); + void Borrow(Qubit* qubitsToBorrow, int32_t qubitCountToBorrow); + // Return (We treat returning as release currently) + void Return(Qubit qubit); + void Return(Qubit* qubitsToReturn, int32_t qubitCountToReturn); + + // Disables a given qubit. + // Once a qubit is disabled it can never be "enabled" or reallocated. + void Disable(Qubit qubit); + // Disables a set of given qubits. + // Once a qubit is disabled it can never be "enabled" or reallocated. + void Disable(Qubit* qubitsToDisable, int32_t qubitCountToDisable); + + bool IsValidQubit(Qubit qubit) const; + bool IsDisabledQubit(Qubit qubit) const; + bool IsExplicitlyAllocatedQubit(Qubit qubit) const; + bool IsFreeQubitId(QubitIdType id) const; + + QubitIdType GetQubitId(Qubit qubit) const; + + // Qubit counts: + + // Number of qubits that are disabled. When an explicitly allocated qubit + // gets disabled, it is removed from allocated count and is added to + // disabled count immediately. Subsequent Release doesn't affect counts. + int32_t GetDisabledQubitCount() const { return disabledQubitCount; } + + // Number of qubits that are explicitly allocated. This counter gets + // increased on allocation of a qubit and decreased on release of a qubit. + // Note that we treat borrowing as allocation now. + int32_t GetAllocatedQubitCount() const { return allocatedQubitCount; } + + // Number of free qubits that are currently tracked by this qubit manager. + // Note that when qubit manager may extend capacity, this doesn't account + // for qubits that may be potentially added in future via capacity extension. + // If qubit manager may extend capacity and reuse is discouraged, released + // qubits still increase this number even though they cannot be reused. + int32_t GetFreeQubitCount() const { return freeQubitCount; } + + // Total number of qubits that are currently tracked by this qubit manager. + int32_t GetQubitCapacity() const { return qubitCapacity; } + bool GetMayExtendCapacity() const { return mayExtendCapacity; } + bool GetEncourageReuse() const { return encourageReuse; } + + protected: + // May be overriden to create a custom Qubit object. + // When not overriden, it just stores qubit Id in place of a pointer to a qubit. + // id: unique qubit id + // Returns a newly instantiated qubit. + virtual Qubit CreateQubitObject(QubitIdType id); + + // May be overriden to delete a custom Qubit object. + // Must be overriden if CreateQubitObject is overriden. + // When not overriden, it does nothing. + // qubit: pointer to QUBIT + virtual void DeleteQubitObject(Qubit qubit); + + // May be overriden to get a qubit id from a custom qubit object. + // Must be overriden if CreateQubitObject is overriden. + // When not overriden, it just reinterprets pointer to qubit as a qubit id. + // qubit: pointer to QUBIT + // Returns id of a qubit pointed to by qubit. + virtual QubitIdType QubitToId(Qubit qubit) const; + + private: + // The end of free lists are marked with NoneMarker value. It is used like null for pointers. + // This value is non-negative just like other values in the free lists. See sharedQubitStatusArray. + constexpr static QubitIdType NoneMarker = std::numeric_limits::max(); + + // Explicitly allocated qubits are marked with AllocatedMarker value. + // If borrowing is implemented, negative values may be used for refcounting. + // See sharedQubitStatusArray. + constexpr static QubitIdType AllocatedMarker = std::numeric_limits::min(); + + // Disabled qubits are marked with this value. See sharedQubitStatusArray. + constexpr static QubitIdType DisabledMarker = -1; + + // QubitListInSharedArray implements a singly-linked list with "pointers" + // to the first and the last element stored. Pointers are the indexes + // in a single shared array. Shared array isn't sotored in this class + // because it can be reallocated. This class maintains status of elements + // in the list by virtue of linking them as part of this list. This class + // sets Allocated status of elementes taken from the list (via TakeQubitFromFront). + // This class is small, contains no C++ pointers and relies on default shallow copying/destruction. + struct QubitListInSharedArray final + { + private: + QubitIdType firstElement = NoneMarker; + QubitIdType lastElement = NoneMarker; + // We are not storing pointer to shared array because it can be reallocated. + // Indexes and special values remain the same on such reallocations. + + public: + // Initialize empty list + QubitListInSharedArray() = default; + + // Initialize as a list with sequential elements from startId to endId inclusve. + QubitListInSharedArray(QubitIdType startId, QubitIdType endId, QubitIdType* sharedQubitStatusArray); + + bool IsEmpty() const; + void AddQubit(QubitIdType id, bool addToFront, QubitIdType* sharedQubitStatusArray); + QubitIdType TakeQubitFromFront(QubitIdType* sharedQubitStatusArray); + void MoveAllQubitsFrom(QubitListInSharedArray& source, QubitIdType* sharedQubitStatusArray); + }; + + // Restricted reuse area consists of multiple segments. Qubits released + // in one segment cannot be reused in another. One restricted reuse area + // can be nested in a segment of another restricted reuse area. This class + // tracks current segment of an area by maintaining a list of free qubits + // in a shared status array FreeQubitsReuseAllowed. Previous segments are + // tracked collectively (not individually) by maintaining FreeQubitsReuseProhibited. + // This class is small, contains no C++ pointers and relies on default shallow copying/destruction. + struct RestrictedReuseArea final + { + public: + QubitListInSharedArray FreeQubitsReuseProhibited; + QubitListInSharedArray FreeQubitsReuseAllowed; + + RestrictedReuseArea() = default; + RestrictedReuseArea(QubitListInSharedArray freeQubits); + }; + + // This is NOT a pure stack! We modify it only by push/pop, but we also iterate over elements. + class CRestrictedReuseAreaStack final : public std::vector + { + public: + // No complex scenarios for now. Don't need to support copying/moving. + CRestrictedReuseAreaStack() = default; + CRestrictedReuseAreaStack(const CRestrictedReuseAreaStack&) = delete; + CRestrictedReuseAreaStack& operator = (const CRestrictedReuseAreaStack&) = delete; + ~CRestrictedReuseAreaStack() = default; + + void PushToBack(RestrictedReuseArea area); + RestrictedReuseArea PopFromBack(); + RestrictedReuseArea& PeekBack(); + int32_t Count() const; + }; + + private: + void EnsureCapacity(QubitIdType requestedCapacity); + + // Take free qubit id from a free list without extending capacity. + // First non-empty free list among nested restricted reuse areas are considered. + QubitIdType TakeFreeQubitId(); + // Allocate free qubit id extending capacity if necessary and possible. + QubitIdType AllocateQubitId(); + // Put qubit id back into a free list for the current restricted reuse area. + void ReleaseQubitId(QubitIdType id); + + bool IsValidId(QubitIdType id) const; + bool IsDisabledId(QubitIdType id) const; + bool IsFreeId(QubitIdType id) const; + bool IsExplicitlyAllocatedId(QubitIdType id) const; + + // Configuration Properties: + bool mayExtendCapacity = true; + bool encourageReuse = true; + + // State: + // sharedQubitStatusArray is used to store statuses of all known qubits. + // Integer value at the index of the qubit id represents the status of that qubit. + // (Ex: sharedQubitStatusArray[4] is the status of qubit with id = 4). + // Therefore qubit ids are in the range of [0..qubitCapacity). + // Capacity may be extended if MayExtendCapacity = true. + // If qubit X is allocated, sharedQubitStatusArray[X] = AllocatedMarker (negative number) + // If qubit X is disabled, sharedQubitStatusArray[X] = DisabledMarker (negative number) + // If qubit X is free, sharedQubitStatusArray[X] is a non-negative number, denote it Next(X). + // Next(X) is either the index of the next element in the list or the list terminator - NoneMarker. + // All free qubits form disjoint singly linked lists bound to to respective resricted reuse areas. + // Each area has two lists of free qubits - see RestrictedReuseArea. + QubitIdType* sharedQubitStatusArray = nullptr; + // qubitCapacity is always equal to the array size. + QubitIdType qubitCapacity = 0; + // All nested restricted reuse areas at the current moment. + // Fresh Free Qubits are added to the outermost area: freeQubitsInAreas[0].FreeQubitsReuseAllowed + CRestrictedReuseAreaStack freeQubitsInAreas; + + // Counts: + int32_t disabledQubitCount = 0; + int32_t allocatedQubitCount = 0; + int32_t freeQubitCount = 0; + }; + +} +} diff --git a/src/Qir/Runtime/unittests/CMakeLists.txt b/src/Qir/Runtime/unittests/CMakeLists.txt index 40c41776d2a..664fc359a8f 100644 --- a/src/Qir/Runtime/unittests/CMakeLists.txt +++ b/src/Qir/Runtime/unittests/CMakeLists.txt @@ -8,6 +8,7 @@ add_executable(qir-runtime-unittests QirRuntimeTests.cpp ToffoliTests.cpp TracerTests.cpp + QubitManagerTests.cpp $ $ $ diff --git a/src/Qir/Runtime/unittests/QubitManagerTests.cpp b/src/Qir/Runtime/unittests/QubitManagerTests.cpp new file mode 100644 index 00000000000..966b36f9645 --- /dev/null +++ b/src/Qir/Runtime/unittests/QubitManagerTests.cpp @@ -0,0 +1,253 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include + +#include "catch.hpp" + +#include "QubitManager.hpp" + +using namespace Microsoft::Quantum; + +TEST_CASE("Simple allocation and release of one qubit", "[QubitManagerBasic]") +{ + std::unique_ptr qm = std::make_unique(); + Qubit q = qm->Allocate(); + qm->Release(q); +} + +TEST_CASE("Allocation and reallocation of one qubit", "[QubitManagerBasic]") +{ + std::unique_ptr qm = std::make_unique(1, false, true); + REQUIRE(qm->GetFreeQubitCount() == 1); + REQUIRE(qm->GetAllocatedQubitCount() == 0); + Qubit q = qm->Allocate(); + REQUIRE(qm->GetQubitId(q) == 0); + REQUIRE(qm->GetFreeQubitCount() == 0); + REQUIRE(qm->GetAllocatedQubitCount() == 1); + REQUIRE_THROWS(qm->Allocate()); + REQUIRE(qm->GetFreeQubitCount() == 0); + REQUIRE(qm->GetAllocatedQubitCount() == 1); + qm->Release(q); + REQUIRE(qm->GetFreeQubitCount() == 1); + REQUIRE(qm->GetAllocatedQubitCount() == 0); + Qubit q0 = qm->Allocate(); + REQUIRE(qm->GetQubitId(q0) == 0); + REQUIRE(qm->GetFreeQubitCount() == 0); + REQUIRE(qm->GetAllocatedQubitCount() == 1); + qm->Release(q0); + REQUIRE(qm->GetFreeQubitCount() == 1); + REQUIRE(qm->GetAllocatedQubitCount() == 0); +} + +TEST_CASE("Qubit Status", "[QubitManagerBasic]") +{ + std::unique_ptr qm = std::make_unique(2, false, true); + + Qubit q0 = qm->Allocate(); + CQubitManager::QubitIdType q0id = qm->GetQubitId(q0); + REQUIRE(qm->IsValidQubit(q0)); + REQUIRE(qm->IsExplicitlyAllocatedQubit(q0)); + REQUIRE(!qm->IsDisabledQubit(q0)); + REQUIRE(!qm->IsFreeQubitId(q0id)); + REQUIRE(qm->GetAllocatedQubitCount() == 1); + + qm->Disable(q0); + REQUIRE(qm->IsValidQubit(q0)); + REQUIRE(!qm->IsExplicitlyAllocatedQubit(q0)); + REQUIRE(qm->IsDisabledQubit(q0)); + REQUIRE(!qm->IsFreeQubitId(q0id)); + REQUIRE(qm->GetDisabledQubitCount() == 1); + + qm->Release(q0); + REQUIRE(!qm->IsFreeQubitId(q0id)); + REQUIRE(qm->GetFreeQubitCount() == 1); + + Qubit q1 = qm->Allocate(); + CQubitManager::QubitIdType q1id = qm->GetQubitId(q1); + REQUIRE(q0id != q1id); + + REQUIRE(qm->IsValidQubit(q1)); + REQUIRE(qm->IsExplicitlyAllocatedQubit(q1)); + REQUIRE(!qm->IsDisabledQubit(q1)); + REQUIRE(!qm->IsFreeQubitId(q1id)); + + REQUIRE(qm->GetAllocatedQubitCount() == 1); + REQUIRE(qm->GetDisabledQubitCount() == 1); + REQUIRE(qm->GetFreeQubitCount() == 0); + + qm->Release(q1); + REQUIRE(qm->IsFreeQubitId(q1id)); +} + +TEST_CASE("Qubit Counts", "[QubitManagerBasic]") +{ + constexpr int totalQubitCount = 100; + constexpr int disabledQubitCount = 29; + constexpr int extraQubitCount = 43; + static_assert(extraQubitCount <= totalQubitCount); + static_assert(disabledQubitCount <= totalQubitCount); + // We don't want capacity to be extended at first... + static_assert(extraQubitCount + disabledQubitCount <= totalQubitCount); + + std::unique_ptr qm = std::make_unique(totalQubitCount, true, true); + REQUIRE(qm->GetQubitCapacity() == totalQubitCount); + REQUIRE(qm->GetFreeQubitCount() == totalQubitCount); + REQUIRE(qm->GetAllocatedQubitCount() == 0); + REQUIRE(qm->GetDisabledQubitCount() == 0); + + Qubit* qubits = new Qubit[disabledQubitCount]; + qm->Allocate(qubits, disabledQubitCount); + REQUIRE(qm->GetQubitCapacity() == totalQubitCount); + REQUIRE(qm->GetFreeQubitCount() == totalQubitCount-disabledQubitCount); + REQUIRE(qm->GetAllocatedQubitCount() == disabledQubitCount); + REQUIRE(qm->GetDisabledQubitCount() == 0); + + qm->Disable(qubits, disabledQubitCount); + REQUIRE(qm->GetQubitCapacity() == totalQubitCount); + REQUIRE(qm->GetFreeQubitCount() == totalQubitCount-disabledQubitCount); + REQUIRE(qm->GetAllocatedQubitCount() == 0); + REQUIRE(qm->GetDisabledQubitCount() == disabledQubitCount); + + qm->Release(qubits, disabledQubitCount); + REQUIRE(qm->GetQubitCapacity() == totalQubitCount); + REQUIRE(qm->GetFreeQubitCount() == totalQubitCount-disabledQubitCount); + REQUIRE(qm->GetAllocatedQubitCount() == 0); + REQUIRE(qm->GetDisabledQubitCount() == disabledQubitCount); + delete[] qubits; + + qubits = new Qubit[extraQubitCount]; + qm->Allocate(qubits, extraQubitCount); + REQUIRE(qm->GetQubitCapacity() == totalQubitCount); + REQUIRE(qm->GetFreeQubitCount() == totalQubitCount-disabledQubitCount-extraQubitCount); + REQUIRE(qm->GetAllocatedQubitCount() == extraQubitCount); + REQUIRE(qm->GetDisabledQubitCount() == disabledQubitCount); + + qm->Release(qubits, extraQubitCount); + REQUIRE(qm->GetQubitCapacity() == totalQubitCount); + REQUIRE(qm->GetFreeQubitCount() == totalQubitCount-disabledQubitCount); + REQUIRE(qm->GetAllocatedQubitCount() == 0); + REQUIRE(qm->GetDisabledQubitCount() == disabledQubitCount); + delete[] qubits; + + qubits = new Qubit[totalQubitCount]; + qm->Allocate(qubits, totalQubitCount); + REQUIRE(qm->GetQubitCapacity() > totalQubitCount); + REQUIRE(qm->GetAllocatedQubitCount() == totalQubitCount); + REQUIRE(qm->GetDisabledQubitCount() == disabledQubitCount); + + qm->Release(qubits, totalQubitCount); + REQUIRE(qm->GetQubitCapacity() > totalQubitCount); + REQUIRE(qm->GetAllocatedQubitCount() == 0); + REQUIRE(qm->GetDisabledQubitCount() == disabledQubitCount); + delete[] qubits; +} + +TEST_CASE("Allocation of released qubits when reuse is encouraged", "[QubitManagerBasic]") +{ + std::unique_ptr qm = std::make_unique(2, false, true); + REQUIRE(qm->GetFreeQubitCount() == 2); + Qubit q0 = qm->Allocate(); + Qubit q1 = qm->Allocate(); + REQUIRE(qm->GetQubitId(q0) == 0); // Qubit ids should be in order + REQUIRE(qm->GetQubitId(q1) == 1); + REQUIRE_THROWS(qm->Allocate()); + REQUIRE(qm->GetFreeQubitCount() == 0); + REQUIRE(qm->GetAllocatedQubitCount() == 2); + + qm->Release(q0); + Qubit q0a = qm->Allocate(); + REQUIRE(qm->GetQubitId(q0a) == 0); // It was the only one available + REQUIRE_THROWS(qm->Allocate()); + + qm->Release(q1); + qm->Release(q0a); + REQUIRE(qm->GetFreeQubitCount() == 2); + REQUIRE(qm->GetAllocatedQubitCount() == 0); + + Qubit q0b = qm->Allocate(); + Qubit q1a = qm->Allocate(); + REQUIRE(qm->GetQubitId(q0b) == 0); // By default reuse is encouraged, LIFO is used + REQUIRE(qm->GetQubitId(q1a) == 1); + REQUIRE_THROWS(qm->Allocate()); + REQUIRE(qm->GetFreeQubitCount() == 0); + REQUIRE(qm->GetAllocatedQubitCount() == 2); + + qm->Release(q0b); + qm->Release(q1a); +} + +TEST_CASE("Extending capacity", "[QubitManager]") +{ + std::unique_ptr qm = std::make_unique(1, true, true); + + Qubit q0 = qm->Allocate(); + REQUIRE(qm->GetQubitId(q0) == 0); + Qubit q1 = qm->Allocate(); // This should double capacity + REQUIRE(qm->GetQubitId(q1) == 1); + REQUIRE(qm->GetFreeQubitCount() == 0); + REQUIRE(qm->GetAllocatedQubitCount() == 2); + + qm->Release(q0); + Qubit q0a = qm->Allocate(); + REQUIRE(qm->GetQubitId(q0a) == 0); + Qubit q2 = qm->Allocate(); // This should double capacity again + REQUIRE(qm->GetQubitId(q2) == 2); + REQUIRE(qm->GetFreeQubitCount() == 1); + REQUIRE(qm->GetAllocatedQubitCount() == 3); + + qm->Release(q1); + qm->Release(q0a); + qm->Release(q2); + REQUIRE(qm->GetFreeQubitCount() == 4); + REQUIRE(qm->GetAllocatedQubitCount() == 0); + + Qubit* qqq = new Qubit[3]; + qm->Allocate(qqq, 3); + REQUIRE(qm->GetFreeQubitCount() == 1); + REQUIRE(qm->GetAllocatedQubitCount() == 3); + qm->Release(qqq, 3); + delete[] qqq; + REQUIRE(qm->GetFreeQubitCount() == 4); + REQUIRE(qm->GetAllocatedQubitCount() == 0); +} + +TEST_CASE("Restricted Area", "[QubitManager]") +{ + std::unique_ptr qm = std::make_unique(3, false, true); + + Qubit q0 = qm->Allocate(); + REQUIRE(qm->GetQubitId(q0) == 0); + + qm->StartRestrictedReuseArea(); + + // Allocates fresh qubit + Qubit q1 = qm->Allocate(); + REQUIRE(qm->GetQubitId(q1) == 1); + qm->Release(q1); // Released, but cannot be used in the next segment. + + qm->NextRestrictedReuseSegment(); + + // Allocates fresh qubit, q1 cannot be reused - it belongs to a differen segment. + Qubit q2 = qm->Allocate(); + REQUIRE(qm->GetQubitId(q2) == 2); + qm->Release(q2); + + Qubit q2a = qm->Allocate(); // Getting the same one as the one that's just released. + REQUIRE(qm->GetQubitId(q2a) == 2); + qm->Release(q2a); // Released, but cannot be used in the next segment. + + qm->NextRestrictedReuseSegment(); + + // There's no qubits left here. q0 is allocated, q1 and q2 are from different segments. + REQUIRE_THROWS(qm->Allocate()); + + qm->EndRestrictedReuseArea(); + + // Qubits 1 and 2 are available here again. + Qubit* qqq = new Qubit[2]; + qm->Allocate(qqq, 2); + // OK to destruct qubit manager while qubits are still allocated. + REQUIRE_THROWS(qm->Allocate()); +} + diff --git a/src/Qir/Tests/FullstateSimulator/FullstateSimulatorTests.cpp b/src/Qir/Tests/FullstateSimulator/FullstateSimulatorTests.cpp index f9369c87fa6..7fe5e789d80 100644 --- a/src/Qir/Tests/FullstateSimulator/FullstateSimulatorTests.cpp +++ b/src/Qir/Tests/FullstateSimulator/FullstateSimulatorTests.cpp @@ -77,6 +77,36 @@ TEST_CASE("Fullstate simulator: X and measure", "[fullstate_simulator]") sim->ReleaseResult(r2); } +TEST_CASE("Fullstate simulator: X, M, reuse, M", "[fullstate_simulator]") +{ + std::unique_ptr sim = CreateFullstateSimulator(); + IQuantumGateSet* iqa = dynamic_cast(sim.get()); + + Qubit q = sim->AllocateQubit(); + Result r1 = MZ(iqa, q); + REQUIRE(Result_Zero == sim->GetResultValue(r1)); + REQUIRE(sim->AreEqualResults(r1, sim->UseZero())); + + iqa->X(q); + Result r2 = MZ(iqa, q); + REQUIRE(Result_One == sim->GetResultValue(r2)); + REQUIRE(sim->AreEqualResults(r2, sim->UseOne())); + + sim->ReleaseQubit(q); + sim->ReleaseResult(r1); + sim->ReleaseResult(r2); + + Qubit qq = sim->AllocateQubit(); + Result r3 = MZ(iqa, qq); + // Allocated qubit should always be in |0> state even though we released + // q in |1> state, and qq is likely reusing the same underlying qubit as q. + REQUIRE(Result_Zero == sim->GetResultValue(r3)); + REQUIRE(sim->AreEqualResults(r3, sim->UseZero())); + + sim->ReleaseQubit(qq); + sim->ReleaseResult(r3); +} + TEST_CASE("Fullstate simulator: measure Bell state", "[fullstate_simulator]") { std::unique_ptr sim = CreateFullstateSimulator(); diff --git a/src/Simulation/Common/Simulators.Dev.props b/src/Simulation/Common/Simulators.Dev.props index 62a9ed4319b..3efc2a5f4ef 100644 --- a/src/Simulation/Common/Simulators.Dev.props +++ b/src/Simulation/Common/Simulators.Dev.props @@ -5,8 +5,10 @@ bin\$(BuildConfiguration)\$(TargetFramework)\$(AssemblyName).xml $([MSBuild]::NormalizeDirectory($(MSBuildThisFileDirectory)..\..\..\)) $([MSBuild]::NormalizePath($(EnlistmentRoot)src/Simulation/Native/build/drop)) + $([MSBuild]::NormalizePath($(EnlistmentRoot)src/Simulation/qdk_sim_rs/drop)) + $([MSBuild]::NormalizePath($(NativeBuildPath)/libMicrosoft.Quantum.Simulator.Runtime.dylib)) $([MSBuild]::NormalizePath($(NativeBuildPath)/libMicrosoft.Quantum.Simulator.Runtime.so)) @@ -28,7 +30,25 @@ false - + + + + $([MSBuild]::NormalizePath($(ExperimentalSimBuildPath)/libqdk_sim.dylib)) + $([MSBuild]::NormalizePath($(ExperimentalSimBuildPath)/libqdk_sim.so)) + $([MSBuild]::NormalizePath($(ExperimentalSimBuildPath)/qdk_sim.dll)) + $(ExperimentalSimDllMac) + $(ExperimentalSimDllLinux) + $(ExperimentalSimDllWindows) + + + + + Microsoft.Quantum.Experimental.Simulators.Runtime.dll + PreserveNewest + false + + + diff --git a/src/Simulation/Simulators.Tests/Circuits/TeleportTest.qs b/src/Simulation/Simulators.Tests/Circuits/TeleportTest.qs index 64d4808065e..829ee22d483 100644 --- a/src/Simulation/Simulators.Tests/Circuits/TeleportTest.qs +++ b/src/Simulation/Simulators.Tests/Circuits/TeleportTest.qs @@ -1,42 +1,45 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. namespace Microsoft.Quantum.Simulation.Simulators.Tests.Circuits { - open Microsoft.Quantum.Intrinsic; - - - operation TeleportTest () : Unit { - - - using (qubits = Qubit[3]) { - let q1 = qubits[0]; - let q2 = qubits[1]; - let q3 = qubits[2]; - - // create a Bell pair - H(q1); - CNOT(q1, q2); - - // create quantum state - H(q3); - Rz(1.1, q3); - - // teleport - CNOT(q3, q2); - H(q3); - Controlled X([q2], q1); - Controlled Z([q3], q1); - - // check teleportation success - Rz(-1.1, q1); - H(q1); - - // Make sure all allocated qubits are retrurned to zero before release - ResetAll(qubits); + open Microsoft.Quantum.Diagnostics; + + @Test("QuantumSimulator") + // TODO: Disabled until we have a noise model for Rz. + // @Test("Microsoft.Quantum.Experimental.OpenSystemsSimulator") + operation TestTeleport() : Unit { + use q1 = Qubit(); + use q2 = Qubit(); + use q3 = Qubit(); + + // create a Bell pair + H(q1); + CNOT(q1, q2); + + // create quantum state + H(q3); + Rz(1.1, q3); + + // teleport + CNOT(q3, q2); + H(q3); + Controlled X([q2], q1); + Controlled Z([q3], q1); + + // check teleportation success + Rz(-1.1, q1); + H(q1); + + // Measure and make sure we get Zero. + // We do so without use of diagnostics functions and operations, since + // many don't exist yet at this point in the build. + if M(q1) != Zero { + fail "Expected Zero after teleportation, but got One."; } - } - -} + // Make sure all allocated qubits are retrurned to zero before release + ResetAll([q1, q2, q3]); + } +} diff --git a/src/Simulation/Simulators.Tests/OpenSystemsSimulatorTests/NativeInterfaceTests.cs b/src/Simulation/Simulators.Tests/OpenSystemsSimulatorTests/NativeInterfaceTests.cs new file mode 100644 index 00000000000..cb95eaba9af --- /dev/null +++ b/src/Simulation/Simulators.Tests/OpenSystemsSimulatorTests/NativeInterfaceTests.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Quantum.Simulation.Core; +using Microsoft.Quantum.Simulation.Simulators.Exceptions; +using Xunit; + +namespace Microsoft.Quantum.Experimental +{ + public partial class NativeInterfaceTests + { + [Fact] + public void GetIdealNoiseModelByNameWorks() + { + var ideal = NativeInterface.GetNoiseModelByName("ideal"); + // TODO: Add assertions here to check properties of the above noise model. + } + + [Fact] + public void GetIdealStabilizerNoiseModelByNameWorks() + { + var idealStabilizer = NativeInterface.GetNoiseModelByName("ideal_stabilizer"); + // TODO: Add assertions here to check properties of each noise model. + } + + [Fact] + public void GetNoiseModelByNameThrowsExceptionForInvalidNames() + { + Assert.Throws(() => { + NativeInterface.GetNoiseModelByName("invalid"); + }); + } + + } +} \ No newline at end of file diff --git a/src/Simulation/Simulators.Tests/SerializationTests/NoiseModelSerializationTests.cs b/src/Simulation/Simulators.Tests/SerializationTests/NoiseModelSerializationTests.cs new file mode 100644 index 00000000000..57af996e521 --- /dev/null +++ b/src/Simulation/Simulators.Tests/SerializationTests/NoiseModelSerializationTests.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.IO; +using System.Text; +using System.Text.Json; +using Microsoft.Quantum.Experimental; +using Microsoft.Quantum.Simulation.Core; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Microsoft.Quantum.Simulation.Simulators.Tests +{ + public class NoiseModelSerializationTests + { + private const string idealJson = @"{""initial_state"":{""n_qubits"":1,""data"":{""Mixed"":{""v"":1,""dim"":[2,2],""data"":[[1,0],[0,0],[0,0],[0,0]]}}},""i"":{""n_qubits"":1,""data"":{""Unitary"":{""v"":1,""dim"":[2,2],""data"":[[1,0],[0,0],[0,0],[1,0]]}}},""x"":{""n_qubits"":1,""data"":{""Unitary"":{""v"":1,""dim"":[2,2],""data"":[[0,0],[1,0],[1,0],[0,0]]}}},""y"":{""n_qubits"":1,""data"":{""Unitary"":{""v"":1,""dim"":[2,2],""data"":[[0,0],[0,1],[0,-1],[0,0]]}}},""z"":{""n_qubits"":1,""data"":{""Unitary"":{""v"":1,""dim"":[2,2],""data"":[[1,0],[0,0],[0,0],[-1,0]]}}},""h"":{""n_qubits"":1,""data"":{""Unitary"":{""v"":1,""dim"":[2,2],""data"":[[0.7071067811865476,0],[0.7071067811865476,0],[0.7071067811865476,0],[-0.7071067811865476,0]]}}},""s"":{""n_qubits"":1,""data"":{""Unitary"":{""v"":1,""dim"":[2,2],""data"":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,1.0]]}}},""s_adj"":{""n_qubits"":1,""data"":{""Unitary"":{""v"":1,""dim"":[2,2],""data"":[[1.0,-0.0],[0.0,-0.0],[0.0,-0.0],[0.0,-1.0]]}}},""t"":{""n_qubits"":1,""data"":{""Unitary"":{""v"":1,""dim"":[2,2],""data"":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.7071067811865476,0.7071067811865476]]}}},""t_adj"":{""n_qubits"":1,""data"":{""Unitary"":{""v"":1,""dim"":[2,2],""data"":[[1.0,-0.0],[0.0,-0.0],[0.0,-0.0],[0.7071067811865476,-0.7071067811865476]]}}},""cnot"":{""n_qubits"":2,""data"":{""Unitary"":{""v"":1,""dim"":[4,4],""data"":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[1.0,0.0],[0.0,0.0],[0.0,0.0],[1.0,0.0],[0.0,0.0]]}}},""z_meas"":{""Effects"":[{""n_qubits"":1,""data"":{""KrausDecomposition"":{""v"":1,""dim"":[1,2,2],""data"":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0]]}}},{""n_qubits"":1,""data"":{""KrausDecomposition"":{""v"":1,""dim"":[1,2,2],""data"":[[0.0,0.0],[0.0,0.0],[0.0,0.0],[1.0,0.0]]}}}]}}"; + + [Fact] + public void IdealNoiseModelDeserializes() + { + var idealNoiseModel = JsonSerializer.Deserialize(idealJson); + + // Assert some stuff about the ideal noise model to make sure we got it right. + idealNoiseModel.ZMeas.AssertTypeAnd(instrument => + { + Assert.Equal(2, instrument.Effects.Count); + }); + idealNoiseModel.Z.AssertTypeAnd(process => + { + Assert.Equal(1, process.NQubits); + }); + } + + [Fact] + public void IdealNoiseModelRoundTrips() + { + var idealNoiseModel = ( + NoiseModel.TryGetByName("ideal", out var model) + ? model + : throw new Exception("Failed to get noise model by name.") + ); + idealNoiseModel.AssertSerializationRoundTrips(); + } + + [Fact] + public void IdealStabilizerNoiseModelRoundTrips() + { + var idealStabilizerModel = ( + NoiseModel.TryGetByName("ideal_stabilizer", out var model) + ? model + : throw new Exception("Could not get noise model by name.") + ); + idealStabilizerModel.AssertSerializationRoundTrips(); + } + } +} diff --git a/src/Simulation/Simulators.Tests/SerializationTests/ProcessSerializationTests.cs b/src/Simulation/Simulators.Tests/SerializationTests/ProcessSerializationTests.cs new file mode 100644 index 00000000000..1e518bff509 --- /dev/null +++ b/src/Simulation/Simulators.Tests/SerializationTests/ProcessSerializationTests.cs @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.Json; +using Microsoft.Quantum.Experimental; +using Microsoft.Quantum.Simulation.Core; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Microsoft.Quantum.Simulation.Simulators.Tests +{ + public class ProcessSerializationTests + { + [Fact] + public void MixedPauliSerializesCorrectly() + { + var mixedPauli = new MixedPauliProcess( + 1, + new List<(double, IList)> + { + (0.9, new List { Pauli.PauliI }), + (0.1, new List { Pauli.PauliX }), + } + ); + var actualJson = JsonSerializer.Serialize(mixedPauli); + @"{ + ""n_qubits"": 1, + ""data"": { + ""MixedPauli"": [ + [0.9, [""I""]], + [0.1, [""X""]] + ] + } + }".AssertJsonIsEqualTo(actualJson); + } + + [Fact] + public void MixedPauliRoundTripsCorrectly() + { + var mixedPauli = new MixedPauliProcess( + 1, + new List<(double, IList)> + { + (0.9, new List { Pauli.PauliI }), + (0.1, new List { Pauli.PauliX }), + } + ); + var expectedJson = JsonSerializer.Serialize(mixedPauli); + var actualJson = JsonSerializer.Serialize(JsonSerializer.Deserialize(expectedJson)); + expectedJson.AssertJsonIsEqualTo(actualJson); + } + + [Fact] + public void CnotSerializesCorrectly() + { + var cnot = new ChpOperation.Cnot + { + IdxControl = 0, + IdxTarget = 1 + }; + var json = JsonSerializer.Serialize(cnot); + var expectedJson = @"{ + ""Cnot"": [0, 1] + }"; + + expectedJson.AssertJsonIsEqualTo(json); + } + + [Fact] + public void HadamardSerializesCorrectly() + { + var h = new ChpOperation.Hadamard + { + IdxTarget = 1 + }; + var json = JsonSerializer.Serialize(h); + var expectedJson = @"{ + ""Hadamard"": 1 + }"; + + expectedJson.AssertJsonIsEqualTo(json); + } + + [Fact] + public void PhaseSerializesCorrectly() + { + var s = new ChpOperation.Phase + { + IdxTarget = 1 + }; + var json = JsonSerializer.Serialize(s); + var expectedJson = @"{ + ""Phase"": 1 + }"; + + expectedJson.AssertJsonIsEqualTo(json); + } + + [Fact] + public void AdjointPhaseSerializesCorrectly() + { + var sAdj = new ChpOperation.AdjointPhase + { + IdxTarget = 1 + }; + var json = JsonSerializer.Serialize(sAdj); + var expectedJson = @"{ + ""AdjointPhase"": 1 + }"; + + expectedJson.AssertJsonIsEqualTo(json); + } + } +} diff --git a/src/Simulation/Simulators.Tests/SerializationTests/SerializationExtensions.cs b/src/Simulation/Simulators.Tests/SerializationTests/SerializationExtensions.cs new file mode 100644 index 00000000000..8194aa7b0b8 --- /dev/null +++ b/src/Simulation/Simulators.Tests/SerializationTests/SerializationExtensions.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.IO; +using System.Text; +using System.Text.Json; +using Microsoft.Quantum.Experimental; +using Microsoft.Quantum.Simulation.Core; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Microsoft.Quantum.Simulation.Simulators.Tests +{ + internal static class SerializationExtensions + { + internal static void AssertTypeAnd(this object obj, Action then) + { + if (!(obj is T t)) + { + Assert.IsType(obj); + } + else + { + then(t); + } + } + + internal static string Serialize(this JsonDocument document) + { + var stream = new MemoryStream(); + var writer = new Utf8JsonWriter(stream); + document.WriteTo(writer); + writer.Flush(); + return Encoding.UTF8.GetString(stream.ToArray()); + } + + internal static void AssertSerializationRoundTrips(this T obj) + { + var expected = JsonSerializer.Serialize(obj); + var actual = JsonSerializer.Serialize(JsonSerializer.Deserialize(expected)); + expected.AssertJsonIsEqualTo(actual); + } + + internal static void AssertJsonIsEqualTo(this string expectedJson, string actualJson) + { + // To get a stable text representation, we first parse both strings to JsonDocument + // objects, then re-serialize them. This avoids numerical precision issues in + // JToken.DeepEquals, and allows for highlighting diffs more easily. + var expectedNormalized = JsonDocument.Parse(expectedJson).Serialize(); + var actualNormalized = JsonDocument.Parse(actualJson).Serialize(); + Assert.Equal(expectedNormalized, actualNormalized); + } + } +} diff --git a/src/Simulation/Simulators.Tests/SerializationTests/StateSerializationTests.cs b/src/Simulation/Simulators.Tests/SerializationTests/StateSerializationTests.cs new file mode 100644 index 00000000000..e90fce0465b --- /dev/null +++ b/src/Simulation/Simulators.Tests/SerializationTests/StateSerializationTests.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.Json; +using Microsoft.Quantum.Experimental; +using Microsoft.Quantum.Simulation.Core; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Microsoft.Quantum.Simulation.Simulators.Tests +{ + public class StateSerializationTests + { + [Fact] + public void StabilizerStateSerializesCorrectly() + { + var state = new StabilizerState + { + NQubits = 2, + Table = new StabilizerState.TableArray + { + Data = new List + { + true, false, false, false, true, false + }, + Dimensions = new List { 2, 3 } + } + }; + var json = JsonSerializer.Serialize(state); + var expectedJson = @"{ + ""n_qubits"": 2, + ""data"": { + ""Stabilizer"": { + ""n_qubits"": 2, + ""table"": { + ""v"": 1, + ""dim"": [2, 3], + ""data"": [true,false,false,false,true,false] + } + } + } + }"; + + expectedJson.AssertJsonIsEqualTo(json); + } + + [Fact] + public void StabilizerArrayDeserializesCorrectly() + { + var expectedJson = @" + { + ""v"": 1, + ""dim"": [2, 3], + ""data"": [true,false,false,false,true,false] + } + "; + var reader = new Utf8JsonReader(System.Text.Encoding.UTF8.GetBytes(expectedJson)); + var array = JsonSerializer.Deserialize(ref reader); + var state = new StabilizerState + { + NQubits = 2, + Table = array + }; + } + } +} diff --git a/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Arrays/Tests.qs b/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Arrays/Tests.qs index 3bbc428984a..518bb5128ea 100644 --- a/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Arrays/Tests.qs +++ b/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Arrays/Tests.qs @@ -8,6 +8,7 @@ namespace Microsoft.Quantum.Arrays { /// Checks that empty arrays are indeed empty. @Test("QuantumSimulator") @Test("ToffoliSimulator") + @Test("Microsoft.Quantum.Experimental.OpenSystemsSimulator") function EmptyArraysAreEmpty() : Unit { Fact( Length(EmptyArray()) == 0, diff --git a/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Random/Tests.qs b/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Random/Tests.qs index c646960b606..3dfe4f48f75 100644 --- a/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Random/Tests.qs +++ b/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Random/Tests.qs @@ -14,7 +14,7 @@ namespace Microsoft.Quantum.Tests { internal function SampleMeanAndVariance(samples : Double[]) : (Double, Double) { mutable meanAcc = 0.0; mutable varAcc = 0.0; - for (idx in 0..Length(samples) - 1) { + for idx in 0..Length(samples) - 1 { let sample = samples[idx]; let oldMeanAcc = meanAcc; let delta = (sample - meanAcc); @@ -27,7 +27,7 @@ namespace Microsoft.Quantum.Tests { internal operation EstimateMeanAndVariance(dist : ContinuousDistribution, nSamples : Int) : (Double, Double) { mutable samples = new Double[nSamples]; - for (idx in 0..nSamples - 1) { + for idx in 0..nSamples - 1 { set samples w/= idx <- dist::Sample(); } return SampleMeanAndVariance(samples); @@ -60,8 +60,9 @@ namespace Microsoft.Quantum.Tests { /// Checks that @"microsoft.quantum.random.drawrandomdouble" obeys ranges. @Test("QuantumSimulator") @Test("ToffoliSimulator") + @Test("Microsoft.Quantum.Experimental.OpenSystemsSimulator") operation CheckDrawRandomDoubleObeysRanges() : Unit { - for (j in 0..10000) { + for j in 0..10000 { let random = DrawRandomDouble(0.0, 1.0); if (random < 0.0 or random > 1.0) { fail $"DrawRandomDouble(0.0, 1.0) returned {random}, outside the allowed interval."; @@ -73,6 +74,7 @@ namespace Microsoft.Quantum.Tests { /// Checks that @"microsoft.quantum.random.drawrandomdint" obeys ranges. @Test("QuantumSimulator") @Test("ToffoliSimulator") + @Test("Microsoft.Quantum.Experimental.OpenSystemsSimulator") operation CheckDrawRandomIntObeysRanges() : Unit { mutable randomInt = DrawRandomInt(0, 45); if (randomInt > 45 or randomInt < 0) { diff --git a/src/Simulation/Simulators/Microsoft.Quantum.Simulators.csproj b/src/Simulation/Simulators/Microsoft.Quantum.Simulators.csproj index 1cb8ed1a068..9baf8e3e8b6 100644 --- a/src/Simulation/Simulators/Microsoft.Quantum.Simulators.csproj +++ b/src/Simulation/Simulators/Microsoft.Quantum.Simulators.csproj @@ -24,6 +24,12 @@ + + + + + + runtimes\win-x64\native\%(RecursiveDir)%(FileName)%(Extension) diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/ArrayConverter.cs b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/ArrayConverter.cs new file mode 100644 index 00000000000..67a23acd2af --- /dev/null +++ b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/ArrayConverter.cs @@ -0,0 +1,251 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text.Json; +using System.Text.Json.Serialization; +using NumSharp; + +namespace Microsoft.Quantum.Experimental +{ + /// + /// Converts instances of dtype double + /// and with a trailing index of length 2 to and from a JSON + /// serialization. + /// + /// + /// + /// The JSON representation consumed and produced by this converter is + /// compatible with the representation used by Rust's ndarray + /// and serde crates. + /// + /// + /// In particular, each JSON object produced by this converter has + /// three properties: + /// + /// + /// "v": Indicates the ndarray format version being + /// being used. Always 1 for this implementation. + /// "dim": Lists the dimensions of the array being + /// serialized. Will always contain one less dimension than the + /// original array, as one dimension represents the real and imaginary + /// components of each element. + /// "data": Lists the elements of the array, with the + /// right-most dimension varying the fastest (defined by ndarray + /// as "logical order", and by NumPy as "C-ordered"). + /// + /// + public class ComplexArrayConverter : JsonConverter + { + public override NDArray Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + // By the end, we need to have read "v", "dim", and "data". + int? version = null; + List? dims = null; + List? data = null; + + // We require that the reader be in the right state to read an + // object. + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException(); + } + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + // We're at the end of the array, and can break out of the + // read loop. + break; + } + + // If it's not the end of the object, the current token needs + // to be a property name. + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException(); + } + + var propertyName = reader.GetString(); + + switch (propertyName) + { + case "v": + reader.Read(); + version = reader.GetInt32(); + break; + + case "dim": + dims = new List(); + reader.Require(JsonTokenType.StartArray); + while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) + { + dims.Add(reader.GetInt32()); + } + break; + + case "data": + data = new List(); + reader.Require(JsonTokenType.StartArray); + + while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) + { + // We expect an inner array at this point, so + // go on and read the begin array token. + reader.Require(JsonTokenType.StartArray, read: false); + + reader.Read(); + var real = reader.GetDouble(); + + reader.Read(); + var imag = reader.GetDouble(); + + reader.Require(JsonTokenType.EndArray); + + data.Add(new Complex(real, imag)); + } + break; + + default: + throw new JsonException(); + } + } + + // At this point, none of version, dims, or data should be null. + if (version == null) throw new JsonException(nameof(version)); + if (dims == null) throw new JsonException(nameof(dims)); + if (data == null) throw new JsonException(nameof(data)); + + // We now know we have everything we need to make the new array. + // In doing so, we allocate an ndarray with of shape (nElements, 2) + // where the last index represents real-vs-imag. We'll reshape + // it to the correct shape at the end. + var array = np.zeros((data.Count, 2)); + foreach (var idx in Enumerable.Range(0, data.Count)) + { + var element = data[idx]; + array[idx, 0] = element.Real; + array[idx, 1] = element.Imaginary; + } + return np.reshape(array, dims.Concat(new [] { 2 }).ToArray()); + } + + public override void Write(Utf8JsonWriter writer, NDArray value, JsonSerializerOptions options) + { + // Before proceeding, check that `value` is complex-like. That is, + // that `value` is of dtype double, and has a trailing dimension + // of length 2. + if (!value.IsComplexLike()) + { + throw new ArgumentException($"Cannot serialize ndarray, as it is not complex-like: {value}"); + } + + writer.WriteStartObject(); + writer.WriteNumber("v", 1); + + writer.WritePropertyName("dim"); + writer.WriteStartArray(); + foreach (var dim in value.shape[0..^1]) + { + writer.WriteNumberValue(dim); + } + writer.WriteEndArray(); + + writer.WritePropertyName("data"); + writer.WriteStartArray(); + // By default, NumSharp reshapes in C-order, matching + // ndarray's logical ordering. Thus, we reshape down to + // a two-axis array, and loop over the left most axis + // (corresponding to elements of the serialized array), + // leaving the second axis (corresponding to + // real-vs-imag). + var nElements = value.shape[0..^1].Aggregate((acc, dim) => acc * dim); + var flattened = value.reshape((nElements, 2)); + foreach (var idx in Enumerable.Range(0, flattened.shape[0])) + { + var element = flattened[idx]; + // Each element is a JSON array `[real, imag]`. + writer.WriteStartArray(); + writer.WriteNumberValue((double) element[0]); + writer.WriteNumberValue((double) element[1]); + writer.WriteEndArray(); + } + writer.WriteEndArray(); + writer.WriteEndObject(); + } + + public static (int nQubits, string kind, NDArray data) ReadQubitSizedArray(ref Utf8JsonReader reader, JsonSerializerOptions options) + { + var arrayConverter = new ComplexArrayConverter(); + int? nQubits = null; + NDArray? data = null; + string? kind = null; + + // We require that the reader be in the right state to read an + // object. + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException(); + } + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + // We're at the end of the array, and can break out of the + // read loop. + break; + } + + // If it's not the end of the object, the current token needs + // to be a property name. + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException(); + } + + var propertyName = reader.GetString(); + + switch (propertyName) + { + case "n_qubits": + reader.Read(); + nQubits = reader.GetInt32(); + break; + + case "data": + // Here, we expect an object with one property indicating + // the kind of state. The value of that property can + // be read using the complexarrayconverter from above. + reader.Require(JsonTokenType.StartObject); + reader.Require(JsonTokenType.PropertyName); + kind = reader.GetString(); + + // Advance the reader onto the array itself and use + // the converter. + reader.Read(); + data = arrayConverter.Read(ref reader, typeof(NDArray), options); + + // Finally, require an end to the object. + reader.Require(JsonTokenType.EndObject); + break; + + default: + throw new JsonException($"Unexpected property name {propertyName}."); + } + } + + if (nQubits == null) throw new JsonException(nameof(nQubits)); + if (data == null) throw new JsonException(nameof(data)); + if (kind == null) throw new JsonException(nameof(kind)); + + return (nQubits.Value, kind!, data!); + } + } +} diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/ChpDecomposition.cs b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/ChpDecomposition.cs new file mode 100644 index 00000000000..85900a3e4a7 --- /dev/null +++ b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/ChpDecomposition.cs @@ -0,0 +1,132 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.Quantum.Experimental +{ + + public class ChpOperationConverter : JsonConverter + { + public override ChpOperation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + reader.Require(JsonTokenType.StartObject, read: false); + reader.Require(JsonTokenType.PropertyName); + ChpOperation? operation = null; + ulong idx; + switch (reader.GetString()) + { + case "Cnot": + var idxs = JsonSerializer.Deserialize>(ref reader); + operation = new ChpOperation.Cnot + { + IdxControl = idxs[0], + IdxTarget = idxs[1] + }; + break; + + case "Hadamard": + reader.Read(); + idx = reader.GetUInt64(); + operation = new ChpOperation.Hadamard + { + IdxTarget = idx + }; + break; + + case "Phase": + reader.Read(); + idx = reader.GetUInt64(); + operation = new ChpOperation.Phase + { + IdxTarget = idx + }; + break; + + case "AdjointPhase": + reader.Read(); + idx = reader.GetUInt64(); + operation = new ChpOperation.AdjointPhase + { + IdxTarget = idx + }; + break; + } + if (operation == null) + { + throw new JsonException("Did not read an operation."); + } + reader.Require(JsonTokenType.EndObject); + return operation; + } + + public override void Write(Utf8JsonWriter writer, ChpOperation value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + writer.WritePropertyName( + value switch + { + ChpOperation.Cnot _ => "Cnot", + ChpOperation.Hadamard _ => "Hadamard", + ChpOperation.Phase _ => "Phase", + ChpOperation.AdjointPhase _ => "AdjointPhase", + _ => throw new JsonException() + } + ); + if (value is ChpOperation.Cnot cnot) + { + writer.WriteStartArray(); + writer.WriteNumberValue(cnot.IdxControl); + writer.WriteNumberValue(cnot.IdxTarget); + writer.WriteEndArray(); + } + else + { + writer.WriteNumberValue( + value switch + { + ChpOperation.AdjointPhase { IdxTarget: var target } => target, + ChpOperation.Phase { IdxTarget: var target } => target, + ChpOperation.Hadamard { IdxTarget: var target } => target, + _ => throw new JsonException() + } + ); + } + writer.WriteEndObject(); + } + } + + [JsonConverter(typeof(ChpOperationConverter))] + public abstract class ChpOperation + { + private ChpOperation() + { } + + public class Cnot : ChpOperation + { + public ulong IdxControl { get; set; } + public ulong IdxTarget { get; set; } + } + + public class Hadamard : ChpOperation + { + public ulong IdxTarget { get; set; } + } + + public class Phase : ChpOperation + { + public ulong IdxTarget { get; set; } + } + + public class AdjointPhase : ChpOperation + { + public ulong IdxTarget { get; set; } + } + } + +} \ No newline at end of file diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/DelegatedConverter.cs b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/DelegatedConverter.cs new file mode 100644 index 00000000000..7ec7db03fa9 --- /dev/null +++ b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/DelegatedConverter.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Numerics; +using System.Text.Json; +using System.Text.Json.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using NumSharp; + +namespace Microsoft.Quantum.Experimental +{ + public class DelegatedConverter : Newtonsoft.Json.JsonConverter + { + public override T ReadJson(JsonReader reader, Type objectType, [AllowNull] T existingValue, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer) + { + var serialized = JToken.ReadFrom(reader).ToString(); + return System.Text.Json.JsonSerializer.Deserialize(serialized); + } + + public override void WriteJson(JsonWriter writer, [AllowNull] T value, Newtonsoft.Json.JsonSerializer serializer) + { + var serialized = System.Text.Json.JsonSerializer.Serialize(value); + var deserialized = Newtonsoft.Json.Linq.JToken.Parse(serialized); + deserialized.WriteTo(writer); + } + } +} diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/Extensions.cs b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/Extensions.cs new file mode 100644 index 00000000000..670234325ab --- /dev/null +++ b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/Extensions.cs @@ -0,0 +1,164 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Text.Json; +using System.Collections.Generic; +using System.Linq; +using NumSharp; +using System; + +namespace Microsoft.Quantum.Experimental +{ + internal delegate Func ReaderContinuation(ref Utf8JsonReader reader, string variant); + internal static class Extensions + { + internal static bool HasProperty(this JsonElement element, string propertyName) => + element.TryGetProperty(propertyName, out var _); + + internal static string AsLaTeXMatrixOfComplex(this NDArray array) => + // NB: Assumes 𝑛 × 𝑛 × 2 array, where the trailing index is + // [real, imag]. + // TODO: Consolidate with logic at: + // https://github.com/microsoft/QuantumLibraries/blob/505fc27383c9914c3e1f60fb63d0acfe60b11956/Visualization/src/DisplayableUnitaryEncoders.cs#L43 + string.Join( + "\\\\\n", + Enumerable + .Range(0, array.Shape[0]) + .Select( + idxRow => string.Join(" & ", + Enumerable + .Range(0, array.Shape[1]) + .Select( + idxCol => $"{array[idxRow, idxCol, 0]} + {array[idxRow, idxCol, 1]} i" + ) + ) + ) + ); + + internal static IEnumerable IterateOverLeftmostIndex(this NDArray array) + { + foreach (var idx in Enumerable.Range(0, array.shape[0])) + { + yield return array[idx, Slice.Ellipsis]; + } + } + + internal static string AsTextMatrixOfComplex(this NDArray array, string rowSep = "\n") => + // NB: Assumes 𝑛 × 𝑛 × 2 array, where the trailing index is + // [real, imag]. + // TODO: Consolidate with logic at: + // https://github.com/microsoft/QuantumLibraries/blob/505fc27383c9914c3e1f60fb63d0acfe60b11956/Visualization/src/DisplayableUnitaryEncoders.cs#L43 + "[" + rowSep + string.Join( + rowSep, + Enumerable + .Range(0, array.Shape[0]) + .Select( + idxRow => "[" + string.Join(", ", + Enumerable + .Range(0, array.Shape[1]) + .Select( + idxCol => $"{array[idxRow, idxCol, 0]} + {array[idxRow, idxCol, 1]} i" + ) + ) + "]" + ) + ) + rowSep + "]"; + + public static void Require(this ref Utf8JsonReader reader, JsonTokenType type, bool read = true) + { + if (read) reader.Read(); + if (reader.TokenType != type) + { + // Try to read what it actually was. + string? value = reader.TokenType switch + { + JsonTokenType.String => reader.GetString(), + JsonTokenType.Number => reader.GetDecimal().ToString(), + JsonTokenType.True => "true", + JsonTokenType.False => "false", + JsonTokenType.Null => "null", + JsonTokenType.PropertyName => reader.GetString(), + _ => null + }; + throw new JsonException($"Expected a JSON token of type {type}, got {reader.TokenType} instead.{(value == null ? "" : $"\nValue: {value}")}"); + } + } + public static bool IsComplexLike(this NDArray array) => + array.dtype == typeof(double) && + array.shape[^1] == 2; + + public static Func Bind(this TInput input, Func action) => + (completion) => action(completion, input); + + internal static TResult ReadQubitSizedData(this ref Utf8JsonReader reader, ReaderContinuation readData) + { + + reader.Require(JsonTokenType.StartObject, read: false); + + int? nQubits = null; + Func? completion = null; + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + // We're at the end of the object, and can break out of the + // read loop. + break; + } + + // If it's not the end of the object, the current token needs + // to be a property name. + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException(); + } + + var propertyName = reader.GetString(); + + switch (propertyName) + { + case "n_qubits": + reader.Read(); + nQubits = reader.GetInt32(); + break; + + case "data": + // In most cases, we expect an object with one property + // indicating + // the kind of data for the object. + // For variants of type unit, it's just the string. + reader.Read(); + if (reader.TokenType == JsonTokenType.StartObject) + { + reader.Require(JsonTokenType.PropertyName); + var kind = reader.GetString(); + + reader.Read(); + completion = readData(ref reader, kind); + + // Finally, require an end to the object. + reader.Require(JsonTokenType.EndObject); + } + else if (reader.TokenType == JsonTokenType.String) + { + var kind = reader.GetString(); + completion = readData(ref reader, kind); + } + else + { + throw new JsonException($"Expected either the start of an object or a string."); + } + break; + + default: + throw new JsonException($"Unexpected property name {propertyName}."); + } + } + + if (nQubits == null) throw new JsonException(nameof(nQubits)); + if (completion == null) throw new JsonException(); + + return completion(nQubits.Value); + } + } +} diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/Instrument.cs b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/Instrument.cs new file mode 100644 index 00000000000..f99737d0a54 --- /dev/null +++ b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/Instrument.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text.Json; +using System.Text.Json.Serialization; +using NumSharp; + +namespace Microsoft.Quantum.Experimental +{ + public class InstrumentConverter : JsonConverter + { + public override Instrument Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + // We use the technique at + // https://stackoverflow.com/questions/58074304/is-polymorphic-deserialization-possible-in-system-text-json/59744873#59744873 + // to implement polymorphic deserialization, based on the property name. + reader.Require(JsonTokenType.StartObject, read: false); + reader.Require(JsonTokenType.PropertyName); + + var variant = reader.GetString(); + + Instrument result = variant switch + { + "Effects" => new EffectsInstrument + { + Effects = JsonSerializer.Deserialize>(ref reader) + }, + "ZMeasurement" => JsonSerializer.Deserialize(ref reader), + _ => throw new JsonException($"Enum variant {variant} not yet supported.") + }; + + reader.Require(JsonTokenType.EndObject); + + return result; + } + + public override void Write(Utf8JsonWriter writer, Instrument value, JsonSerializerOptions options) + { + + switch (value) + { + case EffectsInstrument effectsInstrument: + writer.WriteStartObject(); + writer.WritePropertyName("Effects"); + JsonSerializer.Serialize(writer, effectsInstrument.Effects); + writer.WriteEndObject(); + break; + + case ZMeasurementInstrument zInstrument: + writer.WriteStartObject(); + writer.WritePropertyName("ZMeasurement"); + JsonSerializer.Serialize(writer, zInstrument); + writer.WriteEndObject(); + break; + + default: + throw new JsonException($"Enum variant {value.GetType()} not yet supported."); + } + + } + } + + [JsonConverter(typeof(InstrumentConverter))] + public abstract class Instrument + { + + } + + public class EffectsInstrument : Instrument + { + [JsonPropertyName("effects")] + public IList Effects { get; set; } = new List(); + + public override string ToString() => + $"Instrument {{ Effects = {String.Join(", ", Effects.Select(effect => effect.ToString()))} }}"; + } + + public class ZMeasurementInstrument : Instrument + { + [JsonPropertyName("pr_readout_error")] + public double PrReadoutError { get; set; } = 0.0; + + public override string ToString() => + $"Instrument {{ Z measurement with readout error = {PrReadoutError} }}"; + } +} diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/NoiseModel.cs b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/NoiseModel.cs new file mode 100644 index 00000000000..79c122a102d --- /dev/null +++ b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/NoiseModel.cs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text.Json; +using System.Text.Json.Serialization; +using NumSharp; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.Quantum.Experimental +{ + // NB: To make this compatible with Newtonsoft.Json as well as + // System.Text.Json, we use a Newtonsoft converter that delegates to + // S.T.Json. + [Newtonsoft.Json.JsonConverter(typeof(DelegatedConverter))] + public class NoiseModel + { + [JsonPropertyName("initial_state")] + public State? InitialState { get; set; } + + [JsonPropertyName("cnot")] + public Process? Cnot { get; set; } + + [JsonPropertyName("i")] + public Process? I { get; set; } + + [JsonPropertyName("s")] + public Process? S { get; set; } + + [JsonPropertyName("s_adj")] + public Process? SAdj { get; set; } + + [JsonPropertyName("t")] + public Process? T { get; set; } + + [JsonPropertyName("t_adj")] + public Process? TAdj { get; set; } + + [JsonPropertyName("h")] + public Process? H { get; set; } + + [JsonPropertyName("x")] + public Process? X { get; set; } + + [JsonPropertyName("y")] + public Process? Y { get; set; } + + [JsonPropertyName("z")] + public Process? Z { get; set; } + + [JsonPropertyName("z_meas")] + public Instrument? ZMeas { get; set; } + + public static bool TryGetByName(string name, [NotNullWhen(true)] out NoiseModel? model) + { + try + { + model = NativeInterface.GetNoiseModelByName(name); + return true; + } + catch (SimulationException) + { + model = null; + return false; + } + } + } +} diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/Process.cs b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/Process.cs new file mode 100644 index 00000000000..cde27687137 --- /dev/null +++ b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/Process.cs @@ -0,0 +1,263 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.Quantum.Simulation.Core; +using NumSharp; +using static System.Math; + +namespace Microsoft.Quantum.Experimental +{ + public class ProcessConverter : JsonConverter + { + private static IList<(double, IList)> ReadMixedPauliData(ref Utf8JsonReader reader) + { + var results = new List<(double, IList)>(); + reader.Require(JsonTokenType.StartArray, read: false); + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndArray) + { + break; + } + + reader.Require(JsonTokenType.StartArray, read: false); + reader.Read(); + var p = reader.GetDouble(); + var ops = new List(); + reader.Require(JsonTokenType.StartArray); + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndArray) + { + break; + } + + ops.Add(reader.GetString() switch + { + "I" => Pauli.PauliI, + "X" => Pauli.PauliX, + "Y" => Pauli.PauliY, + "Z" => Pauli.PauliZ, + var unknown => throw new JsonException($"Expected I, X, Y, or Z for a Pauli value, but got {unknown}.") + }); + } + reader.Require(JsonTokenType.EndArray); + + results.Add((p, ops)); + } + return results; + } + + public override Process Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + reader.Require(JsonTokenType.StartObject, read: false); + + var arrayConverter = new ComplexArrayConverter(); + return reader.ReadQubitSizedData((ref Utf8JsonReader reader, string kind) => + kind switch + { + "Unitary" => arrayConverter.Read(ref reader, typeof(NDArray), options).Bind( + (int nQubits, NDArray data) => new UnitaryProcess(nQubits, data) + ), + "KrausDecomposition" => arrayConverter.Read(ref reader, typeof(NDArray), options).Bind( + (int nQubits, NDArray data) => new KrausDecompositionProcess(nQubits, data) + ), + "MixedPauli" => ReadMixedPauliData(ref reader).Bind( + (int nQubits, IList<(double, IList)> data) => new MixedPauliProcess(nQubits, data) + ), + "Sequence" => JsonSerializer.Deserialize>(ref reader).Bind( + (int nQubits, IList processes) => new SequenceProcess(nQubits, processes) + ), + "ChpDecomposition" => JsonSerializer.Deserialize>(ref reader).Bind( + (int nQubits, IList operations) => new ChpDecompositionProcess(nQubits, operations) + ), + "Unsupported" => (null as object).Bind( + (int nQubits, object? _) => new UnsupportedProcess(nQubits) + ), + _ => throw new JsonException($"Process kind {kind} not supported for deserialization.") + } + ); + } + + public override void Write(Utf8JsonWriter writer, Process value, JsonSerializerOptions options) + { + var arrayConverter = new ComplexArrayConverter(); + + writer.WriteStartObject(); + writer.WriteNumber("n_qubits", value.NQubits); + + writer.WritePropertyName("data"); + if (value is UnsupportedProcess) + { + writer.WriteStringValue("Unsupported"); + } + else + { + writer.WriteStartObject(); + writer.WritePropertyName( + value switch + { + UnitaryProcess _ => "Unitary", + KrausDecompositionProcess _ => "KrausDecomposition", + MixedPauliProcess _ => "MixedPauli", + SequenceProcess _ => "Sequence", + ChpDecompositionProcess _ => "ChpDecomposition", + _ => throw new JsonException() + } + ); + + if (value is ArrayProcess { Data: var data }) + { + arrayConverter.Write(writer, data, options); + } + else if (value is MixedPauliProcess mixedPauliProcess) + { + writer.WriteStartArray(); + foreach (var op in mixedPauliProcess.Operators) + { + writer.WriteStartArray(); + writer.WriteNumberValue(op.Item1); + writer.WriteStartArray(); + foreach (var p in op.Item2) + { + writer.WriteStringValue(p switch + { + Pauli.PauliI => "I", + Pauli.PauliX => "X", + Pauli.PauliY => "Y", + Pauli.PauliZ => "Z", + var unknown => throw new JsonException($"Unexpected Pauli value {unknown}.") + }); + } + writer.WriteEndArray(); + writer.WriteEndArray(); + } + writer.WriteEndArray(); + } + else if (value is ChpDecompositionProcess chpDecomposition) + { + JsonSerializer.Serialize(writer, chpDecomposition.Operations); + } + else if (value is SequenceProcess sequence) + { + JsonSerializer.Serialize(writer, sequence.Processes); + } + writer.WriteEndObject(); + } + writer.WriteEndObject(); + } + } + + [JsonConverter(typeof(ProcessConverter))] + public abstract class Process + { + [JsonPropertyName("n_qubits")] + public int NQubits { get; } + + internal Process(int nQubits) + { + NQubits = nQubits; + } + } + + public abstract class ArrayProcess : Process + { + [JsonPropertyName("data")] + public NDArray Data { get; } + + internal ArrayProcess(int nQubits, NDArray data) : base(nQubits) + { + this.Data = data; + } + } + + public class MixedPauliProcess : Process + { + public IList<(double, IList)> Operators; + + internal MixedPauliProcess(int nQubits, IList<(double, IList)> operators) : base(nQubits) + { + this.Operators = operators; + } + } + + public class ChpDecompositionProcess : Process + { + public IList Operations; + + internal ChpDecompositionProcess(int nQubits, IList operations) : base(nQubits) + { + this.Operations = operations; + } + } + + public class SequenceProcess : Process + { + public IList Processes; + + internal SequenceProcess(int nQubits, IList processes) : base(nQubits) + { + this.Processes = processes; + } + } + + public class UnsupportedProcess : Process + { + internal UnsupportedProcess(int nQubits) : base(nQubits) + { } + } + + public class UnitaryProcess : ArrayProcess + { + public UnitaryProcess(int nQubits, NDArray data) : base(nQubits, data) + { + // Unitary matrices should be of dimension (2^n, 2^n, 2), with the last + // index indicating real-vs-imag. + var dim = (int) Pow(2, nQubits); + if (data.shape.Length != 3 || data.shape[0] != dim || data.shape[1] != dim || data.shape[2] != 2) + { + throw new ArgumentException("Expected (2^nQubits, 2) array.", nameof(data)); + } + } + + public override string ToString() => + $@"Unitary process on {NQubits} qubits: {Data.AsTextMatrixOfComplex(rowSep: " ")}"; + } + public class KrausDecompositionProcess : ArrayProcess + { + public KrausDecompositionProcess(int nQubits, NDArray data) : base(nQubits, data) + { + // Kraus decompositions should have between 1 and 4^n operators, + // each of which should be 2^n by 2^n, for a final dims of + // [k, 2^n, 2^n, 2] where 1 ≤ k ≤ 4^n. + var dim = (int) Pow(2, nQubits); + var superDim = dim * dim; + + if (data.shape.Length != 4 || data.shape[0] > superDim || data.shape[0] < 1 + || data.shape[1] != dim + || data.shape[2] != dim + || data.shape[3] != 2) + { + throw new ArgumentException("Expected (k, 2^nQubits, 2^nQubits, 2) array.", nameof(data)); + } + } + + public override string ToString() + { + var ops = String.Join(",", + Data.IterateOverLeftmostIndex() + .Select(op => op.AsTextMatrixOfComplex(rowSep: " ")) + ); + return $@"Kraus decomposition of process on {NQubits} qubits: {ops}"; + } + } + +} diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/README.md b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/README.md new file mode 100644 index 00000000000..8fa99c08df3 --- /dev/null +++ b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/README.md @@ -0,0 +1,9 @@ +# Open systems simulation data model + +The C# classes in this folder define a data model and JSON serialization logic that can be used to exchange data with the native runtime for the open systems simulator. + +## Newtonsoft.Json versus System.Text.Json + +The JSON converters in this folder target System.Text.Json as this is the recommended path for .NET Core 3.1 and forward, both in terms of where new functionality will be developed, and in terms of performance improvements due to `Span` and other recent additions to the .NET platform. + +Since IQ# and many other Quantum Development Kit components use Newtonsoft.Json, however, the `DelegatedConverter` class allows for exposing a System.Text.Json converter as a Newtonsoft.Json converter; this has a significant performance implication, but provides a path forward for using System.Text.Json in the future. diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/State.cs b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/State.cs new file mode 100644 index 00000000000..439e3570b81 --- /dev/null +++ b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/State.cs @@ -0,0 +1,116 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text.Json; +using System.Text.Json.Serialization; +using NumSharp; +using static System.Math; + +namespace Microsoft.Quantum.Experimental +{ + [JsonConverter(typeof(StateConverter))] + public abstract class State + { + [JsonPropertyName("n_qubits")] + public int NQubits { get; set; } = 1; + + internal State() + { } + + internal State(int nQubits) + { + NQubits = nQubits; + } + } + + public class StabilizerState : State + { + // Design note: + // We could use the same array converter as used of complex-like arrays + // of floats, but it's a bit easier in this case to directly + // deserialize into a type that represents the underlying data that + // we need. + public class TableArray + { + // When serializing multidimensional arrays with serde, the + // `ndarray` crate for Rust uses the "v" property to denote + // serialization schema versions. This property name is hardcoded + // at https://github.com/rust-ndarray/ndarray/blob/master/src/array_serde.rs#L96, + // such that we follow that property name here to make it easier + // to interoperate with `ndarray`. + [JsonPropertyName("v")] + public int SchemaVersion { get; set; } = 1; + + [JsonPropertyName("dim")] + public List? Dimensions { get; set; } + + [JsonPropertyName("data")] + public List? Data { get; set; } + + [JsonIgnore] + public NDArray? AsArray => + Dimensions == null || Data == null + ? null + : np.ndarray( + new Shape(Dimensions.ToArray()), + typeof(bool), + Data.ToArray() + ); + } + + [JsonPropertyName("table")] + public TableArray? Table { get; set; } + + [JsonIgnore] + public NDArray? Data => Table?.AsArray; + } + + public abstract class ArrayState : State + { + public NDArray Data { get; } + + internal ArrayState(int nQubits, NDArray data) : base(nQubits) + { + this.Data = data; + } + } + + public class PureState : ArrayState + { + public PureState(int nQubits, NDArray data) : base(nQubits, data) + { + // Pure states should be of dimension (2^n, 2), with the last + // index indicating real-vs-imag. + if (data.shape.Length != 2 || data.shape[0] != (int) (Pow(2, nQubits)) || data.shape[1] != 2) + { + throw new ArgumentException("Expected (2^nQubits, 2) array.", nameof(data)); + } + } + + // TODO: Override ToString here! + } + + public class MixedState : ArrayState + { + public MixedState(int nQubits, NDArray data) : base(nQubits, data) + { + // Pure states should be of dimension (2^n, 2^n, 2), with the last + // index indicating real-vs-imag. + var dim = (int) Pow(2, nQubits); + if (data.shape.Length != 3 || data.shape[0] != dim || data.shape[1] != dim || data.shape[2] != 2) + { + throw new ArgumentException("Expected (2^nQubits, 2) array.", nameof(data)); + } + } + + public override string ToString() => + $@"Mixed state on {NQubits} qubits: {Data.AsTextMatrixOfComplex(rowSep: " ")}"; + } + +} diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/StateConverter.cs b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/StateConverter.cs new file mode 100644 index 00000000000..22c1fa3867c --- /dev/null +++ b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/StateConverter.cs @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text.Json; +using System.Text.Json.Serialization; +using NumSharp; +using static System.Math; + +namespace Microsoft.Quantum.Experimental +{ + public class StateConverter : JsonConverter + { + + /* + We expect JSON of the form: + + { + "n_qubits": 1, + "data": { + "Mixed": { + "v": 1, + "dim": [2, 2], + "data": [ + [1, 0], + [0, 0], + [0, 0], + [0, 0] + ] + } + } + } + + Here, the presence of either $.data.Mixed or $.data.Pure tells us how + to interpret data. + */ + + public override State Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + reader.Require(JsonTokenType.StartObject, read: false); + + var arrayConverter = new ComplexArrayConverter(); + return reader.ReadQubitSizedData((ref Utf8JsonReader reader, string kind) => + kind switch + { + "Pure" => arrayConverter.Read(ref reader, typeof(NDArray), options).Bind( + (int nQubits, NDArray data) => new PureState(nQubits, data) + ), + "Mixed" => arrayConverter.Read(ref reader, typeof(NDArray), options).Bind( + (int nQubits, NDArray data) => new MixedState(nQubits, data) + ), + "Stabilizer" => JsonSerializer.Deserialize(ref reader).Bind( + (int nQubits, StabilizerState state) => + { + System.Diagnostics.Debug.Assert((state?.Data as object) != null); + System.Diagnostics.Debug.Assert(nQubits == state.NQubits); + return state; + } + ), + _ => throw new JsonException($"Unknown state kind {kind}.") + } + ); + } + + public override void Write(Utf8JsonWriter writer, State value, JsonSerializerOptions options) + { + var arrayConverter = new ComplexArrayConverter(); + + writer.WriteStartObject(); + writer.WriteNumber("n_qubits", value.NQubits); + + writer.WritePropertyName("data"); + writer.WriteStartObject(); + writer.WritePropertyName( + value switch + { + PureState _ => "Pure", + MixedState _ => "Mixed", + StabilizerState _ => "Stabilizer", + _ => throw new JsonException($"Unknown state type {value.GetType()}.") + } + ); + + if (value is ArrayState { Data: var data }) + { + arrayConverter.Write(writer, data, options); + } + else if (value is StabilizerState stabilizerState) + { + var array = new StabilizerState.TableArray + { + Data = stabilizerState.Data.flat.ToArray().ToList(), + Dimensions = stabilizerState.Data.Shape.Dimensions.ToList() + }; + writer.WriteStartObject(); + writer.WritePropertyName("n_qubits"); + writer.WriteNumberValue(stabilizerState.NQubits); + writer.WritePropertyName("table"); + JsonSerializer.Serialize(writer, array); + writer.WriteEndObject(); + } + writer.WriteEndObject(); + writer.WriteEndObject(); + } + } + +} diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/Decompositions/CZ.qs b/src/Simulation/Simulators/OpenSystemsSimulator/Decompositions/CZ.qs new file mode 100644 index 00000000000..7fc5eea3f53 --- /dev/null +++ b/src/Simulation/Simulators/OpenSystemsSimulator/Decompositions/CZ.qs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Experimental { + + internal operation ApplyCZUsingCNOT(control : Qubit, target : Qubit) : Unit { + within { + ApplyUncontrolledH(target); + } apply { + ApplySinglyControlledX(control, target); + } + } +} diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/Decompositions/Interface.qs b/src/Simulation/Simulators/OpenSystemsSimulator/Decompositions/Interface.qs new file mode 100644 index 00000000000..31059665b33 --- /dev/null +++ b/src/Simulation/Simulators/OpenSystemsSimulator/Decompositions/Interface.qs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Experimental { + open Microsoft.Quantum.Intrinsic; + + operation ApplyUncontrolledH(target : Qubit) : Unit is Adj { + body intrinsic; + adjoint self; + } + + operation ApplySinglyControlledX(control : Qubit, target : Qubit) : Unit is Adj { + body intrinsic; + adjoint self; + } + +} + diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/Decompositions/README.md b/src/Simulation/Simulators/OpenSystemsSimulator/Decompositions/README.md new file mode 100644 index 00000000000..f687ce8d0d6 --- /dev/null +++ b/src/Simulation/Simulators/OpenSystemsSimulator/Decompositions/README.md @@ -0,0 +1,4 @@ +# TODO + +The decompositions in this folder should eventually be converted into a new "type 4" targeting package. +We use the legacy approach to making intrinsics available to simulators, however, on a temporary basis for compatibility with Python and IQ#. diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/NativeInterface.cs b/src/Simulation/Simulators/OpenSystemsSimulator/NativeInterface.cs new file mode 100644 index 00000000000..84db6154488 --- /dev/null +++ b/src/Simulation/Simulators/OpenSystemsSimulator/NativeInterface.cs @@ -0,0 +1,265 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.IO; +using System.Reflection; +using System.Text.Json; +using System.Runtime.InteropServices; +using Microsoft.Quantum.Simulation.Core; +using Newtonsoft.Json.Linq; + +#nullable enable + +namespace Microsoft.Quantum.Experimental +{ + /// + /// Represents an exception that is raised by native simulator code. + /// + [Serializable()] + public class SimulationException : Exception + { + private readonly string? source; + + /// + /// The name of the native shared library which raised this + /// exception if known, null otherwise. + /// + public string? SourceLibrary => source; + + internal SimulationException(string message, string? source) : base(message) + { + this.source = source; + } + } + + /// + /// Abstracts away calls to and from the experimental simulators DLL. + /// + internal static class NativeInterface + { + public static event Action? OnVerbose = null; + private static void LogCall(string callName) => + OnVerbose?.Invoke($"[VERBOSE] NativeInterface: calling {callName}."); + + private static void CheckCall(Int64 errorCode) + { + if (errorCode != 0) + { + var error = _LastError(); + throw new SimulationException($"Exception in native open systems simulator runtime: {error}", DLL_NAME); + } + } + + + public const string DLL_NAME = "Microsoft.Quantum.Experimental.Simulators.Runtime.dll"; + + [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="lasterr")] + private static extern string _LastError(); + + + [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="get_simulator_info")] + private static extern string _GetSimulatorInfo(); + + internal static readonly JToken SimulatorInfo; + + static NativeInterface() + { + SimulatorInfo = JToken.Parse(_GetSimulatorInfo()); + } + + public static string Name + { + get + { + return SimulatorInfo.Value("name"); + } + } + + [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="init")] + private static extern Int64 _Init(uint initialCapacity, string representation, out uint simulatorId); + + public static ulong Init(uint initialCapacity, string representation = "mixed") + { + LogCall("init"); + CheckCall(_Init(initialCapacity, representation, out var simulatorId)); + return simulatorId; + } + + [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="destroy")] + private static extern Int64 _Destroy(ulong simId); + + public static void Destroy(ulong simId) + { + LogCall("destroy"); + CheckCall(_Destroy(simId)); + } + + [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="dump_to_console")] + private static extern void _DumpToConsole(ulong simId); + + public static void DumpToConsole(ulong simId) + { + LogCall("dump_to_console"); + _DumpToConsole(simId); + } + + [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="get_current_state")] + private static extern string _GetCurrentState(ulong simId); + + public static State GetCurrentState(ulong simId) + { + LogCall("get_current_state"); + return JsonSerializer.Deserialize(_GetCurrentState(simId)); + } + + [DllImport(DLL_NAME, ExactSpelling =true, CallingConvention =CallingConvention.Cdecl, EntryPoint="get_noise_model_by_name")] + private static extern Int64 _GetNoiseModelByName(string name, out string noiseModel); + + public static NoiseModel GetNoiseModelByName(string name) + { + LogCall("get_noise_model_by_name"); + CheckCall(_GetNoiseModelByName(name, out var noiseModelJson)); + return JsonSerializer.Deserialize(noiseModelJson); + } + + [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="get_noise_model")] + private static extern Int64 _GetNoiseModel(ulong simId, out string noiseModel); + + public static NoiseModel GetNoiseModel(ulong simId) + { + LogCall("get_noise_model"); + CheckCall(_GetNoiseModel(simId, out var noiseModelJson)); + return JsonSerializer.Deserialize(noiseModelJson); + } + + [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="set_noise_model")] + private static extern Int64 _SetNoiseModel(ulong simId, string noiseModel); + + public static void SetNoiseModel(ulong simId, NoiseModel noiseModel) + { + LogCall("set_noise_model"); + string? jsonData = null; + try + { + jsonData = JsonSerializer.Serialize(noiseModel); + CheckCall(_SetNoiseModel(simId, jsonData)); + } + catch (NotSupportedException ex) + { + throw new ArgumentException("Could not serialize noise model to JSON, as no suitable serializer was found.", ex); + } + catch (JsonException ex) + { + throw new Exception($"Could not serialize noise model: {ex.Message}", ex); + } + catch (SimulationException ex) + { + throw new Exception($"Could not set noise model from JSON: {ex.Message}\nJSON data:\n{jsonData}", ex); + } + } + + [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="set_noise_model_by_name")] + private static extern Int64 _SetNoiseModelByName(ulong simId, string name); + + public static void SetNoiseModelByName(ulong simId, string name) + { + LogCall("set_noise_model_by_name"); + CheckCall(_SetNoiseModelByName(simId, name)); + } + + + [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="h")] + private static extern Int64 _H(ulong simId, uint idx); + + public static void H(ulong simId, Qubit q) + { + LogCall("h"); + CheckCall(_H(simId, (uint)q.Id)); + } + + [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="x")] + private static extern Int64 _X(ulong simId, uint idx); + + public static void X(ulong simId, Qubit q) + { + LogCall("x"); + CheckCall(_X(simId, (uint)q.Id)); + } + + [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="y")] + private static extern Int64 _Y(ulong simId, uint idx); + + public static void Y(ulong simId, Qubit q) + { + LogCall("y"); + CheckCall(_Y(simId, (uint)q.Id)); + } + + [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="z")] + private static extern Int64 _Z(ulong simId, uint idx); + + public static void Z(ulong simId, Qubit q) + { + LogCall("z"); + CheckCall(_Z(simId, (uint)q.Id)); + } + + [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="s")] + private static extern Int64 _S(ulong simId, uint idx); + + public static void S(ulong simId, Qubit q) + { + LogCall("s"); + CheckCall(_S(simId, (uint)q.Id)); + } + + [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="s_adj")] + private static extern Int64 _SAdj(ulong simId, uint idx); + + public static void SAdj(ulong simId, Qubit q) + { + LogCall("s"); + CheckCall(_SAdj(simId, (uint)q.Id)); + } + + [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="t")] + private static extern Int64 _T(ulong simId, uint idx); + + public static void T(ulong simId, Qubit q) + { + LogCall("t"); + CheckCall(_T(simId, (uint)q.Id)); + } + + [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="t_adj")] + private static extern Int64 _TAdj(ulong simId, uint idx); + + public static void TAdj(ulong simId, Qubit q) + { + LogCall("t_adj"); + CheckCall(_TAdj(simId, (uint)q.Id)); + } + + [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="cnot")] + private static extern Int64 _CNOT(ulong simId, uint idxControl, uint idxTarget); + + public static void CNOT(ulong simId, Qubit control, Qubit target) + { + LogCall("cnot"); + CheckCall(_CNOT(simId, (uint)control.Id, (uint)target.Id)); + } + + + [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="m")] + private static extern Int64 _M(ulong simId, uint idx, out uint result); + + public static Result M(ulong simId, Qubit q) + { + LogCall("m"); + CheckCall(_M(simId, (uint)q.Id, out var result)); + return result == 1 ? Result.One : Result.Zero; + } + + } +} diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/OpenSystemsSimulator.cs b/src/Simulation/Simulators/OpenSystemsSimulator/OpenSystemsSimulator.cs new file mode 100644 index 00000000000..dfe8e965f94 --- /dev/null +++ b/src/Simulation/Simulators/OpenSystemsSimulator/OpenSystemsSimulator.cs @@ -0,0 +1,289 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Linq; +using Microsoft.Quantum.Simulation.Core; +using Microsoft.Quantum.Simulation.Common; +using System.Runtime.InteropServices; +using System.Threading; +using Microsoft.Quantum.Simulation.Simulators.Exceptions; +using Microsoft.Quantum.Intrinsic.Interfaces; +using System.Collections.Generic; +using System.Diagnostics; +using Newtonsoft.Json.Linq; + +namespace Microsoft.Quantum.Experimental +{ + // NB: This class should not implement IQSharpCore, but does so temporarily + // to make the simulator available to IQ# (note that the I in IQSharpCore + // refers to interfaces, and not to IQ# itself...) + public partial class OpenSystemsSimulator : SimulatorBase, IQSharpCore + { + public static JToken BuildInfo => NativeInterface.SimulatorInfo; + + private readonly ulong Id; + + public override string Name => NativeInterface.Name; + + public NoiseModel NoiseModel + { + get + { + return NativeInterface.GetNoiseModel(Id); + } + + set + { + NativeInterface.SetNoiseModel(Id, value); + } + } + + public State CurrentState => NativeInterface.GetCurrentState(this.Id); + + public class OpenSystemsQubitManager : QubitManager + { + private readonly OpenSystemsSimulator Parent; + public OpenSystemsQubitManager(OpenSystemsSimulator parent, uint capacity) + : base(capacity) + { + this.Parent = parent; + } + + protected override void Release(Qubit qubit, bool wasUsedOnlyForBorrowing) + { + if (qubit != null && qubit.IsMeasured) + { + // Try to reset measured qubits. + // TODO: There are better ways to do this; increment on the + // design and make it customizable. + // FIXME: In particular, this implementation uses a lot of + // extraneous measurements. + if ((this.Parent as IIntrinsicMeasure).Body(new QArray(Pauli.PauliZ), new QArray(qubit)) == Result.One) + { + (this.Parent as IIntrinsicX).Body(qubit); + } + } + base.Release(qubit, wasUsedOnlyForBorrowing); + } + } + + public OpenSystemsSimulator(uint capacity = 3, string representation = "mixed") : base(new QubitManager((long)capacity)) + { + this.Id = NativeInterface.Init(capacity, representation); + } + + void IIntrinsicExp.Body(IQArray paulis, double angle, IQArray targets) + { + throw new NotImplementedException(); + } + + void IIntrinsicExp.AdjointBody(IQArray paulis, double angle, IQArray targets) + { + throw new NotImplementedException(); + } + + void IIntrinsicExp.ControlledBody(IQArray controls, IQArray paulis, double angle, IQArray targets) + { + throw new NotImplementedException(); + } + + void IIntrinsicExp.ControlledAdjointBody(IQArray controls, IQArray paulis, double angle, IQArray targets) + { + throw new NotImplementedException(); + } + + void IIntrinsicH.Body(Qubit target) + { + NativeInterface.H(this.Id, target); + } + + void IIntrinsicH.ControlledBody(IQArray controls, Qubit target) + { + throw new NotImplementedException(); + } + + Result IIntrinsicMeasure.Body(IQArray paulis, IQArray targets) + { + if (targets is { Count: 1 } && paulis is { Count: 1 } && paulis.Single() == Pauli.PauliZ) + { + return NativeInterface.M(this.Id, targets.Single()); + } + else + { + // FIXME: Pass multiqubit and non-Z cases to decompositions. + throw new NotImplementedException(); + } + } + + void IIntrinsicR.Body(Pauli pauli, double angle, Qubit target) + { + if (pauli == Pauli.PauliI) + { + // Don't apply global phases on uncontrolled operations. + return; + } + throw new NotImplementedException("Arbitrary rotation with noise is not yet supported."); + } + + void IIntrinsicR.AdjointBody(Pauli pauli, double angle, Qubit target) + { + (this as IIntrinsicR).Body(pauli, -angle, target); + } + + void IIntrinsicR.ControlledBody(IQArray controls, Pauli pauli, double angle, Qubit target) + { + if (controls is { Count: 0 }) + { + (this as IIntrinsicR).Body(pauli, angle, target); + } + else + { + throw new NotImplementedException("Arbitrary controlled rotation with noise is not yet supported."); + } + } + + void IIntrinsicR.ControlledAdjointBody(IQArray controls, Pauli pauli, double angle, Qubit target) + { + (this as IIntrinsicR).ControlledBody(controls, pauli, -angle, target); + } + + void IIntrinsicS.Body(Qubit target) + { + NativeInterface.S(this.Id, target); + } + + void IIntrinsicS.AdjointBody(Qubit target) + { + NativeInterface.SAdj(this.Id, target); + } + + void IIntrinsicS.ControlledBody(IQArray controls, Qubit target) + { + throw new NotImplementedException(); + } + + void IIntrinsicS.ControlledAdjointBody(IQArray controls, Qubit target) + { + throw new NotImplementedException(); + } + + void IIntrinsicT.Body(Qubit target) + { + NativeInterface.T(this.Id, target); + } + + void IIntrinsicT.AdjointBody(Qubit target) + { + NativeInterface.TAdj(this.Id, target); + } + + void IIntrinsicT.ControlledBody(IQArray controls, Qubit target) + { + throw new NotImplementedException(); + } + + void IIntrinsicT.ControlledAdjointBody(IQArray controls, Qubit target) + { + throw new NotImplementedException(); + } + + void IIntrinsicX.Body(Qubit target) + { + NativeInterface.X(this.Id, target); + } + + void IIntrinsicX.ControlledBody(IQArray controls, Qubit target) + { + // TODO: pass off to decompositions for more than one control. + if (controls is { Count: 0 }) + { + (this as IIntrinsicX).Body(target); + } + else if (controls is { Count: 1 }) + { + NativeInterface.CNOT(this.Id, controls[0], target); + } + else + { + throw new NotImplementedException(); + } + } + + void IIntrinsicY.Body(Qubit target) + { + NativeInterface.Y(this.Id, target); + } + + void IIntrinsicY.ControlledBody(IQArray controls, Qubit target) + { + throw new NotImplementedException(); + } + + void IIntrinsicZ.Body(Qubit target) + { + NativeInterface.Z(this.Id, target); + } + + void IIntrinsicZ.ControlledBody(IQArray controls, Qubit target) + { + // TODO: pass off to decompositions for more than one control. + if (controls is { Count: 0 }) + { + (this as IIntrinsicZ).Body(target); + } + else if (controls is { Count: 1 }) + { + Get().__Body__((controls[0], target)); + } + else + { + throw new NotImplementedException(); + } + } + + public void Dispose() + { + NativeInterface.Destroy(this.Id); + } + + public class OpenSystemsDumpMachine : Quantum.Diagnostics.DumpMachine + { + private OpenSystemsSimulator Simulator { get; } + + public OpenSystemsDumpMachine(OpenSystemsSimulator m) : base(m) + { + this.Simulator = m; + } + + public override Func __Body__ => (location) => + { + if (location == null) { throw new ArgumentNullException(nameof(location)); } + Simulator.MaybeDisplayDiagnostic(Simulator.CurrentState); + return QVoid.Instance; + }; + } + + // TODO: implement this by adding a new PartialTrace trait to the + // Rust side, and then exposing it through the C API. + // Until we have that, there's not a sensible way to interpret + // states on subregisters in general. + public class OpenSystemsDumpRegister : Quantum.Diagnostics.DumpRegister + { + private OpenSystemsSimulator Simulator { get; } + + public OpenSystemsDumpRegister(OpenSystemsSimulator m) : base(m) + { + this.Simulator = m; + } + + public override Func<(T, IQArray), QVoid> __Body__ => (args) => + { + var (location, register) = args; + if (location == null) { throw new ArgumentNullException(nameof(location)); } + this.Simulator.Get().__Body__?.Invoke("DumpRegister not yet supported on OpenSystemsSimulator; skipping."); + return QVoid.Instance; + }; + } + } +} diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/README.md b/src/Simulation/Simulators/OpenSystemsSimulator/README.md new file mode 100644 index 00000000000..59d2d1a1f4e --- /dev/null +++ b/src/Simulation/Simulators/OpenSystemsSimulator/README.md @@ -0,0 +1,65 @@ +# .NET Bindings for Quantum Development Kit Experimental Simulators + +The .NET classes defined in this folder utilize the C library built from the `qdk_sim` Rust crate to provide bindings to the experimental simulators that can be used from Q# programs. + + +For more details on the `qdk_sim` crate, and its APIs for Rust, C, and Python, please see the documentation provided with that crate. From the root of this repository: + +```bash +pwsh ./bootstrap.ps1 +cd src/Simulation/qdk_sim_rs +cargo +nightly doc --features python --open +``` + +For more details on using these simulators from Q# programs called from Python hosts, please see . + +## Native interface + +The core interoperability boundary between the C# runtime for the Quantum Development Kit and the `qdk_sim` native library is defined in the [`NativeInterface` static class](./NativeInterface.cs). This class provides P/Invoke declarations for each function exposed by the C API for the `qdk_sim` crate, as well as methods that call into these C API methods. The managed methods that correspond to each C API function check error codes reuturned by C API functions and convert them into .NET exceptions, allowing for C API errors to be easily caught by managed code. + +For example, to use the native API to create a new simulator with a mixed-state representation: + +```csharp +using static Microsoft.Quantum.Experimental.NativeInterface; + +var simId = Init(initialCapacity: 3, representation: "mixed"); +try +{ + H(simId, 0); + var result = M(simId, 0); + System.Console.Out.WriteLine($"Got {result}!"); +} +finally +{ + Destroy(simId); +} +``` + +## Q# simulator interface + +For most applications, the native interface is not useful on its own, but as called as a Q# simulator. The [`OpenSystemsSimulator` class](./OpenSystemsSimulator.cs) provides bindings between the native interface described above and the `SimulatorBase` class in the C# runtime for Q#. + +This class implements each of the intrinsic Q# operations either directly as a call into the `qdk_sim` library, or by using [decompositions](./Decompositions) to reduce Q# intrinsics into those intrinsics supported by the experimental simulators. + +To create a new simulator, use the constructor for the `OpenSystemsSimulator` class: + +```csharp +var qsim = new OpenSystemsSimulator(3, "mixed"); +``` + +The noise model for the simulator can be accessed or set using the `OpenSystemsSimulator.NoiseModel` property (see [noise modeling API](#noise-modeling-api), below)): + +```csharp +qsim.NoiseModel = + NoiseModel.TryGetByName("ideal_stabilizer", out var noiseModel) + ? noiseModel + : throw new Exception("Noise model with name 'ideal_stabilizer' not found."); +``` + +This simulator can then be used as any other Q# simulator. + +## Noise modeling API + +To get and set noise models, each of the data structures in the `qdk_sim` crate has an analogous C# class that can be used to serialize and deserialize `qdk_sim` data structures as JSON objects. + +In particular, the [`NoiseModel` class](./DataModel/NoiseModel.cs) class parallels the `NoiseModel` struct in the `qdk_sim` crate, and allows accessing or modifying the noise model used by experimental simulators to run each Q# program. diff --git a/src/Simulation/Simulators/QuantumSimulator/SimulatorBase.cs b/src/Simulation/Simulators/QuantumSimulator/SimulatorBase.cs index 2797a2b13ab..8bb52e118e4 100644 --- a/src/Simulation/Simulators/QuantumSimulator/SimulatorBase.cs +++ b/src/Simulation/Simulators/QuantumSimulator/SimulatorBase.cs @@ -248,15 +248,15 @@ public void CheckQubit(Qubit q, string qubitName) { if (q == null) throw new ArgumentNullException(qubitName, "Trying to perform an intrinsic operation on a null Qubit"); - if (!QubitManager.IsValid(q)) + if (!(QubitManager?.IsValid(q) ?? true)) { throw new ArgumentException($"Cannot use qubit {q.Id}. Qubit is invalid.", qubitName); } - if (QubitManager.IsFree(q)) + if (QubitManager?.IsFree(q) ?? false) { throw new ArgumentException($"Cannot use qubit {q.Id}. Qubit has already been released.", qubitName); } - if (QubitManager.IsDisabled(q)) + if (QubitManager?.IsDisabled(q) ?? false) { throw new ArgumentException($"Cannot use qubit {q.Id}. Qubit is disabled.", qubitName); } @@ -291,6 +291,10 @@ public class Allocate : Intrinsic.Allocate public Allocate(SimulatorBase m) : base(m) { this.sim = m; + if (m.QubitManager == null) + { + throw new NullReferenceException($"SimulatorBase {m} has a null-valued qubit manager, but a qubit manager is required to implement the Allocate operation."); + } this.manager = m.QubitManager; } @@ -323,6 +327,10 @@ public class Release : Intrinsic.Release public Release(SimulatorBase m) : base(m) { this.sim = m; + if (m.QubitManager == null) + { + throw new NullReferenceException($"SimulatorBase {m} has a null-valued qubit manager, but a qubit manager is required to implement the Release operation."); + } this.manager = m.QubitManager; } @@ -353,6 +361,10 @@ public class Borrow : Intrinsic.Borrow public Borrow(SimulatorBase m) : base(m) { this.sim = m; + if (m.QubitManager == null) + { + throw new NullReferenceException($"SimulatorBase {m} has a null-valued qubit manager, but a qubit manager is required to implement the Borrow operation."); + } this.manager = m.QubitManager; } @@ -385,6 +397,10 @@ public class Return : Intrinsic.Return public Return(SimulatorBase m) : base(m) { this.sim = m; + if (m.QubitManager == null) + { + throw new NullReferenceException($"SimulatorBase {m} has a null-valued qubit manager, but a qubit manager is required to implement the Return operation."); + } this.manager = m.QubitManager; } @@ -414,6 +430,10 @@ public class GetQubitsAvailableToUse : Environment.GetQubitsAvailableToUse public GetQubitsAvailableToUse(SimulatorBase m) : base(m) { this.sim = m; + if (m.QubitManager == null) + { + throw new NullReferenceException($"SimulatorBase {m} has a null-valued qubit manager, but a qubit manager is required to implement the GetQubitsAvailableToUse operation."); + } this.manager = m.QubitManager; } @@ -432,6 +452,11 @@ public class GetQubitsAvailableToBorrow : Environment.GetQubitsAvailableToBorrow public GetQubitsAvailableToBorrow(SimulatorBase m) : base(m) { this.sim = m; + + if (m.QubitManager == null) + { + throw new NullReferenceException($"SimulatorBase {m} has a null-valued qubit manager, but a qubit manager is required to implement the GetQubitsAvailableToBorrow operation."); + } this.manager = m.QubitManager; } diff --git a/src/Simulation/qdk_sim_rs/.config/dotnet-tools.json b/src/Simulation/qdk_sim_rs/.config/dotnet-tools.json new file mode 100644 index 00000000000..8e180ea3016 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-script": { + "version": "1.1.0", + "commands": [ + "dotnet-script" + ] + } + } +} \ No newline at end of file diff --git a/src/Simulation/qdk_sim_rs/.gitignore b/src/Simulation/qdk_sim_rs/.gitignore new file mode 100644 index 00000000000..a740f3ce698 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/.gitignore @@ -0,0 +1,22 @@ +linux +osx +win10 +target +drop + +# We inject version numbers into Cargo.toml, so don't want them stored in repo. +Cargo.toml +# In the future, it would be good to enable reproducible builds by committing +# the lockfile and using --locked in calls to cargo. +Cargo.lock + +# Ignore Python temporaries and build artifacts. +*.pyd +*.whl +*.egg-info + +# Ignore generated C/C++ headers. +include/ + +# Don't ignore documentation here. +!docs diff --git a/src/Simulation/qdk_sim_rs/CONTRIBUTING.md b/src/Simulation/qdk_sim_rs/CONTRIBUTING.md new file mode 100644 index 00000000000..894a188cb9b --- /dev/null +++ b/src/Simulation/qdk_sim_rs/CONTRIBUTING.md @@ -0,0 +1,30 @@ +# Contributing to the QDK Experimental Simulators + +## Build prerequisites + +The build for the experimental simulators requires the nightly Rust toolchain to be installed, along with support for clippy and rustfmt. These prerequisites can be installed by using the `prerequisites.ps1` script in this folder: + +```pwsh +PS> ./prerequistes.ps1 +``` + +## Code quality checks + +The build for this crate enforces the following mandatory code quality checks: + +- `rustfmt`: Check code style and formatting rules. +- `clippy`: Check for common programming errors and linter violations. +- `#[deny(missing_docs)]`: Require API documentation for all public crates, modules, traits, functions, and types. + +## Testing strategy + +Tests for the open systems simulator consist of five distinct parts: + +- Rust-language unit tests for the Rust library. + These tests are defined in `#[cfg(test)]` submodules of each module in `./src/`. +- Rust-language integration tests for the Rust library. + These tests are defined in modules under the `./tests/` folder. +- Q#-language unit tests in the C#-based simulation runtime. + These tests ensure that the binding of the Rust library works as expected when included into the C#-based runtime, and are defined in operations marked with `@Test("Microsoft.Quantum.Experimental.OpenSystemsSimulator")` under the `qsharp-runtime/src/Simulation/Simulators.Tests/QuantumTestSuite` folder. +- C#-language unit tests in the IQ# kernel. + These tests ensure that noise models and noisy simulation can be correctly exposed to Python and Q# notebook users; please see the `microsoft/iqsharp` repo for more details. diff --git a/src/Simulation/qdk_sim_rs/Cargo.toml.template b/src/Simulation/qdk_sim_rs/Cargo.toml.template new file mode 100644 index 00000000000..64e8483c780 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/Cargo.toml.template @@ -0,0 +1,88 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +[package] +name = "qdk_sim_experimental" +version = "0.1.0" +authors = ["Christopher Granade "] +edition = "2018" +license = "MIT" +description = "Experimental simulators for use with the Quantum Development Kit." +homepage = "https://github.com/microsoft/qsharp-runtime" +repository = "https://github.com/microsoft/qsharp-runtime" + +# Exclude files specific to QDK build pipelines. +exclude = [ + "*.template", "*.csx", "*.ps1", "NuGet.Config", "drops" +] + +[lib] +name = "qdk_sim" +path = "src/lib.rs" +crate-type = ["rlib", "staticlib", "cdylib"] + +# Optional build-time features: we use this to create Python and WASM bindings. +[features] +default = [] +wasm = ["web-sys"] +# When Python bindings are enabled, we also need the pyo3 dependency. +python = ["pyo3", "numpy"] + +# Enable LaTeX on docs.rs. +# See https://stackoverflow.com/a/54573800/267841 and +# https://github.com/rust-num/num/pull/226/files for why this works. +[package.metadata.docs.rs] +rustdoc-args = [ "--html-in-header", "docs-includes/header.html", "--html-after-content", "docs-includes/after.html" ] + + +[profile.release] +opt-level = 3 +codegen-units = 1 # Reduce number of codegen units to increase optimizations. +panic = 'unwind' + +[dependencies] +ndarray = { version = "0.15.2", features = ["serde"] } +num-complex = { version = "0.4", features = ["serde"] } +num-traits = "0.2" +derive_more = "0.99.10" +itertools = "0.9.0" +rand = { version = "0.7.3", features = ["alloc"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +lazy_static = "1.4.0" +cfg-if = "1.0.0" +num_enum = "0.5.1" +# See https://github.com/rust-random/rand/issues/990 +# and https://docs.rs/getrandom/0.1.15/getrandom/index.html#support-for-webassembly-and-asmjs +# for why this is needed. +# NB: We depend on 0.1.15, since that's what gets brought in transitively +# by rand and rand_core. +getrandom = { version = "0.1.15", features = ["wasm-bindgen"] } + +# We only need web-sys when compiling with the wasm feature. +web-sys = { version = "0.3.4", features = ['console'], optional = true } + +# We only need PyO3 when generating Python bindings. +pyo3 = { version = "0.13.2", features = ["extension-module"], optional = true } +numpy = {version = "0.13.1", optional = true} + +# We use built to expose compile-time metadata about how this crate +# was built to C and Rust callers. +built = "0.5.0" + +[build-dependencies] +built = "0.5.0" +cbindgen = "0.19.0" + + +[dev-dependencies] +assert-json-diff = "2.0.1" +criterion = { version = "0.3", features = ['html_reports', 'csv_output'] } + +[[bench]] +name = "c_api_benchmark" +harness = false + +[[bench]] +name = "microbenchmark" +harness = false diff --git a/src/Simulation/qdk_sim_rs/NuGet.Config b/src/Simulation/qdk_sim_rs/NuGet.Config new file mode 100644 index 00000000000..8b638f09a31 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/NuGet.Config @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/src/Simulation/qdk_sim_rs/README.md b/src/Simulation/qdk_sim_rs/README.md new file mode 100644 index 00000000000..f48c36c0bd5 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/README.md @@ -0,0 +1,150 @@ + + +# Quantum Development Kit Experimental Simulators + +> ## **⚠** WARNING **⚠** +> +> This crate is **experimental**, and may undergo breaking API changes with no notice, and may not be maintained in future versions of the Quantum Development Kit. +> +> As an experimental feature of the Quantum Development Kit, this crate may be buggy or incomplete. Please check the tracking issue at [microsoft/qsharp-runtime#714](https://github.com/microsoft/qsharp-runtime/issues/714) for more information. + +> ## **ⓘ** TIP +> +> This crate provides low-level APIs for interacting with experimental simulators. If you're interested in using the experimental simulators to run your Q# programs, please see the installation instructions at . + +This **experimental** crate implements simulation functionality for the Quantum Development Kit, including: + +- Open systems simulation +- Stabilizer simulation + +The [`c_api`] module allows for using the simulation functionality in this crate from C, or from other languages with a C FFI (e.g.: C++ or C#), while Rust callers can take advantage of the structs and methods in this crate directly. + +Similarly, the [`python`] module allows exposing data structures in this crate to Python programs. + +## Cargo Features + +- **`python`**: Enables Python bindings for this crate. +- **`wasm`**: Ensures that the crate is compatible with usage from WebAssembly. + +## Representing quantum systems + +This crate provides several different data structures for representing quantum systems in a variety of different conventions: + +- [`State`]\: Represents stabilizer, pure, or mixed states of a register of qubits. +- [`Process`]\: Represents processes that map states to states. +- [`Instrument`]\: Represents quantum instruments, the most general form of measurement. + +## Noise model serialization + +Noise models can be serialized to JSON for interoperability across languages. In particular, each noise model is represented by a JSON object with properties for each operation, for the initial state, and for the instrument used to implement $Z$-basis measurement. + +For example: + +```json +{ + "initial_state": { + "n_qubits": 1, + "data": { + "Mixed": { + "v": 1, "dim":[2 ,2], + "data": [[1.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0]] + } + } + }, + "i": { + "n_qubits": 1, + "data": { + "Unitary": { + "v": 1,"dim": [2, 2], + "data": [[1.0, 0.0], [0.0, 0.0], [0.0, 0.0], [1.0, 0.0]] + } + } + }, + ... + "z_meas": { + "Effects": [ + { + "n_qubits": 1, + "data": { + "KrausDecomposition": { + "v":1, "dim": [1, 2, 2], + "data": [[1.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0]] + } + } + }, + { + "n_qubits": 1, + "data": { + "KrausDecomposition": { + "v": 1,"dim": [1, 2, 2], + "data":[[0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [1.0, 0.0]] + } + } + } + ] + } +} +``` + +The value of the `initial_state` property is a serialized [`State`], the value of each operation property (i.e.: `i`, `x`, `y`, `z`, `h`, `s`, `s_adj`, `t`, `t_adj`, and `cnot`) is a serialized [`Process`], and the value of `z_meas` is a serialized [`Instrument`]. + +### Representing arrays of complex numbers + +Throughout noise model serialization, JSON objects representing $n$-dimensional arrays of complex numbers are used to store various vectors, matrices, and tensors. Such arrays are serialized as JSON objects with three properties: + +- `v`: The version number of the JSON schema; must be `"1"`. +- `dims`: A list of the dimensions of the array being represented. +- `data`: A list of the elements of the flattened array, each of which is represented as a list with two entries representing the real and complex parts of each element. + +For example, consider the serialization of the ideal `y` operation: + +```json +"y": { + "n_qubits": 1, + "data": { + "Unitary": { + "v": 1, "dim": [2, 2], + "data": [[0.0, 0.0], [0.0, 1.0], [0.0, -1.0], [0.0, 0.0]] + } + } +} +``` + +### Representing states and processes + +Each state and process is represented in JSON by an object with two properties, `n_qubits` and `data`. The value of `data` is itself a JSON object with one property indicating which variant of the [`StateData`] or [`ProcessData`] enum is used to represent that state or process, respectively. + +For example, the following JSON object represents the mixed state $\ket{0}\bra{0}$: + +```json +{ + "n_qubits": 1, + "data": { + "Mixed": { + "v": 1, "dim":[2 ,2], + "data": [[1.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0]] + } + } +} +``` + +### Representing instruments + +TODO + +## Known issues + +- Performance of open systems simulation still needs additional work for larger registers. +- Some gaps in different conversion functions and methods. +- Stabilizer states cannot yet be measured through [`Instrument`] struct, only through underlying [`Tableau`]. +- Many parts of the crate do not yet have Python bindings. +- Stabilizer simulation not yet exposed via C API. +- Test and microbenchmark coverage still incomplete. +- Too many APIs `panic!` or `unwrap`, and need replaced with `Result` returns instead. diff --git a/src/Simulation/qdk_sim_rs/benches/c_api_benchmark.rs b/src/Simulation/qdk_sim_rs/benches/c_api_benchmark.rs new file mode 100644 index 00000000000..d4ab3fbdc19 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/benches/c_api_benchmark.rs @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! This set of benchmarks exercises the open systems simulator exclusively via +//! its C API, so as to gauge any potential issues for C-based consumers of the +//! simulator. + +use criterion::{criterion_group, criterion_main, Criterion}; +use qdk_sim::c_api; +use std::ffi::CString; + +// Use include_str! to store test case JSON as a string into the compiled +// test executable. +static BENCHMARK_NOISE_MODEL_JSON: &str = include_str!("data/benchmark-noise-model.json"); + +fn with_test_suite( + sim_id: usize, + group: &mut criterion::BenchmarkGroup, +) { + group.bench_function("apply x", |b| { + b.iter(|| { + c_api::x(sim_id, 0); + }) + }); + group.bench_function("apply z", |b| { + b.iter(|| { + c_api::z(sim_id, 0); + }) + }); + group.bench_function("apply cnot", |b| { + b.iter(|| { + c_api::cnot(sim_id, 0, 1); + }) + }); + group.bench_function("measure", |b| { + b.iter(|| { + let mut result: usize = 0; + // NB: The C API is not in general safe. + unsafe { + c_api::m(sim_id, 0, &mut result); + } + }) + }); +} + +fn ideal(c: &mut Criterion) { + let mut sim_id: usize = 0; + unsafe { + let _err = c_api::init(3, CString::new("mixed").unwrap().as_ptr(), &mut sim_id); + } + let mut group = c.benchmark_group("ideal"); + with_test_suite(sim_id, &mut group); + group.finish(); + c_api::destroy(sim_id); +} + +fn noisy(c: &mut Criterion) { + let mut sim_id: usize = 0; + unsafe { + let _err = c_api::init(3, CString::new("mixed").unwrap().as_ptr(), &mut sim_id); + } + // NB: The C API is not in general safe. + unsafe { + c_api::set_noise_model( + sim_id, + CString::new(BENCHMARK_NOISE_MODEL_JSON).unwrap().as_ptr(), + ); + } + let mut group = c.benchmark_group("noisy"); + with_test_suite(sim_id, &mut group); + group.finish(); + c_api::destroy(sim_id); +} + +criterion_group!(benches, ideal, noisy); +criterion_main!(benches); diff --git a/src/Simulation/qdk_sim_rs/benches/data/benchmark-noise-model.json b/src/Simulation/qdk_sim_rs/benches/data/benchmark-noise-model.json new file mode 100644 index 00000000000..e9d7f107f7c --- /dev/null +++ b/src/Simulation/qdk_sim_rs/benches/data/benchmark-noise-model.json @@ -0,0 +1 @@ +{"initial_state":{"n_qubits":1,"data":{"Mixed":{"v":1,"dim":[2,2],"data":[[1,0],[0,0],[0,0],[0,0]]}}},"cnot":{"n_qubits":2,"data":{"Unitary":{"v":1,"dim":[4,4],"data":[[1,0],[0,0],[0,0],[0,0],[0,0],[1,0],[0,0],[0,0],[0,0],[0,0],[0,0],[1,0],[0,0],[0,0],[1,0],[0,0]]}}},"i":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1,0],[0,0],[0,0],[1,0]]}}},"s":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1,0],[0,0],[0,0],[0,1]]}}},"s_adj":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1,0],[0,0],[0,0],[0,-1]]}}},"t":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1,0],[0,0],[0,0],[0.7071067811865476,0.7071067811865476]]}}},"t_adj":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1,0],[0,0],[0,0],[0.7071067811865476,-0.7071067811865476]]}}},"h":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[0.7071067811865476,0],[0.7071067811865476,0],[0.7071067811865476,0],[-0.7071067811865476,0]]}}},"x":{"n_qubits":1,"data":{"KrausDecomposition":{"v":1,"dim":[4,2,2],"data":[[0,0],[0.961769203083567,0],[0.9617692030835675,0],[0,0],[0,0],[0.15811388300841897,0],[-0.1581138830084189,0],[0,0],[0.22360679774997896,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0.22360679774997896,0]]}}},"y":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[0,0],[0,1],[0,-1],[0,0]]}}},"z":{"n_qubits":1,"data":{"KrausDecomposition":{"v":1,"dim":[2,2,2],"data":[[0.9746794344808962,0],[0,0],[0,0],[-0.9746794344808962,0],[0.2236067977499789,0],[0,0],[0,0],[0.22360679774997896,0]]}}},"z_meas":{"effects":[{"n_qubits":1,"data":{"KrausDecomposition":{"v":1,"dim":[1,2,2],"data":[[0.975,0],[0,0],[0,0],[0.025000000000000022,0]]}}},{"n_qubits":1,"data":{"KrausDecomposition":{"v":1,"dim":[1,2,2],"data":[[0.025000000000000022,0],[0,0],[0,0],[0.9749999999999999,0]]}}}]}} \ No newline at end of file diff --git a/src/Simulation/qdk_sim_rs/benches/data/noise-model-export.ipynb b/src/Simulation/qdk_sim_rs/benches/data/noise-model-export.ipynb new file mode 100644 index 00000000000..804cd28ccd4 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/benches/data/noise-model-export.ipynb @@ -0,0 +1,212 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "logical-diving", + "metadata": {}, + "source": [ + "This notebook is used to define a noise model for benchmarking purposes." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "understanding-business", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import qutip as qt" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "fallen-office", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Preparing Q# environment...\n" + ] + } + ], + "source": [ + "import qsharp\n", + "from qsharp.experimental import enable_noisy_simulation, get_noise_model, set_noise_model\n", + "enable_noisy_simulation()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "pursuant-plenty", + "metadata": {}, + "outputs": [], + "source": [ + "I, X, Y, Z = qt.qeye(2), qt.sigmax(), qt.sigmay(), qt.sigmaz()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "labeled-strike", + "metadata": {}, + "outputs": [], + "source": [ + "sI, sX, sY, sZ = map(qt.to_super, [I, X, Y, Z])" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "answering-europe", + "metadata": {}, + "outputs": [], + "source": [ + "def total_dephasing_channel():\n", + " return (1 / 2) * sI + (1 / 2) * sZ" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "attached-juice", + "metadata": {}, + "outputs": [], + "source": [ + "def dephasing_channel(p):\n", + " return (1 - p) * sI + p * total_dephasing_channel()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "serious-warner", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "Quantum object: dims = [[[2], [2]], [[2], [2]]], shape = (4, 4), type = super, isherm = True\\begin{equation*}\\left(\\begin{array}{*{11}c}1.0 & 0.0 & 0.0 & 0.0\\\\0.0 & 0.900 & 0.0 & 0.0\\\\0.0 & 0.0 & 0.900 & 0.0\\\\0.0 & 0.0 & 0.0 & 1.0\\\\\\end{array}\\right)\\end{equation*}" + ], + "text/plain": [ + "Quantum object: dims = [[[2], [2]], [[2], [2]]], shape = (4, 4), type = super, isherm = True\n", + "Qobj data =\n", + "[[1. 0. 0. 0. ]\n", + " [0. 0.9 0. 0. ]\n", + " [0. 0. 0.9 0. ]\n", + " [0. 0. 0. 1. ]]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dephasing_channel(0.1)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "requested-instruction", + "metadata": {}, + "outputs": [], + "source": [ + "def total_depolarizing_channel():\n", + " return (sI + sX + sY + sZ) / 4" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "exact-argument", + "metadata": {}, + "outputs": [], + "source": [ + "def depolarizing_channel(p):\n", + " return (1 - p) * sI + p * total_depolarizing_channel()" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "academic-focus", + "metadata": {}, + "outputs": [], + "source": [ + "def finite_visibility_meas(visibility):\n", + " return [\n", + " qt.to_super(visibility * (I + sign * Z) / 2 + (1 - visibility) * I / 2)\n", + " for sign in (+1, -1)\n", + " ]" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "inclusive-active", + "metadata": {}, + "outputs": [], + "source": [ + "noise_model = get_noise_model()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "instructional-mortality", + "metadata": {}, + "outputs": [], + "source": [ + "noise_model['x'] = depolarizing_channel(0.1) * sX\n", + "noise_model['z'] = dephasing_channel(0.1) * sZ\n", + "noise_model['z_meas']['effects'] = finite_visibility_meas(0.95)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "voluntary-parallel", + "metadata": {}, + "outputs": [], + "source": [ + "set_noise_model(noise_model)\n", + "qsharp.client._execute('%experimental.noise_model --save benchmark-noise-model.json')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "functional-concentrate", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/src/Simulation/qdk_sim_rs/benches/microbenchmark.rs b/src/Simulation/qdk_sim_rs/benches/microbenchmark.rs new file mode 100644 index 00000000000..89ccd07b809 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/benches/microbenchmark.rs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! The benchmarks in this module exercise the internals of the simulator at +//! a low level, and thus are not indicative of user-facing performance. +//! Rather, these microbenchmarks are intended to help diagnose what the root +//! cause may be when user-facing performance is degraded. +//! In particular, optimizing these benchmarks may not translate into improved +//! performance in user code. + +use criterion::{criterion_group, criterion_main, Criterion}; +use qdk_sim::{ + common_matrices, + common_matrices::nq_eye, + linalg::{extend_one_to_n, extend_two_to_n, Tensor}, +}; + +fn linalg(c: &mut Criterion) { + let mut group = c.benchmark_group("linalg"); + for n_qubits in [1usize, 2, 3, 4].iter() { + group.bench_with_input(format!("nq_eye({})", n_qubits), n_qubits, |b, nq| { + b.iter(|| { + let _eye = nq_eye(*nq); + }) + }); + } + for idx_qubit in [0usize, 1, 2].iter() { + group.bench_with_input( + format!( + "extend_one_to_n(n_left: {}, n_right: {})", + idx_qubit, + 2 - idx_qubit + ), + idx_qubit, + |b, i| { + // Create some test data. + let data = nq_eye(1); + b.iter(|| { + let _extended = extend_one_to_n(data.view(), *i, 3); + }) + }, + ); + } + for idx_qubit in [0usize, 1, 2].iter() { + group.bench_with_input( + format!( + "extend_two_to_n(n_left: {}, n_right: {})", + idx_qubit, + 2 - idx_qubit + ), + idx_qubit, + |b, i| { + // Create some test data. + let data = common_matrices::cnot(); + b.iter(|| { + let _extended = extend_two_to_n(data.view(), *i, 3, 4); + }) + }, + ); + } + group.bench_function("tensor 2x2 with 2x2", |b| { + let x = common_matrices::x(); + let y = common_matrices::y(); + b.iter(|| { + let _result = x.tensor(&y); + }) + }); + group.bench_function("tensor 2x2 with 4x4", |b| { + let x = common_matrices::x(); + let cnot = common_matrices::cnot(); + b.iter(|| { + let _result = x.tensor(&cnot); + }) + }); + group.bench_function("tensor 4x4 with 2x2", |b| { + let x = common_matrices::x(); + let cnot = common_matrices::cnot(); + b.iter(|| { + let _result = cnot.tensor(&x); + }) + }); + group.finish(); +} + +criterion_group!(benches, linalg); +criterion_main!(benches); diff --git a/src/Simulation/qdk_sim_rs/build-qdk-sim-rs.ps1 b/src/Simulation/qdk_sim_rs/build-qdk-sim-rs.ps1 new file mode 100644 index 00000000000..4ab192620d6 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/build-qdk-sim-rs.ps1 @@ -0,0 +1,41 @@ +& (Join-Path $PSScriptRoot ".." ".." ".." "build" "set-env.ps1"); +$IsCI = "$Env:TF_BUILD" -ne "" -or "$Env:CI" -eq "true"; + +Push-Location $PSScriptRoot + # Start with the quick check first and make sure that Rust sources + # meet formatting and style guide rules. + cargo fmt -- --check + $script:allOk = $script:allOk -and $LASTEXITCODE -eq 0; + + # Check linting rules defined by clippy, a linting tool provided with the + # Rust toolchain. Please see https://github.com/rust-lang/rust-clippy + # and https://rust-lang.github.io/rust-clippy/master/index.html + # for more information. + # If there's a false positive, that check should be explicitly disabled + # at the point where the false positive occurs with an explanation as to + # why it's OK. + cargo clippy -- -D warnings + $script:allOk = $script:allOk -and $LASTEXITCODE -eq 0; + + $releaseFlag = "$Env:BUILD_CONFIGURATION" -eq "Release" ? @("--release") : @(); + + # Enable control flow guard (see https://github.com/microsoft/qsharp-runtime/pull/647) + # for interoperating Rust and C. + # NB: CFG is only supported on Windows, but the Rust flag is supported on + # all platforms; it's ignored on platforms without CFG functionality. + $Env:RUSTFLAGS = "-C control-flow-guard"; + + # Actually run the build. + cargo +nightly build -Z unstable-options @releaseFlag --out-dir "drop"; + + # Make sure docs are complete. + $Env:RUSTDOCFLAGS = "--html-in-header $(Resolve-Path docs-includes/header.html) " + ` + "--html-after-content $(Resolve-Path docs-includes/after.html)" + cargo +nightly doc; + + # When building in CI, free disk space by cleaning up. + # Note that this takes longer, but saves ~1 GB of space. + if ($IsCI) { + cargo clean; + } +Pop-Location diff --git a/src/Simulation/qdk_sim_rs/build.rs b/src/Simulation/qdk_sim_rs/build.rs new file mode 100644 index 00000000000..aae76e31ea3 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/build.rs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +extern crate cbindgen; + +use std::env; + +fn main() -> Result<(), String> { + built::write_built_file().expect("Failed to acquire build-time information"); + + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + + cbindgen::generate_with_config( + &crate_dir, + cbindgen::Config { + language: cbindgen::Language::C, + ..Default::default() + }, + ) + .map_err(|e| e.to_string())? + .write_to_file("include/qdk_sim.h"); + + cbindgen::generate_with_config( + &crate_dir, + cbindgen::Config { + ..Default::default() + }, + ) + .map_err(|e| e.to_string())? + .write_to_file("include/qdk_sim.hpp"); + + Ok(()) +} diff --git a/src/Simulation/qdk_sim_rs/cbindgen.toml b/src/Simulation/qdk_sim_rs/cbindgen.toml new file mode 100644 index 00000000000..08094f28fc2 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/cbindgen.toml @@ -0,0 +1 @@ +language = "C" diff --git a/src/Simulation/qdk_sim_rs/docs-includes/after.html b/src/Simulation/qdk_sim_rs/docs-includes/after.html new file mode 100644 index 00000000000..d855ca7e178 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/docs-includes/after.html @@ -0,0 +1,12 @@ + diff --git a/src/Simulation/qdk_sim_rs/docs-includes/header.html b/src/Simulation/qdk_sim_rs/docs-includes/header.html new file mode 100644 index 00000000000..affe693fa82 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/docs-includes/header.html @@ -0,0 +1,3 @@ + + + diff --git a/src/Simulation/qdk_sim_rs/docs/c-api.md b/src/Simulation/qdk_sim_rs/docs/c-api.md new file mode 100644 index 00000000000..11d152bafe3 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/docs/c-api.md @@ -0,0 +1,145 @@ +# Using Experimental Simulators from C + +This module exposes a C API for this crate, useful for embedding into simulation +runtimes. + +## Safety + +As this is a foreign-function interface, many of the functions exposed here +are **unsafe**, representing that the caller is required to ensure that safety +conditions in the host language are upheld. + +Please pay attention to any listed safety notes when calling into this C +API. + +## Generating and Using C API Headers + +The [`qdk_sim`](..) crate has enabled the use of [`cbindgen`](https://crates.io/crates/cbindgen), such that C-language header files are generated automatically as part of the build for this crate. + +```bash +cargo build +``` + +This will generate `include/qdk_sim.h`, which can then be used from C callers: + +```c +#include +#include "qdk_sim.h" + +int main() { + uintptr_t sim_id; + uintptr_t result0, result1; + + init(2, "mixed", &sim_id); + h(sim_id, 0); + h(sim_id, 1); + + m(sim_id, 0, &result0); + m(sim_id, 1, &result1); + + printf("got %llu %llu", result0, result1); + destroy(sim_id); +} +``` + +To build and run the above example using Clang on Windows: + +```bash +$ clang docs/example.c -Iinclude -Ltarget/debug -lqdk_sim -lws2_32 -lAdvapi32 -lUserenv +got 1 1 +``` + +## Error Handling and Return Values + +Most C API functions for this crate return an integer, with `0` indicating success and any other value indicating failure. In the case that a non-zero value is returned, API functions will also set the last error message, accessible by calling [`lasterr`]: + +```c +#include +#include "qdk_sim.h" + +int main() { + uintptr_t sim_id; + uintptr_t result0, result1; + + if (init(2, "invalid", &sim_id) != 0) { + printf("Got an error message: %s", lasterr()); + } + + destroy(sim_id); +} +``` + +C API functions that need to return data to the caller, such as [`m`], do so by accepting pointers to memory where results should be stored. + +> **⚠ WARNING**: It is the caller's responsibility to ensure that pointers used to hold results are valid (that is, point to memory that can be safely written into). + +For example: + +```c +uintptr_t result; +if (m(sim_id, 0, &result) != 0) { + printf("Got an error message: %s", lasterr()); +} +printf("Got a measurement result: %llu", result); +``` + +## Initializing, Using, and Destroying Simulators + +To create a new simulator from C, use the [`init`] function. This function accepts a pointer to an unsigned integer that will be set to an ID for the new simulator: + +```c +uintptr_t sim_id; +// Initialize a new simulator with two qubits and using a mixed-state +// representation. +if (init(2, "mixed", &sim_id) != 0) { + printf("Error initializing simulator: %s", lasterr()); +} +``` + +The ID for the newly created simulator can then be used to call into functions that apply different quantum operations, such as [`x`], [`h`], or [`cnot`]: + +```c +// Apply an 𝑋 operation to qubit #0. +if (x(sim_id, 0) != 0) { + printf("Error applying X: %s", lasterr()); +} +``` + +To free the memory associated with a given simulator, use the [`destroy`] function: + +```c +if (destroy(sim_id) != 0) { + printf("Error destroying simulator: %s", lasterr()); +} +``` + +## Getting and Setting Noise Models + +Noise models for each simulator can be accessed or set by using [`get_noise_model`], [`get_noise_model_by_name`], [`set_noise_model`], and [`set_noise_model_by_name`]. Each of these functions accepts either a name of a built-in noise model (see [`crate::NoiseModel::get_by_name`] for details). + +Noise models in the C API are represented by strings containing JSON serializations of the [`crate::NoiseModel`] data model. For example: + +```c +#include +#include "qdk_sim.h" + +int main() { + const char *noise_model; + + if (get_noise_model_by_name("ideal", &noise_model) != 0) { + printf("Error getting noise model: %s", lasterr()); + } + + printf("Noise model:\n%s", noise_model); +} + +``` + +Running the above results in the JSON representation of the ideal noise model being written to the console: + +```text +Noise model: +{"initial_state":{"n_qubits":1,"data":{"Mixed":{"v":1,"dim":[2,2],"data":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0]]}}},"i":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[1.0,0.0]]}}},"x":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[0.0,0.0],[1.0,0.0],[1.0,0.0],[0.0,0.0]]}}},"y":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[0.0,0.0],[0.0,1.0],[-0.0,-1.0],[0.0,0.0]]}}},"z":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[-1.0,-0.0]]}}},"h":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[0.7071067811865476,0.0],[0.7071067811865476,0.0],[0.7071067811865476,0.0],[-0.7071067811865476,-0.0]]}}},"s":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,1.0]]}}},"s_adj":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1.0,-0.0],[0.0,-0.0],[0.0,-0.0],[0.0,-1.0]]}}},"t":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.7071067811865476,0.7071067811865476]]}}},"t_adj":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1.0,-0.0],[0.0,-0.0],[0.0,-0.0],[0.7071067811865476,-0.7071067811865476]]}}},"cnot":{"n_qubits":2,"data":{"Unitary":{"v":1,"dim":[4,4],"data":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[1.0,0.0],[0.0,0.0],[0.0,0.0],[1.0,0.0],[0.0,0.0]]}}},"z_meas":{"Effects":[{"n_qubits":1,"data":{"KrausDecomposition":{"v":1,"dim":[1,2,2],"data":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0]]}}},{"n_qubits":1,"data":{"KrausDecomposition":{"v":1,"dim":[1,2,2],"data":[[0.0,0.0],[0.0,0.0],[0.0,0.0],[1.0,0.0]]}}}]}} +``` + +See [noise model serialization](crate#noise-model-serialization) for more details. diff --git a/src/Simulation/qdk_sim_rs/docs/python-api.md b/src/Simulation/qdk_sim_rs/docs/python-api.md new file mode 100644 index 00000000000..1950730920c --- /dev/null +++ b/src/Simulation/qdk_sim_rs/docs/python-api.md @@ -0,0 +1,45 @@ +# Using Experimental Simulators from Python + +This module exposes the various data structures from this crate as Python objects, useful for embedding in Python programs. + +Note that this module contains Python-specific functionality, and cannot be used directly from Rust. + +> **ⓘ NOTE**: The Python API for this crate allows direct and low-level access to simulation data structures. This is distinct from using Python to run Q# programs on the experimental simulators implemented by this library. For details on how to use Python and Q# together with experimental simulators, please see documentation on the repository. + +## Building the Python API + +This crate automatically builds a Python extension module when compiled using the `python` Cargo feature. This module can be built into an installable Python package by using the `pip` command: + +```bash +# Install the qdk_sim crate as a Python package. +pip install . + +# Build this crate as a redistributable Python wheel, targeting the current +# host architecture. +pip wheel . +``` + +In both cases, `pip` will automatically discover and use your Rust toolchain to call `cargo build` and include its output in the Python package built from this crate. + +## Importing and working with the Python API + +Once installed using the above steps, the Python interface into the experimental simulators can be accessed as `import qdk_sim`. Doing so gives access to Python representations of different data structures in this crate. For example: + +```python +>>> import qdk_sim +>>> noise_model = qdk_sim.NoiseModel.get_by_name("ideal_stabilizer") +>>> noise_model + +``` + +Many Python objects implemented by this crate offer `as_json` methods that can be used to convert experimental simulator data structures to built-in Python objects: + +```python +>>> import json +>>> json.loads(qdk_sim.NoiseModel.get_by_name("ideal_stabilizer").as_json()) +{'initial_state': {'n_qubits': 1, 'data': {'Stabilizer': {'n_qubits': 1, 'table': {'v': 1, 'dim': [2, 3], 'data': [True, False, False, False, True, False]}}}}, 'i': {'n_qubits': 1, 'data': {'Sequence': []}}, 'x': {'n_qubits': 1, 'data': {'ChpDecomposition': [{'Hadamard': 0}, {'Phase': 0}, {'Phase': 0}, {'Hadamard': 0}]}}, 'y': {'n_qubits': 1, 'data': {'ChpDecomposition': [{'AdjointPhase': 0}, {'Hadamard': 0}, {'Phase': 0}, {'Phase': 0}, {'Hadamard': 0}, {'Phase': 0}]}}, 'z': {'n_qubits': 1, 'data': {'ChpDecomposition': [{'Phase': 0}, {'Phase': 0}]}}, 'h': {'n_qubits': 1, 'data': {'ChpDecomposition': [{'Hadamard': +0}]}}, 's': {'n_qubits': 1, 'data': {'ChpDecomposition': [{'Phase': 0}]}}, 's_adj': {'n_qubits': 1, 'data': {'ChpDecomposition': [{'AdjointPhase': 0}]}}, 't': {'n_qubits': 1, 'data': 'Unsupported'}, 't_adj': {'n_qubits': 1, 'data': 'Unsupported'}, 'cnot': {'n_qubits': 1, 'data': {'ChpDecomposition': [{'Cnot': [0, 1]}]}}, 'z_meas': {'ZMeasurement': {'pr_readout_error': 0.0}}} +``` diff --git a/src/Simulation/qdk_sim_rs/inject-version.csx b/src/Simulation/qdk_sim_rs/inject-version.csx new file mode 100644 index 00000000000..ec71338f066 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/inject-version.csx @@ -0,0 +1,39 @@ +#r "nuget: System.CommandLine, 2.0.0-beta1.21216.1" +#r "nuget: Tommy, 2.0.0" + +using System.CommandLine; +using System.Linq; +using System.CommandLine.Invocation; +using Tommy; + +// Create a root command with some options +var rootCommand = new RootCommand +{ + new Option( + "--template", + description: "A file to use as the template for cargo manifest."), + new Option( + "--out-path", + description: "Path to write the generated manifest to."), + new Option( + "--version", + description: "The version number to inject.") +}; + +// Note that the parameters of the handler method are matched according to the names of the options +rootCommand.Handler = CommandHandler.Create((template, outPath, version) => +{ + Console.Out.WriteLine($"Injecting version {version} into {template} and writing to {outPath}."); + using var reader = new StreamReader(File.OpenRead(template.FullName)); + var table = TOML.Parse(reader); + + // Set the version number in the table. + table["package"]["version"] = version; + + using var writer = new StreamWriter(File.OpenWrite(outPath)); + table.WriteTo(writer); + // Remember to flush the data if needed! + writer.Flush(); +}); + +await rootCommand.InvokeAsync(Args.ToArray()); diff --git a/src/Simulation/qdk_sim_rs/prerequisites.ps1 b/src/Simulation/qdk_sim_rs/prerequisites.ps1 new file mode 100644 index 00000000000..494635b723e --- /dev/null +++ b/src/Simulation/qdk_sim_rs/prerequisites.ps1 @@ -0,0 +1,25 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +if (-not (Get-Command rustup -ErrorAction SilentlyContinue)) { + if ($IsWindows -or $PSVersionTable.PSEdition -eq "Desktop") { + Invoke-WebRequest "https://win.rustup.rs" -OutFile rustup-init.exe + Unblock-File rustup-init.exe; + ./rustup-init.exe -y + } elseif ($IsLinux -or $IsMacOS) { + Invoke-WebRequest "https://sh.rustup.rs" | Select-Object -ExpandProperty Content | sh -s -- -y; + } else { + Write-Error "Host operating system not recognized as being Windows, Linux, or macOS; please download Rust manually from https://rustup.rs/." + } + + if (-not (Get-Command rustup -ErrorAction SilentlyContinue)) { + Write-Error "After running rustup-init, rustup was not available. Please check logs above to see if something went wrong."; + exit -1; + } +} + +# Now that rustup is available, go on and make sure that nightly support for +# rustfmt and clippy is available. +rustup install nightly +rustup component add rustfmt clippy +rustup component add rustfmt clippy --toolchain nightly diff --git a/src/Simulation/qdk_sim_rs/pyproject.toml b/src/Simulation/qdk_sim_rs/pyproject.toml new file mode 100644 index 00000000000..8619d8784c5 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools", "wheel", "setuptools-rust"] +build-backend = "setuptools.build_meta" diff --git a/src/Simulation/qdk_sim_rs/qdk_sim_experimental/__init__.py b/src/Simulation/qdk_sim_rs/qdk_sim_experimental/__init__.py new file mode 100644 index 00000000000..53262d7bb3a --- /dev/null +++ b/src/Simulation/qdk_sim_rs/qdk_sim_experimental/__init__.py @@ -0,0 +1,53 @@ +#!/bin/env python +# -*- coding: utf-8 -*- +## +# __init__.py: Root for the qdk_sim_experimental package. +## +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +## + +## IMPORTS ## + +from typing import Dict, List, Union +from enum import Enum +import enum +import json + +import qdk_sim_experimental._qdk_sim_rs as _native +try: + import qdk_sim_experimental.version as _version +except ImportError: + # This could happen if setup.py was not run. + _version = None + +## EXPORTS ## + +__all__ = [ + "Tableau", "NoiseModel", "Instrument", "State", "Process", + "Pauli" +] + +# Re-export native classes. +from qdk_sim_experimental._qdk_sim_rs import ( + Tableau, NoiseModel, Instrument, State, Process +) + +class Pauli(enum.Enum): + I = 0 + X = 1 + Y = 3 + Z = 2 + +## BUILD METADATA ## + +# Re-export the autogenerated version. +__version__ = getattr(_version, "__version__", "") +_is_conda = getattr(_version, "_is_conda", False) + +def build_info() -> Dict[str, Union[List[str], str]]: + """ + Returns information about the environment in which this + module was built. + """ + return json.loads(_native.build_info_json()) diff --git a/src/Simulation/qdk_sim_rs/qdk_sim_experimental/version.py b/src/Simulation/qdk_sim_rs/qdk_sim_experimental/version.py new file mode 100644 index 00000000000..d15c19d5933 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/qdk_sim_experimental/version.py @@ -0,0 +1,9 @@ +# Auto-generated file, do not edit. +## +# version.py: Specifies the version of the qsharp package. +## +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +## +__version__ = "0.0.0.1" +_is_conda = False diff --git a/src/Simulation/qdk_sim_rs/setup.py b/src/Simulation/qdk_sim_rs/setup.py new file mode 100644 index 00000000000..6d3fad30592 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/setup.py @@ -0,0 +1,47 @@ +#!/bin/env python +# -*- coding: utf-8 -*- +## +# setup.py: Installs Python integration for qdk_sim_experimental. +## +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +## + +## IMPORTS ## + +import setuptools +from setuptools_rust import Binding, RustExtension +import os + +## VERSION INFORMATION ## +# Our build process sets the PYTHON_VERSION environment variable to a version +# string that is compatible with PEP 440, and so we inherit that version number +# here and propagate that to qsharp/version.py. +# +# To make sure that local builds still work without the same environment +# variables, we'll default to 0.0.0.1 as a development version. + +version = os.environ.get('PYTHON_VERSION', '0.0.0.1') +is_conda = bool(os.environ.get('CONDA_BUILD', False)) + +with open('./qdk_sim_experimental/version.py', 'w') as f: + f.write(f'''# Auto-generated file, do not edit. +## +# version.py: Specifies the version of the qsharp package. +## +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +## +__version__ = "{version}" +_is_conda = {is_conda} +''') + +setuptools.setup( + name="qdk_sim_experimental", + version=version, + rust_extensions=[RustExtension("qdk_sim_experimental._qdk_sim_rs", binding=Binding.PyO3, features=["python"])], + packages=["qdk_sim_experimental"], + # rust extensions are not zip safe, just like C-extensions. + zip_safe=False, + include_package_data=True, +) diff --git a/src/Simulation/qdk_sim_rs/src/c_api.rs b/src/Simulation/qdk_sim_rs/src/c_api.rs new file mode 100644 index 00000000000..bf88c09065e --- /dev/null +++ b/src/Simulation/qdk_sim_rs/src/c_api.rs @@ -0,0 +1,443 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// The following two attributes include the README.md for this module when +// building docs (requires +nightly). +// See https://github.com/rust-lang/rust/issues/82768#issuecomment-803935643 +// for discussion. +#![cfg_attr(doc, feature(extended_key_value_attributes))] +#![cfg_attr(doc, cfg_attr(doc, doc = include_str!("../docs/c-api.md")))] + +use crate::{built_info, NoiseModel, Process, State}; +use lazy_static::lazy_static; +use serde_json::json; +use std::collections::HashMap; +use std::ffi::CStr; +use std::ffi::CString; +use std::os::raw::c_char; +use std::ptr; +use std::sync::Mutex; + +struct CApiState { + register_state: State, + noise_model: NoiseModel, +} + +lazy_static! { + static ref STATE: Mutex> = Mutex::new(HashMap::new()); + static ref LAST_ERROR: Mutex> = Mutex::new(None); +} + +// UTILITY FUNCTIONS // + +/// Exposes a result to C callers by setting LAST_ERROR in the Error +/// case, and generating an appropriate error code. +fn as_capi_err Result<(), String>>(result_fn: F) -> i64 { + let result = result_fn(); + match result { + Ok(_) => 0, + Err(msg) => { + *LAST_ERROR.lock().unwrap() = Some(msg); + -1 + } + } +} + +fn apply &Process>( + sim_id: usize, + idxs: &[usize], + channel_fn: F, +) -> Result<(), String> { + let state = &mut *STATE.lock().unwrap(); + if let Some(sim_state) = state.get_mut(&sim_id) { + let channel = channel_fn(&sim_state.noise_model); + match channel.apply_to(idxs, &sim_state.register_state) { + Ok(new_state) => { + sim_state.register_state = new_state; + Ok(()) + } + Err(err) => Err(err), + } + } else { + return Err(format!("No simulator with id {}.", sim_id)); + } +} + +// C API FUNCTIONS // + +/// Returns information about how this simulator was built, serialized as a +/// JSON object. +#[no_mangle] +pub extern "C" fn get_simulator_info() -> *const c_char { + let build_info = json!({ + "name": "Microsoft.Quantum.Experimental.Simulators", + "version": built_info::PKG_VERSION, + "opt_level": built_info::OPT_LEVEL, + "features": built_info::FEATURES, + "target": built_info::TARGET + }); + CString::new(serde_json::to_string(&build_info).unwrap().as_str()) + .unwrap() + .into_raw() +} + +/// Returns the last error message raised by a call to a C function. +/// If no error message has been raised, returns a null pointer. +#[no_mangle] +pub extern "C" fn lasterr() -> *const c_char { + match &*LAST_ERROR.lock().unwrap() { + None => ptr::null(), + Some(msg) => CString::new(msg.as_str()).unwrap().into_raw(), + } +} + +/// Allocate a new simulator with a given capacity, measured in the number of +/// qubits supported by that simulator. Returns an id that can be used to refer +/// to the new simulator in future function calls. +/// +/// The initial state of the new simulator is populated using the +/// representation nominated by the `representation` argument: +/// +/// - **`pure`**: Creates the simulator with an initial state represented by +/// a state vector. +/// - **`mixed`**: Creates the simulator with an initial state represented by +/// a density operat. +/// - **`stabilizer`**: Creates the simulator with an initial state represented by +/// a stabilizer tableau. +/// +/// # Safety +/// The caller is responsible for: +/// +/// - Ensuring that `sim_id_out` points to valid +/// memory, and that the lifetime of this pointer extends at least for the +/// duration of this call. +/// - Ensuring that `representation` is a valid pointer to a null-terminated +/// string of Unicode characters, encoded as UTF-8. +#[no_mangle] +pub unsafe extern "C" fn init( + initial_capacity: usize, + representation: *const c_char, + sim_id_out: *mut usize, +) -> i64 { + as_capi_err(|| { + if representation.is_null() { + return Err("init called with null pointer for representation".to_string()); + } + let representation = CStr::from_ptr(representation) + .to_str() + .map_err(|e| format!("UTF-8 error decoding representation argument: {}", e))?; + + let state = &mut *STATE.lock().unwrap(); + let id = 1 + state.keys().fold(std::usize::MIN, |a, b| a.max(*b)); + state.insert( + id, + CApiState { + register_state: match representation { + "mixed" => State::new_mixed(initial_capacity), + "pure" => State::new_pure(initial_capacity), + "stabilizer" => State::new_stabilizer(initial_capacity), + _ => { + return Err(format!( + "Unknown initial state representation {}.", + representation + )) + } + }, + noise_model: NoiseModel::ideal(), + }, + ); + *sim_id_out = id; + Ok(()) + }) +} + +/// Deallocates the simulator with the given id, releasing any resources owned +/// by that simulator. +#[no_mangle] +pub extern "C" fn destroy(sim_id: usize) -> i64 { + as_capi_err(|| { + let state = &mut *STATE.lock().unwrap(); + if state.contains_key(&sim_id) { + state.remove(&sim_id); + Ok(()) + } else { + Err(format!("No simulator with id {} exists.", sim_id)) + } + }) +} + +// TODO[code quality]: refactor the following several functions into a macro. + +/// Applies the `X` operation acting on a given qubit to a given simulator, +/// using the currently set noise model. +#[no_mangle] +pub extern "C" fn x(sim_id: usize, idx: usize) -> i64 { + as_capi_err(|| apply(sim_id, &[idx], |model| &model.x)) +} + +/// Applies the `Y` operation acting on a given qubit to a given simulator, +/// using the currently set noise model. +#[no_mangle] +pub extern "C" fn y(sim_id: usize, idx: usize) -> i64 { + as_capi_err(|| apply(sim_id, &[idx], |model| &model.y)) +} + +/// Applies the `Z` operation acting on a given qubit to a given simulator, +/// using the currently set noise model. +#[no_mangle] +pub extern "C" fn z(sim_id: usize, idx: usize) -> i64 { + as_capi_err(|| apply(sim_id, &[idx], |model| &model.z)) +} + +/// Applies the `H` operation acting on a given qubit to a given simulator, +/// using the currently set noise model. +#[no_mangle] +pub extern "C" fn h(sim_id: usize, idx: usize) -> i64 { + as_capi_err(|| apply(sim_id, &[idx], |model| &model.h)) +} + +/// Applies the `S` operation acting on a given qubit to a given simulator, +/// using the currently set noise model. +#[no_mangle] +pub fn s(sim_id: usize, idx: usize) -> i64 { + as_capi_err(|| apply(sim_id, &[idx], |model| &model.s)) +} + +/// Applies the `Adjoint S` operation acting on a given qubit to a given +/// simulator, using the currently set noise model. +#[no_mangle] +pub fn s_adj(sim_id: usize, idx: usize) -> i64 { + as_capi_err(|| apply(sim_id, &[idx], |model| &model.s_adj)) +} + +/// Applies the `T` operation acting on a given qubit to a given simulator, +/// using the currently set noise model. +#[no_mangle] +pub fn t(sim_id: usize, idx: usize) -> i64 { + as_capi_err(|| apply(sim_id, &[idx], |model| &model.t)) +} + +/// Applies the `Adjoint T` operation acting on a given qubit to a given simulator, +/// using the currently set noise model. +#[no_mangle] +pub fn t_adj(sim_id: usize, idx: usize) -> i64 { + as_capi_err(|| apply(sim_id, &[idx], |model| &model.t_adj)) +} + +/// Applies the `CNOT` operation acting on two given qubits to a given simulator, +/// using the currently set noise model. +#[no_mangle] +pub fn cnot(sim_id: usize, idx_control: usize, idx_target: usize) -> i64 { + as_capi_err(|| apply(sim_id, &[idx_control, idx_target], |model| &model.cnot)) +} + +/// Measures a single qubit in the $Z$-basis, returning the result by setting +/// the value at a given pointer. +/// +/// # Safety +/// This function is marked as unsafe as it is the caller's responsibility to +/// ensure that `result_out` is a valid pointer, and that the memory referenced +/// by `result_out` can be safely set. +#[no_mangle] +pub unsafe extern "C" fn m(sim_id: usize, idx: usize, result_out: *mut usize) -> i64 { + as_capi_err(|| { + let state = &mut *STATE.lock().unwrap(); + if let Some(sim_state) = state.get_mut(&sim_id) { + let instrument = &sim_state.noise_model.z_meas; + let (result, new_state) = instrument.sample(&[idx], &sim_state.register_state); + sim_state.register_state = new_state; + *result_out = result; + Ok(()) + } else { + Err(format!("No simulator with id {} exists.", sim_id)) + } + }) +} + +/// Gets the noise model corresponding to a particular name, serialized as a +/// string representing a JSON object. +/// +/// Currently recognized names: +/// - `ideal` +/// - `ideal_stabilizer` +/// +/// # Safety +/// As this is a C-language API, the Rust compiler cannot guarantee safety when +/// calling into this function. The caller is responsible for ensuring that: +/// +/// - `name` is a pointer to a null-terminated string of Unicode characters, +/// encoded as UTF-8, and that the pointer remains valid for the lifetime of +/// this call. +/// - `noise_model_json` is a valid pointer whose lifetime extends for the +/// duration of this function call. +/// +/// After this call completes, this function guarantees that either of the two +/// conditions below holds: +/// +/// - The return value is negative, in which case calling `lasterr` will return +/// an actionable error message, or +/// - The return value is `0`, and `*noise_model_json` is a valid pointer to a +/// null-terminated string of Unicode characters, encoded as UTF-8. In this +/// case, the caller is considered to own the memory allocated for this +/// string. +#[no_mangle] +pub extern "C" fn get_noise_model_by_name( + name: *const c_char, + noise_model_json: *mut *const c_char, +) -> i64 { + as_capi_err(|| { + let name = unsafe { CStr::from_ptr(name) } + .to_str() + .map_err(|e| format!("UTF-8 error decoding representation argument: {}", e))?; + let noise_model = NoiseModel::get_by_name(name)?; + let noise_model = CString::new(noise_model.as_json()).unwrap(); + unsafe { + *noise_model_json = noise_model.into_raw(); + } + Ok(()) + }) +} + +/// Returns the currently configured noise model for a given simulator, +/// serialized as a string representing a JSON object. +/// +/// # Safety +/// As this is a C-language API, the Rust compiler cannot guarantee safety when +/// calling into this function. The caller is responsible for ensuring that: +/// +/// - `noise_model_json` is a valid pointer whose lifetime extends for the +/// duration of this function call. +/// +/// After this call completes, this function guarantees that either of the two +/// conditions below holds: +/// +/// - The return value is negative, in which case calling `lasterr` will return +/// an actionable error message, or +/// - The return value is `0`, and `*noise_model_json` is a valid pointer to a +/// null-terminated string of Unicode characters, encoded as UTF-8. In this +/// case, the caller is considered to own the memory allocated for this +/// string. +#[no_mangle] +pub extern "C" fn get_noise_model(sim_id: usize, noise_model_json: *mut *const c_char) -> i64 { + as_capi_err(|| { + let state = &*STATE + .lock() + .map_err(|e| format!("Lock poisoning error: {}", e))?; + if let Some(sim_state) = state.get(&sim_id) { + let c_str = CString::new(sim_state.noise_model.as_json().as_str()).map_err(|e| { + format!("Null error while converting noise model to C string: {}", e) + })?; + unsafe { + *noise_model_json = c_str.into_raw(); + } + } else { + return Err(format!("No simulator with id {} exists.", sim_id)); + } + Ok(()) + }) +} + +/// Sets the noise model used by a given simulator instance, given a string +/// containing a JSON serialization of that noise model. +/// +/// # Safety +/// This function is marked as unsafe as the caller is responsible for ensuring +/// that `new_model`: +/// +/// - Is a valid pointer to a null-terminated array of C +/// characters. +/// - The pointer remains valid for at least the duration +/// of the call. +/// - No other thread may modify the memory referenced by `new_model` for at +/// least the duration of the call. +#[no_mangle] +pub unsafe extern "C" fn set_noise_model(sim_id: usize, new_model: *const c_char) -> i64 { + as_capi_err(|| { + if new_model.is_null() { + return Err("set_noise_model called with null pointer".to_string()); + } + + let c_str = CStr::from_ptr(new_model); + match c_str.to_str() { + Ok(serialized_noise_model) => match serde_json::from_str(serialized_noise_model) { + Ok(noise_model) => { + let state = &mut *STATE.lock().unwrap(); + if let Some(sim_state) = state.get_mut(&sim_id) { + sim_state.noise_model = noise_model; + Ok(()) + } else { + Err(format!("No simulator with id {} exists.", sim_id)) + } + } + Err(serialization_error) => Err(format!( + "{} error deserializing noise model at line {}, column {}.", + match serialization_error.classify() { + serde_json::error::Category::Data => "Data / schema", + serde_json::error::Category::Eof => "End-of-file", + serde_json::error::Category::Io => "I/O", + serde_json::error::Category::Syntax => "Syntax", + }, + serialization_error.line(), + serialization_error.column() + )), + }, + Err(msg) => Err(format!( + "UTF-8 error decoding serialized noise model; was valid until byte {}.", + msg.valid_up_to() + )), + } + }) +} + +/// Sets the noise model used by a given simulator instance, given a string +/// containing the name of a built-in noise model. +/// +/// # Safety +/// This function is marked as unsafe as the caller is responsible for ensuring +/// that `name`: +/// +/// - Is a valid pointer to a null-terminated array of C +/// characters. +/// - The pointer remains valid for at least the duration +/// of the call. +/// - No other thread may modify the memory referenced by `new_model` for at +/// least the duration of the call. +#[no_mangle] +pub unsafe extern "C" fn set_noise_model_by_name(sim_id: usize, name: *const c_char) -> i64 { + as_capi_err(|| { + if name.is_null() { + return Err("set_noise_model_by_name called with null pointer".to_string()); + } + + let name = CStr::from_ptr(name) + .to_str() + .map_err(|e| format!("UTF-8 error decoding name: {}", e))?; + let noise_model = NoiseModel::get_by_name(name)?; + let state = &mut *STATE.lock().unwrap(); + if let Some(sim_state) = state.get_mut(&sim_id) { + sim_state.noise_model = noise_model; + Ok(()) + } else { + Err(format!("No simulator with id {} exists.", sim_id)) + } + }) +} + +/// Returns the state of a given simulator, serialized as a JSON object. +#[no_mangle] +pub extern "C" fn get_current_state(sim_id: usize) -> *const c_char { + let state = &mut *STATE.lock().unwrap(); + if let Some(sim_state) = state.get_mut(&sim_id) { + CString::new( + serde_json::to_string(&sim_state.register_state) + .unwrap() + .as_str(), + ) + .unwrap() + // NB: into_raw implies transferring ownership to the C caller, + // and hence moves its self. + .into_raw() + } else { + ptr::null() + } +} diff --git a/src/Simulation/qdk_sim_rs/src/chp_decompositions.rs b/src/Simulation/qdk_sim_rs/src/chp_decompositions.rs new file mode 100644 index 00000000000..d896eef532f --- /dev/null +++ b/src/Simulation/qdk_sim_rs/src/chp_decompositions.rs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use serde::{Deserialize, Serialize}; + +/// Represents a single step of a decomposition of +/// a Clifford operation into CNOT, Hadamard, and phase operations. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ChpOperation { + /// The controlled-NOT operation between two qubits. + Cnot(usize, usize), + + /// The Hadamard operation. + Hadamard(usize), + + /// The phase operation, represented by the matrix + /// $$ + /// \begin{align} + /// S = \left( \begin{matrix} + /// 1 & 0 \\\\ + /// 0 & i + /// \end{matrix} \right). + /// \end{align} + /// $$ + Phase(usize), + + /// The phase operation, represented by the matrix + /// $$ + /// \begin{align} + /// S = \left( \begin{matrix} + /// 1 & 0 \\\\ + /// 0 & i + /// \end{matrix} \right). + /// \end{align} + /// $$ + AdjointPhase(usize), +} diff --git a/src/Simulation/qdk_sim_rs/src/common_matrices.rs b/src/Simulation/qdk_sim_rs/src/common_matrices.rs new file mode 100644 index 00000000000..4247b1d6234 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/src/common_matrices.rs @@ -0,0 +1,186 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Definitions for commonly used vectors and matrices, such as the Pauli +//! matrices, common Clifford operators, and elementary matrices. + +use core::f64::consts::FRAC_1_SQRT_2; +use std::convert::TryInto; + +use ndarray::{Array, Array1, Array2}; +use num_traits::{One, Zero}; + +use crate::utils::*; + +/// Returns a copy of the single-qubit identity matrix. +pub fn i() -> Array2 { + array![[ONE_C, ZERO_C], [ZERO_C, ONE_C]] +} + +/// Returns a unitary matrix representing the `X` operation. +pub fn x() -> Array2 { + array![[ZERO_C, ONE_C], [ONE_C, ZERO_C]] +} + +/// Returns a unitary matrix representing the `Y` operation. +pub fn y() -> Array2 { + array![[ZERO_C, I_C], [-I_C, ZERO_C]] +} + +/// Returns a unitary matrix representing the `Z` operation. +pub fn z() -> Array2 { + array![[ONE_C, ZERO_C], [ZERO_C, -ONE_C]] +} + +/// Returns a unitary matrix representing the single-qubit Hadamard transformation. +pub fn h() -> Array2 { + array![[ONE_C, ONE_C], [ONE_C, -ONE_C]] * FRAC_1_SQRT_2 +} + +/// Returns a unitary matrix representing the `T` operation. +pub fn t() -> Array2 { + array![ + [ONE_C, ZERO_C], + [ZERO_C, C64::new(FRAC_1_SQRT_2, FRAC_1_SQRT_2)] + ] +} + +/// Returns a unitary matrix representing the `S` operation. +pub fn s() -> Array2 { + array![[ONE_C, ZERO_C], [ZERO_C, C64::new(0.0_f64, 1.0_f64)]] +} + +/// Returns a unitary matrix representing the `CNOT` operation. +pub fn cnot() -> Array2 { + array![ + [ONE_C, ZERO_C, ZERO_C, ZERO_C], + [ZERO_C, ONE_C, ZERO_C, ZERO_C], + [ZERO_C, ZERO_C, ZERO_C, ONE_C], + [ZERO_C, ZERO_C, ONE_C, ZERO_C] + ] +} + +/// Returns an elementary vector; that is, a vector with a one at a given +/// index and zeros everywhere else. +/// +/// # Examples +/// The following are equivalent: +/// ``` +/// # #[macro_use] extern crate ndarray; +/// # use qdk_sim::common_matrices::elementary_vec; +/// let vec = elementary_vec::(2, 4); +/// ``` +/// and: +/// ``` +/// # #[macro_use] extern crate ndarray; +/// let vec = array![0i64, 0i64, 1i64, 0i64]; +/// ``` +pub fn elementary_vec(idx: usize, n: usize) -> Array1 { + Array::from_shape_fn(n, |i| if i == idx { T::one() } else { T::zero() }) +} + +/// Returns an elementary matrix; that is, a matrix with a one at a given index +/// and zeros everywhere else. +pub fn elementary_matrix( + (idx0, idx1): (usize, usize), + (n, m): (usize, usize), +) -> Array2 { + Array::from_shape_fn((n, m), |(i, j)| { + if i == idx0 && j == idx1 { + T::one() + } else { + T::zero() + } + }) +} + +/// Returns an identity matrix that acts on state vectors of registers of +/// qubits with a given size. +/// +/// # Example +/// The following snippet defines a two-qubit identity matrix: +/// ``` +/// # #[macro_use] extern crate ndarray; +/// # use qdk_sim::common_matrices::nq_eye; +/// use num_complex::Complex; +/// let eye = nq_eye(2usize); +/// assert_eq!(eye, array![ +/// [Complex::new(1f64, 0f64), Complex::new(0f64, 0f64), Complex::new(0f64, 0f64), Complex::new(0f64, 0f64)], +/// [Complex::new(0f64, 0f64), Complex::new(1f64, 0f64), Complex::new(0f64, 0f64), Complex::new(0f64, 0f64)], +/// [Complex::new(0f64, 0f64), Complex::new(0f64, 0f64), Complex::new(1f64, 0f64), Complex::new(0f64, 0f64)], +/// [Complex::new(0f64, 0f64), Complex::new(0f64, 0f64), Complex::new(0f64, 0f64), Complex::new(1f64, 0f64)], +/// ]); +/// ``` +pub fn nq_eye(nq: usize) -> Array2 { + Array2::eye(2usize.pow(nq.try_into().unwrap())) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::linalg::HasDagger; + + fn is_self_adjoint(arr: Array2) -> bool { + arr == arr.dag() + } + + fn are_equal_to_precision(actual: Array2, expected: Array2) -> bool { + // If we use assert_eq here, we'll get bitten by finite precision. + // We also can't use LAPACK, since that greatly complicates bindings, + // so we do an ad hoc implementation here. + (actual - expected).map(|x| x.norm()).sum() <= 1e-10 + } + + #[test] + fn h_is_self_adjoint() { + assert!(is_self_adjoint(h())); + } + + #[test] + fn x_is_self_adjoint() { + assert!(is_self_adjoint(x())); + } + + #[test] + fn y_is_self_adjoint() { + assert!(is_self_adjoint(y())); + } + + #[test] + fn z_is_self_adjoint() { + assert!(is_self_adjoint(z())); + } + + #[test] + fn cnot_is_self_adjoint() { + assert!(is_self_adjoint(cnot())); + } + + #[test] + fn s_squares_to_z() { + assert_eq!(s().dot(&s()), z()); + } + + #[test] + fn t_squares_to_s() { + assert!(are_equal_to_precision(t().dot(&t()), s())); + } + + #[test] + fn elementary_vec_is_correct() { + assert_eq!(elementary_vec::(2, 4), array![0i64, 0i64, 1i64, 0i64]) + } + + #[test] + fn elementary_matrix_is_correct() { + assert_eq!( + elementary_matrix::((1, 3), (4, 5)), + array![ + [0i64, 0i64, 0i64, 0i64, 0i64], + [0i64, 0i64, 0i64, 1i64, 0i64], + [0i64, 0i64, 0i64, 0i64, 0i64], + [0i64, 0i64, 0i64, 0i64, 0i64] + ] + ) + } +} diff --git a/src/Simulation/qdk_sim_rs/src/instrument.rs b/src/Simulation/qdk_sim_rs/src/instrument.rs new file mode 100644 index 00000000000..2ef4c49f1e4 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/src/instrument.rs @@ -0,0 +1,152 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::{states::StateData::Mixed, StateData}; +use crate::{Process, ProcessData, C64}; +use num_traits::{One, Zero}; +use rand::Rng; +use std::iter::Iterator; + +use crate::linalg::Trace; +use crate::State; + +use serde::{Deserialize, Serialize}; + +// TODO[design]: Instrument works pretty differently from State and Process; should +// likely refactor for consistency. + +#[derive(Serialize, Deserialize, Debug)] +/// Represents a quantum instrument; that is, a process that accepts a quantum +/// state and returns the new state of a system and classical data extracted +/// from that system. +pub enum Instrument { + /// The effects of the instrument, represented as completely positive + /// trace non-increasing (CPTNI) processes. + Effects(Vec), + + /// An instrument that measures a single qubit in the $Z$-basis, up to a + /// readout error (probability of result being flipped). + /// + /// Primarily useful when working with stabilizer states or other + /// subtheories. + ZMeasurement { + /// Probability with which a result is flipped. + pr_readout_error: f64, + }, +} + +impl Instrument { + /// Samples from this instrument, returning the measurement result and + /// the new state of the system conditioned on that measurement result. + pub fn sample(&self, idx_qubits: &[usize], state: &State) -> (usize, State) { + match self { + Instrument::Effects(ref effects) => sample_effects(effects, idx_qubits, state), + Instrument::ZMeasurement { pr_readout_error } => { + if idx_qubits.len() != 1 { + panic!("Z-basis measurement instruments only supported for single qubits."); + } + let idx_target = idx_qubits[0]; + match state.data { + StateData::Pure(_) | StateData::Mixed(_) => { + // Get the ideal Z measurement instrument, apply it, + // and then assign a readout error. + // TODO[perf]: Cache this instrument as a lazy static. + let ideal_z_meas = Instrument::Effects(vec![ + Process { + n_qubits: 1, + data: ProcessData::KrausDecomposition(array![[ + [C64::one(), C64::zero()], + [C64::zero(), C64::zero()] + ]]), + }, + Process { + n_qubits: 1, + data: ProcessData::KrausDecomposition(array![[ + [C64::zero(), C64::zero()], + [C64::zero(), C64::one()] + ]]), + }, + ]); + let (result, new_state) = ideal_z_meas.sample(idx_qubits, state); + let result = (result == 1) ^ rand::thread_rng().gen_bool(*pr_readout_error); + (if result { 1 } else { 0 }, new_state) + } + StateData::Stabilizer(ref tableau) => { + // TODO[perf]: allow instruments to sample in-place, + // reducing copying. + let mut new_tableau = tableau.clone(); + let result = new_tableau.meas_mut(idx_target) + ^ rand::thread_rng().gen_bool(*pr_readout_error); + ( + if result { 1 } else { 0 }, + State { + n_qubits: state.n_qubits, + data: StateData::Stabilizer(new_tableau), + }, + ) + } + } + } + } + } + + // TODO: Add more methods for making new instruments in convenient ways. + + /// Returns a serialization of this instrument as a JSON object. + pub fn as_json(&self) -> String { + serde_json::to_string(&self).unwrap() + } +} + +fn sample_effects(effects: &[Process], idx_qubits: &[usize], state: &State) -> (usize, State) { + let mut possible_outcomes = effects + .iter() + .enumerate() + .map(|(idx, effect)| { + let output_state = effect.apply_to(idx_qubits, state).unwrap(); + let tr = (&output_state).trace(); + (idx, output_state, tr.norm()) + }) + .collect::>(); + // TODO[perf]: Downgrade this to a debug_assert!, and configure the CI + // build to enable debug_assertions at full_validation. + assert!( + possible_outcomes.iter().any(|post_state| post_state.1.trace().norm() >= 1e-10), + "Expected output of applying instrument to be nonzero trace.\nInstrument effects:\n{:?}\n\nInput state:\n{}\n\nPostselected states:\n{:?}", + effects, state, possible_outcomes + ); + let mut rng = rand::thread_rng(); + let random_sample: f64 = rng.gen(); + for (idx, cum_pr) in possible_outcomes + .iter() + .scan(0.0f64, |acc, (_idx, _, pr)| { + *acc += *pr; + Some(*acc) + }) + .enumerate() + { + if random_sample < cum_pr { + // In order to not have to copy the output state, we need + // to be able to move it out from the vector. To do so, + // we retain only the element of the vector whose index + // is the one we want and then pop it, leaving an empty + // vector (that is, a vector that owns no data). + possible_outcomes.retain(|(i, _, _)| idx == *i); + let (_, mut output_state, tr) = possible_outcomes.pop().unwrap(); + if tr.abs() >= 1e-10 { + if let Mixed(ref rho) = output_state.data { + output_state.data = Mixed(rho * (1.0f64 / tr)); + } else { + panic!("Couldn't renormalize, expected mixed output from instrument."); + } + } + assert!( + (output_state.trace() - 1.0).norm() <= 1e-10, + "Expected output of instrument to be trace 1." + ); + return (idx, output_state); + } + } + let (idx, output_state, _) = possible_outcomes.pop().unwrap(); + (idx, output_state) +} diff --git a/src/Simulation/qdk_sim_rs/src/lib.rs b/src/Simulation/qdk_sim_rs/src/lib.rs new file mode 100644 index 00000000000..e451a567a3b --- /dev/null +++ b/src/Simulation/qdk_sim_rs/src/lib.rs @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// The following two attributes include the README.md for this crate when +// building docs (requires +nightly). +// See https://github.com/rust-lang/rust/issues/82768#issuecomment-803935643 +// for discussion. +#![cfg_attr(doc, feature(extended_key_value_attributes))] +#![cfg_attr(doc, cfg_attr(doc, doc = include_str!("../README.md")))] +// Set linting rules for documentation. We will stop the build on missing docs, +// or docs with known broken links. We only enable this when all relevant +// features are enabled, otherwise the docs build will fail on links to +// features that are disabled for the current build. +#![cfg_attr(all(doc, feature = "python"), deny(rustdoc::broken_intra_doc_links))] +#![cfg_attr(doc, deny(missing_docs))] +// This linting rule raises a warning on any documentation comments +// that are missing an `# Example` section. Currently, that raises a lot of +// warnings when building docs, but ideally we should make sure to address +// warnings going forward by adding relevant examples. +#![cfg_attr(doc, warn(missing_doc_code_examples))] + +#[macro_use(array, s)] +extern crate ndarray; + +extern crate derive_more; +extern crate serde; +use serde::{Deserialize, Serialize}; +use std::usize; + +pub mod c_api; +mod chp_decompositions; +pub mod common_matrices; +mod instrument; +pub mod linalg; +mod noise_model; +mod paulis; +mod processes; +mod states; +mod tableau; +mod utils; + +pub use crate::instrument::*; +pub use crate::noise_model::NoiseModel; +pub use crate::paulis::*; +pub use crate::processes::*; +pub use crate::states::{State, StateData}; +pub use crate::tableau::Tableau; +pub use crate::utils::*; + +// When documenting, we want to make sure to expose the Python module as +// public so that rustdoc can see its documentation. When the "python" crate +// feature is off, the module shouldn't even be private. +#[cfg(all(not(doc), feature = "python"))] +mod python; +#[cfg(all(doc, feature = "python"))] +pub mod python; + +/// Represents that a given type has a size that can be measured in terms +/// of a number of qubits (e.g.: [`State`]). +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct QubitSized { + n_qubits: usize, + data: T, +} + +impl QubitSized { + /// Returns the number of qubits that this value relates to. + pub fn get_n_qubits(&self) -> usize { + self.n_qubits + } +} + +/// Metadata about how this crate was built. +pub mod built_info { + include!(concat!(env!("OUT_DIR"), "/built.rs")); +} diff --git a/src/Simulation/qdk_sim_rs/src/linalg.rs b/src/Simulation/qdk_sim_rs/src/linalg.rs new file mode 100644 index 00000000000..98e41a02f8c --- /dev/null +++ b/src/Simulation/qdk_sim_rs/src/linalg.rs @@ -0,0 +1,250 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Provides common linear algebra functions and traits. + +use ndarray::{Array, Array1, Array2, ArrayView1, ArrayView2}; +use num_traits::Zero; +use std::{convert::TryInto, ops::Mul}; + +use crate::{common_matrices::nq_eye, C64}; + +/// Represents types that have hermitian conjugates (e.g.: $A^\dagger$ for +/// a matrix $A$ is defined as the complex conjugate transpose of $A$, +/// $(A^\dagger)\_{ij} = A\_{ji}^*$). +pub trait HasDagger { + /// The type of the hermitian conjugate. + type Output; + + /// Returns the hermitian conjugate (colloquially, the dagger) of a + /// borrowed reference as a new copy. + /// + /// For most types implementing this trait, the hermitian conjugate + /// is represented by the conjugate transpose. + fn dag(&self) -> Self::Output; +} + +impl HasDagger for Array2 { + type Output = Self; + + fn dag(&self) -> Self { + self.t().map(|element| element.conj()) + } +} + +impl HasDagger for ArrayView2<'_, C64> { + type Output = Array2; + + fn dag(&self) -> Self::Output { + self.t().map(|element| element.conj()) + } +} + +/// Represent types that can be conjugated by 2-dimensional arrays; that is, +/// as $UXU^{\dagger}$. +pub trait ConjBy { + /// Conjugates this value by a given matrix, returning a copy. + fn conjugate_by(&self, op: &ArrayView2) -> Self; +} + +impl ConjBy for Array2 { + fn conjugate_by(&self, op: &ArrayView2) -> Self { + op.dot(self).dot(&op.dag()) + } +} + +/// The tensor product operator ($\otimes$). +pub trait Tensor { + /// The resulting type after applying the tensor product. + type Output; + + /// Performs the tensor product. + /// + /// # Example + /// ``` + /// // TODO + /// ``` + fn tensor(self, rhs: Rhs) -> Self::Output; +} + +impl, T: Copy + Mul> Tensor for ArrayView1<'_, T> { + type Output = Array1; + fn tensor(self, other: Other) -> Self::Output { + let other: Self = other.into(); + let unflat = Array::from_shape_fn((self.shape()[0], other.shape()[0]), |(i, j)| { + self[(i)] * other[(j)] + }); + unflat + .into_shape(self.shape()[0] * other.shape()[0]) + .unwrap() + } +} + +impl, T: Copy + Mul> Tensor for &Array1 { + type Output = Array1; + + fn tensor(self, other: Other) -> Self::Output { + let other: Self = other.into(); + self.view().tensor(other) + } +} + +impl, T: Copy + Mul> Tensor for ArrayView2<'_, T> { + type Output = Array2; + fn tensor(self, other: Other) -> Self::Output { + let other: Self = other.into(); + let unflat = Array::from_shape_fn( + ( + self.shape()[0], + other.shape()[0], + self.shape()[1], + other.shape()[1], + ), + |(i, j, k, l)| self[(i, k)] * other[(j, l)], + ); + unflat + .into_shape(( + self.shape()[0] * other.shape()[0], + self.shape()[1] * other.shape()[1], + )) + .unwrap() + } +} + +impl, T: Copy + Mul> Tensor for &Array2 { + type Output = Array2; + + fn tensor(self, other: Other) -> Self::Output { + let other: Self = other.into(); + self.view().tensor(other).to_owned() + } +} + +/// Represents types for which the trace can be computed. +pub trait Trace { + /// The type returned by the trace. + type Output; + + /// The trace (typically, the sum of the eigenvalues, + /// or the sum of the diagonal elements $\sum_i A_{ii}$). + /// + /// # Example + /// ``` + /// // TODO + /// ``` + fn trace(self) -> Self::Output; +} + +impl Trace for Array2 { + type Output = T; + + fn trace(self) -> Self::Output { + self.diag().sum() + } +} + +impl Trace for &Array2 { + type Output = T; + + fn trace(self) -> Self::Output { + self.diag().sum() + } +} + +// FIXME: modify to Result<..., String> so that errors can propagate to the C API. +// FIXME[perf]: This function is significantly slower than would be expected +// from microbenchmarks on tensor and nq_eye directly. +/// Given an array representing an operator acting on single-qubit states, +/// returns a new operator that acts on $n$-qubit states. +pub fn extend_one_to_n(data: ArrayView2, idx_qubit: usize, n_qubits: usize) -> Array2 { + let n_left = idx_qubit; + let n_right = n_qubits - idx_qubit - 1; + match (n_left, n_right) { + (0, _) => { + let right_eye = nq_eye(n_right); + data.view().tensor(&right_eye) + } + (_, 0) => { + let left_eye = Array2::eye(2usize.pow(n_left.try_into().unwrap())); + left_eye.view().tensor(&data) + } + (_, _) => { + let eye = nq_eye(n_right); + let right = data.view().tensor(&eye); + nq_eye(n_left).view().tensor(&right) + } + } +} + +/// Given a view of an array representing a matrix acting on two-qubit states, +/// extends that array to act on $n$ qubits. +pub fn extend_two_to_n( + data: ArrayView2, + idx_qubit1: usize, + idx_qubit2: usize, + n_qubits: usize, +) -> Array2 { + // TODO: double check that data is 4x4. + let mut permutation = Array::from((0..n_qubits).collect::>()); + match (idx_qubit1, idx_qubit2) { + (1, 0) => permutation.swap(0, 1), + (_, 0) => { + permutation.swap(1, idx_qubit2); + permutation.swap(1, idx_qubit1); + } + _ => { + permutation.swap(1, idx_qubit2); + permutation.swap(0, idx_qubit1); + } + }; + + // TODO: there is almost definitely a more elegant way to write this. + if n_qubits == 2 { + // TODO[perf]: Eliminate the to_owned here by weakening permute_mtx. + permute_mtx(&data.to_owned(), &permutation.to_vec()[..]) + } else { + permute_mtx( + &data.view().tensor(&nq_eye(n_qubits - 2)), + &permutation.to_vec()[..], + ) + } +} + +/// Given a two-index array (i.e.: a matrix) of dimensions 2^n × 2^n for some +/// n, permutes the left and right indices of the matrix. +/// Used to represent, for example, swapping qubits in a register. +pub fn permute_mtx(data: &Array2, new_order: &[usize]) -> Array2 { + // Check that data is square. + let (n_rows, n_cols) = (data.shape()[0], data.shape()[1]); + assert_eq!(n_rows, n_cols); + + // Check that dims are 2^n and find n. + let n_qubits = (n_rows as f64).log2().floor() as usize; + assert_eq!(n_rows, 2usize.pow(n_qubits.try_into().unwrap())); + + // Check that the length of new_order is the same as the number of qubits. + assert_eq!(n_qubits, new_order.len()); + + // FIXME: there has to be a better way to make a vector that consists of + // 2n copies of 2usize. + let new_dims: Vec = vec![2usize] + .iter() + .cycle() + .take(2 * n_qubits) + .copied() + .collect(); + // FIXME: make this a result and propagate the result out to the return. + let tensor = data.clone().into_shared().reshape(new_dims); + let mut permutation = new_order.to_vec(); + permutation.extend(new_order.to_vec().iter().map(|idx| idx + n_qubits)); + let permuted = tensor.permuted_axes(permutation); + + // Finish by collapsing back down. + permuted.reshape([n_rows, n_rows]).into_owned() +} + +/// Returns a new array of the same type and shape as a given array, but +/// containing only zeros. +pub fn zeros_like(data: &Array) -> Array { + Array::zeros(data.dim()) +} diff --git a/src/Simulation/qdk_sim_rs/src/noise_model.rs b/src/Simulation/qdk_sim_rs/src/noise_model.rs new file mode 100644 index 00000000000..50fdd79e12a --- /dev/null +++ b/src/Simulation/qdk_sim_rs/src/noise_model.rs @@ -0,0 +1,252 @@ +use crate::chp_decompositions::ChpOperation; +use crate::common_matrices; +use crate::instrument::Instrument; +use crate::linalg::HasDagger; +use crate::processes::Process; +use crate::processes::{ + ProcessData, + ProcessData::{KrausDecomposition, Unitary}, +}; +use crate::states::State; +use crate::states::StateData::Mixed; +use crate::StateData; +use crate::Tableau; +use crate::C64; +use num_traits::{One, Zero}; + +use serde::{Deserialize, Serialize}; + +/// A description of the noise that applies to the state of a quantum system +/// as the result of applying operations. +#[derive(Serialize, Deserialize, Debug)] +pub struct NoiseModel { + /// The initial state that freshly allocated qubits start off in. + pub initial_state: State, + + /// The process that applies to the state of a simulator + /// when the `I` operation is called. + pub i: Process, + + /// The process that applies to the state of a simulator + /// when the `X` operation is called. + pub x: Process, + + /// The process that applies to the state of a simulator + /// when the `Y` operation is called. + pub y: Process, + + /// The process that applies to the state of a simulator + /// when the `Z` operation is called. + pub z: Process, + + /// The process that applies to the state of a simulator + /// when the `H` operation is called. + pub h: Process, + + /// The process that applies to the state of a simulator + /// when the `S` operation is called. + pub s: Process, + + /// The process that applies to the state of a simulator + /// when the `Adjoint S` operation is called. + pub s_adj: Process, + + /// The process that applies to the state of a simulator + /// when the `T` operation is called. + pub t: Process, + + /// The process that applies to the state of a simulator + /// when the `Adjoint T` operation is called. + pub t_adj: Process, + + /// The process that applies to the state of a simulator + /// when the `CNOT` operation is called. + pub cnot: Process, + + /// The instrument that is used to the measure the state of a simulator + /// in the $Z$-basis. + pub z_meas: Instrument, +} + +impl NoiseModel { + /// Returns a serialization of this noise model as a JSON object. + pub fn as_json(&self) -> String { + serde_json::to_string(&self).unwrap() + } + + /// Given the name of a built-in noise model, returns either that noise + /// model if it exists, or an [`Err`] variant if no such model exists. + /// + /// Currently accepted noise model names: + /// + /// - `"ideal"`: A full-state noise model in which all operations are + /// ideal (that is, without errors). + /// - `"ideal_stabilizer"`: A noise model in which all operations except + /// [`NoiseModel::t`] and [`NoiseModel::t_adj`] are ideal, and + /// represented in a form compatible with stabilizer simulation. + /// + /// # Example + /// ``` + /// # use qdk_sim::NoiseModel; + /// let noise_model = NoiseModel::get_by_name("ideal"); + /// ``` + pub fn get_by_name(name: &str) -> Result { + match name { + "ideal" => Ok(NoiseModel::ideal()), + "ideal_stabilizer" => Ok(NoiseModel::ideal_stabilizer()), + _ => Err(format!("Unrecognized noise model name {}.", name)), + } + } + + /// Returns a copy of the ideal noise model; that is, a noise model + /// describing the case in which no noise acts on the quantum system. + pub fn ideal() -> NoiseModel { + let i = Process { + n_qubits: 1, + data: Unitary(common_matrices::i()), + }; + let z = Process { + n_qubits: 1, + data: Unitary(common_matrices::z()), + }; + let z_meas = Instrument::Effects(vec![ + Process { + n_qubits: 1, + data: KrausDecomposition(array![[ + [C64::one(), C64::zero()], + [C64::zero(), C64::zero()] + ]]), + }, + Process { + n_qubits: 1, + data: KrausDecomposition(array![[ + [C64::zero(), C64::zero()], + [C64::zero(), C64::one()] + ]]), + }, + ]); + NoiseModel { + initial_state: State { + n_qubits: 1, + data: Mixed((common_matrices::i() + common_matrices::z()) / 2.0), + }, + i, + x: Process { + n_qubits: 1, + data: Unitary(common_matrices::x()), + }, + y: Process { + n_qubits: 1, + data: Unitary(common_matrices::y()), + }, + z, + h: Process { + n_qubits: 1, + data: Unitary(common_matrices::h()), + }, + t: Process { + n_qubits: 1, + data: Unitary(common_matrices::t()), + }, + t_adj: Process { + n_qubits: 1, + data: Unitary(common_matrices::t().dag()), + }, + s: Process { + n_qubits: 1, + data: Unitary(common_matrices::s()), + }, + s_adj: Process { + n_qubits: 1, + data: Unitary(common_matrices::s().dag()), + }, + cnot: Process { + n_qubits: 2, + data: Unitary(common_matrices::cnot()), + }, + z_meas, + } + } + + /// Returns a copy of the ideal noise model suitable for use with + /// stabilizer simulation; that is, a noise model + /// describing the case in which no noise acts on the quantum system, and + /// in which all channels can be represented by CHP decompositions. + pub fn ideal_stabilizer() -> NoiseModel { + NoiseModel { + initial_state: State { + n_qubits: 1, + data: StateData::Stabilizer(Tableau::new(1)), + }, + i: Process { + n_qubits: 1, + data: ProcessData::Sequence(vec![]), + }, + x: Process { + n_qubits: 1, + data: ProcessData::ChpDecomposition(vec![ + ChpOperation::Hadamard(0), + ChpOperation::Phase(0), + ChpOperation::Phase(0), + ChpOperation::Hadamard(0), + ]), + }, + y: Process { + n_qubits: 1, + data: ProcessData::ChpDecomposition(vec![ + ChpOperation::AdjointPhase(0), + ChpOperation::Hadamard(0), + ChpOperation::Phase(0), + ChpOperation::Phase(0), + ChpOperation::Hadamard(0), + ChpOperation::Phase(0), + ]), + }, + z: Process { + n_qubits: 1, + data: ProcessData::ChpDecomposition(vec![ + ChpOperation::Phase(0), + ChpOperation::Phase(0), + ]), + }, + h: Process { + n_qubits: 1, + data: ProcessData::ChpDecomposition(vec![ChpOperation::Hadamard(0)]), + }, + s: Process { + n_qubits: 1, + data: ProcessData::ChpDecomposition(vec![ChpOperation::Phase(0)]), + }, + s_adj: Process { + n_qubits: 1, + data: ProcessData::ChpDecomposition(vec![ChpOperation::AdjointPhase(0)]), + }, + t: Process { + n_qubits: 1, + data: ProcessData::Unsupported, + }, + t_adj: Process { + n_qubits: 1, + data: ProcessData::Unsupported, + }, + cnot: Process { + n_qubits: 2, + data: ProcessData::ChpDecomposition(vec![ChpOperation::Cnot(0, 1)]), + }, + z_meas: Instrument::ZMeasurement { + pr_readout_error: 0.0, + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn can_serialize_noise_model() { + let noise_model = NoiseModel::ideal(); + let _json = serde_json::to_string(&noise_model); + } +} diff --git a/src/Simulation/qdk_sim_rs/src/paulis.rs b/src/Simulation/qdk_sim_rs/src/paulis.rs new file mode 100644 index 00000000000..a5eec88965c --- /dev/null +++ b/src/Simulation/qdk_sim_rs/src/paulis.rs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use ndarray::Array2; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use serde::{Deserialize, Serialize}; + +use crate::{common_matrices, linalg::Tensor, C64}; + +/// An element of the single-qubit Pauli group. +#[derive(Debug, Copy, Clone, Serialize, Deserialize, IntoPrimitive, TryFromPrimitive)] +#[repr(u8)] +pub enum Pauli { + /// The identity operator. + I = 0, + /// The Pauli $X$ operator. + X = 1, + /// The Pauli $Y$ operator. + Y = 3, + /// The Pauli $Z$ operator. + Z = 2, +} + +/// Types that can be converted to unitary matrices. +pub trait AsUnitary { + /// Returns a representation as a unitary matrix. + fn as_unitary(&self) -> Array2; +} + +impl AsUnitary for Pauli { + fn as_unitary(&self) -> Array2 { + match self { + Pauli::I => common_matrices::nq_eye(1), + Pauli::X => common_matrices::x(), + Pauli::Y => common_matrices::y(), + Pauli::Z => common_matrices::z(), + } + } +} + +impl AsUnitary for Vec { + fn as_unitary(&self) -> Array2 { + let sq_unitaries = self.iter().map(|p| p.as_unitary()); + let result = sq_unitaries.reduce(|p, q| p.tensor(&q)); + result.unwrap() + } +} diff --git a/src/Simulation/qdk_sim_rs/src/processes/apply.rs b/src/Simulation/qdk_sim_rs/src/processes/apply.rs new file mode 100644 index 00000000000..21de1e77060 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/src/processes/apply.rs @@ -0,0 +1,240 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Public implementations and crate-private functions for applying processes +//! in each different representation. + +use itertools::Itertools; +use ndarray::{Array, Array2, Array3, ArrayView2, Axis}; +use rand::{distributions::WeightedIndex, prelude::Distribution, thread_rng}; + +use crate::{ + chp_decompositions::ChpOperation, linalg::ConjBy, log, log_as_err, Pauli, Process, + ProcessData::*, State, StateData::*, Tableau, C64, +}; + +use super::promote_pauli_channel; + +impl Process { + /// Applies this process to a quantum register with a given + /// state, returning the new state of that register. + pub fn apply(&self, state: &State) -> Result { + if state.n_qubits != self.n_qubits { + return Err(format!( + "Channel acts on {} qubits, but was applied to {}-qubit state.", + self.n_qubits, state.n_qubits + )); + } + + match &self.data { + Unitary(u) => apply_unitary(&u, state), + KrausDecomposition(ks) => apply_kraus_decomposition(&ks, state), + MixedPauli(paulis) => apply_pauli_channel(&paulis, state), + Sequence(processes) => { + // TODO[perf]: eliminate the extraneous clone here. + let mut acc_state = state.clone(); + for process in processes { + acc_state = process.apply(state)?; + } + Ok(acc_state) + } + ChpDecomposition(_operations) => todo!(), + Unsupported => Err("Unsupported quantum process.".to_string()), + } + } + + /// Applies this process to the given qubits in a register with a given + /// state, returning the new state of that register. + pub fn apply_to(&self, idx_qubits: &[usize], state: &State) -> Result { + // If we have a sequence, we can apply each in turn and exit early. + if let Sequence(channels) = &self.data { + // TODO[perf]: eliminate the extraneous clone here. + let mut acc_state = state.clone(); + for channel in channels { + acc_state = channel.apply_to(idx_qubits, &acc_state)?; + } + return Ok(acc_state); + } + + // Fail if there's not enough qubits. + if state.n_qubits < self.n_qubits { + return log_as_err(format!( + "Channel acts on {} qubits, but a state on only {} qubits was given.\n\nChannel:\n{:?}\n\nState:\n{:?}", + self.n_qubits, state.n_qubits, self, state + )); + } + + // Fail if any indices are repeated. + if idx_qubits.iter().unique().count() < idx_qubits.len() { + return log_as_err(format!( + "List of qubit indices {:?} contained repeated elements.", + idx_qubits + )); + } + + // Make sure that there are only as many indices as qubits that this + // channel acts upon. + if idx_qubits.len() != self.n_qubits { + return log_as_err(format!( + "Qubit indices were specified as {:?}, but this channel only acts on {} qubits.", + idx_qubits, self.n_qubits + )); + } + + // At this point we know that idx_qubits has self.n_qubits many unique + // indices, such that we can meaningfully apply the channel to the + // qubits described by idx_qubits. + // + // To do so in general, we can proceed to make a new channel + // that expands this channel to act on the full register and then use + // the ordinary apply method. + // + // In some cases, however, we can do so more efficiently by working + // with the small channel directly, so we check for those cases first + // before falling through to the general case. + + // TODO[perf]: For larger systems, we could add another "fast path" using + // matrix multiplication kernels to avoid extending + // channels to larger Hilbert spaces. + // For smaller systems, extending channels and possibly + // caching them is likely to be more performant; need to + // tune to find crossover point. + if let ChpDecomposition(operations) = &self.data { + if let Stabilizer(tableau) = &state.data { + return apply_chp_decomposition_to(operations, state.n_qubits, idx_qubits, tableau); + } + } + + // Having tried fast paths above, we now fall back to the most general + // case. + match self.n_qubits { + 1 => { + if state.n_qubits == 1 { + self.apply(state) + } else { + self.extend_one_to_n(idx_qubits[0], state.n_qubits) + .apply(state) + } + } + // TODO[perf]: If the size of the register matches the size of the + // channel, permute rather than expanding. + 2 => self + .extend_two_to_n(idx_qubits[0], idx_qubits[1], state.n_qubits) + .apply(state), + _ => { + log(&format!( + "Expanding {}-qubit channels is not yet implemented.", + self.n_qubits + )); + unimplemented!(""); + } + } + } +} + +fn apply_chp_decomposition_to( + operations: &[ChpOperation], + n_qubits: usize, + idx_qubits: &[usize], + tableau: &Tableau, +) -> Result { + let mut new_tableau = tableau.clone(); + for operation in operations { + match *operation { + ChpOperation::Phase(idx) => new_tableau.apply_s_mut(idx_qubits[idx]), + ChpOperation::AdjointPhase(idx) => new_tableau.apply_s_adj_mut(idx_qubits[idx]), + ChpOperation::Hadamard(idx) => new_tableau.apply_h_mut(idx_qubits[idx]), + ChpOperation::Cnot(idx_control, idx_target) => { + new_tableau.apply_cnot_mut(idx_qubits[idx_control], idx_qubits[idx_target]) + } + }; + } + Ok(State { + n_qubits, + data: Stabilizer(new_tableau), + }) +} + +pub(crate) fn apply_unitary(u: &Array2, state: &State) -> Result { + Ok(State { + n_qubits: state.n_qubits, + data: match &state.data { + Pure(psi) => Pure(u.dot(psi)), + Mixed(rho) => Mixed(rho.conjugate_by(&u.into())), + Stabilizer(_tableau) => { + return Err( + "TODO: Promote stabilizer state to state vector and recurse.".to_string(), + ) + } + }, + }) +} + +pub(crate) fn apply_kraus_decomposition(ks: &Array3, state: &State) -> Result { + Ok(State { + n_qubits: state.n_qubits, + data: match &state.data { + Pure(psi) => { + // We can't apply a channel with more than one Kraus operator (Choi rank > 1) to a + // pure state directly, so if the Choi rank is bigger than 1, promote to + // Mixed and recurse. + if ks.shape()[0] == 1 { + Pure({ + let k: ArrayView2 = ks.slice(s![0, .., ..]); + k.dot(psi) + }) + } else { + apply_kraus_decomposition(ks, &state.to_mixed())?.data + } + } + Mixed(rho) => Mixed({ + let mut sum: Array2 = Array::zeros((rho.shape()[0], rho.shape()[1])); + for k in ks.axis_iter(Axis(0)) { + sum = sum + rho.conjugate_by(&k); + } + sum + }), + Stabilizer(_tableau) => { + return Err( + "TODO: Promote stabilizer state to state vector and recurse.".to_string(), + ) + } + }, + }) +} + +pub(crate) fn apply_pauli_channel( + paulis: &[(f64, Vec)], + state: &State, +) -> Result { + Ok(State { + n_qubits: state.n_qubits, + data: match &state.data { + Pure(_) | Mixed(_) => { + // Promote and recurse. + let promoted = promote_pauli_channel(paulis); + return promoted.apply(state); + } + Stabilizer(tableau) => { + // TODO[perf]: Introduce an apply_mut method to + // avoid extraneous cloning. + let mut new_tableau = tableau.clone(); + // Sample a Pauli and apply it. + let weighted = WeightedIndex::new(paulis.iter().map(|(pr, _)| pr)).unwrap(); + let idx = weighted.sample(&mut thread_rng()); + let pauli = &(&paulis)[idx].1; + // TODO: Consider moving the following to a method + // on Tableau itself. + for (idx_qubit, p) in pauli.iter().enumerate() { + match p { + Pauli::I => (), + Pauli::X => new_tableau.apply_x_mut(idx_qubit), + Pauli::Y => new_tableau.apply_y_mut(idx_qubit), + Pauli::Z => new_tableau.apply_z_mut(idx_qubit), + } + } + Stabilizer(new_tableau) + } + }, + }) +} diff --git a/src/Simulation/qdk_sim_rs/src/processes/mod.rs b/src/Simulation/qdk_sim_rs/src/processes/mod.rs new file mode 100644 index 00000000000..4aacc049d96 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/src/processes/mod.rs @@ -0,0 +1,422 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +mod apply; + +use crate::chp_decompositions::ChpOperation; +use crate::linalg::{extend_one_to_n, extend_two_to_n, zeros_like}; +use crate::processes::ProcessData::{KrausDecomposition, MixedPauli, Unitary}; +use crate::NoiseModel; +use crate::QubitSized; +use crate::C64; +use crate::{AsUnitary, Pauli}; +use itertools::Itertools; +use ndarray::{Array, Array2, Array3, Axis, NewAxis}; +use num_complex::Complex; +use num_traits::{One, Zero}; +use serde::{Deserialize, Serialize}; +use std::convert::TryInto; +use std::ops::Add; +use std::ops::Mul; + +/// A linear function from quantum states to quantum states. +/// +/// # Remarks +/// A process that is completely positive and trace preserving is a channel. +pub type Process = QubitSized; + +/// Data used to represent a given process. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum ProcessData { + /// Representation of a process as a mixture of Pauli operators + /// $\{(p_i, P_i)\}$ such that the channel acts as $\rho \mapsto + /// \sum_i p_i P_i \rho P_i^{\dagger}$. + MixedPauli(Vec<(f64, Vec)>), + + /// Representation of the process by an arbitrary unitary matrix. + Unitary(Array2), + + /// Representation of the process by the singular vectors of its Choi + /// representation (colloquially, the Kraus decomposition). + /// + /// The first index denotes each Kraus operator, with the second and third + /// indices representing the indices of each operator. + KrausDecomposition(Array3), // TODO: Superoperator and Choi reps. + + /// Representation of a process as a sequence of other processes. + Sequence(Vec), + + /// Representation of a Clifford operation in terms of a decomposition + /// into CNOT, Hadamard, and phase operations. + ChpDecomposition(Vec), + + /// Represents a process that is not supported by a given noise model, + /// and thus always fails when applied. + Unsupported, +} + +impl Process { + /// Returns a new Pauli channel, given a mixture of Pauli operators. + pub fn new_pauli_channel(data: T) -> Self { + let data = data.into_pauli_mixture(); + // How many qubits? + // TODO: check that every Pauli is supported on the same number of + // qubits. + let n_qubits = data[0].1.len(); + Process { + n_qubits, + data: MixedPauli(data), + } + } + // TODO: methods to forcibly convert representations. + + /// Returns a serialization of this quantum process as a JSON object. + pub fn as_json(&self) -> String { + serde_json::to_string(&self).unwrap() + } + + /// Returns a copy of this process that applies to registers of a given + /// size. + pub fn extend_one_to_n(&self, idx_qubit: usize, n_qubits: usize) -> Process { + assert_eq!(self.n_qubits, 1); + Process { + n_qubits, + data: match &self.data { + Unitary(u) => Unitary(extend_one_to_n(u.view(), idx_qubit, n_qubits)), + KrausDecomposition(ks) => { + let new_dim = 2usize.pow(n_qubits.try_into().unwrap()); + let n_kraus = ks.shape()[0]; + let mut extended: Array3 = Array::zeros((n_kraus, new_dim, new_dim)); + for (idx_kraus, kraus) in ks.axis_iter(Axis(0)).enumerate() { + let mut target = extended.index_axis_mut(Axis(0), idx_kraus); + let big_kraus = extend_one_to_n(kraus.view(), idx_qubit, n_qubits); + target.assign(&big_kraus); + } + KrausDecomposition(extended) + }, + MixedPauli(paulis) => MixedPauli( + paulis.iter() + .map(|(pr, pauli)| { + if pauli.len() != 1 { + panic!("Pauli channel acts on more than one qubit, cannot extend to 𝑛 qubits."); + } + let p = pauli[0]; + let mut extended = std::iter::repeat(Pauli::I).take(n_qubits).collect_vec(); + extended[idx_qubit] = p; + (*pr, extended) + }) + .collect_vec() + ), + ProcessData::Unsupported => ProcessData::Unsupported, + ProcessData::Sequence(processes) => ProcessData::Sequence( + processes.iter().map(|p| p.extend_one_to_n(idx_qubit, n_qubits)).collect() + ), + ProcessData::ChpDecomposition(_) => todo!(), + }, + } + } + + /// Returns a copy of this process that applies to registers of a given + /// size. + pub fn extend_two_to_n( + &self, + idx_qubit1: usize, + idx_qubit2: usize, + n_qubits: usize, + ) -> Process { + assert_eq!(self.n_qubits, 2); + Process { + n_qubits, + data: match &self.data { + Unitary(u) => Unitary(extend_two_to_n(u.view(), idx_qubit1, idx_qubit2, n_qubits)), + KrausDecomposition(ks) => { + // TODO: consolidate with extend_one_to_n, above. + let new_dim = 2usize.pow(n_qubits.try_into().unwrap()); + let n_kraus = ks.shape()[0]; + let mut extended: Array3 = Array::zeros((n_kraus, new_dim, new_dim)); + for (idx_kraus, kraus) in ks.axis_iter(Axis(0)).enumerate() { + let mut target = extended.index_axis_mut(Axis(0), idx_kraus); + let big_kraus = extend_two_to_n(kraus, idx_qubit1, idx_qubit2, n_qubits); + target.assign(&big_kraus); + } + KrausDecomposition(extended) + }, + MixedPauli(paulis) => MixedPauli( + paulis.iter() + .map(|(pr, pauli)| { + if pauli.len() != 2 { + panic!("Pauli channel acts on more than one qubit, cannot extend to 𝑛 qubits."); + } + let p = (pauli[0], pauli[1]); + let mut extended = std::iter::repeat(Pauli::I).take(n_qubits).collect_vec(); + extended[idx_qubit1] = p.0; + extended[idx_qubit2] = p.1; + (*pr, extended) + }) + .collect_vec() + ), + ProcessData::Unsupported => ProcessData::Unsupported, + ProcessData::Sequence(processes) => ProcessData::Sequence( + processes.iter().map(|p| p.extend_two_to_n(idx_qubit1, idx_qubit2, n_qubits)).collect() + ), + ProcessData::ChpDecomposition(_) => todo!(), + }, + } + } +} + +impl Mul<&Process> for C64 { + type Output = Process; + + fn mul(self, channel: &Process) -> Self::Output { + Process { + n_qubits: channel.n_qubits, + data: match &channel.data { + // Note that we need to multiply by the square root in + // both cases, since these representations are both in terms + // of linear operators, but the multiplication is on + // superoperators (two copies of the original vectorspace). + Unitary(u) => KrausDecomposition({ + let mut ks = Array3::::zeros((1, u.shape()[0], u.shape()[1])); + ks.index_axis_mut(Axis(0), 0).assign(&(self.sqrt() * u)); + ks + }), + KrausDecomposition(ks) => KrausDecomposition(self.sqrt() * ks), + MixedPauli(paulis) => (self * promote_pauli_channel(paulis)).data, + ProcessData::Unsupported => ProcessData::Unsupported, + ProcessData::Sequence(processes) => { + ProcessData::Sequence(processes.iter().map(|p| self * p).collect()) + } + ProcessData::ChpDecomposition(_) => todo!(), + }, + } + } +} + +impl Mul for C64 { + type Output = Process; + fn mul(self, channel: Process) -> Self::Output { + self * (&channel) + } +} + +impl Mul<&Process> for f64 { + type Output = Process; + fn mul(self, chanel: &Process) -> Self::Output { + C64::new(self, 0f64) * chanel + } +} + +impl Mul for f64 { + type Output = Process; + fn mul(self, channel: Process) -> Self::Output { + self * (&channel) + } +} + +// Base case: both channels that we're composing are borrowed. +impl Mul<&Process> for &Process { + type Output = Process; + + fn mul(self, rhs: &Process) -> Self::Output { + assert_eq!(self.n_qubits, rhs.n_qubits); + Process { + n_qubits: self.n_qubits, + data: match (&self.data, &rhs.data) { + (Unitary(u), Unitary(v)) => Unitary(u.dot(v)), + (Unitary(u), KrausDecomposition(ks)) => { + // post-multiply each kraus operator by u. + let mut post = zeros_like(ks); + for (idx_kraus, kraus) in ks.axis_iter(Axis(0)).enumerate() { + post.index_axis_mut(Axis(0), idx_kraus) + .assign(&u.dot(&kraus)); + } + KrausDecomposition(post) + } + // TODO: product of two kraus decompositions would be... not + // fun. + _ => todo!(), + }, + } + } +} + +impl Add<&Process> for &Process { + type Output = Process; + + fn add(self, rhs: &Process) -> Self::Output { + assert_eq!(self.n_qubits, rhs.n_qubits); + Process { + n_qubits: self.n_qubits, + data: match (&self.data, &rhs.data) { + (KrausDecomposition(ks1), KrausDecomposition(ks2)) => { + let mut sum = Array::zeros([ + ks1.shape()[0] + ks2.shape()[0], + ks1.shape()[1], + ks1.shape()[2], + ]); + for (idx_kraus, kraus) in ks1.axis_iter(Axis(0)).enumerate() { + sum.index_axis_mut(Axis(0), idx_kraus).assign(&kraus); + } + for (idx_kraus, kraus) in ks2.axis_iter(Axis(0)).enumerate() { + sum.index_axis_mut(Axis(0), ks1.shape()[0] + idx_kraus) + .assign(&kraus); + } + KrausDecomposition(sum) + } + _ => todo!(), + }, + } + } +} + +impl Mul for &Process { + type Output = Process; + + fn mul(self, rhs: Process) -> Self::Output { + self * &rhs + } +} + +impl Mul<&Process> for Process { + type Output = Process; + + fn mul(self, rhs: &Process) -> Self::Output { + &self * rhs + } +} + +impl Mul for Process { + type Output = Process; + + fn mul(self, rhs: Process) -> Self::Output { + &self * &rhs + } +} + +impl Add for &Process { + type Output = Process; + + fn add(self, rhs: Process) -> Self::Output { + self + &rhs + } +} + +impl Add<&Process> for Process { + type Output = Process; + + fn add(self, rhs: &Process) -> Self::Output { + &self + rhs + } +} + +impl Add for Process { + type Output = Process; + + fn add(self, rhs: Process) -> Self::Output { + &self + &rhs + } +} + +/// Returns a copy of a depolarizing channel of a given strength (that is, a +/// channel representing relaxation to the maximally mixed state). +pub fn depolarizing_channel(p: f64) -> Process { + let ideal = NoiseModel::ideal(); + (1.0 - p) * ideal.i + p / 3.0 * ideal.x + p / 3.0 * ideal.y + p / 3.0 * ideal.z +} + +/// Returns a copy of an amplitude damping channel of a given strength (that is, +/// a channel representing relaxation to the $\ket{0}$ state in a +/// characteristic time given by $1 / \gamma$). +pub fn amplitude_damping_channel(gamma: f64) -> Process { + Process { + n_qubits: 1, + data: KrausDecomposition(array![ + [ + [C64::one(), C64::zero()], + [C64::zero(), C64::one() * (1.0 - gamma).sqrt()] + ], + [ + [C64::zero(), C64::one() * gamma.sqrt()], + [C64::zero(), C64::zero()] + ] + ]), + } +} + +/// A type that can be converted into a mixture of Pauli operators. +pub trait IntoPauliMixture { + /// Convert this value into a mixture of multi-qubit Pauli operators. + fn into_pauli_mixture(self) -> Vec<(f64, Vec)>; +} + +impl IntoPauliMixture for Vec<(f64, Vec)> { + fn into_pauli_mixture(self) -> Vec<(f64, Vec)> { + self + } +} + +impl IntoPauliMixture for Vec { + fn into_pauli_mixture(self) -> Vec<(f64, Vec)> { + vec![(1.0, self)] + } +} + +impl IntoPauliMixture for Vec<(f64, Pauli)> { + fn into_pauli_mixture(self) -> Vec<(f64, Vec)> { + self.iter().map(|(pr, p)| (*pr, vec![*p])).collect_vec() + } +} + +impl IntoPauliMixture for Pauli { + fn into_pauli_mixture(self) -> Vec<(f64, Vec)> { + vec![(1.0, vec![self])] + } +} + +/// Given a vector of Paulis and the probability of applying each one, promotes +/// that vector to a process that acts on state vectors (e.g.: a unitary matrix, +/// or Kraus decomposition). +/// +/// This function is a private utility mainly used in handling the case where +/// a mixed Pauli channel is applied to a pure or mixed state. +fn promote_pauli_channel(paulis: &[(f64, Vec)]) -> Process { + // TODO: Check that there's at least one Pauli... empty vectors aren't + // supported here. + if paulis.len() == 1 { + // Just one Pauli, so can box it up into a unitary. + let (_, pauli) = &paulis[0]; + // TODO[testing]: check that pr is 1.0. + Process { + n_qubits: pauli.len(), + data: Unitary(pauli.as_unitary()), + } + } else { + // To turn a mixed Pauli channel into a Kraus decomposition, we need + // to take the square root of each probability. + let matrices = paulis + .iter() + .map(|p| { + p.1.as_unitary() + * Complex { + re: p.0.sqrt(), + im: 0.0f64, + } + }) + .map(|u| { + let x = u.slice(s![NewAxis, .., ..]); + x.to_owned() + }) + .collect_vec(); + Process { + n_qubits: paulis[0].1.len(), + data: KrausDecomposition( + ndarray::concatenate( + Axis(0), + matrices.iter().map(|u| u.view()).collect_vec().as_slice(), + ) + .unwrap(), + ), + } + } +} diff --git a/src/Simulation/qdk_sim_rs/src/python.rs b/src/Simulation/qdk_sim_rs/src/python.rs new file mode 100644 index 00000000000..c23b1a62427 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/src/python.rs @@ -0,0 +1,243 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// The following two attributes include the README.md for this module when +// building docs (requires +nightly). +// See https://github.com/rust-lang/rust/issues/82768#issuecomment-803935643 +// for discussion. +#![cfg_attr(doc, feature(extended_key_value_attributes))] +#![cfg_attr(doc, cfg_attr(doc, doc = include_str!("../docs/python-api.md")))] +// FIXME: remove when the Python module is documented. +#![allow(missing_docs)] + +use std::convert::TryFrom; + +use crate::{built_info, Instrument, NoiseModel, Pauli, Process}; +use crate::{tableau::Tableau, State}; +use pyo3::{exceptions::PyRuntimeError, prelude::*, types::PyType, PyObjectProtocol}; +use serde_json::json; + +#[pymodule] +fn _qdk_sim_rs(_py: Python, m: &PyModule) -> PyResult<()> { + /// Returns information about how this simulator was built, serialized as a + /// JSON object. + #[pyfn(m, "build_info_json")] + fn build_info_json_py(_py: Python) -> String { + // TODO[code quality]: Deduplicate this with the + // version in the c_api module. + let build_info = json!({ + "name": "Microsoft.Quantum.Experimental.Simulators", + "version": built_info::PKG_VERSION, + "opt_level": built_info::OPT_LEVEL, + "features": built_info::FEATURES, + "target": built_info::TARGET + }); + serde_json::to_string(&build_info).unwrap() + } + + #[pyfn(m, "pauli_test")] + fn pauli_test(_py: Python, p: Pauli) -> Pauli { + p + } + + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + Ok(()) +} + +// Design notes: +// +// Some important Rust concepts, such as enums, do not forward "nicely" to +// Python types. When that happens, we make new types here to wrap the pure-Rust +// equivalents. + +#[pyclass(name = "State")] +#[derive(Debug, Clone)] +pub struct PyState { + data: State, +} + +#[pyproto] +impl PyObjectProtocol for PyState { + fn __repr__(&self) -> String { + format!("", self.data) + } +} + +#[pymethods] +impl PyState { + #[staticmethod] + pub fn new_mixed(n_qubits: usize) -> Self { + Self { + data: State::new_mixed(n_qubits), + } + } + + #[staticmethod] + pub fn new_stabilizer(n_qubits: usize) -> Self { + Self { + data: State::new_stabilizer(n_qubits), + } + } + + pub fn as_json(&self) -> String { + self.data.as_json() + } +} + +#[pyclass(name = "Process")] +#[derive(Debug)] +pub struct PyProcess { + data: Process, +} + +#[pyproto] +impl PyObjectProtocol for PyProcess { + fn __repr__(&self) -> String { + format!("", self.data) + } +} + +#[pymethods] +impl PyProcess { + #[staticmethod] + pub fn new_pauli_channel(data: Vec<(f64, Vec)>) -> Self { + PyProcess { + data: Process::new_pauli_channel(data), + } + } + + pub fn as_json(&self) -> String { + self.data.as_json() + } + + pub fn apply(&self, state: PyState) -> PyResult { + let data = self + .data + .apply(&state.data) + .map_err(|e| PyErr::new::(e))?; + Ok(PyState { data }) + } + + pub fn apply_to(&self, idx_qubits: Vec, state: PyState) -> PyResult { + let data = self + .data + .apply_to(idx_qubits.as_slice(), &state.data) + .map_err(|e| PyErr::new::(e))?; + Ok(PyState { data }) + } +} + +#[pyclass(name = "Instrument")] +#[derive(Debug)] +pub struct PyInstrument { + data: Instrument, +} + +#[pyproto] +impl PyObjectProtocol for PyInstrument { + fn __repr__(&self) -> String { + format!("", self.data) + } +} + +#[pymethods] +impl PyInstrument { + pub fn sample(&self, idx_qubits: Vec, state: &PyState) -> (usize, PyState) { + let (result, data) = self.data.sample(idx_qubits.as_slice(), &state.data); + (result, PyState { data }) + } + + #[staticmethod] + #[args(pr_readout_error = "0.0")] + pub fn new_z_measurement(pr_readout_error: f64) -> PyInstrument { + PyInstrument { + data: Instrument::ZMeasurement { pr_readout_error }, + } + } + + pub fn as_json(&self) -> String { + self.data.as_json() + } +} + +#[pyclass(name = "NoiseModel")] +#[derive(Debug)] +pub struct PyNoiseModel { + data: NoiseModel, +} + +#[pyproto] +impl PyObjectProtocol for PyNoiseModel { + fn __repr__(&self) -> String { + format!("", self.data) + } +} + +#[pymethods] +impl PyNoiseModel { + #[staticmethod] + pub fn ideal() -> PyNoiseModel { + PyNoiseModel { + data: NoiseModel::ideal(), + } + } + + #[staticmethod] + pub fn ideal_stabilizer() -> PyNoiseModel { + PyNoiseModel { + data: NoiseModel::ideal_stabilizer(), + } + } + + #[staticmethod] + pub fn get_by_name(name: &str) -> PyResult { + Ok(PyNoiseModel { + data: NoiseModel::get_by_name(name) + .map_err(|e| PyErr::new::(e))?, + }) + } + + pub fn as_json(&self) -> String { + self.data.as_json() + } +} + +// See https://stackoverflow.com/q/67412827/267841 for why the following works +// to expose Pauli. If we have more enums like this, we could likewise expose +// them by using the macro from that SO question. +// +// We got through some extra hoops to make sure everything user-facing appears +// as a value of the Pauli enum on the Python side. +impl IntoPy for Pauli { + fn into_py(self, py: Python) -> PyObject { + // Import the root module containing this native extension, and find + // the enum definition there that we can call. + let root = PyModule::import(py, "qdk_sim").unwrap(); + let py_enum = root.get("Pauli").unwrap(); + let args = ((self as u8).into_py(py),); + py_enum.call1(args).unwrap().into_py(py) + } +} + +impl FromPyObject<'_> for Pauli { + fn extract(ob: &'_ PyAny) -> PyResult { + // We want to support either a primitive type that can extract to u8, + // or a value of type qdk_sim.Pauli from the root module. + Python::with_gil(|py| { + let root = PyModule::import(py, "qdk_sim_experimental").unwrap(); + let py_enum: &PyType = root.get("Pauli").unwrap().downcast().unwrap(); + let value: u8 = match py_enum.is_instance(ob) { + Ok(true) => ob.getattr("value")?, + _ => ob, + } + .extract()?; + let value = Pauli::try_from(value) + .map_err(|e| PyErr::new::(format!("{:?}", e)))?; + Ok(value) + }) + } +} diff --git a/src/Simulation/qdk_sim_rs/src/states.rs b/src/Simulation/qdk_sim_rs/src/states.rs new file mode 100644 index 00000000000..3bf69611212 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/src/states.rs @@ -0,0 +1,182 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::common_matrices; +use crate::linalg::Tensor; +use crate::linalg::Trace; +use crate::states::StateData::{Mixed, Pure, Stabilizer}; +use crate::tableau::Tableau; +use crate::QubitSized; +use crate::C64; +use core::fmt::Display; +use ndarray::{Array1, Array2, Axis}; +use num_traits::One; +use serde::{Deserialize, Serialize}; +use std::convert::TryInto; + +#[cfg(feature = "python")] +use pyo3::prelude::*; + +/// Data used to represent a given state. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum StateData { + /// A pure state, represented as a vector of complex numbers. + Pure(Array1), + + /// A mixed state, represented as a density operator. + Mixed(Array2), + + /// A stabilizer state, represented as a tableau. + Stabilizer(Tableau), +} + +/// The state of a quantum system. +pub type State = QubitSized; + +impl Display for State { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + write!( + f, + "Register state on {} qubits ({} representation)\nData:\n{}", + self.n_qubits, + match self.data { + Pure(_) => "state vector", + Mixed(_) => "density operator", + StateData::Stabilizer(_) => "stabilizer tableau", + }, + self.data + ) + } +} + +impl Display for StateData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + match self { + Pure(psi) => write!(f, "{}", psi), + Mixed(rho) => write!(f, "{}", rho), + StateData::Stabilizer(tableau) => write!(f, "{}", tableau), + } + } +} + +impl State { + /// Returns a new mixed state on a given number of qubits. + /// By convention, new mixed states start off in the "all-zeros" state, + /// $\rho = \ket{00\cdots 0}\bra{00\cdots 0}$. + pub fn new_mixed(n_qubits: usize) -> State { + let new_dim = 2usize.pow(n_qubits.try_into().unwrap()); + State { + n_qubits, + data: Mixed(common_matrices::elementary_matrix( + (0, 0), + (new_dim, new_dim), + )), + } + } + + /// Returns a new stabilizer state on a given number of qubits. + /// By convention, new stabilizer states start off in the "all-zeros" state, + /// $\left\langle Z_0, Z_1, \dots, Z_{n - 1} \right\rangle$. + pub fn new_stabilizer(n_qubits: usize) -> State { + State { + n_qubits, + data: Stabilizer(Tableau::new(n_qubits)), + } + } + + /// Returns a new pure state on a given number of qubits. + /// By convention, new pure states start off in the "all-zeros" state, + /// $\ket{\psi} = \ket{00\cdots 0}$. + pub fn new_pure(n_qubits: usize) -> State { + let new_dim = 2usize.pow(n_qubits.try_into().unwrap()); + State { + n_qubits, + data: Pure(common_matrices::elementary_vec(0, new_dim)), + } + } + + /// Returns a serialization of this quantum state as a JSON object. + pub fn as_json(&self) -> String { + serde_json::to_string(&self).unwrap() + } + + /// Extends this state to be a state on `n_qubits` additional qubits. + /// New qubits are added "to the right," e.g.: $\left|\psi\right\rangle$ + /// is extended to $\left|\psi 0\right\rangle$. + /// + /// # Example + /// + /// ``` + /// # use qdk_sim::State; + /// let rho = State::new_mixed(2); + /// assert_eq!(5, rho.extend(3).get_n_qubits()); + /// ``` + pub fn extend(&self, n_qubits: usize) -> State { + let new_dim = 2usize.pow(n_qubits.try_into().unwrap()); + State { + n_qubits: self.n_qubits + n_qubits, + data: match &self.data { + Pure(psi) => Pure(psi.tensor(&common_matrices::elementary_vec(0, new_dim))), + Mixed(rho) => Mixed(rho.tensor(&common_matrices::elementary_matrix( + (0, 0), + (new_dim, new_dim), + ))), + _ => todo!(), + }, + } + } + + /// Returns a copy of this state, represented as a mixed state. + pub fn to_mixed(&self) -> State { + State { + n_qubits: self.n_qubits, + data: match &self.data { + Mixed(rho) => Mixed(rho.clone()), + Pure(psi) => Mixed({ + // Take the outer product of psi with its complex conjugate + // by using insert_axis. + // Note that since we can't prove that this is a dim2 array, + // we can't use the HasDagger trait here yet; that's a possible + // improvement for the HasDagger trait itself. + let psi = psi.view().insert_axis(Axis(1)); + psi.t().map(|e| e.conj()) * psi + }), + _ => todo!(), + }, + } + } + + /// If the given state can be represented by a stabilizer tableau, returns + /// that tableau. + pub fn get_tableau(&self) -> Option<&Tableau> { + match self.data { + Stabilizer(ref tableau) => Some(tableau), + _ => None, + } + } +} + +impl Trace for &State { + type Output = C64; + + fn trace(self) -> Self::Output { + match &self.data { + Pure(_) | StateData::Stabilizer(_) => C64::one(), + Mixed(ref rho) => (&rho).trace(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn trace_pure_is_one() { + let pure = State { + n_qubits: 1usize, + data: Pure(common_matrices::elementary_vec(0, 2)), + }; + assert_eq!(pure.trace(), C64::one()); + } +} diff --git a/src/Simulation/qdk_sim_rs/src/tableau.rs b/src/Simulation/qdk_sim_rs/src/tableau.rs new file mode 100644 index 00000000000..ccbf5ddd98f --- /dev/null +++ b/src/Simulation/qdk_sim_rs/src/tableau.rs @@ -0,0 +1,298 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::fmt::Display; + +use crate::utils::{set_row_to_row_sum, set_vec_to_row_sum, swap_columns}; +use ndarray::{s, Array, Array1, Array2}; +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "python")] +use pyo3::{prelude::*, PyObjectProtocol}; + +#[cfg(feature = "python")] +use std::io::{Error, ErrorKind}; + +/// Represents a stabilizer group with logical dimension 1; +/// that is, a single stabilizer state expressed in terms +/// of the generators of its stabilizer group, and those +/// generators of the Pauli group that anticommute with +/// each stabilizer generator (colloquially, the destabilizers +/// of the represented state). +#[cfg_attr(feature = "python", pyclass(name = "Tableau"))] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Tableau { + // TODO[code quality]: This is redundant with the n_qubits field in + // QubitSized, such that this type should be refactored + // to only have the table itself. + n_qubits: usize, + table: Array2, +} + +impl Display for Tableau { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Stabilizer tableau on {} qubits:\n{:?}", + self.n_qubits, self.table + ) + } +} + +impl Tableau { + const fn idx_phase(&self) -> usize { + 2 * self.n_qubits + } + + /// Returns a new stabilizer tableau representing + /// the $\ket{00\cdots 0}$ state of an $n$-qubit + /// register. + pub fn new(n_qubits: usize) -> Self { + Tableau { + n_qubits, + table: Array::from_shape_fn((2 * n_qubits, 2 * n_qubits + 1), |(i, j)| i == j), + } + } + + fn idx_x(&self, idx_col: usize) -> usize { + idx_col + } + + fn idx_z(&self, idx_col: usize) -> usize { + idx_col + self.n_qubits + } + + /// Returns the determinstic result that would be obtained from measuring + /// the given qubit in the 𝑍 basis, or `None` if the result is random. + fn determinstic_result(&self, idx_target: usize) -> Option { + let determined = !self + .table + .slice(s![self.n_qubits.., idx_target]) + .iter() + .any(|b| *b); + if determined { + // What was it determined to be? + let mut vector: Array1 = Array::default(2 * self.n_qubits + 1); + for idx_destabilizer in 0..self.n_qubits { + if self.table[(idx_destabilizer, self.idx_x(idx_target))] { + set_vec_to_row_sum(&mut vector, &self.table, idx_destabilizer + self.n_qubits); + } + } + Some(vector[2 * self.n_qubits]) + } else { + None + } + } +} + +impl Tableau { + /// Asserts whether a hypothetical single-qubit $Z$-basis measurement + /// would agree with an expected result. + /// + /// If the assertion would pass, `Ok(())` is returned, otherwise an [`Err`] + /// describing the assertion failure is returned. + pub fn assert_meas(&self, idx_target: usize, expected: bool) -> Result<(), String> { + let actual = self.determinstic_result(idx_target).ok_or(format!( + "Expected {}, but measurement result would be random.", + expected + ))?; + if actual != expected { + Err(format!( + "Expected {}, but measurement result would actually be {}.", + expected, actual + )) + } else { + Ok(()) + } + } +} + +#[cfg_attr(feature = "python", pymethods)] +impl Tableau { + /// Returns a serialization of this stabilizer tableau as a JSON object. + #[cfg(feature = "python")] + pub fn as_json(&self) -> PyResult { + serde_json::to_string(self) + .map_err(|e| PyErr::from(Error::new(ErrorKind::Other, e.to_string()))) + } + + /// Applies a Hadamard operation in-place to the given qubit. + pub fn apply_h_mut(&mut self, idx_target: usize) { + let idxs = (self.idx_x(idx_target), self.idx_z(idx_target)); + swap_columns(&mut self.table, idxs); + let idx_phase = self.idx_phase(); + for idx_row in 0..2 * self.n_qubits { + let a = self.table[(idx_row, self.idx_x(idx_target))]; + let b = self.table[(idx_row, self.idx_z(idx_target))]; + self.table[(idx_row, idx_phase)] ^= a && b; + } + } + + /// Applies a phase operation ($S$) in-place to the given qubit. + pub fn apply_s_mut(&mut self, idx_target: usize) { + let idx_phase = self.idx_phase(); + for idx_row in 0..2 * self.n_qubits { + self.table[(idx_row, idx_phase)] ^= self.table[(idx_row, self.idx_x(idx_target))] + && self.table[(idx_row, self.idx_z(idx_target))]; + } + + for idx_row in 0..2 * self.n_qubits { + let idx_x_target = self.idx_x(idx_target); + let idx_z_target = self.idx_z(idx_target); + self.table[(idx_row, idx_z_target)] ^= self.table[(idx_row, idx_x_target)]; + } + } + + /// Applies a controlled-NOT operation in-place, given control and target + /// qubits. + pub fn apply_cnot_mut(&mut self, idx_control: usize, idx_target: usize) { + let idx_phase = self.idx_phase(); + for idx_row in 0..2 * self.n_qubits { + self.table[(idx_row, idx_phase)] ^= self.table[(idx_row, self.idx_x(idx_control))] + && self.table[(idx_row, self.idx_z(idx_target))] + && (self.table[(idx_row, self.idx_x(idx_target))] + ^ self.table[(idx_row, self.idx_z(idx_control))] + ^ true); + } + + for idx_row in 0..2 * self.n_qubits { + let idx_x_target = self.idx_x(idx_target); + let idx_x_control = self.idx_x(idx_control); + self.table[(idx_row, idx_x_target)] ^= self.table[(idx_row, idx_x_control)]; + } + + for idx_row in 0..2 * self.n_qubits { + let idx_z_target = self.idx_z(idx_target); + let idx_z_control = self.idx_z(idx_control); + self.table[(idx_row, idx_z_control)] ^= self.table[(idx_row, idx_z_target)]; + } + } + + /// Applies a Pauli $X$ operation in-place to the given qubit. + pub fn apply_x_mut(&mut self, idx_target: usize) { + self.apply_h_mut(idx_target); + self.apply_z_mut(idx_target); + self.apply_h_mut(idx_target); + } + + /// Applies an adjoint phase operation ($S^{\dagger}$) in-place to the + /// given qubit. + pub fn apply_s_adj_mut(&mut self, idx_target: usize) { + self.apply_s_mut(idx_target); + self.apply_s_mut(idx_target); + self.apply_s_mut(idx_target); + } + + /// Applies a Pauli $Y$ operation in-place to the given qubit. + pub fn apply_y_mut(&mut self, idx_target: usize) { + self.apply_s_adj_mut(idx_target); + self.apply_x_mut(idx_target); + self.apply_s_mut(idx_target); + } + + /// Applies a Pauli $Z$ operation in-place to the given qubit. + pub fn apply_z_mut(&mut self, idx_target: usize) { + self.apply_s_mut(idx_target); + self.apply_s_mut(idx_target); + } + + /// Applies a SWAP operation in-place between two qubits. + pub fn apply_swap_mut(&mut self, idx_1: usize, idx_2: usize) { + self.apply_cnot_mut(idx_1, idx_2); + self.apply_cnot_mut(idx_2, idx_1); + self.apply_cnot_mut(idx_1, idx_2); + } + + /// Measures a single qubit in the Pauli $Z$-basis, returning the result, + /// and updating the stabilizer tableau in-place. + pub fn meas_mut(&mut self, idx_target: usize) -> bool { + if let Some(result) = self.determinstic_result(idx_target) { + return result; + } + + // If we're still here, we know the measurement result is random; + // thus we need to pick a random result and use that to update the + // tableau. + let idx_phase = self.idx_phase(); + let result = rand::random(); + let collisions: Vec = self + .table + .slice(s![.., self.idx_x(idx_target)]) + .indexed_iter() + .filter(|(_i, b)| **b) + .map(|(i, _b)| i) + .collect(); + // Find the first stabilizer that intersects with idx_target in the X + // sector. + let idx_first: usize = self.n_qubits + + self + .table + .slice(s![self.n_qubits.., self.idx_x(idx_target)]) + .indexed_iter() + .find(|(_i, b)| **b) + .unwrap() + .0; + // Make an owned copy of the first colliding stabilizer, as we'll + // need that later. + let old_stab = self.table.slice(s![idx_first, ..]).to_owned(); + + // For all collisions other than the first stabilizer, take the row + // sum of that row with the first stabilizer. + for idx_collision in collisions.iter() { + if *idx_collision != idx_first { + set_row_to_row_sum(&mut self.table, *idx_collision, idx_first); + } + } + + // Move the old stabilizer into being a destabilizer, then make a new + // stabilizer that's constrained to agree with our random result. + self.table + .slice_mut(s![idx_first - self.n_qubits, ..]) + .assign(&old_stab); + self.table.slice_mut(s![idx_first, ..]).fill(false); + let idx_z_target = self.idx_z(idx_target); + self.table[(idx_first, idx_z_target)] = true; + self.table[(idx_first, idx_phase)] = result; + result + } +} + +// Forward the ::new method onto Python. +#[cfg(feature = "python")] +#[pymethods] +impl Tableau { + /// Returns a new stabilizer tableau representing + /// the $\ket{00\cdots 0}$ state of an $n$-qubit + /// register. + #[new] + pub fn new_py(n_qubits: usize) -> Self { + Self::new(n_qubits) + } +} + +#[cfg(feature = "python")] +#[pyproto] +impl PyObjectProtocol for Tableau { + fn __repr__(&self) -> String { + format!("<{:?}>", self) + } + + fn __str__(&self) -> String { + format!("{}", self) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn bell_pair_meas_agree() { + let mut t = Tableau::new(2); + t.apply_h_mut(0); + t.apply_cnot_mut(0, 1); + let left = t.meas_mut(0); + let right = t.meas_mut(1); + assert_eq!(left, right) + } +} diff --git a/src/Simulation/qdk_sim_rs/src/utils.rs b/src/Simulation/qdk_sim_rs/src/utils.rs new file mode 100644 index 00000000000..753ea0f5d46 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/src/utils.rs @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use ndarray::{s, Array1, Array2, ArrayView1}; +use num_complex::Complex; + +/// A complex number with two 64-bit floating point fields. +/// That is, the analogy of [`f64`] to complex values. +pub type C64 = Complex; + +/// The real unit 1, represented as a complex number with two 64-bit floating +/// point fields. +pub const ONE_C: C64 = Complex::new(1f64, 0f64); + +/// The number zero, represented as a complex number with two 64-bit floating +/// point fields. +pub const ZERO_C: C64 = Complex::new(0f64, 0f64); + +/// The imaginary unit $i$, represented as a complex number with two 64-bit +/// floating point fields. +pub const I_C: C64 = Complex::new(0f64, 1f64); + +#[cfg(feature = "web-sys-log")] +fn log_message(msg: &str) { + web_sys::console::log_1(&msg.into()); +} + +#[cfg(not(feature = "web-sys-log"))] +fn log_message(msg: &str) { + println!("{}", msg); +} + +/// Prints a message as an error, and returns it as a [`Result`]. +pub fn log_as_err(msg: String) -> Result { + log(&msg); + Err(msg) +} + +/// Prints a message as an error. +pub fn log(msg: &str) { + log_message(msg); +} + +/// Given two columns in a two-dimensional array, swaps them in-place. +pub fn swap_columns(data: &mut Array2, idxs: (usize, usize)) { + // FIXME[perf]: can be accelerated for bool by using three XORs. + // FIXME[perf]: should be possible with one tmp instead of two + let tmp_a = data.slice(s![.., idxs.0]).to_owned(); + let tmp_b = data.slice(s![.., idxs.1]).to_owned(); + data.slice_mut(s![.., idxs.0]).assign(&tmp_b); + data.slice_mut(s![.., idxs.1]).assign(&tmp_a); +} + +/// Given a one-dimensional array, updates it to be the row-sum of that vector +/// and the given row of a matrix, taking into account the phase product +/// introduced by the binary symplectic product. +pub fn set_vec_to_row_sum(data: &mut Array1, matrix: &Array2, idx_source: usize) { + // FIXME[perf]: change to ndarray broadcast op. + // NB: we use - 1 in the range here, so that we don't also iterate over phases. + for idx_col in 0..matrix.shape()[1] - 1 { + data[idx_col] ^= matrix[(idx_source, idx_col)]; + } + + let idx_phase = data.shape()[0] - 1; + data[idx_phase] = phase_product(&data.slice(s![..]), &matrix.slice(s![idx_source, ..])); +} + +/// Given a two-dimensional array, updates a row of that matrix to be the +/// row-sum of that row and the given row of another matrix, taking into +/// account the phase product introduced by the binary symplectic product. +pub fn set_row_to_row_sum(data: &mut Array2, idx_source: usize, idx_target: usize) { + // FIXME[perf]: change to ndarray broadcast op. + // NB: we use - 1 in the range here, so that we don't also iterate over phases. + for idx_col in 0..data.shape()[1] - 1 { + data[(idx_target, idx_col)] ^= data[(idx_source, idx_col)]; + } + + let idx_phase = data.shape()[1] - 1; + data[(idx_target, idx_phase)] = phase_product( + &data.slice(s![idx_target, ..]), + &data.slice(s![idx_source, ..]), + ); +} + +fn g(x1: bool, z1: bool, x2: bool, z2: bool) -> i32 { + match (x1, z1) { + (false, false) => 0, + (true, true) => (if z2 { 1 } else { 0 }) - (if x2 { 1 } else { 0 }), + (true, false) => (if z2 { 1 } else { 0 }) * (if x2 { 1 } else { -1 }), + (false, true) => (if x2 { 1 } else { 0 }) * (if z2 { 1 } else { -1 }), + } +} + +/// Given a row of a binary symplectic matrix augmented with phase information, +/// returns its $X$, $Z$, and phase parts. +pub fn split_row(row: &ArrayView1) -> (Array1, Array1, bool) { + let n_qubits = (row.shape()[0] - 1) / 2; + // FIXME[perf]: relax to_owned call here. + ( + row.slice(s![0..n_qubits]).to_owned(), + row.slice(s![n_qubits..]).to_owned(), + row[2 * n_qubits], + ) +} + +/// Returns the phase introduced by the binary symplectic product of two rows. +pub fn phase_product(row1: &ArrayView1, row2: &ArrayView1) -> bool { + let mut acc = 0i32; + let (xs1, zs1, r1) = split_row(row1); + let (xs2, zs2, r2) = split_row(row2); + + for idx_col in 0..xs1.shape()[0] { + acc += g(xs1[idx_col], zs1[idx_col], xs2[idx_col], zs2[idx_col]); + } + + ((if r1 { 2 } else { 0 }) + (if r2 { 2 } else { 0 }) + acc) % 4 == 2 +} diff --git a/src/Simulation/qdk_sim_rs/test-qdk-sim-rs.ps1 b/src/Simulation/qdk_sim_rs/test-qdk-sim-rs.ps1 new file mode 100644 index 00000000000..441e76e10ae --- /dev/null +++ b/src/Simulation/qdk_sim_rs/test-qdk-sim-rs.ps1 @@ -0,0 +1,50 @@ +& (Join-Path $PSScriptRoot ".." ".." ".." "build" "set-env.ps1"); +$IsCI = "$Env:TF_BUILD" -ne "" -or "$Env:CI" -eq "true"; + +$script:allOk = $true; + +Push-Location $PSScriptRoot + # If running in CI, use cargo2junit to expose unit tests to the + # PublishTestResults task. + if ($IsCI) { + cargo install cargo2junit + $testJson = cargo +nightly test -- -Z unstable-options --format json; + $script:allOk = $script:allOk -and $LASTEXITCODE -eq 0; + + $testJson ` + | cargo2junit ` + <# We use this name to match the *_results.xml pattern that is used + to find test results in steps-wrap-up.yml. #> ` + | Out-File -FilePath opensim_results.xml -Encoding utf8NoBOM + } else { + # Outside of CI, show human-readable output. + cargo +nightly test + $script:allOk = $script:allOk -and $LASTEXITCODE -eq 0; + } + + # Run performance benchmarks as well. + cargo bench + $script:allOk = $script:allOk -and $LASTEXITCODE -eq 0; + + # This step isn't required, but we use it to upload run summaries. + $reportPath = (Join-Path "target" "criterion"); + $perfDest = (Join-Path $Env:DROPS_DIR "perf" "qdk_sim_rs"); + if (Get-Item -ErrorAction SilentlyContinue $reportPath) { + New-Item -Type Directory -Force -Path $perfDest; + Copy-Item -Recurse -Force -Path $reportPath -Destination $perfDest; + } + + # Free disk space by cleaning up. + # Note that this takes longer, but saves ~1 GB of space, which is + # exceptionally helpful in CI builds. + # When building in CI, free disk space by cleaning up. + # Note that this takes longer, but saves ~1 GB of space. + if ($IsCI) { + cargo clean; + } +Pop-Location + +if (-not $script:allOk) { + Write-Host "##vso[task.logissue type=error;]Failed to test qdk_sim_rs — please check logs above." + exit -1; +} diff --git a/src/Simulation/qdk_sim_rs/tests/chp_simulation_tests.rs b/src/Simulation/qdk_sim_rs/tests/chp_simulation_tests.rs new file mode 100644 index 00000000000..c709d664cd3 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/tests/chp_simulation_tests.rs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use qdk_sim::{Pauli, Process, State, Tableau}; + +#[test] +fn pauli_channel_applies_correctly() -> Result<(), String> { + let x = Process::new_pauli_channel(Pauli::X); + let state = State::new_stabilizer(1); + let output_state = x.apply(&state)?; + + let tableau = output_state.get_tableau().unwrap(); + tableau.assert_meas(0, true) +} diff --git a/src/Simulation/qdk_sim_rs/tests/data/ideal-noise-model.json b/src/Simulation/qdk_sim_rs/tests/data/ideal-noise-model.json new file mode 100644 index 00000000000..33c2d3089bb --- /dev/null +++ b/src/Simulation/qdk_sim_rs/tests/data/ideal-noise-model.json @@ -0,0 +1 @@ +{"initial_state":{"n_qubits":1,"data":{"Mixed":{"v":1,"dim":[2,2],"data":[[1,0],[0,0],[0,0],[0,0]]}}},"cnot":{"n_qubits":2,"data":{"Unitary":{"v":1,"dim":[4,4],"data":[[1,0],[0,0],[0,0],[0,0],[0,0],[1,0],[0,0],[0,0],[0,0],[0,0],[0,0],[1,0],[0,0],[0,0],[1,0],[0,0]]}}},"i":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1,0],[0,0],[0,0],[1,0]]}}},"s":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1,0],[0,0],[0,0],[0,1]]}}},"s_adj":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1,-0],[0,-0],[0,-0],[0,-1]]}}},"t":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1,0],[0,0],[0,0],[0.7071067811865476,0.7071067811865476]]}}},"t_adj":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1,-0],[0,-0],[0,-0],[0.7071067811865476,-0.7071067811865476]]}}},"h":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[0.7071067811865476,0],[0.7071067811865476,0],[0.7071067811865476,0],[-0.7071067811865476,-0]]}}},"x":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[0,0],[1,0],[1,0],[0,0]]}}},"y":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[0,0],[0,1],[-0,-1],[0,0]]}}},"z":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1,0],[0,0],[0,0],[-1,-0]]}}},"z_meas":{"Effects":[{"n_qubits":1,"data":{"KrausDecomposition":{"v":1,"dim":[1,2,2],"data":[[1,0],[0,0],[0,0],[0,0]]}}},{"n_qubits":1,"data":{"KrausDecomposition":{"v":1,"dim":[1,2,2],"data":[[0,0],[0,0],[0,0],[1,0]]}}}]}} \ No newline at end of file diff --git a/src/Simulation/qdk_sim_rs/tests/serialization_tests.rs b/src/Simulation/qdk_sim_rs/tests/serialization_tests.rs new file mode 100644 index 00000000000..6253b47f2c5 --- /dev/null +++ b/src/Simulation/qdk_sim_rs/tests/serialization_tests.rs @@ -0,0 +1,23 @@ +use assert_json_diff::assert_json_eq; +use qdk_sim::NoiseModel; +use serde_json::Value; + +// Use include_str! to store test case JSON as a string into the compiled +// test executable. +static IDEAL_NOISE_MODEL_JSON: &str = include_str!("data/ideal-noise-model.json"); + +#[test] +fn ideal_noise_model_serializes_correctly() { + let noise_model = NoiseModel::ideal(); + let expected: Value = + serde_json::from_str(&*(serde_json::to_string(&noise_model).unwrap())).unwrap(); + + assert_json_eq!(noise_model, expected); +} + +#[test] +fn ideal_noise_model_deserializes_correctly() { + let actual: NoiseModel = serde_json::from_str(IDEAL_NOISE_MODEL_JSON).unwrap(); + + assert_json_eq!(actual, NoiseModel::ideal()); +}