polyfix 0.1.0__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.
- polyfix-0.1.0/PKG-INFO +36 -0
- polyfix-0.1.0/README.md +6 -0
- polyfix-0.1.0/pyproject.toml +42 -0
- polyfix-0.1.0/src/polyfix/__init__.py +1 -0
- polyfix-0.1.0/src/polyfix/bends/bends.py +130 -0
- polyfix-0.1.0/src/polyfix/bends/errors.py +61 -0
- polyfix-0.1.0/src/polyfix/bends/graph.py +195 -0
- polyfix-0.1.0/src/polyfix/bends/interfaces.py +363 -0
- polyfix-0.1.0/src/polyfix/bends/main.py +147 -0
- polyfix-0.1.0/src/polyfix/bends/points.py +89 -0
- polyfix-0.1.0/src/polyfix/bends/utils.py +64 -0
- polyfix-0.1.0/src/polyfix/bends/viz.py +79 -0
- polyfix-0.1.0/src/polyfix/cli/main.py +26 -0
- polyfix-0.1.0/src/polyfix/cli/make/main.py +96 -0
- polyfix-0.1.0/src/polyfix/cli/make/utils.py +53 -0
- polyfix-0.1.0/src/polyfix/cli/studies/main.py +42 -0
- polyfix-0.1.0/src/polyfix/cli/studies/study_msd.py +177 -0
- polyfix-0.1.0/src/polyfix/cli/studies/study_validation.py +33 -0
- polyfix-0.1.0/src/polyfix/config.py +3 -0
- polyfix-0.1.0/src/polyfix/examples/bends.py +111 -0
- polyfix-0.1.0/src/polyfix/examples/domains.py +56 -0
- polyfix-0.1.0/src/polyfix/examples/layout.py +43 -0
- polyfix-0.1.0/src/polyfix/examples/msd.py +105 -0
- polyfix-0.1.0/src/polyfix/examples/sample_updates.py +17 -0
- polyfix-0.1.0/src/polyfix/geometry/layout.py +69 -0
- polyfix-0.1.0/src/polyfix/geometry/modify/delete.py +15 -0
- polyfix-0.1.0/src/polyfix/geometry/modify/precision.py +76 -0
- polyfix-0.1.0/src/polyfix/geometry/modify/update.py +158 -0
- polyfix-0.1.0/src/polyfix/geometry/modify/validate.py +182 -0
- polyfix-0.1.0/src/polyfix/geometry/ortho.py +128 -0
- polyfix-0.1.0/src/polyfix/geometry/paired_coords.py +61 -0
- polyfix-0.1.0/src/polyfix/geometry/range.py +93 -0
- polyfix-0.1.0/src/polyfix/geometry/shapely_helpers.py +17 -0
- polyfix-0.1.0/src/polyfix/geometry/surfaces.py +164 -0
- polyfix-0.1.0/src/polyfix/geometry/vectors.py +206 -0
- polyfix-0.1.0/src/polyfix/layout/interfaces.py +78 -0
- polyfix-0.1.0/src/polyfix/layout/main/move.py +143 -0
- polyfix-0.1.0/src/polyfix/layout/main/plan.py +103 -0
- polyfix-0.1.0/src/polyfix/layout/neighbors.py +138 -0
- polyfix-0.1.0/src/polyfix/layout/viz.py +103 -0
- polyfix-0.1.0/src/polyfix/nonortho/dot.py +49 -0
- polyfix-0.1.0/src/polyfix/nonortho/interfaces.py +67 -0
- polyfix-0.1.0/src/polyfix/nonortho/main.py +28 -0
- polyfix-0.1.0/src/polyfix/nonortho/rotation.py +25 -0
- polyfix-0.1.0/src/polyfix/paths.py +13 -0
- polyfix-0.1.0/src/polyfix/pydantic_models.py +108 -0
- polyfix-0.1.0/src/polyfix/rotate/main.py +21 -0
- polyfix-0.1.0/src/polyfix/rotate/utils.py +61 -0
- polyfix-0.1.0/src/polyfix/visuals/styles.py +83 -0
- polyfix-0.1.0/src/polyfix/visuals/visuals.py +146 -0
polyfix-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: polyfix
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Module for creating EnergyPlus ready floor plans from geometry containing holes.
|
|
5
|
+
Author: Juliet Nwagwu Ume-Ezeoke
|
|
6
|
+
Author-email: Juliet Nwagwu Ume-Ezeoke <jnwagwu@stanford.edu>
|
|
7
|
+
Requires-Dist: bpython>=0.25
|
|
8
|
+
Requires-Dist: cyclopts>=4.4.3
|
|
9
|
+
Requires-Dist: expression>=5.6.0
|
|
10
|
+
Requires-Dist: geom>=0.3.0
|
|
11
|
+
Requires-Dist: loguru>=0.7.3
|
|
12
|
+
Requires-Dist: matplotlib>=3.10.6
|
|
13
|
+
Requires-Dist: networkx>=3.5
|
|
14
|
+
Requires-Dist: numpy>=2.3.3
|
|
15
|
+
Requires-Dist: pipe>=2.2
|
|
16
|
+
Requires-Dist: pre-commit>=4.5.1
|
|
17
|
+
Requires-Dist: ptpython>=3.0.31
|
|
18
|
+
Requires-Dist: pydantic>=2.12.5
|
|
19
|
+
Requires-Dist: pyprojroot>=0.3.0
|
|
20
|
+
Requires-Dist: pytest>=8.4.2
|
|
21
|
+
Requires-Dist: pytest-cov>=7.0.0
|
|
22
|
+
Requires-Dist: shapely>=2.1.2
|
|
23
|
+
Requires-Dist: snakemake>=9.14.5
|
|
24
|
+
Requires-Dist: tomli-w>=1.2.0
|
|
25
|
+
Requires-Dist: types-networkx>=3.5.0.20251106
|
|
26
|
+
Requires-Dist: utils4plans
|
|
27
|
+
Requires-Dist: whenever>=0.9.3
|
|
28
|
+
Requires-Python: >=3.13
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# polyfix
|
|
32
|
+
|
|
33
|
+
### Geometry conventions
|
|
34
|
+
|
|
35
|
+
- Polygonal domains can be specified using coordinate in either a clockwise or counterclockwise direction.
|
|
36
|
+
- After the coordinates are normalized, they will have a clockwise direction, starting from the bottom left. This comes from Shapely's defaults for normalizing
|
polyfix-0.1.0/README.md
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# polyfix
|
|
2
|
+
|
|
3
|
+
### Geometry conventions
|
|
4
|
+
|
|
5
|
+
- Polygonal domains can be specified using coordinate in either a clockwise or counterclockwise direction.
|
|
6
|
+
- After the coordinates are normalized, they will have a clockwise direction, starting from the bottom left. This comes from Shapely's defaults for normalizing
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "polyfix"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Module for creating EnergyPlus ready floor plans from geometry containing holes."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Juliet Nwagwu Ume-Ezeoke", email = "jnwagwu@stanford.edu" },
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.13"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"bpython>=0.25",
|
|
12
|
+
"cyclopts>=4.4.3",
|
|
13
|
+
"expression>=5.6.0",
|
|
14
|
+
"geom>=0.3.0",
|
|
15
|
+
"loguru>=0.7.3",
|
|
16
|
+
"matplotlib>=3.10.6",
|
|
17
|
+
"networkx>=3.5",
|
|
18
|
+
"numpy>=2.3.3",
|
|
19
|
+
"pipe>=2.2",
|
|
20
|
+
"pre-commit>=4.5.1",
|
|
21
|
+
"ptpython>=3.0.31",
|
|
22
|
+
"pydantic>=2.12.5",
|
|
23
|
+
"pyprojroot>=0.3.0",
|
|
24
|
+
"pytest>=8.4.2",
|
|
25
|
+
"pytest-cov>=7.0.0",
|
|
26
|
+
"shapely>=2.1.2",
|
|
27
|
+
"snakemake>=9.14.5",
|
|
28
|
+
"tomli-w>=1.2.0",
|
|
29
|
+
"types-networkx>=3.5.0.20251106",
|
|
30
|
+
"utils4plans",
|
|
31
|
+
"whenever>=0.9.3",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.scripts]
|
|
35
|
+
polyfix = "polyfix.cli.main:main"
|
|
36
|
+
|
|
37
|
+
[build-system]
|
|
38
|
+
requires = ["uv_build>=0.8.3,<0.9.0"]
|
|
39
|
+
build-backend = "uv_build"
|
|
40
|
+
|
|
41
|
+
# [tool.uv.sources]
|
|
42
|
+
# utils4plans = { path = "../utils4plans" }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from rich.pretty import pretty_repr
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from loguru import logger
|
|
2
|
+
from utils4plans.lists import chain_flatten
|
|
3
|
+
from polyfix.geometry.modify.validate import InvalidPolygonError, validate_polygon
|
|
4
|
+
from polyfix.bends.graph import (
|
|
5
|
+
create_surface_graph_for_domain,
|
|
6
|
+
find_small_node_groups,
|
|
7
|
+
get_nodes_data,
|
|
8
|
+
get_successor_node,
|
|
9
|
+
handle_components,
|
|
10
|
+
update_small_nbs,
|
|
11
|
+
)
|
|
12
|
+
from polyfix.geometry.ortho import FancyOrthoDomain
|
|
13
|
+
from polyfix.bends.interfaces import (
|
|
14
|
+
BendHolder,
|
|
15
|
+
KappaOne,
|
|
16
|
+
KappaTwo,
|
|
17
|
+
PiOne,
|
|
18
|
+
PiThree,
|
|
19
|
+
PiTwo,
|
|
20
|
+
)
|
|
21
|
+
import networkx as nx
|
|
22
|
+
|
|
23
|
+
from polyfix.geometry.surfaces import Surface
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def check_is_pi_two(G: nx.DiGraph, node: str):
|
|
27
|
+
data = get_nodes_data(G, node)
|
|
28
|
+
if data.is_small and data.is_nb2_small and not data.is_nb_small:
|
|
29
|
+
return True
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def identify_pi_twos(domain: FancyOrthoDomain, G_: nx.DiGraph):
|
|
33
|
+
|
|
34
|
+
def make(node: str):
|
|
35
|
+
n1 = get_successor_node(G, node)
|
|
36
|
+
n2 = get_successor_node(G, n1)
|
|
37
|
+
|
|
38
|
+
s1 = get_nodes_data(G, node).surface
|
|
39
|
+
s2 = get_nodes_data(G, n2).surface
|
|
40
|
+
assert s1 and s2
|
|
41
|
+
return PiTwo.from_surfaces(domain, G, s1, s2)
|
|
42
|
+
|
|
43
|
+
G = update_small_nbs(G_)
|
|
44
|
+
|
|
45
|
+
bends = [make(node) for node in G.nodes if check_is_pi_two(G, node)]
|
|
46
|
+
|
|
47
|
+
# only want those where are passing bend checks
|
|
48
|
+
passing_bends = [i for i in bends if i.are_vectors_correct]
|
|
49
|
+
return passing_bends
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def is_part_of_pi_twos(bends: list[PiTwo], surface: Surface):
|
|
53
|
+
pi2surfs = chain_flatten([[i.s1, i.s2] for i in bends])
|
|
54
|
+
if surface in pi2surfs:
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def assign_bends(domain: FancyOrthoDomain, domain_name: int | str = ""):
|
|
59
|
+
|
|
60
|
+
bh = BendHolder()
|
|
61
|
+
try:
|
|
62
|
+
validate_polygon(domain.polygon, domain.name)
|
|
63
|
+
except InvalidPolygonError as e:
|
|
64
|
+
logger.error(
|
|
65
|
+
f"Could not validate polygon, and could not assign bends for domain<{domain_name}> ----- {e.message()}"
|
|
66
|
+
)
|
|
67
|
+
return bh
|
|
68
|
+
|
|
69
|
+
G = create_surface_graph_for_domain(domain)
|
|
70
|
+
bh.pi2s.extend(identify_pi_twos(domain, G))
|
|
71
|
+
# TODO find 2pi groups
|
|
72
|
+
components = find_small_node_groups(G)
|
|
73
|
+
logger.trace(f"components = {components}")
|
|
74
|
+
|
|
75
|
+
# TODO:pi2s
|
|
76
|
+
|
|
77
|
+
info = (domain, G)
|
|
78
|
+
|
|
79
|
+
# large_groups = []
|
|
80
|
+
# uncategorized = []H
|
|
81
|
+
|
|
82
|
+
for comp_ in components:
|
|
83
|
+
comp = list(comp_) # TODO: can simplify this.
|
|
84
|
+
size = len(comp)
|
|
85
|
+
if size > 3:
|
|
86
|
+
bh.large.append(comp)
|
|
87
|
+
elif size == 3:
|
|
88
|
+
res = handle_components(G, comp)
|
|
89
|
+
# logger.debug(
|
|
90
|
+
# f"after have handled components: {[i.name_w_domain for i in res]}"
|
|
91
|
+
# )
|
|
92
|
+
bend = PiThree.from_surfaces(*info, *res)
|
|
93
|
+
if bend.are_vectors_correct:
|
|
94
|
+
bh.pi3s.append(bend)
|
|
95
|
+
else:
|
|
96
|
+
bh.large.append(comp)
|
|
97
|
+
# todo: check if vectors are correct, if not goes to same treatment as large..
|
|
98
|
+
#
|
|
99
|
+
elif size == 2:
|
|
100
|
+
res = handle_components(G, comp)
|
|
101
|
+
res = KappaTwo.from_surfaces(*info, *res)
|
|
102
|
+
bh.kappa2s.append(res)
|
|
103
|
+
elif size == 1:
|
|
104
|
+
res = handle_components(G, comp)[0]
|
|
105
|
+
if is_part_of_pi_twos(bh.pi2s, res):
|
|
106
|
+
continue
|
|
107
|
+
|
|
108
|
+
bend = KappaOne.from_surfaces(*info, res)
|
|
109
|
+
if bend.are_vectors_correct:
|
|
110
|
+
bh.kappas.append(bend)
|
|
111
|
+
continue
|
|
112
|
+
|
|
113
|
+
bend = PiOne.from_surfaces(*info, res)
|
|
114
|
+
if bend.are_vectors_correct:
|
|
115
|
+
bh.pis.append(bend)
|
|
116
|
+
continue
|
|
117
|
+
|
|
118
|
+
bh.not_found.append(comp)
|
|
119
|
+
|
|
120
|
+
# bh larges and bad pi3s become kappa2s
|
|
121
|
+
for comp in bh.large:
|
|
122
|
+
res = handle_components(G, comp)
|
|
123
|
+
s1, s2, *_ = res
|
|
124
|
+
bend = KappaTwo.from_surfaces(*info, s1, s2)
|
|
125
|
+
bh.kappa2s.append(bend)
|
|
126
|
+
|
|
127
|
+
return bh
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# TODO: consider making the checks of the bend vectos external.. ?
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from polyfix.geometry.ortho import FancyOrthoDomain
|
|
2
|
+
|
|
3
|
+
from polyfix.bends.interfaces import Bend, BendHolder
|
|
4
|
+
from polyfix.geometry.surfaces import Surface
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
from loguru import logger
|
|
8
|
+
|
|
9
|
+
FAIL_TYPES = Literal[
|
|
10
|
+
"Invalid Move",
|
|
11
|
+
"Problem Finding Bends",
|
|
12
|
+
"Failed to Clean Domain Correctly",
|
|
13
|
+
"Invalid Incoming Domain",
|
|
14
|
+
"Exceeded number of iterations",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DomainCleanFailure(Exception):
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
domain: FancyOrthoDomain,
|
|
22
|
+
fail_type: FAIL_TYPES,
|
|
23
|
+
details: str,
|
|
24
|
+
surfaces: list[Surface] = [],
|
|
25
|
+
bends: BendHolder | None = None,
|
|
26
|
+
current_bend: Bend | None = None,
|
|
27
|
+
):
|
|
28
|
+
self.domain = domain
|
|
29
|
+
self.fail_type = fail_type
|
|
30
|
+
self.details = details
|
|
31
|
+
self.surfaces = surfaces
|
|
32
|
+
self.bends = bends
|
|
33
|
+
self.current_bend = current_bend
|
|
34
|
+
|
|
35
|
+
def __rich_repr__(self):
|
|
36
|
+
yield "domain", self.domain.name
|
|
37
|
+
yield "fail_type", self.fail_type
|
|
38
|
+
yield "details", self.details
|
|
39
|
+
|
|
40
|
+
def show_message(self, layout_id: str):
|
|
41
|
+
logger.warning(f"[red bold]{self.fail_type} for {layout_id}-{self.domain.name}")
|
|
42
|
+
logger.warning(f"{self.details}")
|
|
43
|
+
|
|
44
|
+
if self.bends:
|
|
45
|
+
logger.warning(self.bends.summary_str)
|
|
46
|
+
|
|
47
|
+
if self.current_bend:
|
|
48
|
+
logger.warning(f"Current bend is {str(self.current_bend)}")
|
|
49
|
+
logger.warning(self.current_bend.study_vectors())
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class DomainCleanIterationFailure(Exception):
|
|
53
|
+
def __init__(
|
|
54
|
+
self, domain_name: str, fail_type: FAIL_TYPES, current_bend: Bend | None = None
|
|
55
|
+
):
|
|
56
|
+
self.domain = domain_name
|
|
57
|
+
self.fail_type = fail_type
|
|
58
|
+
self.current_bend = current_bend
|
|
59
|
+
|
|
60
|
+
def message(self):
|
|
61
|
+
logger.warning(f"[red bold]{self.fail_type} for {self.domain}")
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from utils4plans.lists import get_unique_one
|
|
4
|
+
from utils4plans.sets import set_difference
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
from loguru import logger
|
|
8
|
+
from rich.pretty import pretty_repr
|
|
9
|
+
from polyfix.geometry.surfaces import Surface
|
|
10
|
+
from polyfix.geometry.ortho import FancyOrthoDomain
|
|
11
|
+
from typing import Iterable, TypeVar
|
|
12
|
+
import networkx as nx
|
|
13
|
+
from utils4plans.lists import pairwise
|
|
14
|
+
from polyfix.bends.utils import make_repr_obj
|
|
15
|
+
|
|
16
|
+
T = TypeVar("T")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class DomainGraph:
|
|
21
|
+
graph: nx.DiGraph
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class NodeData:
|
|
26
|
+
is_small: bool = False
|
|
27
|
+
surface: Surface | None = None
|
|
28
|
+
is_nb_small: bool = False
|
|
29
|
+
is_nb2_small: bool = False
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def repr_dict(self):
|
|
33
|
+
def fx():
|
|
34
|
+
yield "surface_name", self.surface.name_w_domain if self.surface else ""
|
|
35
|
+
yield "is_small", self.is_small
|
|
36
|
+
yield "is_nb_small", self.is_nb_small
|
|
37
|
+
yield "is_nb2_small", self.is_nb2_small
|
|
38
|
+
|
|
39
|
+
return make_repr_obj(fx)
|
|
40
|
+
|
|
41
|
+
# def __repr__(self):
|
|
42
|
+
# def fx():
|
|
43
|
+
# yield "is_small", self.is_small
|
|
44
|
+
# yield "surface_name", self.surface.name if self.surface else ""
|
|
45
|
+
#
|
|
46
|
+
# return make_repr_obj(fx, self)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True)
|
|
50
|
+
class SurfaceNode:
|
|
51
|
+
data: NodeData
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_nodes_data(G: nx.DiGraph, node: str):
|
|
55
|
+
data = G.nodes[node].get("data")
|
|
56
|
+
assert isinstance(data, NodeData)
|
|
57
|
+
d: NodeData = data
|
|
58
|
+
return d
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_predecesor(G: nx.DiGraph, node: object):
|
|
62
|
+
res = list(G.predecessors(node))[0]
|
|
63
|
+
return get_surface(G, res)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def get_successor(G: nx.DiGraph, node: object):
|
|
67
|
+
res = list(G.successors(node))[0]
|
|
68
|
+
return get_surface(G, res)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def get_successor_node(G: nx.DiGraph, node: object):
|
|
72
|
+
res = list(G.successors(node))
|
|
73
|
+
assert len(res) == 1, f"Expected one successor, but got {res}"
|
|
74
|
+
# should assert num of succesors = 1
|
|
75
|
+
return res[0]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def create_cycle_graph(nodes_: Iterable[T]):
|
|
79
|
+
nodes = list(nodes_)
|
|
80
|
+
node_cycle = nodes + [nodes[0]]
|
|
81
|
+
|
|
82
|
+
# G: nx.DiGraph[SurfaceNode] = nx.DiGraph()
|
|
83
|
+
G = nx.DiGraph()
|
|
84
|
+
for i, j in pairwise(node_cycle):
|
|
85
|
+
G.add_edge(i, j)
|
|
86
|
+
|
|
87
|
+
return G
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def create_surface_graph_for_domain(domain: FancyOrthoDomain):
|
|
91
|
+
|
|
92
|
+
surf_names = [i.name_w_domain for i in domain.surfaces]
|
|
93
|
+
G = create_cycle_graph(surf_names)
|
|
94
|
+
node_data = {
|
|
95
|
+
name: {"data": NodeData(is_small=surf.is_small, surface=surf)}
|
|
96
|
+
for name, surf in zip(surf_names, domain.surfaces)
|
|
97
|
+
}
|
|
98
|
+
nx.set_node_attributes(G, node_data)
|
|
99
|
+
return G
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def update_small_nbs(G_: nx.DiGraph):
|
|
103
|
+
def check_nb_is_small(G: nx.DiGraph, nb: str):
|
|
104
|
+
data = get_nodes_data(G, nb)
|
|
105
|
+
return data.is_small
|
|
106
|
+
|
|
107
|
+
def update(G: nx.DiGraph, node: str):
|
|
108
|
+
data = get_nodes_data(G, node)
|
|
109
|
+
n1 = get_successor_node(G, node)
|
|
110
|
+
data.is_nb_small = check_nb_is_small(G, n1)
|
|
111
|
+
|
|
112
|
+
n2 = get_successor_node(G, n1)
|
|
113
|
+
data.is_nb2_small = check_nb_is_small(G, n2)
|
|
114
|
+
|
|
115
|
+
G = deepcopy(G_)
|
|
116
|
+
for node in G.nodes:
|
|
117
|
+
update(G, node)
|
|
118
|
+
|
|
119
|
+
return G
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def find_small_node_groups(G: nx.DiGraph):
|
|
123
|
+
nodes = [n for n, d in G.nodes(data=True) if d["data"].is_small]
|
|
124
|
+
sg = G.subgraph(nodes)
|
|
125
|
+
components = nx.connected_components(sg.to_undirected())
|
|
126
|
+
|
|
127
|
+
return list(components)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def find_ends_of_a_directed_graph(G: nx.DiGraph):
|
|
131
|
+
root = get_unique_one(G.nodes, lambda x: len(list(G.predecessors(x))) == 0)
|
|
132
|
+
end = get_unique_one(G.nodes, lambda x: len(list(G.successors(x))) == 0)
|
|
133
|
+
return root, end
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def order_nodes_based_on_graph(G: nx.DiGraph, nodes_: Iterable[T]):
|
|
137
|
+
nodes = list(nodes_)
|
|
138
|
+
nodes_to_remove = set_difference(G.nodes, nodes)
|
|
139
|
+
sg = deepcopy(G)
|
|
140
|
+
sg.remove_nodes_from(nodes_to_remove)
|
|
141
|
+
|
|
142
|
+
logger.trace(sg.nodes)
|
|
143
|
+
if len(nodes) > 2:
|
|
144
|
+
root, end = find_ends_of_a_directed_graph(sg)
|
|
145
|
+
paths = list(nx.shortest_simple_paths(sg, root, end))
|
|
146
|
+
logger.trace(paths)
|
|
147
|
+
assert len(paths) == 1
|
|
148
|
+
return paths[0]
|
|
149
|
+
|
|
150
|
+
logger.trace(G.edge_subgraph(sg.edges).edges)
|
|
151
|
+
assert sg.order() == 2
|
|
152
|
+
assert len(sg.edges) == 1
|
|
153
|
+
e = list(sg.edges)[0]
|
|
154
|
+
return [e[0], e[1]]
|
|
155
|
+
# TODO: some stronger conditions on this.. this is assuming that everuthing is connected..
|
|
156
|
+
return list(sg.edges)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def find_small_node_surfaces(G: nx.DiGraph, nodes: Iterable[str]) -> list[Surface]:
|
|
160
|
+
def find(node: str):
|
|
161
|
+
res = G.nodes[node].get("data")
|
|
162
|
+
assert isinstance(res, NodeData)
|
|
163
|
+
s = res.surface
|
|
164
|
+
assert s
|
|
165
|
+
return s
|
|
166
|
+
|
|
167
|
+
return [find(n) for n in nodes]
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def handle_components(G: nx.DiGraph, nodes_: Iterable[str]):
|
|
171
|
+
nodes = list(nodes_)
|
|
172
|
+
if len(nodes) >= 2:
|
|
173
|
+
ordered_nodes = order_nodes_based_on_graph(G, nodes)
|
|
174
|
+
logger.trace(pretty_repr({"orig_nodes": nodes, "ordered": ordered_nodes}))
|
|
175
|
+
else:
|
|
176
|
+
ordered_nodes = nodes
|
|
177
|
+
return find_small_node_surfaces(G, ordered_nodes)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def get_surface(G: nx.Graph, node: str):
|
|
181
|
+
data = G.nodes[node].get("data")
|
|
182
|
+
assert isinstance(data, NodeData)
|
|
183
|
+
s = data.surface
|
|
184
|
+
assert s
|
|
185
|
+
return s
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def repr_graph(G: nx.DiGraph):
|
|
189
|
+
fd = {}
|
|
190
|
+
for node, d in G.nodes(data=True):
|
|
191
|
+
# TODO: can clean up with get method..
|
|
192
|
+
data = d["data"]
|
|
193
|
+
assert isinstance(data, NodeData)
|
|
194
|
+
fd[node] = data.repr_dict
|
|
195
|
+
return pretty_repr(fd, expand_all=True)
|