Skip to content

Commit

Permalink
QuantumWorld: use sparse-vector simulator by default
Browse files Browse the repository at this point in the history
QuantumWorld now defaults to using SparseSimulator instead of
cirq.Simulator for the sampler. This should be a good default because
users are likely to construct sparse states with the sort of operations
provided by Unitary, and with the previous default performance problems
quickly arise as soon as you have more than a handful of qubits.
compile_to_qubits, which is required for
the sparse simulator, is now automatically enabled when using a sparse
simulator.

All examples should now be using sparse simulation except Tic-Tac-Toe.
I left the Tic-Tac-Toe example using the old default of cirq.Simulator
since I'm not sure if the states are actually sparse in this game, and
it would have been some effort to fix tests.
  • Loading branch information
losos0 committed Mar 22, 2024
1 parent b4567ea commit 35f7b97
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 30 deletions.
32 changes: 26 additions & 6 deletions unitary/alpha/quantum_object_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,20 @@ def test_add_world_after_state_change(simulator, compile_to_qubits):
assert board.peek() == [[1]]


@pytest.mark.parametrize("compile_to_qubits", [False, True])
def test_qutrit(compile_to_qubits):
@pytest.mark.parametrize(
("simulator", "compile_to_qubits"),
[
(cirq.Simulator, False),
(cirq.Simulator, True),
# Cannot use SparseSimulator without `compile_to_qubits` due to issue #78.
(alpha.SparseSimulator, True),
],
)
def test_qutrit(simulator, compile_to_qubits):
piece = alpha.QuantumObject("t", 2)
board = alpha.QuantumWorld(piece, compile_to_qubits=compile_to_qubits)
board = alpha.QuantumWorld(
piece, sampler=simulator(), compile_to_qubits=compile_to_qubits
)
assert board.peek() == [[2]]
piece += 1
assert board.peek() == [[0]]
Expand All @@ -64,15 +74,25 @@ def test_qutrit(compile_to_qubits):
assert board.peek() == [[2]]


@pytest.mark.parametrize("compile_to_qubits", [False, True])
def test_enum(compile_to_qubits):
@pytest.mark.parametrize(
("simulator", "compile_to_qubits"),
[
(cirq.Simulator, False),
(cirq.Simulator, True),
# Cannot use SparseSimulator without `compile_to_qubits` due to issue #78.
(alpha.SparseSimulator, True),
],
)
def test_enum(simulator, compile_to_qubits):
class Color(enum.Enum):
RED = 0
YELLOW = 1
GREEN = 2

piece = alpha.QuantumObject("t", Color.YELLOW)
board = alpha.QuantumWorld(piece, compile_to_qubits=compile_to_qubits)
board = alpha.QuantumWorld(
piece, sampler=simulator(), compile_to_qubits=compile_to_qubits
)
assert board.peek() == [[Color.YELLOW]]
piece += Color.YELLOW
assert board.peek() == [[Color.GREEN]]
Expand Down
13 changes: 9 additions & 4 deletions unitary/alpha/quantum_world.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,11 @@ class QuantumWorld:
This object also has a history so that effects can be undone.
This object should be initialized with a sampler that determines
how to evaluate the quantum game state. If not specified, this
defaults to the built-in cirq Simulator.
how to evaluate the quantum game state. If not specified, this
defaults to a noiseless simulator optimized for sparse state vectors.
You may also use e.g. cirq.Simulator, a noiseless simulator using
dense state vectors, which natively supports qudits unlike the sparse
simulator.
Setting the `compile_to_qubits` option results in an internal state
representation of ancilla qubits for every qudit in the world. That
Expand All @@ -48,12 +51,14 @@ class QuantumWorld:
def __init__(
self,
objects: Optional[List[QuantumObject]] = None,
sampler=cirq.Simulator(),
compile_to_qubits: bool = False,
sampler: cirq.Sampler = SparseSimulator(),
compile_to_qubits: Optional[bool] = None,
):
self.clear()
self.sampler = sampler
self.use_sparse = isinstance(sampler, SparseSimulator)
if compile_to_qubits is None:
compile_to_qubits = self.use_sparse
self.compile_to_qubits = compile_to_qubits

if isinstance(objects, QuantumObject):
Expand Down
24 changes: 19 additions & 5 deletions unitary/alpha/quantum_world_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ def test_two_qubits(simulator, compile_to_qubits):
def test_two_qutrits(compile_to_qubits):
light = alpha.QuantumObject("yellow", StopLight.YELLOW)
light2 = alpha.QuantumObject("green", StopLight.GREEN)
board = alpha.QuantumWorld([light, light2], compile_to_qubits=compile_to_qubits)
board = alpha.QuantumWorld(
[light, light2], sampler=cirq.Simulator(), compile_to_qubits=compile_to_qubits
)
assert board.peek(convert_to_enum=False) == [[1, 2]]
assert board.peek() == [[StopLight.YELLOW, StopLight.GREEN]]
assert board.peek([light], count=2) == [[StopLight.YELLOW], [StopLight.YELLOW]]
Expand Down Expand Up @@ -630,19 +632,31 @@ def test_combine_incompatibly_sparse_worlds(compile_to_qubits):
world2.combine_with(world1)


