Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge with Development #67

Merged
merged 9 commits into from
Jul 8, 2024
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# ======= CUSTOM SECTION =======
temp/
circuit_images/

# ======= VISUAL STUDIO SECTION =======
.vscode/*
Expand Down
11 changes: 11 additions & 0 deletions playground/examples/shift_operation.qut
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//Generic Rotation Algorithm (|b| = any)
qustring b = "110111"; //111011 -> 111110
print b;
b << 1;
print b;

//Pavone-Viola Rotation Algorithm (|c| = 2^p)
qustring c = "11011111"; //111011 -> 111110
print c;
c << 1;
print c;
6 changes: 3 additions & 3 deletions qutes-vscode/extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function runQutes(runs, params = []){
request: "launch",
program: "src/qutes.py",
console: "integratedTerminal",
args: params.concat(["-image","-circuit","-iter",runs,filePath]),
args: params.concat(["-image","-iter",runs,filePath]),
justMyCode: true
};

Expand All @@ -36,11 +36,11 @@ function activate(context) {
});

let runQutesFileVerboseCommand = vscode.commands.registerCommand('qutes.runQutesFileVerbose', function () {
runQutes("1", ["--verbose"]);
runQutes("1", ["--verbose", "-circuit"]);
});

let runQutesFile100VerboseCommand = vscode.commands.registerCommand('qutes.runQutesFileVerbose100', function () {
runQutes("100", ["--verbose"]);
runQutes("100", ["--verbose", "-circuit"]);
});

context.subscriptions.push(runQutesFileCommand);
Expand Down
2 changes: 2 additions & 0 deletions specification/grammar/qutes_lexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ GREATER : '>' ;
GREATEREQUAL : '>=' ;
LOWER : '<' ;
LOWEREQUAL : '<=' ;
LSHIFT : '<<' ;
RSHIFT : '>>' ;
ASSIGN : '=' ;
AUTO_INCREMENT : '++' ;
AUTO_DECREMENT : '--' ;
Expand Down
3 changes: 2 additions & 1 deletion specification/grammar/qutes_parser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ variableDeclaration
: variableType variableName (ASSIGN expr)?
;

expr
expr // Order: https://en.wikipedia.org/wiki/Order_of_operations#Programming_languages
: ROUND_PARENTHESIS_OPEN expr ROUND_PARENTHESIS_CLOSE #ParentesizeExpression
| literal #LiteralExpression
| qualifiedName #QualifiedNameExpression
Expand All @@ -45,6 +45,7 @@ expr
// cast operation
| expr op=(MULTIPLY | DIVIDE | MODULE) expr #MultiplicativeOperator
| expr op=(ADD | SUB) expr #SumOperator
| expr op=(LSHIFT | RSHIFT) expr #ShiftOperator
| expr op=(GREATEREQUAL | LOWEREQUAL | GREATER | LOWER ) expr #RelationalOperator
| expr op=(EQUAL | NOT_EQUAL) expr #EqualityOperator
| expr op=AND expr #LogicAndOperator
Expand Down
59 changes: 49 additions & 10 deletions src/grammar_frontend/operation.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import numpy as np
from grammar_frontend.qutes_parser import QutesParser as qutes_parser
from symbols.scope_tree_node import ScopeTreeNode
from symbols.symbol import Symbol
Expand All @@ -8,6 +9,7 @@
from grammar_frontend.qutes_base_visitor import QutesBaseVisitor
from symbols.types import Qubit, Quint, QutesDataType
import math
import utils

class QutesGrammarOperationVisitor(QutesBaseVisitor):
def __init__(self, symbols_tree:ScopeTreeNode, quantum_circuit_handler : QuantumCircuitHandler, scope_handler:ScopeHandlerForSymbolsUpdate, variables_handler:VariablesHandler, verbose:bool = False):
Expand All @@ -23,7 +25,10 @@ def visitMultiplicativeOperator(self, ctx:qutes_parser.MultiplicativeOperatorCon
return self.__visit_binary_operator(ctx)

def visitSumOperator(self, ctx:qutes_parser.SumOperatorContext):
return self.__visit_binary_operator(ctx)
return self.__visit_binary_operator(ctx)

def visitShiftOperator(self, ctx:qutes_parser.ShiftOperatorContext):
return self.__visit_binary_operator(ctx)

def visitRelationalOperator(self, ctx:qutes_parser.RelationalOperatorContext):
return self.__visit_boolean_operation(ctx)
Expand Down Expand Up @@ -73,6 +78,27 @@ def __visit_binary_operator(self, ctx:qutes_parser.SumOperatorContext | qutes_pa
if (first_term_symbol and QutesDataType.is_quantum_type(first_term_symbol.symbol_declaration_static_type)):
pass
result = first_term_value - second_term_value
if(isinstance(ctx, qutes_parser.ShiftOperatorContext)):
if(ctx.LSHIFT()):
if (first_term_symbol and QutesDataType.is_quantum_type(first_term_symbol.symbol_declaration_static_type)):
if(second_term_symbol and not QutesDataType.is_quantum_type(second_term_symbol.symbol_declaration_static_type)):
from quantum_circuit.qutes_gates import QutesGates
self.quantum_circuit_handler.push_compose_circuit_operation(QutesGates.left_rot(len(first_term_symbol.quantum_register), second_term_value, 1), [first_term_symbol.quantum_register]) #TODO: handle block size
result = first_term_symbol
else:
raise NotImplementedError("Left shift operator doesn't support second term to be a quantum variable.")
else:
result = first_term_value << second_term_value
if(ctx.RSHIFT()):
if (first_term_symbol and QutesDataType.is_quantum_type(first_term_symbol.symbol_declaration_static_type)):
if(second_term_symbol and not QutesDataType.is_quantum_type(second_term_symbol.symbol_declaration_static_type)):
from quantum_circuit.qutes_gates import QutesGates
self.quantum_circuit_handler.push_compose_circuit_operation(QutesGates.right_rot(len(first_term_symbol.quantum_register), second_term_value, 1), [first_term_symbol.quantum_register]) #TODO: handle block size
result = first_term_symbol
else:
raise NotImplementedError("Right shift operator doesn't support second term to be a quantum variable.")
else:
result = first_term_value >> second_term_value
if(isinstance(ctx, qutes_parser.MultiplicativeOperatorContext)):
if(ctx.MULTIPLY()):
if (first_term_symbol and QutesDataType.is_quantum_type(first_term_symbol.symbol_declaration_static_type)):
Expand Down Expand Up @@ -239,9 +265,16 @@ def visitGroverOperator(self, ctx:qutes_parser.GroverOperatorContext):
return None
if(ctx.IN_STATEMENT()):
array_register = target_symbol.quantum_register
block_size = 1
try:
block_size = target_symbol.value.default_block_size
except:
pass
array_size = int(len(target_symbol.quantum_register)/block_size)
n_element_to_rotate = array_size/block_size

self.quantum_circuit_handler.start_quantum_function()
termList:list[Symbol] = self.visit(ctx.termList())
array_size = len(target_symbol.quantum_register)

grover_result = self.quantum_circuit_handler.declare_quantum_register("grover_phase_ancilla", Qubit())
oracle_registers = [array_register]
Expand All @@ -264,19 +297,23 @@ def visitGroverOperator(self, ctx:qutes_parser.GroverOperatorContext):
self.quantum_circuit_handler.push_equals_operation(array_register, term.value)
else:
term_to_quantum = QutesDataType.promote_classical_to_quantum_value(term.value)
block_size = target_symbol.value.default_block_size
array_size = int(len(target_symbol.quantum_register)/block_size)
logn = max(int(math.log2(array_size)),1)

if(n_element_to_rotate.is_integer() and utils.is_power_of_two(int(n_element_to_rotate))):
logn = max(int(math.log2(n_element_to_rotate)),1)
else:
logn = max(int(math.log2(n_element_to_rotate))+1,1)

if(term_to_quantum.size == 1):
if(phase_kickback_ancilla == None):
phase_kickback_ancilla = self.quantum_circuit_handler.declare_quantum_register(f"phase_kickback_ancilla_{current_grover_count}", Qubit(0,1))
oracle_registers.append(phase_kickback_ancilla)
if(rotation_register == None):
rotation_register = self.quantum_circuit_handler.declare_quantum_register(f"rotation(grover:{current_grover_count})", Quint.init_from_integer(0,logn,True))
oracle_registers.append(rotation_register)
if(self.log_grover_verbose):
if(self.log_grover_esm_rotation):
registers_to_measure.append(rotation_register)
self.quantum_circuit_handler.push_ESM_operation(array_register, rotation_register, term_to_quantum, phase_kickback_ancilla)
self.quantum_circuit_handler.push_ESM_operation(array_register, rotation_register, term_to_quantum, block_size, phase_kickback_ancilla)

oracle_registers.append(grover_result)
quantum_function = self.quantum_circuit_handler.end_quantum_function(*oracle_registers, gate_name=f"grover_oracle_{current_grover_count}", create_gate=False)
Expand All @@ -285,17 +322,19 @@ def visitGroverOperator(self, ctx:qutes_parser.GroverOperatorContext):
if(rotation_register != None):
qubits_involved_in_grover = [*range(quantum_function.num_qubits-len(rotation_register)-1, quantum_function.num_qubits-1), quantum_function.num_qubits-1]

for n_results in range(1, array_size+1):
oracle_result = self.quantum_circuit_handler.push_grover_operation(*oracle_registers, quantum_function=quantum_function, register_involved_indexes=qubits_involved_in_grover, dataset_size=array_size, n_results=n_results, verbose=self.log_grover_verbose)
for n_results in np.arange(1.1, 1.1**math.log(n_element_to_rotate/2 + 1, 1.1)):
oracle_result = self.quantum_circuit_handler.push_grover_operation(*oracle_registers, quantum_function=quantum_function, register_involved_indexes=qubits_involved_in_grover, dataset_size=array_size, n_results=int(n_results), verbose=self.log_grover_verbose)
registers_to_measure.append(oracle_result)
circuit_runs = 3
self.quantum_circuit_handler.get_run_and_measure_results(registers_to_measure.copy(), repetition=circuit_runs)

positive_results = [(index, result) for index, result in enumerate(oracle_result.measured_classical_register.measured_values) if "1" in result]
any_positive_results = len(positive_results) > 0
if (any_positive_results):
if(self.log_grover_verbose and rotation_register.measured_classical_register is not None):
print(f"Solution found with rotation {rotation_register.measured_classical_register.measured_values[positive_results[0][0]]} (for the first hit)")
if(self.log_grover_esm_rotation and rotation_register.measured_classical_register is not None):
for result in positive_results:
print(f"Solution found with {int(rotation_register.measured_classical_register.measured_values[result[0]], 2)} left rotations")
# print(f"Solution found with rotation {int(rotation_register.measured_classical_register.measured_values[result[0]], 2) % (n_element_to_rotate)}")
return self.variables_handler.create_anonymous_symbol(QutesDataType.bool, True, ctx.start.tokenIndex)
registers_to_measure.remove(oracle_result)
return self.variables_handler.create_anonymous_symbol(QutesDataType.bool, False, ctx.start.tokenIndex)
1 change: 1 addition & 0 deletions src/grammar_frontend/qutes_base_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def __init__(self, symbols_tree:ScopeTreeNode, quantum_circuit_handler : Quantum
self.log_trace_enabled = verbose
self.log_step_by_step_results_enabled = verbose
self.log_grover_verbose = verbose
self.log_grover_esm_rotation = True

if(self.log_code_structure or self.log_trace_enabled or self.log_step_by_step_results_enabled):
print()
Expand Down
51 changes: 32 additions & 19 deletions src/quantum_circuit/quantum_circuit_handler.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import math
import utils
from typing import Any, Callable, cast
from quantum_circuit.classical_register import ClassicalRegister
from quantum_circuit.quantum_circuit import QuantumCircuit
Expand Down Expand Up @@ -117,15 +118,17 @@ def create_circuit(self, *regs, do_initialization:bool = True) -> QuantumCircuit
operation(circuit)
return circuit

def print_circuit(self, circuit:QuantumCircuit, save_image:bool = False, print_circuit_to_console = True):
def print_circuit(self, circuit:QuantumCircuit, save_image:bool = False, print_circuit_to_console = True, image_file_prefix = ""):
if(save_image):
import os
directory = "temp"
file_name = "circuit.png"
from datetime import datetime
directory = "circuit_images"
timestamp = datetime.now().strftime("%Y_%m_%d-%H_%M_%S")
file_name = f"{image_file_prefix}-{timestamp}.png"
file_path = os.path.join(directory, file_name)
if not os.path.exists(directory):
os.mkdir(directory)
circuit.draw(output='mpl', filename=file_path)
circuit.draw(output='mpl', filename=file_path, style='iqp')
print(f"Circuit image printed at: {file_path}")
if(print_circuit_to_console):
print(circuit.draw())
Expand Down Expand Up @@ -163,20 +166,32 @@ def __run__(self, circuit, shots, print_counts:bool = False):

if(cnt != None):
table = []
for run in cnt:
for index, run in enumerate(cnt):
count = cnt[run]
values = run.split(" ")[::-1]
# reverse the bits to have the least significant as rightmost,
# and the order of the measured variables from left to right where in the circuit were from top to bottom
# values = [f"{value[::-1]}₂ ←→ {int(value[::-1], 2):>4}⏨" for value in run.split(" ")[::-1]]
values = [f"{value}₂ | {int(value, 2)}⏨" for value in run.split(" ")[::-1]]
values.append(count)
values.append("Least Significant bit as rightmost") if(index == 0) else values.append("")
table.append(values)

if(print_counts):
from tabulate import tabulate
print(tabulate(table, headers=[f"{creg[0]}[{creg[1]}]" for creg in cnt.creg_sizes] + ["count"]))

measurement_for_runs = [res.split(" ")[::-1] for res in cnt.keys()]
print("⚠️ ~ Following results only show the last execution of the circuit, in case of measurements in the middle of the circuit, like the ones needed for casts and Grover search, those results are not shown.")
headers = [f"{creg[0]}" for creg in cnt.creg_sizes]
headers.append("Counts")
headers.append("Notes")
maxcolwidths = [40] * len(headers)
maxcolwidths[-1] = 40
colalign = ["right"] * len(headers)
colalign[-1] = "left"
print(tabulate(table, headers=headers, stralign="right", tablefmt="fancy_grid", maxcolwidths=maxcolwidths, colalign=colalign))

measurement_for_runs = [res.split(" ")[::-1] for res in cnt.keys()] # reverse the order of the measured variables from left to right where in the circuit were from top to bottom
counts_for_runs = [res[1] for res in cnt.items()]
for index in range(len(cnt.creg_sizes)):
measurement_for_variable = [a[index] for a in measurement_for_runs]
measurement_for_variable = [a[index] for a in measurement_for_runs] # reverse the bits to have the least significant as rightmost
Classical_registers = [reg for reg in self._classic_registers if reg.name == cnt.creg_sizes[index][0]]
Classical_registers[0].measured_values = measurement_for_variable
Classical_registers[0].measured_counts = counts_for_runs
Expand Down Expand Up @@ -281,15 +296,13 @@ def push_measure_operation(self, quantum_registers : list[QuantumRegister] = Non
self._current_operation_stack.append(lambda circuit : cast(QuantumCircuit, circuit).measure(unwrap(quantum_registers), unwrap(classical_registers)))
return classical_registers

def push_ESM_operation(self, input:QuantumRegister, rotation_register:QuantumRegister, to_match, phase_kickback_ancilla = None) -> None:
def push_ESM_operation(self, input:QuantumRegister, rotation_register:QuantumRegister, to_match, block_size, phase_kickback_ancilla = None) -> None:
array_len = len(input)
to_match_len = len(to_match.qubit_state)
block_size = Qustring.default_char_size
logn = max(int(math.log2(array_len/block_size)),1)

# rotate input array
from quantum_circuit.qutes_gates import QutesGates
for i in range(logn):
for i in range(len(rotation_register)):
self.push_compose_circuit_operation(QutesGates.crot(array_len, 2**i, block_size), [rotation_register[i], *input])

# compare x and y[:m]
Expand All @@ -301,18 +314,18 @@ def push_ESM_operation(self, input:QuantumRegister, rotation_register:QuantumReg

self.push_equals_operation(input[:to_match_len], to_match)

for i in range(logn)[::-1]:
self.push_compose_circuit_operation(QutesGates.crot(array_len,2**i,Qustring.default_char_size).inverse(), [rotation_register[i], *input])
for i in range(len(rotation_register))[::-1]:
self.push_compose_circuit_operation(QutesGates.crot(array_len, 2**i, block_size).inverse(), [rotation_register[i], *input])

grover_count = iter(range(1, 1000))
# It expects the register to put the result into to be the last one in the list
grover_count = iter(range(1, 1000))
def push_grover_operation(self, *oracle_registers, quantum_function:QuantumCircuit, register_involved_indexes, dataset_size, n_results = 1, verbose:bool = False) -> QuantumRegister:
current_grover_count = next(self.grover_count)
grover_op = GroverOperator(quantum_function, reflection_qubits=register_involved_indexes, insert_barriers=True, name=f"Grover{current_grover_count}")

if(verbose):
self.print_circuit(quantum_function)
self.print_circuit(grover_op.decompose())
self.print_circuit(quantum_function, save_image=True, image_file_prefix="quantum function")
self.print_circuit(grover_op.decompose(), save_image=True, image_file_prefix="grover")

n_iteration = math.floor(
(math.pi / 4) * math.sqrt(dataset_size / n_results)
Expand Down
Loading
Loading