S.R.A.I / app.py
Sephfox's picture
Update app.py
fc14acd verified
raw
history blame
12.5 kB
import random
import numpy as np
import streamlit as st
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import time
from collections import deque # Add this line
import threading
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 = set()
self.size = 1
self.color = "lightblue"
self.division_threshold = 150
self.update_properties()
def update_properties(self):
if self.cell_type == "early_eukaryote":
self.organelles.add("nucleus")
self.color = "green"
self.size = 2
elif self.cell_type == "advanced_eukaryote":
self.organelles.update(["nucleus", "mitochondria"])
self.color = "red"
self.size = 3
elif self.cell_type == "plant_like":
self.organelles.update(["nucleus", "mitochondria", "chloroplast"])
self.color = "darkgreen"
self.size = 4
elif self.cell_type == "complete":
self.organelles.update(["nucleus", "mitochondria", "chloroplast", "endoplasmic_reticulum", "golgi_apparatus"])
self.color = "purple"
self.size = 5
def move(self, environment):
dx, dy = random.uniform(-1, 1), 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):
base_energy = environment.grid[int(self.y)][int(self.x)] * 0.1
if "chloroplast" in self.organelles:
base_energy += environment.light_level * 2
self.energy += base_energy
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
new_cell = Cell(self.x, self.y, self.cell_type)
new_cell.organelles = self.organelles.copy()
return new_cell
return None
def can_merge(self, other):
return (self.cell_type == other.cell_type and
random.random() < 0.01) # 1% chance of merging
def merge(self, other):
new_cell_type = self.cell_type
if self.cell_type == "prokaryote":
new_cell_type = "early_eukaryote"
elif self.cell_type == "early_eukaryote":
new_cell_type = "advanced_eukaryote"
elif self.cell_type == "advanced_eukaryote":
new_cell_type = "plant_like"
elif self.cell_type == "plant_like":
new_cell_type = "complete"
new_cell = Cell((self.x + other.x) / 2, (self.y + other.y) / 2, new_cell_type)
new_cell.energy = self.energy + other.energy
new_cell.organelles = self.organelles.union(other.organelles)
new_cell.update_properties()
return new_cell
class Environment:
def __init__(self, width, height, effects):
self.width = width
self.height = height
self.grid = np.random.rand(height, width) * 10
self.light_level = 5
self.cells = []
self.time = 0
self.population_history = {
"prokaryote": [], "early_eukaryote": [],
"advanced_eukaryote": [], "plant_like": [], "complete": []
}
self.effects = effects
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)
# Handle cell merging
for i, cell1 in enumerate(self.cells):
for cell2 in self.cells[i+1:]:
if cell1.can_merge(cell2):
new_cell = cell1.merge(cell2)
new_cells.append(new_cell)
cells_to_remove.extend([cell1, cell2])
# Apply effects
if self.effects['radiation']:
self.apply_radiation()
if self.effects['predation']:
self.apply_predation()
if self.effects['symbiosis']:
self.apply_symbiosis()
# Add new cells and remove dead/merged cells
self.cells.extend(new_cells)
self.cells = [cell for cell in self.cells if cell not in cells_to_remove]
# Record population counts
for cell_type in self.population_history.keys():
count = len([cell for cell in self.cells if cell.cell_type == cell_type])
self.population_history[cell_type].append(count)
def apply_radiation(self):
for cell in self.cells:
if random.random() < 0.01: # 1% chance of mutation
cell.energy *= 0.8
if random.random() < 0.5:
cell.organelles.add(random.choice(["nucleus", "mitochondria", "chloroplast", "endoplasmic_reticulum", "golgi_apparatus"]))
else:
if cell.organelles:
cell.organelles.remove(random.choice(list(cell.organelles)))
cell.update_properties()
def apply_predation(self):
for i, predator in enumerate(self.cells):
if predator.cell_type in ["advanced_eukaryote", "plant_like", "complete"]:
for prey in self.cells[i+1:]:
if prey.cell_type in ["prokaryote", "early_eukaryote"] and random.random() < 0.05:
predator.energy += prey.energy * 0.5
self.cells.remove(prey)
def apply_symbiosis(self):
for i, cell1 in enumerate(self.cells):
for cell2 in self.cells[i+1:]:
if cell1.cell_type != cell2.cell_type and random.random() < 0.01:
shared_energy = (cell1.energy + cell2.energy) * 0.1
cell1.energy += shared_energy
cell2.energy += shared_energy
def get_visualization_data(self):
cell_data = {cell_type: {"x": [], "y": [], "size": []} for cell_type in self.population_history.keys()}
colors = {"prokaryote": "lightblue", "early_eukaryote": "green", "advanced_eukaryote": "red", "plant_like": "darkgreen", "complete": "purple"}
for cell in self.cells:
cell_data[cell.cell_type]["x"].append(cell.x)
cell_data[cell.cell_type]["y"].append(cell.y)
cell_data[cell.cell_type]["size"].append(cell.size * 3)
return cell_data, self.population_history, colors
def setup_figure(env):
cell_types = list(env.population_history.keys())
fig = make_subplots(rows=2, cols=2,
subplot_titles=("Cell Distribution", "Total Population",
"Population by Cell Type", "Organelle Distribution"),
vertical_spacing=0.1,
horizontal_spacing=0.05)
cell_data, population_history, colors = env.get_visualization_data()
# Cell distribution
for cell_type, data in cell_data.items():
fig.add_trace(go.Scatter(
x=data["x"], y=data["y"], mode='markers',
marker=dict(color=colors[cell_type], size=data["size"]),
name=cell_type
), row=1, col=1)
# Total population over time
total_population = [sum(counts) for counts in zip(*population_history.values())]
fig.add_trace(go.Scatter(y=total_population, mode='lines', name="Total"), row=1, col=2)
# Population by cell type
for cell_type, counts in population_history.items():
fig.add_trace(go.Scatter(y=counts, mode='lines', name=cell_type, line=dict(color=colors[cell_type])), row=2, col=1)
# Organelle distribution
organelle_counts = {"nucleus": 0, "mitochondria": 0, "chloroplast": 0, "endoplasmic_reticulum": 0, "golgi_apparatus": 0}
for cell in env.cells:
for organelle in cell.organelles:
organelle_counts[organelle] += 1
fig.add_trace(go.Bar(x=list(organelle_counts.keys()), y=list(organelle_counts.values()), name="Organelles"), row=2, 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_xaxes(title_text="Time", row=2, col=1)
fig.update_yaxes(title_text="Population", row=2, col=1)
fig.update_xaxes(title_text="Organelle", row=2, col=2)
fig.update_yaxes(title_text="Count", row=2, col=2)
fig.update_layout(height=800, width=1200, title_text="Advanced Cell Evolution Simulation")
return fig
def format_number(num):
if num >= 1_000_000:
return f"{num/1_000_000:.1f}M"
elif num >= 1_000:
return f"{num/1_000:.1f}K"
else:
return str(num)
# Streamlit app
st.title("Continuous Cell Evolution Simulation")
# Sidebar for controls and live statistics
st.sidebar.header("Simulation Controls")
initial_cells = st.sidebar.slider("Initial number of cells", 10, 500, 200)
update_interval = st.sidebar.slider("Update interval (seconds)", 0.01, 1.0, 0.05)
st.sidebar.header("Environmental Effects")
radiation = st.sidebar.checkbox("Radiation")
predation = st.sidebar.checkbox("Predation")
symbiosis = st.sidebar.checkbox("Symbiosis")
effects = {
"radiation": radiation,
"predation": predation,
"symbiosis": symbiosis
}
# Live statistics placeholders
st.sidebar.header("Live Statistics")
total_cells_text = st.sidebar.empty()
cell_type_breakdown = st.sidebar.empty()
dominant_type_text = st.sidebar.empty()
avg_energy_text = st.sidebar.empty()
total_merges_text = st.sidebar.empty()
# Event log
st.sidebar.header("Event Log")
event_log = deque(maxlen=10) # Keep the last 10 events
event_log_text = st.sidebar.empty()
# Create placeholders for the chart
chart_placeholder = st.empty()
if 'running' not in st.session_state:
st.session_state.running = False
if 'total_merges' not in st.session_state:
st.session_state.total_merges = 0
if 'env' not in st.session_state:
st.session_state.env = None
if 'fig' not in st.session_state:
st.session_state.fig = None
def start_simulation():
st.session_state.running = True
if st.session_state.env is None:
st.session_state.env = Environment(100, 100, effects)
for _ in range(initial_cells):
cell = Cell(random.uniform(0, st.session_state.env.width), random.uniform(0, st.session_state.env.height))
st.session_state.env.add_cell(cell)
st.session_state.fig = setup_figure(st.session_state.env)
def stop_simulation():
st.session_state.running = False
# Create two columns for start and stop buttons
col1, col2 = st.columns(2)
with col1:
start_button = st.button("Start Simulation", on_click=start_simulation)
with col2:
stop_button = st.button("Stop Simulation", on_click=stop_simulation)
# Main simulation loop
simulation_container = st.empty()
if st.session_state.running and st.session_state.env is not None:
with simulation_container.container():
for _ in range(4): # Update 4 times per frame to increase simulation speed
initial_cell_count = len(st.session_state.env.cells)
st.session_state.env.update()
final_cell_count = len(st.session_state.env.cells)
# Check for merges
if final_cell_count < initial_cell_count:
merges = initial_cell_count - final_cell_count
st.session_state.total_merges += merges
event_log.appendleft(f"Time {st.session_state.env.time}: {merges} cell merges occurred")
update_chart()
update_statistics()
time.sleep(update_interval)
st.experimental_rerun()
if not st.session_state.running:
st.write("Simulation stopped. Click 'Start Simulation' to run again.")