@pytest.mark.parametrize("compile_to_qubits", [False, True])
def test_combine_worlds(compile_to_qubits):
@pytest.mark.parametrize(
("simulator", "compile_to_qubits"),
[
(cirq.Simulator, False),
(cirq.Simulator, True),
# Cannot use SparseSimulator without `compile_to_qubits` due to issue #78.
(alpha.SparseSimulator, True),
],
)
def test_combine_worlds(simulator, compile_to_qubits):
l1 = alpha.QuantumObject("l1", Light.GREEN)
l2 = alpha.QuantumObject("l2", Light.RED)
l3 = alpha.QuantumObject("l3", Light.RED)
world1 = alpha.QuantumWorld([l1, l2, l3], compile_to_qubits=compile_to_qubits)
world1 = alpha.QuantumWorld(
[l1, l2, l3], sampler=simulator(), compile_to_qubits=compile_to_qubits
)

# Split and pop to test post-selection after combining
alpha.Split()(l1, l2, l3)
result = world1.pop()

l4 = alpha.QuantumObject("stop_light", StopLight.YELLOW)
world2 = alpha.QuantumWorld([l4], compile_to_qubits=compile_to_qubits)
world2 = alpha.QuantumWorld(
[l4], sampler=simulator(), compile_to_qubits=compile_to_qubits
)
world2.combine_with(world1)

results = world2.peek(count=100)
Expand Down
30 changes: 24 additions & 6 deletions unitary/alpha/qudit_effects_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import enum
import pytest

import cirq

import unitary.alpha as alpha


Expand All @@ -24,9 +26,17 @@ class StopLight(enum.Enum):
GREEN = 2


@pytest.mark.parametrize("compile_to_qubits", [False, True])
def test_qudit_cycle(compile_to_qubits):
board = alpha.QuantumWorld(compile_to_qubits=compile_to_qubits)
@pytest.mark.parametrize(
("simulator", "compile_to_qubits"),
[
(cirq.Simulator, False),
(cirq.Simulator, True),
# Cannot use SparseSimulator without `compile_to_qubits` due to issue #78.
(alpha.SparseSimulator, True),
],
)
def test_qudit_cycle(simulator, compile_to_qubits):
board = alpha.QuantumWorld(sampler=simulator(), compile_to_qubits=compile_to_qubits)
piece = alpha.QuantumObject("t", StopLight.GREEN)
board.add_object(piece)
alpha.QuditCycle(3)(piece)
Expand All @@ -43,9 +53,17 @@ def test_qudit_cycle(compile_to_qubits):
assert all(result == [StopLight.YELLOW] for result in results)


