Skip to content

Commit 98ee095

Browse files
Changed physical model to use mapped graph
1 parent 8472cfc commit 98ee095

6 files changed

Lines changed: 189 additions & 132 deletions

File tree

src/Graph.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from typing import List, Tuple
1+
from collections import defaultdict, deque
2+
from typing import Dict, List, Tuple
23

34
class Graph:
45
def __init__(self):
@@ -91,5 +92,28 @@ def dfs(node, path):
9192
self._nodes = list(new_nodes)
9293
self._edges = list(new_edges)
9394

95+
def getAdyacencyTree(self) -> Dict:
96+
tree = defaultdict(list)
97+
for root in self.roots:
98+
tree[''].append(root)
99+
for parent, child in self._edges:
100+
tree[parent].append(child)
101+
return tree
102+
103+
def getNodesByLevels(self) -> List:
104+
adyacencyTree = self.getAdyacencyTree()
105+
qeue = deque([('',0)])
106+
nodeList = []
107+
while qeue:
108+
node,level = qeue.popleft()
109+
nodeList.append(node)
110+
for child in adyacencyTree[node]:
111+
qeue.append((child, level+1))
112+
return nodeList[1:] #Removes case 0 that is not part of nodes
113+
114+
def _reorderData(self) -> None:
115+
self._edges = sorted(self._edges)
116+
self._nodes = sorted(self._nodes)
117+
94118
def __str__(self):
95119
return f"Graph(Nodes: {self._nodes},\n Edges: {self._edges})"

src/ShapesClassification.py

Lines changed: 47 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ class ShapesClassification:
1515
_ROUND_VALUE:int = 6
1616

1717
isOpenCase:bool
18-
crossSectionData: Dict
19-
pecs: Dict
20-
dielectrics: Dict
18+
crossSectionData: Dict[str,List[Dict[str,any]]]
19+
pecs: Dict[str,List[Tuple[int,int]]]
20+
dielectrics: Dict[str,List[Tuple[int,int]]]
2121
nestedGraph: Graph
2222

2323
def __init__(self, shapes, jsonFile:str):
@@ -29,12 +29,11 @@ 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.shieldReference = dict()
3332
self.vacuum = dict()
33+
self.open = dict()
3434
self.nestedGraph = self.__getNestedGraph()
3535
self.isOpenCase = self.isOpenProblem()
3636

37-
3837
@staticmethod
3938
def getNumberFromName(entity_name: str, label: str):
4039
ini = entity_name.rindex(label) + len(label)
@@ -72,67 +71,51 @@ def __getGeometryNamesByMaterialType(self, materialType:str) -> List[str]:
7271
return names
7372

7473
def isOpenProblem(self) -> None:
75-
if len(self.nestedGraph.roots) > 1:
74+
roots = self.nestedGraph.roots
75+
if len(roots) > 1: #Más de un componente pec/pec pec/dielectric dielectric/dielectric etc da al exterior
76+
return True
77+
if roots[0] in self.dielectrics.keys(): #El único root es un dielectrico
7678
return True
7779
return False
7880

7981
def removeConductorsFromDielectrics(self):
8082
for num, diel in self.dielectrics.items():
8183
pec_surfs = []
8284
for num2, pec_surf in self.pecs.items():
83-
if num2 == 0 and not self.isOpenCase:
85+
if (num2 in self.nestedGraph.roots) and (not self.isOpenCase):
8486
continue
8587
pec_surfs.extend(pec_surf)
8688
self.dielectrics[num] = gmsh.model.occ.cut(diel, pec_surfs, removeTool=False)[0]
8789

8890
gmsh.model.occ.synchronize()
8991

9092
def ensureDielectricsDoNotOverlap(self):
91-
for n1, diel1 in self.dielectrics.items():
92-
others = list(
93-
chain(
94-
*[x[1] for x in self.dielectrics.items() if x[0] != n1]
95-
)
96-
)
93+
for currentKey in self.dielectrics.keys():
94+
95+
others = list(chain(*[tag for key, tag in self.dielectrics.items() if currentKey != key]))
9796

9897
if len(others) == 0:
9998
continue
10099

101-
self.dielectrics[n1] = gmsh.model.occ.cut(
102-
self.dielectrics[n1], others, removeObject=True, removeTool=False)[0]
100+
self.dielectrics[currentKey] = gmsh.model.occ.cut(
101+
self.dielectrics[currentKey], others, removeObject=True, removeTool=False
102+
)[0]
103103

104104
gmsh.model.occ.synchronize()
105105

