Skip to content

Commit a9463f3

Browse files
authored
Merge pull request #23 from OpenSEMBA/realistic-case
Adds realistic bundle case
2 parents 297b191 + 21f9f47 commit a9463f3

20 files changed

+6273
-214
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
*.FCStd1
22
*.pyc
3-
*.msh
43
.vscode/
54
.pytest_cache/
65
.idea/
76
tmpFolder/
87
venv/
98
gmshDoc/
9+
10+
*.msh
1011
*.vtk
12+
*.areas.json

src/AreaExporterService.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,34 @@ def addComputedArea(self, geometry:str, label:str, area:float):
1919
}
2020
self.computedAreas['geometries'].append(geometry)
2121

22-
def addPhysicalModelOfDimension(self, mappedElements:Dict[str,str], dimension=2):
23-
physicalGroups = gmsh.model.getPhysicalGroups(dimension)
22+
def addPhysicalModelForConductors(self, mappedElements:Dict[str,str]):
23+
physicalGroups = gmsh.model.getPhysicalGroups(1)
2424
for physicalGroup in physicalGroups:
2525
entityTags = gmsh.model.getEntitiesForPhysicalGroup(*physicalGroup)
2626
geometryName = gmsh.model.getPhysicalName(*physicalGroup)
27+
if not geometryName.startswith("Conductor_"):
28+
continue
29+
2730
label = ''
2831
for key, geometry in mappedElements.items():
2932
if geometry == geometryName:
3033
label = key
31-
for tag in entityTags:
32-
if dimension == 1:
33-
rad = gmsh.model.occ.getMass(dimension, tag) / (2*np.pi)
34-
area = rad*rad*np.pi
35-
if dimension == 2:
36-
area = gmsh.model.occ.getMass(dimension, tag)
37-
if geometryName != AreaExporterService._EMPTY_NAME_CASE:
38-
self.addComputedArea(geometryName, label, area)
39-
34+
break
35+
36+
# Find surface that has these curves as boundaries
37+
allSurfaces = gmsh.model.getEntities(2)
38+
foundSurface = None
39+
for surface in allSurfaces:
40+
boundary = gmsh.model.getBoundary([surface], oriented=False, recursive=False)
41+
boundaryTags = set(tag for dim, tag in boundary)
42+
if set(entityTags) == boundaryTags:
43+
foundSurface = surface
44+
break
45+
46+
if foundSurface:
47+
area = gmsh.model.occ.getMass(2, foundSurface[1])
48+
self.addComputedArea(geometryName, label, area)
49+
4050
def exportToJson(self, exportFileName:str):
4151
with open(exportFileName + ".areas.json", 'w') as f:
4252
json.dump(self.computedAreas, f, indent=3)

src/Graph.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ def getParentNodes(self) -> List:
5959
def getChildNodes(self) -> List:
6060
return [edge[-1] for edge in self._edges]
6161

62-
#Necesita una revisión pero por ahora hace lo que necesito
6362
def prune_to_longest_paths(self):
6463
connections = self.get_connections()
6564
roots = [n for n in self._nodes if n not in self.getChildNodes()]

src/ShapesClassification.py

Lines changed: 132 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ def __init__(self, shapes, jsonFile:str):
2929
self.crossSectionData = jsonData['CrossSection']
3030
self.pecs = self.get_pecs(shapes)
3131
self.dielectrics = self.get_dielectrics(shapes)
32+
self.open = self.get_open_boundaries(shapes)
3233
self.vacuum = dict()
33-
self.open = dict()
3434
self.nestedGraph = self.__getNestedGraph()
3535
self.isOpenCase = self.isOpenProblem()
3636

@@ -40,27 +40,36 @@ def getNumberFromName(entity_name: str, label: str):
4040
num = int(entity_name[ini:])
4141
return num
4242

