2019 年 9 月

第 34 卷,第 9 期

[量子计算]

使用 Q# 和 Blazor 进行量子消息传递

作者:Daniel Vaughan

本文探索量子消息传递,该过程利用量子纠缠的显著现象,跨远距离即时传输半条消息,从而消除窃听风险。我将介绍如何在 Microsoft 新的量子编程语言 Q# 中实现一种用于超密集编码的量子算法;如何使用量子门纠缠量子位;以及如何将 ASCII 消息编码为量子位。然后,我将构建一个基于 Web 的 Blazor 驱动的 UI,它利用量子算法并模拟向不同接收方传递量子粒子。我将向你展示如何在 Blazor 服务器应用程序中使用 Q# 库,以及如何启动和协调多个浏览器窗口。你还将了解如何在 Blazor 应用程序中使用模型-视图-视图模型 (MVVM)。

本文使用了较多量子理论。如果你是刚开始接触量子计算,建议阅读我编写的“量子计算入门”。 你可以在 tinyurl.com/quantumprimer1 查阅第一部分。

我们先来了解一下超密集编码。

了解超密集编码

超密集编码利用了量子纠缠现象,即纠缠对中的一个粒子可能会影响两个粒子的共享状态,尽管它们可能相隔很远。

为了说明超密集编码协议的原理,现在假设有三个操作者:Alice、Bob 和 Charlie(A、B 和 C)。Charlie 创建了一对纠缠的量子位,并将一个传递给 Alice,另一个传递给 Bob。当 Alice 想要向 Bob 传递一个 2 位的消息时,她所要做的就是操纵她自己的量子位来影响两个量子位的量子态,然后将她的单个量子位传递给 Bob。然后,Bob 测量 Alice 的量子位和他自己的量子位以接收 2 位消息。

需要指出的是,无法将多个位的信息编码到单个量子位中,而传递 2 位消息只需要一个量子位进行转手即可。不管量子位之间相隔多远,纠缠的量子位的共享状态使得完整的 2 位消息只需使用其中一个量子位即可编码。

而且,当 Alice 给 Bob 传递消息时,距 Charlie 给他俩各传递一个纠缠的量子位可能已过去很长时间了。你可以把纠缠的量子位视为一种资源,将作为通信过程的一部分被使用。

将量子位进行预共享还有一个重要的安全优势:任何想要解码消息的人都需要同时拥有 Alice 和 Bob 的量子位。只截取 Alice 的量子位无法解码消息,并且由于纠缠的量子位是预先共享的,因此消除了窃听风险。

如果你对超密集编码的物理原理持怀疑态度,这并不是一件坏事,该现象已通过实验方式进行了验证 (tinyurl.com/75entanglement),甚至采用了卫星和激光 (tinyurl.com/spookyrecord)。

Q# 入门

Visual Studio 的 Q# 语言支持随 Microsoft 量子开发工具包一起提供。安装 Visual Studio 工具包的最简单方法是使用 Visual Studio 扩展管理器。

从 Visual Studio 2019 中的“扩展”菜单中,选择“管理扩展”以显示“管理扩展”对话框。在搜索框中输入“量子”以查找工具包。安装完成后,可以创建新的 Q# 项目类型,工具中启用了 Q# 语法突出显示,并会按预期方式调试 Q# 项目。

Q# 是一种过程化、特定于领域的语言,在句法上受到 C# 和 F# 的影响。Q# 文件包含操作、函数和自定义类型定义,其名称在命名空间中必须是唯一的,如图 1 所示。可使用“打开”指令,使其他命名空间中的成员可用。

图 1:函数、操作和自定义类型定义

namespace Quantum.SuperdenseCoding
{
  open Microsoft.Quantum.Diagnostics;
  open Microsoft.Quantum.Intrinsic;
  open Microsoft.Quantum.Canon;
  function SayHellow(name : String) : String
  {
    return “Hello “ + name;
  }
  operation EntangledPair(qubit1 : Qubit, qubit2 : Qubit) : Unit
    is Ctl + Adj
  {
    H(qubit1);
    CNOT(qubit1, qubit2);
  }
  newtype IntTuple = (Int, Int);
}

