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

Cosmetic/pretty printing of compiler output #599

Closed
wants to merge 37 commits into from
Closed
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
46dce86
added basic repr_pretty
dowusu-antwi Mar 8, 2023
e1bc78c
Merge branch 'main' into aqt-hr
dowusu-antwi Mar 9, 2023
5cabc1f
Merge branch 'main' into aqt-hr
dowusu-antwi Mar 9, 2023
191f1fd
added repr_pretty and simple test
dowusu-antwi Mar 9, 2023
54abf18
Merge remote-tracking branch 'origin' into aqt-hr
dowusu-antwi Mar 21, 2023
b3e53be
Merge branch 'main' into aqt-hr
dowusu-antwi Mar 30, 2023
050dbba
cosmetic printing for aqt compiler_output
dowusu-antwi Apr 27, 2023
4eb4816
Merge branch 'main' into aqt-hr
dowusu-antwi Apr 27, 2023
a8605f9
refactored qtrl repr_pretty to only run when qtrl installed
dowusu-antwi May 8, 2023
b911f9e
added css compiler output changes
dowusu-antwi May 8, 2023
dfb906a
Merge branch 'main' into aqt-hr
dowusu-antwi May 8, 2023
a627979
fixed test pretty_repr output typos
dowusu-antwi May 8, 2023
c53f804
added pulse schedule pretty print
dowusu-antwi May 9, 2023
3efdf5b
simplify repr using black
dowusu-antwi May 15, 2023
4d5cd35
fix merge conflicts + edits
dowusu-antwi Jul 3, 2023
a1be870
repr pretty for coverage
dowusu-antwi Jul 3, 2023
4697b3f
clean up prettify for circuit objects + tests
dowusu-antwi Jul 4, 2023
23b394a
test-driven changes
dowusu-antwi Jul 4, 2023
ac74e37
don't need circuit placeholders for cirq
dowusu-antwi Jul 6, 2023
ed17193
Merge branch 'main' into aqt-hr
dowusu-antwi Jul 11, 2023
860bb86
refactored pretty_print and moved to separate file
dowusu-antwi Jul 20, 2023
7a22155
fixed merge conflict
dowusu-antwi Jul 20, 2023
a0c2124
moved pretty printing to separate gss file
dowusu-antwi Jul 21, 2023
e37fa9d
use gss pretty_printing
dowusu-antwi Jul 21, 2023
c0ae76c
tests for pretty printing
dowusu-antwi Jul 21, 2023
10d4923
Merge branch 'main' into aqt-hr
dowusu-antwi Jul 21, 2023
a021d4d
Merge branch 'main' into aqt-hr
dowusu-antwi Jul 27, 2023
f88ba0d
Merge branch 'main' into aqt-hr
dowusu-antwi Aug 1, 2023
d90f6d1
Merge branch 'main' into aqt-hr
dowusu-antwi Aug 11, 2023
309a6a6
micro-nit: typo
dowusu-antwi Aug 22, 2023
d020882
Merge branch 'main' into aqt-hr
dowusu-antwi Aug 22, 2023
6191636
move black to requirements
dowusu-antwi Aug 22, 2023
88aa0af
Merge branch 'main' into aqt-hr
dowusu-antwi Sep 4, 2023
26f84b5
Merge branch 'main' into aqt-hr
dowusu-antwi Sep 14, 2023
a120f9d
added extra attribute for pule gate circuits + requirement supdate
dowusu-antwi Sep 15, 2023
57f6e15
Merge branch 'main' into aqt-hr
dowusu-antwi Sep 15, 2023
13b987e
Merge branch 'main' into aqt-hr
dowusu-antwi Sep 22, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions cirq-superstaq/cirq_superstaq/compiler_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,23 @@ def __repr__(self) -> str:
f"{self.pulse_lists!r})"
)

def __repr_pretty__(self) -> str:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to be picked up by ipython/jupyter/etc i think this needs to be

Suggested change
def __repr_pretty__(self) -> str:
def _repr_pretty_(self, p, cycle=False) -> None:
...
p.text(<string>)

