From e55648a0ce5b2f498c6f68fc8e8ef050aaab33de Mon Sep 17 00:00:00 2001 From: HaYeong Lee <110161434+glorialeezero@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:33:08 +0900 Subject: [PATCH 1/9] Update pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3ae4289..db29d13 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ classifiers = [ dynamic = ["version"] dependencies = [ "rich>=13", - "numpy>=2", + "cirq>=1" ] [project.optional-dependencies] From ffef2b89c67e1a231fd3d0585550c7728dd0c752 Mon Sep 17 00:00:00 2001 From: HaYeong Lee <110161434+glorialeezero@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:33:18 +0900 Subject: [PATCH 2/9] Update spec.py --- qupsy/spec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qupsy/spec.py b/qupsy/spec.py index 3109e6b..33bdda9 100644 --- a/qupsy/spec.py +++ b/qupsy/spec.py @@ -54,7 +54,7 @@ def make_spec(data: SpecData) -> Spec: testcases: list[tuple[npt.ArrayLike, npt.ArrayLike]] = [] for tc in data["testcases"].values(): output = np.fromstring(tc["output"], dtype="complex", sep=",") - input = tc["input"] or (np.concat([[1], np.zeros_like(output[1:])])) + input = tc["input"] or (np.concatenate([[1], np.zeros_like(output[1:])])) testcases.append((input, output)) # logger.debug("Parsed output: %s", output) # logger.debug("Parsed input: %s", input) From d7de8292e3154a3eae3e2469ad1ce697338b483f Mon Sep 17 00:00:00 2001 From: HaYeong Lee <110161434+glorialeezero@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:33:25 +0900 Subject: [PATCH 3/9] Update language.py --- qupsy/language.py | 129 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 112 insertions(+), 17 deletions(-) diff --git a/qupsy/language.py b/qupsy/language.py index 69d32aa..8110f63 100644 --- a/qupsy/language.py +++ b/qupsy/language.py @@ -4,6 +4,10 @@ from dataclasses import dataclass from textwrap import indent +import cirq +import numpy as np +from cirq import Circuit, Gate, LineQubit # type: ignore + TAB = " " @@ -45,6 +49,10 @@ def terminated(self) -> bool: def copy(self) -> Aexp: return self.__class__(*[child.copy() for child in self.children]) + @abstractmethod + def __call__(self, memory: dict[str, int]) -> int: + pass + @dataclass class Integer(Aexp): @@ -68,6 +76,9 @@ def children(self) -> list[Aexp]: def copy(self) -> Integer: return Integer(self.value) + def __call__(self, memory: dict[str, int]) -> int: + return self.value + @dataclass class Var(Aexp): @@ -91,6 +102,9 @@ def children(self) -> list[Aexp]: def copy(self) -> Var: return Var(self.name) + def __call__(self, memory: dict[str, int]) -> int: + return memory[self.name] + @dataclass class HoleAexp(Aexp): @@ -112,6 +126,9 @@ def children(self) -> list[Aexp]: def terminated(self) -> bool: return False + def __call__(self, memory: dict[str, int]) -> int: + raise ValueError("HoleAexp cannot be called") + @dataclass class Add(Aexp): @@ -123,7 +140,7 @@ def __init__(self, a: Aexp | None = None, b: Aexp | None = None) -> None: self.b = b or HoleAexp() def __str__(self) -> str: - return f"{self.a} + {self.b}" + return f"({self.a} + {self.b})" def __repr__(self) -> str: return f"Add({repr(self.a)}, {repr(self.b)})" @@ -136,6 +153,9 @@ def cost(self) -> int: def children(self) -> list[Aexp]: return [self.a, self.b] + def __call__(self, memory: dict[str, int]) -> int: + return self.a(memory) + self.b(memory) + @dataclass class Sub(Aexp): @@ -147,7 +167,7 @@ def __init__(self, a: Aexp | None = None, b: Aexp | None = None) -> None: self.b = b or HoleAexp() def __str__(self) -> str: - return f"{self.a} - {self.b}" + return f"({self.a} - {self.b})" def __repr__(self) -> str: return f"Sub({repr(self.a)}, {repr(self.b)})" @@ -160,6 +180,9 @@ def cost(self) -> int: def children(self) -> list[Aexp]: return [self.a, self.b] + def __call__(self, memory: dict[str, int]) -> int: + return self.a(memory) - self.b(memory) + @dataclass class Mul(Aexp): @@ -171,7 +194,7 @@ def __init__(self, a: Aexp | None = None, b: Aexp | None = None) -> None: self.b = b or HoleAexp() def __str__(self) -> str: - return f"{self.a} * {self.b}" + return f"({self.a} * {self.b})" def __repr__(self) -> str: return f"Mul({repr(self.a)}, {repr(self.b)})" @@ -184,6 +207,9 @@ def cost(self) -> int: def children(self) -> list[Aexp]: return [self.a, self.b] + def __call__(self, memory: dict[str, int]) -> int: + return self.a(memory) * self.b(memory) + @dataclass class Div(Aexp): @@ -195,7 +221,7 @@ def __init__(self, a: Aexp | None = None, b: Aexp | None = None) -> None: self.b = b or HoleAexp() def __str__(self) -> str: - return f"{self.a} // {self.b}" + return f"({self.a} // {self.b})" def __repr__(self) -> str: return f"Div({repr(self.a)}, {repr(self.b)})" @@ -208,6 +234,9 @@ def cost(self) -> int: def children(self) -> list[Aexp]: return [self.a, self.b] + def __call__(self, memory: dict[str, int]) -> int: + return self.a(memory) // self.b(memory) + @dataclass class Gate(ABC): @@ -246,6 +275,10 @@ def terminated(self) -> bool: def copy(self) -> Gate: return self.__class__(*[child.copy() for child in self.children]) + @abstractmethod + def __call__(self, qbits: list[LineQubit], memory: dict[str, int]) -> Gate: + pass + @dataclass class HoleGate(Gate): @@ -267,6 +300,9 @@ def children(self) -> list[Aexp]: def terminated(self) -> bool: return False + def __call__(self, qbits: list[LineQubit], memory: dict[str, int]) -> Gate: + raise ValueError("HoleGate cannot be called") + @dataclass class H(Gate): @@ -276,7 +312,7 @@ def __init__(self, qreg: Aexp | None = None) -> None: self.qreg = qreg or HoleAexp() def __str__(self) -> str: - return f"qc.append(cirq.H(qbits[{self.qreg}]))" + return f"H({self.qreg})" def __repr__(self) -> str: return f"H({repr(self.qreg)})" @@ -289,6 +325,10 @@ def cost(self) -> int: def children(self) -> list[Aexp]: return [self.qreg] + def __call__(self, qbits: list[LineQubit], memory: dict[str, int]) -> Gate: + idx = self.qreg(memory) + return cirq.H(qbits[idx]) # type: ignore + @dataclass class X(Gate): @@ -298,7 +338,7 @@ def __init__(self, qreg: Aexp | None = None) -> None: self.qreg = qreg or HoleAexp() def __str__(self) -> str: - return f"qc.append(cirq.X(qbits[{self.qreg}]))" + return f"X({self.qreg})" def __repr__(self) -> str: return f"X({repr(self.qreg)})" @@ -311,12 +351,16 @@ def cost(self) -> int: def children(self) -> list[Aexp]: return [self.qreg] + def __call__(self, qbits: list[LineQubit], memory: dict[str, int]) -> Gate: + idx = self.qreg(memory) + return cirq.X(qbits[idx]) # type: ignore + @dataclass class Ry(Gate): qreg: Aexp - p: Aexp - q: Aexp + p: Aexp # TODO + q: Aexp # TODO def __init__( self, qreg: Aexp | None = None, p: Aexp | None = None, q: Aexp | None = None @@ -326,7 +370,7 @@ def __init__( self.q = q or HoleAexp() def __str__(self) -> str: - return f"qc.append(cirq.Ry(rads=2*np.arccos(math.sqrt({self.p}/{self.q}))))(qbits[{self.qreg}])" + return f"Ry({self.qreg}, {self.p}, {self.q})" def __repr__(self) -> str: return f"Ry({repr(self.qreg)}, {repr(self.p)}, {repr(self.q)})" @@ -339,6 +383,10 @@ def cost(self) -> int: def children(self) -> list[Aexp]: return [self.qreg, self.p, self.q] + def __call__(self, qbits: list[LineQubit], memory: dict[str, int]) -> Gate: + idx = self.qreg(memory) + return cirq.Ry(rads=2 * np.arccos(np.sqrt(self.p(memory) / self.q(memory))))(qbits[idx]) # type: ignore + @dataclass class CX(Gate): @@ -350,7 +398,7 @@ def __init__(self, qreg1: Aexp | None = None, qreg2: Aexp | None = None) -> None self.qreg2 = qreg2 or HoleAexp() def __str__(self) -> str: - return f"qc.append(cirq.CX(qbits[{self.qreg1}], qbits[{self.qreg2}]))" + return f"CX({self.qreg1}, {self.qreg2})" def __repr__(self) -> str: return f"CX({repr(self.qreg1)}, {repr(self.qreg2)})" @@ -363,6 +411,11 @@ def cost(self) -> int: def children(self) -> list[Aexp]: return [self.qreg1, self.qreg2] + def __call__(self, qbits: list[LineQubit], memory: dict[str, int]) -> Gate: + idx1 = self.qreg1(memory) + idx2 = self.qreg2(memory) + return cirq.CX(qbits[idx1], qbits[idx2]) # type: ignore + @dataclass class CRy(Gate): @@ -380,11 +433,11 @@ def __init__( ) -> None: self.qreg1 = qreg1 or HoleAexp() self.qreg2 = qreg2 or HoleAexp() - self.p = p or HoleAexp() - self.q = q or HoleAexp() + self.p = p or HoleAexp() # TODO + self.q = q or HoleAexp() # TODO def __str__(self) -> str: - return f"qc.append(cirq.Ry(rads=2*np.arccos(math.sqrt({self.p}/{self.q}))).controlled(num_controls=1)(qbits[{self.qreg1}], qbits[{self.qreg2}])" + return f"CRy({self.qreg1}, {self.qreg2}, {self.p}, {self.q})" def __repr__(self) -> str: return f"CRy({repr(self.qreg1)}, {repr(self.qreg2)}, {repr(self.p)}, {repr(self.q)})" @@ -397,6 +450,11 @@ def cost(self) -> int: def children(self) -> list[Aexp]: return [self.qreg1, self.qreg2, self.p, self.q] + def __call__(self, qbits: list[LineQubit], memory: dict[str, int]) -> Gate: + idx1 = self.qreg1(memory) + idx2 = self.qreg2(memory) + return cirq.Ry(rads=2 * np.arccos(np.sqrt(self.p(memory) / self.q(memory)))).controlled(num_controls=1)(qbits[idx1], qbits[idx2]) # type: ignore + @dataclass class Cmd(ABC): @@ -436,6 +494,12 @@ def terminated(self) -> bool: def copy(self) -> Cmd: return self.__class__(*[child.copy() for child in self.children]) + @abstractmethod + def __call__( + self, qc: Circuit, qbits: list[LineQubit], memory: dict[str, int] + ) -> None: + pass + @dataclass class HoleCmd(Cmd): @@ -457,6 +521,11 @@ def children(self) -> list[Cmd | Gate | Aexp]: def terminated(self) -> bool: return False + def __call__( + self, qc: Circuit, qbits: list[LineQubit], memory: dict[str, int] + ) -> None: + raise ValueError("HoleCmd cannot be called") + @dataclass class SeqCmd(Cmd): @@ -481,6 +550,12 @@ def cost(self) -> int: def children(self) -> list[Cmd | Gate | Aexp]: return [self.pre, self.post] + def __call__( + self, qc: Circuit, qbits: list[LineQubit], memory: dict[str, int] + ) -> None: + self.pre(qc, qbits, memory) + self.post(qc, qbits, memory) + @dataclass class ForCmd(Cmd): @@ -518,6 +593,16 @@ def cost(self) -> int: def children(self) -> list[Cmd | Gate | Aexp]: return [self.start, self.end, self.body] + def __call__( + self, qc: Circuit, qbits: list[LineQubit], memory: dict[str, int] + ) -> None: + start = self.start(memory) + end = self.end(memory) + for i in range(start, end): + memory[self.var] = i + self.body(qc, qbits, memory) + del memory[self.var] + @dataclass class GateCmd(Cmd): @@ -540,6 +625,12 @@ def cost(self) -> int: def children(self) -> list[Cmd | Gate | Aexp]: return [self.gate] + def __call__( + self, qc: Circuit, qbits: list[LineQubit], memory: dict[str, int] + ) -> None: + g = self.gate(qbits, memory) + qc.append(g) # type: ignore + @dataclass class Pgm: @@ -551,10 +642,7 @@ def __init__(self, n: str, body: Cmd | None = None) -> None: self.body = body or HoleCmd() def __str__(self) -> str: - qreg = "qbits = cirq.LineQubit.range(n)" - circuit = "qc = cirq.Circuit()" - ret = "return qc" - return f"import cirq, numpy as np\ndef pgm({self.n}):\n{indent(qreg, TAB)}\n{indent(circuit, TAB)}\n{indent(str(self.body), TAB)}\n{indent(ret, TAB)}" + return f"def pgm({self.n}):\n{indent(str(self.body), TAB)}" def __repr__(self) -> str: return f"Pgm({repr(self.body)})" @@ -570,6 +658,13 @@ def cost(self) -> int: def depth(self) -> int: return self.body.depth + def __call__(self, n: int) -> Circuit: + circuit = Circuit() + qbits = LineQubit.range(n) # type: ignore + memory: dict[str, int] = {self.n: n} + self.body(circuit, qbits, memory) + return circuit + ALL_AEXPS: list[type[Aexp]] = [ a From 612e767f0352dc6efccb4697600935e562f474ce Mon Sep 17 00:00:00 2001 From: HaYeong Lee <110161434+glorialeezero@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:33:31 +0900 Subject: [PATCH 4/9] Create verify.py --- qupsy/verify.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 qupsy/verify.py diff --git a/qupsy/verify.py b/qupsy/verify.py new file mode 100644 index 0000000..472e074 --- /dev/null +++ b/qupsy/verify.py @@ -0,0 +1,15 @@ +import cirq +import numpy as np +import numpy.typing as npt + +from qupsy.language import Pgm + + +def verify(testcase: tuple[npt.ArrayLike, npt.ArrayLike], pgm: Pgm) -> bool: + input, output = testcase + assert isinstance(input, np.ndarray) + assert isinstance(output, np.ndarray) + n = int(np.log2(input.size)) + pgm_qc = pgm(n) + pgm_sv = cirq.final_state_vector(pgm_qc, initial_state=input, qubit_order=cirq.LineQubit.range(n)) # type: ignore + return cirq.linalg.allclose_up_to_global_phase(output, pgm_sv) # type: ignore From 56e7c6bfb1a4c152e8ce16234143038f7940660e Mon Sep 17 00:00:00 2001 From: HaYeong Lee <110161434+glorialeezero@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:33:35 +0900 Subject: [PATCH 5/9] Create test_verify.py --- tests/test_verify.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/test_verify.py diff --git a/tests/test_verify.py b/tests/test_verify.py new file mode 100644 index 0000000..fc8bc3b --- /dev/null +++ b/tests/test_verify.py @@ -0,0 +1,17 @@ +from qupsy.language import CX, ForCmd, GateCmd, H, Integer, Pgm, SeqCmd, Var +from qupsy.spec import parse_spec +from qupsy.verify import verify + + +def test_verify(): + ghz = Pgm( + "n", + SeqCmd( + GateCmd(H(Integer(0))), + ForCmd("i0", Integer(1), Var("n"), GateCmd(CX(Integer(0), Var("i0")))), + ), + ) + spec = parse_spec("benchmarks/ghz.json") + testcases = spec.testcases + for testcase in testcases: + assert verify(testcase, ghz) From f9e8f48dae09322e2ea5e81462e64ad99df4326c Mon Sep 17 00:00:00 2001 From: HaYeong Lee <110161434+glorialeezero@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:33:47 +0900 Subject: [PATCH 6/9] Update devcontainer.json --- .devcontainer/devcontainer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 6f600a2..2b59e5a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "name": "qupsy", - "image": "mcr.microsoft.com/devcontainers/python:1-3.12-bullseye", + "image": "mcr.microsoft.com/devcontainers/python:1-3.11-bullseye", "features": { "ghcr.io/devcontainers/features/common-utils:2": { "installZsh": true, From fca79c8c0f1b3480064c96f9529a025a4e0d2a79 Mon Sep 17 00:00:00 2001 From: HaYeong Lee <110161434+glorialeezero@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:36:18 +0900 Subject: [PATCH 7/9] Remove external file dependency --- tests/test_verify.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/tests/test_verify.py b/tests/test_verify.py index fc8bc3b..0690cd3 100644 --- a/tests/test_verify.py +++ b/tests/test_verify.py @@ -1,5 +1,5 @@ from qupsy.language import CX, ForCmd, GateCmd, H, Integer, Pgm, SeqCmd, Var -from qupsy.spec import parse_spec +from qupsy.spec import SpecData, make_spec from qupsy.verify import verify @@ -11,7 +11,21 @@ def test_verify(): ForCmd("i0", Integer(1), Var("n"), GateCmd(CX(Integer(0), Var("i0")))), ), ) - spec = parse_spec("benchmarks/ghz.json") + raw_spec: SpecData = { + "gates": ["H", "CX"], + "testcases": { + "1": { + "input": None, + "output": "0.70710677,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.70710677", + }, + "2": { + "input": None, + "output": "0.70710677, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.70710677", + }, + "3": {"input": None, "output": "0.70710677,0,0,0,0,0,0,0.70710677"}, + }, + } + spec = make_spec(raw_spec) testcases = spec.testcases for testcase in testcases: assert verify(testcase, ghz) From 98051402fb9dc40474d23726ae6aef62a7aaf8b1 Mon Sep 17 00:00:00 2001 From: HaYeong Lee <110161434+glorialeezero@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:39:49 +0900 Subject: [PATCH 8/9] Update test_transition.py --- tests/test_transition.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/test_transition.py b/tests/test_transition.py index ca0d70b..e65c441 100644 --- a/tests/test_transition.py +++ b/tests/test_transition.py @@ -145,13 +145,9 @@ def test_ghz_next(): assert ( str(pgm11) == """ -import cirq, numpy as np def pgm(n): - qbits = cirq.LineQubit.range(n) - qc = cirq.Circuit() - qc.append(cirq.H(qbits[0])) + H(0) for i0 in range(1,n): - qc.append(cirq.CX(qbits[0], qbits[i0])) - return qc + CX(0, i0) """.strip() ) From be2407f6c6c6e81f4108b0d9fe743095a56f3e63 Mon Sep 17 00:00:00 2001 From: HaYeong Lee <110161434+glorialeezero@users.noreply.github.com> Date: Tue, 26 Nov 2024 05:45:29 +0000 Subject: [PATCH 9/9] Python version change --- .github/workflows/ci.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 57df331..d5739a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,6 @@ jobs: - '3.10' - '3.11' - '3.12' - - '3.13' runs-on: ubuntu-latest @@ -82,10 +81,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Python 3.13 + - name: Set up Python 3.12 uses: actions/setup-python@v5 with: - python-version: '3.13' + python-version: '3.12' - name: Install dependencies run: |