Scientists built a cell from scratch that eats, divides and evolves. They just won’t call it alive
Back to Tutorials
scienceTutorialbeginner

Scientists built a cell from scratch that eats, divides and evolves. They just won’t call it alive

July 2, 202615 views6 min read

Learn to build a simulation of synthetic cells that eat, grow, and divide using Python. This hands-on tutorial demonstrates the principles behind artificial life systems like the SpudCell research.

Introduction

In this tutorial, you'll learn how to create a simple simulation of a synthetic cell using Python. This project will help you understand the fundamental principles behind creating artificial life systems, similar to the groundbreaking SpudCell developed by researchers at the University of Minnesota. While we won't be building a real biological cell, we'll create a digital model that demonstrates key behaviors like growth, division, and competition.

Prerequisites

Before starting this tutorial, you should have:

  • Basic understanding of Python programming
  • Python 3.6 or higher installed on your computer
  • Text editor or IDE (like VS Code or PyCharm)
  • Basic knowledge of object-oriented programming concepts

Step-by-Step Instructions

Step 1: Setting Up Your Python Environment

Install Required Libraries

First, we'll create a new Python project and install any necessary libraries. Open your terminal or command prompt and run:

pip install numpy matplotlib

This installs NumPy for numerical operations and Matplotlib for visualization. These libraries will help us simulate cell behavior and visualize the results.

Step 2: Creating the Basic Cell Class

Define the Cell Structure

Let's start by creating the foundation of our synthetic cell. Create a new file called synthetic_cell.py and add the following code:

import random
import matplotlib.pyplot as plt
import numpy as np


class SyntheticCell:
    def __init__(self, x, y, energy=100):
        self.x = x  # Position on x-axis
        self.y = y  # Position on y-axis
        self.energy = energy  # Energy level
        self.age = 0  # Age in time steps
        self.divided = False  # Whether the cell has divided
        
    def consume_energy(self, amount):
        self.energy -= amount
        
    def produce_energy(self, amount):
        self.energy += amount
        
    def grow(self):
        self.age += 1
        
    def can_divide(self):
        return self.energy > 150 and self.age > 5
        
    def divide(self):
        if self.can_divide():
            # Create a new cell with half the energy
            new_cell = SyntheticCell(
                x=self.x + random.randint(-2, 2),
                y=self.y + random.randint(-2, 2),
                energy=self.energy // 2
            )
            self.energy //= 2  # Halve the parent's energy
            self.divided = True
            return new_cell
        return None

This class defines a basic synthetic cell with essential properties: position, energy, age, and division status. The consume_energy and produce_energy methods control how the cell gains and spends energy, which is crucial for simulating life-like behaviors.

Step 3: Adding Environmental Interactions

Creating a Food Source

Next, we'll add an environment where cells can find food to consume. Add this code to your file:

class Environment:
    def __init__(self, width=100, height=100):
        self.width = width
        self.height = height
        self.food_sources = []
        self.initialize_food()
        
    def initialize_food(self):
        # Create random food sources
        for _ in range(20):
            x = random.randint(0, self.width)
            y = random.randint(0, self.height)
            self.food_sources.append((x, y))
            
    def find_nearest_food(self, cell):
        nearest_food = None
        min_distance = float('inf')
        
        for food_x, food_y in self.food_sources:
            distance = ((cell.x - food_x) ** 2 + (cell.y - food_y) ** 2) ** 0.5
            if distance < min_distance:
                min_distance = distance
                nearest_food = (food_x, food_y)
        
        return nearest_food
        
    def remove_food(self, x, y):
        if (x, y) in self.food_sources:
            self.food_sources.remove((x, y))
            return True
        return False

This environment class creates a space where cells can find food. The cells will move toward the nearest food source, consuming it to gain energy. This simulates how real cells eat nutrients to survive and grow.

Step 4: Implementing Cell Movement and Behavior

Adding Movement and Survival Logic

Now let's add movement and survival logic to our cells:

