From 2c217489c6cfdcef3a8641a747ce92959bcbfe42 Mon Sep 17 00:00:00 2001
From: Oscar Gustafsson <oscar.gustafsson@gmail.com>
Date: Fri, 1 Sep 2023 16:03:35 +0200
Subject: [PATCH] Add support for removing empty resources from architecture

---
 b_asic/architecture.py           | 62 ++++++++++++++++++++++++++++++--
 examples/fivepointwinograddft.py | 15 ++++----
 2 files changed, 67 insertions(+), 10 deletions(-)

diff --git a/b_asic/architecture.py b/b_asic/architecture.py
index dc78118a..c2943e6e 100644
--- a/b_asic/architecture.py
+++ b/b_asic/architecture.py
@@ -764,19 +764,75 @@ of :class:`~b_asic.architecture.ProcessingElement`
                     d_out[i][v] += 1
         return [dict(d) for d in d_in], [dict(d) for d in d_out]
 
-    def resource_from_name(self, name: str):
+    def resource_from_name(self, name: str) -> Resource:
+        """
+        Get :class:`Resource` based on name.
+
+        Parameters
+        ----------
+        name : str
+            Name of the resource.
+
+        Returns
+        -------
+        :class:`Resource`
+
+        """
         re = {p.entity_name: p for p in chain(self.memories, self.processing_elements)}
         return re[name]
 
+    def remove_resource(
+        self,
+        resource: Union[str, Resource],
+    ) -> None:
+        """
+        Remove an empty :class:`Resource` from the architecture.
+
+        Parameters
+        ----------
+        resource : :class:`b_asic.architecture.Resource` or str
+            The resource or the resource name to remove.
+        """
+        if isinstance(resource, str):
+            resource = self.resource_from_name(resource)
+
+        if resource.collection:
+            raise ValueError("Resource must be empty")
+
+        if resource in self.memories:
+            self.memories.remove(resource)
+        elif resource in self.processing_elements:
+            self.processing_elements.remove(resource)
+        else:
+            raise ValueError('Resource not in architecture')
+
+    def assign_resources(self, heuristic: str = "left_edge") -> None:
+        """
+        Convenience method to assign all resources in the architecture.
+
+        Parameters
+        ----------
+        heuristic : str, default: "left_edge"
+            The heurstic to use.
+
+        See Also
+        --------
+        Memory.assign
+        ProcessingElement.assign
+
+        """
+        for resource in chain(self.memories, self.processing_elements):
+            resource.assign(heuristic=heuristic)
+
     def move_process(
         self,
         proc: Union[str, Process],
         re_from: Union[str, Resource],
         re_to: Union[str, Resource],
         assign: bool = False,
-    ):
+    ) -> None:
         """
-        Move a :class:`b_asic.process.Process` from one resource to another.
+        Move a :class:`b_asic.process.Process` from one :class:`Resource`  to another.
 
         Both the resource moved from and will become unassigned after a process has been
         moved, unless *assign* is set to True.
diff --git a/examples/fivepointwinograddft.py b/examples/fivepointwinograddft.py
index b1a0186d..f9afd0e6 100644
--- a/examples/fivepointwinograddft.py
+++ b/examples/fivepointwinograddft.py
@@ -186,17 +186,18 @@ arch
 # Move memory variables to optimize architecture
 arch.move_process('addsub2.0', 'memory3', 'memory2')
 arch.move_process('bfly2.0', 'memory2', 'memory3')
-memories[2].assign()
-memories[3].assign()
-
 arch.move_process('cmul2.0', 'memory1', 'memory0')
 arch.move_process('bfly3.0', 'memory0', 'memory1')
 arch.move_process('cmul3.0', 'memory4', 'memory0')
-memories[0].assign()
-memories[1].assign()
-memories[4].assign()
 
-for memory in memories:
+arch.assign_resources()
+
+# %%
+# Memory 4 is now empty, so remove it.
+
+arch.remove_resource('memory4')
+
+for memory in arch.memories:
     memory.show_content(title=f"Improved {memory.entity_name}")
 
 arch
-- 
GitLab