函数与其他过程语言中的函数或方法类似。函数接受零个或多个参数,并可以返回单个对象、值或元组。函数的每个参数都由变量名、冒号和类型构成。函数的返回值在参数列表末尾的冒号后指定。

回想一下“量子计算入门”的第 3 部分 (tinyurl.com/controlledgates),受控 U 门允许为任意量子门添加控制输入。Q# 具有用于此目的的受控关键字,支持向运算符应用控制,例如:

Controlled X([controlQubit1, controlQubit2], targetQubit)

此处,受控语句在 Pauli-X 门上放置两个控制输入。

操作与函数具有相同的特征,但操作也可以表示单式运算符(酉算子)。可将其视为复合量子门。使用操作可定义当其作为某个受控操作的一部分使用时会发生什么情况。

此外,使用操作可以显式定义操作的反向过程(通过伴随关键字)。伴随矩阵是运算符的复共轭转置。有关详细信息,请参阅 tinyurl.com/brafromket

newtype 关键字允许定义可在操作和函数中使用的自定义类型。

现在来看看一些最常见的表达式类型。

默认情况下,变量在 Q# 中是不可变的。要声明新变量,请使用 let 关键字,如下所示:

let foo = 3;

编译器根据向变量分配的值来推断变量类型。如果需要更改变量的值,请在声明变量时使用可变的关键字:

mutable foo = 0;
set foo += 1;

要在 Q# 中创建不可变的数组,请使用:

let immutableArray = [11, 21, 3, 0];

Q# 中的数组是一种值类型,在很大程度上是不可变的,即使是使用可变关键字创建的亦是如此。使用可变关键字时,可以为标识符分配另一个数组,但不能替换数组中的项:

mutable foo = new Int[4];
set foo = foo w/ 0 <- 1;

w/ 运算符是单词“with”的缩写。 在第二行,foo 中的所有值都复制到一个新数组中,数组中的第一项(索引 0)设置为 1。它具有 O(n) 运行时复杂度。前一赋值语句可以通过与等号组合更简洁地表示,如下所示:

set foo w/= 0 <- 1;

使用 w/= 运算符后,会在可能的情况下执行就地替换,将运行时复杂度降低到 O(1)。

可以使用 for 语句创建循环:

for (i in 1..10)
{
  Message($”i={i},”);
}

内置的消息函数将消息写入控制台。Q# 支持字符串插值。通过在字符串文字前加上“$”,可以将表达式放在大括号中。

要循环访问某个集合,请使用 for 语句,如下所示:

for (qubit in qubits) // Qubits is an array of Qubits
{
  H(qubit); // Apply a Hadamard gate to the Qubit
}

以上是 Q# 的简要概述。如需更详尽的介绍,请访问 tinyurl.com/qsharp。同时还建议使用 Microsoft Quantum Katas (tinyurl.com/quantumkatas)。

使用 Q# 实现超密集编码

现在来看看如何在 Q# 中实现量子超密集编码协议。假设你已经阅读了本系列的第一部分和第二部分,或者已经理解了 Dirac 符号和量子计算的基本原理。

回想一下在超密集编码的开头介绍中,Charlie 使一对量子位发生纠缠,并将一个传递给 Alice,另一个传递给 Bob。

要使一对以 |0... 状态开始的量子位发生纠缠,可以通过 Hadamard 门发送第一个量子位,该门将其状态更改为:

Hadamard 门将第一个量子位放在 |0 和 |1 的叠加中,并将其发送到 |+ 状态。代码如下:

operation CreateEntangledPair(qubit1 : Qubit, qubit2 : Qubit) : Unit
{
  H(qubit1);
  CNOT(qubit1, qubit2);
}

如果 qubit1 的状态为 |1...,则 CNOT 门反转 qubit2,从而产生 |Ф+... 状态:

量子位在被测量时的状态有 50% 的可能是 00 或 11。

创建此叠加后,如果只测量其中一个量子位,不管量子位之间的距离如何,都会导致另一个量子位的状态塌缩为相同的值。量子位据说是纠缠的。