(see https://ipython.readthedocs.io/en/stable/config/integrating.html#pretty-printing)

if not self.has_multiple_circuits():
circuit_reprs = [repr(self.circuit)]
circuit_drawings = [str(self.circuit)]
else:
circuit_reprs = []
circuit_drawings = []
for circuit in self.circuits:
circuit_reprs.append(repr(circuit))
circuit_drawings.append(str(circuit))
if self.seq: # pragma: no cover, requires qtrl installation
circuit_reprs.append(repr(self.seq))
circuit_drawings.append(repr(self.seq))
return gss.pretty_printing.pretty_print_compiler_output(
repr(self), circuit_reprs, circuit_drawings
)


def read_json(json_dict: Dict[str, Any], circuits_is_list: bool) -> CompilerOutput:
"""Reads out returned JSON from Superstaq API's IBMQ compilation endpoint.
Expand Down
89 changes: 87 additions & 2 deletions cirq-superstaq/cirq_superstaq/compiler_output_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,96 @@ def test_compiler_output_repr() -> None:
repr(css.compiler_output.CompilerOutput(circuit, qubit_map))
== f"CompilerOutput({circuit!r}, {{}}, None, None, None, None)"
)
assert (
css.compiler_output.CompilerOutput(circuit, qubit_map).__repr_pretty__()
== f"CompilerOutput({circuit!r}, {{}}, None, None, None, None)\n"
)

circuits = [circuit, circuit]
assert (
repr(css.compiler_output.CompilerOutput(circuits, [qubit_map]))
== f"CompilerOutput({circuits!r}, [{{}}], None, None, None, None)"
repr(css.compiler_output.CompilerOutput(circuits, [qubit_map, qubit_map]))
== f"CompilerOutput({circuits!r}, [{{}}, {{}}], None, None, None, None)"
)
assert css.compiler_output.CompilerOutput(
circuits, [qubit_map, qubit_map]
).__repr_pretty__() == (
"CompilerOutput("
"\n [cirq.Circuit(), cirq.Circuit()],\n"
" [{}, {}],\n"
" None,\n"
" None,\n"
" None,\n"
" None,\n"
")\n"
)

circuits = [circuit] * 10
assert css.compiler_output.CompilerOutput(
circuits, [qubit_map, qubit_map]
).__repr_pretty__() == (
"CompilerOutput(\n"
" [\n"
f" {circuits[0]!r},\n"
" \n"
Copy link
Contributor

@richrines1 richrines1 Aug 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible/easy to avoid these spaces on black *blank* lines?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean replace them with \t tab characters?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops sorry that was supposed to say "blank lines" - my thinking was just that there's no reason to include spaces in otherwise empty lines, e.g. this line could just be

Suggested change
" \n"
"\n"

f" {circuits[1]!r},\n"
" \n"
f" {circuits[2]!r},\n"
" \n"
f" {circuits[3]!r},\n"
" \n"
f" {circuits[4]!r},\n"
" \n"
f" {circuits[5]!r},\n"
" \n"
f" {circuits[6]!r},\n"
" \n"
f" {circuits[7]!r},\n"
" \n"
f" {circuits[8]!r},\n"
" \n"
f" {circuits[9]!r},\n"
" ],\n"
" [{}, {}],\n"
" None,\n"
" None,\n"
" None,\n"
" None,\n"
")\n"
)


def test_compiler_output_pretty_repr() -> None: # pragma: no cover; test requires qtrl installation
# Tests more involved "pretty" repr
qtrl = pytest.importorskip("qtrl", reason="qtrl not installed")
mock_pulse = qtrl.sequencer.UniquePulse(
envelope=qtrl.sequencer.VirtualEnvelope(0.0, 0.0),
freq=0.0,
channel=0.0,
subchannel=0.0,
)

circuit = cirq.Circuit()
qubit_map: Dict[cirq.Qid, cirq.Qid] = {}

circuits = [circuit, circuit]
assert css.compiler_output.CompilerOutput(
circuits,
[qubit_map],
pulse_lists=[mock_pulse],
).__repr_pretty__() == (
"CompilerOutput(\n"
f" [{circuits[0]!r}, {circuits[1]!r}],\n"
" [{}],\n"
" None,\n"
" None,\n"
" None,\n"
" [\n"
" UniquePulse(\n"
" envelope=VirtualEnvelope(phase=0.0, width=0.0), freq=0.0, channel=0.0, "
"subchannel=0.0\n"
" )\n"
" ],\n"
")\n"
)


Expand Down
11 changes: 10 additions & 1 deletion general-superstaq/general_superstaq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,15 @@
)
from general_superstaq.typing import QuboModel

from . import qubo, serialization, service, superstaq_client, superstaq_exceptions, validation
from . import (
pretty_printing,
qubo,
serialization,
service,
superstaq_client,
superstaq_exceptions,
validation,
)

__all__ = [
"__version__",
Expand All @@ -20,6 +28,7 @@
"SuperstaqUnsuccessfulJobException",
"SuperstaqServerException",
"QuboModel",
"pretty_printing",
"qubo",
"ResourceEstimate",
"serialization",
Expand Down
245 changes: 245 additions & 0 deletions general-superstaq/general_superstaq/pretty_printing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
from typing import List

import black

MAX_LINE_LENGTH = 100


def get_circuit_drawing_lines(circuit_drawing: str, circuit_repr: str) -> List[str]:
"""Gets a list of the lines making up a circuit drawing.

Args:
circuit_drawing: The single-line str representation of a multi-line circuit drawing.
circuit_repr: The repr for the circuit.

Returns:
The list of newline-separated circuit drawing lines.
"""
if circuit_drawing == "":
return [circuit_repr]
return circuit_drawing.split("\n")


def get_circuit_placeholder(circuit_repr: str, circuit_drawing: str, circuit_index: int = 0) -> str:
"""Gets placeholder text for a given circuit in the compiler output for Black parsing.

We want Black to format each circuit drawing (or repr if the drawing is too large). Black will
wrap lines and indent accordingly with respect to a maximum MAX_LINE_LENGTH. However, Black is
not sufficient because (a) it doesn't treat the lines of a circuit drawing as a contiguous
piece, and (b) it doesn't recognize the custom characters in some circuit drawings, e.g., in
Qiskit, so we instead replace each line with a unique placeholder (using only Black-recognizable
characters) that we then replace *after* using Black to format the compiler output repr.

We comma-separate the placeholder lines so that Black formatting converts, e.g.,

# Assume this line exceeds MAX_LINE_LENGTH
LINE_PLACEHOLDER_0, LINE_PLACEHOLDER_1, LINE_PLACEHOLDER_2,

into

LINE_PLACEHOLDER_0,
LINE_PLACEHOLDER_1,
LINE_PLACEHOLDER_2,

which we then replace to get, e.g.,

0: ───Rz(1.5π)───Rx(0.5π)───AceCR+-(Z side)───
1: ───Rx(0.5π)──────────────AceCR+-(X side)───,

and so on for any given circuit drawing. We use "quantum_circuit_M_line_N" as the Nth line
placeholder for the Mth quantum circuit in a sequence, or "quantum_circuit_M" as a placeholder
for the repr if the circuit drawing is too large, i.e., any given line is greater than some
fixed maximum length. We also add padding (using "x") to each placeholder to make it the same
length as the original line so Black formats it correctly.

Args:
circuit_repr: The repr for the circuit.
circuit_drawing: The single-line str representation of a multi-line circuit drawing.
circuit_index: The index of the circuit if it is in a list.

Returns:
A string placeholder for the circuit repr or drawing that can be parsed by Black.
"""
# Befor for loop, if circuit is empty or any given line is too long, use a single placeholder
dowusu-antwi marked this conversation as resolved.
Show resolved Hide resolved
# for the entire circuit, to be replaced with the circuit repr.
circuit_drawing_lines = get_circuit_drawing_lines(circuit_drawing, circuit_repr)
if (
circuit_drawing_lines == [circuit_repr]
or len(max(circuit_drawing_lines, key=len)) > MAX_LINE_LENGTH
):
placeholder = f"quantum_circuit_{circuit_index}"
padding = "x" * (len(circuit_repr) - len(placeholder))
return placeholder + padding

placeholder_lines = []
# TODO: try replacing the for loop with the commented-out line below, regarding parsing of
# custom characters placeholder_lines = circuit_drawing_lines # gives `KeyError` for qiskit;
# can we get Black to deal with it?
for line_index, line in enumerate(circuit_drawing_lines):
placeholder = f"quantum_circuit_{circuit_index}_{line_index}"
if len(placeholder) < len(line):
padding = "x" * (len(line) - len(placeholder))
placeholder += padding
placeholder_lines.append(placeholder)
return ", ".join(placeholder_lines)


def replace_circuit_placeholder(
text: str, circuit_repr: str, circuit_repr_placeholder: str, circuit_drawing: str
) -> str:
"""Replace given circuit drawing placeholder w/ corresponding circuit drawing.

Given the placeholder text for a circuit drawing, we consider each line and replace it with the
corresponding line in the circuit drawing. In order to preserve Black's formatting, we need to
replace the placeholder text line by line.

Args:
text: The output text containing a placeholder to replace.
circuit_repr: The circuit repr (to replace the placeholder if the drawing is too large).
circuit_repr_placeholder: The placeholder to replace with a drawing or repr.
circuit_drawing: The single-line circuit drawing.

Returns:
The input text modified by replacing placeholders with drawings (or reprs).
"""
circuit_placeholder_lines = circuit_repr_placeholder.split(", ")
circuit_drawing_lines = get_circuit_drawing_lines(circuit_drawing, circuit_repr)

# TODO fix bug, this isn't adding commas in the way that it should
# This will remove commas at the end of each circuit placeholder line except for the last line,
# in order to preserve comma-separation between circuits (and at the end of the last circuit, a
# la comma separation for Google style docstrings:
#
# LINE_PLACEHOLDER_0,
# LINE_PLACEHOLDER_1,
# LINE_PLACEHOLDER_2,
#
# becomes
#
# CIRCUIT_DRAWING_LINE_0
# CIRCUIT_DRAWING_LINE_1
# CIRCUIT_DRAWING_LINE_2,
#
# Note that only one comma remains.
for idx, (placeholder, line) in enumerate(
zip(circuit_placeholder_lines, circuit_drawing_lines)
):
# If it's one of the last two lines (but not a newline) leave the comma. O/w remove it.
if idx == len(circuit_placeholder_lines) - 1 and "newline" not in line:
text = text.replace(placeholder, line)
elif (
idx == len(circuit_placeholder_lines) - 2
and "newline" in circuit_placeholder_lines[idx + 1]
):
text = text.replace(placeholder, line)
else:
text = text.replace(placeholder + ",", line)
return text


def preprocess_compiler_output(
compiler_output_repr: str, circuit_reprs: List[str], circuit_repr_placeholders: List[str]
) -> str:
"""Replace parts of the compiler output unrecognizable to Black with placeholders.

Args:
compiler_output_repr: A repr for the compiler output.
circuit_reprs: A list of reprs for the compiler output circuits.
circuit_repr_placeholders: A list of placeholders to replace reprs in the compiler output.

Returns:
A compiler output repr, preprocessed to work with Black.
"""
# First, replace components that Black cannot parse: (a) an empty schedule will contain a
# parenthesis followed by a comma, (b) an empty logical-to-physical qubit mapping shows up as
# enclosed braces.
preprocessed_out = compiler_output_repr.replace("Schedule(,", "Schedule(None,").replace(
"{}", "empty_map"
)
circuit_reprs = ",|".join(circuit_reprs).split("|")
circuit_repr_placeholders = ", newline_placeholder,|".join(circuit_repr_placeholders).split("|")
for circuit_repr, placeholder in zip(circuit_reprs, circuit_repr_placeholders):
preprocessed_out = preprocessed_out.replace(circuit_repr, placeholder)
return preprocessed_out


def prettify(text: str) -> str:
"""Uses Black to format text.

Args:
text: A raw unformatted string.

Returns:
A string formatted to wrap at the hard-coded maximum line length.
"""
return black.format_str(text, mode=black.Mode(line_length=MAX_LINE_LENGTH))


def postprocess_compiler_output(
pretty_out: str,
circuit_reprs: List[str],
circuit_repr_placeholders: List[str],
circuit_drawings: List[str],
) -> str:
"""Replace each circuit drawing placeholder with the corresponding circuit drawing line.

Args:
pretty_out: The prettified preprocessed compiler output repr.
circuit_reprs: A list of reprs for the compiler output circuits.
circuit_repr_placeholders: A list of placeholders to replace reprs in the compiler output.
circuit_drawings: A list of circuit drawings.

Returns:
The final postprocessed prettified compiler output.
"""
# First, restore preprocessed non-circuit placeholders for (a) an empty schedule will contain
# a parenthesis followed by a comma, (b) an empty logical-to-physical qubit mapping shows up as
# enclosed braces.
postprocessed_out = (
pretty_out.replace("empty_map", "{}")
.replace("Schedule(None,", "Schedule(,")
.replace("newline_placeholder,", "")
)
circuit_repr_placeholders = ", newline_placeholder,|".join(circuit_repr_placeholders).split("|")
for circuit_repr, circuit_repr_placeholder, circuit_drawing in zip(
circuit_reprs, circuit_repr_placeholders, circuit_drawings
):
postprocessed_out = replace_circuit_placeholder(
postprocessed_out,
circuit_repr,
circuit_repr_placeholder,
circuit_drawing,
)
return postprocessed_out


def pretty_print_compiler_output(
compiler_output_repr: str,
circuit_reprs: List[str],
circuit_drawings: List[str],
) -> str:
"""Prints human-readable version of compiler output.

Args:
compiler_output_repr: A repr for the compiler output.
circuit_reprs: A list of reprs for the compiler output circuits.
circuit_drawings: A list of circuit drawings.

Returns
The final postprocessed prettified compiler output.:
"""
circuit_repr_placeholders = []
for circuit_index, (circuit_repr, circuit_drawing) in enumerate(
zip(circuit_reprs, circuit_drawings)
):
circuit_repr_placeholders.append(
get_circuit_placeholder(circuit_repr, circuit_drawing, circuit_index)
)
preprocessed_out = preprocess_compiler_output(
compiler_output_repr, circuit_reprs, circuit_repr_placeholders
)
pretty_out = prettify(preprocessed_out)
return postprocess_compiler_output(
pretty_out, circuit_reprs, circuit_repr_placeholders, circuit_drawings
)
Loading