diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..48cce9c --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-18 - Optimize CC3D SWIG Array Lookups +**Learning:** In CompuCell3D (CC3D) Python scripts, using `try...except` blocks inside tightly nested spatial loops for field accesses (like `self.cell_field`) incurs significant overhead. Out-of-bounds SWIG object lookups throw exceptions that are costly to handle inside hot loops. +**Action:** Replace bare `except:` blocks around array accesses in spatial loops with explicit boundary checks (e.g., `0 <= x < self.dim.x`) before accessing arrays to significantly improve performance. diff --git a/Simulation/CancerInvasionSteppables.py b/Simulation/CancerInvasionSteppables.py index 3782ab5..baab75d 100644 --- a/Simulation/CancerInvasionSteppables.py +++ b/Simulation/CancerInvasionSteppables.py @@ -89,13 +89,11 @@ def initialize_paper_ecm(self): pixels_assigned = 0 for x, y in fiber_pixels: - try: + if 0 <= x < 500 and 0 <= y < 500: if self.cell_field[x, y, 0] is None: self.cell_field[x, y, 0] = fiber_cell self.fiber_locations.add((x, y)) pixels_assigned += 1 - except: - continue if pixels_assigned >= 10: fibers_created += 1 @@ -176,12 +174,9 @@ def create_paper_cell(self, center_x, center_y, radius): if dx*dx + dy*dy <= radius*radius: px, py = center_x + dx, center_y + dy if 0 <= px < 500 and 0 <= py < 500: - try: - if self.cell_field[px, py, 0] is None: - self.cell_field[px, py, 0] = cell - pixels_added += 1 - except: - continue + if self.cell_field[px, py, 0] is None: + self.cell_field[px, py, 0] = cell + pixels_added += 1 if pixels_added >= 20: return True @@ -210,12 +205,10 @@ def safe_cell_removal(self, cell): min(self.dim.x, int(cell.xCOM) + search_radius)): for y in range(max(0, int(cell.yCOM) - search_radius), min(self.dim.y, int(cell.yCOM) + search_radius)): - try: + if 0 <= x < self.dim.x and 0 <= y < self.dim.y: if self.cell_field[x, y, 0] == cell: self.cell_field[x, y, 0] = None pixels_cleared += 1 - except: - continue return pixels_cleared > 0 except Exception as e: return False @@ -285,16 +278,13 @@ def paper_mmp_system(self): fibers_to_remove = [] for cell in self.cell_list: if cell.type == self.ECMFIBER: - try: - cx, cy = int(cell.xCOM), int(cell.yCOM) - if 0 <= cx < 500 and 0 <= cy < 500: - mmp_conc = mmp_field[cx, cy, 0] - if mmp_conc >= self.degradation_threshold: - fibers_to_remove.append(cell) - # Paper: reduce MMP count by 1 after degradation - mmp_field[cx, cy, 0] = max(0, mmp_conc - 1) - except: - continue + cx, cy = int(cell.xCOM), int(cell.yCOM) + if 0 <= cx < 500 and 0 <= cy < 500: + mmp_conc = mmp_field[cx, cy, 0] + if mmp_conc >= self.degradation_threshold: + fibers_to_remove.append(cell) + # Paper: reduce MMP count by 1 after degradation + mmp_field[cx, cy, 0] = max(0, mmp_conc - 1) # Remove degraded fibers for fiber in fibers_to_remove: @@ -313,12 +303,9 @@ def check_ecm_contact(self, cell): for dy in range(-3, 4): nx, ny = cx + dx, cy + dy if 0 <= nx < 500 and 0 <= ny < 500: - try: - neighbor = self.cell_field[nx, ny, 0] - if neighbor and neighbor.type == self.ECMFIBER: - return True - except: - continue + neighbor = self.cell_field[nx, ny, 0] + if neighbor and neighbor.type == self.ECMFIBER: + return True return False except: return False @@ -371,11 +358,14 @@ def step(self, mcs): contact_area += commonSurfaceArea if contact_area < self.crowding_threshold: - try: - mmp_conc = self.field.MMP[int(cell.xCOM), int(cell.yCOM), 0] + cx, cy = int(cell.xCOM), int(cell.yCOM) + dim_x = getattr(self.dim, 'x', 500) + dim_y = getattr(self.dim, 'y', 500) + if 0 <= cx < dim_x and 0 <= cy < dim_y: + mmp_conc = self.field.MMP[cx, cy, 0] growth_boost = 1.0 + (mmp_conc * 0.1) cell.targetVolume += self.growth_rate * growth_boost - except: + else: cell.targetVolume += self.growth_rate diff --git a/Simulation/__pycache__/CancerInvasionSteppables.cpython-312.pyc b/Simulation/__pycache__/CancerInvasionSteppables.cpython-312.pyc index 611fe30..d58d68d 100644 Binary files a/Simulation/__pycache__/CancerInvasionSteppables.cpython-312.pyc and b/Simulation/__pycache__/CancerInvasionSteppables.cpython-312.pyc differ diff --git a/test_cancer_invasion.py b/test_cancer_invasion.py new file mode 100644 index 0000000..daf23b8 --- /dev/null +++ b/test_cancer_invasion.py @@ -0,0 +1,71 @@ +import sys +from unittest.mock import MagicMock + +# Mock cc3d +cc3d_mock = MagicMock() +cc3d_mock.core = MagicMock() +cc3d_mock.core.PySteppables = MagicMock() + +# Mock SteppableBasePy and MitosisSteppableBase +class MockSteppableBasePy: + def __init__(self, frequency=1): + self.frequency = frequency + self.cell_list = [] + self.cell_field = MagicMock() + self.field = MagicMock() + self.dim = MagicMock() + self.dim.x = 500 + self.dim.y = 500 + self.dim.z = 1 + self.CELL = 1 + self.ECMFIBER = 2 + + def new_cell(self, cell_type): + cell = MagicMock() + cell.type = cell_type + return cell + + def get_field_secretor(self, field_name): + return MagicMock() + +class MockMitosisSteppableBase(MockSteppableBasePy): + pass + +cc3d_mock.core.PySteppables.SteppableBasePy = MockSteppableBasePy +cc3d_mock.core.PySteppables.MitosisSteppableBase = MockMitosisSteppableBase +sys.modules['cc3d'] = cc3d_mock +sys.modules['cc3d.core'] = cc3d_mock.core +sys.modules['cc3d.core.PySteppables'] = cc3d_mock.core.PySteppables + +# Now import +import builtins +builtins.SteppableBasePy = MockSteppableBasePy +builtins.MitosisSteppableBase = MockMitosisSteppableBase + +from Simulation.CancerInvasionSteppables import CancerInvasionSteppable + +def test_safe_cell_removal(): + steppable = CancerInvasionSteppable() + cell = MagicMock() + cell.xCOM = 250 + cell.yCOM = 250 + + # Setup cell_field mock to return cell at a specific location + def cell_field_getitem(idx): + x, y, z = idx + if x == 250 and y == 250: + return cell + return None + + def cell_field_setitem(idx, val): + pass + + steppable.cell_field.__getitem__.side_effect = cell_field_getitem + steppable.cell_field.__setitem__.side_effect = cell_field_setitem + + result = steppable.safe_cell_removal(cell) + assert result == True + +if __name__ == "__main__": + test_safe_cell_removal() + print("Test passed!")