分别向 Alice 和 Bob 发送纠缠的量子位对中的一个量子位后,Alice 就可以通过简单地操纵自己的量子位来影响这两个量子位的整体量子态。单个量子位只能编码 1 位信息。但当与另一个量子位纠缠时,该量子位可单独将 2 位的信息编码到共享的量子状态中。

Alice 能够通过让其量子位经过一个或多个门,来传输 2 位信息。这些门和由此产生的量子位对的量子态如图 2 所示。量子位上的下标标识出其所有者:A 是 Alice,B 是 Bob。

Gates Used to Produce Bell States
图 2:用于生成 Bell 状态的门

发送 00 的 2 位消息不需要更改 Alice 的量子位(“I”是标识门,不执行任何操作)。发送 01 或 10 需要单个门:分别为 X 或 Z;发送 11 需要同时应用 X 和 Z 门。

在量子力学中,唯一完全可分辨的状态是那些正交的状态。对于量子位,这些是 Bloch 球体上的垂直状态。Bell 状态恰巧是正交的,所以当 Bob 测量量子位时,他可以对两个量子位执行正交测量以得出 Alice 的原始 2 位消息。

为编码 Alice 的量子位,量子位被发送到 EncodeMessageInQubit Q# 操作,如图 3 所示。除了 Alice 的量子位,EncodeMessageInQubit 还会接受两个 Bool 值,表示要发送给 Bob 的 2 位信息。第一个 let 语句将这对位值连接成 002 到 112 之间的某个整数,其中 bit1 占据高位。生成的位序列指示要应用的一个或多个门。

图 3:EncodeMessageInQubit 操作

operation EncodeMessageInQubit(
  aliceQubit : Qubit, bit1 : Bool, bit2 : Bool) : Unit
{
  let bits = (bit1 ? 1 | 0) * 2 + (bit2 ? 1 | 0);
  if (bits == 0b00)
  {
    I(aliceQubit);
  }
  elif (bits == 0b01)
  {
    X(aliceQubit);
  }
  elif (bits == 0b10)
  {
    Z(aliceQubit);
  }
  elif (bits == 0b11)
  {
    X(aliceQubit);
    Z(aliceQubit);
  }
}

Q# 支持 C# 和 F# 中现有的许多语言功能。它包括二进制文本,这很方便。

在 Alice 对其消息进行编码并将量子位发送给 Bob 之后,Bob 收到消息并将 CNOT 门应用于量子位,然后将 Hadamard 门应用于 Alice 的量子位:

operation DecodeMessageFromQubits (bobQubit : Qubit, aliceQubit : Qubit) : (Bool, Bool)
{
  CNOT(aliceQubit, bobQubit);
  H(aliceQubit);
  return (M(aliceQubit) == One, M(bobQubit) == One);
}

然后测量每个量子位。其结果是由 Alice 发送的两个原始消息位组成的元组。每个量子位的测量值对应于由 Alice 编码的经典位消息。

现在来深入了解这种超密集编码协议的工作原理。回想一下,Alice 和 Bob 都有一个属于纠缠对的量子位。两个量子位的量子态的矩阵表示形式如下:

稍后我将使用这个矩阵结果来演示编码过程。

如果 Alice 想要将消息 012 发送给 Bob,她会将 X 门应用于自己的量子位,从而将量子态更改为:

将其转换为矩阵形式可以得到:

然后 Alice 把她的量子位发送给 Bob。

要解码消息,Bob 首先对量子位应用 CNOT 门:

现在,可以将上一步的矩阵结果乘以 CNOT 矩阵,记住标量乘数是可变的:

在对结果矩阵进行因子分解时,得到:

然后,Bob 将 Hadamard 门应用于 Alice 的量子位:

可以在 Hadamard 门和量子位的矩阵表示形式中进行替换:

将顶部和底部除以 √2,就消除了公约数,然后再执行一些矩阵算法:

可以看到,最终结果正是 Alice 发送的原始邮件!

当 Alice 通过 X 门传递其量子位时,它影响了两个量子位的整体叠加。Bob 能够通过展开叠加状态,确定 Alice 所做的更改。

分配量子位

在 Q# 中分配量子位对象时,需要将其返回到模拟器。这在语法上是强制执行的。分配量子位需要 using 语句。

