File size: 11,175 Bytes
22b66c4
 
55aad04
22b66c4
 
55aad04
5e7860c
22b66c4
 
16eb022
 
9aae907
 
 
 
 
22b66c4
 
 
 
 
 
9aae907
 
22b66c4
16eb022
22b66c4
9aae907
16eb022
 
 
 
9aae907
22b66c4
 
16eb022
9aae907
22b66c4
 
16eb022
9aae907
22b66c4
 
 
 
9aae907
 
22b66c4
 
 
 
 
16eb022
9aae907
16eb022
9aae907
 
 
 
16eb022
 
22b66c4
 
 
 
 
 
 
16eb022
 
9aae907
16eb022
22b66c4
 
9aae907
16eb022
9aae907
22b66c4
9aae907
16eb022
 
 
 
 
 
9aae907
 
 
 
 
22b66c4
9aae907
 
16eb022
22b66c4
 
9aae907
 
 
 
 
 
 
 
 
 
 
 
22b66c4
9aae907
22b66c4
 
16eb022
 
22b66c4
 
55aad04
16eb022
9aae907
55aad04
22b66c4
 
 
 
 
 
 
16eb022
22b66c4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9aae907
22b66c4
 
9aae907
 
22b66c4
 
 
9aae907
22b66c4
 
 
9aae907
1140353
9aae907
 
 
 
 
1140353
9aae907
 
 
1140353
9aae907
 
 
 
 
 
 
1140353
a841a36
9aae907
 
 
 
 
 
 
55aad04
 
9aae907
 
 
 
55aad04
9aae907
22b66c4
55aad04
9aae907
 
60a321b
9aae907
 
60a321b
 
f259ff8
 
9aae907
f259ff8
 
9aae907
f259ff8
 
 
60a321b
9aae907
 
 
 
 
 
 
 
 
 
 
 
 
 
60a321b
f259ff8
 
 
 
9aae907
 
 
 
60a321b
 
f259ff8
 
22b66c4
9aae907
 
 
 
 
 
 
 
 
d015dfd
9aae907
 
 
 
5e7860c
9aae907
 
 
5e7860c
9aae907
 
 
 
 
 
 
 
 
 
 
0b5745b
9aae907
 
 
 
 
 
 
0b5745b
9aae907
 
 
 
 
 
0b5745b
9aae907
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
import random
import numpy as np
import streamlit as st
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import time

class Organelle:
    def __init__(self, type):
        self.type = type

class Modification:
    def __init__(self, name, effect):
        self.name = name
        self.effect = effect

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.modifications = []
        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.append(Organelle("nucleus"))
            self.color = "green"
            self.size = 2
        elif self.cell_type == "advanced_eukaryote":
            self.organelles.extend([Organelle("nucleus"), Organelle("mitochondria")])
            self.color = "red"
            self.size = 3
        elif self.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):
        base_energy = environment.grid[int(self.y)][int(self.x)] * 0.1
        if "chloroplast" in [org.type for org in self.organelles]:
            base_energy += environment.light_level * 2
        
        for mod in self.modifications:
            base_energy *= mod.effect

        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()
            new_cell.modifications = self.modifications.copy()
            return new_cell
        return None

    def can_fuse(self, other):
        return (self.cell_type == other.cell_type and
                random.random() < 0.005)  # 0.5% chance of fusion

    def fuse(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"

        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 = list(set(self.organelles + other.organelles))
        new_cell.modifications = list(set(self.modifications + other.modifications))
        new_cell.update_properties()
        return new_cell

    def acquire_modification(self):
        possible_mods = [
            Modification("Enhanced metabolism", 1.2),
            Modification("Thick cell wall", 0.8),
            Modification("Efficient energy storage", 1.1),
            Modification("Rapid division", 0.9)
        ]
        new_mod = random.choice(possible_mods)
        if new_mod not in self.modifications:
            self.modifications.append(new_mod)
            self.color = "purple"  # Visual indicator of modification

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
        self.population_history = {
            "prokaryote": [], "early_eukaryote": [],
            "advanced_eukaryote": [], "plant_like": [], "modified": []
        }

    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 fusion
        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])

        # Add new cells and remove dead/fused cells
        self.cells.extend(new_cells)
        self.cells = [cell for cell in self.cells if cell not in cells_to_remove]

        # Introduce mutations and modifications
        for cell in self.cells:
            if random.random() < 0.0001:  # 0.01% chance of mutation
                if cell.cell_type == "early_eukaryote":
                    cell.cell_type = "advanced_eukaryote"
                elif cell.cell_type == "advanced_eukaryote" and random.random() < 0.5:
                    cell.cell_type = "plant_like"
                cell.update_properties()
            
            if random.random() < 0.0005:  # 0.05% chance of acquiring a modification
                cell.acquire_modification()

        # Record population counts
        for cell_type in self.population_history.keys():
            if cell_type != "modified":
                count = len([cell for cell in self.cells if cell.cell_type == cell_type and not cell.modifications])
            else:
                count = len([cell for cell in self.cells if cell.modifications])
            self.population_history[cell_type].append(count)

    def get_visualization_data(self):
        cell_data = {
            "prokaryote": {"x": [], "y": [], "size": [], "color": "lightblue", "symbol": "circle"},
            "early_eukaryote": {"x": [], "y": [], "size": [], "color": "green", "symbol": "square"},
            "advanced_eukaryote": {"x": [], "y": [], "size": [], "color": "red", "symbol": "diamond"},
            "plant_like": {"x": [], "y": [], "size": [], "color": "darkgreen", "symbol": "star"},
            "modified": {"x": [], "y": [], "size": [], "color": "purple", "symbol": "cross"}
        }

        for cell in self.cells:
            cell_type = "modified" if cell.modifications else cell.cell_type
            cell_data[cell_type]["x"].append(cell.x)
            cell_data[cell_type]["y"].append(cell.y)
            cell_data[cell_type]["size"].append(cell.size * 3)

        return cell_data, self.population_history

