Skip to content

Commit

Permalink
Add server-side custom gate handling
Browse files Browse the repository at this point in the history
  • Loading branch information
juanrein committed Aug 7, 2023
1 parent 85fb86a commit 59d8008
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 13 deletions.
80 changes: 70 additions & 10 deletions timApp/plugin/quantum_circuit/quantumCircuit.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from dataclasses import dataclass, asdict
from typing import Union
import json

from flask import render_template_string

from qulacs import QuantumCircuit, QuantumState, QuantumGateMatrix
from qulacs.gate import H, X, Y, Z, S, T, to_matrix_gate
from qulacs.gate import H, X, Y, Z, S, T, to_matrix_gate, DenseMatrix
import numpy as np

from timApp.tim_app import csrf
Expand Down Expand Up @@ -66,6 +67,15 @@ def to_json(self) -> dict:
return asdict(self)


@dataclass
class AnswerCustomGateInfo:
name: str
matrix: str

def to_json(self) -> dict:
return asdict(self)


@dataclass
class QuantumCircuitMarkup(GenericMarkupModel):
"""Class that defines plugin markup (the YAML settings and their types)"""
Expand Down Expand Up @@ -102,6 +112,7 @@ class QuantumCircuitInputModel:

userCircuit: list[GateInfo] | None = None
userInput: list[int] | None = None
customGates: list[AnswerCustomGateInfo] | None = None


