@@ -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