106106
def buildVacuumDomain(self):
107-
if self.isOpenCase and len(self.open) == 0:
107+
if self.isOpenCase:
108108
self.vacuum = self._buildDefaultVacuumDomain()
109-
elif self.isOpenCase and len(self.open) > 0:
110-
self.vacuum = self._buildVacuumDomainFromOpenBoundary()
111109
else:
112110
self.vacuum = self._buildClosedVacuumDomain()
113111
return self.vacuum
114112

115-
def _buildVacuumDomainFromOpenBoundary(self) -> Dict[int, List[int]]:
116-
dom = self.open[0]
117-
118-
surfsToRemove = []
119-
for num, surf in self.pecs.items():
120-
surfsToRemove.extend(surf)
121-
122-
for _, surf in self.dielectrics.items():
123-
surfsToRemove.extend(surf)
124-
125-
dom = gmsh.model.occ.cut(
126-
dom, surfsToRemove, removeObject=False, removeTool=False)[0]
127-
gmsh.model.occ.synchronize()
128-
129-
return dict([[0, dom]])
130-
131113
def _buildClosedVacuumDomain(self) -> Tuple[int, int]:
132-
dom = self.pecs[0]
114+
root = self.nestedGraph.roots[0]
115+
dom = self.pecs[root]
133116
surfsToRemove = []
134117
for num, surf in self.pecs.items():
135-
if num == 0:
118+
if num == root:
136119
continue
137120
surfsToRemove.extend(surf)
138121

@@ -141,7 +124,7 @@ def _buildClosedVacuumDomain(self) -> Tuple[int, int]:
141124
dom = gmsh.model.occ.cut(
142125
dom, surfsToRemove, removeObject=False, removeTool=False)[0]
143126
gmsh.model.occ.synchronize()
144-
return dict([[0, dom]])
127+
return dict([['Vacuum_0', dom]])
145128

146129
def _buildDefaultVacuumDomain(self):
147130
NEAR_REGION_BOUNDING_BOX_SCALING_FACTOR = 1.25
@@ -170,7 +153,7 @@ def _buildDefaultVacuumDomain(self):
170153
farVacuumDiameter, farVacuumDiameter))]
171154

172155
gmsh.model.occ.synchronize()
173-
self.open = dict([[0, gmsh.model.getBoundary(farVacuum)]])
156+
self.open = dict([['OpenBoundary_0', gmsh.model.getBoundary(farVacuum)]])
174157

175158
farVacuum = gmsh.model.occ.cut(
176159
farVacuum, nearVacuum, removeObject=True, removeTool=False)[0]
@@ -189,32 +172,47 @@ def _buildDefaultVacuumDomain(self):
189172
innerRegion = gmsh.model.getBoundary(nearVacuum, recursive=True)
190173
gmsh.model.mesh.setSize(innerRegion, minSide / 20)
191174

192-
193-
194175
gmsh.model.occ.synchronize()
195176

196-
return dict([[0, nearVacuum], [1, farVacuum]])
177+
return dict([['Vacuum_0', nearVacuum], ['Vacuum_1', farVacuum]])
197178

198179
def __getNestedGraph(self):
199180
gmsh.model.occ.synchronize()
200181
graph = Graph()
201-
for key in self.pecs.keys():
182+
elements:Dict = {}
183+
elements = {**self.pecs, **self.dielectrics}
184+
for key in elements:
202185
graph.add_node(key)
203-
for i, keyA in enumerate(self.pecs.keys()):
204-
for j, keyB in enumerate(self.pecs.keys()):
186+
for i, keyA in enumerate(elements):
187+
for j, keyB in enumerate(elements):
205188
if i < j:
206189
inter = gmsh.model.occ.intersect(
207-
self.pecs[keyA],
208-
self.pecs[keyB],
190+
elements[keyA],
191+
elements[keyB],
209192
removeObject=False,
210193
removeTool=False
211194
)
212195
if len(inter[1][0]) == 0: #comprueba las intersecciones en las que interfiere el objeto
213196
continue
214197
else:
215-
if inter[1][0] == self.pecs[keyA]:
198+
if inter[1][0] == elements[keyA]:
216199
graph.add_edge(keyB, keyA)
217-
elif inter[1][0] == self.pecs[keyB]:
200+
elif inter[1][0] == elements[keyB]:
218201
graph.add_edge(keyA, keyB)
219202
graph.prune_to_longest_paths()
220-
return graph
203+
graph._reorderData()
204+
return graph
205+
206+
def getComponentsMappedByLevel(self) -> Dict[str,str]:
207+
sortedNodes = self.nestedGraph.getNodesByLevels()
208+
mappedElements = []
209+
conductors = []
210+
dielectrics = []
211+
for node in sortedNodes:
212+
if node in self.pecs.keys():
213+
conductors.append((node, 'Conductor_{}'.format(len(conductors))))
214+
if node in self.dielectrics.keys():
215+
dielectrics.append((node, 'Dielectric_{}'.format(len(dielectrics))))
216+
mappedElements.extend(conductors)
217+
mappedElements.extend(dielectrics)
218+
return {element[0]:element[1] for element in mappedElements}

