physdes-py 0.8__tar.gz → 0.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {physdes_py-0.8 → physdes_py-0.9}/CHANGELOG.md +18 -0
- {physdes_py-0.8/src/physdes_py.egg-info → physdes_py-0.9}/PKG-INFO +1 -2
- {physdes_py-0.8 → physdes_py-0.9}/alpha10.svg +1 -1
- {physdes_py-0.8 → physdes_py-0.9}/alpha12.svg +1 -1
- {physdes_py-0.8 → physdes_py-0.9}/alpha14.svg +1 -1
- {physdes_py-0.8 → physdes_py-0.9}/alpha8.svg +1 -1
- {physdes_py-0.8 → physdes_py-0.9}/alpha9998.svg +1 -1
- {physdes_py-0.8 → physdes_py-0.9}/example_route3d_with_constraint_and_keepouts.svg +1 -1
- {physdes_py-0.8 → physdes_py-0.9}/example_route3d_with_steiner_and_keepouts.svg +1 -1
- {physdes_py-0.8 → physdes_py-0.9}/example_route_with_delay_constraint.svg +1 -1
- {physdes_py-0.8 → physdes_py-0.9}/example_route_with_keepouts.svg +1 -1
- {physdes_py-0.8 → physdes_py-0.9}/example_route_with_steiner.svg +1 -1
- {physdes_py-0.8 → physdes_py-0.9}/example_route_with_steiner_and_keepouts.svg +1 -1
- {physdes_py-0.8 → physdes_py-0.9}/example_route_with_steiner_special.svg +1 -1
- {physdes_py-0.8 → physdes_py-0.9}/neg_specical10.svg +1 -1
- {physdes_py-0.8 → physdes_py-0.9}/requirements/default.txt +0 -1
- {physdes_py-0.8 → physdes_py-0.9}/setup.cfg +0 -1
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/cts/dme_algorithm.py +17 -15
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/manhattan_arc.py +0 -5
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/router/routing_tree.py +94 -34
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/steiner_forest/congestion_map.py +9 -8
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/steiner_forest/steiner_forest_grid.py +66 -56
- {physdes_py-0.8 → physdes_py-0.9/src/physdes_py.egg-info}/PKG-INFO +1 -2
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes_py.egg-info/SOURCES.txt +1 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes_py.egg-info/requires.txt +0 -1
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes_py.egg-info/scm_file_list.json +1 -0
- physdes_py-0.9/src/physdes_py.egg-info/scm_version.json +8 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/conftest.py +13 -0
- physdes_py-0.9/tests/test_benchmarks.py +126 -0
- physdes_py-0.8/src/physdes_py.egg-info/scm_version.json +0 -8
- {physdes_py-0.8 → physdes_py-0.9}/.coveragerc +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/.flake8 +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/.github/ISSUE_TEMPLATE/documentation.md +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/.github/workflows/jekyll-gh-pages.yml +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/.github/workflows/multi-platforms.yml +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/.github/workflows/python-app.yml +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/.github/workflows/python-publish.yml +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/.gitignore +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/.gitpod.Dockerfile +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/.gitpod.yml +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/.hadolint.yaml +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/.isort.cfg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/.markdownlint.yaml +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/.pre-commit-config.yaml +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/.readthedocs.yml +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/.shellcheckrc +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/AGENTS.md +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/AUTHORS.rst +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/CHANGELOG.rst +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/CONTRIBUTING.rst +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/GEMINI.md +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/LICENSE +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/README.md +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/_config.yml +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/changelog/1.misc.rst +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/changelog/README.md +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/coverage.json +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/demo_overlap.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/demo_overlap.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/diagram.bob +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/dme_algorithm-slides.html +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/dme_algorithm-slides.md +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/Makefile +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/_static/.gitignore +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/authors.rst +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/build_from_md.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/build_remark.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/build_slides.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/changelog.rst +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/conf.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/contributing.md +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/contributing.rst +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/essays/essay.md +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/essays/polygon_vs_rpolygon_comparison.html +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/essays/polygon_vs_rpolygon_comparison.md +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/examples/plot_cts.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/examples/plot_global_router.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/examples/plot_rectangles.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/examples/plot_rpolygon.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/figures_demo.rst +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/index.rst +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/license.rst +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/misc/note.md +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/misc/report.md +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/my_congestion.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/proposals/fyproj2026.md +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/proposals/fyproj2026.md.bad +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/proposals/fyproj2026_cts.md +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/proposals/fyproj2026_cts_2d.md +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/proposals/mulit-fpga-routing.md +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/readme.rst +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/requirements.txt +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/slides_content.md +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/docs/steiner_forest_diagonal.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/environment.yml +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/examples/steiner_forest_grid_example.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/examples/steiner_forest_grid_large_example.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/examples/steiner_forest_simple_example.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/experimental/distance_comparison.png +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/experimental/rectilinear_distance_field.png +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/experimental/rectilinear_voronoi.ipynb +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/experimental/rectilinear_voronoi.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/experimental/rectilinear_voronoi_diamond.png +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/experimental/rectilinear_voronoi_grid.png +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/experimental/rectilinear_voronoi_random.png +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/experimental/steiner_trees_demo.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/global_router-slides.html +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/global_router-slides.md +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/my_congestion.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/mypy.ini +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/oryx-build-commands.txt +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/comparison.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/convex_hull.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/delay_model_comparison.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/delay_model_comparison3d.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/elmore_model_clock_tree.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/elmore_model_clock_tree3d.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/example_route3d_with_constraint.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/example_route3d_with_constraint_and_keepouts.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/example_route3d_with_steiner.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/example_route3d_with_steiner_and_keepouts.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/example_route_with_constraint.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/example_route_with_constraint_specical.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/example_route_with_delay_constraint.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/example_route_with_keepouts.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/example_route_with_steiner.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/example_route_with_steiner_and_keepouts.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/example_route_with_steiner_special.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/linear_model_clock_tree.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/linear_model_clock_tree3d.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/my_congestion.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/neg_specical10.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/polygon_convex_hull.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/polygon_convex_hull13.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/rectilinear-shapes-for-vlsi-physical-desgin.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/rpolygon_convex_cut.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/rpolygon_cut_explicit.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/rpolyon_convex_hull.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/rpolyon_convex_hull13.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/rpolyon_cut_convex.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/rpolyon_cut_convex2.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/rpolyon_xmono.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/rpolyon_xmono_hull.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/rpolyon_ymono_hull.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/steiner_forest.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/steiner_forest_diagonal.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/test.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/outputs/xmono_hull.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/plot_alpha.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/presentation_overlap_detection.html +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/presentation_overlap_detection.md +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/pyproject.toml +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/requirements/README.md +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/requirements/dev.txt +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/requirements/doc.txt +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/requirements/extras.txt +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/requirements/test.txt +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/requirements.txt +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/rpolygon-slides.html +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/rpolygon-slides.md +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/setup.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/__init__.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/cts/clk_tree3d_vis.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/cts/clk_tree_vis.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/generic.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/generic.tpy +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/interval.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/manhattan_arc_3d.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/manhattan_arc_3d.tpy +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/point.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/point.tpy +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/polygon.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/py.typed +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/rdllist.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/recti.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/router/global_router.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/router/global_router3d.tpy +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/router/routing_tree3d.tpy +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/router/routing_visualizer.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/router/steiner_forest.md +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/rpolygon.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/rpolygon_cut.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/steiner_forest/my_congestion.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/steiner_forest/steiner_forest.md +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/steiner_forest/steiner_forest.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/steiner_forest/steiner_forest_diagonal.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/to_polygon.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/vector2.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes/vector2.tpy +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes_py.egg-info/dependency_links.txt +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes_py.egg-info/not-zip-safe +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/src/physdes_py.egg-info/top_level.txt +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/svgo.config.js +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/test.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/__init__.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/golden.txt +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/mysvg.css +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/pnrpoly.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/run_failing_tests.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_alpha.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_clk_tree_vis.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_dme_algorithm.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_generic_functions.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_global_router.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_global_router3d.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_global_router3d.tpy +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_global_router3d_with_keepouts.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_global_router3d_with_keepouts2.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_global_router_logic.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_global_router_logic3d.tpy +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_global_router_with_keepouts.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_global_router_with_keepouts2.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_interval.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_manhattan_arc_3d.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_manhattan_arc_3d.tpy +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_merge.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_merge3d.tpy +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_nearest_point_to.tpy +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_overlap.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_point.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_point_extended.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_point_hypothesis.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_polygon.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_polygon.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_polygon_convex_decomposition.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_polygon_hypothesis.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_rdllist.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_rect3d.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_recti.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_recti_hypothesis.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_routing_tree.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_routing_tree3d.tpy +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_routing_visualizer.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_rpolygon.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_rpolygon_convex_cut.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_rpolygon_convex_hull.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_rpolygon_cut.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_steiner_forest_grid.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_to_polygon.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_vector2.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/test_vector2_hypothesis.py +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/xmono_polygon.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/xmono_polygon2.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/ymono_polygon.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tests/ymono_rpolygon.svg +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/tox.ini +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/wirelength_vs_alpha.png +0 -0
- {physdes_py-0.8 → physdes_py-0.9}/wirelength_vs_alpha_small.png +0 -0
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Version 0.9 (2026-10-09)
|
|
4
|
+
|
|
5
|
+
### Performance
|
|
6
|
+
- **Hot-path allocation & debug-I/O cuts**: `manhattan_arc` drops live `ic()` calls from the DME embedding path (and removes `icecream` from the runtime deps); `congestion_map` guards its demo behind `__main__`; `steiner_forest_grid` uses an iterative `UnionFind.find`, hoists finds 4→2 per edge, indexes `paid` by edge, and replaces O(|F|²) reverse-delete with union-of-paths pruning (~1.7×); `routing_tree` gets a scalar 2D fast path in `_find_insertion_point` and list-join `get_tree_structure` (~4× insertion); `dme_algorithm` precomputes keys and does a single-pass stable partition in `_build_merging_tree` (1.2–1.5×). Output-identical, verified by differential tests. (#10a4e9f)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
- **Unbounded wirelength sentinel**: `insert_terminal_with_steiner` previously ignored the tree's `worst_wirelength` by passing a literal `10**12`; the default is now `float('inf')` and Steiner insertion passes it through, so the unconstrained variant honors any caller-set bound. (#0947827)
|
|
10
|
+
|
|
11
|
+
### Testing & Code Quality
|
|
12
|
+
- **Temp-cwd isolation for tests**: Added an autouse `_isolate_cwd` fixture so visualization tests' relative-path SVG writes land in pytest's temporary directory instead of the repository root. (#409d422)
|
|
13
|
+
- **New benchmarks**: Added `test_benchmarks.py` (5 benchmarks). (#10a4e9f)
|
|
14
|
+
|
|
15
|
+
### Code Cleanup
|
|
16
|
+
- **SVG EOF newlines**: Added trailing newlines to the example SVGs and formatted the sources. (#0c698e0)
|
|
17
|
+
|
|
18
|
+
### Build & CI
|
|
19
|
+
- **Dropped `icecream` runtime dep**: Removed it from `setup.cfg` and `requirements/default.txt`. (#10a4e9f)
|
|
20
|
+
|
|
3
21
|
## Version 0.8 (2026-09-04)
|
|
4
22
|
|
|
5
23
|
### Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: physdes-py
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9
|
|
4
4
|
Summary: Physical Design Python Code
|
|
5
5
|
Home-page: https://github.com/luk036/physdes-py/
|
|
6
6
|
Author: Wai-Shing Luk
|
|
@@ -13,7 +13,6 @@ Classifier: Programming Language :: Python
|
|
|
13
13
|
Description-Content-Type: text/markdown; charset=UTF-8
|
|
14
14
|
Requires-Dist: typing_extensions
|
|
15
15
|
Requires-Dist: mywheel
|
|
16
|
-
Requires-Dist: icecream
|
|
17
16
|
Provides-Extra: testing
|
|
18
17
|
Requires-Dist: setuptools; extra == "testing"
|
|
19
18
|
Requires-Dist: pytest; extra == "testing"
|
|
@@ -386,23 +386,25 @@ class DMEAlgorithm:
|
|
|
386
386
|
raise ValueError("Empty node range")
|
|
387
387
|
|
|
388
388
|
mid = lo + count // 2
|
|
389
|
-
key_fn = (
|
|
390
|
-
(lambda n: n.position.xcoord) if vertical else (lambda n: n.position.ycoord)
|
|
391
|
-
)
|
|
392
389
|
|
|
393
|
-
# Median-partition in O(n): like Rust's select_nth_unstable_by / C++ nth_element
|
|
394
390
|
sub = nodes[lo:hi]
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
391
|
+
keys = (
|
|
392
|
+
[n.position.xcoord for n in sub]
|
|
393
|
+
if vertical
|
|
394
|
+
else [n.position.ycoord for n in sub]
|
|
395
|
+
)
|
|
396
|
+
median_val = sorted(keys)[count // 2]
|
|
397
|
+
lt: List["TreeNode"] = []
|
|
398
|
+
eq: List["TreeNode"] = []
|
|
399
|
+
gt: List["TreeNode"] = []
|
|
400
|
+
for node, value in zip(sub, keys):
|
|
401
|
+
if value < median_val:
|
|
402
|
+
lt.append(node)
|
|
403
|
+
elif value == median_val:
|
|
404
|
+
eq.append(node)
|
|
405
|
+
else:
|
|
406
|
+
gt.append(node)
|
|
407
|
+
nodes[lo:hi] = lt + eq + gt
|
|
406
408
|
|
|
407
409
|
left_child = self._build_merging_tree(nodes, not vertical, lo, mid)
|
|
408
410
|
right_child = self._build_merging_tree(nodes, not vertical, mid, hi)
|
|
@@ -8,8 +8,6 @@ operations during clock tree synthesis.
|
|
|
8
8
|
|
|
9
9
|
from typing import TYPE_CHECKING, Any, Generic, TypeVar, Union
|
|
10
10
|
|
|
11
|
-
from icecream import ic # type: ignore
|
|
12
|
-
|
|
13
11
|
from .generic import min_dist
|
|
14
12
|
from .interval import enlarge
|
|
15
13
|
from .point import Point
|
|
@@ -274,7 +272,6 @@ class ManhattanArc(Generic[T1, T2]):
|
|
|
274
272
|
(700, 550)
|
|
275
273
|
"""
|
|
276
274
|
nearest_pt = self.impl.nearest_to(manhattan_arc.impl)
|
|
277
|
-
ic(nearest_pt)
|
|
278
275
|
return nearest_pt.inv_rotates()
|
|
279
276
|
|
|
280
277
|
def nearest_point_to(self, other: Point[int, int]) -> Point[Any, Any]:
|
|
@@ -295,6 +292,4 @@ class ManhattanArc(Generic[T1, T2]):
|
|
|
295
292
|
"""
|
|
296
293
|
|
|
297
294
|
ms = ManhattanArc.from_point(other)
|
|
298
|
-
ic(self)
|
|
299
|
-
ic(ms)
|
|
300
295
|
return self._nearest_point_to(ms)
|
|
@@ -179,7 +179,7 @@ class GlobalRoutingTree:
|
|
|
179
179
|
self.nodes: dict[str, RoutingNode] = {}
|
|
180
180
|
self.next_steiner_id = 1
|
|
181
181
|
self.next_terminal_id = 1
|
|
182
|
-
self.worst_wirelength =
|
|
182
|
+
self.worst_wirelength = float("inf")
|
|
183
183
|
self.source = self._create_node(NodeType.Source, source_position)
|
|
184
184
|
|
|
185
185
|
def _create_node(self, node_type: NodeType, pt: Point[Any, Any]) -> "RoutingNode":
|
|
@@ -408,7 +408,7 @@ class GlobalRoutingTree:
|
|
|
408
408
|
def _find_insertion_point(
|
|
409
409
|
self,
|
|
410
410
|
point: Point[Any, Any],
|
|
411
|
-
allowed_wirelength:
|
|
411
|
+
allowed_wirelength: float,
|
|
412
412
|
keepouts: Optional[List[Point[Interval[int], Interval[int]]]] = None,
|
|
413
413
|
) -> Tuple[Optional["RoutingNode"], "RoutingNode"]:
|
|
414
414
|
"""
|
|
@@ -442,7 +442,7 @@ class GlobalRoutingTree:
|
|
|
442
442
|
>>> tree = GlobalRoutingTree(Point(0, 0))
|
|
443
443
|
>>> _ = tree.insert_terminal_node(Point(10, 0))
|
|
444
444
|
>>> keepouts = [Point(Interval(4, 6), Interval(-1, 1))]
|
|
445
|
-
>>> parent, nearest = tree._find_insertion_point(Point(5, 5),
|
|
445
|
+
>>> parent, nearest = tree._find_insertion_point(Point(5, 5), float("inf"), keepouts)
|
|
446
446
|
>>> nearest.pt
|
|
447
447
|
Point(0, 0)
|
|
448
448
|
"""
|
|
@@ -454,6 +454,10 @@ class GlobalRoutingTree:
|
|
|
454
454
|
min_distance = self.worst_wirelength # initially
|
|
455
455
|
valid_found = False
|
|
456
456
|
|
|
457
|
+
px = point.xcoord
|
|
458
|
+
py = point.ycoord
|
|
459
|
+
scalar_coords = isinstance(px, (int, float)) and isinstance(py, (int, float))
|
|
460
|
+
|
|
457
461
|
def traverse(node: "RoutingNode") -> None:
|
|
458
462
|
nonlocal nearest_node
|
|
459
463
|
nonlocal parent_node
|
|
@@ -461,30 +465,82 @@ class GlobalRoutingTree:
|
|
|
461
465
|
nonlocal valid_found
|
|
462
466
|
|
|
463
467
|
for child in node.children:
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
468
|
+
if scalar_coords:
|
|
469
|
+
nx = node.pt.xcoord
|
|
470
|
+
ny = node.pt.ycoord
|
|
471
|
+
cx = child.pt.xcoord
|
|
472
|
+
cy = child.pt.ycoord
|
|
473
|
+
if nx <= cx:
|
|
474
|
+
lo_x, hi_x = nx, cx
|
|
475
|
+
else:
|
|
476
|
+
lo_x, hi_x = cx, nx
|
|
477
|
+
if ny <= cy:
|
|
478
|
+
lo_y, hi_y = ny, cy
|
|
479
|
+
else:
|
|
480
|
+
lo_y, hi_y = cy, ny
|
|
481
|
+
if px < lo_x:
|
|
482
|
+
distance = lo_x - px
|
|
483
|
+
qx = lo_x
|
|
484
|
+
elif px > hi_x:
|
|
485
|
+
distance = px - hi_x
|
|
486
|
+
qx = hi_x
|
|
487
|
+
else:
|
|
488
|
+
distance = 0
|
|
489
|
+
qx = px
|
|
490
|
+
if py < lo_y:
|
|
491
|
+
distance += lo_y - py
|
|
492
|
+
qy = lo_y
|
|
493
|
+
elif py > hi_y:
|
|
494
|
+
distance += py - hi_y
|
|
495
|
+
qy = hi_y
|
|
496
|
+
else:
|
|
497
|
+
qy = py
|
|
498
|
+
to_node = abs(nx - qx) + abs(ny - qy)
|
|
499
|
+
at_node = qx == nx and qy == ny
|
|
500
|
+
at_child = qx == cx and qy == cy
|
|
501
|
+
if keepouts:
|
|
502
|
+
nearest_pt = Point(qx, qy)
|
|
503
|
+
path1 = nearest_pt.hull_with(point)
|
|
504
|
+
path2 = nearest_pt.hull_with(node.pt)
|
|
505
|
+
path3 = nearest_pt.hull_with(child.pt)
|
|
506
|
+
block = False
|
|
507
|
+
for keepout in keepouts:
|
|
508
|
+
if (
|
|
509
|
+
keepout.contains(nearest_pt)
|
|
510
|
+
or keepout.blocks(path1)
|
|
511
|
+
or keepout.blocks(path2)
|
|
512
|
+
or keepout.blocks(path3)
|
|
513
|
+
):
|
|
514
|
+
block = True
|
|
515
|
+
break
|
|
516
|
+
if block:
|
|
517
|
+
continue
|
|
518
|
+
else:
|
|
519
|
+
possible_path = node.pt.hull_with(child.pt)
|
|
520
|
+
distance = possible_path.min_dist_with(point)
|
|
521
|
+
nearest_pt = possible_path.nearest_to(point)
|
|
522
|
+
to_node = node.pt.min_dist_with(nearest_pt)
|
|
523
|
+
at_node = nearest_pt == node.pt
|
|
524
|
+
at_child = nearest_pt == child.pt
|
|
525
|
+
|
|
526
|
+
if keepouts:
|
|
527
|
+
block = False
|
|
528
|
+
path1 = nearest_pt.hull_with(point)
|
|
529
|
+
path2 = nearest_pt.hull_with(node.pt)
|
|
530
|
+
path3 = nearest_pt.hull_with(child.pt)
|
|
531
|
+
for keepout in keepouts:
|
|
532
|
+
if (
|
|
533
|
+
keepout.contains(nearest_pt)
|
|
534
|
+
or keepout.blocks(path1)
|
|
535
|
+
or keepout.blocks(path2)
|
|
536
|
+
or keepout.blocks(path3)
|
|
537
|
+
):
|
|
538
|
+
block = True
|
|
539
|
+
break
|
|
540
|
+
if block:
|
|
541
|
+
continue
|
|
542
|
+
|
|
543
|
+
path_length = node.path_length + to_node + distance
|
|
488
544
|
update = False
|
|
489
545
|
if path_length <= allowed_wirelength:
|
|
490
546
|
if valid_found:
|
|
@@ -504,10 +560,10 @@ class GlobalRoutingTree:
|
|
|
504
560
|
|
|
505
561
|
if update:
|
|
506
562
|
min_distance = distance
|
|
507
|
-
if
|
|
563
|
+
if at_node:
|
|
508
564
|
nearest_node = node
|
|
509
565
|
parent_node = None
|
|
510
|
-
elif
|
|
566
|
+
elif at_child:
|
|
511
567
|
nearest_node = child
|
|
512
568
|
parent_node = None
|
|
513
569
|
else: # need to insert steiner point
|
|
@@ -565,7 +621,7 @@ class GlobalRoutingTree:
|
|
|
565
621
|
terminal_node = self._create_node(NodeType.Terminal, point)
|
|
566
622
|
|
|
567
623
|
parent_node, nearest_node = self._find_insertion_point(
|
|
568
|
-
point,
|
|
624
|
+
point, self.worst_wirelength, keepouts
|
|
569
625
|
)
|
|
570
626
|
|
|
571
627
|
if parent_node is None:
|
|
@@ -732,11 +788,15 @@ class GlobalRoutingTree:
|
|
|
732
788
|
if node is None:
|
|
733
789
|
node = self.source
|
|
734
790
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
791
|
+
parts: List[str] = []
|
|
792
|
+
|
|
793
|
+
def collect(current: "RoutingNode", depth: int) -> None:
|
|
794
|
+
parts.append(" " * depth + str(current) + "\n")
|
|
795
|
+
for child in current.children:
|
|
796
|
+
collect(child, depth + 1)
|
|
738
797
|
|
|
739
|
-
|
|
798
|
+
collect(node, level)
|
|
799
|
+
return "".join(parts)
|
|
740
800
|
|
|
741
801
|
def find_path_to_source(self, node_id: str) -> List["RoutingNode"]:
|
|
742
802
|
"""Find the path from a node back to the source.
|
|
@@ -119,11 +119,12 @@ def draw_congestion_map(
|
|
|
119
119
|
print(f"Congestion map saved to {filename}")
|
|
120
120
|
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
]
|
|
128
|
-
|
|
129
|
-
|
|
122
|
+
if __name__ == "__main__":
|
|
123
|
+
grid = [
|
|
124
|
+
[0, 20, 40, 80, 100],
|
|
125
|
+
[10, 30, 60, 90, 70],
|
|
126
|
+
[25, 50, 75, 95, 50],
|
|
127
|
+
[0, 15, 35, 55, 30],
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
draw_congestion_map(grid, "my_congestion.svg")
|
|
@@ -43,16 +43,21 @@ class UnionFind:
|
|
|
43
43
|
"""
|
|
44
44
|
Find the representative (root) of the set containing idx.
|
|
45
45
|
|
|
46
|
-
Uses path compression to flatten the tree structure for
|
|
46
|
+
Uses iterative path compression to flatten the tree structure for
|
|
47
|
+
faster future lookups.
|
|
47
48
|
|
|
48
49
|
:param idx: The index to find.
|
|
49
50
|
:type idx: int
|
|
50
51
|
:return: The representative (root) of the set.
|
|
51
52
|
:rtype: int
|
|
52
53
|
"""
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
parent = self.parent
|
|
55
|
+
root = idx
|
|
56
|
+
while parent[root] != root:
|
|
57
|
+
root = parent[root]
|
|
58
|
+
while parent[idx] != root:
|
|
59
|
+
parent[idx], idx = root, parent[idx]
|
|
60
|
+
return root
|
|
56
61
|
|
|
57
62
|
def union(self, idx1: int, idx2: int) -> bool:
|
|
58
63
|
"""
|
|
@@ -208,13 +213,12 @@ def steiner_forest_grid(
|
|
|
208
213
|
if row_idx + 1 < height:
|
|
209
214
|
edges.append((node, node + width, 1.0))
|
|
210
215
|
|
|
211
|
-
paid:
|
|
216
|
+
paid: List[float] = [0.0] * len(edges)
|
|
212
217
|
F: List[Tuple[int, int, float]] = [] # list of (u, v, c) added in order
|
|
213
218
|
|
|
214
219
|
while True:
|
|
215
|
-
|
|
216
|
-
term_root: Dict[int, int] = {term:
|
|
217
|
-
# Check if feasible
|
|
220
|
+
find = uf.find
|
|
221
|
+
term_root: Dict[int, int] = {term: find(term) for term in all_term}
|
|
218
222
|
feasible = True
|
|
219
223
|
for source in pair_dict:
|
|
220
224
|
root_source = term_root[source]
|
|
@@ -227,12 +231,10 @@ def steiner_forest_grid(
|
|
|
227
231
|
if feasible:
|
|
228
232
|
break
|
|
229
233
|
|
|
230
|
-
# Compute comp_terms
|
|
231
234
|
comp_terms: Dict[int, Set[int]] = collections.defaultdict(set)
|
|
232
235
|
for terminal in all_term:
|
|
233
236
|
comp_terms[term_root[terminal]].add(terminal)
|
|
234
237
|
|
|
235
|
-
# Compute active_comps
|
|
236
238
|
active_comps: Set[int] = set()
|
|
237
239
|
for root, terms in comp_terms.items():
|
|
238
240
|
is_active = False
|
|
@@ -246,14 +248,15 @@ def steiner_forest_grid(
|
|
|
246
248
|
if is_active:
|
|
247
249
|
active_comps.add(root)
|
|
248
250
|
|
|
249
|
-
# Find min_delta and chosen edge(s)
|
|
250
251
|
min_delta = float("inf")
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
252
|
+
chosen_idx = -1
|
|
253
|
+
chosen_u = chosen_v = 0
|
|
254
|
+
chosen_c = 0.0
|
|
255
|
+
for edge_idx, (node_u, node_v, cost) in enumerate(edges):
|
|
256
|
+
root_u = find(node_u)
|
|
257
|
+
root_v = find(node_v)
|
|
258
|
+
if root_u == root_v:
|
|
254
259
|
continue
|
|
255
|
-
root_u = uf.find(node_u)
|
|
256
|
-
root_v = uf.find(node_v)
|
|
257
260
|
num = 0
|
|
258
261
|
if root_u in active_comps:
|
|
259
262
|
num += 1
|
|
@@ -261,29 +264,23 @@ def steiner_forest_grid(
|
|
|
261
264
|
num += 1
|
|
262
265
|
if num == 0:
|
|
263
266
|
continue
|
|
264
|
-
|
|
265
|
-
paid_val = paid[key]
|
|
267
|
+
paid_val = paid[edge_idx]
|
|
266
268
|
if paid_val > cost:
|
|
267
269
|
continue
|
|
268
|
-
delta_e = (cost - paid_val) / num
|
|
270
|
+
delta_e = (cost - paid_val) / num
|
|
269
271
|
if delta_e < min_delta:
|
|
270
272
|
min_delta = delta_e
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
candidate_es.append((node_u, node_v, cost, key))
|
|
273
|
+
chosen_idx = edge_idx
|
|
274
|
+
chosen_u, chosen_v, chosen_c = node_u, node_v, cost
|
|
274
275
|
|
|
275
276
|
if min_delta == float("inf"):
|
|
276
277
|
raise ValueError("Graph is not connected or cannot connect pairs")
|
|
277
278
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
for u2, v2, c2 in edges:
|
|
283
|
-
if uf.find(u2) == uf.find(v2):
|
|
279
|
+
for edge_idx, (u2, v2, c2) in enumerate(edges):
|
|
280
|
+
ru2 = find(u2)
|
|
281
|
+
rv2 = find(v2)
|
|
282
|
+
if ru2 == rv2:
|
|
284
283
|
continue
|
|
285
|
-
ru2 = uf.find(u2)
|
|
286
|
-
rv2 = uf.find(v2)
|
|
287
284
|
num2 = 0
|
|
288
285
|
if ru2 in active_comps:
|
|
289
286
|
num2 += 1
|
|
@@ -291,39 +288,52 @@ def steiner_forest_grid(
|
|
|
291
288
|
num2 += 1
|
|
292
289
|
if num2 == 0:
|
|
293
290
|
continue
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
291
|
+
new_paid = paid[edge_idx] + min_delta * num2
|
|
292
|
+
if new_paid > c2 + 1e-6: # tolerance
|
|
293
|
+
new_paid = c2
|
|
294
|
+
paid[edge_idx] = new_paid
|
|
298
295
|
|
|
299
|
-
|
|
300
|
-
if paid[chosen_key] >= chosen_c - 1e-6:
|
|
296
|
+
if paid[chosen_idx] >= chosen_c - 1e-6:
|
|
301
297
|
F.append((chosen_u, chosen_v, chosen_c))
|
|
302
298
|
uf.union(chosen_u, chosen_v)
|
|
303
299
|
|
|
304
|
-
#
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
300
|
+
# ``F`` is a forest: every added edge merges two distinct components. The
|
|
301
|
+
# minimal sub-forest preserving all required pair connections is therefore
|
|
302
|
+
# the union of the unique paths between each pair, so mark those paths
|
|
303
|
+
# directly instead of running O(|F|^2) reverse-delete passes that rebuild a
|
|
304
|
+
# UnionFind per candidate edge.
|
|
305
|
+
adjacency: Dict[int, List[Tuple[int, int]]] = collections.defaultdict(list)
|
|
306
|
+
for edge_idx, (node_u, node_v, _cost) in enumerate(F):
|
|
307
|
+
adjacency[node_u].append((node_v, edge_idx))
|
|
308
|
+
adjacency[node_v].append((node_u, edge_idx))
|
|
309
|
+
|
|
310
|
+
needed: List[bool] = [False] * len(F)
|
|
311
|
+
for source in sources:
|
|
312
|
+
parent_edge: Dict[int, int] = {}
|
|
313
|
+
parent_node: Dict[int, int] = {}
|
|
314
|
+
visited: Set[int] = {source}
|
|
315
|
+
stack: List[int] = [source]
|
|
316
|
+
while stack:
|
|
317
|
+
node = stack.pop()
|
|
318
|
+
for neighbor, edge_idx in adjacency[node]:
|
|
319
|
+
if neighbor not in visited:
|
|
320
|
+
visited.add(neighbor)
|
|
321
|
+
parent_edge[neighbor] = edge_idx
|
|
322
|
+
parent_node[neighbor] = node
|
|
323
|
+
stack.append(neighbor)
|
|
324
|
+
for target in pair_dict[source]:
|
|
325
|
+
current = target
|
|
326
|
+
while current != source:
|
|
327
|
+
edge_idx = parent_edge[current]
|
|
328
|
+
needed[edge_idx] = True
|
|
329
|
+
current = parent_node[current]
|
|
330
|
+
|
|
331
|
+
F_pruned: List[Tuple[int, int, float]] = [
|
|
332
|
+
edge for edge_idx, edge in enumerate(F) if needed[edge_idx]
|
|
333
|
+
]
|
|
322
334
|
|
|
323
|
-
# Compute cost
|
|
324
335
|
total_cost: float = sum(cost for _, _, cost in F_pruned)
|
|
325
336
|
|
|
326
|
-
# Identify Steiner nodes
|
|
327
337
|
used_nodes: Set[int] = set()
|
|
328
338
|
for node_u, node_v, _ in F_pruned:
|
|
329
339
|
used_nodes.add(node_u)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: physdes-py
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9
|
|
4
4
|
Summary: Physical Design Python Code
|
|
5
5
|
Home-page: https://github.com/luk036/physdes-py/
|
|
6
6
|
Author: Wai-Shing Luk
|
|
@@ -13,7 +13,6 @@ Classifier: Programming Language :: Python
|
|
|
13
13
|
Description-Content-Type: text/markdown; charset=UTF-8
|
|
14
14
|
Requires-Dist: typing_extensions
|
|
15
15
|
Requires-Dist: mywheel
|
|
16
|
-
Requires-Dist: icecream
|
|
17
16
|
Provides-Extra: testing
|
|
18
17
|
Requires-Dist: setuptools; extra == "testing"
|
|
19
18
|
Requires-Dist: pytest; extra == "testing"
|