From 90e3dc077ad525738449985fb6aa49bcf0409cc3 Mon Sep 17 00:00:00 2001 From: Eric Bertasi Date: Fri, 23 Aug 2024 14:35:40 +0200 Subject: [PATCH 1/5] Simplify rendering calls, by removing useless "content" and "**opts" parameters of all shapes --- perceval/components/__init__.py | 2 +- perceval/components/unitary_components.py | 3 - perceval/rendering/canvas/canvas.py | 14 +- perceval/rendering/circuit/abstract_skin.py | 10 +- perceval/rendering/circuit/debug_skin.py | 278 +------------------- perceval/rendering/circuit/phys_skin.py | 64 ++--- perceval/rendering/circuit/renderer.py | 91 +++---- perceval/rendering/circuit/symb_skin.py | 73 +++-- perceval/rendering/pdisplay.py | 13 +- perceval/utils/format.py | 10 - 10 files changed, 129 insertions(+), 429 deletions(-) diff --git a/perceval/components/__init__.py b/perceval/components/__init__.py index 07ceb354..28539bd7 100644 --- a/perceval/components/__init__.py +++ b/perceval/components/__init__.py @@ -38,7 +38,7 @@ get_pauli_eigenvectors) from .tomography_exp_configurer import processor_circuit_configurator from .comp_utils import decompose_perms -from .port import Port, Herald, PortLocation, get_basic_state_from_ports +from .port import APort, Port, Herald, PortLocation, get_basic_state_from_ports from .unitary_components import BSConvention, BS, PS, WP, HWP, QWP, PR, Unitary, PERM, PBS, Barrier from .non_unitary_components import TD, LC from .component_catalog import Catalog diff --git a/perceval/components/unitary_components.py b/perceval/components/unitary_components.py index 7886e84a..f006945c 100644 --- a/perceval/components/unitary_components.py +++ b/perceval/components/unitary_components.py @@ -368,9 +368,6 @@ def __init__(self, perm): u[v, i] = 1 super().__init__(U=u) - def get_variables(self): - return {'PERM': ''} - def describe(self): return f"PERM({self.perm_vector})" diff --git a/perceval/rendering/canvas/canvas.py b/perceval/rendering/canvas/canvas.py index 00616fe5..30ce62fe 100644 --- a/perceval/rendering/canvas/canvas.py +++ b/perceval/rendering/canvas/canvas.py @@ -144,12 +144,6 @@ def add_mpath(self, stroke_linejoin: str = "miter", stroke_dasharray=None): """Draw a path - - :param fill: - :param points: - :param stroke: - :param stroke_width: - :return: """ assert not self._drawn, "calling add_mpath on drawn canvas" norm_points = [] @@ -237,7 +231,7 @@ def add_circle(self, self.position = (points[0] + r, points[1] + r) self.position = (points[0] - r, points[1] - r) self.position = points - return (self.position[0], self._inverse_Y * self.position[1]) + return self.position[0], self._inverse_Y * self.position[1] def add_text(self, points: Tuple[float, float], text: str, size: float, @@ -253,10 +247,10 @@ def add_text(self, points: Tuple[float, float], else: self.position = (points[0]-size*len(text)/2, points[1]+size) self.position = (points[0]+size*len(text)/2, points[1]+size) - return (f_points[0], self._inverse_Y * f_points[1]) + return f_points[0], self._inverse_Y * f_points[1] - def add_shape(self, shape_fn, circuit, content, mode_style, **opt): - shape_fn(circuit, self, content, mode_style, **opt) + def add_shape(self, shape_fn, circuit, mode_style): + shape_fn(circuit, self, mode_style) def set_background_color(self, background_color): """ diff --git a/perceval/rendering/circuit/abstract_skin.py b/perceval/rendering/circuit/abstract_skin.py index 8b962f37..a73616e0 100644 --- a/perceval/rendering/circuit/abstract_skin.py +++ b/perceval/rendering/circuit/abstract_skin.py @@ -32,9 +32,8 @@ from typing import Callable, Tuple from multipledispatch import dispatch -from perceval.components import ACircuit, AProcessor, PERM -from perceval.components.abstract_component import AComponent -from perceval.components.non_unitary_components import TD +from perceval.components import ACircuit, AProcessor, PERM, AComponent, TD +from perceval.utils import format_parameters class ModeStyle(Enum): @@ -64,6 +63,8 @@ def __init__(self, stroke_style, style_subcircuit, compact_display: bool = False # ModeStyle.HERALD: {"stroke": "yellow", "stroke_width": 1} # Use this for debug } self.style_subcircuit = style_subcircuit + self.precision: float = 1e-6 + self.nsimplify: bool = True @dispatch((ACircuit, TD), bool) def get_size(self, c: ACircuit, recursive: bool = False) -> Tuple[int, int]: @@ -120,3 +121,6 @@ def get_width(self, c) -> int: @abstractmethod def get_shape(self, c) -> Callable: """Returns the shape function of component c""" + + def _get_display_content(self, circuit: ACircuit) -> str: + return format_parameters(circuit.get_variables(), self.precision, self.nsimplify) diff --git a/perceval/rendering/circuit/debug_skin.py b/perceval/rendering/circuit/debug_skin.py index 55c98a01..289595d2 100644 --- a/perceval/rendering/circuit/debug_skin.py +++ b/perceval/rendering/circuit/debug_skin.py @@ -33,218 +33,25 @@ from perceval.components import AComponent, Circuit, Port, Herald, PortLocation,\ unitary_components as cp,\ non_unitary_components as nu -from .abstract_skin import ASkin, ModeStyle +from .abstract_skin import ModeStyle +from .phys_skin import PhysSkin from .skin_common import bs_convention_color -class DebugSkin(ASkin): +class DebugSkin(PhysSkin): def __init__(self, compact_display: bool = False): - super().__init__({"stroke": "darkred", "stroke_width": 10}, - {"width": 2, - "fill": "lightpink", - "stroke_style": {"stroke": "darkred", "stroke_width": 1}}, - compact_display) + super().__init__(compact_display) + self.style[ModeStyle.PHOTONIC]["stroke_width"] = 8 self.style[ModeStyle.HERALD] = {"stroke": "yellow", "stroke_width": 1} # Display ancillary modes in yellow - @dispatch(AComponent) - def get_width(self, c) -> int: - """Absolute fallback""" - return 1 - - @dispatch(cp.Unitary) - def get_width(self, c) -> int: - return c.m - - @dispatch((Circuit, cp.BS, cp.PBS)) - def get_width(self, c) -> int: - return 2 - - @dispatch((cp.PS, nu.TD, cp.PERM, cp.WP, cp.PR, nu.LC)) - def get_width(self, c) -> int: - return 1 - - @dispatch(cp.Barrier) - def get_width(self, c) -> int: - return 1 if c.visible else 0 - - @dispatch(AComponent) - def get_shape(self, c): - return self.default_shape - - @dispatch(cp.BS) - def get_shape(self, c): - return self.bs_shape - - @dispatch(cp.PS) - def get_shape(self, c): - return self.ps_shape - - @dispatch(cp.PBS) - def get_shape(self, c): - return self.pbs_shape - - @dispatch(nu.TD) - def get_shape(self, c): - return self.td_shape - - @dispatch(cp.Unitary) - def get_shape(self, c): - return self.unitary_shape - - @dispatch(cp.PERM) - def get_shape(self, c): - return self.perm_shape - - @dispatch((cp.WP, cp.HWP, cp.QWP)) - def get_shape(self, c): - return self.wp_shape - - @dispatch(cp.PR) - def get_shape(self, c): - return self.pr_shape - - @dispatch(cp.Barrier) - def get_shape(self, c): - return self.barrier_shape - - @dispatch(nu.LC) - def get_shape(self, c): - return self.lc_shape - - @dispatch(Port, PortLocation) - def get_shape(self, port, location): - if location == PortLocation.INPUT: - return self.port_shape_in - return self.port_shape_out - - @dispatch(Herald, PortLocation) - def get_shape(self, herald, location): - if location == PortLocation.INPUT: - return self.herald_shape_in - return self.herald_shape_out - - def port_shape_in(self, port, canvas, content, mode_style, **opts): - m_index = None - if 'starting_mode' in opts: - m_index = opts['starting_mode'] - canvas.add_rect((-2, 15), 12, 50*port.m - 30, fill="lightgray") - if m_index is not None: - for i in range(port.m): - canvas.add_text((4, 50 * i + 27), text=str(m_index+i), size=7, ta="middle") - if port.name: - canvas.add_text((-2, 50*port.m - 9), text='[' + port.name + ']', size=6, ta="left", fontstyle="italic") - - def port_shape_out(self, port, canvas, content, mode_style, **opts): - m_index = None - if 'starting_mode' in opts: - m_index = opts['starting_mode'] - canvas.add_rect((15, 15), 12, 50*port.m - 30, fill="lightgray") - if m_index is not None: - for i in range(port.m): - canvas.add_text((21, 50 * i + 27), text=str(m_index+i), size=7, ta="middle") - if port.name: - canvas.add_text((27, 50*port.m - 9), text='[' + port.name + ']', size=6, ta="right", fontstyle="italic") - - def default_shape(self, circuit, canvas, content, mode_style, **opts): - """ - Default shape is a gray box - """ - w = self.get_width(circuit) - for i in range(circuit.m): - canvas.add_mpath(["M", 0, 25 + i*50, "l", 50*w, 0], **self.style[ModeStyle.PHOTONIC]) - canvas.add_rect((5, 5), 50*w - 10, 50*circuit.m - 10, fill="gray") - canvas.add_text((25*w, 25*circuit.m), size=7, ta="middle", text=content) - - @staticmethod - def _reflective_side(theta, convention: cp.BSConvention) -> int: - """ - Return the reflective side of a beam splitter given a theta parameter and a BSConvention - 1 means top - -1 means bottom - 0 means undecided - """ - if convention == cp.BSConvention.Rx: - return 1 # top - if not theta.defined: - return 0 - theta = float(theta) - if convention == cp.BSConvention.Ry: - return 1 if theta < 2*math.pi else -1 - elif convention == cp.BSConvention.H: - return -1 if round(theta/2/math.pi) % 2 else 1 - - def bs_shape(self, bs: cp.BS, canvas, content, mode_style, **opts): - split_content = content.split("\n") - head_content = "\n".join([s for s in split_content if s.startswith("theta=")]) - bottom_content_list = [s for s in split_content if not s.startswith("theta=")] - bottom_nline = len(bottom_content_list) - bottom_size = 7 if bottom_nline < 3 else 6 - mode_style = self.style[ModeStyle.PHOTONIC] - fill_color = "black" if bs.param('theta').defined else "red" - canvas.add_mline([0, 25, 28, 25, 47, 44], stroke_linejoin="round", **mode_style) - canvas.add_mline([53, 44, 72, 25, 100, 25], stroke_linejoin="round", **mode_style) - canvas.add_mline([0, 75, 28, 75, 47, 56], stroke_linejoin="round", **mode_style) - canvas.add_mline([53, 56, 72, 75, 100, 75], stroke_linejoin="round", **mode_style) - canvas.add_rect((25, 43), 50, 14, fill=fill_color) - canvas.add_text((50, 80+5*bottom_nline), '\n'.join(bottom_content_list).replace('phi_', 'Φ_'), - size=bottom_size, ta="middle") - canvas.add_text((50, 26), head_content.replace('theta=', 'Θ='), size=7, ta="middle") - canvas.add_rect((25, 53), 50, 4, fill="lightgray") - # Add BS convention badge - canvas.add_rect((68, 50), 10, 10, fill=bs_convention_color(bs.convention)) - canvas.add_text((73, 57), bs.convention.name, size=6, ta="middle") - - def ps_shape(self, circuit: cp.PS, canvas, content, mode_style, **opts): + def ps_shape(self, circuit: cp.PS, canvas, mode_style): canvas.add_mline([0, 25, 50, 25], **self.style[ModeStyle.PHOTONIC]) fill_color = "gray" if circuit.defined else "red" canvas.add_polygon([5, 40, 14, 40, 28, 10, 19, 10, 5, 40, 14, 40], stroke="black", fill=fill_color, stroke_width=1, stroke_linejoin="miter") - canvas.add_text((22, 38), text=content.replace("phi=", "Φ="), size=7, ta="left") - - def lc_shape(self, circuit, canvas, content, mode_style, **opts): - style = {'stroke': 'black', 'stroke_width': 1} - canvas.add_mline([0, 25, 50, 25], **self.style[ModeStyle.PHOTONIC]) - canvas.add_mline([25, 25, 25, 32], **style) - canvas.add_mline([15, 32, 35, 32], **style) - canvas.add_mline([18, 34, 32, 34], **style) - canvas.add_mline([21, 36, 29, 36], **style) - canvas.add_mline([24, 38, 26, 38], **style) - canvas.add_rect((22, 22), 6, 6, fill="gray") - canvas.add_text((6, 20), text=content, size=7, ta="left") - - def pbs_shape(self, circuit, canvas, content, mode_style, **opts): - style = self.style[ModeStyle.PHOTONIC] - canvas.add_mline([0, 25, 28, 25, 37.5, 37.5], **style, stroke_linejoin="round") - canvas.add_mline([62.5, 37.5, 72, 25, 100, 25], **style, stroke_linejoin="round") - canvas.add_mline([0, 75, 28, 75, 37.5, 62.5], **style, stroke_linejoin="round") - canvas.add_mline([62.5, 62.5, 72, 75, 100, 75], **style, stroke_linejoin="round") - canvas.add_mline([62.5, 62.5, 72, 75, 100, 75], **style, stroke_linejoin="round") - canvas.add_polygon([25, 50, 50, 24, 75, 50, 50, 76, 25, 50], stroke="black", stroke_width=1, fill="gray") - canvas.add_mline([25, 50, 75, 50], stroke="black", stroke_width=1) - canvas.add_text((50, 86), text=content, size=7, ta="middle") - - def td_shape(self, circuit, canvas, content, mode_style, **opts): - style = self.style[ModeStyle.PHOTONIC] - canvas.add_circle((34, 14), 11, stroke_width=5, fill=None, stroke="white") - canvas.add_circle((34, 14), 11, fill=None, **style) - canvas.add_circle((25, 14), 11, stroke_width=5, fill=None, stroke="white") - canvas.add_circle((25, 14), 11, fill=None, **style) - canvas.add_circle((16, 14), 11, stroke_width=5, fill=None, stroke="white") - canvas.add_circle((16, 14), 11, fill=None, **style) - canvas.add_mline([0, 25, 19, 25], stroke="white", stroke_width=5) - canvas.add_mline([0, 25, 19, 25], **style) - canvas.add_mline([34, 25, 50, 25], stroke="white", stroke_width=5) - canvas.add_mline([32, 25, 50, 25], **style) - canvas.add_text((25, 38), content, 7, "middle") - - def unitary_shape(self, circuit, canvas, content, mode_style, **opts): - m = circuit.m - for i in range(m): - canvas.add_mpath(["M", 0, 25 + i*50, "l", 50*m, 0], **self.style[ModeStyle.PHOTONIC]) - canvas.add_rect((5, 5), 50*m-10, 50*m-10, fill="gold") - canvas.add_text((25*m, 25*m), size=10, ta="middle", text=circuit.name) + canvas.add_text((22, 38), text=self._get_display_content(circuit).replace("phi=", "Φ="), size=7, ta="left") - def barrier_shape(self, circuit, canvas, content, mode_style, **opts): + def barrier_shape(self, circuit, canvas, mode_style): m = circuit.m if not circuit.visible: # even if invisible, draw a thin line for debug purpose @@ -256,72 +63,3 @@ def barrier_shape(self, circuit, canvas, content, mode_style, **opts): for i in range(m): canvas.add_mpath(["M", 0, 25 + i*50, "l", 50, 0], **self.style[ModeStyle.PHOTONIC]) canvas.add_rect((24, 10), 2, 50 * m - 20, fill="dimgrey", stroke="dimgrey") - - def perm_shape(self, circuit, canvas, content, mode_style, **opts): - for an_input, an_output in enumerate(circuit.perm_vector): - style = self.style[mode_style[an_input]] - if style['stroke']: - canvas.add_mline([3, 25+an_input*50, 47, 25+an_output*50], - stroke="white", stroke_width=12) - canvas.add_mline([0, 25+an_input*50, 3, 25+an_input*50, 47, 25+an_output*50, 50, 25+an_output*50], - **style) - - def wp_shape(self, circuit, canvas, content, mode_style, **opts): - params = content.replace("xsi=", "ξ=").replace("delta=", "δ=").split("\n") - canvas.add_mline([0, 25, 50, 25], **self.style[ModeStyle.PHOTONIC]) - canvas.add_rect((13, 7), width=14, height=36, fill="gray", - stroke_width=1, stroke="black", stroke_linejoin="miter") - canvas.add_mline([20, 7, 20, 43], stroke="black", stroke_width=1) - canvas.add_text((28.5, 36), text=params[0], size=7, ta="left") - canvas.add_text((28.5, 45), text=params[1], size=7, ta="left") - - def pr_shape(self, circuit, canvas, content, mode_style, **opts): - canvas.add_mline([0, 25, 15, 25], **self.style[ModeStyle.PHOTONIC]) - canvas.add_mline([35, 25, 50, 25], **self.style[ModeStyle.PHOTONIC]) - canvas.add_rect((14, 14), width=22, height=22, stroke="black", fill="lightgray", - stroke_width=1, stroke_linejoin="miter") - canvas.add_mpath(["M", 18, 27, "c", 0.107, 0.131, 0.280, 0.131, 0.387, 0, - "l", 2.305, -2.821, "c", 0.107, -0.131, 0.057, -0.237, -0.112, -0.237, - "h", -1.22, "c", -0.169, 0, -0.284, -0.135, -0.247, -0.300, - "c", 0.629, -2.866, 3.187, -5.018, 6.240, -5.018, - "c", 3.524, 0, 6.39, 2.867, 6.390, 6.3902, - "c", 0, 3.523, -2.866, 6.39, -6.390, 6.390, - "c", -0.422, 0, -0.765, 0.342, -0.765, 0.765, - "s", 0.342, 0.765, 0.765, 0.765, - "c", 4.367, 0, 7.92, -3.552, 7.920, -7.920, - "c", 0, -4.367, -3.552, -7.920, -7.920, -7.920, - "c", -3.898, 0, -7.146, 2.832, -7.799, 6.546, - "c", -0.029, 0.166, -0.184, 0.302, -0.353, 0.302, - "h", -1.201, "c", -0.169, 0, -0.219, 0.106, -0.112, 0.237, - "z" - ], fill="black") - canvas.add_text((25, 45), text=content.replace("delta=", "δ="), size=7, ta="middle") - - def subcircuit_shape(self, circuit, canvas, content, mode_style, **opts): - w = self.style_subcircuit['width'] - for idx in range(circuit.m): - canvas.add_mline([0, 50*idx+25, w*50, 50*idx+25], **self.style[ModeStyle.PHOTONIC]) - canvas.add_rect((2.5, 2.5), w*50 - 5, 50*circuit.m - 5, - fill=self.style_subcircuit['fill'], **self.style_subcircuit['stroke_style']) - title = circuit.name.upper().split(" ") - canvas.add_text((10, 8+8*len(title)), "\n".join(title), 8, fontstyle="bold") - - def herald_shape_in(self, herald, canvas, content, mode_style, **opts): - r = 10 - canvas.add_mpath(["M", 7, 25, "c", 0, 0, 0, -r, r, -r, - "h", 8, "v", 2 * r, "h", -8, - "c", -r, 0, -r, -r, -r, -r, "z"], - stroke="black", stroke_width=1, fill="white") - if herald.name: - canvas.add_text((13, 41), text='[' + herald.name + ']', size=6, ta="middle", fontstyle="italic") - canvas.add_text((17, 28), text=str(herald.expected), size=7, ta="middle") - - def herald_shape_out(self, herald, canvas, content, mode_style, **opts): - r = 10 # Radius of the half-circle - canvas.add_mpath(["M", 8, 35, "h", -8, "v", -2 * r, "h", 8, - "c", 0, 0, r, 0, r, r, - "c", 0, r, -r, r, -r, r, "z"], - stroke="black", stroke_width=1, fill="white") - if herald.name: - canvas.add_text((13, 11), text='[' + herald.name + ']', size=6, ta="middle", fontstyle="italic") - canvas.add_text((8, 28), text=str(herald.expected), size=7, ta="middle") diff --git a/perceval/rendering/circuit/phys_skin.py b/perceval/rendering/circuit/phys_skin.py index 6ee2abff..b46ddac5 100644 --- a/perceval/rendering/circuit/phys_skin.py +++ b/perceval/rendering/circuit/phys_skin.py @@ -122,35 +122,24 @@ def get_shape(self, herald, location): return self.herald_shape_in return self.herald_shape_out - def port_shape_in(self, port, canvas, content, mode_style, **opts): - m_index = None - if 'starting_mode' in opts: - m_index = opts['starting_mode'] + def port_shape_in(self, port, canvas, mode_style): canvas.add_rect((-2, 15), 12, 50*port.m - 30, fill="lightgray") - if m_index is not None: - for i in range(port.m): - canvas.add_text((4, 50 * i + 27), text=str(m_index+i), size=7, ta="middle") if port.name: canvas.add_text((-2, 50*port.m - 9), text='[' + port.name + ']', size=6, ta="left", fontstyle="italic") - def port_shape_out(self, port, canvas, content, mode_style, **opts): - m_index = None - if 'starting_mode' in opts: - m_index = opts['starting_mode'] + def port_shape_out(self, port, canvas, mode_style): canvas.add_rect((15, 15), 12, 50*port.m - 30, fill="lightgray") - if m_index is not None: - for i in range(port.m): - canvas.add_text((21, 50 * i + 27), text=str(m_index+i), size=7, ta="middle") if port.name: canvas.add_text((27, 50*port.m - 9), text='[' + port.name + ']', size=6, ta="right", fontstyle="italic") - def default_shape(self, circuit, canvas, content, mode_style, **opts): + def default_shape(self, circuit, canvas, mode_style): """ Default shape is a gray box """ w = self.get_width(circuit) + content = self._get_display_content(circuit) for i in range(circuit.m): - canvas.add_mpath(["M", 0, 25 + i*50, "l", 50*w, 0], **self.style[ModeStyle.PHOTONIC]) + canvas.add_mpath(["M", 0, 25 + i*50, "l", 50*w, 0], **self.style[mode_style]) canvas.add_rect((5, 5), 50*w - 10, 50*circuit.m - 10, fill="gray") canvas.add_text((25*w, 25*circuit.m), size=7, ta="middle", text=content) @@ -172,8 +161,8 @@ def _reflective_side(theta, convention: cp.BSConvention) -> int: elif convention == cp.BSConvention.H: return -1 if round(theta/2/math.pi) % 2 else 1 - def bs_shape(self, bs, canvas, content, mode_style, **opts): - split_content = content.split("\n") + def bs_shape(self, bs, canvas, mode_style): + split_content = self._get_display_content(bs).split("\n") head_content = "\n".join([s for s in split_content if s.startswith("R=") or s.startswith("theta=")]) bottom_content_list = [s for s in split_content @@ -200,13 +189,14 @@ def bs_shape(self, bs, canvas, content, mode_style, **opts): canvas.add_rect((68, 50), 10, 10, fill=bs_convention_color(bs.convention)) canvas.add_text((73, 57), bs.convention.name, size=6, ta="middle") - def ps_shape(self, circuit, canvas, content, mode_style, **opts): + def ps_shape(self, circuit, canvas, mode_style): canvas.add_mline([0, 25, 50, 25], **self.style[ModeStyle.PHOTONIC]) canvas.add_polygon([5, 40, 14, 40, 28, 10, 19, 10, 5, 40, 14, 40], stroke="black", fill="gray", stroke_width=1, stroke_linejoin="miter") - canvas.add_text((22, 38), text=content.replace("phi=", "Φ="), size=7, ta="left") + content = self._get_display_content(circuit).replace("phi=", "Φ=") + canvas.add_text((22, 38), text=content, size=7, ta="left") - def lc_shape(self, circuit, canvas, content, mode_style, **opts): + def lc_shape(self, circuit, canvas, mode_style): style = {'stroke': 'black', 'stroke_width': 1} canvas.add_mline([0, 25, 50, 25], **self.style[ModeStyle.PHOTONIC]) canvas.add_mline([25, 25, 25, 32], **style) @@ -215,9 +205,9 @@ def lc_shape(self, circuit, canvas, content, mode_style, **opts): canvas.add_mline([21, 36, 29, 36], **style) canvas.add_mline([24, 38, 26, 38], **style) canvas.add_rect((22, 22), 6, 6, fill="gray") - canvas.add_text((6, 20), text=content, size=7, ta="left") + canvas.add_text((6, 20), text=self._get_display_content(circuit), size=7, ta="left") - def pbs_shape(self, circuit, canvas, content, mode_style, **opts): + def pbs_shape(self, circuit, canvas, mode_style): style = self.style[ModeStyle.PHOTONIC] canvas.add_mline([0, 25, 28, 25, 37.5, 37.5], **style, stroke_linejoin="round") canvas.add_mline([62.5, 37.5, 72, 25, 100, 25], **style, stroke_linejoin="round") @@ -226,9 +216,9 @@ def pbs_shape(self, circuit, canvas, content, mode_style, **opts): canvas.add_mline([62.5, 62.5, 72, 75, 100, 75], **style, stroke_linejoin="round") canvas.add_polygon([25, 50, 50, 24, 75, 50, 50, 76, 25, 50], stroke="black", stroke_width=1, fill="gray") canvas.add_mline([25, 50, 75, 50], stroke="black", stroke_width=1) - canvas.add_text((50, 86), text=content, size=7, ta="middle") + canvas.add_text((50, 86), text=self._get_display_content(circuit), size=7, ta="middle") - def td_shape(self, circuit, canvas, content, mode_style, **opts): + def td_shape(self, circuit, canvas, mode_style): style = self.style[ModeStyle.PHOTONIC] canvas.add_circle((34, 14), 11, stroke_width=5, fill=None, stroke="white") canvas.add_circle((34, 14), 11, fill=None, **style) @@ -240,16 +230,16 @@ def td_shape(self, circuit, canvas, content, mode_style, **opts): canvas.add_mline([0, 25, 19, 25], **style) canvas.add_mline([34, 25, 50, 25], stroke="white", stroke_width=5) canvas.add_mline([32, 25, 50, 25], **style) - canvas.add_text((25, 38), content, 7, "middle") + canvas.add_text((25, 38), self._get_display_content(circuit), 7, "middle") - def unitary_shape(self, circuit, canvas, content, mode_style, **opts): + def unitary_shape(self, circuit, canvas, mode_style): m = circuit.m for i in range(m): canvas.add_mpath(["M", 0, 25 + i*50, "l", 50*m, 0], **self.style[ModeStyle.PHOTONIC]) canvas.add_rect((5, 5), 50*m-10, 50*m-10, fill="gold") canvas.add_text((25*m, 25*m), size=10, ta="middle", text=circuit.name) - def barrier_shape(self, circuit, canvas, content, mode_style, **opts): + def barrier_shape(self, circuit, canvas, mode_style): if not circuit.visible: return @@ -260,17 +250,17 @@ def barrier_shape(self, circuit, canvas, content, mode_style, **opts): canvas.add_mpath(["M", 0, 25 + i*50, "l", 50, 0], **self.style[ModeStyle.PHOTONIC]) canvas.add_rect((24, 10), 2, 50 * m - 20, fill="dimgrey", stroke="dimgrey") - def perm_shape(self, circuit, canvas, content, mode_style, **opts): + def perm_shape(self, circuit, canvas, mode_style): for an_input, an_output in enumerate(circuit.perm_vector): style = self.style[mode_style[an_input]] if style['stroke']: canvas.add_mline([3, 25+an_input*50, 47, 25+an_output*50], - stroke="white", stroke_width=6) + stroke="white", stroke_width=style["stroke_width"]+3) canvas.add_mline([0, 25+an_input*50, 3, 25+an_input*50, 47, 25+an_output*50, 50, 25+an_output*50], **style) - def wp_shape(self, circuit, canvas, content, mode_style, **opts): - params = content.replace("xsi=", "ξ=").replace("delta=", "δ=").split("\n") + def wp_shape(self, circuit, canvas, mode_style): + params = self._get_display_content(circuit).replace("xsi=", "ξ=").replace("delta=", "δ=").split("\n") canvas.add_mline([0, 25, 50, 25], **self.style[ModeStyle.PHOTONIC]) canvas.add_rect((13, 7), width=14, height=36, fill="gray", stroke_width=1, stroke="black", stroke_linejoin="miter") @@ -278,7 +268,7 @@ def wp_shape(self, circuit, canvas, content, mode_style, **opts): canvas.add_text((28.5, 36), text=params[0], size=7, ta="left") canvas.add_text((28.5, 45), text=params[1], size=7, ta="left") - def pr_shape(self, circuit, canvas, content, mode_style, **opts): + def pr_shape(self, circuit, canvas, mode_style): canvas.add_mline([0, 25, 15, 25], **self.style[ModeStyle.PHOTONIC]) canvas.add_mline([35, 25, 50, 25], **self.style[ModeStyle.PHOTONIC]) canvas.add_rect((14, 14), width=22, height=22, stroke="black", fill="lightgray", @@ -298,9 +288,9 @@ def pr_shape(self, circuit, canvas, content, mode_style, **opts): "h", -1.201, "c", -0.169, 0, -0.219, 0.106, -0.112, 0.237, "z" ], fill="black") - canvas.add_text((25, 45), text=content.replace("delta=", "δ="), size=7, ta="middle") + canvas.add_text((25, 45), text=self._get_display_content(circuit).replace("delta=", "δ="), size=7, ta="middle") - def subcircuit_shape(self, circuit, canvas, content, mode_style, **opts): + def subcircuit_shape(self, circuit, canvas, mode_style): w = self.style_subcircuit['width'] for idx in range(circuit.m): canvas.add_mline([0, 50*idx+25, w*50, 50*idx+25], **self.style[ModeStyle.PHOTONIC]) @@ -309,7 +299,7 @@ def subcircuit_shape(self, circuit, canvas, content, mode_style, **opts): title = circuit.name.upper().split(" ") canvas.add_text((10, 8+8*len(title)), "\n".join(title), 8, fontstyle="bold") - def herald_shape_in(self, herald, canvas, content, mode_style, **opts): + def herald_shape_in(self, herald, canvas, mode_style): r = 10 canvas.add_mpath(["M", 7, 25, "c", 0, 0, 0, -r, r, -r, "h", 8, "v", 2 * r, "h", -8, @@ -319,7 +309,7 @@ def herald_shape_in(self, herald, canvas, content, mode_style, **opts): canvas.add_text((13, 41), text='[' + herald.name + ']', size=6, ta="middle", fontstyle="italic") canvas.add_text((17, 28), text=str(herald.expected), size=7, ta="middle") - def herald_shape_out(self, herald, canvas, content, mode_style, **opts): + def herald_shape_out(self, herald, canvas, mode_style): r = 10 # Radius of the half-circle canvas.add_mpath(["M", 8, 35, "h", -8, "v", -2 * r, "h", 8, "c", 0, 0, r, 0, r, r, diff --git a/perceval/rendering/circuit/renderer.py b/perceval/rendering/circuit/renderer.py index c2f9a3ce..13ab7aef 100644 --- a/perceval/rendering/circuit/renderer.py +++ b/perceval/rendering/circuit/renderer.py @@ -35,7 +35,7 @@ from perceval.rendering.circuit import ASkin, ModeStyle from perceval.rendering.format import Format from perceval.rendering.canvas import Canvas, MplotCanvas, SvgCanvas, LatexCanvas -from perceval.components import ACircuit, Circuit, PortLocation, PERM, Herald, Barrier +from perceval.components import ACircuit, Circuit, APort, PortLocation, PERM, Herald, Barrier from perceval.utils.format import format_parameters @@ -45,6 +45,9 @@ def __init__(self, x, y): self.y = y +_PERM_DESC = '_╲ ╱\n_ ╳ \n_╱ ╲' + + class ICircuitRenderer(ABC): """ Base class for circuit renderers. @@ -81,12 +84,9 @@ def render_circuit(self, recursive: bool = False, precision: float = 1e-6, nsimplify: bool = True) -> None: - """Renders the input circuit - """ + """Renders the input circuit""" if not isinstance(circuit, Circuit): - variables = circuit.get_variables() - description = format_parameters(variables, precision, nsimplify) - self.append_circuit(tuple(p + shift for p in range(circuit.m)), circuit, description) + self.append_circuit(tuple(p + shift for p in range(circuit.m)), circuit) if circuit.is_composite() and circuit.ncomponents() > 0: grouped_components = circuit.group_components_by_xgrid() @@ -111,13 +111,9 @@ def render_circuit(self, nsimplify=nsimplify) self.close_subblock(shiftr) else: - component_vars = c.get_variables() - description = format_parameters(component_vars, precision, nsimplify) - self.append_subcircuit(shiftr, c, description) + self.append_subcircuit(shiftr, c) else: - component_vars = c.get_variables() - description = format_parameters(component_vars, precision, nsimplify) - self.append_circuit(shiftr, c, description, pos=pos) + self.append_circuit(shiftr, c, pos=pos) self.extend_pos(0, circuit.m - 1) @abstractmethod @@ -171,13 +167,13 @@ def draw(self) -> Any: """ @abstractmethod - def append_subcircuit(self, lines: Tuple[int, ...], circuit: Circuit, content: str) -> None: + def append_subcircuit(self, lines: Tuple[int, ...], circuit: Circuit) -> None: """ Add a composite circuit to the rendering. Render each subcomponent independently. """ @abstractmethod - def append_circuit(self, lines: Tuple[int, ...], circuit: ACircuit, content: str) -> None: + def append_circuit(self, lines: Tuple[int, ...], circuit: ACircuit, pos=None) -> None: """ Add a component (or a circuit treated as a single component) to the rendering, on modes 'lines' """ @@ -189,13 +185,13 @@ def add_mode_index(self) -> None: """ @abstractmethod - def add_out_port(self, m: int, content: str, **opts) -> None: + def add_out_port(self, m: int, port: APort) -> None: """ Render a port on the right side (outputs) of a previously rendered circuit, located on mode 'm' """ @abstractmethod - def add_in_port(self, m: int, content: str, **opts) -> None: + def add_in_port(self, m: int, content: str) -> None: """ Render a port on the left side (inputs) of a previously rendered circuit, located on mode 'm' """ @@ -284,12 +280,12 @@ def close_subblock(self, lines): self._h[k] += "║" self._h[end * self._hc + 4] += "╝" - def append_subcircuit(self, lines, circuit, content): + def append_subcircuit(self, lines, circuit): self.open_subblock(lines, circuit.name, None) self.extend_pos(lines[0], lines[-1], header=True, internal=True, char="░") self.close_subblock(lines) - def append_circuit(self, lines, circuit, content, pos=None): + def append_circuit(self, lines, circuit, pos=None): # opening the box start = lines[0] end = lines[-1] @@ -305,6 +301,10 @@ def append_circuit(self, lines, circuit, content, pos=None): return # put variables on the right number of lines + if isinstance(circuit, PERM): + content = _PERM_DESC + else: + content = format_parameters(circuit.get_variables(), 1e-3, False) content = circuit.name + (content and "\n" + content or "") lcontents = content.split("\n") if start == end: @@ -379,7 +379,7 @@ def add_mode_index(self): self._h[self._hc * k + 2] = f'{k:{offset-1}d}:' + self._h[self._hc * k + 2][offset:] self._h[self._hc * k + 2] += ':' + str(k) + f" (depth {self._depth[k]})" - def add_out_port(self, n_mode, port, **opts): + def add_out_port(self, n_mode: int, port: APort): content = '' if isinstance(port, Herald): content = port.expected @@ -387,7 +387,7 @@ def add_out_port(self, n_mode, port, **opts): self._h[self._hc * (n_mode + i) + 2] += f'[{content})' self._h[self._hc * (n_mode + i) + 3] += f"[{port.name}]" - def add_in_port(self, n_mode, port, **opts): + def add_in_port(self, n_mode: int, port: APort): content = '' if isinstance(port, Herald): content = str(port.expected) @@ -440,6 +440,10 @@ def get_circuit_size(self, circuit: ACircuit, recursive: bool = False): return self._skin.get_size(circuit, recursive) def add_mode_index(self): + self._canvas.set_offset( + (CanvasRenderer.AFFIX_ALL_SIZE + max(self._chart) * CanvasRenderer.SCALE, 0), + CanvasRenderer.AFFIX_ALL_SIZE, + CanvasRenderer.SCALE * (self._nsize + 1)) for k in range(self._nsize): if self._mode_style[k] != ModeStyle.HERALD: self._canvas.add_text( @@ -464,7 +468,7 @@ def add_mode_index(self): self._n_font_size, ta="left") - def add_out_port(self, n_mode, port, **opts): + def add_out_port(self, n_mode: int, port: APort): max_pos = max(self._chart[0:self._nsize]) h_pos = self._out_port_pos[n_mode].x v_pos = self._out_port_pos[n_mode].y @@ -475,25 +479,16 @@ def add_out_port(self, n_mode, port, **opts): ), CanvasRenderer.AFFIX_ALL_SIZE, CanvasRenderer.SCALE) - opts['starting_mode'] = n_mode - self._canvas.add_shape( - self._skin.get_shape( - port, PortLocation.OUTPUT), port, None, None, **opts) + self._canvas.add_shape(self._skin.get_shape(port, PortLocation.OUTPUT), port, None) - def add_in_port(self, n_mode, port, **opts): + def add_in_port(self, n_mode: int, port: APort): h_pos = self._in_port_pos[n_mode].x * CanvasRenderer.SCALE v_pos = self._in_port_pos[n_mode].y * CanvasRenderer.SCALE self._canvas.set_offset( (h_pos, v_pos), CanvasRenderer.AFFIX_ALL_SIZE, CanvasRenderer.SCALE) - opts['starting_mode'] = n_mode - self._canvas.add_shape( - self._skin.get_shape(port, PortLocation.INPUT), - port, - None, - None, - **opts) + self._canvas.add_shape(self._skin.get_shape(port, PortLocation.INPUT), port, None) def open_subblock(self, lines, name, size, color=None): # Get recommended margins for this block @@ -567,7 +562,7 @@ def extend_pos(self, start, end, pos=None): **style) self._chart[p] = maxpos - def _add_shape(self, lines, circuit, content, w, shape_fn=None, pos=None): + def _add_shape(self, lines, circuit, w, shape_fn=None, pos=None): if shape_fn is None: shape_fn = self._skin.get_shape(circuit) start = lines[0] @@ -582,7 +577,7 @@ def _add_shape(self, lines, circuit, content, w, shape_fn=None, pos=None): CanvasRenderer.SCALE * w, CanvasRenderer.SCALE * (end - start + 1)) modes = self._mode_style[start:(end + 1)] - self._canvas.add_shape(shape_fn, circuit, content, modes) + self._canvas.add_shape(shape_fn, circuit, modes) def set_herald_info(self, info): self._herald_info = info @@ -618,18 +613,17 @@ def _update_mode_style(self, lines, circuit, w: int): out_modes[m_output + lines[0]] = self._mode_style[m_input + m0] self._mode_style = out_modes - def append_circuit(self, lines, circuit, content, pos=None): + def append_circuit(self, lines, circuit, pos=None): w = self._skin.get_width(circuit) - self._add_shape(lines, circuit, content, w, pos=pos) + self._add_shape(lines, circuit, w, pos=pos) self._update_mode_style(lines, circuit, w) for i in range(lines[0], lines[-1] + 1): self._chart[i] += w - def append_subcircuit(self, lines, circuit, content): + def append_subcircuit(self, lines, circuit): w = self._skin.style_subcircuit['width'] if w: - self._add_shape( - lines, circuit, content, w, self._skin.subcircuit_shape) + self._add_shape(lines, circuit, w, self._skin.subcircuit_shape) self._update_mode_style(lines, circuit, w) for i in range(lines[0], lines[-1] + 1): self._chart[i] += w @@ -686,10 +680,10 @@ def close(self): def add_mode_index(self) -> None: pass - def add_out_port(self, m: int, content: str, **opts) -> None: + def add_out_port(self, m: int, port: APort) -> None: pass - def add_in_port(self, m: int, content: str, **opts) -> None: + def add_in_port(self, m: int, content: str) -> None: pass def set_herald_info(self, info): @@ -724,7 +718,7 @@ def extend_pos(self, start, end, pos=None): for p in range(start, end + 1): self._chart[p] = maxpos - def _add_shape(self, lines, circuit, content, w, shape_fn=None, pos=None): + def _add_shape(self, lines, circuit, w, shape_fn=None, pos=None): start = lines[0] end = lines[-1] self.extend_pos(start, end, pos=pos) @@ -747,19 +741,18 @@ def _update_mode_style(self, lines, circuit, w: int): self._herald_range[0], self._chart[lines[0] + in_mode]) - def append_circuit(self, lines, circuit, content, pos=None): + def append_circuit(self, lines, circuit, pos=None): w = self._skin.get_width(circuit) if w: - self._add_shape(lines, circuit, content, w, pos=pos) + self._add_shape(lines, circuit, w, pos=pos) self._update_mode_style(lines, circuit, w) for i in range(lines[0], lines[-1] + 1): self._chart[i] += w - def append_subcircuit(self, lines, circuit, content): + def append_subcircuit(self, lines, circuit): w = self._skin.style_subcircuit['width'] if w: - self._add_shape( - lines, circuit, content, w, self._skin.subcircuit_shape) + self._add_shape(lines, circuit, w, self._skin.subcircuit_shape) self._update_mode_style(lines, circuit, w) for i in range(lines[0], lines[-1] + 1): self._chart[i] += w @@ -770,7 +763,7 @@ def create_renderer( output_format: Format = Format.TEXT, # rendering method skin: ASkin = None, # skin (unused in text rendering) **opts, -) -> tuple[ICircuitRenderer, ICircuitRenderer]: +) -> Tuple[ICircuitRenderer, ICircuitRenderer]: """ Creates a renderer given the selected format. Dispatches parameters to generated canvas objects A skin object is needed for circuit graphic rendering. diff --git a/perceval/rendering/circuit/symb_skin.py b/perceval/rendering/circuit/symb_skin.py index a945f095..aff4d611 100644 --- a/perceval/rendering/circuit/symb_skin.py +++ b/perceval/rendering/circuit/symb_skin.py @@ -134,17 +134,18 @@ def get_shape(self, herald, location): return self.herald_shape_in return self.herald_shape_out - def default_shape(self, circuit, canvas, content, mode_style, **opts): + def default_shape(self, circuit, canvas, mode_style): """ Default shape is a gray box """ w = self.get_width(circuit) + content = self._get_display_content(circuit) for i in range(circuit.m): - canvas.add_mpath(["M", 0, 25 + i*50, "l", 50*w, 0], **self.style[ModeStyle.PHOTONIC]) + canvas.add_mpath(["M", 0, 25 + i*50, "l", 50*w, 0], **self.style[mode_style]) canvas.add_rect((5, 5), 50*w - 10, 50*circuit.m - 10, fill="lightgray") canvas.add_text((25*w, 25*circuit.m), size=7, ta="middle", text=content) - def bs_shape(self, bs, canvas, content, mode_style, **opts): + def bs_shape(self, bs, canvas, mode_style): if self._compact: path_data = ["M", 6.4721, 25.0002, "c", 6.8548, 0, 6.8241, 24.9998, 13.6789, 24.9998, "m", 0.0009, 0, "c", -6.8558, 0, -6.825, 24.9998, -13.6799, 24.9998, "m", 13.6799, -24.9998, "h", 10.9423, "m", 0, @@ -158,20 +159,20 @@ def bs_shape(self, bs, canvas, content, mode_style, **opts): 13.7116, 0, 13.65, 24.9998, 27.3597, 24.9998, "m", -89.5481, -49.9998, "h", 13, "m", 0.0019, 49.9998, "h", -13.0019, "m", 87.6453, 0, "h", 12.3547, "m", -12.8056, -50, "h", 12.8056] canvas.add_mpath(path_data, **self.style[ModeStyle.PHOTONIC]) - canvas.add_text((25 if self._compact else 50, 38), - content.replace('phi', 'Φ').replace('theta=', 'Θ='), - 7, "middle") + content = self._get_display_content(bs).replace('phi', 'Φ').replace('theta=', 'Θ=') + canvas.add_text((25 if self._compact else 50, 38), content, 7, "middle") # Add BS convention badge canvas.add_rect((35 if self._compact else 72, 53), 10, 10, fill=bs_convention_color(bs.convention)) canvas.add_text((40 if self._compact else 77, 60), bs.convention.name, size=6, ta="middle") - def ps_shape(self, circuit, canvas, content, mode_style, **opts): + def ps_shape(self, circuit, canvas, mode_style): canvas.add_mpath(["M", 0, 25, "h", 20, "m", 10, 0, "h", 20], **self.style[ModeStyle.PHOTONIC]) canvas.add_mpath(["M", 15, 35, "h", 20, "v", -20, "h", -20, "z"], stroke="black", stroke_width=1, fill="lightgray") - canvas.add_text((25, 44), text=content.replace("phi=", "Φ="), size=7, ta="middle") + content = self._get_display_content(circuit).replace("phi=", "Φ=") + canvas.add_text((25, 44), text=content, size=7, ta="middle") - def lc_shape(self, circuit, canvas, content, mode_style, **opts): + def lc_shape(self, circuit, canvas, mode_style): style = {'stroke': 'black', 'stroke_width': 1} canvas.add_mline([0, 25, 50, 25], **self.style[ModeStyle.PHOTONIC]) canvas.add_mline([25, 25, 25, 32], **style) @@ -180,9 +181,9 @@ def lc_shape(self, circuit, canvas, content, mode_style, **opts): canvas.add_mline([21, 36, 29, 36], **style) canvas.add_mline([24, 38, 26, 38], **style) canvas.add_rect((22, 22), 6, 6, fill="white") - canvas.add_text((6, 20), text=content, size=7, ta="left") + canvas.add_text((6, 20), text=self._get_display_content(circuit), size=7, ta="left") - def pbs_shape(self, circuit, canvas, content, mode_style, **opts): + def pbs_shape(self, circuit, canvas, mode_style): if self._compact: path_data1 = ["M", 0, 25.1, "h", 11.049, "m", -11.049, 50, "h", 10.9375, "m", 27.9029, -50, "h", 11.1596, @@ -202,9 +203,9 @@ def pbs_shape(self, circuit, canvas, content, mode_style, **opts): -10.5087, "z", "m", 0.35, 0, "h", -19.2, "z"] canvas.add_mpath(path_data1, **self.style[ModeStyle.PHOTONIC]) canvas.add_mpath(path_data2, stroke_width=1, fill="#fff") - canvas.add_text((25 if self._compact else 50, 86), text=content, size=7, ta="middle") + canvas.add_text((25 if self._compact else 50, 86), text=self._get_display_content(circuit), size=7, ta="middle") - def td_shape(self, circuit, canvas, content, mode_style, **opts): + def td_shape(self, circuit, canvas, mode_style): stroke = self.style[ModeStyle.PHOTONIC]['stroke'] canvas.add_circle((34, 14), 11, stroke="white", stroke_width=3) canvas.add_circle((34, 14), 11, stroke=stroke, stroke_width=2) @@ -216,9 +217,9 @@ def td_shape(self, circuit, canvas, content, mode_style, **opts): canvas.add_mline([0, 25, 19, 25], stroke=stroke, stroke_width=2) canvas.add_mline([34, 25, 50, 25], stroke="white", stroke_width=3) canvas.add_mline([32, 25, 50, 25], stroke=stroke, stroke_width=2) - canvas.add_text((25, 38), text=content.replace("t=", ""), size=7, ta="middle") + canvas.add_text((25, 38), text=self._get_display_content(circuit), size=7, ta="middle") - def unitary_shape(self, circuit, canvas, content, mode_style, **opts): + def unitary_shape(self, circuit, canvas, mode_style): w = circuit.m for i in range(circuit.m): canvas.add_mpath(["M", 0, 25 + i*50, "l", 50*w, 0], **self.style[ModeStyle.PHOTONIC]) @@ -230,7 +231,7 @@ def unitary_shape(self, circuit, canvas, content, mode_style, **opts): **self.style[ModeStyle.PHOTONIC], fill="lightyellow") canvas.add_text((25*w, 25*w), size=10, ta="middle", text=circuit.name) - def barrier_shape(self, circuit, canvas, content, mode_style, **opts): + def barrier_shape(self, circuit, canvas, mode_style): if not circuit.visible: return m = circuit.m @@ -240,7 +241,7 @@ def barrier_shape(self, circuit, canvas, content, mode_style, **opts): ["M", 0, 25 + i*50, "l", 50, 0], **self.style[ModeStyle.PHOTONIC]) - def perm_shape(self, circuit, canvas, content, mode_style, **opts): + def perm_shape(self, circuit, canvas, mode_style): for an_input, an_output in enumerate(circuit.perm_vector): style = self.style[mode_style[an_input]] if style['stroke']: @@ -251,28 +252,28 @@ def perm_shape(self, circuit, canvas, content, mode_style, **opts): "C", 20, 25 + an_input * 50, 30, 25 + an_output * 50, 50, 25 + an_output * 50], **style) - def wp_shape(self, circuit, canvas, content, mode_style, **opts): - params = content.replace("xsi=", "ξ=").replace("delta=", "δ=").split("\n") + def wp_shape(self, circuit, canvas, mode_style): + params = self._get_display_content(circuit).replace("xsi=", "ξ=").replace("delta=", "δ=").split("\n") style = self.style[ModeStyle.PHOTONIC] canvas.add_mpath(["M", 0, 25, "h", 15, "m", 21, 0, "h", 15], **style) canvas.add_mpath(["M", 15, 45, "h", 21, "v", -40, "h", -21, "z"], **style) canvas.add_text((25, 55), text=params[0], size=7, ta="middle") canvas.add_text((25, 65), text=params[1], size=7, ta="middle") - def hwp_shape(self, circuit, canvas, content, mode_style, **opts): - params = content.replace("xsi=", "ξ=").replace("delta=", "δ=").split("\n") + def hwp_shape(self, circuit, canvas, mode_style): + params = self._get_display_content(circuit).replace("xsi=", "ξ=").replace("delta=", "δ=").split("\n") canvas.add_mpath(["M", 0, 25, "v", 0, "h", 0, "h", 50], **self.style[ModeStyle.PHOTONIC]) canvas.add_mpath(["M", 20, 0, "v", 50], stroke="black", stroke_width=2) canvas.add_mpath(["M", 30, 0, "v", 50], stroke="black", stroke_width=2) canvas.add_text((25, 60), text=params[0], size=7, ta="middle") - def qwp_shape(self, circuit, canvas, content, mode_style, **opts): - params = content.replace("xsi=", "ξ=").replace("delta=", "δ=").split("\n") + def qwp_shape(self, circuit, canvas, mode_style): + params = self._get_display_content(circuit).replace("xsi=", "ξ=").replace("delta=", "δ=").split("\n") canvas.add_mpath(["M", 0, 25, "v", 0, "h", 0, "h", 50], **self.style[ModeStyle.PHOTONIC]) canvas.add_mpath(["M", 25, 0, "v", 50], stroke="black", stroke_width=2) canvas.add_text((25, 60), text=params[0], size=7, ta="middle") - def pr_shape(self, circuit, canvas, content, mode_style, **opts): + def pr_shape(self, circuit, canvas, mode_style): canvas.add_mpath(["M", 0, 25, "h", 15, "m", 22, 0, "h", 15], **self.style[ModeStyle.PHOTONIC]) canvas.add_mpath(["M", 15, 36, "h", 22, "v", -22, "h", -22, "z"], stroke="black", stroke_width=1) canvas.add_mpath(["M", 19, 27, "c", 0.107, 0.131, 0.280, 0.131, 0.387, 0, @@ -290,9 +291,9 @@ def pr_shape(self, circuit, canvas, content, mode_style, **opts): "H", 17, "c", -0.169, 0, -0.219, 0.106, -0.112, 0.237, "z" ], fill="black", stroke_width=0.1) - canvas.add_text((27, 50), text=content.replace("delta=", "δ="), size=7, ta="middle") + canvas.add_text((27, 50), text=self._get_display_content(circuit).replace("delta=", "δ="), size=7, ta="middle") - def subcircuit_shape(self, circuit, canvas, content, mode_style, **opts): + def subcircuit_shape(self, circuit, canvas, mode_style): w = self.style_subcircuit['width'] for idx in range(circuit.m): canvas.add_mline([0, 50*idx+25, w*50, 50*idx+25], **self.style[ModeStyle.PHOTONIC]) @@ -301,7 +302,7 @@ def subcircuit_shape(self, circuit, canvas, content, mode_style, **opts): title = circuit.name.upper().split(" ") canvas.add_text((10, 8 + 8 * len(title)), "\n".join(title), 8, fontstyle="bold") - def herald_shape_in(self, herald, canvas, content, mode_style, **opts): + def herald_shape_in(self, herald, canvas, mode_style): r = 10 canvas.add_mpath(["M", 7, 25, "c", 0, 0, 0, -r, r, -r, "h", 8, "v", 2 * r, "h", -8, @@ -311,7 +312,7 @@ def herald_shape_in(self, herald, canvas, content, mode_style, **opts): canvas.add_text((13, 41), text='[' + herald.name + ']', size=6, ta="middle", fontstyle="italic") canvas.add_text((17, 28), text=str(herald.expected), size=7, ta="middle") - def herald_shape_out(self, herald, canvas, content, mode_style, **opts): + def herald_shape_out(self, herald, canvas, mode_style): r = 10 # Radius of the half-circle canvas.add_mpath(["M", 8, 35, "h", -8, "v", -2 * r, "h", 8, "c", 0, 0, r, 0, r, r, @@ -321,24 +322,12 @@ def herald_shape_out(self, herald, canvas, content, mode_style, **opts): canvas.add_text((13, 11), text='[' + herald.name + ']', size=6, ta="middle", fontstyle="italic") canvas.add_text((8, 28), text=str(herald.expected), size=7, ta="middle") - def port_shape_in(self, port, canvas, content, mode_style, **opts): - m_index = None - if 'starting_mode' in opts: - m_index = opts['starting_mode'] + def port_shape_in(self, port, canvas, mode_style): canvas.add_rect((-2, 15), 12, 50*port.m - 30, fill="white") - if m_index is not None: - for i in range(port.m): - canvas.add_text((4, 50 * i + 27), text=str(m_index+i), size=7, ta="middle") if port.name: canvas.add_text((-2, 50*port.m - 9), text='[' + port.name + ']', size=6, ta="left", fontstyle="italic") - def port_shape_out(self, port, canvas, content, mode_style, **opts): - m_index = None - if 'starting_mode' in opts: - m_index = opts['starting_mode'] + def port_shape_out(self, port, canvas, mode_style): canvas.add_rect((15, 15), 12, 50*port.m - 30, fill="white") - if m_index is not None: - for i in range(port.m): - canvas.add_text((21, 50 * i + 27), text=str(m_index+i), size=7, ta="middle") if port.name: canvas.add_text((27, 50*port.m - 9), text='[' + port.name + ']', size=6, ta="right", fontstyle="italic") diff --git a/perceval/rendering/pdisplay.py b/perceval/rendering/pdisplay.py index 9b80fa54..30c01942 100644 --- a/perceval/rendering/pdisplay.py +++ b/perceval/rendering/pdisplay.py @@ -48,8 +48,8 @@ from perceval.algorithm import Analyzer, AProcessTomography from perceval.components import ACircuit, Circuit, AProcessor, non_unitary_components as nl -from perceval.rendering.circuit import DisplayConfig, create_renderer, ModeStyle -from perceval.rendering._density_matrix_utils import _csr_to_rgb, _csr_to_greyscale, generate_ticks +from .circuit import DisplayConfig, create_renderer, ModeStyle, ASkin +from ._density_matrix_utils import _csr_to_rgb, _csr_to_greyscale, generate_ticks from perceval.utils import Matrix, simple_float, simple_complex, DensityMatrix, mlstr from perceval.utils.logging import logger, channel from perceval.utils.statevector import ProbabilityDistribution, StateVector, BSCount @@ -86,10 +86,12 @@ def pdisplay_circuit( compact: bool = False, precision: float = 1e-6, nsimplify: bool = True, - skin=None, + skin: ASkin = None, **opts): if skin is None: skin = DisplayConfig.get_selected_skin(compact_display=compact) + skin.precision = precision + skin.nsimplify = nsimplify w, h = skin.get_size(circuit, recursive) renderer, _ = create_renderer( circuit.m, @@ -112,11 +114,13 @@ def pdisplay_processor(processor: AProcessor, compact: bool = False, precision: float = 1e-6, nsimplify: bool = True, - skin=None, + skin: ASkin = None, **opts): n_modes = processor.circuit_size if skin is None: skin = DisplayConfig.get_selected_skin(compact_display=compact) + skin.precision = precision + skin.nsimplify = nsimplify w, h = skin.get_size(processor, recursive) renderer, pre_renderer = create_renderer( n_modes, @@ -158,6 +162,7 @@ def pdisplay_processor(processor: AProcessor, for port, port_range in processor._out_ports.items(): renderer.add_out_port(port_range[0], port) + renderer.add_mode_index() return renderer.draw() diff --git a/perceval/utils/format.py b/perceval/utils/format.py index d5f948a4..25a9cc80 100644 --- a/perceval/utils/format.py +++ b/perceval/utils/format.py @@ -110,27 +110,17 @@ def simple_complex(c, precision=1e-6, nsimplify=True, fracmax=63): return spr+spz*sp.I, cr+cz -SPECIAL_OUTPUTS = { - 'PERM': '_╲ ╱\n_ ╳ \n_╱ ╲' -} - - def format_parameters(params: dict, precision: float = 1e-6, nsimplify: bool = True, separator: str = '\n') -> str: """ Prepares a string output from a dictionary of parameters. params: dictionary where keys are the parameter names and values are the corresponding parameter value. Values can either be a string or a float. - If a key is found in SPECIAL_OUTPUTS, the value is replaced by the hardcoded value. precision: Rounds a float value to the given precision nsimplify: Try to simplify numerical display in case of float value separator: String separator for the final join """ output = [] for key, value in params.items(): - if key in SPECIAL_OUTPUTS: - output.append(SPECIAL_OUTPUTS[key]) - continue - if not isinstance(value, str): _, value = simple_float(value, precision, nsimplify) output.append(f'{key}={value}') From 125ea617a59899bb09c662693c196e28608c4813 Mon Sep 17 00:00:00 2001 From: Eric Bertasi Date: Fri, 23 Aug 2024 16:56:47 +0200 Subject: [PATCH 2/5] Split renderer classes in multiple files --- perceval/rendering/circuit/__init__.py | 2 +- perceval/rendering/circuit/canvas_renderer.py | 400 +++++++++ perceval/rendering/circuit/create_renderer.py | 66 ++ perceval/rendering/circuit/renderer.py | 785 ------------------ .../rendering/circuit/renderer_interface.py | 192 +++++ perceval/rendering/circuit/text_renderer.py | 226 +++++ 6 files changed, 885 insertions(+), 786 deletions(-) create mode 100644 perceval/rendering/circuit/canvas_renderer.py create mode 100644 perceval/rendering/circuit/create_renderer.py delete mode 100644 perceval/rendering/circuit/renderer.py create mode 100644 perceval/rendering/circuit/renderer_interface.py create mode 100644 perceval/rendering/circuit/text_renderer.py diff --git a/perceval/rendering/circuit/__init__.py b/perceval/rendering/circuit/__init__.py index 6641637c..2a69ca7e 100644 --- a/perceval/rendering/circuit/__init__.py +++ b/perceval/rendering/circuit/__init__.py @@ -33,7 +33,7 @@ from .phys_skin import PhysSkin from .symb_skin import SymbSkin from .debug_skin import DebugSkin -from .renderer import create_renderer +from .create_renderer import create_renderer class DisplayConfig: diff --git a/perceval/rendering/circuit/canvas_renderer.py b/perceval/rendering/circuit/canvas_renderer.py new file mode 100644 index 00000000..1e62fd8b --- /dev/null +++ b/perceval/rendering/circuit/canvas_renderer.py @@ -0,0 +1,400 @@ +# MIT License +# +# Copyright (c) 2022 Quandela +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# As a special exception, the copyright holders of exqalibur library give you +# permission to combine exqalibur with code included in the standard release of +# Perceval under the MIT license (or modified versions of such code). You may +# copy and distribute such a combined system following the terms of the MIT +# license for both exqalibur and Perceval. This exception for the usage of +# exqalibur is limited to the python bindings used by Perceval. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from copy import copy + +from .abstract_skin import ModeStyle +from .renderer_interface import ICircuitRenderer +from ..canvas import Canvas +from perceval.components import ACircuit, APort, PortLocation, PERM + + +class _PortPos: + def __init__(self, x, y): + self.x = x + self.y = y + + +class CanvasRenderer(ICircuitRenderer): + + AFFIX_PORT_SIZE = 15 + AFFIX_ALL_SIZE = 25 + SCALE = 50 + + def __init__(self, nsize, canvas: Canvas, skin): + super().__init__(nsize) + # first position available for row n + self._chart = [0] * (nsize + 1) + + self._canvas = canvas + self._skin = skin + self._canvas.set_offset( + (0, 0), + CanvasRenderer.AFFIX_ALL_SIZE, + CanvasRenderer.SCALE * (nsize + 1)) + self._n_font_size = min(10, max(6, self._nsize + 1)) + + self._herald_info = None + + self._in_port_pos = [] + self._out_port_pos = [] + for i in range(nsize): + self._in_port_pos.append(_PortPos(0, i)) + for i in range(nsize): + self._out_port_pos.append(_PortPos(0, i)) + + def open(self): + for k in range(self._nsize): + mode_style = self._skin.style[self._mode_style[k]] + if mode_style['stroke']: + self._canvas.add_mpath([ + "M", + CanvasRenderer.AFFIX_ALL_SIZE - CanvasRenderer.AFFIX_PORT_SIZE, + CanvasRenderer.SCALE / 2 + CanvasRenderer.SCALE * k, + "l", + CanvasRenderer.AFFIX_PORT_SIZE, 0], **mode_style) + + def get_circuit_size(self, circuit: ACircuit, recursive: bool = False): + return self._skin.get_size(circuit, recursive) + + def add_mode_index(self): + self._canvas.set_offset( + (CanvasRenderer.AFFIX_ALL_SIZE + max(self._chart) * CanvasRenderer.SCALE, 0), + CanvasRenderer.AFFIX_ALL_SIZE, + CanvasRenderer.SCALE * (self._nsize + 1)) + for k in range(self._nsize): + if self._mode_style[k] != ModeStyle.HERALD: + self._canvas.add_text( + (CanvasRenderer.AFFIX_ALL_SIZE, + CanvasRenderer.SCALE / 2 + 3 + CanvasRenderer.SCALE * k), + str(k), + self._n_font_size, + ta="right") + + self._canvas.set_offset( + (0, 0), + CanvasRenderer.AFFIX_ALL_SIZE, + CanvasRenderer.SCALE * (self._nsize + 1)) + for k in range(self._nsize): + if self._mode_style[k] != ModeStyle.HERALD: + self._canvas.add_text( + ( + 0, + CanvasRenderer.SCALE / 2 + 3 + CanvasRenderer.SCALE * k + ), + str(k), + self._n_font_size, + ta="left") + + def add_out_port(self, n_mode: int, port: APort): + max_pos = max(self._chart[0:self._nsize]) + h_pos = self._out_port_pos[n_mode].x + v_pos = self._out_port_pos[n_mode].y + self._canvas.set_offset( + ( + CanvasRenderer.AFFIX_ALL_SIZE + CanvasRenderer.SCALE * (h_pos or max_pos), + CanvasRenderer.SCALE * v_pos + ), + CanvasRenderer.AFFIX_ALL_SIZE, + CanvasRenderer.SCALE) + self._canvas.add_shape(self._skin.get_shape(port, PortLocation.OUTPUT), port, None) + + def add_in_port(self, n_mode: int, port: APort): + h_pos = self._in_port_pos[n_mode].x * CanvasRenderer.SCALE + v_pos = self._in_port_pos[n_mode].y * CanvasRenderer.SCALE + self._canvas.set_offset( + (h_pos, v_pos), + CanvasRenderer.AFFIX_ALL_SIZE, + CanvasRenderer.SCALE) + self._canvas.add_shape(self._skin.get_shape(port, PortLocation.INPUT), port, None) + + def open_subblock(self, lines, name, size, color=None): + # Get recommended margins for this block + margins = self._current_subblock_info.get('margins', (0, 0)) + + start = lines[0] + end = lines[-1] + subblock_start = self.max_pos(start, end) + area = ( + subblock_start, + start, + size[0] + margins[0] + margins[1], + size[1]) + self._canvas.set_offset( + ( + CanvasRenderer.AFFIX_ALL_SIZE + CanvasRenderer.SCALE * area[0], + CanvasRenderer.SCALE * area[1] + ), + CanvasRenderer.SCALE * area[2], + CanvasRenderer.SCALE * area[3]) + if color is None: + color = "lightblue" + self._canvas.set_background_color(color) + self._canvas.add_rect( + (2, 2), + CanvasRenderer.SCALE * area[2] - 4, + CanvasRenderer.SCALE * area[3] - 4, + fill=color, + stroke_dasharray="1,2") + self._canvas.add_text( + (4, CanvasRenderer.SCALE * (end - start + 1) + 5), + name.upper(), 8) + # Extend lines on the left side + if margins[0]: + self.extend_pos(start, end, self.max_pos(start, end) + margins[0]) + + def close_subblock(self, lines): + start = lines[0] + end = lines[-1] + subblock_end = self.max_pos(start, end) + # Extend lines on the right side + right_margins = self._current_subblock_info.get('margins', (0, 0))[1] + self.extend_pos(start, end, subblock_end + right_margins) + + def max_pos(self, start, end, _=None): + return max(self._chart[start:end + 1]) + + def extend_pos(self, start, end, pos=None): + if pos is None: + maxpos = self.max_pos(start, end) + else: + maxpos = pos + for p in range(start, end + 1): + if self._chart[p] != maxpos: + self._canvas.set_offset( + ( + CanvasRenderer.AFFIX_ALL_SIZE + self._chart[p] * CanvasRenderer.SCALE, + p * CanvasRenderer.SCALE + ), + (maxpos - self._chart[p]) * CanvasRenderer.SCALE, + CanvasRenderer.SCALE) + style = self._skin.style[self._mode_style[p]] + if style['stroke']: + self._canvas.add_mline( + [ + 0, + CanvasRenderer.SCALE / 2, + (maxpos - self._chart[p]) * CanvasRenderer.SCALE, + CanvasRenderer.SCALE / 2 + ], + **style) + self._chart[p] = maxpos + + def _add_shape(self, lines, circuit, w, shape_fn=None, pos=None): + if shape_fn is None: + shape_fn = self._skin.get_shape(circuit) + start = lines[0] + end = lines[-1] + self.extend_pos(start, end, pos=pos) + max_pos = self.max_pos(start, end) + self._canvas.set_offset( + ( + CanvasRenderer.AFFIX_ALL_SIZE + CanvasRenderer.SCALE * max_pos, + CanvasRenderer.SCALE * start + ), + CanvasRenderer.SCALE * w, + CanvasRenderer.SCALE * (end - start + 1)) + modes = self._mode_style[start:(end + 1)] + self._canvas.add_shape(shape_fn, circuit, modes) + + def set_herald_info(self, info): + self._herald_info = info + + def _update_mode_style(self, lines, circuit, w: int): + if not isinstance(circuit, PERM): + input_heralds = {} + output_heralds = {} + + herald_info = self._herald_info if self._herald_info else {} + if circuit in herald_info: + output_heralds = herald_info[circuit].output_heralds + input_heralds = herald_info[circuit].input_heralds + + # Position input and output heralds + for in_mode, herald_in_mode in input_heralds.items(): + self._in_port_pos[herald_in_mode].y = lines[0] + in_mode + self._in_port_pos[herald_in_mode].x = \ + self._chart[lines[0] + in_mode] + # Start drawing this mode in "photonic" style + self._mode_style[lines[0] + in_mode] = ModeStyle.PHOTONIC + for out_mode, herald_out_mode in output_heralds.items(): + self._out_port_pos[herald_out_mode].y = lines[0] + out_mode + self._out_port_pos[herald_out_mode].x = \ + self._chart[lines[0] + out_mode] + w + # Stop drawing this mode (set it in "herald" style) + self._mode_style[lines[0] + out_mode] = ModeStyle.HERALD + + else: # Permutation case + m0 = lines[0] + out_modes = copy(self._mode_style) + for m_input, m_output in enumerate(circuit.perm_vector): + out_modes[m_output + lines[0]] = self._mode_style[m_input + m0] + self._mode_style = out_modes + + def append_circuit(self, lines, circuit, pos=None): + w = self._skin.get_width(circuit) + self._add_shape(lines, circuit, w, pos=pos) + self._update_mode_style(lines, circuit, w) + for i in range(lines[0], lines[-1] + 1): + self._chart[i] += w + + def append_subcircuit(self, lines, circuit): + w = self._skin.style_subcircuit['width'] + if w: + self._add_shape(lines, circuit, w, self._skin.subcircuit_shape) + self._update_mode_style(lines, circuit, w) + for i in range(lines[0], lines[-1] + 1): + self._chart[i] += w + + def close(self): + self.extend_pos(0, self._nsize - 1) + max_pos = self.max_pos(0, self._nsize - 1) + self._canvas.set_offset( + (CanvasRenderer.AFFIX_ALL_SIZE + CanvasRenderer.SCALE * max_pos, 0), + CanvasRenderer.AFFIX_ALL_SIZE, + CanvasRenderer.SCALE * (self._nsize + 1)) + for k in range(self._nsize): + mode_style = self._skin.style[self._mode_style[k]] + if mode_style['stroke']: + self._canvas.add_mpath([ + "M", + 0, + CanvasRenderer.SCALE / 2 + CanvasRenderer.SCALE * k, + "l", + CanvasRenderer.AFFIX_PORT_SIZE, 0], + **mode_style) + + def draw(self): + return self._canvas.draw() + + +class PreRenderer(ICircuitRenderer): + """ + This performs a dummy rendering pass to keep track of potential + layout issues to be fixed in the main rendering. + + At the moment it is keeping track of the recommended margins + for each subblock. + """ + def __init__(self, nsize, skin): + super().__init__(nsize) + self._chart = [0] * (nsize + 1) + self._herald_info = None + self._skin = skin + + # All these are relative to the subblock currently being rendered + self._herald_range = [0, 0] + self._subblock_start = 0 + + def open(self): + pass + + def draw(self): + pass + + def close(self): + pass + + def add_mode_index(self) -> None: + pass + + def add_out_port(self, m: int, port: APort) -> None: + pass + + def add_in_port(self, m: int, content: str) -> None: + pass + + def set_herald_info(self, info): + self._herald_info = info + + def get_circuit_size(self, circuit: ACircuit, recursive: bool = False): + return None + + def open_subblock(self, lines, name, size, color=None): + start = lines[0] + end = lines[-1] + self._subblock_start = self.max_pos(start, end) + self._herald_range = [1 << 32, -1] + + def close_subblock(self, lines): + start = lines[0] + end = lines[-1] + subblock_end = self.max_pos(start, end) + # Add the margin requirements for this subblock + self._current_subblock_info['margins'] = ( + int(self._herald_range[0] == self._subblock_start), + int(self._herald_range[1] == subblock_end)) + + def max_pos(self, start, end, _=None): + return max(self._chart[start:end + 1]) + + def extend_pos(self, start, end, pos=None): + if pos is None: + maxpos = self.max_pos(start, end) + else: + maxpos = pos + for p in range(start, end + 1): + self._chart[p] = maxpos + + def _add_shape(self, lines, circuit, w, shape_fn=None, pos=None): + self.extend_pos(lines[0], lines[-1], pos=pos) + + def _update_mode_style(self, lines, circuit, w: int): + if not isinstance(circuit, PERM): + input_heralds = {} + output_heralds = {} + herald_info = self._herald_info if self._herald_info else {} + if circuit in herald_info: + output_heralds = herald_info[circuit].output_heralds + input_heralds = herald_info[circuit].input_heralds + + for out_mode, herald_out_mode in output_heralds.items(): + self._herald_range[1] = max( + self._herald_range[1], + self._chart[lines[0] + out_mode] + w) + for in_mode, herald_in_mode in input_heralds.items(): + self._herald_range[0] = min( + self._herald_range[0], + self._chart[lines[0] + in_mode]) + + def append_circuit(self, lines, circuit, pos=None): + w = self._skin.get_width(circuit) + if w: + self._add_shape(lines, circuit, w, pos=pos) + self._update_mode_style(lines, circuit, w) + for i in range(lines[0], lines[-1] + 1): + self._chart[i] += w + + def append_subcircuit(self, lines, circuit): + w = self._skin.style_subcircuit['width'] + if w: + self._add_shape(lines, circuit, w, self._skin.subcircuit_shape) + self._update_mode_style(lines, circuit, w) + for i in range(lines[0], lines[-1] + 1): + self._chart[i] += w diff --git a/perceval/rendering/circuit/create_renderer.py b/perceval/rendering/circuit/create_renderer.py new file mode 100644 index 00000000..f620bf98 --- /dev/null +++ b/perceval/rendering/circuit/create_renderer.py @@ -0,0 +1,66 @@ +# MIT License +# +# Copyright (c) 2022 Quandela +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# As a special exception, the copyright holders of exqalibur library give you +# permission to combine exqalibur with code included in the standard release of +# Perceval under the MIT license (or modified versions of such code). You may +# copy and distribute such a combined system following the terms of the MIT +# license for both exqalibur and Perceval. This exception for the usage of +# exqalibur is limited to the python bindings used by Perceval. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from typing import Tuple, Union + +from .renderer_interface import ICircuitRenderer +from .abstract_skin import ASkin +from .canvas_renderer import CanvasRenderer, PreRenderer +from .text_renderer import TextRenderer +from ..canvas import LatexCanvas, MplotCanvas, SvgCanvas +from ..format import Format + + +_CANVAS = { + Format.HTML: SvgCanvas, + Format.MPLOT: MplotCanvas, + Format.LATEX: LatexCanvas +} + + +def create_renderer( + m: int, # number of modes + output_format: Format = Format.TEXT, # rendering method + skin: ASkin = None, # skin (unused in text rendering) + **opts, +) -> Tuple[ICircuitRenderer, Union[ICircuitRenderer, None]]: + """ + Creates a renderer given the selected format. Dispatches parameters to generated canvas objects + A skin object is needed for circuit graphic rendering. + + This returns a (renderer, pre_renderer) tuple. It is recommended to + invoke the pre-renderer on the circuit to correctly pre-compute + additional position information that cannot be guessed in a single pass. + """ + if output_format == Format.TEXT: + return TextRenderer(m), None + + assert skin is not None, "A skin must be selected for circuit rendering" + canvas = _CANVAS[output_format](**opts) + return CanvasRenderer(m, canvas, skin), PreRenderer(m, skin) diff --git a/perceval/rendering/circuit/renderer.py b/perceval/rendering/circuit/renderer.py deleted file mode 100644 index 13ab7aef..00000000 --- a/perceval/rendering/circuit/renderer.py +++ /dev/null @@ -1,785 +0,0 @@ -# MIT License -# -# Copyright (c) 2022 Quandela -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# As a special exception, the copyright holders of exqalibur library give you -# permission to combine exqalibur with code included in the standard release of -# Perceval under the MIT license (or modified versions of such code). You may -# copy and distribute such a combined system following the terms of the MIT -# license for both exqalibur and Perceval. This exception for the usage of -# exqalibur is limited to the python bindings used by Perceval. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -from abc import ABC, abstractmethod -import copy -import math -from typing import Any, Tuple - -from perceval.rendering.circuit import ASkin, ModeStyle -from perceval.rendering.format import Format -from perceval.rendering.canvas import Canvas, MplotCanvas, SvgCanvas, LatexCanvas -from perceval.components import ACircuit, Circuit, APort, PortLocation, PERM, Herald, Barrier -from perceval.utils.format import format_parameters - - -class PortPos: - def __init__(self, x, y): - self.x = x - self.y = y - - -_PERM_DESC = '_╲ ╱\n_ ╳ \n_╱ ╲' - - -class ICircuitRenderer(ABC): - """ - Base class for circuit renderers. - Provides an interface to implement + a render_circuit() generic method. - ICircuitRenderer internally works with circuit sizes in arbitrary units (AU), where single components and composite - circuits size are measured by a given skin object. - """ - - def __init__(self, nsize): - self._nsize = nsize # number of modes - self._mode_style = [ModeStyle.PHOTONIC] * nsize - self._in_port_pos = [] - self._out_port_pos = [] - for i in range(nsize): - self._in_port_pos.append(PortPos(0, i)) - for i in range(nsize): - self._out_port_pos.append(PortPos(0, i)) - - # A dictionary mapping a subblock to information pertaining to its - # rendering. This is written by the pre-rendering pass, and read by - # the main rendering pass. - self._subblock_info = {} - - # Custom settings for the subblock being rendered. - # They are loaded before open_subblock is invoked. - self._current_subblock_info = {} - - def set_mode_style(self, index, style): - self._mode_style[index] = style - - def render_circuit(self, - circuit: ACircuit, - shift: int = 0, - recursive: bool = False, - precision: float = 1e-6, - nsimplify: bool = True) -> None: - """Renders the input circuit""" - if not isinstance(circuit, Circuit): - self.append_circuit(tuple(p + shift for p in range(circuit.m)), circuit) - - if circuit.is_composite() and circuit.ncomponents() > 0: - grouped_components = circuit.group_components_by_xgrid() - for group in grouped_components: - # each component of the group is to be rendered at the same horizontal position, use built-in - # extend_pos method for that - pos = None - if len(group) > 1: - pos = -1 - for r, _ in group: - pos = max(pos, self.max_pos(r[0], r[-1])) - for r, c in group: - shiftr = tuple(p + shift for p in r) - if c.is_composite() and c._components: - if recursive: - self._current_subblock_info = self._subblock_info.setdefault(c, {}) - self.open_subblock(shiftr, c.name, self.get_circuit_size(c, recursive=False), c._color) - self.render_circuit( - c, - shift=shiftr[0], - precision=precision, - nsimplify=nsimplify) - self.close_subblock(shiftr) - else: - self.append_subcircuit(shiftr, c) - else: - self.append_circuit(shiftr, c, pos=pos) - self.extend_pos(0, circuit.m - 1) - - @abstractmethod - def get_circuit_size(self, circuit: ACircuit, recursive: bool = False) -> Tuple[int, int]: - """ - Returns the circuit size (in AU) - """ - - @abstractmethod - def max_pos(self, start, end) -> int: - """ - Returns the highest horizontal position on the circuit graph, between start and end modes (in AU) - """ - - @abstractmethod - def extend_pos(self, start: int, end: int) -> None: - """ - Extends horizontal position on the circuit graph, from modes 'start' to 'end' - """ - - @abstractmethod - def open(self) -> None: - """ - Starts the circuit drawing - """ - - @abstractmethod - def close(self) -> None: - """ - Finalizes circuit rendering when nothing more needs to be added. - The opposite 'open' action should be run in __init__. - """ - - @abstractmethod - def open_subblock(self, lines: Tuple[int, ...], name: str, size: Tuple[int, int], color=None) -> None: - """ - Opens a visual area, highlighting a part of the circuit - """ - - @abstractmethod - def close_subblock(self, lines: Tuple[int, ...]) -> None: - """ - Close a visual area - """ - - @abstractmethod - def draw(self) -> Any: - """ - Finalize drawing, returns a fully drawn circuit (type is relative to the rendering method which was used). - This should always be the last call. - """ - - @abstractmethod - def append_subcircuit(self, lines: Tuple[int, ...], circuit: Circuit) -> None: - """ - Add a composite circuit to the rendering. Render each subcomponent independently. - """ - - @abstractmethod - def append_circuit(self, lines: Tuple[int, ...], circuit: ACircuit, pos=None) -> None: - """ - Add a component (or a circuit treated as a single component) to the rendering, on modes 'lines' - """ - - @abstractmethod - def add_mode_index(self) -> None: - """ - Render mode indexes on the right and left side of a previously rendered circuit - """ - - @abstractmethod - def add_out_port(self, m: int, port: APort) -> None: - """ - Render a port on the right side (outputs) of a previously rendered circuit, located on mode 'm' - """ - - @abstractmethod - def add_in_port(self, m: int, content: str) -> None: - """ - Render a port on the left side (inputs) of a previously rendered circuit, located on mode 'm' - """ - - def set_herald_info(self, info): - """ - Provides the renderer with a pre-computed dict mapping a component to - information about which heralds are attached to its input and output - ports. This is used to correctly position the Heralds within the - circuit box, and not with the input and output ports. - """ - pass - - @property - def subblock_info(self) -> dict: - """ - A dictionary mapping a subblock to a dictionary of settings relevant - to its rendering. - """ - return self._subblock_info - - -class TextRenderer(ICircuitRenderer): - def __init__(self, nsize, hc=3, min_box_size=5): - super().__init__(nsize) - self._hc = hc - self._h = [''] * (hc * nsize + 2) - self.extend_pos(0, self._nsize - 1) - self._depth = [0] * nsize - self._offset = 0 - self.min_box_size = min_box_size - - def get_circuit_size(self, circuit: ACircuit, recursive: bool = False): - return None # Don't need circuit size for text rendering - - def open(self): - for k in range(self._nsize): - self._h[self._hc * k + 2] += "──" - - def close(self): - self.extend_pos(0, self._nsize - 1) - for k in range(self._nsize): - self._h[self._hc * k + 2] += "──" - - def max_pos(self, start, end, header=False): - maxpos = 0 - for nl in range(start * self._hc + (not header and 1 or 0), end * self._hc + 4 + (header and 1 or 0)): - if len(self._h[nl]) > maxpos: - maxpos = len(self._h[nl]) - return maxpos - - def extend_pos(self, start, end, internal=False, header=False, char=" ", pos=None): - if pos is None: - maxpos = self.max_pos(start, end, header) - else: - maxpos = pos - for i in range(start * self._hc + (not header and 1 or 0), end * self._hc + 4 + ((header and not internal) and 1 or 0)): - if internal: - self._h[i] += char * (maxpos - len(self._h[i])) - else: - self._h[i] += ((i % self._hc) == 2 and "─" or char) * (maxpos - len(self._h[i])) - - def open_subblock(self, lines, name, size, color=None): - start = lines[0] - end = lines[-1] - self.extend_pos(start, end, header=True) - for k in range(start * self._hc, end * self._hc + 4): - if k == start * self._hc: - self._h[k] += "╔[" + name + "]" - elif k % self._hc == 2: - self._h[k] += "╫" - else: - self._h[k] += "║" - self._h[end * self._hc + 4] += "╚" - - def close_subblock(self, lines): - start = lines[0] - end = lines[-1] - self.extend_pos(start, end, header=True) - for k in range(start * self._hc, end * self._hc + 4): - if k == start * self._hc: - self._h[k] += "╗" - elif k % self._hc == 2: - self._h[k] += "╫" - else: - self._h[k] += "║" - self._h[end * self._hc + 4] += "╝" - - def append_subcircuit(self, lines, circuit): - self.open_subblock(lines, circuit.name, None) - self.extend_pos(lines[0], lines[-1], header=True, internal=True, char="░") - self.close_subblock(lines) - - def append_circuit(self, lines, circuit, pos=None): - # opening the box - start = lines[0] - end = lines[-1] - self.extend_pos(start, end, pos=pos) - - if isinstance(circuit, Barrier): - for k in range(start * self._hc + 1, (end + 1) * self._hc + 1): - if k % self._hc == 2: - self._h[k] += "──║──" - else: - self._h[k] += " ║ " - self.extend_pos(start, end, pos=pos) - return - - # put variables on the right number of lines - if isinstance(circuit, PERM): - content = _PERM_DESC - else: - content = format_parameters(circuit.get_variables(), 1e-3, False) - content = circuit.name + (content and "\n" + content or "") - lcontents = content.split("\n") - if start == end: - content = " ".join(lcontents) - else: - nperlines = math.ceil((len(lcontents) - 1) / ((end - start) * self._hc)) - nlcontents = [lcontents[0]] - idx = 1 - pnlcontent = [] - while idx < len(lcontents): - pnlcontent.append(lcontents[idx]) - idx += 1 - if len(pnlcontent) == nperlines: - nlcontents.append(" ".join(pnlcontent)) - pnlcontent = [] - if pnlcontent: - nlcontents.append(" ".join(pnlcontent)) - content = "\n".join(nlcontents) - - # display box opening - for k in range(start, end + 1): - self._depth[k] += 1 - for k in range(start * self._hc + 1, end * self._hc + 3): - if k == start * self._hc + 1: - self._h[k] += "╭" - elif k % self._hc == 2: - self._h[k] += "┤" - else: - self._h[k] += "│" - self._h[end * self._hc + 3] += "╰" - - lcontents = content.split("\n") - maxw = max(len(nl) for nl in lcontents) - maxw = max(maxw, self.min_box_size) - # check if there are some "special effects" (centering _, right adjusting) - for idx, l in enumerate(lcontents): - if l.startswith("_"): - lcontents[idx] = (" " * ((maxw - (len(l) - 1)) // 2)) + l[1:] - - for i in range(maxw): - self._h[start * self._hc + 1] += "─" - self._h[end * self._hc + 3] += "─" - for j, l in enumerate(lcontents): - if i < len(l): - self._h[self._hc * start + 2 + j] += l[i] - self.extend_pos(start, end, True) - # closing the box - for k in range(start * self._hc + 1, end * self._hc + 3): - if k == start * self._hc + 1: - self._h[k] += "╮" - elif k % self._hc == 2: - self._h[k] += "├" - else: - self._h[k] += "│" - self._h[end * self._hc + 3] += "╯" - - def draw(self): - return "\n".join(self._h) - - def _set_offset(self, offset): - offset_diff = offset - self._offset - if offset_diff <= 0: - return - self._offset = offset - for nl in range(len(self._h)): - self._h[nl] = ' ' * offset_diff + self._h[nl] - - def add_mode_index(self): - offset = len(str(self._nsize)) + 1 - self._set_offset(offset) - for k in range(self._nsize): - self._h[self._hc * k + 2] = f'{k:{offset-1}d}:' + self._h[self._hc * k + 2][offset:] - self._h[self._hc * k + 2] += ':' + str(k) + f" (depth {self._depth[k]})" - - def add_out_port(self, n_mode: int, port: APort): - content = '' - if isinstance(port, Herald): - content = port.expected - for i in range(port.m): - self._h[self._hc * (n_mode + i) + 2] += f'[{content})' - self._h[self._hc * (n_mode + i) + 3] += f"[{port.name}]" - - def add_in_port(self, n_mode: int, port: APort): - content = '' - if isinstance(port, Herald): - content = str(port.expected) - shape_size = len(content) + 2 - name = port.name - name_size = len(name) - self._set_offset(max(shape_size, name_size)) - for i in range(port.m): - self._h[self._hc * (n_mode + i) + 2] = f'({content}]' + \ - '─' * (self._offset - shape_size) + \ - self._h[self._hc * (n_mode + i) + 2][self._offset:] - self._h[self._hc * (n_mode + i) + 3] = name + ' ' * \ - (self._offset - name_size) + \ - self._h[self._hc * (n_mode + i) + 3][self._offset:] - - -class CanvasRenderer(ICircuitRenderer): - - AFFIX_PORT_SIZE = 15 - AFFIX_ALL_SIZE = 25 - SCALE = 50 - - def __init__(self, nsize, canvas: Canvas, skin): - super().__init__(nsize) - # first position available for row n - self._chart = [0] * (nsize + 1) - - self._canvas = canvas - self._skin = skin - self._canvas.set_offset( - (0, 0), - CanvasRenderer.AFFIX_ALL_SIZE, - CanvasRenderer.SCALE * (nsize + 1)) - self._n_font_size = min(10, max(6, self._nsize + 1)) - - self._herald_info = None - - def open(self): - for k in range(self._nsize): - mode_style = self._skin.style[self._mode_style[k]] - if mode_style['stroke']: - self._canvas.add_mpath([ - "M", - CanvasRenderer.AFFIX_ALL_SIZE - CanvasRenderer.AFFIX_PORT_SIZE, - CanvasRenderer.SCALE / 2 + CanvasRenderer.SCALE * k, - "l", - CanvasRenderer.AFFIX_PORT_SIZE, 0], **mode_style) - - def get_circuit_size(self, circuit: ACircuit, recursive: bool = False): - return self._skin.get_size(circuit, recursive) - - def add_mode_index(self): - self._canvas.set_offset( - (CanvasRenderer.AFFIX_ALL_SIZE + max(self._chart) * CanvasRenderer.SCALE, 0), - CanvasRenderer.AFFIX_ALL_SIZE, - CanvasRenderer.SCALE * (self._nsize + 1)) - for k in range(self._nsize): - if self._mode_style[k] != ModeStyle.HERALD: - self._canvas.add_text( - (CanvasRenderer.AFFIX_ALL_SIZE, - CanvasRenderer.SCALE / 2 + 3 + CanvasRenderer.SCALE * k), - str(k), - self._n_font_size, - ta="right") - - self._canvas.set_offset( - (0, 0), - CanvasRenderer.AFFIX_ALL_SIZE, - CanvasRenderer.SCALE * (self._nsize + 1)) - for k in range(self._nsize): - if self._mode_style[k] != ModeStyle.HERALD: - self._canvas.add_text( - ( - 0, - CanvasRenderer.SCALE / 2 + 3 + CanvasRenderer.SCALE * k - ), - str(k), - self._n_font_size, - ta="left") - - def add_out_port(self, n_mode: int, port: APort): - max_pos = max(self._chart[0:self._nsize]) - h_pos = self._out_port_pos[n_mode].x - v_pos = self._out_port_pos[n_mode].y - self._canvas.set_offset( - ( - CanvasRenderer.AFFIX_ALL_SIZE + CanvasRenderer.SCALE * (h_pos or max_pos), - CanvasRenderer.SCALE * v_pos - ), - CanvasRenderer.AFFIX_ALL_SIZE, - CanvasRenderer.SCALE) - self._canvas.add_shape(self._skin.get_shape(port, PortLocation.OUTPUT), port, None) - - def add_in_port(self, n_mode: int, port: APort): - h_pos = self._in_port_pos[n_mode].x * CanvasRenderer.SCALE - v_pos = self._in_port_pos[n_mode].y * CanvasRenderer.SCALE - self._canvas.set_offset( - (h_pos, v_pos), - CanvasRenderer.AFFIX_ALL_SIZE, - CanvasRenderer.SCALE) - self._canvas.add_shape(self._skin.get_shape(port, PortLocation.INPUT), port, None) - - def open_subblock(self, lines, name, size, color=None): - # Get recommended margins for this block - margins = self._current_subblock_info.get('margins', (0, 0)) - - start = lines[0] - end = lines[-1] - subblock_start = self.max_pos(start, end) - area = ( - subblock_start, - start, - size[0] + margins[0] + margins[1], - size[1]) - self._canvas.set_offset( - ( - CanvasRenderer.AFFIX_ALL_SIZE + CanvasRenderer.SCALE * area[0], - CanvasRenderer.SCALE * area[1] - ), - CanvasRenderer.SCALE * area[2], - CanvasRenderer.SCALE * area[3]) - if color is None: - color = "lightblue" - self._canvas.set_background_color(color) - self._canvas.add_rect( - (2, 2), - CanvasRenderer.SCALE * area[2] - 4, - CanvasRenderer.SCALE * area[3] - 4, - fill=color, - stroke_dasharray="1,2") - self._canvas.add_text( - (4, CanvasRenderer.SCALE * (end - start + 1) + 5), - name.upper(), 8) - # Extend lines on the left side - if margins[0]: - self.extend_pos(start, end, self.max_pos(start, end) + margins[0]) - - def close_subblock(self, lines): - start = lines[0] - end = lines[-1] - subblock_end = self.max_pos(start, end) - # Extend lines on the right side - right_margins = self._current_subblock_info.get('margins', (0, 0))[1] - self.extend_pos(start, end, subblock_end + right_margins) - - def max_pos(self, start, end, _=None): - return max(self._chart[start:end + 1]) - - def extend_pos(self, start, end, pos=None): - if pos is None: - maxpos = self.max_pos(start, end) - else: - maxpos = pos - for p in range(start, end + 1): - if self._chart[p] != maxpos: - self._canvas.set_offset( - ( - CanvasRenderer.AFFIX_ALL_SIZE + self._chart[p] * CanvasRenderer.SCALE, - p * CanvasRenderer.SCALE - ), - (maxpos - self._chart[p]) * CanvasRenderer.SCALE, - CanvasRenderer.SCALE) - style = self._skin.style[self._mode_style[p]] - if style['stroke']: - self._canvas.add_mline( - [ - 0, - CanvasRenderer.SCALE / 2, - (maxpos - self._chart[p]) * CanvasRenderer.SCALE, - CanvasRenderer.SCALE / 2 - ], - **style) - self._chart[p] = maxpos - - def _add_shape(self, lines, circuit, w, shape_fn=None, pos=None): - if shape_fn is None: - shape_fn = self._skin.get_shape(circuit) - start = lines[0] - end = lines[-1] - self.extend_pos(start, end, pos=pos) - max_pos = self.max_pos(start, end) - self._canvas.set_offset( - ( - CanvasRenderer.AFFIX_ALL_SIZE + CanvasRenderer.SCALE * max_pos, - CanvasRenderer.SCALE * start - ), - CanvasRenderer.SCALE * w, - CanvasRenderer.SCALE * (end - start + 1)) - modes = self._mode_style[start:(end + 1)] - self._canvas.add_shape(shape_fn, circuit, modes) - - def set_herald_info(self, info): - self._herald_info = info - - def _update_mode_style(self, lines, circuit, w: int): - if not isinstance(circuit, PERM): - input_heralds = {} - output_heralds = {} - - herald_info = self._herald_info if self._herald_info else {} - if circuit in herald_info: - output_heralds = herald_info[circuit].output_heralds - input_heralds = herald_info[circuit].input_heralds - - # Position input and output heralds - for in_mode, herald_in_mode in input_heralds.items(): - self._in_port_pos[herald_in_mode].y = lines[0] + in_mode - self._in_port_pos[herald_in_mode].x = \ - self._chart[lines[0] + in_mode] - # Start drawing this mode in "photonic" style - self._mode_style[lines[0] + in_mode] = ModeStyle.PHOTONIC - for out_mode, herald_out_mode in output_heralds.items(): - self._out_port_pos[herald_out_mode].y = lines[0] + out_mode - self._out_port_pos[herald_out_mode].x = \ - self._chart[lines[0] + out_mode] + w - # Stop drawing this mode (set it in "herald" style) - self._mode_style[lines[0] + out_mode] = ModeStyle.HERALD - - else: # Permutation case - m0 = lines[0] - out_modes = copy.copy(self._mode_style) - for m_input, m_output in enumerate(circuit.perm_vector): - out_modes[m_output + lines[0]] = self._mode_style[m_input + m0] - self._mode_style = out_modes - - def append_circuit(self, lines, circuit, pos=None): - w = self._skin.get_width(circuit) - self._add_shape(lines, circuit, w, pos=pos) - self._update_mode_style(lines, circuit, w) - for i in range(lines[0], lines[-1] + 1): - self._chart[i] += w - - def append_subcircuit(self, lines, circuit): - w = self._skin.style_subcircuit['width'] - if w: - self._add_shape(lines, circuit, w, self._skin.subcircuit_shape) - self._update_mode_style(lines, circuit, w) - for i in range(lines[0], lines[-1] + 1): - self._chart[i] += w - - def close(self): - self.extend_pos(0, self._nsize - 1) - max_pos = self.max_pos(0, self._nsize - 1) - self._canvas.set_offset( - (CanvasRenderer.AFFIX_ALL_SIZE + CanvasRenderer.SCALE * max_pos, 0), - CanvasRenderer.AFFIX_ALL_SIZE, - CanvasRenderer.SCALE * (self._nsize + 1)) - for k in range(self._nsize): - mode_style = self._skin.style[self._mode_style[k]] - if mode_style['stroke']: - self._canvas.add_mpath([ - "M", - 0, - CanvasRenderer.SCALE / 2 + CanvasRenderer.SCALE * k, - "l", - CanvasRenderer.AFFIX_PORT_SIZE, 0], - **mode_style) - - def draw(self): - return self._canvas.draw() - - -class PreRenderer(ICircuitRenderer): - """ - This performs a dummy rendering pass to keep track of potential - layout issues to be fixed in the main rendering. - - At the moment it is keeping track of the recommended margins - for each subblock. - """ - def __init__(self, nsize, skin): - super().__init__(nsize) - self._chart = [0] * (nsize + 1) - self._herald_info = None - self._skin = skin - - # All these are relative to the subblock currently being rendered - self._herald_range = [0, 0] - self._subblock_start = 0 - - def open(self): - pass - - def draw(self): - pass - - def close(self): - pass - - def add_mode_index(self) -> None: - pass - - def add_out_port(self, m: int, port: APort) -> None: - pass - - def add_in_port(self, m: int, content: str) -> None: - pass - - def set_herald_info(self, info): - self._herald_info = info - - def get_circuit_size(self, circuit: ACircuit, recursive: bool = False): - return None - - def open_subblock(self, lines, name, size, color=None): - start = lines[0] - end = lines[-1] - self._subblock_start = self.max_pos(start, end) - self._herald_range = [1 << 32, -1] - - def close_subblock(self, lines): - start = lines[0] - end = lines[-1] - subblock_end = self.max_pos(start, end) - # Add the margin requirements for this subblock - self._current_subblock_info['margins'] = ( - int(self._herald_range[0] == self._subblock_start), - int(self._herald_range[1] == subblock_end)) - - def max_pos(self, start, end, _=None): - return max(self._chart[start:end + 1]) - - def extend_pos(self, start, end, pos=None): - if pos is None: - maxpos = self.max_pos(start, end) - else: - maxpos = pos - for p in range(start, end + 1): - self._chart[p] = maxpos - - def _add_shape(self, lines, circuit, w, shape_fn=None, pos=None): - start = lines[0] - end = lines[-1] - self.extend_pos(start, end, pos=pos) - - def _update_mode_style(self, lines, circuit, w: int): - if not isinstance(circuit, PERM): - input_heralds = {} - output_heralds = {} - herald_info = self._herald_info if self._herald_info else {} - if circuit in herald_info: - output_heralds = herald_info[circuit].output_heralds - input_heralds = herald_info[circuit].input_heralds - - for out_mode, herald_out_mode in output_heralds.items(): - self._herald_range[1] = max( - self._herald_range[1], - self._chart[lines[0] + out_mode] + w) - for in_mode, herald_in_mode in input_heralds.items(): - self._herald_range[0] = min( - self._herald_range[0], - self._chart[lines[0] + in_mode]) - - def append_circuit(self, lines, circuit, pos=None): - w = self._skin.get_width(circuit) - if w: - self._add_shape(lines, circuit, w, pos=pos) - self._update_mode_style(lines, circuit, w) - for i in range(lines[0], lines[-1] + 1): - self._chart[i] += w - - def append_subcircuit(self, lines, circuit): - w = self._skin.style_subcircuit['width'] - if w: - self._add_shape(lines, circuit, w, self._skin.subcircuit_shape) - self._update_mode_style(lines, circuit, w) - for i in range(lines[0], lines[-1] + 1): - self._chart[i] += w - - -def create_renderer( - n: int, # number of modes - output_format: Format = Format.TEXT, # rendering method - skin: ASkin = None, # skin (unused in text rendering) - **opts, -) -> Tuple[ICircuitRenderer, ICircuitRenderer]: - """ - Creates a renderer given the selected format. Dispatches parameters to generated canvas objects - A skin object is needed for circuit graphic rendering. - - This returns a (renderer, pre_renderer) tuple. It is recommended to - invoke the pre-renderer on the circuit to correctly pre-compute - additional position information that cannot be guessed in a single pass. - """ - if output_format == Format.TEXT: - return TextRenderer(n), None - - assert skin is not None, "A skin must be selected for circuit graphical rendering" - if output_format == Format.HTML: - canvas = SvgCanvas(**opts) - elif output_format == Format.LATEX: - canvas = LatexCanvas(**opts) - else: - canvas = MplotCanvas(**opts) - return CanvasRenderer(n, canvas, skin), PreRenderer(n, skin) diff --git a/perceval/rendering/circuit/renderer_interface.py b/perceval/rendering/circuit/renderer_interface.py new file mode 100644 index 00000000..1da5720c --- /dev/null +++ b/perceval/rendering/circuit/renderer_interface.py @@ -0,0 +1,192 @@ +# MIT License +# +# Copyright (c) 2022 Quandela +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# As a special exception, the copyright holders of exqalibur library give you +# permission to combine exqalibur with code included in the standard release of +# Perceval under the MIT license (or modified versions of such code). You may +# copy and distribute such a combined system following the terms of the MIT +# license for both exqalibur and Perceval. This exception for the usage of +# exqalibur is limited to the python bindings used by Perceval. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +from abc import ABC, abstractmethod +from typing import Any, Tuple + +from .abstract_skin import ModeStyle +from perceval.components import ACircuit, Circuit, APort + + +class ICircuitRenderer(ABC): + """ + Base class for circuit renderers. + Provides an interface to implement + a render_circuit() generic method. + ICircuitRenderer internally works with circuit sizes in arbitrary units (AU), where single components and composite + circuits size are measured by a given skin object. + """ + + def __init__(self, nsize): + self._nsize = nsize # number of modes + self._mode_style = [ModeStyle.PHOTONIC] * nsize + + # A dictionary mapping a subblock to information pertaining to its + # rendering. This is written by the pre-rendering pass, and read by + # the main rendering pass. + self._subblock_info = {} + + # Custom settings for the subblock being rendered. + # They are loaded before open_subblock is invoked. + self._current_subblock_info = {} + + def set_mode_style(self, index, style): + self._mode_style[index] = style + + def render_circuit(self, + circuit: ACircuit, + shift: int = 0, + recursive: bool = False, + precision: float = 1e-6, + nsimplify: bool = True) -> None: + """Renders the input circuit""" + if not isinstance(circuit, Circuit): + self.append_circuit(tuple(p + shift for p in range(circuit.m)), circuit) + + if circuit.is_composite() and circuit.ncomponents() > 0: + grouped_components = circuit.group_components_by_xgrid() + for group in grouped_components: + # each component of the group is to be rendered at the same horizontal position, use built-in + # extend_pos method for that + pos = None + if len(group) > 1: + pos = -1 + for r, _ in group: + pos = max(pos, self.max_pos(r[0], r[-1])) + for r, c in group: + shiftr = tuple(p + shift for p in r) + if c.is_composite() and c._components: + if recursive: + self._current_subblock_info = self._subblock_info.setdefault(c, {}) + self.open_subblock(shiftr, c.name, self.get_circuit_size(c, recursive=False), c._color) + self.render_circuit( + c, + shift=shiftr[0], + precision=precision, + nsimplify=nsimplify) + self.close_subblock(shiftr) + else: + self.append_subcircuit(shiftr, c) + else: + self.append_circuit(shiftr, c, pos=pos) + self.extend_pos(0, circuit.m - 1) + + @abstractmethod + def get_circuit_size(self, circuit: ACircuit, recursive: bool = False) -> Tuple[int, int]: + """ + Returns the circuit size (in AU) + """ + + @abstractmethod + def max_pos(self, start, end) -> int: + """ + Returns the highest horizontal position on the circuit graph, between start and end modes (in AU) + """ + + @abstractmethod + def extend_pos(self, start: int, end: int) -> None: + """ + Extends horizontal position on the circuit graph, from modes 'start' to 'end' + """ + + @abstractmethod + def open(self) -> None: + """ + Starts the circuit drawing + """ + + @abstractmethod + def close(self) -> None: + """ + Finalizes circuit rendering when nothing more needs to be added. + The opposite 'open' action should be run in __init__. + """ + + @abstractmethod + def open_subblock(self, lines: Tuple[int, ...], name: str, size: Tuple[int, int], color=None) -> None: + """ + Opens a visual area, highlighting a part of the circuit + """ + + @abstractmethod + def close_subblock(self, lines: Tuple[int, ...]) -> None: + """ + Close a visual area + """ + + @abstractmethod + def draw(self) -> Any: + """ + Finalize drawing, returns a fully drawn circuit (type is relative to the rendering method which was used). + This should always be the last call. + """ + + @abstractmethod + def append_subcircuit(self, lines: Tuple[int, ...], circuit: Circuit) -> None: + """ + Add a composite circuit to the rendering. Render each subcomponent independently. + """ + + @abstractmethod + def append_circuit(self, lines: Tuple[int, ...], circuit: ACircuit, pos=None) -> None: + """ + Add a component (or a circuit treated as a single component) to the rendering, on modes 'lines' + """ + + @abstractmethod + def add_mode_index(self) -> None: + """ + Render mode indexes on the right and left side of a previously rendered circuit + """ + + @abstractmethod + def add_out_port(self, m: int, port: APort) -> None: + """ + Render a port on the right side (outputs) of a previously rendered circuit, located on mode 'm' + """ + + @abstractmethod + def add_in_port(self, m: int, content: str) -> None: + """ + Render a port on the left side (inputs) of a previously rendered circuit, located on mode 'm' + """ + + def set_herald_info(self, info): + """ + Provides the renderer with a pre-computed dict mapping a component to + information about which heralds are attached to its input and output + ports. This is used to correctly position the Heralds within the + circuit box, and not with the input and output ports. + """ + + @property + def subblock_info(self) -> dict: + """ + A dictionary mapping a subblock to a dictionary of settings relevant + to its rendering. + """ + return self._subblock_info diff --git a/perceval/rendering/circuit/text_renderer.py b/perceval/rendering/circuit/text_renderer.py new file mode 100644 index 00000000..7a3cd43b --- /dev/null +++ b/perceval/rendering/circuit/text_renderer.py @@ -0,0 +1,226 @@ +# MIT License +# +# Copyright (c) 2022 Quandela +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# As a special exception, the copyright holders of exqalibur library give you +# permission to combine exqalibur with code included in the standard release of +# Perceval under the MIT license (or modified versions of such code). You may +# copy and distribute such a combined system following the terms of the MIT +# license for both exqalibur and Perceval. This exception for the usage of +# exqalibur is limited to the python bindings used by Perceval. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import math + +from .renderer_interface import ICircuitRenderer +from perceval.components import ACircuit, Barrier, PERM, APort, Herald +from perceval.utils import format_parameters + + +class TextRenderer(ICircuitRenderer): + _PERM_DESC = '_╲ ╱\n_ ╳ \n_╱ ╲' # ASCII art representation of a permutation + + def __init__(self, nsize, hc=3, min_box_size=5): + super().__init__(nsize) + self._hc = hc + self._h = [''] * (hc * nsize + 2) + self.extend_pos(0, self._nsize - 1) + self._depth = [0] * nsize + self._offset = 0 + self.min_box_size = min_box_size + + def get_circuit_size(self, circuit: ACircuit, recursive: bool = False): + return None # Don't need circuit size for text rendering + + def open(self): + for k in range(self._nsize): + self._h[self._hc * k + 2] += "──" + + def close(self): + self.extend_pos(0, self._nsize - 1) + for k in range(self._nsize): + self._h[self._hc * k + 2] += "──" + + def max_pos(self, start, end, header=False): + maxpos = 0 + for nl in range(start * self._hc + (not header and 1 or 0), end * self._hc + 4 + (header and 1 or 0)): + if len(self._h[nl]) > maxpos: + maxpos = len(self._h[nl]) + return maxpos + + def extend_pos(self, start, end, internal=False, header=False, char=" ", pos=None): + if pos is None: + maxpos = self.max_pos(start, end, header) + else: + maxpos = pos + for i in range(start * self._hc + (not header and 1 or 0), end * self._hc + 4 + ((header and not internal) and 1 or 0)): + if internal: + self._h[i] += char * (maxpos - len(self._h[i])) + else: + self._h[i] += ((i % self._hc) == 2 and "─" or char) * (maxpos - len(self._h[i])) + + def open_subblock(self, lines, name, size, color=None): + start = lines[0] + end = lines[-1] + self.extend_pos(start, end, header=True) + for k in range(start * self._hc, end * self._hc + 4): + if k == start * self._hc: + self._h[k] += "╔[" + name + "]" + elif k % self._hc == 2: + self._h[k] += "╫" + else: + self._h[k] += "║" + self._h[end * self._hc + 4] += "╚" + + def close_subblock(self, lines): + start = lines[0] + end = lines[-1] + self.extend_pos(start, end, header=True) + for k in range(start * self._hc, end * self._hc + 4): + if k == start * self._hc: + self._h[k] += "╗" + elif k % self._hc == 2: + self._h[k] += "╫" + else: + self._h[k] += "║" + self._h[end * self._hc + 4] += "╝" + + def append_subcircuit(self, lines, circuit): + self.open_subblock(lines, circuit.name, None) + self.extend_pos(lines[0], lines[-1], header=True, internal=True, char="░") + self.close_subblock(lines) + + def append_circuit(self, lines, circuit, pos=None): + # opening the box + start = lines[0] + end = lines[-1] + self.extend_pos(start, end, pos=pos) + + if isinstance(circuit, Barrier): + for k in range(start * self._hc + 1, (end + 1) * self._hc + 1): + if k % self._hc == 2: + self._h[k] += "──║──" + else: + self._h[k] += " ║ " + self.extend_pos(start, end, pos=pos) + return + + # put variables on the right number of lines + if isinstance(circuit, PERM): + content = self._PERM_DESC + else: + content = format_parameters(circuit.get_variables(), 1e-3, False) + content = circuit.name + (content and "\n" + content or "") + lcontents = content.split("\n") + if start == end: + content = " ".join(lcontents) + else: + nperlines = math.ceil((len(lcontents) - 1) / ((end - start) * self._hc)) + nlcontents = [lcontents[0]] + idx = 1 + pnlcontent = [] + while idx < len(lcontents): + pnlcontent.append(lcontents[idx]) + idx += 1 + if len(pnlcontent) == nperlines: + nlcontents.append(" ".join(pnlcontent)) + pnlcontent = [] + if pnlcontent: + nlcontents.append(" ".join(pnlcontent)) + content = "\n".join(nlcontents) + + # display box opening + for k in range(start, end + 1): + self._depth[k] += 1 + for k in range(start * self._hc + 1, end * self._hc + 3): + if k == start * self._hc + 1: + self._h[k] += "╭" + elif k % self._hc == 2: + self._h[k] += "┤" + else: + self._h[k] += "│" + self._h[end * self._hc + 3] += "╰" + + lcontents = content.split("\n") + maxw = max(len(nl) for nl in lcontents) + maxw = max(maxw, self.min_box_size) + # check if there are some "special effects" (centering _, right adjusting) + for idx, l in enumerate(lcontents): + if l.startswith("_"): + lcontents[idx] = (" " * ((maxw - (len(l) - 1)) // 2)) + l[1:] + + for i in range(maxw): + self._h[start * self._hc + 1] += "─" + self._h[end * self._hc + 3] += "─" + for j, l in enumerate(lcontents): + if i < len(l): + self._h[self._hc * start + 2 + j] += l[i] + self.extend_pos(start, end, True) + # closing the box + for k in range(start * self._hc + 1, end * self._hc + 3): + if k == start * self._hc + 1: + self._h[k] += "╮" + elif k % self._hc == 2: + self._h[k] += "├" + else: + self._h[k] += "│" + self._h[end * self._hc + 3] += "╯" + + def draw(self): + return "\n".join(self._h) + + def _set_offset(self, offset): + offset_diff = offset - self._offset + if offset_diff <= 0: + return + self._offset = offset + for nl in range(len(self._h)): + self._h[nl] = ' ' * offset_diff + self._h[nl] + + def add_mode_index(self): + offset = len(str(self._nsize)) + 1 + self._set_offset(offset) + for k in range(self._nsize): + self._h[self._hc * k + 2] = f'{k:{offset-1}d}:' + self._h[self._hc * k + 2][offset:] + self._h[self._hc * k + 2] += ':' + str(k) + f" (depth {self._depth[k]})" + + def add_out_port(self, n_mode: int, port: APort): + content = '' + if isinstance(port, Herald): + content = port.expected + for i in range(port.m): + self._h[self._hc * (n_mode + i) + 2] += f'[{content})' + self._h[self._hc * (n_mode + i) + 3] += f"[{port.name}]" + + def add_in_port(self, n_mode: int, port: APort): + content = '' + if isinstance(port, Herald): + content = str(port.expected) + shape_size = len(content) + 2 + name = port.name + name_size = len(name) + self._set_offset(max(shape_size, name_size)) + for i in range(port.m): + self._h[self._hc * (n_mode + i) + 2] = f'({content}]' + \ + '─' * (self._offset - shape_size) + \ + self._h[self._hc * (n_mode + i) + 2][self._offset:] + self._h[self._hc * (n_mode + i) + 3] = name + ' ' * \ + (self._offset - name_size) + \ + self._h[self._hc * (n_mode + i) + 3][self._offset:] From 870c35c994c52d9b0f267336a0b4c99ba15a6de9 Mon Sep 17 00:00:00 2001 From: Eric Bertasi Date: Fri, 23 Aug 2024 17:04:49 +0200 Subject: [PATCH 3/5] Simplify td_shape --- perceval/rendering/circuit/phys_skin.py | 9 +++------ perceval/rendering/circuit/symb_skin.py | 9 +++------ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/perceval/rendering/circuit/phys_skin.py b/perceval/rendering/circuit/phys_skin.py index b46ddac5..08edd9bf 100644 --- a/perceval/rendering/circuit/phys_skin.py +++ b/perceval/rendering/circuit/phys_skin.py @@ -220,12 +220,9 @@ def pbs_shape(self, circuit, canvas, mode_style): def td_shape(self, circuit, canvas, mode_style): style = self.style[ModeStyle.PHOTONIC] - canvas.add_circle((34, 14), 11, stroke_width=5, fill=None, stroke="white") - canvas.add_circle((34, 14), 11, fill=None, **style) - canvas.add_circle((25, 14), 11, stroke_width=5, fill=None, stroke="white") - canvas.add_circle((25, 14), 11, fill=None, **style) - canvas.add_circle((16, 14), 11, stroke_width=5, fill=None, stroke="white") - canvas.add_circle((16, 14), 11, fill=None, **style) + for h_shift in [0, 9, 18]: + canvas.add_circle((34 - h_shift, 14), 11, stroke_width=5, fill=None, stroke="white") + canvas.add_circle((34 - h_shift, 14), 11, fill=None, **style) canvas.add_mline([0, 25, 19, 25], stroke="white", stroke_width=5) canvas.add_mline([0, 25, 19, 25], **style) canvas.add_mline([34, 25, 50, 25], stroke="white", stroke_width=5) diff --git a/perceval/rendering/circuit/symb_skin.py b/perceval/rendering/circuit/symb_skin.py index aff4d611..35abb078 100644 --- a/perceval/rendering/circuit/symb_skin.py +++ b/perceval/rendering/circuit/symb_skin.py @@ -207,12 +207,9 @@ def pbs_shape(self, circuit, canvas, mode_style): def td_shape(self, circuit, canvas, mode_style): stroke = self.style[ModeStyle.PHOTONIC]['stroke'] - canvas.add_circle((34, 14), 11, stroke="white", stroke_width=3) - canvas.add_circle((34, 14), 11, stroke=stroke, stroke_width=2) - canvas.add_circle((25, 14), 11, stroke="white", stroke_width=3) - canvas.add_circle((25, 14), 11, stroke=stroke, stroke_width=2) - canvas.add_circle((16, 14), 11, stroke="white", stroke_width=3) - canvas.add_circle((16, 14), 11, stroke=stroke, stroke_width=2) + for h_shift in [0, 9, 18]: + canvas.add_circle((34 - h_shift, 14), 11, stroke="white", stroke_width=3) + canvas.add_circle((34 - h_shift, 14), 11, stroke=stroke, stroke_width=2) canvas.add_mline([0, 25, 17, 25], stroke="white", stroke_width=3) canvas.add_mline([0, 25, 19, 25], stroke=stroke, stroke_width=2) canvas.add_mline([34, 25, 50, 25], stroke="white", stroke_width=3) From 9991c967215759db6f7783c5588c279bbda9ca24 Mon Sep 17 00:00:00 2001 From: Eric Bertasi Date: Mon, 23 Sep 2024 20:11:43 +0200 Subject: [PATCH 4/5] Make heralded modes more visible in DebugSkin Barriers now use mode style --- perceval/rendering/circuit/debug_skin.py | 11 ++--------- perceval/rendering/circuit/phys_skin.py | 8 ++++---- perceval/rendering/circuit/symb_skin.py | 11 +++++------ 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/perceval/rendering/circuit/debug_skin.py b/perceval/rendering/circuit/debug_skin.py index 289595d2..ea2d2dde 100644 --- a/perceval/rendering/circuit/debug_skin.py +++ b/perceval/rendering/circuit/debug_skin.py @@ -26,23 +26,16 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. - -import math -from multipledispatch import dispatch - -from perceval.components import AComponent, Circuit, Port, Herald, PortLocation,\ - unitary_components as cp,\ - non_unitary_components as nu +from perceval.components import unitary_components as cp from .abstract_skin import ModeStyle from .phys_skin import PhysSkin -from .skin_common import bs_convention_color class DebugSkin(PhysSkin): def __init__(self, compact_display: bool = False): super().__init__(compact_display) self.style[ModeStyle.PHOTONIC]["stroke_width"] = 8 - self.style[ModeStyle.HERALD] = {"stroke": "yellow", "stroke_width": 1} # Display ancillary modes in yellow + self.style[ModeStyle.HERALD] = {"stroke": "orange", "stroke_width": 3} # Display ancillary modes in yellow def ps_shape(self, circuit: cp.PS, canvas, mode_style): canvas.add_mline([0, 25, 50, 25], **self.style[ModeStyle.PHOTONIC]) diff --git a/perceval/rendering/circuit/phys_skin.py b/perceval/rendering/circuit/phys_skin.py index 08edd9bf..b5b73097 100644 --- a/perceval/rendering/circuit/phys_skin.py +++ b/perceval/rendering/circuit/phys_skin.py @@ -236,15 +236,15 @@ def unitary_shape(self, circuit, canvas, mode_style): canvas.add_rect((5, 5), 50*m-10, 50*m-10, fill="gold") canvas.add_text((25*m, 25*m), size=10, ta="middle", text=circuit.name) - def barrier_shape(self, circuit, canvas, mode_style): - if not circuit.visible: + def barrier_shape(self, barrier, canvas, mode_style): + if not barrier.visible: return - m = circuit.m + m = barrier.m if canvas.background_color is None: canvas.add_rect((10, 10), 30, 50 * m - 20, fill="whitesmoke", stroke="whitesmoke") for i in range(m): - canvas.add_mpath(["M", 0, 25 + i*50, "l", 50, 0], **self.style[ModeStyle.PHOTONIC]) + canvas.add_mpath(["M", 0, 25 + i*50, "l", 50, 0], **self.style[mode_style[i]]) canvas.add_rect((24, 10), 2, 50 * m - 20, fill="dimgrey", stroke="dimgrey") def perm_shape(self, circuit, canvas, mode_style): diff --git a/perceval/rendering/circuit/symb_skin.py b/perceval/rendering/circuit/symb_skin.py index 35abb078..2f82dc92 100644 --- a/perceval/rendering/circuit/symb_skin.py +++ b/perceval/rendering/circuit/symb_skin.py @@ -228,15 +228,14 @@ def unitary_shape(self, circuit, canvas, mode_style): **self.style[ModeStyle.PHOTONIC], fill="lightyellow") canvas.add_text((25*w, 25*w), size=10, ta="middle", text=circuit.name) - def barrier_shape(self, circuit, canvas, mode_style): - if not circuit.visible: + def barrier_shape(self, barrier: cp.Barrier, canvas, mode_style): + if not barrier.visible: return - m = circuit.m + + m = barrier.m canvas.add_mline((24, 10, 24, 50 * m - 16), stroke_width=1, stroke="lightgray") for i in range(m): - canvas.add_mpath( - ["M", 0, 25 + i*50, "l", 50, 0], - **self.style[ModeStyle.PHOTONIC]) + canvas.add_mpath(["M", 0, 25 + i*50, "l", 50, 0], **self.style[mode_style[i]]) def perm_shape(self, circuit, canvas, mode_style): for an_input, an_output in enumerate(circuit.perm_vector): From 0a034b783f9fe5174c938d08d737805d3d2e459c Mon Sep 17 00:00:00 2001 From: Eric Bertasi Date: Mon, 23 Sep 2024 20:38:23 +0200 Subject: [PATCH 5/5] Heralds can go through Barriers --- perceval/rendering/_processor_utils.py | 4 +- perceval/rendering/circuit/abstract_skin.py | 1 - perceval/rendering/circuit/debug_skin.py | 4 +- perceval/rendering/circuit/phys_skin.py | 4 +- perceval/rendering/circuit/symb_skin.py | 4 +- ...ocessor_with_heralds_and_barriers_phys.svg | 400 +++++++++--------- tests/test_visualization.py | 3 +- 7 files changed, 218 insertions(+), 202 deletions(-) diff --git a/perceval/rendering/_processor_utils.py b/perceval/rendering/_processor_utils.py index 5a1d93d0..a9cca79b 100644 --- a/perceval/rendering/_processor_utils.py +++ b/perceval/rendering/_processor_utils.py @@ -27,7 +27,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from perceval.components import PERM, AProcessor +from perceval.components import PERM, AProcessor, Barrier class ComponentHeraldInfo: @@ -82,6 +82,8 @@ def collect_herald_info(processor: AProcessor, recursive: bool): mode = component.perm_vector[mode - m0] + m0 else: mode = component.perm_vector.index(mode - m0) + m0 + elif isinstance(component, Barrier): + pass # Heralds can go through barriers too else: h_info = herald_info.setdefault( component, ComponentHeraldInfo()) diff --git a/perceval/rendering/circuit/abstract_skin.py b/perceval/rendering/circuit/abstract_skin.py index f4634680..7d56c4e1 100644 --- a/perceval/rendering/circuit/abstract_skin.py +++ b/perceval/rendering/circuit/abstract_skin.py @@ -59,7 +59,6 @@ def __init__(self, stroke_style, style_subcircuit, compact_display: bool = False self._compact = compact_display self.style = {ModeStyle.PHOTONIC: stroke_style, ModeStyle.HERALD: {"stroke": None, "stroke_width": 1} - # ModeStyle.HERALD: {"stroke": "yellow", "stroke_width": 1} # Use this for debug } self.style_subcircuit = style_subcircuit self.precision: float = 1e-6 diff --git a/perceval/rendering/circuit/debug_skin.py b/perceval/rendering/circuit/debug_skin.py index ea2d2dde..22208d08 100644 --- a/perceval/rendering/circuit/debug_skin.py +++ b/perceval/rendering/circuit/debug_skin.py @@ -54,5 +54,7 @@ def barrier_shape(self, circuit, canvas, mode_style): if canvas.background_color is None: canvas.add_rect((10, 10), 30, 50 * m - 20, fill="whitesmoke", stroke="whitesmoke") for i in range(m): - canvas.add_mpath(["M", 0, 25 + i*50, "l", 50, 0], **self.style[ModeStyle.PHOTONIC]) + style = self.style[mode_style[i]] + if style["stroke"]: + canvas.add_mpath(["M", 0, 25 + i*50, "l", 50, 0], **style) canvas.add_rect((24, 10), 2, 50 * m - 20, fill="dimgrey", stroke="dimgrey") diff --git a/perceval/rendering/circuit/phys_skin.py b/perceval/rendering/circuit/phys_skin.py index b5b73097..75f63c53 100644 --- a/perceval/rendering/circuit/phys_skin.py +++ b/perceval/rendering/circuit/phys_skin.py @@ -244,7 +244,9 @@ def barrier_shape(self, barrier, canvas, mode_style): if canvas.background_color is None: canvas.add_rect((10, 10), 30, 50 * m - 20, fill="whitesmoke", stroke="whitesmoke") for i in range(m): - canvas.add_mpath(["M", 0, 25 + i*50, "l", 50, 0], **self.style[mode_style[i]]) + style = self.style[mode_style[i]] + if style["stroke"]: + canvas.add_mpath(["M", 0, 25 + i*50, "l", 50, 0], **style) canvas.add_rect((24, 10), 2, 50 * m - 20, fill="dimgrey", stroke="dimgrey") def perm_shape(self, circuit, canvas, mode_style): diff --git a/perceval/rendering/circuit/symb_skin.py b/perceval/rendering/circuit/symb_skin.py index 2f82dc92..04226835 100644 --- a/perceval/rendering/circuit/symb_skin.py +++ b/perceval/rendering/circuit/symb_skin.py @@ -235,7 +235,9 @@ def barrier_shape(self, barrier: cp.Barrier, canvas, mode_style): m = barrier.m canvas.add_mline((24, 10, 24, 50 * m - 16), stroke_width=1, stroke="lightgray") for i in range(m): - canvas.add_mpath(["M", 0, 25 + i*50, "l", 50, 0], **self.style[mode_style[i]]) + style = self.style[mode_style[i]] + if style["stroke"]: + canvas.add_mpath(["M", 0, 25 + i*50, "l", 50, 0], **style) def perm_shape(self, circuit, canvas, mode_style): for an_input, an_output in enumerate(circuit.perm_vector): diff --git a/tests/imgs/test_svg_processor_with_heralds_and_barriers_phys.svg b/tests/imgs/test_svg_processor_with_heralds_and_barriers_phys.svg index cdd87961..59c5bb29 100644 --- a/tests/imgs/test_svg_processor_with_heralds_and_barriers_phys.svg +++ b/tests/imgs/test_svg_processor_with_heralds_and_barriers_phys.svg @@ -30,236 +30,188 @@ z - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - - + - + - + - + - + - + - + - + @@ -705,13 +657,13 @@ z - + - + @@ -725,10 +677,68 @@ z - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/test_visualization.py b/tests/test_visualization.py index b2a10238..b2c069bb 100644 --- a/tests/test_visualization.py +++ b/tests/test_visualization.py @@ -307,8 +307,7 @@ def test_svg_processor_with_heralds_perm_following_phys(tmp_path, save_figs): def test_svg_processor_with_heralds_and_barriers_phys(tmp_path, save_figs): - c = pcvl.Circuit(4) @ (1, PERM([1, 0])) // (1, BS()) // (0, PERM([1, 0])) // BS() // (1, PERM([1, 0])) - c.barrier() + c = pcvl.Circuit(4) // (1, PERM([1, 0])) @ (1, BS()) // (0, PERM([1, 0])) // BS() @ (1, PERM([1, 0])) pc = pcvl.Processor('SLOS', c) pc.add_herald(0, 0) pc.add_herald(2, 1)