@dataclass
Expand Down Expand Up @@ -150,12 +161,15 @@ class QuantumCircuitAnswerModel(


def get_gate_matrix(
name: str, target: int, custom_gate: dict[str, np.ndarray]
name: str, target: int, custom_gates: dict[str, np.ndarray]
) -> QuantumGateMatrix | None:
gate_mapping = {"H": H, "X": X, "Y": Y, "Z": Z, "S": S, "T": T}
f = gate_mapping[name]
if f:
f = gate_mapping.get(name, None)
if f is not None:
return to_matrix_gate(f(target))
f = custom_gates.get(name, None)
if f is not None:
return DenseMatrix(target, f)
return None


Expand Down Expand Up @@ -191,8 +205,36 @@ def add_gates_to_circuit(
print(f"undefined type {gate_def}")


def parse_custom_gates(gates: list[CustomGateInfo] | None) -> dict[str, np.ndarray]:
return {}
def parse_matrix(m_str: str) -> np.ndarray:
"""
Parse n x n array from string. The values are formatted as complex numbers.
:param m_str:
:return:
"""
arr = json.loads(m_str)
if isinstance(arr, list):
m = []
for row in arr:
m_row = []
if isinstance(row, list):
for cell in row:
value = complex(cell)
m_row.append(value)
m.append(m_row)
return np.array(m, dtype=complex)
raise ValueError("failed to parse matrix from input: " + m_str)


def parse_custom_gates(
gates: list[AnswerCustomGateInfo] | None,
) -> dict[str, np.ndarray]:
custom_gates = {}
if not gates:
return custom_gates
for gate in gates:
m = parse_matrix(gate.matrix)
custom_gates[gate.name] = m
return custom_gates


def run_simulation(
Expand All @@ -201,6 +243,14 @@ def run_simulation(
n_qubits: int,
custom_gates: dict[str, np.ndarray],
) -> np.ndarray:
"""
Runs simulator.
:param gates: gates that are in the circuit
:param input_list: input bits as a list of 0s and 1s [q0 value, q1 value,...]
:param n_qubits: how many qubits the circuit has
:param custom_gates: custom gates defined in YAML
:return: state vector after simulation
"""
state = QuantumState(n_qubits)
initial_state = input_to_int(input_list)
state.set_computational_basis(initial_state)
Expand All @@ -217,6 +267,12 @@ def run_simulation(


def check_answer(user_result: np.ndarray, model_result: np.ndarray) -> bool:
"""
Checks whether the arrays have same values.
:param user_result: the state of user's circuit after simulation
:param model_result: the state after simulation of the model circuit
:return: whether the array are same up to some small error margin
"""
return np.allclose(user_result, model_result)


Expand All @@ -231,19 +287,23 @@ def answer(args: QuantumCircuitAnswerModel) -> PluginAnswerResp:

n_qubits = args.markup.nQubits

custom_gates = parse_custom_gates(args.markup.customGates)

points = 1.0
result = "tallennettu"
error = ""
if model_circuit and model_input and user_circuit and user_input and n_qubits:
if (
model_circuit is not None
and model_input is not None
and user_circuit is not None
and user_input is not None
and n_qubits is not None
):
custom_gates = parse_custom_gates(args.input.customGates)
user_result = run_simulation(user_circuit, user_input, n_qubits, custom_gates)
model_result = run_simulation(
model_circuit, model_input, n_qubits, custom_gates
)

correct = check_answer(user_result, model_result)
print(user_result, model_result)
if correct:
points = 1.0
result = "Oikein"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
ActiveGateInfo,
CircuitActiveGateInfo,
} from "tim/plugin/quantumcircuit/active-gate";
import {format} from "mathjs";

export interface QubitOutput {
value: number;
Expand Down Expand Up @@ -106,6 +107,13 @@ const CustomGateInfo = t.type({

export type ICustomGateInfo = t.TypeOf<typeof CustomGateInfo>;

const AnswerCustomGateInfo = t.type({
name: t.string,
matrix: t.string,
});

export type IAnswerCustomGateInfo = t.TypeOf<typeof AnswerCustomGateInfo>;

const CircuitType = nullable(t.array(GateInfo));

type ICircuit = t.TypeOf<typeof CircuitType>;
Expand Down Expand Up @@ -246,8 +254,8 @@ export interface CircuitStyleOptions {
</div>
</div>
<div>{{result}}</div>
<div>{{error}}</div>
<div *ngIf="error" [innerHTML]="error | purify"></div>
<div *ngIf="result" [innerHTML]="result | purify"></div>
</ng-container>
<p footer *ngIf="footer" [innerHTML]="footer | purify"></p>
Expand Down Expand Up @@ -430,6 +438,30 @@ export class QuantumCircuitComponent
return userCircuit;
}

/**
* Turns matrix into json format that can be loaded in Python.
* @param name name of matrix
*/
customMatrixDefToPythonJsonStr(name: string) {
const m = this.gateService.getMatrix(name);
if (!m) {
return undefined;
}
const [n] = m.size();
const res: string[][] = [];
for (let i = 0; i < n; i++) {
const resRow: string[] = [];
for (let j = 0; j < n; j++) {
const d = m.get([i, j]);
// format number or complex number,
// remove whitespace and replace i with j as complex number marker.
resRow.push(format(d).replace(/\s/g, "").replace(/i/g, "j"));
}
res.push(resRow);
}
return JSON.stringify(res);
}

/**
* Save answer.
*/
Expand All @@ -438,10 +470,26 @@ export class QuantumCircuitComponent

const userInput: IUserInput = this.qubits.map((q) => q.value);

const customGates: IAnswerCustomGateInfo[] = [];
if (this.markup.customGates) {
for (const g of this.markup.customGates) {
const m = this.customMatrixDefToPythonJsonStr(g.name);
if (m) {
customGates.push({
name: g.name,
matrix: m,
});
} else {
console.error("failed to serialize gate", g);
}
}
}

const params = {
input: {
userCircuit: userCircuit,
userInput: userInput,
customGates: customGates,
},
};
const r = await this.postAnswer<{
Expand All @@ -450,7 +498,6 @@ export class QuantumCircuitComponent
if (r.ok) {
this.result = r.result.web.result ?? "";
this.error = r.result.web.error ?? "";
console.log(this.result, this.error);
} else {
this.result = "";
this.error = r.result.error.error;
Expand Down

0 comments on commit 59d8008

Please sign in to comment.