要分配单个量子位,请使用量子位表达式,如下所示:

using (qubit = Qubit())
{
  Reset(qubit);
}

重置函数将量子位返回到 |0 状态。

退出 using 块时,量子位必须处于 |0状态。这是一个运行时要求,由量子模拟器强制执行。可以将量子位传递给其他操作并更改其状态等,但如果执行到达 using 块的末尾,而量子位没有返回到 |0 状态,则会引发异常。可以通过 QuantumSimulator 构造函数,禁止对非重置量子位产生异常。

也可以使用数组表达式量子位 [] 创建多个量子位对象:

using (qubits = Qubit[2])
{
  // Do something with the qubits...
  ResetAll(qubits);
}

使用数组表达式创建的量子位在完成后仍需要重置。使用内置的 ResetAll 函数重置多个量子位。

请注意,模拟的量子位使用的内存量呈指数级增长。每个分配的量子位使所需的 RAM 量翻倍。在 16GB 内存的机器的测试中,26 个量子位的内存用量达到 13GB 左右。如果超过此数量,就会开始使用虚拟内存。我达到了 31 个量子位;但内存使用量激增到 53GB。不出所料,32 个量子位引发了互操作异常。

根据你的意愿塑造 Q#

示例项目在表示 Alice、Bob 和 Charlie 的对象之间异步传递量子位。出于此原因,我需要找到一种方法来保留量子位对象,并规避使用 using 块这一 Q# 要求。

在 Visual Studio 中,Q# 工具和 C# 工具的效用非常相似。构建 Q# 项目时,该项目中的 .qs 文件将转换为 C#。Q# 工具输出其生成的扩展名为“.g.cs”的 C# 文件,并将它们放在父项目的 \obj\qsharp\src 目录中。

考虑到 Microsoft 在 Q# 编译器上的投资,我怀疑此转换步骤将来可能会被替换为直接编译到 IL 这一过程或替换为其他内容。不过,目前我发现,通过查看内容在后台的运行情况,来浏览生成的 .cs 文件是很有用的。我很快意识到,通过 C# 利用 Q# SDK 程序集可以实现我所需要的功能。

在可下载的示例代码中,QOperations 类直接调用 Q# API,这允许它从模拟器请求量子位而无需立即释放。我用 C# 来分配、纠缠和释放量子位,而不是只使用 Q# 来实现所有量子活动。

QOperations 类创建 QuantumSimulator 类的实例,后者位于 Microsoft.Quantum.Simulation.Simulators 命名空间中。QuantumSimulator 提供了一个用于操作本机模拟器组件的 API。模拟器随 Microsoft Quantum 开发工具包一起安装。

QuantumSimulator 有一个内部 IoC 容器,允许检索生成的 C# 代码常使用的各种对象。例如,在 C# 文件中,可以使用 QuantumSimulator 对象的 Get<T> 方法检索对象,用于重置量子位的状态并释放量子位,执行与 Q# 中 using 块相同的操作:

resetOperation = simulator.Get<ICallable<Qubit, QVoid>>(typeof(Reset));
releaseOperation = simulator.Get<Release>(typeof(Release));

若要分配新的量子位,请使用 Microsoft.Quantum.Internal.Allocate 对象。还需要 CNOT 和 H (Hadamard) 门对象(从容器中检索),如下所示:

allocator = simulator.Get<Allocate>(typeof(Allocate));
cnotGate = simulator.Get<IUnitary<(Qubit, Qubit)>>(typeof(CNOT));
hGate = simulator.Get<IUnitary<Qubit>>(typeof(H));

使用这些对象,可以生成纠缠的量子位对。QOperations 类的 GetEntangledPairsAsync 方法创建一个量子位元组的列表(参见图 4)。

图 4:在 C# 中创建纠缠的量子位

public Task<IList<(Qubit, Qubit)>> GetEntangledPairsAsync(int count)
{
  IList<(Qubit, Qubit)> result = new List<(Qubit, Qubit)>(count);
  for (int i = 0; i < count; i++)
  {
    var qubits = allocator.Apply(2);
    hGate.Apply(qubits[0]);
    cnotGate.Apply((qubits[0], qubits[1]));
    result.Add((qubits[0], qubits[1]));
  }
  return Task.FromResult(result);
}

