|
import random |
|
import numpy as np |
|
import plotly.graph_objects as go |
|
from plotly.subplots import make_subplots |
|
import streamlit as st |
|
|
|
class Organelle: |
|
def __init__(self, type): |
|
self.type = type |
|
|
|
class Cell: |
|
def __init__(self, x, y, cell_type="prokaryote"): |
|
self.x = x |
|
self.y = y |
|
self.energy = 100 |
|
self.cell_type = cell_type |
|
self.organelles = [] |
|
self.size = 1 |
|
self.color = "blue" |
|
self.division_threshold = 150 |
|
|
|
if cell_type == "prokaryote": |
|
self.color = "lightblue" |
|
elif cell_type == "early_eukaryote": |
|
self.organelles.append(Organelle("nucleus")) |
|
self.color = "green" |
|
self.size = 2 |
|
elif cell_type == "advanced_eukaryote": |
|
self.organelles.extend([Organelle("nucleus"), Organelle("mitochondria")]) |
|
self.color = "red" |
|
self.size = 3 |
|
elif cell_type == "plant_like": |
|
self.organelles.extend([Organelle("nucleus"), Organelle("mitochondria"), Organelle("chloroplast")]) |
|
self.color = "darkgreen" |
|
self.size = 4 |
|
|
|
def move(self, environment): |
|
dx = random.uniform(-1, 1) |
|
dy = random.uniform(-1, 1) |
|
self.x = max(0, min(environment.width - 1, self.x + dx)) |
|
self.y = max(0, min(environment.height - 1, self.y + dy)) |
|
self.energy -= 0.5 * self.size |
|
|
|
def feed(self, environment): |
|
if "chloroplast" in [org.type for org in self.organelles]: |
|
|
|
self.energy += environment.light_level * 2 |
|
else: |
|
|
|
self.energy += environment.grid[int(self.y)][int(self.x)] * 0.1 |
|
environment.grid[int(self.y)][int(self.x)] *= 0.9 |
|
|
|
def can_divide(self): |
|
return self.energy > self.division_threshold |
|
|
|
def divide(self): |
|
if self.can_divide(): |
|
self.energy /= 2 |
|
return Cell(self.x, self.y, self.cell_type) |
|
return None |
|
|
|
def can_fuse(self, other): |
|
return (self.cell_type == "prokaryote" and other.cell_type == "prokaryote" and |
|
random.random() < 0.001) |
|
|
|
def fuse(self, other): |
|
new_cell = Cell( |
|
(self.x + other.x) / 2, |
|
(self.y + other.y) / 2, |
|
"early_eukaryote" |
|
) |
|
new_cell.energy = self.energy + other.energy |
|
return new_cell |
|
|
|
class Environment: |
|
def __init__(self, width, height): |
|
self.width = width |
|
self.height = height |
|
self.grid = np.random.rand(height, width) * 10 |
|
self.light_level = 5 |
|
self.cells = [] |
|
self.time = 0 |
|
|
|
def add_cell(self, cell): |
|
self.cells.append(cell) |
|
|
|
def update(self): |
|
self.time += 1 |
|
|
|
|
|
self.grid += np.random.rand(self.height, self.width) * 0.1 |
|
self.light_level = 5 + np.sin(self.time / 100) * 2 |
|
|
|
new_cells = [] |
|
cells_to_remove = [] |
|
|
|
for cell in self.cells: |
|
cell.move(self) |
|
cell.feed(self) |
|
|
|
if cell.energy <= 0: |
|
cells_to_remove.append(cell) |
|
elif cell.can_divide(): |
|
new_cell = cell.divide() |
|
if new_cell: |
|
new_cells.append(new_cell) |
|
|
|
|
|
for i, cell1 in enumerate(self.cells): |
|
for cell2 in self.cells[i+1:]: |
|
if cell1.can_fuse(cell2): |
|
new_cell = cell1.fuse(cell2) |
|
new_cells.append(new_cell) |
|
cells_to_remove.extend([cell1, cell2]) |
|
|
|
|
|
self.cells.extend(new_cells) |
|
self.cells = [cell for cell in self.cells if cell not in cells_to_remove] |
|
|
|
|
|
for cell in self.cells: |
|
if random.random() < 0.0001: |
|
if cell.cell_type == "early_eukaryote": |
|
cell.cell_type = "advanced_eukaryote" |
|
cell.organelles.append(Organelle("mitochondria")) |
|
cell.color = "red" |
|
cell.size = 3 |
|
elif cell.cell_type == "advanced_eukaryote" and random.random() < 0.5: |
|
cell.cell_type = "plant_like" |
|
cell.organelles.append(Organelle("chloroplast")) |
|
cell.color = "darkgreen" |
|
cell.size = 4 |
|
|
|
def visualize(self): |
|
fig = make_subplots(rows=1, cols=2, subplot_titles=("Cell Distribution", "Population Over Time")) |
|
|
|
def setup_figure(env): |
|
fig = make_subplots(rows=1, cols=2, subplot_titles=("Cell Distribution", "Population Over Time")) |
|
|
|
|
|
for cell_type, data in env.get_visualization_data()[0].items(): |
|
fig.add_trace(go.Scatter( |
|
x=data["x"], y=data["y"], mode='markers', |
|
marker=dict(color=data["color"], size=data["size"]), |
|
name=cell_type |
|
), row=1, col=1) |
|
|
|
|
|
for cell_type, counts in env.population_history.items(): |
|
fig.add_trace(go.Scatter(y=counts, mode='lines', name=cell_type), row=1, col=2) |
|
|
|
fig.update_xaxes(title_text="X", row=1, col=1) |
|
fig.update_yaxes(title_text="Y", row=1, col=1) |
|
fig.update_xaxes(title_text="Time", row=1, col=2) |
|
fig.update_yaxes(title_text="Population", row=1, col=2) |
|
|
|
fig.update_layout(height=600, width=1200, title_text="Cell Evolution Simulation") |
|
|
|
return fig |
|
|
|
|
|
st.title("Cell Evolution Simulation") |
|
|
|
num_steps = st.slider("Number of simulation steps", 100, 1000, 500) |
|
initial_cells = st.slider("Initial number of cells", 10, 100, 50) |
|
update_interval = st.slider("Update interval (milliseconds)", 100, 1000, 200) |
|
|
|
if st.button("Run Simulation"): |
|
env = Environment(100, 100) |
|
|
|
|
|
for _ in range(initial_cells): |
|
cell = Cell(random.uniform(0, env.width), random.uniform(0, env.height)) |
|
env.add_cell(cell) |
|
|
|
|
|
fig = setup_figure(env) |
|
chart = st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
for step in range(num_steps): |
|
env.update() |
|
|
|
|
|
with fig.batch_update(): |
|
cell_data, population_history = env.get_visualization_data() |
|
for i, (cell_type, data) in enumerate(cell_data.items()): |
|
fig.data[i].x = data["x"] |
|
fig.data[i].y = data["y"] |
|
fig.data[i].marker.size = data["size"] |
|
|
|
for i, (cell_type, counts) in enumerate(population_history.items()): |
|
fig.data[i+4].y = counts |
|
|
|
fig.layout.title.text = f"Cell Evolution Simulation (Time: {env.time})" |
|
|
|
|
|
chart.plotly_chart(fig, use_container_width=True) |
|
|
|
time.sleep(update_interval / 1000) |
|
|
|
st.write("Simulation complete!") |