140 lines
4.9 KiB
Python
140 lines
4.9 KiB
Python
from __future__ import annotations
|
|
|
|
import warnings
|
|
from dataclasses import dataclass, field
|
|
from typing import TYPE_CHECKING, Literal
|
|
|
|
from shapely.geometry import Polygon
|
|
from inire.seeds import PathSeed
|
|
|
|
if TYPE_CHECKING:
|
|
from inire.geometry.components import BendCollisionModel, BendPhysicalGeometry
|
|
from inire.geometry.primitives import Port
|
|
|
|
|
|
NetOrder = Literal["user", "shortest", "longest"]
|
|
VisibilityGuidance = Literal["off", "exact_corner", "tangent_corner"]
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class NetSpec:
|
|
net_id: str
|
|
start: Port
|
|
target: Port
|
|
width: float = 2.0
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ObjectiveWeights:
|
|
unit_length_cost: float = 1.0
|
|
bend_penalty: float = 250.0
|
|
sbend_penalty: float = 500.0
|
|
danger_weight: float = 1.0
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class SearchOptions:
|
|
node_limit: int = 1000000
|
|
max_straight_length: float = 2000.0
|
|
min_straight_length: float = 5.0
|
|
greedy_h_weight: float = 1.5
|
|
sbend_offsets: tuple[float, ...] | None = None
|
|
bend_radii: tuple[float, ...] = (50.0, 100.0)
|
|
sbend_radii: tuple[float, ...] = (10.0,)
|
|
bend_collision_type: BendCollisionModel = "arc"
|
|
bend_proxy_geometry: BendCollisionModel | None = None
|
|
bend_physical_geometry: BendPhysicalGeometry | None = None
|
|
bend_clip_margin: float | None = None
|
|
visibility_guidance: VisibilityGuidance = "tangent_corner"
|
|
|
|
def __post_init__(self) -> None:
|
|
object.__setattr__(self, "bend_radii", tuple(self.bend_radii))
|
|
object.__setattr__(self, "sbend_radii", tuple(self.sbend_radii))
|
|
if self.sbend_offsets is not None:
|
|
object.__setattr__(self, "sbend_offsets", tuple(self.sbend_offsets))
|
|
if self.bend_physical_geometry is None and isinstance(self.bend_proxy_geometry, Polygon):
|
|
warnings.warn(
|
|
"Custom bend proxy provided without bend_physical_geometry; routed bends will keep standard arc geometry.",
|
|
stacklevel=2,
|
|
)
|
|
|
|
|
|
def resolve_bend_geometry(
|
|
search: SearchOptions,
|
|
*,
|
|
bend_collision_override: BendCollisionModel | None = None,
|
|
) -> tuple[BendCollisionModel, BendPhysicalGeometry]:
|
|
bend_physical_geometry = search.bend_physical_geometry
|
|
if bend_physical_geometry is None and isinstance(search.bend_collision_type, Polygon) and search.bend_proxy_geometry is None:
|
|
bend_physical_geometry = search.bend_collision_type
|
|
if bend_physical_geometry is None:
|
|
bend_physical_geometry = "arc"
|
|
|
|
if bend_collision_override is not None:
|
|
bend_proxy_geometry = bend_collision_override
|
|
elif search.bend_proxy_geometry is not None:
|
|
bend_proxy_geometry = search.bend_proxy_geometry
|
|
elif isinstance(search.bend_collision_type, Polygon):
|
|
bend_proxy_geometry = search.bend_collision_type
|
|
elif bend_physical_geometry != "arc" and search.bend_collision_type == "arc":
|
|
bend_proxy_geometry = bend_physical_geometry
|
|
else:
|
|
bend_proxy_geometry = search.bend_collision_type
|
|
|
|
return bend_proxy_geometry, bend_physical_geometry
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class CongestionOptions:
|
|
max_iterations: int = 10
|
|
base_penalty: float = 100.0
|
|
multiplier: float = 1.5
|
|
use_tiered_strategy: bool = True
|
|
net_order: NetOrder = "user"
|
|
warm_start_enabled: bool = True
|
|
shuffle_nets: bool = False
|
|
seed: int | None = None
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class RefinementOptions:
|
|
enabled: bool = True
|
|
objective: ObjectiveWeights | None = None
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class DiagnosticsOptions:
|
|
capture_expanded: bool = False
|
|
capture_conflict_trace: bool = False
|
|
capture_frontier_trace: bool = False
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class RoutingOptions:
|
|
search: SearchOptions = field(default_factory=SearchOptions)
|
|
objective: ObjectiveWeights = field(default_factory=ObjectiveWeights)
|
|
congestion: CongestionOptions = field(default_factory=CongestionOptions)
|
|
refinement: RefinementOptions = field(default_factory=RefinementOptions)
|
|
diagnostics: DiagnosticsOptions = field(default_factory=DiagnosticsOptions)
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class RoutingProblem:
|
|
bounds: tuple[float, float, float, float]
|
|
nets: tuple[NetSpec, ...] = ()
|
|
static_obstacles: tuple[Polygon, ...] = ()
|
|
initial_paths: dict[str, PathSeed] = field(default_factory=dict)
|
|
clearance: float = 2.0
|
|
safety_zone_radius: float = 0.0021
|
|
|
|
def __post_init__(self) -> None:
|
|
object.__setattr__(self, "nets", tuple(self.nets))
|
|
object.__setattr__(self, "static_obstacles", tuple(self.static_obstacles))
|
|
initial_paths = dict(self.initial_paths)
|
|
if any(not isinstance(seed, PathSeed) for seed in initial_paths.values()):
|
|
raise TypeError("RoutingProblem.initial_paths values must be PathSeed instances")
|
|
object.__setattr__(
|
|
self,
|
|
"initial_paths",
|
|
initial_paths,
|
|
)
|