使用 Allocate 对象的 Apply 方法从模拟器中检索两个空闲量子位。我将 Hadamard 门应用于第一个量子位,然后将 CNOT 应用于两个量子位,使它们处于纠缠态。无需立即重置和释放它们;我可以从方法中返回量子位对。仍然需要释放量子位,否则应用程序将很快耗尽内存。我只是推迟了这一步。

使用 ReleaseQubitsAsync 方法释放量子位,如图 5 所示。

图 5:在 C# 中释放量子位

public Task ReleaseQubitsAsync(IEnumerable<Qubit> qubits)
{
  foreach (Qubit qubit in qubits)
  {
    resetOperation.Apply(qubit);
    releaseOperation.Apply(qubit);
    /* Alternatively, we could do: */
    // simulator.QubitManager.Release(qubit);
  }
  return Task.CompletedTask;
}

对于每个提供的量子位,使用 Reset 对象的 Apply 方法重置量子位。然后,我使用 Release 对象的 Apply 方法通知模拟器量子位是空闲的。或者,可以使用模拟器的 QubitManager 属性来释放量子位。

这样,本文的量子元素就完成了。现在来了解 Blazor,以及如何用 MVVM 实现服务器端的 Blazor 项目。

配合使用 MVVM 模式与 Blazor

我一直是 XAML 的忠实粉丝。用 C# 编写视图模型逻辑,并将其与一个易于在设计器中使用且可重用的布局文件(通过数据绑定拼合而成)相结合,实现这个过程的方式我很喜欢。Blazor 支持相同的方法,但它使用 Razor 而不是 XAML,Razor 有一个简洁的语法可标记 HTML 中的动态内容。

如果你是 Blazor 新手,请根据所选的 IDE,参阅 tinyurl.com/installblazor 上的安装说明。

我首先使用通用 Windows 平台 (UWP) 实现了此项目。UWP 与 .NET Core 兼容,且使用基于 XAML 的技术快速构建 UI 是有意义的。实际上,应用程序的 UWP 版本位于可下载的示例代码中。如果不想安装 Blazor,只想看到该应用正常运行的样子,只需运行 UWP 版本即可。两个应用程序的行为是相同的。

将应用移植到 Blazor 非常简单。无需更改代码;NuGet 对 Codon (codonfx.com) MVVM 框架的引用效果良好,一切正常运行。

我为这个项目选择了 Blazor 的服务器端模型,这样可以在代表 Alice、Bob 和 Charlie 的三个视图模型中共享一个 QuantumSimulator 对象,并传递量子位对象。 

解析 Blazor 应用程序 Blazor 应用程序包含三个 Razor 页面,如图 6 所示,分别表示三个参与者:Alice.razor、Bob.razor 和 Charlie.razor。

Alice, Bob and Charlie Razor Pages
图 6:Alice、Bob 和 Charlie Razor 页面

每个页面都有一个关联的视图模型。项目子类 Codon 的 ViewModelBase 类中的全部三个视图模型,该类为 INotifyPropertyChanged (inpc) 事件、IoC 容器引用和应用程序内组件之间的松散耦合消息传递提供内置支持。

每个 razor 页面都有一个函数块,其中包含其各自视图模型的属性。下面是 Alice.razor 中的函数块:

@functions {
  AliceViewModel ViewModel { get; set; }
  protected override async Task OnInitAsync()
  {
    ViewModel = Dependency.Resolve<AliceViewModel>();
    ViewModel.PropertyChanged += (o, e) => Invoke(StateHasChanged);
  }
}

在 OnInitAsync 方法期间,从 Codon 的 IoC 容器中检索视图模型。

视图模型属性更改不会自动导致页面内容更新。为此,要订阅 ViewModelBase 类 PropertyChanged 事件。当事件发生时,将调用 razor 页面的 StateHasChanged 方法,该方法会触发对 HTML 元素的更新,这些元素以数据绑定的方式绑定到视图模型。