43-
def get_pecs(self, entity_tags) -> Dict[str, Dict[str,any]]:
44-
pecNames = self.__getGeometryNamesByMaterialType('PEC')
45-
pecs = dict()
43+
def get_entities_by_material_type(self, entity_tags, material_type: str, entity_dim: int = 2) \
44+
-> Dict[str, List[Tuple[int,int]]]:
45+
"""
46+
Generic method to extract entities by material type from the cross-section data.
47+
48+
Args:
49+
entity_tags: List of entity tags from gmsh
50+
material_type: The material type to filter by (e.g., 'PEC', 'Dielectric', 'OpenBoundary')
51+
entity_dim: The entity dimension to filter by (default: 2 for surfaces)
52+
53+
Returns:
54+
Dictionary mapping entity names to lists of entity tags
55+
"""
56+
material_names = self.__getGeometryNamesByMaterialType(material_type)
57+
entities = dict()
4658
for s in entity_tags:
4759
name = gmsh.model.get_entity_name(*s).split('/')[-1]
48-
if s[0] != 2 or name not in pecNames:
60+
if s[0] != entity_dim or name not in material_names:
4961
continue
50-
pecs[name] = [s]
51-
52-
return pecs
62+
entities.setdefault(name, []).append(s)
63+
return entities
5364

54-
def get_dielectrics(self, entity_tags) -> Dict[str, Dict[str,any]]:
55-
dielectricNames = self.__getGeometryNamesByMaterialType('Dielectric')
56-
dielectrics = dict()
57-
for s in entity_tags:
58-
name = gmsh.model.get_entity_name(*s).split('/')[-1]
59-
if s[0] != 2 or name not in dielectricNames:
60-
continue
61-
dielectrics[name] = [s]
62-
63-
return dielectrics
65+
def get_pecs(self, entity_tags) -> Dict[str, List[Tuple[int,int]]]:
66+
return self.get_entities_by_material_type(entity_tags, 'PEC')
67+
68+
def get_dielectrics(self, entity_tags) -> Dict[str, List[Tuple[int,int]]]:
69+
return self.get_entities_by_material_type(entity_tags, 'Dielectric')
70+
71+
def get_open_boundaries(self, entity_tags) -> Dict[str, List[Tuple[int,int]]]:
72+
return self.get_entities_by_material_type(entity_tags, 'OpenBoundary')
6473

6574
def __getGeometryNamesByMaterialType(self, materialType:str) -> List[str]:
6675
names = [
@@ -70,19 +79,25 @@ def __getGeometryNamesByMaterialType(self, materialType:str) -> List[str]:
7079
]
7180
return names
7281

73-
def isOpenProblem(self) -> None:
82+
def isOpenBoundaryDefined(self) -> bool:
83+
return len(self.open) > 0
84+
85+
def isOpenProblem(self) -> bool:
7486
roots = self.nestedGraph.roots
75-
if len(roots) > 1: #Más de un componente pec/pec pec/dielectric dielectric/dielectric etc da al exterior
87+
if len(self.open) == 1:
7688
return True
77-
if roots[0] in self.dielectrics.keys(): #El único root es un dielectrico
89+
if len(roots) > 1:
90+
return True
91+
if roots[0] in self.dielectrics.keys():
7892
return True
7993
return False
8094

8195
def removeConductorsFromDielectrics(self):
96+
conductorsOnlyGraph = self.getConductorOnlyGraph()
8297
for num, diel in self.dielectrics.items():
8398
pec_surfs = []
8499
for num2, pec_surf in self.pecs.items():
85-
if (num2 in self.nestedGraph.roots) and (not self.isOpenCase):
100+
if (num2 in conductorsOnlyGraph.roots) and (not self.isOpenCase):
86101
continue
87102
pec_surfs.extend(pec_surf)
88103
self.dielectrics[num] = gmsh.model.occ.cut(diel, pec_surfs, removeTool=False)[0]
@@ -105,7 +120,7 @@ def ensureDielectricsDoNotOverlap(self):
105120

106121
def buildVacuumDomain(self):
107122
if self.isOpenCase:
108-
self.vacuum = self._buildDefaultVacuumDomain()
123+
self.vacuum = self._buildOpenVacuumDomain()
109124
else:
110125
self.vacuum = self._buildClosedVacuumDomain()
111126
return self.vacuum
@@ -126,61 +141,73 @@ def _buildClosedVacuumDomain(self) -> Tuple[int, int]:
126141
gmsh.model.occ.synchronize()
127142
return dict([['Vacuum_0', dom]])
128143

