Source code for fabulous.fabric_generator.gds_generator.steps.while_step
"""A template for while loop steps.
Will be replaced into librelane eventually.
"""
from pathlib import Path
from typing import TYPE_CHECKING
from librelane.common.misc import slugify
from librelane.flows.flow import FlowProgressBar
from librelane.logging.logger import warn
from librelane.state.design_format import DesignFormat
from librelane.state.state import State
from librelane.steps.step import MetricsUpdate, Step, ViewsUpdate
if TYPE_CHECKING:
from librelane.config.variable import Variable
[docs]
class WhileStep(Step):
"""A step that runs a sub-step repeatedly while a condition is met."""
Steps: list[type[Step]]
max_iterations: int = 10
raise_on_failure: bool = True
break_on_failure: bool = True
_current_iter_dir: Path | None = None
def __init_subclass__(Self): # noqa: ANN204, D105
super().__init_subclass__()
available_inputs = set()
input_set: set[DesignFormat] = set()
output_set: set[DesignFormat] = set()
config_var_dict: dict[str, Variable] = {}
for step in Self.Steps:
for input in step.inputs: # noqa: A001
if input not in available_inputs:
input_set.add(input)
available_inputs.add(input)
for output in step.outputs:
available_inputs.add(output)
output_set.add(output)
for cvar in step.config_vars:
if existing := config_var_dict.get(cvar.name):
if existing != cvar:
raise TypeError(
"Internal error: composite step has mismatching "
f"config_vars: {cvar.name} contradicts an "
"earlier declaration"
)
else:
config_var_dict[cvar.name] = cvar
Self.inputs = list(input_set)
if Self.outputs == NotImplemented: # Allow for setting explicit outputs
Self.outputs = list(output_set)
if Self.config_vars:
config_var_dict.update({v.name: v for v in Self.config_vars})
Self.config_vars = list(config_var_dict.values())
[docs]
def condition(self, _state: State) -> bool:
"""Return true if the condition is met and keep the loop going."""
return True
[docs]
def mid_iteration_break(self, _state: State, _step: type[Step]) -> bool:
"""Return True to break the current iteration and start the next iteration.
If True, breaks the current iteration and starts the next iteration. Breaking
mid-iteration will not trigger the post_iteration_callback.
"""
return False
[docs]
def post_loop_callback(self, state: State) -> State:
"""Modify the state after all iterations are complete."""
return state
[docs]
def pre_iteration_callback(self, pre_iteration: State) -> State:
"""Modify the state before each iteration."""
return pre_iteration
[docs]
def post_iteration_callback(
self, post_iteration: State, _full_iter_completed: bool
) -> State:
"""Modify the state after each iteration."""
return post_iteration
[docs]
def get_current_iteration_dir(self) -> Path | None:
"""Get the current iteration directory, if any."""
return self._current_iter_dir
[docs]
def run(
self,
state_in: State,
**_kwargs: dict,
) -> tuple[ViewsUpdate, MetricsUpdate]:
"""Run the while loop step."""
current_state = state_in
total_views_update: dict = {}
total_metrics_update: dict = {}
progress_bar = FlowProgressBar(self.name)
ordinal_length = len(str(len(self.Steps) - 1))
start_state = state_in.copy()
progress_bar.start()
progress_bar.set_max_stage_count(self.max_iterations)
for i in range(self.max_iterations):
progress_bar.start_stage(f"Iteration {i + 1}/{self.max_iterations}")
if not self.condition(current_state):
break
current_state = start_state.copy()
current_state = self.pre_iteration_callback(current_state)
full_iter_completed = False
# loop body
for si, cStep in enumerate(self.Steps):
step = cStep(self.config, current_state)
try:
self._current_iter_dir = Path(self.step_dir) / f"iter_{i}"
current_state = step.start(
toolbox=self.toolbox,
step_dir=str(
self._current_iter_dir
/ f"{si:0{ordinal_length}d}-{slugify(step.id)}"
),
_no_rule=True,
)
if self.mid_iteration_break(current_state, step):
break
except Exception as e:
if self.raise_on_failure:
raise e from None
if self.break_on_failure:
break
warn(
f"Step {step.name} failed with exception {e}, "
"but continuing as both break_on_failure and "
"break_on_raise is False."
)
else:
full_iter_completed = True
current_state = self.post_iteration_callback(
current_state, full_iter_completed
)
progress_bar.end_stage()
current_state = self.post_loop_callback(current_state)
for key in current_state:
if (
state_in.get(key) != current_state.get(key)
and DesignFormat.factory.get(key) in self.outputs
):
total_views_update[key] = current_state[key]
for key in current_state.metrics:
if state_in.metrics.get(key) != current_state.metrics.get(key):
total_metrics_update[key] = current_state.metrics[key]
progress_bar.end()
return total_views_update, total_metrics_update