From 35f7b97b7be7ae2cc05d499b7fc8ba2d0c137d69 Mon Sep 17 00:00:00 2001 From: Conrad Holt Date: Wed, 20 Mar 2024 19:54:19 -0500 Subject: [PATCH] QuantumWorld: use sparse-vector simulator by default 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. --- unitary/alpha/quantum_object_test.py | 32 +++++++++++++++---- unitary/alpha/quantum_world.py | 13 +++++--- unitary/alpha/quantum_world_test.py | 24 +++++++++++--- unitary/alpha/qudit_effects_test.py | 30 +++++++++++++---- unitary/alpha/sparse_vector_simulator.py | 3 +- unitary/examples/quantum_rpg/qaracter.py | 3 +- .../examples/tictactoe/tic_tac_split_test.py | 16 +++++++--- unitary/examples/tictactoe/tic_tac_toe.py | 8 +++-- 8 files changed, 99 insertions(+), 30 deletions(-) diff --git a/unitary/alpha/quantum_object_test.py b/unitary/alpha/quantum_object_test.py index 70b76b25..2b06c859 100644 --- a/unitary/alpha/quantum_object_test.py +++ b/unitary/alpha/quantum_object_test.py @@ -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]] @@ -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]] diff --git a/unitary/alpha/quantum_world.py b/unitary/alpha/quantum_world.py index daa00dff..416ac0a7 100644 --- a/unitary/alpha/quantum_world.py +++ b/unitary/alpha/quantum_world.py @@ -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 @@ -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): diff --git a/unitary/alpha/quantum_world_test.py b/unitary/alpha/quantum_world_test.py index 508c3901..cd5c368a 100644 --- a/unitary/alpha/quantum_world_test.py +++ b/unitary/alpha/quantum_world_test.py @@ -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]] @@ -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) diff --git a/unitary/alpha/qudit_effects_test.py b/unitary/alpha/qudit_effects_test.py index 963ddac6..c2c3d8a2 100644 --- a/unitary/alpha/qudit_effects_test.py +++ b/unitary/alpha/qudit_effects_test.py @@ -15,6 +15,8 @@ import enum import pytest +import cirq + import unitary.alpha as alpha @@ -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) @@ -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) diff --git a/unitary/alpha/sparse_vector_simulator.py b/unitary/alpha/sparse_vector_simulator.py index 67d79a20..9a0d9d66 100644 --- a/unitary/alpha/sparse_vector_simulator.py +++ b/unitary/alpha/sparse_vector_simulator.py @@ -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 diff --git a/unitary/examples/quantum_rpg/qaracter.py b/unitary/examples/quantum_rpg/qaracter.py index c48f7f38..00bfa197 100644 --- a/unitary/examples/quantum_rpg/qaracter.py +++ b/unitary/examples/quantum_rpg/qaracter.py @@ -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. diff --git a/unitary/examples/tictactoe/tic_tac_split_test.py b/unitary/examples/tictactoe/tic_tac_split_test.py index e4b1f31c..a26d2117 100644 --- a/unitary/examples/tictactoe/tic_tac_split_test.py +++ b/unitary/examples/tictactoe/tic_tac_split_test.py @@ -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] @@ -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) @@ -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) @@ -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) diff --git a/unitary/examples/tictactoe/tic_tac_toe.py b/unitary/examples/tictactoe/tic_tac_toe.py index 49418f32..0be2af27 100644 --- a/unitary/examples/tictactoe/tic_tac_toe.py +++ b/unitary/examples/tictactoe/tic_tac_toe.py @@ -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 ( @@ -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: @@ -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.