diff --git a/mxpic_router/builder.py b/mxpic_router/builder.py index 553d97f..6df2fb8 100644 --- a/mxpic_router/builder.py +++ b/mxpic_router/builder.py @@ -313,7 +313,7 @@ def _route_bundle_links(links: list, pin_map: dict, Route, warnings: list) -> No plans.append(plan) by_order[order] = plan - _assign_sbend_lstart_spacing(plans) + _assign_bend_route_spacing(plans) accepted = [] for plan in plans: @@ -365,29 +365,78 @@ def _route_bundle_links(links: list, pin_map: dict, Route, warnings: list) -> No f"{plan['link'].src_inst}:{plan['link'].src_pin} -> " f"{plan['link'].dst_inst}:{plan['link'].dst_pin}" ) + if plan.get("route_options", {}).get("length"): + warnings.append( + f"Applied ubend length {plan['route_options']['length']:g}um to " + f"{plan['link'].src_inst}:{plan['link'].src_pin} -> " + f"{plan['link'].dst_inst}:{plan['link'].dst_pin}" + ) _route_link(plan["link"], pin_map, Route, warnings, plan.get("route_options")) -def _assign_sbend_lstart_spacing(plans: list) -> None: +def _assign_bend_route_spacing(plans: list) -> None: groups = {} for plan in plans: if not plan.get("automatic"): continue - if _route_method_name_for_pins(plan["pin1"], plan["pin2"]) != "sbend_p2p": + method_name = _route_method_name_for_pins(plan["pin1"], plan["pin2"]) + if method_name not in {"sbend_p2p", "ubend_p2p"}: continue - key = _sbend_spacing_group_key(plan) + key = _bend_spacing_group_key(plan, method_name) if key is None: continue - groups.setdefault(key, []).append(plan) + bucket = groups.setdefault(key, {"method": method_name, "plans": []}) + bucket["plans"].append(plan) - for group in groups.values(): + for bucket in groups.values(): + group = bucket["plans"] if len(group) < 2: continue axis = _sbend_forward_axis(group[0]) coord_key = "y" if axis == "horizontal" else "x" step = _sbend_lstart_step(group) - for plan, rank in _sbend_lstart_ranks(group, coord_key): - plan["route_options"]["Lstart"] = rank * step + if bucket["method"] == "sbend_p2p": + ranked_plans = _sbend_lstart_ranks(group, coord_key) + else: + ranked_plans = _ubend_length_ranks(group, coord_key) + for plan, rank in ranked_plans: + if bucket["method"] == "sbend_p2p": + plan["route_options"]["Lstart"] = rank * step + else: + plan["route_options"]["length"] = rank * step + + _assign_mixed_bend_route_spacing(plans) + + +def _assign_mixed_bend_route_spacing(plans: list) -> None: + groups = {} + for plan in plans: + if not plan.get("automatic"): + continue + method_name = _route_method_name_for_pins(plan["pin1"], plan["pin2"]) + if method_name not in {"sbend_p2p", "ubend_p2p"}: + continue + key = _mixed_bend_spacing_group_key(plan) + if key is None: + continue + bucket = groups.setdefault(key, {"methods": set(), "plans": []}) + bucket["methods"].add(method_name) + bucket["plans"].append(plan) + + for bucket in groups.values(): + if len(bucket["methods"]) < 2: + continue + group = bucket["plans"] + axis = _sbend_forward_axis(group[0]) + coord_key = "y" if axis == "horizontal" else "x" + step = _sbend_lstart_step(group) + for method_name, plan, rank in _mixed_bend_route_ranks(group, coord_key, step): + if method_name == "sbend_p2p": + plan["route_options"]["Lstart"] = rank * step + plan["route_options"].pop("length", None) + else: + plan["route_options"]["length"] = rank * step + plan["route_options"].pop("Lstart", None) def _sbend_lstart_step(group: list) -> float: @@ -413,13 +462,52 @@ def _sbend_lstart_ranks(group: list, coord_key: str) -> list: ] +def _ubend_length_ranks(group: list, coord_key: str) -> list: + spans = [round(abs(_route_coord_delta(plan, coord_key)), 6) for plan in group] + nonzero_spans = [span for span in spans if span > 1e-9] + if nonzero_spans and len(set(nonzero_spans)) > 1: + ordered_spans = sorted(set(spans)) + return [(plan, ordered_spans.index(round(abs(_route_coord_delta(plan, coord_key)), 6)) + 1) for plan in group] + + center = sum(_route_start_coord(plan, coord_key) for plan in group) / len(group) + distances = sorted({round(abs(_route_start_coord(plan, coord_key) - center), 6) for plan in group}) + return [ + (plan, distances.index(round(abs(_route_start_coord(plan, coord_key) - center), 6)) + 1) + for plan in group + ] + + +def _mixed_bend_route_ranks(group: list, coord_key: str, step: float) -> list: + ubend_plans = [plan for plan in group if _route_method_name_for_pins(plan["pin1"], plan["pin2"]) == "ubend_p2p"] + sbend_plans = [plan for plan in group if _route_method_name_for_pins(plan["pin1"], plan["pin2"]) == "sbend_p2p"] + ranked = [] + ubend_ranked = _ubend_length_ranks(ubend_plans, coord_key) + for plan, rank in ubend_ranked: + ranked.append(("ubend_p2p", plan, rank)) + first_sbend_rank = _mixed_first_sbend_rank_after_ubends(ubend_ranked, step) + for plan, rank in _sbend_lstart_ranks(sbend_plans, coord_key): + ranked.append(("sbend_p2p", plan, first_sbend_rank + rank - 1)) + return ranked + + +def _mixed_first_sbend_rank_after_ubends(ubend_ranked: list, step: float) -> int: + if not ubend_ranked: + return 1 + max_length = max(rank * step for plan, rank in ubend_ranked) + max_radius = max((_safe_float(plan["link"].radius, 10.0) or 10.0) for plan, rank in ubend_ranked) + total_width = sum((_safe_float(plan["link"].width, 0.5) or 0.5) for plan, rank in ubend_ranked) + spacing_slots = len(ubend_ranked) + 1 + ubend_envelope = max_length + max_radius + total_width + spacing_slots * ROUTE_MIN_SPACING + return max(1, math.ceil(ubend_envelope / step)) + + def _route_coord_delta(plan: dict, coord_key: str) -> float: start = plan["points"][0] end = plan["points"][-1] return float(end[coord_key]) - float(start[coord_key]) -def _sbend_spacing_group_key(plan: dict): +def _bend_spacing_group_key(plan: dict, method_name: str): link = plan["link"] axis = _sbend_forward_axis(plan) main_key = "x" if axis == "horizontal" else "y" @@ -433,6 +521,27 @@ def _sbend_spacing_group_key(plan: dict): _sbend_span_bucket(plan["points"][0][main_key]), _sbend_span_bucket(plan["points"][-1][main_key]), ) + return ( + method_name, + axis, + _route_direction_sign(main_delta), + route_scope, + str(link.xsection or ""), + _safe_float(link.width, 0.0), + _safe_float(link.radius, 0.0), + ) + + +def _mixed_bend_spacing_group_key(plan: dict): + link = plan["link"] + axis = _sbend_forward_axis(plan) + main_key = "x" if axis == "horizontal" else "y" + main_delta = _route_coord_delta(plan, main_key) + explicit_group = _link_explicit_route_group(link) + if explicit_group: + route_scope = ("explicit", explicit_group) + else: + route_scope = ("start-span", _sbend_span_bucket(plan["points"][0][main_key])) return ( axis, _route_direction_sign(main_delta), @@ -1143,6 +1252,7 @@ class _NazcaInterconnectRoute: width=self._route_width(width), radius=self._route_radius(radius), xs=self._route_xs(xs), + length=kwargs.get("length", 0), arrow=arrow, )