129-
def _buildDefaultVacuumDomain(self):
130-
NEAR_REGION_BOUNDING_BOX_SCALING_FACTOR = 1.25
144+
def _buildOpenVacuumDomain(self):
145+
NEAR_REGION_BOUNDING_BOX_SCALING_FACTOR = 1.15
131146
FAR_REGION_DISK_SCALING_FACTOR = 4.0
132147
nonVacuumSurfaces = []
133148
for _, surf in self.pecs.items():
134149
nonVacuumSurfaces.extend(surf)
135150
for _, surf in self.dielectrics.items():
136151
nonVacuumSurfaces.extend(surf)
152+
153+
if self.isOpenBoundaryDefined():
154+
name, vacuum = next(iter(self.open.items()))
137155

138-
boundingBox = BoundingBox.getBoundingBoxFromGroup(nonVacuumSurfaces)
156+
vacuum = gmsh.model.occ.cut(vacuum, nonVacuumSurfaces,
157+
removeObject=True, removeTool=False)[0]
158+
gmsh.model.occ.synchronize()
139159

140-
141-
bbMaxLength = np.max(boundingBox.getLengths())
142-
nearVacuumBoxSize = bbMaxLength*NEAR_REGION_BOUNDING_BOX_SCALING_FACTOR
143-
nVOrigin = tuple(
144-
np.subtract(boundingBox.getCenter(),
145-
(nearVacuumBoxSize/2.0, nearVacuumBoxSize/2.0, 0.0)))
146-
nearVacuum = [
147-
(2, gmsh.model.occ.addRectangle(*nVOrigin, *(nearVacuumBoxSize,)*2))
148-
]
160+
vacuumBoundaries = gmsh.model.getBoundary(vacuum)
161+
externalVacuumBoundaries = [dt for dt in vacuumBoundaries if dt[1]>0]
162+
self.open = dict([[name, externalVacuumBoundaries]])
149163

150-
farVacuumDiameter = FAR_REGION_DISK_SCALING_FACTOR * boundingBox.getDiagonal()
151-
farVacuum = [(2, gmsh.model.occ.addDisk(
152-
*boundingBox.getCenter(),
153-
farVacuumDiameter, farVacuumDiameter))]
164+
return dict([['Vacuum_0', vacuum]])
165+
else:
166+
boundingBox = BoundingBox.getBoundingBoxFromGroup(nonVacuumSurfaces)
154167

155-
gmsh.model.occ.synchronize()
156-
self.open = dict([['OpenBoundary_0', gmsh.model.getBoundary(farVacuum)]])
168+
bbMaxLength = np.max(boundingBox.getLengths())
169+
nearVacuumBoxSize = bbMaxLength*NEAR_REGION_BOUNDING_BOX_SCALING_FACTOR
170+
nVOrigin = tuple(
171+
np.subtract(boundingBox.getCenter(),
172+
(nearVacuumBoxSize/2.0, nearVacuumBoxSize/2.0, 0.0)))
173+
nearVacuum = [
174+
(2, gmsh.model.occ.addRectangle(*nVOrigin, *(nearVacuumBoxSize,)*2))
175+
]
157176

158-
farVacuum = gmsh.model.occ.cut(
159-
farVacuum, nearVacuum, removeObject=True, removeTool=False)[0]
177+
farVacuumDiameter = FAR_REGION_DISK_SCALING_FACTOR * boundingBox.getDiagonal()
178+
farVacuum = [(2, gmsh.model.occ.addDisk(
179+
*boundingBox.getCenter(),
180+
farVacuumDiameter, farVacuumDiameter))]
160181

182+
gmsh.model.occ.synchronize()
183+
self.open = dict([['OpenBoundary_0', gmsh.model.getBoundary(farVacuum)]])
161184

162-
nearVacuum = gmsh.model.occ.cut(
163-
nearVacuum, nonVacuumSurfaces, removeObject=True, removeTool=False)[0]
185+
farVacuum = gmsh.model.occ.cut(
186+
farVacuum, nearVacuum, removeObject=True, removeTool=False)[0]
187+
nearVacuum = gmsh.model.occ.cut(
188+
nearVacuum, nonVacuumSurfaces, removeObject=True, removeTool=False)[0]
164189

165-
gmsh.model.occ.synchronize()
166-
167-
# -- Set mesh size for near vacuum region
168-
bb = BoundingBox(
169-
gmsh.model.getBoundingBox(2, nearVacuum[0][1]))
170-
minSide = np.min(np.array([bb.getLengths()[0], bb.getLengths()[1]]))
190+
gmsh.model.occ.synchronize()
191+
192+
# -- Set mesh size for near vacuum region
193+
bb = BoundingBox(
194+
gmsh.model.getBoundingBox(2, nearVacuum[0][1]))
195+
minSide = np.min(np.array([bb.getLengths()[0], bb.getLengths()[1]]))
196+
197+
innerRegion = gmsh.model.getBoundary(nearVacuum, recursive=True)
198+
gmsh.model.mesh.setSize(innerRegion, minSide / 20)
199+
200+
gmsh.model.occ.synchronize()
171201