可以使用页面的 Invoke 方法来确保更新发生在 UI 线程上。如果从非 UI 线程调用 StateHasChanged 方法,会引发异常。

向 Charlie 请求量子位 Charlie razor 页面指示发送给 Alice 和 Bob 的量子位数。如图 7 所示,此页的函数块中有更多内容。

图 7:Charlie.razor 函数块摘录

@functions {
  bool scriptInvoked;
  ...
  protected override void OnAfterRender()
  {
    if (!scriptInvoked)
    {
      scriptInvoked = true;
      jsRuntime.InvokeAsync<object>(“eval”, evalScript);
    }
  }
  const string evalScript = @”var w1 = window.open(‘/alice’, ‘_blank’,
    ‘left=100,top=50,width=500,height=350,toolbar=0,resizable=0’);
var w2 = window.open(‘/bob’, ‘_blank’,
  ‘left=100,top=500,width=500,height=350,toolbar=0,resizable=0’);
window.addEventListener(‘beforeunload’, function (e) {
  try {
    w1.close();
  } catch (err) {}
  try {
    w2.close();
  } catch (err) {}
  (e || window.event).returnValue = null;
  return null;
});”;
}

除了视图模型初始化之外,该页面还负责为 Alice 和 Bob 打开新的浏览器窗口。它通过调用 JSRuntime.InvokeAsync 并使用 JavaScript eval 函数在浏览器中运行 JavaScript 来完成此操作。Eval 脚本会打开并将窗口保留为变量。当 Charlie 页面的 beforeunload 事件发生时,窗口关闭。这可以防止在停止调试器时累积孤立的浏览器窗口。

请注意,目前大多数浏览器在默认情况下都会阻止弹出窗口,或至少在打开弹出窗口之前会请求同意。如果发生这种情况,你需要选择“确定”来允许弹出窗口并刷新页面。我在该项目中使用弹出窗口只是为了方便同时打开所有三个参与者窗口。

Charlie.razor 页面包含一个动态元素,这是一个计数器,显示已发送的量子位的数量:

<p>Qubit sent count: @Model.QubitSentCount</p>

段落中的值绑定到视图模型的 QubitSentCount 属性。

当类从 Codon.UIModel.ViewModelBase 派生时,IMessageSubscriber<T> 的任何实现都会自动订阅 T 类型的消息。CharlieViewModel 实现 IMessageSubscriber<RequestQubitsMessage> 和 IMessageSubscriber<ReleaseQubitsMessage>;因此,当 AliceViewModel 发布 RequestQubitsMessage 或 BobViewModel 发布 ReleaseQubitsMessage 时,会在 Charlie ViewModel 类中进行处理。这就是 Charlie 的全部任务;在需要的时候,他把纠缠的量子位发送给 Alice 和 Bob。

当 CharlieViewModel 收到 RequestQubitsMessage 时,它使用 QOperations 实例来检索纠缠的量子位对的列表,如图 8 所示。

图 8:CharlieViewModel ReceiveMessageAsync (RequestQubitsMessage) 方法

async Task IMessageSubscriber<RequestQubitsMessage>.ReceiveMessageAsync(
  RequestQubitsMessage message)
{
  IList<(Qubit, Qubit)> qubits =
    await QOperations.GetEntangledPairsAsync(message.QubitCount);
  await Messenger.PublishAsync(new BobQubitMessage(qubits.Select(x => x.Item2)));
  await Messenger.PublishAsync(new AliceQubitMessage(qubits.Select(x => x.Item1)));
  QubitSentCount += message.QubitCount;
}

然后,CharlieViewModel 通过 IMessenger 将每对中的第一个发送给 Alice,将第二个发送给 Bob。

最后,QubitSentCount 增加,这会更新 Charlie 页面。

以 Alice 的身份发送消息 让我们看看 Alice 页面及其关联的视图模型。AliceViewModel 类包含 AsyncActionCommand,其定义如下:

ICommand sendCommand;
public ICommand SendCommand => sendCommand ??
         (sendCommand = new AsyncActionCommand(SendAsync));

看到如此简洁的属性编写方式,我很开心,其中 lambda 表达式与 null 合并运算符结合,只需要几行代码即可实现延迟加载。

