Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -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.
52 changes: 21 additions & 31 deletions Simulation/CancerInvasionSteppables.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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


Expand Down
Binary file modified Simulation/__pycache__/CancerInvasionSteppables.cpython-312.pyc
Binary file not shown.
71 changes: 71 additions & 0 deletions test_cancer_invasion.py
Original file line number Diff line number Diff line change
@@ -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!")