From c3793718baecd1004f6cb366410f47f710d10d94 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson <oscar.gustafsson@gmail.com> Date: Sat, 15 Mar 2025 14:08:41 +0100 Subject: [PATCH] Use constrained layout for plots --- b_asic/architecture.py | 4 +- b_asic/gui_utils/mpl_window.py | 2 +- b_asic/resources.py | 8 ++-- b_asic/schedule.py | 67 +++++++++++++++++++-------- examples/latency_offset_scheduling.py | 20 ++++---- 5 files changed, 64 insertions(+), 37 deletions(-) diff --git a/b_asic/architecture.py b/b_asic/architecture.py index c641e95a..d7b41982 100644 --- a/b_asic/architecture.py +++ b/b_asic/architecture.py @@ -260,7 +260,7 @@ class Resource(HardwareBlock): **kwargs Passed to :meth:`~b_asic.resources.ProcessCollection.plot`. """ - fig, ax = plt.subplots() + fig, ax = plt.subplots(layout="constrained") self.plot_content(ax, **kwargs) if title: fig.suptitle(title) @@ -294,7 +294,7 @@ class Resource(HardwareBlock): This is visible in enriched shells, but the object itself has no further meaning (it is a Matplotlib Figure). """ - fig, ax = plt.subplots() + fig, ax = plt.subplots(layout="constrained") self.plot_content(ax) return fig diff --git a/b_asic/gui_utils/mpl_window.py b/b_asic/gui_utils/mpl_window.py index 1a6db678..2a350011 100644 --- a/b_asic/gui_utils/mpl_window.py +++ b/b_asic/gui_utils/mpl_window.py @@ -25,7 +25,7 @@ class MPLWindow(QDialog): self._dialog_layout = QVBoxLayout() self.setLayout(self._dialog_layout) - self._plot_fig = Figure(figsize=(5, 4), layout="compressed") + self._plot_fig = Figure(figsize=(5, 4), layout="constrained") self._plot_axes = self._plot_fig.subplots(*subplots) self._plot_canvas = FigureCanvas(self._plot_fig) diff --git a/b_asic/resources.py b/b_asic/resources.py index e54322bc..6f979cee 100644 --- a/b_asic/resources.py +++ b/b_asic/resources.py @@ -580,7 +580,7 @@ class ProcessCollection: # Set up the Axes object if ax is None: - _, _ax = plt.subplots() + _, _ax = plt.subplots(layout="constrained") else: _ax = ax @@ -724,7 +724,7 @@ class ProcessCollection: title : str, optional Figure title. """ - fig, ax = plt.subplots() + fig, ax = plt.subplots(layout="constrained") self.plot( ax=ax, show_name=show_name, @@ -1122,7 +1122,7 @@ class ProcessCollection: This is automatically displayed in e.g. Jupyter Qt console. """ - fig, ax = plt.subplots() + fig, ax = plt.subplots(layout="constrained") self.plot(ax=ax, show_markers=False) f = io.StringIO() fig.savefig(f, format="svg") # type: ignore @@ -1628,7 +1628,7 @@ class ProcessCollection: title : str Figure title. """ - fig, axes = plt.subplots(3, 1) + fig, axes = plt.subplots(3, 1, layout="constrained") self.plot_port_accesses(axes) if title: fig.suptitle(title) diff --git a/b_asic/schedule.py b/b_asic/schedule.py index a20410d4..aa075a20 100644 --- a/b_asic/schedule.py +++ b/b_asic/schedule.py @@ -18,7 +18,6 @@ from matplotlib.lines import Line2D from matplotlib.patches import PathPatch, Polygon from matplotlib.path import Path from matplotlib.ticker import MaxNLocator -from matplotlib.transforms import Bbox, TransformedBbox from b_asic import Signal from b_asic._preferences import ( @@ -1073,18 +1072,48 @@ class Schedule: self.set_y_location(graph_id, i) for graph_id in self._start_times: op = cast(Operation, self._sfg.find_by_id(graph_id)) + # Position Outputs adjacent to the operation generating them if isinstance(op, Output): - self.move_y_location( - graph_id, - self.get_y_location(op.preceding_operations[0].graph_id) + 1, - True, - ) + gen_op: Operation = op.preceding_operations[0] + if ( + gen_op.output_count == 1 + or op.input(0).signals[0].source.index >= gen_op.output_count / 2 + ): + # Single output operation generating or lower half of outputs + # Put below + self.move_y_location( + graph_id, + self.get_y_location(gen_op.graph_id) + 1, + True, + ) + else: + # Put above + self.move_y_location( + graph_id, + self.get_y_location(gen_op.graph_id), + True, + ) + # Position DontCares adjacent to the operation using them if isinstance(op, DontCare): - self.move_y_location( - graph_id, - self.get_y_location(op.subsequent_operations[0].graph_id), - True, - ) + source_op: Operation = op.subsequent_operations[0] + if ( + source_op.input_count == 1 + or op.output(0).signals[0].destination.index + < gen_op.input_count / 2 + ): + # For single input operation consuming or upper half, position above + self.move_y_location( + graph_id, + self.get_y_location(source_op.graph_id), + True, + ) + else: + # Position below + self.move_y_location( + graph_id, + self.get_y_location(source_op.graph_id), + True, + ) def _plot_schedule(self, ax: Axes, operation_gap: float = OPERATION_GAP) -> None: """Draw the schedule.""" @@ -1192,12 +1221,10 @@ class Schedule: y = np.array(_y) xvalues = x + op_start_time xy = np.stack((xvalues, y + y_pos)) - p = ax.add_patch(Polygon(xy.T, fc=_LATENCY_COLOR)) - p.set_clip_box(TransformedBbox(Bbox([[0, 0], [1, 1]]), ax.transAxes)) + ax.add_patch(Polygon(xy.T, fc=_LATENCY_COLOR)) if any(xvalues > self.schedule_time) and not isinstance(operation, Output): xy = np.stack((xvalues - self.schedule_time, y + y_pos)) - p = ax.add_patch(Polygon(xy.T, fc=_LATENCY_COLOR)) - p.set_clip_box(TransformedBbox(Bbox([[0, 0], [1, 1]]), ax.transAxes)) + ax.add_patch(Polygon(xy.T, fc=_LATENCY_COLOR)) if isinstance(operation, Input): ax.annotate( graph_id, @@ -1271,7 +1298,7 @@ class Schedule: + 1 + (OPERATION_GAP if operation_gap is None else operation_gap) ) - ax.axis([-1, self._schedule_time + 1, y_position_max, 0]) # Inverted y-axis + ax.axis([-0.8, self._schedule_time + 0.8, y_position_max, 0]) # Inverted y-axis ax.xaxis.set_major_locator(MaxNLocator(integer=True, min_n_ticks=1)) ax.axvline( 0, @@ -1335,8 +1362,8 @@ class Schedule: ------- The Matplotlib Figure. """ - height = len(self._start_times) * 0.3 + 2 - fig, ax = plt.subplots(figsize=(12, height)) + height = len(self._start_times) * 0.3 + 0.7 + fig, ax = plt.subplots(figsize=(12, height), layout="constrained") self._plot_schedule(ax, operation_gap=operation_gap) return fig @@ -1345,8 +1372,8 @@ class Schedule: Generate an SVG of the schedule. This is automatically displayed in e.g. Jupyter Qt console. """ - height = len(self._start_times) * 0.3 + 2 - fig, ax = plt.subplots(figsize=(12, height)) + height = len(self._start_times) * 0.3 + 0.7 + fig, ax = plt.subplots(figsize=(12, height), layout="constrained") self._plot_schedule(ax) buffer = io.StringIO() fig.savefig(buffer, format="svg") diff --git a/examples/latency_offset_scheduling.py b/examples/latency_offset_scheduling.py index 39c21b72..67fff73b 100644 --- a/examples/latency_offset_scheduling.py +++ b/examples/latency_offset_scheduling.py @@ -28,35 +28,35 @@ sfg # %% # Create an ASAP schedule for reference. -schedule = Schedule(sfg, scheduler=ASAPScheduler()) -schedule.show() +schedule1 = Schedule(sfg, scheduler=ASAPScheduler()) +schedule1.show() # %% # Create an ALAP schedule for reference. -schedule = Schedule(sfg, scheduler=ALAPScheduler()) -schedule.show() +schedule2 = Schedule(sfg, scheduler=ALAPScheduler()) +schedule2.show() # %% # Create a resource restricted schedule. -schedule = Schedule(sfg, scheduler=HybridScheduler()) -schedule.show() +schedule3 = Schedule(sfg, scheduler=HybridScheduler()) +schedule3.show() # %% # Create another schedule with shorter scheduling time by enabling cyclic. -schedule = Schedule( +schedule4 = Schedule( sfg, scheduler=HybridScheduler(), schedule_time=49, cyclic=True, ) -schedule.show() +schedule4.show() # %% # Push the schedule time to the rate limit for one MADS operator. -schedule = Schedule( +schedule5 = Schedule( sfg, scheduler=HybridScheduler(), schedule_time=15, cyclic=True, ) -schedule.show() +schedule5.show() -- GitLab