172-
innerRegion = gmsh.model.getBoundary(nearVacuum, recursive=True)
173-
gmsh.model.mesh.setSize(innerRegion, minSide / 20)
202+
return dict([['Vacuum_0', nearVacuum], ['Vacuum_1', farVacuum]])
174203

175-
gmsh.model.occ.synchronize()
176204

177-
return dict([['Vacuum_0', nearVacuum], ['Vacuum_1', farVacuum]])
178205

179206
def __getNestedGraph(self):
180207
gmsh.model.occ.synchronize()
181208
graph = Graph()
182209
elements:Dict = {}
183-
elements = {**self.pecs, **self.dielectrics}
210+
elements = {**self.pecs, **self.dielectrics, **self.open}
184211
for key in elements:
185212
graph.add_node(key)
186213
for i, keyA in enumerate(elements):
@@ -192,7 +219,7 @@ def __getNestedGraph(self):
192219
removeObject=False,
193220
removeTool=False
194221
)
195-
if len(inter[1][0]) == 0: #comprueba las intersecciones en las que interfiere el objeto
222+
if len(inter[1][0]) == 0:
196223
continue
197224
else:
198225
if inter[1][0] == elements[keyA]:
@@ -203,16 +230,58 @@ def __getNestedGraph(self):
203230
graph._reorderData()
204231
return graph
205232

206-
def getComponentsMappedByLevel(self) -> Dict[str,str]:
207-
sortedNodes = self.nestedGraph.getNodesByLevels()
233+
def getConductorOnlyGraph(self) -> Graph:
234+
"""
235+
Creates a new graph containing only conductor nodes by removing all dielectric nodes
236+
from the nested graph and preserving conductor relationships.
237+
238+
Returns:
239+
Graph: A new graph with only conductor nodes and their direct connections
240+
"""
241+
conductor_graph = Graph()
242+
243+
for conductor_name in self.pecs.keys():
244+
if conductor_name in self.nestedGraph.nodes:
245+
conductor_graph.add_node(conductor_name)
246+
247+
for edge in self.nestedGraph.edges:
248+
source, destination = edge
249+
250+
if source in self.pecs.keys() and destination in self.pecs.keys():
251+
conductor_graph.add_edge(source, destination)
252+
253+
elif source in self.pecs.keys() and destination in self.dielectrics.keys():
254+
# Look for conductor nodes that are children of this dielectric
255+
for child_edge in self.nestedGraph.edges:
256+
child_source, child_dest = child_edge
257+
if child_source == destination and child_dest in self.pecs.keys():
258+
conductor_graph.add_edge(source, child_dest)
259+
260+
conductor_graph.prune_to_longest_paths()
261+
conductor_graph._reorderData()
262+
return conductor_graph
263+
264+
def getMappedComponents(self) -> Dict[str,str]:
265+
208266
mappedElements = []
267+
209268
conductors = []
210-
dielectrics = []
269+
sortedNodes = self.nestedGraph.getNodesByLevels()
211270
for node in sortedNodes:
212271
if node in self.pecs.keys():
213272
conductors.append((node, 'Conductor_{}'.format(len(conductors))))
214-
if node in self.dielectrics.keys():
215-
dielectrics.append((node, 'Dielectric_{}'.format(len(dielectrics))))
216273
mappedElements.extend(conductors)
274+
275+
dielectrics = []
276+
for node in self.dielectrics.keys():
277+
dielectrics.append((node, 'Dielectric_{}'.format(len(dielectrics))))
217278
mappedElements.extend(dielectrics)
218-
return {element[0]:element[1] for element in mappedElements}
279+
280+
mappedComponents = {element[0]:element[1] for element in mappedElements}
281+
282+
for domain in self.vacuum.keys():
283+
mappedComponents[domain] = domain
284+
for openBoundary in self.open.keys():
285+
mappedComponents[openBoundary] = 'OpenBoundary_0'
286+
287+
return mappedComponents

0 commit comments

Comments
 (0)