你会学到什么
- 如何在Visual Studio中设置量子解决方案和项目
- Q#操作的组件
- 如何从C#调用Q#操作
- 如何构建和执行您的量子算法
在Q#中创建一个钟状态
现在您已经安装了Microsoft Quantum Development Kit,并了解了它的工作原理,我们来编写您的第一个量子应用程序。 我们将从最简单的程序开始,并将其构建起来以展示量子叠加和量子纠缠。 我们将从基础状态
中开始一个量子位,对其执行一些操作,然后测量结果。
该页面上的说明是为Visual Studio 2017和Visual Studio代码编写的。 如果您使用的是其他开发环境,请按照Visual Studio代码的说明使用命令行调用.NET Core SDK。
第1步:创建一个项目和解决方案
- Visual Studio 2017
- 命令行/ Visual Studio代码
打开Visual Studio 2017.转到File
菜单并选择New
> Project...
在项目模板资源管理Installed
> Visual C#
,选择Q# Application
模板。 确保您在New Project
对话框顶部的列表中选择了.NET Framework 4.6.1
。 给你的项目命名Bell
。
第2步:输入Q#代码
我们的目标是创建一个显示纠缠的贝尔状态 。 我们将逐个建立起来,以展示量子比特状态,门限和测量的概念。
您的开发环境应该打开两个文件: Driver.cs
,它将保存您的量子代码的C#驱动程序,以及Operation.qs
,它将保存量子代码本身。
第一步是将Q#文件重命名为Bell.qs
右键单击Visual Studio解决方案资源管理器中的Operation.qs
(Ctrl + Alt + L进行聚焦)或Visual Studio代码资源管理器(Ctrl /⌘+ Shift + E进行聚焦),然后选择重命名选项。 用Bell
替换Operation
并返回。
要输入Q#代码,请确保您正在编辑Bell.qs
窗口。 Bell.qs
文件应该包含以下内容:
Q#
namespace Quantum.Bell
{
open Microsoft.Quantum.Primitive;
open Microsoft.Quantum.Canon;
operation Operation () : ()
{
body
{
}
}
}
首先,将字符串Operation
替换为Set
,并更改操作参数(第一对括号)以包含desired: Result, q1: Qubit
的字符串desired: Result, q1: Qubit
。 该文件现在应该如下所示:
Q#
namespace Quantum.Bell
{
open Microsoft.Quantum.Primitive;
open Microsoft.Quantum.Canon;
operation Set (desired: Result, q1: Qubit) : ()
{
body
{
}
}
}
现在,在包围操作体的大括号之间键入以下内容:
Q#
let current = M(q1);
if (desired != current)
{
X(q1);
}
该文件现在应该如下所示:
Q#
namespace Quantum.Bell
{
open Microsoft.Quantum.Primitive;
open Microsoft.Quantum.Canon;
operation Set (desired: Result, q1: Qubit) : ()
{
body
{
let current = M(q1);
if (desired != current)
{
X(q1);
}
}
}
}
现在可以调用这个操作来设置一个已知状态的量子位( Zero
或One
)。 我们测量量子位,如果它处于我们想要的状态,我们将它放在一边,否则,我们用X
门翻转它。
这段代码定义了一个Q# 操作 。 操作是Q#中量子执行的基本单位。 它大致相当于C或C ++或Python中的函数,或者C#或Java中的静态方法。
操作的参数在括号内被指定为一个元组。 操作的返回类型是在冒号后指定的。 在这种情况下, Set
操作没有返回,所以它被标记为返回一个空元组()
。 这是F#中Q# unit
等价物,大致类似于C#中的void
。
操作具有主体部分,其中包含操作的实现。 一个操作也可以有伴随的 , 受控的和受控的伴随节。 这些用于指定适当操作的特定变体。 有关更多信息,请参阅Q#语言参考 。
在Set
操作结束后,将以下操作添加到名称空间中:
Q#
operation BellTest (count : Int, initial: Result) : (Int,Int)
{
body
{
mutable numOnes = 0;
using (qubits = Qubit[1])
{
for (test in 1..count)
{
Set (initial, qubits[0]);
let res = M (qubits[0]);
// Count the number of ones we saw:
if (res == One)
{
set numOnes = numOnes + 1;
}
}
Set(Zero, qubits[0]);
}
// Return number of times we saw a |0> and number of times we saw a |1>
return (count-numOnes, numOnes);
}
}
此操作( BellTest
)将循环进行count
迭代,在量子位上设置指定的initial
值,并测量( M
)结果。 它会收集统计我们已经测量了多少零和一些零并将它们返回给调用者。 它执行另一个必要的操作。 它在返回它之前将量子位重置为已知状态( Zero
),以允许其他人以已知状态分配该量子位。 这是using
语句所要求的。
所有这些调用都使用Microsoft.Quantum.Primitive
命名空间中定义的原始量子操作。 例如, M
操作在计算( Z
)基础上测量其变元量子位,并且X
将围绕x轴的状态翻转应用到其变元量子位。
正如你所看到的,Q#使用类似C#的分号和大括号来表示程序结构。
Q#有一个非常类似于C#的if语句。
Q#以独特的方式处理变量。 默认情况下,Q#中的变量是不可变的; 他们的价值在绑定后可能不会改变。 let
关键字用于指示不可变变量的绑定。 操作参数总是不变的。
如果你需要一个变量值可以改变的变量,例如这个例子中的numOnes
,你可以用mutable
关键字声明变量。 可变变量的值可以使用set
语句进行更改。
在这两种情况下,编译器都会推断变量的类型。 Q#不需要变量的任何类型注释。
using
语句对Q#也是特殊的。 它用于分配一个用于代码块的量子比特数组。 在Q#中,所有量子位都是动态分配和释放的,而不是在复杂算法的整个生命周期中存在的固定资源。 using
语句在开始时分配一组量子位,并在块的末尾释放这些量子位。 有一个类似的borrowing
说明用于分配潜在的肮脏的附属量子比特。
Q#中的for
循环总是迭代一个范围。 没有Q#相当于一个传统的C型电脑的声明。 范围可以由范围中的第一个和最后一个整数指定,如下例所示: 1..10
是范围1,2,3,4,5,6,7,8,9和10.如果a需要使用除+1以外的步骤,然后使用三个整数,它们之间有..
1..2..10
是范围1,3,5,7和9.请注意,范围在两端都包含在内。
BellTest
操作以元组的BellTest
返回两个值。 该操作的返回类型是(Int, Int)
,并且return
语句具有包含两个整数变量的显式元组。 Q#使用元组来传递多个值,而不是使用结构或记录。
第3步:输入C#驱动程序代码
切换到开发环境中的Driver.cs
文件。 这个文件应该有以下内容:
C#
using Microsoft.Quantum.Simulation.Core;
using Microsoft.Quantum.Simulation.Simulators;
namespace Quantum.Bell
{
class Driver
{
static void Main(string[] args)
{
}
}
}
在Main
方法中,输入以下代码:
using (var sim = new QuantumSimulator())
{
// Try initial values
Result[] initials = new Result[] { Result.Zero, Result.One };
foreach (Result initial in initials)
{
var res = BellTest.Run(sim, 1000, initial).Result;
var (numZeros, numOnes) = res;
System.Console.WriteLine(
$"Init:{initial,-4} 0s={numZeros,-4} 1s={numOnes,-4}");
}
}
System.Console.WriteLine("Press any key to continue...");
System.Console.ReadKey();
C#驱动程序有四个部分:
- 构建量子模拟器。 在这个例子中,
sim
是模拟器。 - 计算量子算法所需的任何参数。 在这个例子中,
count
固定为1000,initial
是量子比特的初始值。 运行量子算法。 每个Q#操作都会生成一个具有相同名称的C#类。 这个类有一个
Run
方法, 异步执行该操作。 执行是异步的,因为在实际硬件上的执行将是异步的。由于
Run
方法是异步的,因此我们获取Result
属性; 这会阻止执行,直到任务完成并同步返回结果。处理操作的结果。 在这个例子中,
res
会收到操作的结果。 这里的结果是由模拟器测量的零个数(numZeros
)和个数(numZeros
)的元组。 这是在C#中作为ValueTuple返回的。 我们解构元组以获得两个字段,打印结果,然后等待按键。
第4步:构建并运行
- Visual Studio 2017
- 命令行/ Visual Studio代码
只需点击F5
,你的程序就会建立并运行!
结果应该是:
Init:Zero 0s=1000 1s=0
Init:One 0s=0 1s=1000
Press any key to continue...
按下键后程序将退出。
第5步:创建叠加
现在我们想操纵这个量子位。 首先,我们将尝试翻转它。 这是通过在我们测试BellTest
之前执行一个X
门完成的:
X(qubits[0]);
let res = M (qubits[0]);
现在结果(在按下F5
)相反:
Init:Zero 0s=0 1s=1000
Init:One 0s=1000 1s=0
但是,迄今为止我们所看到的一切都是古典的。 让我们得到一个量子结果。 我们所需要做的就是用H
或Hadamard门替换上一次运行中的X
门。 我们不会一直将量子比特从0改为1,而只是将其翻转一半。 BellTest
的替换行如下所示:
H(qubits[0]);
let res = M (qubits[0]);
现在结果变得更有趣了:
Init:Zero 0s=484 1s=516
Init:One 0s=522 1s=478
每次我们测量时,我们都要求一个经典值,但量子位在0和1之间,所以我们得到(统计上)0时间的一半和时间的一半。 这被称为叠加 ,给我们第一个真正的量子态。
第6步:创建纠缠
现在我们将做出承诺的贝尔州并炫耀纠缠 。 我们需要做的第一件事是在BellTest
分配2个量子位而不是1个量子位:
using (qubits = Qubit[2]) {
在我们测量BellTest
( M
)之前,这将允许我们添加一个新的门( CNOT
):
Set (initial, qubits[0]);
Set (Zero, qubits[1]);
H(qubits[0]);
CNOT(qubits[0],qubits[1]);
let res = M (qubits[0]);
我们添加了另一个Set
操作来初始化量子位1,以确保它在我们启动时始终处于Zero
状态。
我们还需要在释放它之前重置第二个量子位(这也可以用for
循环完成)。 我们将在量子位0重置后添加一行:
Set(Zero, qubits[0]);
Set(Zero, qubits[1]);
完整的例程现在看起来像这样:
operation BellTest (count : Int, initial: Result) : (Int,Int)
{
body
{
mutable numOnes = 0;
using (qubits = Qubit[2])
{
for (test in 1..count)
{
Set (initial, qubits[0]);
Set (Zero, qubits[1]);
H(qubits[0]);
CNOT(qubits[0],qubits[1]);
let res = M (qubits[0]);
// Count the number of ones we saw:
if (res == One)
{
set numOnes = numOnes + 1;
}
}
Set(Zero, qubits[0]);
Set(Zero, qubits[1]);
}
// Return number of times we saw a |0> and number of times we saw a |1>
return (count-numOnes, numOnes);
}
}
如果我们运行这个,我们会得到与之前一样的50-50结果。 然而,我们真正感兴趣的是第二个量子位对第一个量子态的反应。 我们将在BellTest
操作的新版本中添加此统计信息:
operation BellTest (count : Int, initial: Result) : (Int,Int,Int)
{
body
{
mutable numOnes = 0;
mutable agree = 0;
using (qubits = Qubit[2])
{
for (test in 1..count)
{
Set (initial, qubits[0]);
Set (Zero, qubits[1]);
H(qubits[0]);
CNOT(qubits[0],qubits[1]);
let res = M (qubits[0]);
if (M (qubits[1]) == res)
{
set agree = agree + 1;
}
// Count the number of ones we saw:
if (res == One)
{
set numOnes = numOnes + 1;
}
}
Set(Zero, qubits[0]);
Set(Zero, qubits[1]);
}
// Return number of times we saw a |0> and number of times we saw a |1>
return (count-numOnes, numOnes, agree);
}
}
现在有一个新的返回值( agree
),它将记录每次来自第一个量子位的测量与第二个量子位的测量相匹配。 当然,我们也必须相应地更新Driver.cs文件:
using (var sim = new QuantumSimulator())
{
// Try initial values
Result[] initials = new Result[] { Result.Zero, Result.One };
foreach (Result initial in initials)
{
var res = BellTest.Run(sim, 1000, initial).Result;
var (numZeros, numOnes, agree) = res;
System.Console.WriteLine(
$"Init:{initial,-4} 0s={numZeros,-4} 1s={numOnes,-4} agree={agree,-4}");
}
}
System.Console.WriteLine("Press any key to continue...");
System.Console.ReadKey();
现在当我们跑步时,我们得到了一些非常惊人
Init:Zero 0s=499 1s=501 agree=1000
Init:One 0s=490 1s=510 agree=1000
我们对第一个量子位的统计没有改变(一个0或1的概率为50-50),但现在当我们测量第二个量子位时,它总是和第一个量子位相同。 我们的CNOT
使这两个量子纠缠在一起,所以无论发生在其中一个发生什么,发生在另一个发生。 如果你颠倒了测量(在第一个之前做了第二个量子比特),同样的事情会发生。 第一次测量是随机的,第二次测量将与第一次测量的结果保持一致。