From 23b581769324b27e0e7f9125950fc1b87b6c327c Mon Sep 17 00:00:00 2001 From: J <80164315+jbolns@users.noreply.github.com> Date: Mon, 2 Jun 2025 23:37:49 +0300 Subject: [PATCH 1/5] pretty msds v1 --- .../rotations/quantum_variable_rotation.py | 1 + qualtran/drawing/_show_funcs.py | 132 +++++++++++++++++- 2 files changed, 129 insertions(+), 4 deletions(-) diff --git a/qualtran/bloqs/rotations/quantum_variable_rotation.py b/qualtran/bloqs/rotations/quantum_variable_rotation.py index 8177ea80e8..5883d0fc19 100644 --- a/qualtran/bloqs/rotations/quantum_variable_rotation.py +++ b/qualtran/bloqs/rotations/quantum_variable_rotation.py @@ -191,6 +191,7 @@ def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: 'SoquetT') -> Dict[str if self.cost_dtype.signed: out[0] = bb.add(ZPowGate(exponent=1, eps=eps), q=out[0]) offset = 1 + self.cost_dtype.num_frac - self.num_frac_rotations + offset = offset if isinstance(offset, int) else 1 for i in range(num_rotations): power_of_two = i - self.num_frac_rotations exp = (2**power_of_two) * self.gamma * 2 diff --git a/qualtran/drawing/_show_funcs.py b/qualtran/drawing/_show_funcs.py index 932a78ad69..d4087badcf 100644 --- a/qualtran/drawing/_show_funcs.py +++ b/qualtran/drawing/_show_funcs.py @@ -15,7 +15,9 @@ """Convenience functions for showing rich displays in Jupyter notebook.""" import os -from typing import Dict, Optional, overload, Sequence, TYPE_CHECKING, Union +import re +import sympy +from typing import Dict, Optional, Text, overload, Sequence, TYPE_CHECKING, Union import IPython.display import ipywidgets @@ -25,12 +27,132 @@ from .bloq_counts_graph import format_counts_sigma, GraphvizCallGraph from .flame_graph import get_flame_graph_svg_data from .graphviz import PrettyGraphDrawer, TypedGraphDrawer -from .musical_score import draw_musical_score, get_musical_score_data +from .musical_score import MusicalScoreData, TextBox, draw_musical_score, get_musical_score_data from .qpic_diagram import qpic_diagram_for_bloq if TYPE_CHECKING: import networkx as nx - import sympy + + +def pretty_format_msd(msd: MusicalScoreData) -> MusicalScoreData: + """ + Beautifies MSD to enable pretty diagrams. + + Args: + msd: The original MusicalScoreData that wants to be diagramised. + + Returns: + pretty_msd: A beautified MSD + + """ + + def symbolic_abs(n_or_s: Union[int, float, 'sympy.Symbol']) -> int: + """ + Handles absolute value operations that may or may not include a symbol + + Args: + n_or_s: The number or symbol to process. + + Returns: + The absolute value of a numeric input or 1 (integer) if n_or_s is symbol. + + """ + + if isinstance(n_or_s, sympy.Symbol): + return 1 + return sympy.Abs(n_or_s) + + sympify_locals = { + "expression": { + "Y": sympy.Symbol("Y"), + "Abs": symbolic_abs, + }, + "exponent": { + "Min": sympy.Min, + "ceiling": sympy.ceiling, + "log2": lambda x: sympy.log(x, 2), + "Abs": symbolic_abs, + "Y": sympy.Symbol("Y"), + } + } + + new_soqs = [] + for soq_data in msd.soqs: + if isinstance(soq_data.symb, (TextBox, Text)): + original_lbl = soq_data.symb.text + abs_pattern = r"Abs\((?P[a-zA-Z])\)" + match = re.search(abs_pattern, original_lbl) + + if match: + symbol_value = match.group("symbol_value") + removals = r"\*" + re.escape(symbol_value) + r"$" + modified_lbl = re.sub(removals, "", original_lbl) + split_text = re.split(r"\^", modified_lbl, 1) + gate_clean = split_text[0] + + if len(split_text) > 1: + lbl_raw = split_text[1].strip() + lbl_clean = lbl_raw + base, exponent_raw = lbl_raw.split("**") + try: + exponent_sympified = sympy.sympify(exponent_raw, locals=sympify_locals["exponent"], evaluate=True) + exponent_clean = str(sympy.Integer(exponent_sympified.evalf(chop=True))) + lbl_reconstructed = base + "**" + exponent_clean + try: + lbl_reconstructed_sympified = sympy.sympify(lbl_reconstructed, locals=sympify_locals["expression"], evaluate=True) + lbl_to_clean = lbl_reconstructed_sympified + if not lbl_reconstructed_sympified.is_number: + symbols_to_substitute = lbl_reconstructed_sympified.free_symbols + subs_map = {s: sympy.Integer(1) for s in symbols_to_substitute} + lbl_to_clean = lbl_reconstructed_sympified.subs(subs_map) + lbl_clean = str(sympy.simplify(lbl_to_clean)) + try: + int_val = int(float(lbl_clean)) + if int_val == float(lbl_clean): + lbl_clean = str(int_val) + except ValueError: + pass + except (sympy.SympifyError, NameError, TypeError, ValueError) as e: + lbl_clean = lbl_reconstructed + except (sympy.SympifyError, NameError, TypeError, ValueError) as e: + lbl_clean = lbl_raw + else: + lbl_clean = "" + + new_lbl = ( + f"{gate_clean + '^' if gate_clean else ''}" + f"{symbol_value + '*' if symbol_value else ''}" + f"{lbl_clean if lbl_clean else ''}" + ) + + new_symb = None + if isinstance(soq_data.symb, TextBox): + new_symb = TextBox(text=new_lbl) + elif isinstance(soq_data.symb, Text): + new_symb = Text(text=new_lbl, fontsize=soq_data.symb.fontsize) + + new_soq_data = soq_data.__class__( + symb=new_symb, + rpos=soq_data.rpos, + ident=soq_data.ident + ) + new_soqs.append(new_soq_data) + + else: + new_soqs.append(soq_data) + + else: + new_soqs.append(soq_data) + + pretty_msd = MusicalScoreData( + max_x=msd.max_x, + max_y=msd.max_y, + soqs=new_soqs, + hlines=msd.hlines, + vlines=msd.vlines + ) + + return pretty_msd def show_bloq(bloq: 'Bloq', type: str = 'graph'): # pylint: disable=redefined-builtin @@ -49,7 +171,9 @@ def show_bloq(bloq: 'Bloq', type: str = 'graph'): # pylint: disable=redefined-b elif type.lower() == 'dtype': IPython.display.display(TypedGraphDrawer(bloq).get_svg()) elif type.lower() == 'musical_score': - draw_musical_score(get_musical_score_data(bloq)) + msd = get_musical_score_data(bloq) + pretty_msd = pretty_format_msd(msd) + draw_musical_score(pretty_msd) elif type.lower() == 'latex': show_bloq_via_qpic(bloq) else: From 2e5de4676d55380dd40759233211a78d868c53e3 Mon Sep 17 00:00:00 2001 From: J <80164315+jbolns@users.noreply.github.com> Date: Tue, 3 Jun 2025 13:59:43 +0300 Subject: [PATCH 2/5] pretty msds --- qualtran/drawing/_show_funcs.py | 201 +++++++++++++------------------- 1 file changed, 79 insertions(+), 122 deletions(-) diff --git a/qualtran/drawing/_show_funcs.py b/qualtran/drawing/_show_funcs.py index d4087badcf..331d583014 100644 --- a/qualtran/drawing/_show_funcs.py +++ b/qualtran/drawing/_show_funcs.py @@ -17,7 +17,7 @@ import os import re import sympy -from typing import Dict, Optional, Text, overload, Sequence, TYPE_CHECKING, Union +from typing import Dict, Optional, Text, overload, Sequence, TYPE_CHECKING, Union, Tuple import IPython.display import ipywidgets @@ -32,128 +32,7 @@ if TYPE_CHECKING: import networkx as nx - - -def pretty_format_msd(msd: MusicalScoreData) -> MusicalScoreData: - """ - Beautifies MSD to enable pretty diagrams. - Args: - msd: The original MusicalScoreData that wants to be diagramised. - - Returns: - pretty_msd: A beautified MSD - - """ - - def symbolic_abs(n_or_s: Union[int, float, 'sympy.Symbol']) -> int: - """ - Handles absolute value operations that may or may not include a symbol - - Args: - n_or_s: The number or symbol to process. - - Returns: - The absolute value of a numeric input or 1 (integer) if n_or_s is symbol. - - """ - - if isinstance(n_or_s, sympy.Symbol): - return 1 - return sympy.Abs(n_or_s) - - sympify_locals = { - "expression": { - "Y": sympy.Symbol("Y"), - "Abs": symbolic_abs, - }, - "exponent": { - "Min": sympy.Min, - "ceiling": sympy.ceiling, - "log2": lambda x: sympy.log(x, 2), - "Abs": symbolic_abs, - "Y": sympy.Symbol("Y"), - } - } - - new_soqs = [] - for soq_data in msd.soqs: - if isinstance(soq_data.symb, (TextBox, Text)): - original_lbl = soq_data.symb.text - abs_pattern = r"Abs\((?P[a-zA-Z])\)" - match = re.search(abs_pattern, original_lbl) - - if match: - symbol_value = match.group("symbol_value") - removals = r"\*" + re.escape(symbol_value) + r"$" - modified_lbl = re.sub(removals, "", original_lbl) - split_text = re.split(r"\^", modified_lbl, 1) - gate_clean = split_text[0] - - if len(split_text) > 1: - lbl_raw = split_text[1].strip() - lbl_clean = lbl_raw - base, exponent_raw = lbl_raw.split("**") - try: - exponent_sympified = sympy.sympify(exponent_raw, locals=sympify_locals["exponent"], evaluate=True) - exponent_clean = str(sympy.Integer(exponent_sympified.evalf(chop=True))) - lbl_reconstructed = base + "**" + exponent_clean - try: - lbl_reconstructed_sympified = sympy.sympify(lbl_reconstructed, locals=sympify_locals["expression"], evaluate=True) - lbl_to_clean = lbl_reconstructed_sympified - if not lbl_reconstructed_sympified.is_number: - symbols_to_substitute = lbl_reconstructed_sympified.free_symbols - subs_map = {s: sympy.Integer(1) for s in symbols_to_substitute} - lbl_to_clean = lbl_reconstructed_sympified.subs(subs_map) - lbl_clean = str(sympy.simplify(lbl_to_clean)) - try: - int_val = int(float(lbl_clean)) - if int_val == float(lbl_clean): - lbl_clean = str(int_val) - except ValueError: - pass - except (sympy.SympifyError, NameError, TypeError, ValueError) as e: - lbl_clean = lbl_reconstructed - except (sympy.SympifyError, NameError, TypeError, ValueError) as e: - lbl_clean = lbl_raw - else: - lbl_clean = "" - - new_lbl = ( - f"{gate_clean + '^' if gate_clean else ''}" - f"{symbol_value + '*' if symbol_value else ''}" - f"{lbl_clean if lbl_clean else ''}" - ) - - new_symb = None - if isinstance(soq_data.symb, TextBox): - new_symb = TextBox(text=new_lbl) - elif isinstance(soq_data.symb, Text): - new_symb = Text(text=new_lbl, fontsize=soq_data.symb.fontsize) - - new_soq_data = soq_data.__class__( - symb=new_symb, - rpos=soq_data.rpos, - ident=soq_data.ident - ) - new_soqs.append(new_soq_data) - - else: - new_soqs.append(soq_data) - - else: - new_soqs.append(soq_data) - - pretty_msd = MusicalScoreData( - max_x=msd.max_x, - max_y=msd.max_y, - soqs=new_soqs, - hlines=msd.hlines, - vlines=msd.vlines - ) - - return pretty_msd - def show_bloq(bloq: 'Bloq', type: str = 'graph'): # pylint: disable=redefined-builtin """Display a visual representation of the bloq in IPython. @@ -266,3 +145,81 @@ def show_bloq_via_qpic(bloq: 'Bloq', width: int = 1000, height: int = 400): IPython.display.display(Image(output_file_path, width=width, height=height)) os.remove(output_file_path) + + +def pretty_format_msd(msd: MusicalScoreData) -> MusicalScoreData: + """ + Beautifies MSD to enable pretty diagrams + + Args: + msd: A raw MSD + + Returns: + new_msd: A pretty MSD + + """ + + def symbols_to_identity(lbl: str) -> Tuple[str, str]: + """ + Exchanges any symbols in the label for integer 1 or returns lbl if no symbols found. + + Args: + lbl: The label to be processed. + + Returns: + new_lbl: A label without symbols + + """ + + pattern = r"Abs\((?P[a-zA-Z])\)" + match = re.search(pattern, lbl) + if match: + symbol = match.group("symbol") + new_lbl = lbl.replace(symbol, "1") + return new_lbl, symbol + return lbl, "" + + simpify_locals = { + "Min": sympy.Min, + "ceiling": sympy.ceiling, + "log2": lambda x: sympy.log(x, 2) + } + + mult = 1 + pretty_soqs = [] + for soq_item in msd.soqs: + if isinstance(soq_item.symb, (TextBox, Text)): + try: + lbl_raw = soq_item.symb.text + lbl_no_symbols, symbol = symbols_to_identity(lbl_raw) + gate, base, exponent = sum([p.split("**", 1) for p in lbl_no_symbols.split("^")], []) + + if len(base.split("*", 1)) > 1: + mult_str, base = base.rsplit("*", 1) + mult = sympy.sympify(mult_str, evaluate=True) + + exponent = sympy.sympify(exponent, locals=simpify_locals, evaluate=True) + expression = str(base) + "**" + str(exponent) + expression = sympy.sympify(expression, locals=simpify_locals, evaluate=True) + new_lbl = str(gate) + "^" + symbol + "*" + str(expression * mult) + + new_soq = soq_item.__class__( + symb= TextBox(text=new_lbl) if isinstance(soq_item.symb, TextBox) else Text(text=new_lbl, fontsize=soq_item.symb.fontsize), + rpos= soq_item.rpos, + ident= soq_item.ident + ) + pretty_soqs.append(new_soq) + except (ValueError, TypeError, NameError, sympy.SympifyError): + pretty_soqs.append(soq_item) + else: + pretty_soqs.append(soq_item) + + pretty_msd = MusicalScoreData( + max_x=msd.max_x, + max_y=msd.max_y, + soqs=pretty_soqs, + hlines=msd.hlines, + vlines=msd.vlines + ) + + return pretty_msd From 54c4f26f372921db1a29705f23ea131f97804bec Mon Sep 17 00:00:00 2001 From: J <80164315+jbolns@users.noreply.github.com> Date: Wed, 4 Jun 2025 23:34:56 +0300 Subject: [PATCH 3/5] pretty formatting moved to musical_score.py, improved readability, & pytest --- .../rotations/quantum_variable_rotation.py | 3 +- qualtran/drawing/_show_funcs.py | 88 +---------------- qualtran/drawing/musical_score.py | 94 ++++++++++++++++++- qualtran/drawing/musical_score_test.py | 11 ++- 4 files changed, 109 insertions(+), 87 deletions(-) diff --git a/qualtran/bloqs/rotations/quantum_variable_rotation.py b/qualtran/bloqs/rotations/quantum_variable_rotation.py index 5883d0fc19..a9b402256c 100644 --- a/qualtran/bloqs/rotations/quantum_variable_rotation.py +++ b/qualtran/bloqs/rotations/quantum_variable_rotation.py @@ -191,10 +191,11 @@ def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: 'SoquetT') -> Dict[str if self.cost_dtype.signed: out[0] = bb.add(ZPowGate(exponent=1, eps=eps), q=out[0]) offset = 1 + self.cost_dtype.num_frac - self.num_frac_rotations - offset = offset if isinstance(offset, int) else 1 + for i in range(num_rotations): power_of_two = i - self.num_frac_rotations exp = (2**power_of_two) * self.gamma * 2 + offset = offset if isinstance(offset, int) else 1 # offset -> 1 if not int to avoid indexing errors out[-(i + offset)] = bb.add(ZPowGate(exponent=exp, eps=eps), q=out[-(i + offset)]) return {self.cost_reg.name: bb.join(out, self.cost_dtype)} diff --git a/qualtran/drawing/_show_funcs.py b/qualtran/drawing/_show_funcs.py index 331d583014..7bf183aa13 100644 --- a/qualtran/drawing/_show_funcs.py +++ b/qualtran/drawing/_show_funcs.py @@ -15,9 +15,7 @@ """Convenience functions for showing rich displays in Jupyter notebook.""" import os -import re -import sympy -from typing import Dict, Optional, Text, overload, Sequence, TYPE_CHECKING, Union, Tuple +from typing import Dict, Optional, overload, Sequence, TYPE_CHECKING, Union import IPython.display import ipywidgets @@ -27,11 +25,12 @@ from .bloq_counts_graph import format_counts_sigma, GraphvizCallGraph from .flame_graph import get_flame_graph_svg_data from .graphviz import PrettyGraphDrawer, TypedGraphDrawer -from .musical_score import MusicalScoreData, TextBox, draw_musical_score, get_musical_score_data +from .musical_score import draw_musical_score, get_musical_score_data from .qpic_diagram import qpic_diagram_for_bloq if TYPE_CHECKING: import networkx as nx + import sympy def show_bloq(bloq: 'Bloq', type: str = 'graph'): # pylint: disable=redefined-builtin @@ -51,8 +50,7 @@ def show_bloq(bloq: 'Bloq', type: str = 'graph'): # pylint: disable=redefined-b IPython.display.display(TypedGraphDrawer(bloq).get_svg()) elif type.lower() == 'musical_score': msd = get_musical_score_data(bloq) - pretty_msd = pretty_format_msd(msd) - draw_musical_score(pretty_msd) + draw_musical_score(msd, pretty_print=True) elif type.lower() == 'latex': show_bloq_via_qpic(bloq) else: @@ -145,81 +143,3 @@ def show_bloq_via_qpic(bloq: 'Bloq', width: int = 1000, height: int = 400): IPython.display.display(Image(output_file_path, width=width, height=height)) os.remove(output_file_path) - - -def pretty_format_msd(msd: MusicalScoreData) -> MusicalScoreData: - """ - Beautifies MSD to enable pretty diagrams - - Args: - msd: A raw MSD - - Returns: - new_msd: A pretty MSD - - """ - - def symbols_to_identity(lbl: str) -> Tuple[str, str]: - """ - Exchanges any symbols in the label for integer 1 or returns lbl if no symbols found. - - Args: - lbl: The label to be processed. - - Returns: - new_lbl: A label without symbols - - """ - - pattern = r"Abs\((?P[a-zA-Z])\)" - match = re.search(pattern, lbl) - if match: - symbol = match.group("symbol") - new_lbl = lbl.replace(symbol, "1") - return new_lbl, symbol - return lbl, "" - - simpify_locals = { - "Min": sympy.Min, - "ceiling": sympy.ceiling, - "log2": lambda x: sympy.log(x, 2) - } - - mult = 1 - pretty_soqs = [] - for soq_item in msd.soqs: - if isinstance(soq_item.symb, (TextBox, Text)): - try: - lbl_raw = soq_item.symb.text - lbl_no_symbols, symbol = symbols_to_identity(lbl_raw) - gate, base, exponent = sum([p.split("**", 1) for p in lbl_no_symbols.split("^")], []) - - if len(base.split("*", 1)) > 1: - mult_str, base = base.rsplit("*", 1) - mult = sympy.sympify(mult_str, evaluate=True) - - exponent = sympy.sympify(exponent, locals=simpify_locals, evaluate=True) - expression = str(base) + "**" + str(exponent) - expression = sympy.sympify(expression, locals=simpify_locals, evaluate=True) - new_lbl = str(gate) + "^" + symbol + "*" + str(expression * mult) - - new_soq = soq_item.__class__( - symb= TextBox(text=new_lbl) if isinstance(soq_item.symb, TextBox) else Text(text=new_lbl, fontsize=soq_item.symb.fontsize), - rpos= soq_item.rpos, - ident= soq_item.ident - ) - pretty_soqs.append(new_soq) - except (ValueError, TypeError, NameError, sympy.SympifyError): - pretty_soqs.append(soq_item) - else: - pretty_soqs.append(soq_item) - - pretty_msd = MusicalScoreData( - max_x=msd.max_x, - max_y=msd.max_y, - soqs=pretty_soqs, - hlines=msd.hlines, - vlines=msd.vlines - ) - - return pretty_msd diff --git a/qualtran/drawing/musical_score.py b/qualtran/drawing/musical_score.py index 065f2312c4..273afa4f0f 100644 --- a/qualtran/drawing/musical_score.py +++ b/qualtran/drawing/musical_score.py @@ -27,6 +27,9 @@ import attrs import networkx as nx import numpy as np +import re +import sympy + from attrs import frozen, mutable from matplotlib import pyplot as plt from numpy.typing import NDArray @@ -688,7 +691,9 @@ def draw_musical_score( unit_to_inches: float = 0.8, max_width: float = 10.0, max_height: float = 8.0, + pretty_print: bool = False ): + # First, set up data coordinate limits and figure size. # X coordinates go from -1 to max_x # with 1 unit of padding it goes from -2 to max_x+1 @@ -727,7 +732,22 @@ def draw_musical_score( vline.label.draw(ax, vline.x, vline.bottom_y - 0.5) for soq in msd.soqs: - symb = soq.symb + new_soq = soq + if pretty_print and isinstance(soq.symb, TextBox): + # Beautify text items if pretty_print is enabled and is TextBox + try: + pretty_text = pretty_format_soq_text(soq.symb.text) + except (ValueError, NameError, TypeError, sympy.SympifyError) as e: + pretty_text = soq.symb.text + + # Build new soq + new_soq = soq.__class__( + symb= TextBox(text=pretty_text), + rpos= soq.rpos, + ident= soq.ident + ) + + symb = new_soq.symb symb.draw(ax, soq.rpos.seq_x, soq.rpos.y) ax.set_xlim(xlim) @@ -750,3 +770,75 @@ def default(self, o: Any) -> Any: def dump_musical_score(msd: MusicalScoreData, name: str): with open(f'{name}.json', 'w') as f: json.dump(msd, f, indent=2, cls=MusicalScoreEncoder) + + +def pretty_format_soq_text(soq_text: str) -> str: + """ + Evaluates a single soq.symb.text item returning prettiest expression possible or original text if beautification fails. + + Args: + soq_text: A raw soq text (soq.symb.text) + + Returns: + pretty_soq_text: A pretty soq text + + """ + + def symbols_in_soq(raw_text: str) -> str: + """ + Identifies the symbols in the soq text based on existence of an substring "(Y)". + Limitations: + Currently identifies single-character symbols A-Z and a-z. + + Args: + raw_text: A raw soq string. + + Returns: + symbol: A string containing only the symbol as a string or an empty string if no symbol is found + + """ + + # The pattern searches for "(Y)" (with any character), targeting Abs() section of expressions + pattern = r"\(([a-zA-Z])\)" + match = re.search(pattern, raw_text) + + # If no such section is found, the function returns empty string that is used elsewhere as safety check + symbol = match.group(1) if match else "" + + return symbol + + + # Identify any symbols in soq text. + symbol = symbols_in_soq(soq_text) + + # If no symbol found: return original soq_text. + # Note. Careful if removing. + # Block is also a security check. + # User can change symbol that reaches this function. + # Simpify is vulnerable to string injection, so only valid symbols should be allowed through. + if symbol == "": + return soq_text + + # Simpify locals. They enhance sympify's own ability to evaluate match strings. + # Note. Abs(Y) expressions evaluated as Abs(np.pi) to enable full evaluation of string. + # It appears only very small angles in that part of the expression would make a difference. + simpify_locals = { + "Min": sympy.Min, + "ceiling": sympy.ceiling, + "log2": lambda y: sympy.log(y, 2), + "Abs": lambda y: sympy.Abs(np.pi), + "Y": sympy.Symbol("Y") + } + + # Evaluation + # Eval 1. Get the gate out of the way + gate, expression = soq_text.split("^", 1) + + # Eval 2. Evaluate mathematical expression using sympify (enhanced with locals) or default to original soq_text + try: + expression_sympified = sympy.sympify(expression, locals=simpify_locals, evaluate=True) + pretty_text = str(gate) + "^" + str(expression_sympified) + except(sympy.SympifyError, ValueError, NameError, TypeError) as e: + pretty_text = soq_text + + return pretty_text diff --git a/qualtran/drawing/musical_score_test.py b/qualtran/drawing/musical_score_test.py index db35be86a8..968031c38e 100644 --- a/qualtran/drawing/musical_score_test.py +++ b/qualtran/drawing/musical_score_test.py @@ -16,7 +16,7 @@ from qualtran.bloqs.mcmt import MultiAnd from qualtran.drawing import dump_musical_score, get_musical_score_data, HLine from qualtran.testing import execute_notebook - +from qualtran.drawing.musical_score import pretty_format_soq_text def test_dump_json(tmp_path): hline = HLine(y=10, seq_x_start=5, seq_x_end=6) @@ -32,6 +32,15 @@ def test_dump_json(tmp_path): dump_musical_score(msd, name=f'{tmp_path}/musical_score_example') +def test_pretty_format_soq_text(): + soq_text = "Z^2*2**(11 - Min(6, ceiling(log2(6283185307.17959*Abs(Y)))))*Y" + expected_str = "Z^64*Y" + assert expected_str == pretty_format_soq_text(soq_text) + soq_text = "Z^2*Y/2**Min(6, ceiling(log2(6283185307.17959*Abs(Y))))" + expected_str = "Z^Y/32" + assert expected_str == pretty_format_soq_text(soq_text) + + @pytest.mark.notebook def test_notebook(): execute_notebook('musical_score') From 32e57ae81fcd6f0d0a815b13e50ea7c5a3bf917a Mon Sep 17 00:00:00 2001 From: J <80164315+jbolns@users.noreply.github.com> Date: Sat, 7 Jun 2025 20:07:52 +0300 Subject: [PATCH 4/5] format_incremental checks (mypy, pylint, pytests all come clear, forgot this one) --- .../rotations/quantum_variable_rotation.py | 6 +++-- qualtran/drawing/_show_funcs.py | 2 +- qualtran/drawing/musical_score.py | 24 +++++++------------ qualtran/drawing/musical_score_test.py | 3 ++- 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/qualtran/bloqs/rotations/quantum_variable_rotation.py b/qualtran/bloqs/rotations/quantum_variable_rotation.py index a9b402256c..3fa55b4f74 100644 --- a/qualtran/bloqs/rotations/quantum_variable_rotation.py +++ b/qualtran/bloqs/rotations/quantum_variable_rotation.py @@ -191,11 +191,13 @@ def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: 'SoquetT') -> Dict[str if self.cost_dtype.signed: out[0] = bb.add(ZPowGate(exponent=1, eps=eps), q=out[0]) offset = 1 + self.cost_dtype.num_frac - self.num_frac_rotations - + for i in range(num_rotations): power_of_two = i - self.num_frac_rotations exp = (2**power_of_two) * self.gamma * 2 - offset = offset if isinstance(offset, int) else 1 # offset -> 1 if not int to avoid indexing errors + offset = ( + offset if isinstance(offset, int) else 1 + ) # offset -> 1 if not int to avoid indexing errors out[-(i + offset)] = bb.add(ZPowGate(exponent=exp, eps=eps), q=out[-(i + offset)]) return {self.cost_reg.name: bb.join(out, self.cost_dtype)} diff --git a/qualtran/drawing/_show_funcs.py b/qualtran/drawing/_show_funcs.py index 7bf183aa13..28bafb5d72 100644 --- a/qualtran/drawing/_show_funcs.py +++ b/qualtran/drawing/_show_funcs.py @@ -31,7 +31,7 @@ if TYPE_CHECKING: import networkx as nx import sympy - + def show_bloq(bloq: 'Bloq', type: str = 'graph'): # pylint: disable=redefined-builtin """Display a visual representation of the bloq in IPython. diff --git a/qualtran/drawing/musical_score.py b/qualtran/drawing/musical_score.py index 273afa4f0f..3d79f1d63a 100644 --- a/qualtran/drawing/musical_score.py +++ b/qualtran/drawing/musical_score.py @@ -21,15 +21,14 @@ import abc import heapq import json +import re from enum import Enum from typing import Any, Callable, cast, Dict, Iterable, List, Optional, Set, Tuple, Union import attrs import networkx as nx import numpy as np -import re import sympy - from attrs import frozen, mutable from matplotlib import pyplot as plt from numpy.typing import NDArray @@ -691,9 +690,9 @@ def draw_musical_score( unit_to_inches: float = 0.8, max_width: float = 10.0, max_height: float = 8.0, - pretty_print: bool = False + pretty_print: bool = False, ): - + # First, set up data coordinate limits and figure size. # X coordinates go from -1 to max_x # with 1 unit of padding it goes from -2 to max_x+1 @@ -741,11 +740,7 @@ def draw_musical_score( pretty_text = soq.symb.text # Build new soq - new_soq = soq.__class__( - symb= TextBox(text=pretty_text), - rpos= soq.rpos, - ident= soq.ident - ) + new_soq = soq.__class__(symb=TextBox(text=pretty_text), rpos=soq.rpos, ident=soq.ident) symb = new_soq.symb symb.draw(ax, soq.rpos.seq_x, soq.rpos.y) @@ -792,7 +787,7 @@ def symbols_in_soq(raw_text: str) -> str: Args: raw_text: A raw soq string. - + Returns: symbol: A string containing only the symbol as a string or an empty string if no symbol is found @@ -801,12 +796,11 @@ def symbols_in_soq(raw_text: str) -> str: # The pattern searches for "(Y)" (with any character), targeting Abs() section of expressions pattern = r"\(([a-zA-Z])\)" match = re.search(pattern, raw_text) - + # If no such section is found, the function returns empty string that is used elsewhere as safety check symbol = match.group(1) if match else "" - - return symbol + return symbol # Identify any symbols in soq text. symbol = symbols_in_soq(soq_text) @@ -827,7 +821,7 @@ def symbols_in_soq(raw_text: str) -> str: "ceiling": sympy.ceiling, "log2": lambda y: sympy.log(y, 2), "Abs": lambda y: sympy.Abs(np.pi), - "Y": sympy.Symbol("Y") + "Y": sympy.Symbol("Y"), } # Evaluation @@ -838,7 +832,7 @@ def symbols_in_soq(raw_text: str) -> str: try: expression_sympified = sympy.sympify(expression, locals=simpify_locals, evaluate=True) pretty_text = str(gate) + "^" + str(expression_sympified) - except(sympy.SympifyError, ValueError, NameError, TypeError) as e: + except (sympy.SympifyError, ValueError, NameError, TypeError) as e: pretty_text = soq_text return pretty_text diff --git a/qualtran/drawing/musical_score_test.py b/qualtran/drawing/musical_score_test.py index 968031c38e..7ed716a4e2 100644 --- a/qualtran/drawing/musical_score_test.py +++ b/qualtran/drawing/musical_score_test.py @@ -15,8 +15,9 @@ from qualtran.bloqs.mcmt import MultiAnd from qualtran.drawing import dump_musical_score, get_musical_score_data, HLine -from qualtran.testing import execute_notebook from qualtran.drawing.musical_score import pretty_format_soq_text +from qualtran.testing import execute_notebook + def test_dump_json(tmp_path): hline = HLine(y=10, seq_x_start=5, seq_x_end=6) From c0720a8833e0aee4d9244e9c296cb2497770d444 Mon Sep 17 00:00:00 2001 From: J <80164315+jbolns@users.noreply.github.com> Date: Fri, 20 Jun 2025 22:42:00 +0300 Subject: [PATCH 5/5] Recover original MSD prints No. 1 --- qualtran/bloqs/rotations/quantum_variable_rotation.py | 5 +++-- qualtran/drawing/_show_funcs.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/qualtran/bloqs/rotations/quantum_variable_rotation.py b/qualtran/bloqs/rotations/quantum_variable_rotation.py index 3fa55b4f74..3288e7e39d 100644 --- a/qualtran/bloqs/rotations/quantum_variable_rotation.py +++ b/qualtran/bloqs/rotations/quantum_variable_rotation.py @@ -188,12 +188,13 @@ def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: 'SoquetT') -> Dict[str else self.cost_reg.total_bits() ) eps = self.eps / num_rotations + if self.cost_dtype.signed: out[0] = bb.add(ZPowGate(exponent=1, eps=eps), q=out[0]) - offset = 1 + self.cost_dtype.num_frac - self.num_frac_rotations + offset = 1 + self.cost_dtype.num_frac - self.num_frac_rotations for i in range(num_rotations): - power_of_two = i - self.num_frac_rotations + power_of_two = i - self.cost_dtype.num_frac exp = (2**power_of_two) * self.gamma * 2 offset = ( offset if isinstance(offset, int) else 1 diff --git a/qualtran/drawing/_show_funcs.py b/qualtran/drawing/_show_funcs.py index 28bafb5d72..bd4ac7f108 100644 --- a/qualtran/drawing/_show_funcs.py +++ b/qualtran/drawing/_show_funcs.py @@ -50,7 +50,7 @@ def show_bloq(bloq: 'Bloq', type: str = 'graph'): # pylint: disable=redefined-b IPython.display.display(TypedGraphDrawer(bloq).get_svg()) elif type.lower() == 'musical_score': msd = get_musical_score_data(bloq) - draw_musical_score(msd, pretty_print=True) + draw_musical_score(msd, pretty_print=False) elif type.lower() == 'latex': show_bloq_via_qpic(bloq) else: