Skip to content

Commit 620e9df

Browse files
authored
Merge pull request #55 from LIHPC-Computational-Geometry/10-edge-collapse
10 edge collapse
2 parents b546f6e + 7aed23e commit 620e9df

29 files changed

+1142
-537
lines changed

.github/workflows/model-view-ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: model-ci
1+
name: mesh_model-ci
22

33
on:
44
push:
@@ -23,7 +23,7 @@ jobs:
2323
- name: Install dependencies
2424
run: |
2525
python -m pip install --upgrade pip
26-
pip install flake8 pytest numpy coverage pygame
26+
pip install flake8 pytest numpy coverage pygame matplotlib
2727
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
2828
- name: Lint with flake8
2929
run: |

actions/triangular_actions.py

Lines changed: 111 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from __future__ import annotations
22

3-
from model.mesh_struct.mesh import Mesh
4-
from model.mesh_struct.mesh_elements import Dart, Node
5-
from model.mesh_analysis import degree, isFlipOk
3+
from mesh_model.mesh_struct.mesh import Mesh
4+
from mesh_model.mesh_struct.mesh_elements import Dart, Node
5+
from mesh_model.mesh_analysis import isFlipOk, isCollapseOk, adjacent_darts, isSplitOk
66

77

88
def flip_edge_ids(mesh: Mesh, id1: int, id2: int) -> True:
@@ -15,10 +15,7 @@ def flip_edge(mesh: Mesh, n1: Node, n2: Node) -> True:
1515
if not found or not isFlipOk(d):
1616
return False
1717

18-
d2, d1, d11, d21, d211, n1, n2, n3, n4 = active_triangles(mesh, d)
19-
20-
test_degree(n3)
21-
test_degree(n4)
18+
d2, d1, d11, d21, d211, n1, n2, n3, n4 = mesh.active_triangles(d)
2219

2320
f1 = d.get_face()
2421
f2 = d2.get_face()
@@ -54,12 +51,10 @@ def split_edge_ids(mesh: Mesh, id1: int, id2: int) -> True:
5451

5552
def split_edge(mesh: Mesh, n1: Node, n2: Node) -> True:
5653
found, d = mesh.find_inner_edge(n1, n2)
57-
if not found:
54+
if not found or not isSplitOk(d):
5855
return False
5956

60-
d2, d1, d11, d21, d211, n1, n2, n3, n4 = active_triangles(mesh, d)
61-
test_degree(n3)
62-
test_degree(n4)
57+
d2, d1, _, d21, _, n1, n2, n3, n4 = mesh.active_triangles(d)
6358

6459
# create a new node in the middle of [n1, n2]
6560
N5 = mesh.add_node((n1.x() + n2.x()) / 2, (n1.y() + n2.y()) / 2)
@@ -86,33 +81,110 @@ def split_edge(mesh: Mesh, n1: Node, n2: Node) -> True:
8681
return True
8782

8883

89-
def active_triangles(mesh: Mesh, d: Dart) -> tuple[Dart, Dart, Dart, Dart, Dart, Node, Node, Node, Node]:
90-
"""
91-
Return the darts and nodes around selected dart
92-
:param mesh: the mesh
93-
:param d: selected dart
94-
:return: a tuple of darts and nodes
95-
"""
96-
d2 = d.get_beta(2)
97-
d1 = d.get_beta(1)
98-
d11 = d1.get_beta(1)
99-
d21 = d2.get_beta(1)
100-
d211 = d21.get_beta(1)
101-
n1 = d.get_node()
102-
n2 = d2.get_node()
103-
n3 = d11.get_node()
104-
n4 = d211.get_node()
105-
106-
return d2, d1, d11, d21, d211, n1, n2, n3, n4
107-
108-
109-
def test_degree(n: Node) -> bool:
84+
def collapse_edge_ids(mesh: Mesh, id1: int, id2: int) -> True:
85+
return collapse_edge(mesh, Node(mesh, id1), Node(mesh, id2))
86+
87+
88+
def collapse_edge(mesh: Mesh, n1: Node, n2: Node) -> True:
89+
found, d = mesh.find_inner_edge(n1, n2)
90+
91+
if not found or not isCollapseOk(d):
92+
return False
93+
94+
_, d1, d11, d21, d211, n1, n2, _, _ = mesh.active_triangles(d)
95+
96+
d212 = d21.get_beta(2) #T1
97+
d2112 = d211.get_beta(2) #T2
98+
d12 = d1.get_beta(2) #T3
99+
d112 = d11.get_beta(2) #T4
100+
101+
#Delete the darts around selected dart
102+
mesh.del_adj_triangles(d)
103+
104+
#Move n1 node in the middle of [n1, n2]
105+
n1.set_xy((n1.x() + n2.x()) / 2, (n1.y() + n2.y()) / 2)
106+
107+
#Update node relations
108+
if d12 is not None:
109+
d121 = d12.get_beta(1)
110+
d121.set_node(n1)
111+
ds = d121
112+
while ds is not None and ds != d2112:
113+
d2s = ds.get_beta(2)
114+
if d2s is None:
115+
ds = d2112
116+
while ds is not None:
117+
ds.set_node(n1)
118+
ds1 = ds.get_beta(1)
119+
ds11 = ds1.get_beta(1)
120+
ds = ds11.get_beta(2)
121+
else:
122+
ds = d2s.get_beta(1)
123+
ds.set_node(n1)
110124
"""
111-
Verify that the degree of a vertex is lower than 10
112-
:param n: a Node
113-
:return: True if the degree is lower than 10, False otherwise
125+
elif d12 is None and d2112 is not None:
126+
d2112.set_node(n1)
127+
ds = (d2112.get_beta(1)).get_beta(1)
128+
ds2 = ds.get_beta(2)
129+
while ds2 is not None:
130+
ds2.set_node(n1)
131+
ds = (ds2.get_beta(1)).get_beta(1)
132+
ds2 = ds.get_beta(2)
114133
"""
115-
if degree(n) > 10:
116-
return False
117-
else:
118-
return True
134+
#update beta2 relations
135+
if d112 is not None:
136+
d112.set_beta(2, d12)
137+
if d12 is not None:
138+
d12.set_beta(2, d112)
139+
140+
if d212 is not None:
141+
d212.set_beta(2, d2112)
142+
if d2112 is not None:
143+
d2112.set_beta(2, d212)
144+
145+
#delete n2 node
146+
mesh.del_node(n2)
147+
148+
return mesh_check(mesh)
149+
150+
151+
def check_beta2_relation(mesh: Mesh) -> bool:
152+
for dart_info in mesh.active_darts():
153+
d = dart_info[0]
154+
d2 = dart_info[2]
155+
if d2 >= 0 and mesh.dart_info[d2, 0] < 0:
156+
raise ValueError("error beta2")
157+
elif d2 >= 0 and mesh.dart_info[d2, 2] != d:
158+
raise ValueError("error beta2")
159+
return True
160+
161+
162+
def check_double(mesh: Mesh) -> bool:
163+
for dart_info in mesh.active_darts():
164+
d = Dart(mesh, dart_info[0])
165+
d2 = Dart(mesh, dart_info[2]) if dart_info[2] >= 0 else None
166+
n1 = dart_info[3]
167+
if d2 is None:
168+
d1 = d.get_beta(1)
169+
n2 = d1.get_node().id
170+
else:
171+
n2 = d2.get_node().id
172+
for dart_info2 in mesh.active_darts():
173+
ds = Dart(mesh, dart_info2[0])
174+
ds2 = Dart(mesh, dart_info2[2]) if dart_info2[2] >= 0 else None
175+
if d != ds and d != ds2:
176+
ns1 = dart_info2[3]
177+
if ds2 is None:
178+
ds1 = ds.get_beta(1)
179+
ns2 = ds1.get_node().id
180+
else:
181+
ns2 = ds2.get_node().id
182+
183+
if n1 == ns1 and n2 == ns2:
184+
raise ValueError("double error")
185+
elif n2 == ns1 and n1 == ns2:
186+
return False
187+
return True
188+
189+
def mesh_check(mesh: Mesh) -> bool:
190+
return check_double(mesh) and check_beta2_relation(mesh)

environment/trimesh_env.py