Codon 的命令基础结构支持异步命令处理程序。通过使用 Codon 的 AsyncActionCommand,可以将异步方法 SendAsync 指定为命令处理程序。

Alice.razor 包含一个文本字段和一个按钮:

<input type=”text” bind=”@ViewModel.Message” />
<button class=”btn btn-primary”
      onclick=”@ViewModel.SendCommand.Execute”>Send to Bob</button>

输入框绑定到视图模型的 Message 属性。单击“发送给 Bob”按钮后,开始执行视图模型的 SendCommand,并调用其 SendAsync 方法(参见图 9)。

图 9:AliceViewModel.SendAsync 方法

async Task SendAsync(object arg)
{
  var bytes = Encoding.ASCII.GetBytes(Message);
  foreach (byte b in bytes)
  {
    byteQueue.Enqueue(b);
    await Messenger.PublishAsync(
      new RequestQubitsMessage(qubitsRequiredForByte));
  }
  Message = string.Empty;
}

视图模型有一个队列,其中包含经过编码并发送给 Bob 的每条消息的字节。使用 Codon 的 IMessenger 发出一条消息,并最终到达 Charlie,该消息请求将四个量子位进行纠缠并发送出去,两个发送给 Alice,两个发送给 Bob。

最后,Message 属性被重置为 string.empty,这将清除输入框内容并为接收新消息做好准备。

AliceViewModel 实现 IMessageSubscriber<AliceQubitMessage>,所以接收该类型的所有消息:

gasync Task IMessageSubscriber<AliceQubitMessage>.ReceiveMessageAsync(
  AliceQubitMessage message)
{
  foreach (var qubit in message.Qubits)
  {
    qubitQueue.Enqueue(qubit);
  }
  await DispatchItemsInQueue();
}

调用“ReceiveMessageAsync”时,有效负载包含 Charlie 的量子位实例的集合。AliceViewModel 还保留了一个量子位对象的队列,新到达的量子位会被添加到该队列中。

AliceViewModel 现在已准备好向 Bob 发送至少部分消息。调用 DispatchItemsInQueue,如图 10 所示。

图 10:AliceViewModel.DispatchItemsInQueue 方法

async Task DispatchItemsInQueue()
{
  var qOps = Dependency.Resolve<QOperations, QOperations>(true);
  while (byteQueue.Any())
  {
    if (qubitQueue.Count < qubitsRequiredForByte)
    {
      return;
    }
    IList<Qubit> qubits =
      qubitQueue.DequeueMany(qubitsRequiredForByte).ToList();
    byte b = byteQueue.Dequeue();
    BitArray bitArray = new BitArray(new[] { b });
    /* Convert classical bit pairs to single qubits. */
    for (int i = 0, j = 0; i < bitArray.Length; i += 2, j++)
    {
      await qOps.EncodeMessageInQubitAsync(
        qubits[j],
        bitArray[i],
        bitArray[i + 1]);
    }
    await Messenger.PublishAsync(new DecodeQubitsMessage(qubits));
  }
}

DispatchItemsInQueue 首先从 IoC 容器中检索 QOperations 实例。Dependency.Resolve<T,T>(bool singleton) 会导致创建一个新实例(如果还没有)。然后将该实例保留为单个实例,以便将来的请求解析同一个对象。

使用一个扩展方法让四个量子位从量子位队列中取消排队。同时让消息字节取消排队并将其放入 BitArray 中。然后,QOperations 对象负责将每个量子位放入表示 2 位消息的叠加中。请再次留意一个量子位是如何能够表示两个经典位的,因为它的状态会影响这对量子位的量子态。

然后,使用 IMessenger 发送由 Bob 最终接收的 DecodeQubits­Message 消息。

以 Bob 的身份接收消息 与 Alice 一样,Bob 有一个来自 Charlie 的量子位队列。BobViewModel 实现 IMessageSubscriber<BobQubitMessage>。当 Charlie 发送包装在 BobQubitMessage 中的量子位时,它们被放置在队列中。

