@@ -68,6 +68,29 @@ def find(self, name: str, default: Any = _FIND_SENTINEL) -> Union["Leaf", Any]:
6868 return default
6969 raise ValueError (f"Leaf { name } not found in { self } " )
7070
71+ def iterate_hierarchically (self , _parents = None ):
72+ """
73+ Iterates over all Leaf nodes while also yielding the list of all Leaf
74+ nodes that are hierarchical parents over the current node.
75+ """
76+ if _parents is None :
77+ _parents = []
78+
79+ if isinstance (self , Leaf ):
80+ yield self , _parents
81+ _parents .append (self )
82+ return
83+
84+ assert isinstance (self , Branch )
85+ if isinstance (self , (Fork , Flat )):
86+ for node in self .nodes :
87+ yield from node .iterate_hierarchically (list (_parents ))
88+ elif isinstance (self , Hierarchical ):
89+ for node in self .nodes :
90+ yield from node .iterate_hierarchically (_parents )
91+ else :
92+ raise RuntimeError ("unhandled structure type" )
93+
7194 def _render_node_name (self ) -> str :
7295 """The name for a Pydot node."""
7396 return f"{ self .__class__ .__name__ } _{ id (self )} "
@@ -164,6 +187,7 @@ class Branch(ArchNode):
164187 Annotated ["Container" , Tag ("Container" )],
165188 Annotated ["Network" , Tag ("Network" )],
166189 Annotated ["Hierarchical" , Tag ("Hierarchical" )],
190+ Annotated ["Flat" , Tag ("Flat" )],
167191 Annotated ["Fork" , Tag ("Fork" )],
168192 ],
169193 Discriminator (_get_tag ),
@@ -236,6 +260,68 @@ def _power_gating(
236260 return result , non_power_gated_porp
237261
238262
263+ class Flat (Branch ):
264+ def _parent2child_names (
265+ self , parent_name : str = None
266+ ) -> tuple [list [tuple [str , str ]], str ]:
267+ from accelforge .frontend .arch .components import Compute
268+
269+ edges = []
270+ current_parent_name = parent_name
271+
272+ for node in self .nodes :
273+ if isinstance (node , (Hierarchical , Fork )):
274+ child_edges , _last_child_name = node ._parent2child_names (
275+ current_parent_name
276+ )
277+ edges .extend (child_edges )
278+ elif isinstance (node , Flat ):
279+ child_edges , _last_child_name = node ._parent2child_names (
280+ current_parent_name
281+ )
282+ edges .extend (child_edges )
283+ elif isinstance (node , Compute ):
284+ # Compute nodes branch off to the side like a Fork
285+ if current_parent_name is not None :
286+ edges .append ((current_parent_name , node ._render_node_name ()))
287+ else :
288+ if current_parent_name is not None :
289+ edges .append ((current_parent_name , node ._render_node_name ()))
290+ return edges , current_parent_name
291+
292+ def _render_node (self ) -> pydot .Node :
293+ """Hierarchical nodes should not be rendered."""
294+ return None
295+
296+ def _render_make_children (self ) -> list [pydot .Node ]:
297+ """Renders only children, not the Hierarchical node itself."""
298+ result = []
299+ for node in self .nodes :
300+ children = node ._render_make_children ()
301+ result .extend (child for child in children if child is not None )
302+ return result
303+
304+ def render (self ) -> str :
305+ """Renders the architecture as a Pydot graph."""
306+ graph = _pydot_graph ()
307+
308+ # Render all nodes (Hierarchical nodes return None and are filtered out)
309+ for node in self ._render_make_children ():
310+ if node is not None :
311+ graph .add_node (node )
312+
313+ # Add edges
314+ edges , _ = self ._parent2child_names ()
315+ print (edges )
316+ for parent_name , child_name in edges :
317+ graph .add_edge (pydot .Edge (parent_name , child_name ))
318+
319+ return _SVGJupyterRender (graph .create_svg (prog = "dot" ).decode ("utf-8" ))
320+
321+ def _repr_svg_ (self ) -> str :
322+ return self .render ()
323+
324+
239325class Hierarchical (Branch ):
240326 def _flatten (
241327 self ,
@@ -300,6 +386,13 @@ def _parent2child_names(
300386 edges .extend (child_edges )
301387 if last_child_name is not None :
302388 current_parent_name = last_child_name
389+ elif isinstance (node , Flat ):
390+ child_edges , last_child_name = node ._parent2child_names (
391+ current_parent_name
392+ )
393+ edges .extend (child_edges )
394+ if last_child_name is not None :
395+ current_parent_name = last_child_name
303396 elif isinstance (node , Compute ):
304397 # Compute nodes branch off to the side like a Fork
305398 if current_parent_name is not None :
0 commit comments