Lines changed: 60 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,29 @@
11
from typing import Any
2+
import math
23
import numpy as np
3-
from model.mesh_analysis import global_score, isValidAction, find_template_opposite_node
4-
from model.mesh_struct.mesh_elements import Dart
5-
from model.mesh_struct.mesh import Mesh
6-
from actions.triangular_actions import flip_edge
7-
from model.random_trimesh import random_flip_mesh
4+
from mesh_model.mesh_analysis import global_score, find_template_opposite_node
5+
from mesh_model.mesh_struct.mesh_elements import Dart
6+
from mesh_model.mesh_struct.mesh import Mesh
7+
from actions.triangular_actions import flip_edge, split_edge, collapse_edge
8+
from mesh_model.random_trimesh import random_flip_mesh, random_mesh
89

910
# possible actions
1011
FLIP = 0
12+
SPLIT = 1
13+
COLLAPSE = 2
1114
GLOBAL = 0
1215

1316

1417
class TriMesh:
1518
def __init__(self, mesh=None, mesh_size: int = None, max_steps: int = 50, feat: int = 0):
1619
self.mesh = mesh if mesh is not None else random_flip_mesh(mesh_size)
17-
self.mesh_size = len(self.mesh.nodes)
20+
self.mesh_size = len(self.mesh.active_nodes())
1821
self.size = len(self.mesh.dart_info)
19-
self.actions = np.array([FLIP])
22+
self.actions = np.array([FLIP, SPLIT, COLLAPSE])
2023
self.reward = 0
2124
self.steps = 0
2225
self.max_steps = max_steps
23-
self.nodes_scores = global_score(self.mesh)[0]
24-
self.ideal_score = global_score(self.mesh)[2]
26+
self.nodes_scores, self.mesh_score, self.ideal_score = global_score(self.mesh)
2527
self.terminal = False
2628
self.feat = feat
2729
self.won = 0
@@ -30,30 +32,34 @@ def reset(self, mesh=None):
3032
self.reward = 0
3133
self.steps = 0
3234
self.terminal = False
33-
self.mesh = mesh if mesh is not None else random_flip_mesh(self.mesh_size)
35+
self.mesh = mesh if mesh is not None else random_mesh(self.mesh_size)
3436
self.size = len(self.mesh.dart_info)
35-
self.nodes_scores = global_score(self.mesh)[0]
36-
self.ideal_score = global_score(self.mesh)[2]
37+
self.nodes_scores, self.mesh_score, self.ideal_score = global_score(self.mesh)
3738
self.won = 0
3839

3940
def step(self, action):
4041
dart_id = action[1]
41-
_, mesh_score, mesh_ideal_score = global_score(self.mesh)
4242
d = Dart(self.mesh, dart_id)
4343
d1 = d.get_beta(1)
4444
n1 = d.get_node()
4545
n2 = d1.get_node()
46-
flip_edge(self.mesh, n1, n2)
46+
if action[2] == FLIP:
47+
flip_edge(self.mesh, n1, n2)
48+
elif action[2] == SPLIT:
49+
split_edge(self.mesh, n1, n2)
50+
elif action[2] == COLLAPSE:
51+
collapse_edge(self.mesh, n1, n2)
4752
self.steps += 1
4853
next_nodes_score, next_mesh_score, _ = global_score(self.mesh)
4954
self.nodes_scores = next_nodes_score
50-
self.reward = (mesh_score - next_mesh_score)*10
51-
if self.steps >= self.max_steps or next_mesh_score == mesh_ideal_score:
52-
if next_mesh_score == mesh_ideal_score:
55+
self.reward = (self.mesh_score - next_mesh_score) * 10
56+
if self.steps >= self.max_steps or next_mesh_score == self.ideal_score:
57+
if next_mesh_score == self.ideal_score:
5358
self.won = True
5459
self.terminal = True
60+
self.nodes_scores, self.mesh_score = next_nodes_score, next_mesh_score
5561

