pikaia 0.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,42 @@
1
+ # pycache
2
+ __pycache__
3
+
4
+ # ignore ALL .log files
5
+ ## Core latex/pdflatex auxiliary files:
6
+ *.aux
7
+ *.lof
8
+ *.log
9
+ *.lot
10
+ *.fls
11
+ *.out
12
+ *.toc
13
+ *.fmt
14
+ *.fot
15
+ *.cb
16
+ *.cb2
17
+ .*.lb
18
+
19
+ ## Bibliography auxiliary files (bibtex/biblatex/biber):
20
+ *.bbl
21
+ *.bbl-SAVE-ERROR
22
+ *.bcf
23
+ *.blg
24
+ *-blx.aux
25
+ *-blx.bib
26
+ *.run.xml
27
+
28
+ *.zip
29
+
30
+ src/public/*.pdf
31
+ src/public/*.png
32
+ src/public/test
33
+ src/public/venv
34
+ src/public/dist
35
+ src/public/__pycache__/
36
+ src/public/geneticai/__pycache__/
37
+ tmp/
38
+ tutorial_env/
39
+ venv/
40
+ tex/*.pdf
41
+ tex/*.png
42
+ dist/
pikaia-0.0.2/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2018 The Python Packaging Authority
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
pikaia-0.0.2/PKG-INFO ADDED
@@ -0,0 +1,90 @@
1
+ Metadata-Version: 2.3
2
+ Name: pikaia
3
+ Version: 0.0.2
4
+ Summary: Data analysis with evolutionary simulation
5
+ Project-URL: Homepage, https://github.com/danube-ai/pikaia
6
+ Project-URL: Issues, https://github.com/danube-ai/pikaia/issues
7
+ Author-email: Philipp Wissgott <philipp@danube.ai>, Andreas Roschal <andreas@danube.ai>, Martin Bär <martin@danube.ai>
8
+ License: MIT
9
+ Keywords: AI,data analysis,evolutionary simulation
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: >=3.8
13
+ Requires-Dist: matplotlib>=3.6.3
14
+ Requires-Dist: numpy
15
+ Requires-Dist: pandas>=1.5.3
16
+ Requires-Dist: string
17
+ Description-Content-Type: text/markdown
18
+
19
+ # pikaia - Genetic AI
20
+
21
+ pikaia is the Python implementation of Genetic AI (evolutionary simulation for data analysis).
22
+
23
+ ## Installation
24
+
25
+ Use the package manager [pip](https://pip.pypa.io/en/stable/) to install pikaia
26
+
27
+ ```bash
28
+ pip install pikaia
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ We provide here the code for the "hello_model" example
34
+
35
+ ```python
36
+ import pikaia
37
+
38
+ import pikaia
39
+ import pikaia.alg
40
+
41
+
42
+ rawdata = np.zeros([3,3])
43
+ rawdata[:,:] = [[ 300, 10, 2],
44
+ [ 600, 5, 2],
45
+ [1500, 4, 1]]
46
+ # defines the variant fitness rules
47
+ gvfitnessrules = ["inv_percentage", "inv_percentage", "inv_percentage"]
48
+ # converts the raw data to a genetic population
49
+ data = pikaia.alg.Population(rawdata, gvfitnessrules)
50
+ # defines the used evolutionary strategies
51
+ strategy = ["GS Dominant", "OS Balanced"]
52
+ iterations = 1
53
+
54
+ # creating the genetic model
55
+ model = pikaia.alg.Model(data, strategy)
56
+
57
+ initialgenefitness = [1.0/3.0, 1.0/3.0, 1.0/3.0]
58
+ # returns the gene fitness values after 1 iteration
59
+ model.complete_run(initialgenefitness, iterations)
60
+
61
+ ```
62
+
63
+ ## Examples
64
+ ```python
65
+ # provides the data for a small decision problem
66
+ example3x3 = pikaia.examples.assemble_example("3x3-DomBal+AltSal")
67
+
68
+ # provides the data for a real-world decision problem
69
+ example10x5 = pikaia.examples.assemble_example("10x5-DomBal+AltSal")
70
+
71
+ # use genetic ai to search a datafile using keywords and rank results
72
+ # for a more detailed example we refer to examples/geneticAI_run_search_example.py
73
+ search = pikaia.search.Search(data, orgs_labels, gens_labels)
74
+ fitnessOrganisms, fitnessGenes = search.search_request(query, top_k=5)
75
+ ```
76
+
77
+ For details see examples/README.md.
78
+
79
+ ## Scientific Background
80
+
81
+ Please find the preprint of Genetic AI [here](http://arxiv.org/abs/2501.19113)
82
+
83
+
84
+ In Genetic AI, we convert a data problem to a model of genes and organisms. Afterwards, we run evolutionary simulations to obtain understanding of the input data.
85
+
86
+ Genetic AI is an AI that does not use training data to 'learn' but fully autonomously analyzes a problem. This is done by evolutionary strategies that cover certain 'behavior' and correlations of the input data.
87
+
88
+ ## License
89
+
90
+ [MIT](https://choosealicense.com/licenses/mit/)
pikaia-0.0.2/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # pikaia - Genetic AI
2
+
3
+ pikaia is the Python implementation of Genetic AI (evolutionary simulation for data analysis).
4
+
5
+ ## Installation
6
+
7
+ Use the package manager [pip](https://pip.pypa.io/en/stable/) to install pikaia
8
+
9
+ ```bash
10
+ pip install pikaia
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ We provide here the code for the "hello_model" example
16
+
17
+ ```python
18
+ import pikaia
19
+
20
+ import pikaia
21
+ import pikaia.alg
22
+
23
+
24
+ rawdata = np.zeros([3,3])
25
+ rawdata[:,:] = [[ 300, 10, 2],
26
+ [ 600, 5, 2],
27
+ [1500, 4, 1]]
28
+ # defines the variant fitness rules
29
+ gvfitnessrules = ["inv_percentage", "inv_percentage", "inv_percentage"]
30
+ # converts the raw data to a genetic population
31
+ data = pikaia.alg.Population(rawdata, gvfitnessrules)
32
+ # defines the used evolutionary strategies
33
+ strategy = ["GS Dominant", "OS Balanced"]
34
+ iterations = 1
35
+
36
+ # creating the genetic model
37
+ model = pikaia.alg.Model(data, strategy)
38
+
39
+ initialgenefitness = [1.0/3.0, 1.0/3.0, 1.0/3.0]
40
+ # returns the gene fitness values after 1 iteration
41
+ model.complete_run(initialgenefitness, iterations)
42
+
43
+ ```
44
+
45
+ ## Examples
46
+ ```python
47
+ # provides the data for a small decision problem
48
+ example3x3 = pikaia.examples.assemble_example("3x3-DomBal+AltSal")
49
+
50
+ # provides the data for a real-world decision problem
51
+ example10x5 = pikaia.examples.assemble_example("10x5-DomBal+AltSal")
52
+
53
+ # use genetic ai to search a datafile using keywords and rank results
54
+ # for a more detailed example we refer to examples/geneticAI_run_search_example.py
55
+ search = pikaia.search.Search(data, orgs_labels, gens_labels)
56
+ fitnessOrganisms, fitnessGenes = search.search_request(query, top_k=5)
57
+ ```
58
+
59
+ For details see examples/README.md.
60
+
61
+ ## Scientific Background
62
+
63
+ Please find the preprint of Genetic AI [here](http://arxiv.org/abs/2501.19113)
64
+
65
+
66
+ In Genetic AI, we convert a data problem to a model of genes and organisms. Afterwards, we run evolutionary simulations to obtain understanding of the input data.
67
+
68
+ Genetic AI is an AI that does not use training data to 'learn' but fully autonomously analyzes a problem. This is done by evolutionary strategies that cover certain 'behavior' and correlations of the input data.
69
+
70
+ ## License
71
+
72
+ [MIT](https://choosealicense.com/licenses/mit/)
@@ -0,0 +1,36 @@
1
+ [build-system]
2
+ requires = ["hatchling==1.26.3"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "pikaia"
7
+ version = "0.0.2"
8
+ authors = [
9
+ { name="Philipp Wissgott", email="philipp@danube.ai" },
10
+ { name="Andreas Roschal", email="andreas@danube.ai" },
11
+ { name="Martin Bär", email="martin@danube.ai" }
12
+ ]
13
+ description = "Data analysis with evolutionary simulation"
14
+ keywords = ["data analysis", "AI", "evolutionary simulation"]
15
+ readme = "README.md"
16
+ requires-python = ">=3.8"
17
+ classifiers = [
18
+ "Programming Language :: Python :: 3",
19
+ "Operating System :: OS Independent",
20
+ ]
21
+ license = "MIT"
22
+ license-files = ["LICEN[CS]E*"]
23
+ dependencies = [
24
+ "numpy",
25
+ "matplotlib>=3.6.3",
26
+ "string",
27
+ "pandas>=1.5.3"
28
+ ]
29
+
30
+ [tool.hatch.build.targets.sdist]
31
+ include = ["*.py"]
32
+ exclude = ["tmp", "examples"]
33
+
34
+ [project.urls]
35
+ Homepage = "https://github.com/danube-ai/pikaia"
36
+ Issues = "https://github.com/danube-ai/pikaia/issues"
File without changes
@@ -0,0 +1,437 @@
1
+ import numpy as np
2
+
3
+ class Model:
4
+ def __init__(self, inputdata, strategies,
5
+ genelabels=None, orgslabels=None,
6
+ linestyles=None, markerstyles=None):
7
+ self._data = inputdata
8
+ self._strategies = strategies
9
+ self._genelabels = genelabels
10
+ self._orgslabels = orgslabels
11
+ self._linestyles = linestyles
12
+ self._markerstyles = markerstyles
13
+
14
+ @property
15
+ def data(self):
16
+ return self._data
17
+
18
+
19
+ def complete_run(self, initialgenefitness, maxiteration=1, epsilon=None, silent=False):
20
+ """Runs an evolutionary simulation.
21
+
22
+ Runs a evolutionary simulation until either maxiter
23
+ is reached or the difference of gene fitness values
24
+ from two consecutive iterations are below epsilon.
25
+
26
+ Args:
27
+ initialgenefitness (vector of shape `(1, m)`):
28
+ The initial fitness used in this simulation.
29
+
30
+ Returns:
31
+ the similarity matrix `(n, n)`,
32
+ """
33
+ self._maxiter = maxiteration
34
+ self._epsilon = epsilon
35
+ genefitness = initialgenefitness
36
+ print("initial gene fitness = ", genefitness)
37
+ initialorganismfitness = compute_all_organism_fitness(self.data.population, genefitness)
38
+ if not silent:
39
+ print("initial organism fitness = ", initialorganismfitness)
40
+
41
+ #iter = self._maxiter
42
+ n = self.data.population.shape[0]
43
+ m = self.data.population.shape[1]
44
+ # helper structures storing iterations
45
+ self._gps = np.zeros([self._maxiter+1, m])
46
+ self._ofs = np.zeros([self._maxiter+1, n])
47
+ self._gps[0,:] = genefitness
48
+ self._ofs[0,:] = initialorganismfitness
49
+
50
+ # compute similarities necessary for some strategies
51
+ genesimilarity = compute_gene_similarity(self.data.population)
52
+ orgsimilarity = compute_organism_similarity(self.data.population)
53
+
54
+ for k in range(0, self._maxiter):
55
+ if not silent:
56
+ print("+++ Iteration ", k," +++")
57
+ genefitness, fitness = iteration(self.data.population, genefitness,
58
+ genesimilarity, orgsimilarity,
59
+ self._strategies, silent=silent)
60
+ self._gps[k+1,:] = genefitness
61
+ self._ofs[k+1,:] = fitness
62
+ if not silent:
63
+ print("gene fitness = ", genefitness)
64
+ print("organism fitness = ", fitness)
65
+ delta = np.linalg.norm(self._gps[k+1,:]-self._gps[k,:])
66
+ self._iterESE = k + 1
67
+ if self._epsilon is not None and delta < self._epsilon:
68
+ print("reached ESE after ", self._iterESE, "iterations. Final delta = ", delta)
69
+ break
70
+ return genefitness, fitness
71
+
72
+ class Population:
73
+ def __init__(self, rawdata, gvfitnessrule):
74
+ self._rawdata = rawdata
75
+ self._n = self._rawdata.shape[0] #number of organisms
76
+ self._m = self._rawdata.shape[1] #number of genes
77
+ self._gvfitnessrules = gvfitnessrule
78
+ self._population = np.zeros([self._n,self._m])
79
+ for j in range(0, self._m):
80
+ # the populations derive from the raw data by applying gene variant
81
+ # fitness functions
82
+ self._population[:,j] = compute_gene_variant_fitness(self._rawdata[:,j],
83
+ self._gvfitnessrules[j])
84
+ print("Population = ", self._population)
85
+
86
+ @property
87
+ def n(self):
88
+ return self._n
89
+
90
+ @property
91
+ def m(self):
92
+ return self._m
93
+
94
+ @property
95
+ def rawdata(self):
96
+ return self._rawdata
97
+
98
+ @property
99
+ def population(self):
100
+ return self._population
101
+
102
+ def get_uniform_gene_fitness(self):
103
+ genefitness = np.zeros([self._m])
104
+ for j in range(0, self._m):
105
+ genefitness[j] = 1.0/self._m
106
+ return genefitness
107
+
108
+
109
+ def compute_gene_variant_fitness(genedata, gvfrule):
110
+ """Computes the gene variant fitness from raw data.
111
+
112
+ Args:
113
+ genedata (vector of shape `(n, 1)`):
114
+ The input data.
115
+ gvrule:
116
+ The gene variant fitness function applied.
117
+
118
+ Returns:
119
+ A float, the gene variant fitness.
120
+ """
121
+ if "inv" in gvfrule:
122
+ invert = True
123
+ else:
124
+ invert = False
125
+
126
+ if "cap" in gvfrule:
127
+ cap = True
128
+ else:
129
+ cap = False
130
+ n = len(genedata)
131
+ maxvariant = max(genedata)
132
+ minvariant = min(genedata)
133
+ gvfitness = np.zeros(n)
134
+ for i in range(0, n):
135
+ if invert:
136
+ if cap:
137
+ gvfitness[i] = (maxvariant - genedata[i])/(maxvariant-minvariant) if (maxvariant-minvariant) != 0 else 0
138
+ else:
139
+ gvfitness[i] = (maxvariant - genedata[i])/maxvariant if maxvariant > 0 else 0
140
+ else:
141
+ if cap:
142
+ gvfitness[i] = genedata[i]/(maxvariant-minvariant) if maxvariant > 0 else 0
143
+ else:
144
+ gvfitness[i] = genedata[i]/maxvariant if maxvariant > 0 else 0
145
+ return gvfitness
146
+
147
+
148
+ def compute_organism_fitness(organism, genefitness):
149
+ """Computes the (linear) organism fitness.
150
+
151
+ Dot product of organism and genefitness.
152
+
153
+ Args:
154
+ organism (vector of shape `(1, m)`):
155
+ The gene variant fitness values of a single organism.
156
+ genefitness (vector of shape `(1, m)`):
157
+ The list of gene fitness values.
158
+
159
+ Returns:
160
+ A float, the linear organism fitness.
161
+ """
162
+ ofitness = 0.0
163
+ for j in range(0, len(organism)):
164
+ ofitness += organism[j]*genefitness[j]
165
+ return ofitness
166
+
167
+ def compute_all_organism_fitness(population, genefitness):
168
+ """Computes the (linear) organism fitness values for a population.
169
+
170
+ Args:
171
+ population (matrix of shape `(n, m)`):
172
+ The population data in matrix form, rows are organisms,
173
+ colums genes.
174
+ genefitness (vector of shape `(1, m)`):
175
+ The list of gene fitness values.
176
+
177
+ Returns:
178
+ A vector of shape `(n, 1)`, the list of fitness values
179
+ of the organisms in the population.
180
+ """
181
+ n = population.shape[0]
182
+ populationfitness = np.zeros(n)
183
+ for i in range(0, n):
184
+ populationfitness[i] = compute_organism_fitness(population[i,:], genefitness)
185
+ return populationfitness
186
+
187
+
188
+ def compute_gene_deltas(geneindex, genevariantfitness, gene, organism, genefitness,
189
+ genesimilarity, strategy="GS Dominant"):
190
+ """Computes delta values stemming from the gene strategy.
191
+
192
+ Args:
193
+ geneindex (integer):
194
+ The index j for which gene index the delta is to be computed.
195
+ genevariantfitness (float):
196
+ the gene variant fitness at i,j.
197
+ gene (float vector of shape `(1, n)`):
198
+ the current gene vector.
199
+ organism (float vector of shape `(m, 1)`):
200
+ the current organism vector.
201
+ genefitness (float vector of shape `(1, m)`):
202
+ The list of gene fitness values.
203
+ genesimilarity (float vector of shape `(1, m)`):
204
+ The kinship of the current gene to the others.
205
+ strategy (string):
206
+ The evolutionary strategy to be applied.
207
+
208
+ Returns:
209
+ A float, the Delta(i,j) value for a particular gene
210
+ and organism, respectively.
211
+ """
212
+ n = len(gene)
213
+ m = len(organism)
214
+ if strategy == "GS Dominant":
215
+ deltaG = 1/float(n)*genefitness[geneindex]*(genevariantfitness-1/2)*2
216
+ elif strategy == "GS Selfish":
217
+ deltaG = 0
218
+ for j in range(0, m):
219
+ if j == geneindex:
220
+ continue
221
+ deltaG += -1/float(n)*genesimilarity[j]*genefitness[geneindex]*\
222
+ (genevariantfitness-1/2)*(organism[j] - genevariantfitness)
223
+ deltaG = deltaG/float(m)
224
+ elif strategy == "GS Kin-Altruistic":
225
+ deltaG = 0
226
+ for j in range(0, m):
227
+ if j == geneindex:
228
+ continue
229
+ deltaG += 1/float(n)*(0.5-genesimilarity[j])*genefitness[geneindex]*\
230
+ (genevariantfitness-1/2)*(organism[j] - genevariantfitness)
231
+ deltaG = deltaG/float(m)
232
+ elif strategy == "GS Altruistic":
233
+ deltaG = 0
234
+ for j in range(0, m):
235
+ if j == geneindex:
236
+ continue
237
+ deltaG += 1/float(n)*genesimilarity[j]*genefitness[geneindex]*(genevariantfitness-1/2)*\
238
+ genefitness[j]*(organism[j] - genevariantfitness)
239
+ deltaG = deltaG/float(m)
240
+ elif strategy == "GS None":
241
+ deltaG = 0
242
+ else:
243
+ raise NameError("Unknown gene strategy:" + strategy)
244
+
245
+ return deltaG
246
+
247
+ def compute_organism_deltas(orgindex, population, genefitness,
248
+ orgfitness, orgsimilarity, strategy="OS Balance"):
249
+ """Computes delta values stemming from the organism strategy.
250
+
251
+ Args:
252
+ orgindex (integer):
253
+ The index i of the current organism.
254
+ population (matrix of shape `(n, m)`):
255
+ The full population matrix obtained by applying the
256
+ gene variant fitness functions to the input data.
257
+ genefitness (float vector of shape `(1, m)`):
258
+ The list of gene fitness values.
259
+ orgfitness (float vector of shape `(n, 1)`):
260
+ The list of organism fitness values.
261
+ orgsimilarity (float vector of shape `(n, 1)`):
262
+ The kinship of the current organism to the others.
263
+ strategy (string):
264
+ The evolutionary strategy to be applied.
265
+
266
+ Returns:
267
+ A vector shape `(0, m)` containing the changes to the gene
268
+ fitness for the particular organism.
269
+ """
270
+ n = population.shape[0]
271
+ m = population.shape[1]
272
+
273
+ deltaO = np.zeros(m)
274
+ if strategy == "OS Balanced":
275
+ for j in range(0, m):
276
+ genecontribution = population[orgindex, j]*genefitness[j]
277
+ if orgfitness[orgindex] == 0:
278
+ deltaO[j] = 0
279
+ else:
280
+ deltaO[j] = -1/n*(genecontribution/orgfitness[orgindex] - 1/m)*orgfitness[orgindex]
281
+ elif strategy == "OS Altruistic":
282
+ for i in range(0, n):
283
+ if i == orgindex:
284
+ continue
285
+
286
+ for j in range(0, m):
287
+ genecontribution = population[orgindex, j]*genefitness[j]
288
+ if orgfitness[orgindex] == 0:
289
+ deltaO[j] = 0
290
+ else:
291
+ deltaO[j] += -1/n*orgsimilarity[i]*(genecontribution/orgfitness[orgindex] - 1/m)*\
292
+ (1/n)*(orgfitness[orgindex]-orgfitness[i])
293
+ elif strategy == "OS Kin-Selfish":
294
+ for i in range(0, n):
295
+ if i == orgindex:
296
+ continue
297
+
298
+ for j in range(0, m):
299
+ genecontribution = population[orgindex, j]*genefitness[j]
300
+ if orgfitness[orgindex] == 0:
301
+ deltaO[j] = 0
302
+ else:
303
+ deltaO[j] += 1/n*(0.5-orgsimilarity[i])*(genecontribution/orgfitness[orgindex] - 1/m)*\
304
+ (1/n)*(orgfitness[orgindex]-orgfitness[i])
305
+
306
+ elif strategy == "OS Selfish":
307
+ for i in range(0, n):
308
+ if i == orgindex:
309
+ continue
310
+
311
+ for j in range(0, m):
312
+ genecontribution = population[orgindex, j]*genefitness[j]
313
+ if orgfitness[orgindex] == 0:
314
+ deltaO[j] = 0
315
+ else:
316
+ deltaO[j] += -1/n*orgsimilarity[i]*(genecontribution/orgfitness[orgindex] - 1/m)*\
317
+ (1/n)*(orgfitness[orgindex]-orgfitness[i])
318
+ elif strategy == "OS None":
319
+ pass
320
+ else:
321
+ raise NameError("Unknown organism strategy:" + strategy)
322
+ return deltaO
323
+
324
+ def iteration(population, genefitness, genesimilarity,
325
+ orgsimilarity, strategy, silent=False):
326
+ """Runs a single, evolutionary step.
327
+
328
+ One iteration takes a given population and gene fitness,
329
+ calculates a new gene and organism fitness.
330
+
331
+ Args:
332
+ population (matrix of shape `(n, m)`):
333
+ The population data in matrix form, rows are organisms,
334
+ colums genes.
335
+ genefitness (vector of shape `(1, m)`):
336
+ The list of gene fitness values.
337
+ genesimilarity (matrix of shape `(m, m)`):
338
+ The symmetric gene kinship matrix.
339
+ orgsimilarity (matrix of shape `(n, n)`):
340
+ The symmetric organism kinship matrix.
341
+ strategy (string vector of length 2):
342
+ Defines the used gene and organism strategy, respectively.
343
+
344
+ Returns:
345
+ Two vectors, first, the new gene fitness of shape `(1, m)`,
346
+ second, the organism fitness values of shape `(n, 1)`
347
+ """
348
+ n = population.shape[0]
349
+ m = population.shape[1]
350
+
351
+ # get Delta contributions to gene fitness updates
352
+ deltaGs = np.zeros([n,m])
353
+ deltaOs = np.zeros([n,m])
354
+ orgfitness = np.zeros([n,1])
355
+ for i in range(0, n):
356
+ orgfitness[i] = compute_organism_fitness(population[i,:], genefitness)
357
+
358
+ for i in range(0, n):
359
+ deltaOs[i,:] = compute_organism_deltas(i, population, genefitness, orgfitness, orgsimilarity[i,:], strategy[1])
360
+ for j in range(0, m):
361
+ deltaGs[i,j] = compute_gene_deltas(j, population[i,j], population[:,j], population[i,:], genefitness,
362
+ genesimilarity[j,:], strategy[0])
363
+ if not silent:
364
+ print("deltaGs = ", deltaGs)
365
+ print("deltaOs = ", deltaOs)
366
+ deltas = deltaGs + deltaOs
367
+ deltaG = np.sum(deltaGs,axis=0)
368
+ deltaO = np.sum(deltaOs,axis=0)
369
+ delta = np.sum(deltas,axis=0)
370
+ if not silent:
371
+ print("deltaG = ", deltaG)
372
+ print("deltaO = ", deltaO)
373
+ print("delta = ", delta)
374
+
375
+ # apply replicator equations
376
+ newgenefitness = np.zeros(m)
377
+ for j in range(0, m):
378
+ newgenefitness[j] = genefitness[j]*(1+delta[j])
379
+ sumfitness = sum(newgenefitness)
380
+ for j in range(0, m):
381
+ newgenefitness[j] /= sumfitness
382
+ neworganismfitness = compute_all_organism_fitness(population, newgenefitness)
383
+
384
+ return newgenefitness, neworganismfitness
385
+
386
+ def compute_gene_similarity(population):
387
+ """Computes the similarity/kinship matrix for genes.
388
+
389
+ Args:
390
+ population (matrix of shape `(n, m)`):
391
+ The population data in matrix form, rows are organisms,
392
+ colums genes.
393
+
394
+ Returns:
395
+ the similarity matrix `(m, m)`,
396
+ """
397
+ n = population.shape[0]
398
+ m = population.shape[1]
399
+ genediversity = np.zeros([m,m])
400
+ for j in range(0,m):
401
+ for l in range(0,m):
402
+ tmp = np.linalg.norm(population[:,j] - population[:,l])
403
+ genediversity[j,l] += tmp
404
+
405
+ genesimilarity = 1 - genediversity/float(n)
406
+ print("Gene similarity = ")
407
+ print(genesimilarity)
408
+ return genesimilarity
409
+
410
+ def compute_organism_similarity(population):
411
+ """Computes the similarity/kinship matrix for organisms.
412
+
413
+ Args:
414
+ population (matrix of shape `(n, m)`):
415
+ The population data in matrix form, rows are organisms,
416
+ colums genes.
417
+
418
+ Returns:
419
+ the similarity matrix `(n, n)`,
420
+ """
421
+ n = population.shape[0]
422
+ m = population.shape[1]
423
+ orgdiversity = np.zeros([n,n])
424
+ for i in range(0, n):
425
+ for l in range(0, n):
426
+ tmp = np.linalg.norm(population[i,:] - population[l,:])
427
+ orgdiversity[i,l] += tmp
428
+
429
+ orgsimilarity = 1 - orgdiversity/float(m)
430
+ print("Organism similarity = ")
431
+ print(orgsimilarity)
432
+ return orgsimilarity
433
+
434
+
435
+
436
+
437
+
@@ -0,0 +1,107 @@
1
+ import numpy as np
2
+ import string
3
+
4
+ import pikaia.alg
5
+
6
+
7
+ rawdata3x3 = np.zeros([3,3])
8
+ rawdata3x3[:,:] = [[ 300, 10, 2],
9
+ [ 600, 5, 2],
10
+ [1500, 4, 1]]
11
+
12
+ rawdata10x5 = np.zeros([10,5])
13
+ rawdata10x5[:,:] = [[300, 10 , 2, 0, 2.5],
14
+ [600, 5, 2, 1, 3.0],
15
+ [1500, 4, 1, 2, 4.0],
16
+ [400, 8, 2, 0, 3.5],
17
+ [500, 8, 2, 1, 3.0],
18
+ [700, 5, 2, 1, 4.5],
19
+ [900, 6, 1, 1, 4.0],
20
+ [1100, 6, 1, 2, 3.5],
21
+ [1300, 5, 2, 2, 5.0],
22
+ [1700, 4, 1, 2, 5.0]]
23
+
24
+ class Example:
25
+
26
+ def __init__(self, inputdata, genelabels=None, orgslabels=None, labelpostfix=None):
27
+ self._data = inputdata
28
+ self._genelabelsbase = genelabels
29
+ if self._genelabelsbase is None:
30
+ self._genelabels = self._genelabelsbase
31
+ else:
32
+ self._genelabels = []
33
+ for k, pf in enumerate(labelpostfix):
34
+ # import pdb; pdb.set_trace()
35
+ self._genelabels.append([])
36
+ for j in range(0, self._data.m):
37
+ self._genelabels[k].append(self._genelabelsbase[j] + pf)
38
+
39
+ self._orgslabelsbase = orgslabels
40
+ if self._orgslabelsbase is None:
41
+ self._orgslabels = self._orgslabelsbase
42
+ else:
43
+ self._orgslabels = []
44
+ for k, pf in enumerate(labelpostfix):
45
+ self._orgslabels.append([])
46
+ for i in range(0, self._data.n):
47
+ self._orgslabels[k].append(self._orgslabelsbase[i] + pf)
48
+
49
+ @property
50
+ def exampledata(self):
51
+ return self._data
52
+
53
+ def get_gene_labels(self, set=0):
54
+ return self._genelabels[set]
55
+
56
+ def get_org_labels(self, set=0):
57
+ return self._orgslabels[set]
58
+
59
+
60
+ def assemble_example(specifier):
61
+ """Returns a setup ready for modelling.
62
+
63
+ Returns a datastructure for Genetic Ai including
64
+ model and ploting parameters.
65
+
66
+ Args:
67
+ specifier (string):
68
+ Giving '3x3-DomBal+AltSal' returns a small example
69
+ with n=3 and m=3;
70
+ Giving '10x5-DomBal+AltSal' returns a larger example
71
+ with n=10 and m=5.
72
+
73
+
74
+ Returns:
75
+ an Example data structure.
76
+ """
77
+ if specifier == "3x3-DomBal+AltSal":
78
+
79
+ gvfitnessrules = ["inv_percentage", "inv_percentage", "inv_percentage"]
80
+ inputdata = pikaia.alg.Population(rawdata3x3, gvfitnessrules)
81
+
82
+ # defining plotting labels
83
+ genelabels = ["gene 1 = price", "gene 2 = time", "gene 3 = stops"]
84
+ orgslabels = []
85
+ for i in range(0, inputdata.n):
86
+ orgslabels.append("flight " + string.ascii_uppercase[i])
87
+ labelpostfixes = ["(DomBal)", "(AltSel)"]
88
+
89
+ return Example(inputdata, genelabels, orgslabels, labelpostfixes)
90
+
91
+ if specifier == "10x5-DomBal+AltSal":
92
+
93
+ gvfitnessrules = ["inv_percentage", "inv_percentage", "inv_percentage",
94
+ "percentage", "percentage"]
95
+ inputdata = pikaia.alg.Population(rawdata10x5, gvfitnessrules)
96
+ genelabels = ["gene 1 = price", "gene 2 = time", "gene 3 = stops",
97
+ "gene 4 = luggage", "gene 5 = rating"]
98
+ orgslabels = []
99
+ for i in range(0, inputdata.n):
100
+ orgslabels.append("flight " + string.ascii_uppercase[i])
101
+ labelpostfixes = ["(DomBal)", "(AltSel)"]
102
+
103
+ return Example(inputdata, genelabels, orgslabels, labelpostfixes)
104
+
105
+
106
+ else:
107
+ raise ValueError("Unknown example specifier")
@@ -0,0 +1,162 @@
1
+ import matplotlib.pyplot as plt
2
+ import matplotlib
3
+ import numpy as np
4
+ import string
5
+
6
+ import pikaia.alg
7
+
8
+ def initialize_plotting_variables():
9
+ """Initialize styles and markers for plotting.
10
+
11
+
12
+ Returns:
13
+ linestyles (list of linestyle specifiers).
14
+ two sets of markers (list of strings).
15
+ """
16
+
17
+ matplotlib.rcParams['pdf.fonttype'] = 42
18
+ matplotlib.rcParams['ps.fonttype'] = 42
19
+ linestyle_str = [
20
+ ('solid', 'solid'), # Same as (0, ()) or '-'
21
+ ('dotted', 'dotted'), # Same as ':'
22
+ ('dashed', 'dashed'), # Same as '--'
23
+ ('dashdot', 'dashdot')] # Same as '-.'
24
+ linestyle_tuple = [
25
+ ('densely dashdotted', (0, (3, 1, 1, 1))),
26
+ ('dashdotted', (0, (3, 5, 1, 5))),
27
+ ('loosely dotted', (0, (1, 10))),
28
+ ('dotted', (0, (1, 5))),
29
+ ('densely dotted', (0, (1, 1))),
30
+ ('long dash with offset', (5, (10, 3))),
31
+ ('loosely dashed', (0, (5, 10))),
32
+ ('dashed', (0, (5, 5))),
33
+ ('densely dashed', (0, (5, 1))),
34
+ ('loosely dashdotted', (0, (3, 10, 1, 10))),
35
+ ('dashdotdotted', (0, (3, 5, 1, 5, 1, 5))),
36
+ ('loosely dashdotdotted', (0, (3, 10, 1, 10, 1, 10))),
37
+ ('densely dashdotdotted', (0, (3, 1, 1, 1, 1, 1)))]
38
+ linestyles = []
39
+ for i, (name, linestyle) in enumerate(linestyle_str):
40
+ linestyles.append(linestyle)
41
+ for i, (name, linestyle) in enumerate(linestyle_tuple):
42
+ linestyles.append(linestyle)
43
+ markerstylesSet1 = ["o","^","s", ">", "p", "X", ".", "*", "1", "3"]
44
+ markerstylesSet2 = ["x","v", "D","<", "P", "h", "8", "d", "2", "4"]
45
+ return linestyles, markerstylesSet1, markerstylesSet2
46
+
47
+ def plot_gene_fitness(modellist, figurenr, show=True, savename=None,
48
+ fontsize=12, linewidth=3):
49
+ """Plots the gene fitness values over iterations.
50
+
51
+ Args:
52
+ modellist (List of geneticai models):
53
+ List of geneticai models to be plotted.
54
+ figurenr (integer):
55
+ The figure window specifier.
56
+ show (boolean):
57
+ Whether the finished plots are shown.
58
+ savename (String):
59
+ Whether the finished plots are saved as png and pdf.
60
+ fontsize (integer):
61
+ Fontsizes in the plots
62
+ linewidth (integer):
63
+ Linewidths in the plots
64
+
65
+
66
+ """
67
+
68
+ plt.figure(figurenr)
69
+ plt.rcParams.update({'font.size': fontsize})
70
+
71
+ maxpiter = 0
72
+ for model in modellist:
73
+ if model._iterESE > maxpiter:
74
+ maxpiter = model._iterESE
75
+
76
+ if maxpiter > 20:
77
+ inc = round(maxpiter/10)
78
+ plt.xticks(range(0,maxpiter, inc))
79
+ else:
80
+ inc = 5
81
+ plt.xticks(range(0,maxpiter))
82
+
83
+
84
+ for model in modellist:
85
+ n = model._gps.shape[1]
86
+ for j in range(0, n):
87
+ plt.plot(range(0, model._iterESE+1), model._gps[:model._iterESE+1,j],
88
+ label=model._genelabels[j], linestyle=model._linestyles[j],
89
+ marker=model._markerstyles[j], markersize=8, markevery=inc,
90
+ lw=linewidth)
91
+ plt.ylabel('gene fitness [%]')
92
+ plt.xlabel('iterations')
93
+
94
+
95
+ plt.xlim(0,maxpiter)
96
+ plt.legend(handlelength=5, fontsize=10)
97
+ if savename is not None:
98
+ plt.savefig(savename + ".pdf", format="pdf", bbox_inches="tight")
99
+ plt.savefig(savename + ".png", format="png", bbox_inches="tight")
100
+ if show:
101
+ plt.show()
102
+
103
+ def plot_organism_fitness(modellist, figurenr, maxitershown, show=True, savename=None,
104
+ fontsize=12, linewidth=3):
105
+
106
+ """Plots the organism fitness values over iterations.
107
+
108
+ Args:
109
+ modellist (List of geneticai models):
110
+ List of geneticai models to be plotted.
111
+ figurenr (integer):
112
+ The figure window specifier.
113
+ maxitershown (integer):
114
+ The maximum iteration shown in the plot.
115
+ show (boolean):
116
+ Whether the finished plots are shown.
117
+ savename (String):
118
+ Whether the finished plots are saved as png and pdf.
119
+ fontsize (integer):
120
+ Fontsizes in the plots
121
+ linewidth (integer):
122
+ Linewidths in the plots
123
+
124
+
125
+ """
126
+ plt.figure(figurenr)
127
+ plt.rcParams.update({'font.size': fontsize})
128
+
129
+
130
+ maxpiter = 0
131
+ for model in modellist:
132
+ if model._iterESE > maxpiter:
133
+ maxpiter = model._iterESE
134
+
135
+ if maxitershown is not None and maxitershown < maxpiter:
136
+ maxpiter = maxitershown
137
+
138
+ if maxpiter > 20:
139
+ inc = round(maxpiter/10)
140
+ plt.xticks(range(0,maxpiter, inc))
141
+ else:
142
+ inc = 5
143
+ plt.xticks(range(0,maxpiter))
144
+
145
+
146
+ for model in modellist:
147
+ n = model._ofs.shape[1]
148
+ for j in range(0, n):
149
+ plt.plot(range(0, model._iterESE+1), model._ofs[:model._iterESE+1,j],
150
+ label=model._orgslabels[j], linestyle=model._linestyles[j],
151
+ marker=model._markerstyles[j], markersize=8, markevery=inc,
152
+ lw=linewidth)
153
+ plt.ylabel('organism fitness [%]')
154
+ plt.xlabel('iterations')
155
+
156
+ plt.xlim(0,maxpiter)
157
+ plt.legend(handlelength=5, fontsize=10)
158
+ if savename is not None:
159
+ plt.savefig(savename + ".pdf", format="pdf", bbox_inches="tight")
160
+ plt.savefig(savename + ".png", format="png", bbox_inches="tight")
161
+ if show:
162
+ plt.show()
@@ -0,0 +1,116 @@
1
+ import datetime
2
+
3
+ import numpy as np
4
+
5
+ import pikaia as gai
6
+ import pikaia.alg
7
+
8
+ class Search:
9
+ """
10
+ Class that performs a string-based search over a data matrix
11
+ using Genetic AI.
12
+ """
13
+
14
+ def __init__(self, input_data, orgs_labels, gene_labels):
15
+ self._data = input_data
16
+ self._gene_labels = gene_labels
17
+ self._orgs_labels = orgs_labels
18
+
19
+ def search_request(self, query: str, top_k: int = None, silent=False) -> list:
20
+ """
21
+ Performs a search request on the data given a query string.
22
+
23
+ Args:
24
+ query (str): a string containing comma-separated keywords.
25
+ top_k (int): how many top-ranked organisms to return.
26
+
27
+ Returns:
28
+ Tuple[list, list] containing the organism and gene fitness
29
+ values at the end of the simulation.
30
+ """
31
+ # Process query
32
+ split_query = [f.strip().lower() for f in query.split(",")]
33
+ query_features = [f for f in split_query if f in self._gene_labels]
34
+ query_feature_ids = [self._gene_labels.index(f) for f in query_features]
35
+ query_year = next((int(f) for f in split_query if is_year(f)), 0)
36
+
37
+ # For testing purposes set row cutoff
38
+ cutoff = None
39
+ if cutoff is None:
40
+ cutoff = self._data.shape[0]
41
+
42
+ # Get the relevant colum for each feature and create a new matrix
43
+ data_subset = self._data[:cutoff, query_feature_ids]
44
+
45
+ # Use percentage rule for all features except year
46
+ gvfitnessrules = ["percentage"] * len(query_features)
47
+
48
+ # Get relevant columns from matrix
49
+ if query_year > 0:
50
+ # Convert year column -> calculate the distance to the year in the query
51
+ year_col = abs(self._data[:cutoff, self._gene_labels.index("year")] - query_year)
52
+
53
+ # Add to the data_subset
54
+ data_subset = np.column_stack((data_subset, year_col))
55
+
56
+ # Special handling for year
57
+ gvfitnessrules += ["inv_percentage"]
58
+
59
+ # Print logs
60
+ print("Selected features:", query_features, f"(ids: {query_feature_ids})")
61
+ print("Number of non-zero values per selected feature:")
62
+ for f_id, f in zip(query_feature_ids, query_features):
63
+ print(f" - {f}:", len([w for w in self._data[:, f_id] if w != 0]))
64
+
65
+ print(f"Subset of data matrix (shape: {data_subset.shape})\n", data_subset)
66
+
67
+ # Convert raw data to population
68
+ population = pikaia.alg.Population(data_subset, gvfitnessrules)
69
+
70
+ strategy = ["GS Dominant", "OS Balanced"]
71
+ # Use this for AltSel strategies (reduce maxiter for performance)
72
+ # strategy = ["GS Altruistic", "OS Selfish"]
73
+
74
+ # Initialize model
75
+ model = gai.alg.Model(population, strategy)
76
+
77
+ # Start with a uniform distribution
78
+ n_features = data_subset.shape[1]
79
+ initialgenefitness = [1 / n_features] * n_features
80
+
81
+ # Run simulation
82
+ e = 0.00005
83
+ gene_fitness, fitness = model.complete_run(initialgenefitness, maxiteration=100,
84
+ epsilon=e, silent=silent)
85
+
86
+ # Function to get all non-zero features for a selected organism
87
+ def get_features(organism):
88
+ org_index = self._orgs_labels.index(organism)
89
+ return [f_name for f_weight, f_name in zip(self._data[org_index,:], self._gene_labels)
90
+ if f_weight != 0]
91
+
92
+ # Function to get the feature values for a selected organism
93
+ def get_feature_values(organism):
94
+ org_index = self._orgs_labels.index(organism)
95
+ return [(f_name, f_weight)
96
+ for f_weight, f_name in zip(data_subset[org_index,:], query_features)]
97
+
98
+ # Sort organisms with their corresponding fitness value
99
+ org_fitness = list(sorted(
100
+ [(org_label, fit, get_feature_values(org_label))
101
+ for org_label, fit in zip(self._orgs_labels, fitness)],
102
+ key=lambda x: x[1],
103
+ reverse=True
104
+ ))
105
+
106
+ gene_fitness_labels = [(l, f) for l, f in zip(query_features, gene_fitness)]
107
+ # Return the top_k fittest organisms
108
+ return org_fitness[:top_k], gene_fitness_labels
109
+
110
+ def is_year(s):
111
+ """Checks if a string is a year number."""
112
+ try:
113
+ year = int(s)
114
+ except ValueError:
115
+ return False
116
+ return year >= 1890 and year <= datetime.date.today().year + 1