def move_towards_food(self, cell, environment):
        nearest_food = environment.find_nearest_food(cell)
        if nearest_food:
            food_x, food_y = nearest_food
            # Move toward food
            if cell.x < food_x:
                cell.x += 1
            elif cell.x > food_x:
                cell.x -= 1
            
            if cell.y < food_y:
                cell.y += 1
            elif cell.y > food_y:
                cell.y -= 1
            
            # Check if cell reached food
            if abs(cell.x - food_x) < 2 and abs(cell.y - food_y) < 2:
                environment.remove_food(food_x, food_y)
                cell.produce_energy(30)  # Gain energy from food
                
    def survive(self, cell, environment):
        # Cells lose energy over time
        cell.consume_energy(1)
        
        # Cells grow with age
        cell.grow()
        
        # Move towards food
        self.move_towards_food(cell, environment)
        
        # Check if cell should divide
        if cell.can_divide():
            return cell.divide()
        
        return None

This logic simulates how cells behave in real life. They must find food to survive, lose energy over time, and divide when they have enough resources. The movement system makes cells actively seek out food sources.

Step 5: Creating the Simulation Loop

Running the Cell Simulation

Let's create the main simulation loop that will run our synthetic cells:

def run_simulation():
    # Create environment
    env = Environment()
    
    # Create initial cells
    cells = [SyntheticCell(random.randint(0, 100), random.randint(0, 100)) 
             for _ in range(5)]
    
    # Simulation parameters
    time_steps = 100
    
    # Store data for visualization
    cell_positions = []
    
    for step in range(time_steps):
        # Store current positions
        positions = [(cell.x, cell.y) for cell in cells]
        cell_positions.append(positions)
        
        # Process each cell
        new_cells = []
        for cell in cells:
            # Check if cell is still alive
            if cell.energy > 0:
                # Cell survives and acts
                new_cell = env.survive(cell, env)
                if new_cell:
                    new_cells.append(new_cell)
            
        # Add new cells from division
        cells.extend(new_cells)
        
        # Remove dead cells
        cells = [cell for cell in cells if cell.energy > 0]
        
        # Print status every 20 steps
        if step % 20 == 0:
            print(f"Step {step}: {len(cells)} cells, avg energy: {np.mean([c.energy for c in cells]):.1f}")
    
    return cell_positions

# Run the simulation
if __name__ == "__main__":
    positions = run_simulation()

This simulation runs through time steps, processing each cell's behavior. It tracks how many cells exist and their average energy levels over time. This demonstrates how synthetic cells can compete for resources and reproduce.

Step 6: Visualizing the Results

Creating a Graphical Representation

Finally, let's add visualization to see how our synthetic cells behave:

def visualize_simulation(positions):
    plt.figure(figsize=(10, 8))
    
    for step, step_positions in enumerate(positions):
        # Plot cells for each time step
        x_coords = [pos[0] for pos in step_positions]
        y_coords = [pos[1] for pos in step_positions]
        
        plt.scatter(x_coords, y_coords, c='blue', s=20, alpha=0.6)
        
        # Add step label
        plt.text(5, 95, f'Step {step}', fontsize=12)
        
        # Add food sources
        plt.scatter([f[0] for f in env.food_sources], [f[1] for f in env.food_sources], 
                   c='green', s=10, alpha=0.5)
        
        plt.xlim(0, 100)
        plt.ylim(0, 100)
        plt.title('Synthetic Cell Simulation')
        plt.xlabel('X Position')
        plt.ylabel('Y Position')
        
        plt.savefig(f'step_{step}.png')
        plt.close()
        
        if step % 10 == 0:
            print(f"Saved visualization for step {step}")

# Run visualization
visualize_simulation(positions)

This visualization shows how cells move and compete for food resources over time. You'll see how they cluster around food sources and how new cells are created through division.

Summary

In this tutorial, you've built a simulation of synthetic cells that demonstrates key life-like behaviors. Your cells can eat food to gain energy, grow over time, and divide when they have enough resources. This simple model illustrates how scientists are approaching the creation of artificial life systems, similar to the groundbreaking SpudCell research. While this is a simplified simulation, it demonstrates the fundamental principles behind creating artificial biological systems. You've learned how to implement object-oriented programming concepts, simulate environmental interactions, and visualize complex systems - all essential skills for understanding synthetic biology and artificial life research.

Source: TNW Neural

Related Articles