56-
def get_x(self, s: Mesh, a: int) -> tuple[Any, list[int | list[int]]]:
62+
def get_x(self, s: Mesh, a: int):
5763
"""
5864
Get the feature vector of the state-action pair
5965
:param s: the state
@@ -66,19 +72,39 @@ def get_x(self, s: Mesh, a: int) -> tuple[Any, list[int | list[int]]]:
6672
return get_x_global_4(self, s)
6773

6874

69-
def get_x_global_4(env, state: Mesh) -> tuple[Any, list[int | list[int]]]:
75+
def get_x_global_4(env, state: Mesh):
7076
"""
7177
Get the feature vector of the state.
7278
:param state: the state
7379
:param env: The environment
7480
:return: the feature vector
7581
"""
7682
mesh = state
83+
template = get_template_2(mesh)
84+
darts_to_delete = []
85+
darts_id = []
86+
87+
for i, d_info in enumerate(mesh.active_darts()):
88+
d_id = d_info[0]
89+
if d_info[2] == -1: #test the validity of all action type
90+
darts_to_delete.append(i)
91+
else:
92+
darts_id.append(d_id)
93+
valid_template = np.delete(template, darts_to_delete, axis=0)
94+
score_sum = np.sum(np.abs(valid_template), axis=1)
95+
indices_top_10 = np.argsort(score_sum)[-5:][::-1]
96+
valid_dart_ids = [darts_id[i] for i in indices_top_10]
97+
X = valid_template[indices_top_10, :]
98+
X = X.flatten()
99+
return X, valid_dart_ids
100+
101+
102+
def get_template_2(mesh: Mesh):
77103
nodes_scores = global_score(mesh)[0]
78-
size = len(mesh.dart_info)
104+
size = len(mesh.active_darts())
79105
template = np.zeros((size, 6))
80106

81-
for d_info in mesh.dart_info:
107+
for i, d_info in enumerate(mesh.active_darts()):
82108

83109
d = Dart(mesh, d_info[0])
84110
A = d.get_node()
@@ -87,35 +113,21 @@ def get_x_global_4(env, state: Mesh) -> tuple[Any, list[int | list[int]]]:
87113
d11 = d1.get_beta(1)
88114
C = d11.get_node()
89115

90-
#Template niveau 1
91-
template[d_info[0], 0] = nodes_scores[C.id]
92-
template[d_info[0], 1] = nodes_scores[A.id]
93-
template[d_info[0], 2] = nodes_scores[B.id]
116+
# Template niveau 1
117+
template[i, 0] = nodes_scores[C.id] if not math.isnan(nodes_scores[C.id]) else 0
118+
template[i, 1] = nodes_scores[A.id] if not math.isnan(nodes_scores[A.id]) else 0
119+
template[i, 2] = nodes_scores[B.id] if not math.isnan(nodes_scores[B.id]) else 0
94120

95-
#template niveau 2
121+
# template niveau 2
96122

97123
n_id = find_template_opposite_node(d)
98-
if n_id is not None:
99-
template[d_info[0], 3] = nodes_scores[n_id]
124+
if n_id is not None and not math.isnan(nodes_scores[n_id]):
125+
template[i, 3] = nodes_scores[n_id]
100126
n_id = find_template_opposite_node(d1)
101-
if n_id is not None:
102-
template[d_info[0], 4] = nodes_scores[n_id]
127+
if n_id is not None and not math.isnan(nodes_scores[n_id]):
128+
template[i, 4] = nodes_scores[n_id]
103129
n_id = find_template_opposite_node(d11)
104-
if n_id is not None:
105-
template[d_info[0], 5] = nodes_scores[n_id]
106-
107-
dart_to_delete = []
108-
dart_ids = []
109-
for i in range(size):
110-
d = Dart(mesh, i)
111-
if not isValidAction(mesh, d.id):
112-
dart_to_delete.append(i)
113-
else :
114-
dart_ids.append(i)
115-
valid_template = np.delete(template, dart_to_delete, axis=0)
116-
score_sum = np.sum(np.abs(valid_template), axis=1)
117-
indices_top_10 = np.argsort(score_sum)[-5:][::-1]
118-
valid_dart_ids = [dart_ids[i] for i in indices_top_10]
119-
X = valid_template[indices_top_10, :]
120-
X = X.flatten()
121-
return X, valid_dart_ids
130+
if n_id is not None and not math.isnan(nodes_scores[n_id]):
131+
template[i, 5] = nodes_scores[n_id]
132+
133+
return template

0 commit comments

Comments
 (0)