def setup_figure(env):
    cell_types = ["prokaryote", "early_eukaryote", "advanced_eukaryote", "plant_like", "modified"]
    fig = make_subplots(rows=2, cols=3, 
                        subplot_titles=("Cell Distribution", "Total Population", 
                                        "Prokaryotes", "Early Eukaryotes", 
                                        "Advanced Eukaryotes", "Plant-like & Modified"),
                        vertical_spacing=0.1,
                        horizontal_spacing=0.05)

    # Cell distribution
    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"], symbol=data["symbol"]),
            name=cell_type
        ), row=1, col=1)

    # Total population over time
    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)

    # Individual population charts
    for i, cell_type in enumerate(cell_types):
        if cell_type == "modified":
            fig.add_trace(go.Scatter(y=env.population_history[cell_type], mode='lines', 
                                     name=cell_type, line=dict(color="purple")), row=2, col=3)
        elif cell_type == "plant_like":
            fig.add_trace(go.Scatter(y=env.population_history[cell_type], mode='lines', 
                                     name=cell_type, line=dict(color="darkgreen")), row=2, col=3)
        else:
            fig.add_trace(go.Scatter(y=env.population_history[cell_type], mode='lines', 
                                     name=cell_type), row=2, col=i+1)

    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)

    for i in range(1, 4):
        fig.update_xaxes(title_text="Time", row=2, col=i)
        fig.update_yaxes(title_text="Population", row=2, col=i)

    fig.update_layout(height=800, width=1200, title_text="Advanced Cell Evolution Simulation")

    return fig

# Streamlit app
st.title("Advanced Cell Evolution Simulation")

num_steps = st.slider("Number of simulation steps", 100, 2000, 1000)
initial_cells = st.slider("Initial number of cells", 10, 200, 100)
update_interval = st.slider("Update interval (milliseconds)", 50, 500, 100)

if st.button("Run Simulation"):
    env = Environment(100, 100)
    
    # Add initial cells
    for _ in range(initial_cells):
        cell = Cell(random.uniform(0, env.width), random.uniform(0, env.height))
        env.add_cell(cell)
    
    # Set up the figure
    fig = setup_figure(env)
    chart = st.plotly_chart(fig, use_container_width=True)
    
    # Run simulation
    for step in range(num_steps):
        env.update()
        
        # Update the figure data
        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+5].y = counts  # +5 because we have 5 cell types in the first subplot
                if cell_type != "modified" and cell_type != "plant_like":
                    fig.data[i+10].y = counts  # Update individual population charts
                else:
                    fig.data[13].y = population_history["plant_like"]
                    fig.data[14].y = population_history["modified"]
        
        fig.layout.title.text = f"Advanced Cell Evolution Simulation (Time: {env.time})"
        
        # Update the chart
        chart.plotly_chart(fig, use_container_width=True)
        
        time.sleep(update_interval / 1000)  # Convert milliseconds to seconds

    st.write("Simulation complete!")