@pytest.mark.parametrize("compile_to_qubits", [False, True])
def test_qudit_flip(compile_to_qubits):
board = alpha.QuantumWorld(compile_to_qubits=compile_to_qubits)
@pytest.mark.parametrize(
("simulator", "compile_to_qubits"),
[
(cirq.Simulator, False),
(cirq.Simulator, True),
# Cannot use SparseSimulator without `compile_to_qubits` due to issue #78.
(alpha.SparseSimulator, True),
],
)
def test_qudit_flip(simulator, compile_to_qubits):
board = alpha.QuantumWorld(sampler=simulator(), compile_to_qubits=compile_to_qubits)
piece = alpha.QuantumObject("t", StopLight.GREEN)
board.add_object(piece)
alpha.QuditFlip(3, 0, 2)(piece)
Expand Down
3 changes: 1 addition & 2 deletions unitary/alpha/sparse_vector_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@
Supports standard unitary Cirq gates, plus a post-selection operator.
Does not work with 3+-state qudits. TODO: integrate with qutrit-to-qubit conversion
when that is available.
Does not work with 3+-state qudits.
"""
import cirq
import numpy as np
Expand Down
3 changes: 2 additions & 1 deletion unitary/examples/quantum_rpg/qaracter.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ def qar_sheet(self) -> str:
self.get_object_by_name(self.quantum_object_name(i)).qubit
for i in range(1, self.level + 1)
]
return self.circuit.to_text_diagram(qubit_order=all_qubits)
canonical_circuit = cirq.Circuit(cirq.flatten_op_tree(self.circuit))
return canonical_circuit.to_text_diagram(qubit_order=all_qubits)

def qar_status(self) -> str:
"""Prints out the qaracter's name/class/level and circuit.
Expand Down
16 changes: 12 additions & 4 deletions unitary/examples/tictactoe/tic_tac_split_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ def test_tic_tac_split(
):
a = alpha.QuantumObject("a", tictactoe.TicTacSquare.EMPTY)
b = alpha.QuantumObject("b", tictactoe.TicTacSquare.EMPTY)
board = alpha.QuantumWorld([a, b], compile_to_qubits=compile_to_qubits)
board = alpha.QuantumWorld(
[a, b], sampler=cirq.Simulator(), compile_to_qubits=compile_to_qubits
)
tictactoe.TicTacSplit(mark, ruleset)(a, b)
results = board.peek(count=1000)
on_a = [mark, tictactoe.TicTacSquare.EMPTY]
Expand All @@ -114,7 +116,9 @@ def test_tic_tac_split_entangled_v2(compile_to_qubits):
a = alpha.QuantumObject("a", tictactoe.TicTacSquare.EMPTY)
b = alpha.QuantumObject("b", tictactoe.TicTacSquare.EMPTY)
c = alpha.QuantumObject("c", tictactoe.TicTacSquare.EMPTY)
board = alpha.QuantumWorld([a, b, c], compile_to_qubits=compile_to_qubits)
board = alpha.QuantumWorld(
[a, b, c], sampler=cirq.Simulator(), compile_to_qubits=compile_to_qubits
)
ruleset = tictactoe.TicTacRules.QUANTUM_V2
tictactoe.TicTacSplit(tictactoe.TicTacSquare.X, ruleset)(a, b)
tictactoe.TicTacSplit(tictactoe.TicTacSquare.O, ruleset)(b, c)
Expand All @@ -133,7 +137,9 @@ def test_tic_tac_split_entangled_v3(compile_to_qubits):
a = alpha.QuantumObject("a", tictactoe.TicTacSquare.EMPTY)
b = alpha.QuantumObject("b", tictactoe.TicTacSquare.EMPTY)
c = alpha.QuantumObject("c", tictactoe.TicTacSquare.EMPTY)
board = alpha.QuantumWorld([a, b, c], compile_to_qubits=compile_to_qubits)
board = alpha.QuantumWorld(
[a, b, c], sampler=cirq.Simulator(), compile_to_qubits=compile_to_qubits
)
ruleset = tictactoe.TicTacRules.QUANTUM_V3
tictactoe.TicTacSplit(tictactoe.TicTacSquare.X, ruleset)(a, b)
tictactoe.TicTacSplit(tictactoe.TicTacSquare.O, ruleset)(b, c)
Expand All @@ -160,7 +166,9 @@ def test_tic_tac_split_entangled_v3_empty(compile_to_qubits):
a = alpha.QuantumObject("a", tictactoe.TicTacSquare.EMPTY)
b = alpha.QuantumObject("b", tictactoe.TicTacSquare.EMPTY)
c = alpha.QuantumObject("c", tictactoe.TicTacSquare.EMPTY)
board = alpha.QuantumWorld([a, b, c], compile_to_qubits=compile_to_qubits)
board = alpha.QuantumWorld(
[a, b, c], sampler=cirq.Simulator(), compile_to_qubits=compile_to_qubits
)
ruleset = tictactoe.TicTacRules.QUANTUM_V3
tictactoe.TicTacSplit(tictactoe.TicTacSquare.X, ruleset)(a, b)
tictactoe.TicTacSplit(tictactoe.TicTacSquare.O, ruleset)(c, b)
Expand Down
8 changes: 6 additions & 2 deletions unitary/examples/tictactoe/tic_tac_toe.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import sys
from typing import Dict, List, TextIO

import cirq

from unitary.alpha import QuantumObject, QuantumWorld
from unitary.alpha.qudit_effects import QuditFlip
from unitary.examples.tictactoe.enums import (
Expand Down Expand Up @@ -146,7 +148,9 @@ def clear(self, run_on_hardware: bool = False) -> None:
self.empty_squares.add(name)
self.squares[name] = QuantumObject(name, TicTacSquare.EMPTY)
self.board = QuantumWorld(
list(self.squares.values()), compile_to_qubits=run_on_hardware
list(self.squares.values()),
sampler=cirq.Simulator(),
compile_to_qubits=run_on_hardware,
)

def result(self) -> TicTacResult:
Expand All @@ -171,7 +175,7 @@ def measure(self) -> None:
if self.last_result[idx] == TicTacSquare.EMPTY:
self.empty_squares.add(name)
self.squares[name] = QuantumObject(name, self.last_result[idx])
self.board = QuantumWorld(list(self.squares.values()))
self.board = QuantumWorld(list(self.squares.values()), sampler=cirq.Simulator())

def move(self, move: str, mark: TicTacSquare) -> TicTacResult:
"""Make a move on the TicTacToe board.
Expand Down

0 comments on commit 35f7b97

Please sign in to comment.