BobViewModel 还实现 IMessageSubscriber<Decode­QubitsMessage>。当 Alice 向 Bob 发送消息时,消息被包装在 DecodeQubitsMessage 对象中,并在 ReceiveMessageAsync(DecodeQubitsMessage) 方法中进行处理(参见图 11)。该方法让相等数量的量子位从其队列中取消排队。使用 QOperations DecodeQubits 方法将 Alice 和 Bob 的每对量子位转换为 2 位数据。消息中的每个字节由四个 2 位对(8 位)组成,因此循环会同时在 i 和 j 上递增。

图 11:ReceiveMessageAsync(DecodeQubitsMessage) 方法

async Task IMessageSubscriber<DecodeQubitsMessage>.ReceiveMessageAsync(
  DecodeQubitsMessage message)
{
  IList<Qubit> aliceQubits = message.Qubits;
  List<Qubit> bobQubits = qubits.DequeueMany(aliceQubits.Count).ToList();
  var qOps = Dependency.Resolve<QOperations, QOperations>(true);
  var bytes = new List<byte>();
  for (int i = 0; i < bobQubits.Count; i += 4)
  {
    byte b = 0;
    for (int j = 0; j < 4; j++)
    {
      (bool aliceBit, bool bobBit) = await qOps.DecodeQubits(
        bobQubits[i + j], aliceQubits[i + j]);
      if (bobBit)
      {
        b |= (byte)(1 << (j * 2 + 1));
      }
      if (aliceBit)
      {
        b |= (byte)(1 << (j * 2));
      }
    }
    bytes.Add(b);
  }
  Message += Encoding.ASCII.GetString(bytes.ToArray());
  await Messenger.PublishAsync(new ReleaseQubitsMessage(aliceQubits));
  await Messenger.PublishAsync(new ReleaseQubitsMessage(bobQubits));
}

根据解码位的值按左移位构造每个字节。解码后的字节使用 Encoding.ASCII.GetString 方法返回字符串,并附加到页面上显示的 Message 属性中。

一旦量子位被解码,则发布一个 ReleaseQubitsMessage 以释放量子位,并由 CharlieViewModel 接收。然后,使用 QOperations 释放量子位:

async Task IMessageSubscriber<ReleaseQubitsMessage>.ReceiveMessageAsync(
  ReleaseQubitsMessage message)
{
  await QOperations.ReleaseQubitsAsync(message.Qubits);
}

总结

在本文中,我使用 Q# 实现了一种用于超密集编码的量子算法,Q# 是一种新的 Microsoft 量子编程语言。我解释了如何使用量子门纠缠量子位,以及如何将 ASCII 消息编码为量子位。我探讨了基于 Web 的 Blazor 驱动的 UI,它利用量子算法并模拟向不同接收方发送量子粒子。我还展示了如何在 Blazor 服务器应用程序中使用 Q# 库以及如何启动和协调多个浏览器窗口。最后,我解释了如何在 Blazor 应用程序中使用 MVVM。

参考

  • “语句和其他结构”,于 2019 年 7 月 5 日从 bit.ly/2Ofx1Ld 检索
  • Yanofsky, N. 和 Mannucci, M.(2008)《计算机科学家的量子计算》,剑桥大学出版社
  • Nielsen, M. 和 Chuang, I.,(2010),《量子计算与量子信息》,第 10 版,剑桥大学出版社,英国剑桥大学
  • Watrous, J.,(2006)《讲座 3:超密集编码、量子电路和局部测量》,2019 年 7 月 5 日检索自 bit.ly/2XTPEDN

Daniel Vaughan 是一名作家和软件工程师,供职于华盛顿州雷德蒙德的 Microsoft 公司。他曾 9 次获得 Microsoft MVP 荣誉,是多款广受好评的消费者和企业移动应用的开发者,如面向 Android 的 Surfy 浏览器、Windows Phone 和面向 Android 的 Airlock 浏览器。他还创建了许多热门的开源项目,包括 Codon 和 Calcium 框架。Vaughan 的博客网址是 danielvaughan.org,推特是 @dbvaughan

衷心感谢以下 Microsoft 技术专家对本文的审阅:Bettina Heim(Microsoft 量子团队)
Bettina Heim 是 Microsoft 量子团队的成员。


在 MSDN 杂志论坛讨论这篇文章