src/mesher.py

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from typing import Tuple
1+
import os
2+
from typing import List, Tuple
23
import gmsh
34
from pathlib import Path
45
from typing import Dict
@@ -31,23 +32,23 @@ def runFromInput(self, inputFile, runGui=False):
3132
caseName = Path(inputFile).stem
3233

3334
gmsh.initialize()
34-
self.meshFromStep(inputFile, caseName, self.DEFAULT_MESHING_OPTIONS)
35-
self.exportGeometryAreas(caseName)
35+
mappedElements = self.meshFromStep(inputFile, caseName, self.DEFAULT_MESHING_OPTIONS)
36+
self.exportGeometryAreas(caseName, mappedElements)
3637
gmsh.write(caseName + '.msh')
3738
gmsh.write(caseName + '.vtk') # vtk export is just for debugging.
3839
if runGui:
3940
gmsh.fltk.run()
4041

4142
gmsh.finalize()
4243

43-
def meshFromStep(self, inputFile: str, caseName: str, meshingOptions=None):
44+
def meshFromStep(self, inputFile: str, caseName: str, meshingOptions=None) -> Dict[str,str]:
4445
if meshingOptions is None:
4546
meshingOptions = Mesher.DEFAULT_MESHING_OPTIONS
4647

4748
gmsh.model.add(caseName)
4849
allShapes = ShapesClassification(
4950
gmsh.model.occ.importShapes(inputFile, highestDimOnly=False),
50-
inputFile.strip('.step') + '.json'
51+
os.path.splitext(inputFile)[0] +'.json'
5152
)
5253

5354
# --- Geometry manipulation ---
@@ -56,33 +57,40 @@ def meshFromStep(self, inputFile: str, caseName: str, meshingOptions=None):
5657
vacuumDomain = allShapes.buildVacuumDomain()
5758
# -- Boundaries
5859
pecBoundaries = self.extractBoundaries(allShapes.pecs)
59-
60+
mappedComponents = allShapes.getComponentsMappedByLevel()
61+
62+
for domain in vacuumDomain.keys():
63+
mappedComponents[domain] = domain
64+
for openRegion in allShapes.open.keys():
65+
mappedComponents[openRegion] = openRegion
66+
components = {
67+
**pecBoundaries,
68+
**allShapes.dielectrics,
69+
**allShapes.open,
70+
**vacuumDomain,
71+
}
72+
6073
self.buildPhysicalModel(
61-
pecBoundaries,
62-
allShapes.dielectrics,
63-
allShapes.open,
64-
vacuumDomain
74+
components,
75+
mappedComponents
6576
)
6677

6778
for [opt, val] in meshingOptions.items():
6879
gmsh.option.setNumber(opt, val)
6980

70-
# --- Mesh generation ---
71-
7281
gmsh.model.mesh.generate(2)
82+
return mappedComponents
7383

74-
def exportGeometryAreas(self, caseName:str):
84+
85+
def exportGeometryAreas(self, caseName:str, mappedElements:Dict[str,str]):
7586
exporter = AreaExporterService()
76-
exporter.addPhysicalModelOfDimension(dimension=2)
77-
exporter.addPhysicalModelOfDimension(dimension=1)
87+
exporter.addPhysicalModelOfDimension(mappedElements, dimension=2)
88+
exporter.addPhysicalModelOfDimension(mappedElements, dimension=1)
7889
exporter.exportToJson(caseName)
7990

8091

81-
def buildPhysicalModel(self, pecBoundaries, dielectrics, openRegion, vacuumDomain):
82-
self._addPhysicalGroup("Conductor_", pecBoundaries, dimensionTag=1)
83-
self._addPhysicalGroup("OpenBoundary_", openRegion, dimensionTag=1)
84-
self._addPhysicalGroup("Vacuum_", vacuumDomain, dimensionTag=2)
85-
self._addPhysicalGroup("Dielectric_", dielectrics, dimensionTag=2)
92+
def buildPhysicalModel(self, components:Dict[str,List[Tuple[int,int]]], labeMapping:Dict[str,str]):
93+
self._createPhysicalGroups(components, labeMapping)
8694

