Evolife
Evolife has been developed to study Genetic algorithms, Natural evolution and behavioural ecology.
DNA.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2""" @brief Genomes in EVOLIFE are defined as a binary strings.
3
4 The way the genome string is implemented (e.g. list of binary numbers
5 or bits compacted into integers) should remain private to this module """
6
7#============================================================================#
8# EVOLIFE http://evolife.telecom-paris.fr Jean-Louis Dessalles #
9# Telecom Paris 2022-04-11 www.dessalles.fr #
10# -------------------------------------------------------------------------- #
11# License: Creative Commons BY-NC-SA #
12#============================================================================#
13# Documentation: https://evolife.telecom-paris.fr/Classes #
14#============================================================================#
15
16
17
20
21
22import sys
23if __name__ == '__main__':
24 sys.path.append('../..') # for tests
25 from Evolife.Scenarii.MyScenario import InstantiateScenario
26 InstantiateScenario('Cooperation','../Evolife')
27
28import random
29# try: import numpy; NUMPY = True; print('Loading Numpy')
30# except ImportError: NUMPY = False
31NUMPY=False # slower with Numpy !!
32
33from Evolife.Tools import Tools
34
35class DNA:
36 """ class DNA: individuals' 'DNA' defined as a string of bits
37 """
38
39 def __init__(self, Scenario, Nb_nucleotides):
40 self.Scenario = Scenario
41 self.nb_nucleotides = Nb_nucleotides
42 self.__dna = []
43 Fill = self.Scenario.Parameter('DNAFill', Default=-1) # 0 or 1 or -1=random
44 for pos in range(self.nb_nucleotides):
45 if (Fill==1): self.__dna.append(1)
46 elif (Fill==0): self.__dna.append(0)
47 else: self.__dna.append(random.randint(0,1))
48 if NUMPY: self.__dna = numpy.array(self.__dna) # doesn't seem to be very efficient !
49
50 def DNAfill(self, Nucleotides):
51 """ fills the DNA with given Nucleotides
52 """
53 self.__dna = Nucleotides[:] # important: make a ground copy
54 if len(Nucleotides) > 0 and len(Nucleotides) != self.nb_nucleotides:
55 Tools.error('DNA: initialization','Provided genome length does not match gene map')
56 if len(Nucleotides) > 0 and not set(Nucleotides) <= set([0,1]):
57 Tools.error('DNA: initialization','Provided genome is not binary')
58
59 def hybrid(self, mother, father, number_crossover = -1):
60 """ builds the child's DNA from the parents' DNA
61 """
62 # computing random crossover points
63 if number_crossover < 0: number_crossover = self.Scenario.Parameter('NbCrossover')
64 if self.nb_nucleotides > 1:
65 Loci_crossover = random.sample(range(1,self.nb_nucleotides), number_crossover)
66 Loci_crossover = [0] + sorted(Loci_crossover)
67 else:
68 Loci_crossover = [0]
69 Loci_crossover.append(self.nb_nucleotides)
70 # print Loci_crossover
71 # the child's DNA will be read alternatively from parent1 and parent2
72 parent1 = mother.__dna
73 parent2 = father.__dna
74 if random.randint(0,1): # starting indifferently from mother or father
75 parent1, parent2 = parent2, parent1 # swapping parents
76 self.__dna = []
77 for cut_point in range(len(Loci_crossover)-1):
78 self.__dna += list(parent1[Loci_crossover[cut_point]:Loci_crossover[cut_point+1]])
79 parent1, parent2 = parent2, parent1 # swapping parents
80 if NUMPY: self.__dna = numpy.array(self.__dna)
81
82 def mutate(self, mutation_rate = -1):
83 """ computing the expected number of mutations
84 """
85 if mutation_rate < 0: mutation_rate = self.Scenario.Parameter('MutationRate')
86 mutation_number = Tools.chances(mutation_rate/1000.0, self.nb_nucleotides)
87
91 for mutation in range(mutation_number):
92 pos = random.randint(0, self.nb_nucleotides - 1)
93 self.__dna[pos] = 1 - self.__dna[pos]
94 return mutation_number
95
96 def read_DNA(self, start, end, coding = None):
97 """ reads a chunk of DNA
98 """
99 if coding == None: coding = self.Scenario.Parameter('GeneCoding')
100 if coding in range(-1,3):
101 # old numeric designation of coding
102 coding = ['Nocoding', 'Weighted', 'Unweighted', 'Gray'][coding+1]
103 value = 0
104 coding = coding.lower()
105 if coding == 'nocoding':
106 return 0
107 if coding not in ['weighted', 'unweighted', 'gray']:
108 Tools.error("DNA", 'unknown binary coding mode')
109 try:
110 for pos in range(start,end):
111 if coding == 'unweighted':
112 value += self.__dna[pos]
113 else: # Weighted or Gray
114
115 value += (self.__dna[pos] << (end - 1 - pos))
116 if coding == 'gray':
117 value = Tools.GrayTable.Gray2Int(value)
118 return(value)
119 except IndexError:
120 Tools.error("DNA", "reading outside the DNA")
121
122 def hamming(self, alter):
123 """ computes the Hamming distance between two DNA strings
124 """
125 distance = 0
126 for pos in range(self.nb_nucleotides):
127 distance += (self.__dna[pos] != alter.__dna[pos])
128 return distance
129
130 def get_DNA(self):
131 """ returns DNA as a tuple
132 """
133 return tuple(self.__dna)
134
135 def __str__(self, compact=0):
136 if compact:
137 return str(sum(self.__dna))
138 # printing bits separated by "-"
139 return "-".join(["%s" %pos for pos in self.__dna])
140
141 def display(self):
142 pass
143
144 def save(self):
145 pass
146
147
148if __name__ == "__main__":
149 print(__doc__)
150 print(DNA.__doc__ + '\n')
151 mother = DNA(9, Blank=False)
152 print('mother: ')
153 print(mother)
154 father = DNA(9, Blank=False)
155 print('father: ')
156 print(father)
157 child = DNA(9)
158 child.hybrid(mother,father,2)
159 print('child: ')
160 print(child)
161 child.mutate(80)
162 print('child: ')
163 print(child)
164 print(' (mutated)')
165 print('child\'s value (weighted) :')
166 print(child.read_DNA(0,4, coding = MyScenario.Parameter('Weighted')))
167 print('child\'s value (unweighted):')
168 print(child.read_DNA(0,4, coding = MyScenario.Parameter('Unweighted')))
169 print('distance with mother:')
170 print(child.hamming(mother))
171 print('distance with father:')
172 print(child.hamming(father))
173 raw_input('\n[Return]')
174
175
176
177__author__ = 'Dessalles'
class DNA: individuals' 'DNA' defined as a string of bits
Definition: DNA.py:35
def DNAfill(self, Nucleotides)
fills the DNA with given Nucleotides
Definition: DNA.py:50
def hybrid(self, mother, father, number_crossover=-1)
builds the child's DNA from the parents' DNA
Definition: DNA.py:59
def mutate(self, mutation_rate=-1)
computing the expected number of mutations
Definition: DNA.py:82
def hamming(self, alter)
computes the Hamming distance between two DNA strings
Definition: DNA.py:122
def display(self)
Definition: DNA.py:141
def read_DNA(self, start, end, coding=None)
reads a chunk of DNA
Definition: DNA.py:96
def save(self)
Definition: DNA.py:144
def get_DNA(self)
returns DNA as a tuple
Definition: DNA.py:130