diff --git a/b_asic/resources.py b/b_asic/resources.py
index 85d555fac220edbdf33c3c5f3a21321c2b7dfd28..5c5f426ee067614264981ac674bc271292e8a637 100644
--- a/b_asic/resources.py
+++ b/b_asic/resources.py
@@ -929,7 +929,9 @@ class ProcessCollection:
             The heuristic used when splitting this :class:`ProcessCollection`.
             Valid options are:
 
-            * "graph_color"
+            * "ilp_graph_color"
+            * "greedy_graph_color"
+            * "equitable_graph_color"
             * "left_edge"
             * "min_pe_to_mem"
             * "min_mem_to_pe"
@@ -956,8 +958,18 @@ class ProcessCollection:
         read_ports, write_ports, total_ports = _sanitize_port_option(
             read_ports, write_ports, total_ports
         )
-        if heuristic == "graph_color":
-            return self._split_ports_graph_color(read_ports, write_ports, total_ports)
+        if heuristic == "ilp_graph_color":
+            return self._split_ports_ilp_graph_color(
+                read_ports, write_ports, total_ports
+            )
+        elif heuristic == "greedy_graph_color":
+            return self._split_ports_greedy_graph_color(
+                read_ports, write_ports, total_ports
+            )
+        elif heuristic == "equitable_graph_color":
+            return self._split_ports_equitable_graph_color(
+                read_ports, write_ports, total_ports
+            )
         elif heuristic == "left_edge":
             return self.split_ports_sequentially(
                 read_ports,
@@ -1255,7 +1267,7 @@ class ProcessCollection:
                     break
         return count
 
-    def _split_ports_graph_color(
+    def _split_ports_greedy_graph_color(
         self,
         read_ports: int,
         write_ports: int,
@@ -1286,15 +1298,103 @@ class ProcessCollection:
             * 'connected_sequential_dfs' or 'connected_sequential'
             * 'saturation_largest_first' or 'DSATUR'
         """
-        # Create new exclusion graph. Nodes are Processes
+        # create new exclusion graph. Nodes are Processes
         exclusion_graph = self.create_exclusion_graph_from_ports(
             read_ports, write_ports, total_ports
         )
 
-        # Perform assignment from coloring and return result
+        # perform assignment from coloring and return result
         coloring = nx.coloring.greedy_color(exclusion_graph, strategy=coloring_strategy)
         return self._split_from_graph_coloring(coloring)
 
+    def _split_ports_equitable_graph_color(
+        self,
+        read_ports: int,
+        write_ports: int,
+        total_ports: int,
+    ) -> list["ProcessCollection"]:
+        # create new exclusion graph. Nodes are Processes
+        exclusion_graph = self.create_exclusion_graph_from_ports(
+            read_ports, write_ports, total_ports
+        )
+
+        # perform assignment from coloring and return result
+        max_degree = max(dict(exclusion_graph.degree()).values())
+        coloring = nx.coloring.equitable_color(
+            exclusion_graph, num_colors=max_degree + 1
+        )
+        return self._split_from_graph_coloring(coloring)
+
+    def _split_ports_ilp_graph_color(
+        self,
+        read_ports: int,
+        write_ports: int,
+        total_ports: int,
+    ) -> list["ProcessCollection"]:
+        from pulp import (
+            LpBinary,
+            LpProblem,
+            LpStatusOptimal,
+            LpVariable,
+            lpSum,
+            value,
+        )
+
+        # create new exclusion graph. Nodes are Processes
+        exclusion_graph = self.create_exclusion_graph_from_ports(
+            read_ports, write_ports, total_ports
+        )
+        nodes = list(exclusion_graph.nodes())
+        edges = list(exclusion_graph.edges())
+
+        # determine an upper bound on the number of colors
+        max_colors = len(nodes)
+
+        # binary variables:
+        #   x[node, color] - whether node is colored in a certain color
+        #   c[color] - whether color is used
+        x = LpVariable.dicts("x", (nodes, range(max_colors)), cat=LpBinary)
+        c = LpVariable.dicts("c", range(max_colors), cat=LpBinary)
+
+        # create the problem, objective function - minimize the number of colors used
+        problem = LpProblem()
+        problem += lpSum(c[i] for i in range(max_colors))
+
+        # constraints:
+        #   1 - nodes have exactly one color
+        #   2 - adjacent nodes cannot have the same color
+        #   3 - only permit assignments if color is used
+        for node in nodes:
+            problem += lpSum(x[node][i] for i in range(max_colors)) == 1
+        for u, v in edges:
+            for color in range(max_colors):
+                problem += x[u][color] + x[v][color] <= c[color]
+        for node in nodes:
+            for color in range(max_colors):
+                problem += x[node][color] <= c[color]
+
+        status = problem.solve()
+
+        if status != LpStatusOptimal:
+            raise ValueError(
+                "Optimal solution could not be found via ILP, use another method."
+            )
+
+        node_colors = {}
+        for node in nodes:
+            for i in range(max_colors):
+                if value(x[node][i]) == 1:
+                    node_colors[node] = i
+
+        # reduce the solution by removing unused colors
+        sorted_unique_values = sorted(set(node_colors.values()))
+        coloring_mapping = {val: i for i, val in enumerate(sorted_unique_values)}
+        minimal_coloring = {
+            key: coloring_mapping[node_colors[key]] for key in node_colors
+        }
+
+        return self._split_from_graph_coloring(minimal_coloring)
+
     def _split_from_graph_coloring(
         self,
         coloring: dict[Process, int],
diff --git a/examples/ldlt_matrix_inverse.py b/examples/ldlt_matrix_inverse.py
index 11a9684f71e2f0a1dfd93a116c4ba85f5d49c9e2..de7662772d6a4a2a7d0486aa48692c28a94b72d3 100644
--- a/examples/ldlt_matrix_inverse.py
+++ b/examples/ldlt_matrix_inverse.py
@@ -117,7 +117,7 @@ mem_vars.show(title="All memory variables")
 direct, mem_vars = mem_vars.split_on_length()
 mem_vars.show(title="Non-zero time memory variables")
 mem_vars_set = mem_vars.split_on_ports(
-    read_ports=1, write_ports=1, total_ports=2, heuristic="graph_color"
+    read_ports=1, write_ports=1, total_ports=2, heuristic="greedy_graph_color"
 )
 
 # %%
diff --git a/examples/memory_constrained_scheduling.py b/examples/memory_constrained_scheduling.py
index 66cb3db1a254348c397e97fb0278473794505161..7ae0d015e34b893b4e94ee63ad665acd4b297ebd 100644
--- a/examples/memory_constrained_scheduling.py
+++ b/examples/memory_constrained_scheduling.py
@@ -64,7 +64,7 @@ mem_vars.show(title="All memory variables")
 direct, mem_vars = mem_vars.split_on_length()
 mem_vars.show(title="Non-zero time memory variables")
 mem_vars_set = mem_vars.split_on_ports(
-    read_ports=1, write_ports=1, total_ports=2, heuristic="graph_color"
+    read_ports=1, write_ports=1, total_ports=2, heuristic="greedy_graph_color"
 )
 
 # %%
@@ -121,7 +121,7 @@ pe_out = ProcessingElement(outputs, entity_name='output')
 
 mem_vars.show(title="Non-zero time memory variables")
 mem_vars_set = mem_vars.split_on_ports(
-    heuristic="graph_color", read_ports=1, write_ports=1, total_ports=2
+    heuristic="greedy_graph_color", read_ports=1, write_ports=1, total_ports=2
 )
 
 # %% Allocate memories by graph coloring
diff --git a/pyproject.toml b/pyproject.toml
index 88282db0f0b68511b29d006c68f9adbac121e1c9..c664151ce2bdebff91076fbc7028dd68f5d592b1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -12,6 +12,7 @@ dependencies = [
   "matplotlib>=3.7",
   "setuptools_scm[toml]>=6.2",
   "networkx>=3",
+  "pulp",
   "qtawesome",
 ]
 classifiers = [
diff --git a/test/integration/test_sfg_to_architecture.py b/test/integration/test_sfg_to_architecture.py
index 8266b7b9756688f54b900b1f325bc5b18ec4eb01..b530cd3c452087ea24747c22b343bee28aaafdee 100644
--- a/test/integration/test_sfg_to_architecture.py
+++ b/test/integration/test_sfg_to_architecture.py
@@ -126,11 +126,11 @@ def test_pe_and_memory_constrained_schedule():
     mem_vars = schedule.get_memory_variables()
     direct, mem_vars = mem_vars.split_on_length()
     mem_vars_set = mem_vars.split_on_ports(
-        read_ports=1, write_ports=1, total_ports=2, heuristic="graph_color"
+        read_ports=1, write_ports=1, total_ports=2, heuristic="greedy_graph_color"
     )
 
     mem_vars_set = mem_vars.split_on_ports(
-        read_ports=1, write_ports=1, total_ports=2, heuristic="graph_color"
+        read_ports=1, write_ports=1, total_ports=2, heuristic="greedy_graph_color"
     )
 
     memories = []
@@ -268,12 +268,12 @@ def test_different_resource_algorithms():
     assert len(arch.processing_elements) == 6
     assert len(arch.memories) == 6
 
-    # GRAPH COLORING
+    # GREEDY GRAPH COLORING
     mem_vars_set = mem_vars.split_on_ports(
         read_ports=1,
         write_ports=1,
         total_ports=2,
-        heuristic="graph_color",
+        heuristic="greedy_graph_color",
         processing_elements=processing_elements,
     )
 
@@ -290,3 +290,84 @@ def test_different_resource_algorithms():
     )
     assert len(arch.processing_elements) == 6
     assert len(arch.memories) == 4
+
+    # EQUITABLE COLOR
+    mem_vars_set = mem_vars.split_on_ports(
+        read_ports=1,
+        write_ports=1,
+        total_ports=2,
+        heuristic="equitable_graph_color",
+        processing_elements=processing_elements,
+    )
+
+    memories = []
+    for i, mem in enumerate(mem_vars_set):
+        memory = Memory(mem, memory_type="RAM", entity_name=f"memory{i}")
+        memories.append(memory)
+        memory.assign("graph_color")
+
+    arch = Architecture(
+        processing_elements,
+        memories,
+        direct_interconnects=direct,
+    )
+    assert len(arch.processing_elements) == 6
+    assert len(arch.memories) == 7
+
+    # FOR ILP points is reduced due to time complexity
+    POINTS = 16
+    sfg = radix_2_dif_fft(POINTS)
+    sfg.set_latency_of_type(Butterfly, 1)
+    sfg.set_latency_of_type(ConstantMultiplication, 3)
+    sfg.set_execution_time_of_type(Butterfly, 1)
+    sfg.set_execution_time_of_type(ConstantMultiplication, 1)
+
+    schedule_2 = Schedule(
+        sfg,
+        scheduler=HybridScheduler(
+            resources, max_concurrent_reads=4, max_concurrent_writes=4
+        ),
+    )
+
+    operations = schedule_2.get_operations()
+    bfs = operations.get_by_type_name(Butterfly.type_name())
+    bfs = bfs.split_on_execution_time()
+    const_muls = operations.get_by_type_name(ConstantMultiplication.type_name())
+    inputs = operations.get_by_type_name(Input.type_name())
+    outputs = operations.get_by_type_name(Output.type_name())
+
+    bf_pe_1 = ProcessingElement(bfs[0], entity_name="bf1")
+    bf_pe_2 = ProcessingElement(bfs[1], entity_name="bf2")
+
+    mul_pe_1 = ProcessingElement(const_muls, entity_name="mul1")
+
+    pe_in = ProcessingElement(inputs, entity_name="input")
+    pe_out = ProcessingElement(outputs, entity_name="output")
+
+    processing_elements = [bf_pe_1, bf_pe_2, mul_pe_1, pe_in, pe_out]
+
+    mem_vars = schedule_2.get_memory_variables()
+    direct, mem_vars = mem_vars.split_on_length()
+
+    # ILP COLOR
+    mem_vars_set = mem_vars.split_on_ports(
+        read_ports=1,
+        write_ports=1,
+        total_ports=2,
+        heuristic="ilp_graph_color",
+        processing_elements=processing_elements,
+    )
+
+    memories = []
+    for i, mem in enumerate(mem_vars_set):
+        memory = Memory(mem, memory_type="RAM", entity_name=f"memory{i}")
+        memories.append(memory)
+        memory.assign("graph_color")
+
+    arch = Architecture(
+        processing_elements,
+        memories,
+        direct_interconnects=direct,
+    )
+    assert len(arch.processing_elements) == 5
+    assert len(arch.memories) == 4
diff --git a/test/unit/test_resources.py b/test/unit/test_resources.py
index e6c01c83f5fb509cb88f5c441ad327052bdf9070..c54cca949cf90bbe23fcac255ec9e900f9a79bfd 100644
--- a/test/unit/test_resources.py
+++ b/test/unit/test_resources.py
@@ -30,11 +30,11 @@ class TestProcessCollectionPlainMemoryVariable:
         generate_matrix_transposer(4).plot(ax=ax)  # type: ignore
         return fig
 
-    def test_split_memory_variable_graph_color(
+    def test_split_memory_variable_greedy_graph_color(
         self, simple_collection: ProcessCollection
     ):
         collection_split = simple_collection.split_on_ports(
-            heuristic="graph_color", read_ports=1, write_ports=1, total_ports=2
+            heuristic="greedy_graph_color", read_ports=1, write_ports=1, total_ports=2
         )
         assert len(collection_split) == 3