8795
allEnts = gmsh.model.get_entities()
8896
entsInPG = []
@@ -96,13 +104,13 @@ def buildPhysicalModel(self, pecBoundaries, dielectrics, openRegion, vacuumDomai
96104
gmsh.model.occ.synchronize()
97105

98106

99-
def _addPhysicalGroup(self, physicalGroupName:str, objsDict:Dict, dimensionTag=1):
100-
for num, objs in objsDict.items():
101-
name = physicalGroupName + str(num)
102-
tags = [x[1] for x in objs]
103-
gmsh.model.addPhysicalGroup(dimensionTag, tags, name=name)
107+
def _createPhysicalGroups(self, objsDict:Dict[str,List[Tuple[int,int]]], labelMapping:Dict[str,str]):
108+
for name, elements in objsDict.items():
109+
mappedName = labelMapping[name]
110+
dimensionTag = elements[0][0]
111+
tags = [x[1] for x in elements]
112+
gmsh.model.addPhysicalGroup(dimensionTag, tags, name=mappedName)
104113

105-
106114
@staticmethod
107115
def getPhysicalGroupWithName(name: str):
108116
pGs = gmsh.model.getPhysicalGroups()

test/test_ShapesClassification.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def inputFileFromCaseName(self, caseName):
3131
return self.testdataPath + caseName + '/' + caseName + ".step"
3232

3333
def initShapeClassification(self, inputFile:str) -> None:
34-
jsonFile = inputFile.strip('.step') + '.json'
34+
jsonFile = os.path.splitext(inputFile)[0] +'.json'
3535
self.shapeClassification = ShapesClassification(
3636
gmsh.model.occ.importShapes(inputFile, highestDimOnly=False),
3737
jsonFile
@@ -90,4 +90,29 @@ def testDielectricUnshieldedPairClassification(self) -> None:
9090
self.assertListEqual(self.shapeClassification.allShapes, expectedShapes)
9191
self.assertDictEqual(self.shapeClassification.pecs, expectedPecs)
9292
self.assertDictEqual(self.shapeClassification.dielectrics, expectedDielectrics)
93-
self.assertTrue(self.shapeClassification.isOpenCase)
93+
self.assertTrue(self.shapeClassification.isOpenCase)
94+
95+
def test_partially_filled_coax_step_shapes(self):
96+
case = 'partially_filled_coax'
97+
filepath = self.inputFileFromCaseName(case)
98+
self.initShapeClassification(filepath)
99+
100+
self.assertEqual(len(self.shapeClassification.pecs), 2)
101+
self.assertEqual(len(self.shapeClassification.dielectrics), 1)
102+
103+
def test_five_wires_step_shapes(self):
104+
case = 'five_wires'
105+
filepath = self.inputFileFromCaseName(case)
106+
self.initShapeClassification(filepath)
107+
108+
self.assertEqual(len(self.shapeClassification.pecs), 6)
109+
self.assertEqual(len(self.shapeClassification.dielectrics), 5)
110+
111+
def test_three_wires_ribbon_step_shapes(self):
112+
case = 'three_wires_ribbon'
113+
filepath = self.inputFileFromCaseName(case)
114+
self.initShapeClassification(filepath)
115+
116+
self.assertEqual(len(self.shapeClassification.open), 0)
117+
self.assertEqual(len(self.shapeClassification.pecs), 3)
118+
self.assertEqual(len(self.shapeClassification.dielectrics), 3)

test/test_graph.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,19 @@ def testPruneToLongestPaths(self) -> None:
7474
self.graph.prune_to_longest_paths()
7575
self.assertListEqual(sorted(self.graph.edges), sorted(expectedEdges))
7676

77+
def testGetNodesByLevel(self) -> None:
78+
self.graph.nodes = ['A' ,'B', 'C', 'D', 'E', 'F', 'G']
79+
self.graph.edges = [
80+
('A', 'B'), ('A', 'D'),
81+
('B', 'C'),
82+
('C', 'E'),
83+
('F', 'G')
84+
]
85+
86+
expectedList = ['A', 'F', 'B', 'D', 'G', 'C', 'E']
87+
sortedNodes = self.graph.getNodesByLevels()
88+
self.assertListEqual(sortedNodes, expectedList)
89+
7790
def testGetRoots(self) -> None:
7891
self.graph.nodes = ['A' ,'B', 'C', 'D', 'E', 'F', 'G']
7992
self.graph.edges = [

0 commit comments

Comments
 (0)