1
|
|
|
# This Python file uses the following encoding: utf-8 |
2
|
|
|
""" |
3
|
|
|
The :mod:`galactic.context` package defines generic classes for using contexts: |
4
|
|
|
|
5
|
|
|
* :class:`Context`: a context is composed by a population and a model |
6
|
|
|
* :class:`Population` a population is a container for individuals |
7
|
|
|
* :class:`Model` a model is a container for attributes |
8
|
|
|
* :class:`Individual` an individual has an identifier and values |
9
|
|
|
* :class:`Attribute` an attribute has a name and a type |
10
|
|
|
""" |
11
|
|
|
|
12
|
|
|
from abc import abstractmethod |
13
|
|
|
from typing import Container, Union, Mapping, Iterator, TypeVar, Generic |
14
|
|
|
|
15
|
|
|
C = TypeVar('C', bound='Context') |
16
|
|
|
""" |
17
|
|
|
Generic subclass of the :class:`Context` class |
18
|
|
|
""" |
19
|
|
|
|
20
|
|
|
P = TypeVar('P', bound='Population') |
21
|
|
|
""" |
22
|
|
|
Generic subclass of the :class:`Population` class |
23
|
|
|
""" |
24
|
|
|
|
25
|
|
|
M = TypeVar('M', bound='Model') |
26
|
|
|
""" |
27
|
|
|
Generic subclass of the :class:`Model` class |
28
|
|
|
""" |
29
|
|
|
|
30
|
|
|
X = TypeVar('X', bound='Individual') |
31
|
|
|
""" |
32
|
|
|
Generic subclass of the :class:`Individual` class |
33
|
|
|
""" |
34
|
|
|
|
35
|
|
|
A = TypeVar('A', bound='Attribute') |
36
|
|
|
""" |
37
|
|
|
Generic subclass of the :class:`Attribute` class |
38
|
|
|
""" |
39
|
|
|
|
40
|
|
|
|
41
|
|
|
# pylint: disable=too-few-public-methods |
42
|
|
|
class Context(Generic[M, P, X, A], Container[Union[X, A]]): |
43
|
|
|
""" |
44
|
|
|
A :class:`Context` handles a model and a population. |
45
|
|
|
|
46
|
|
|
It's possible to access to the context :attr:`population` attribute or to |
47
|
|
|
the context :attr:`model` attribute. |
48
|
|
|
|
49
|
|
|
Example |
50
|
|
|
------- |
51
|
|
|
|
52
|
|
|
>>> from galactic.context.memory import MemoryContext |
53
|
|
|
>>> context = MemoryContext( |
54
|
|
|
... definition={'mybool': bool, 'myint': int}, |
55
|
|
|
... individuals=['0', '1'] |
56
|
|
|
... ) |
57
|
|
|
>>> print(context.population) |
58
|
|
|
['0', '1'] |
59
|
|
|
>>> print(context.model) |
60
|
|
|
{'mybool': <class 'bool'>, 'myint': <class 'int'>} |
61
|
|
|
|
62
|
|
|
It's possible to check if a context is not empty (both :attr:`model` and :attr:`population` |
63
|
|
|
are not empty) using the python builtin :func:`bool` function. |
64
|
|
|
|
65
|
|
|
It's possible to get a readable representation of a context using the python builtin :func:`str` |
66
|
|
|
function. |
67
|
|
|
|
68
|
|
|
Example |
69
|
|
|
------- |
70
|
|
|
|
71
|
|
|
>>> print(context) |
72
|
|
|
{'population': ['0', '1'], 'model': {'mybool': <class 'bool'>, 'myint': <class 'int'>}} |
73
|
|
|
|
74
|
|
|
Example |
75
|
|
|
------- |
76
|
|
|
|
77
|
|
|
>>> bool(context) |
78
|
|
|
True |
79
|
|
|
|
80
|
|
|
Contexts are container for individuals and attributes. It's possible to know if an individual |
81
|
|
|
or an attribute belongs to a context using the python :keyword:`in` keyword. |
82
|
|
|
|
83
|
|
|
Example |
84
|
|
|
------- |
85
|
|
|
|
86
|
|
|
>>> context.model['mybool'] in context |
87
|
|
|
True |
88
|
|
|
>>> context.population['0'] in context |
89
|
|
|
True |
90
|
|
|
|
91
|
|
|
.. versionadded:: 0.0.1 |
92
|
|
|
""" |
93
|
|
|
|
94
|
|
|
@property |
95
|
|
|
@abstractmethod |
96
|
|
|
def population(self) -> P: |
97
|
|
|
""" |
98
|
|
|
Get the population for this context. |
99
|
|
|
|
100
|
|
|
Returns |
101
|
|
|
------- |
102
|
|
|
the underlying population : :class:`P` |
103
|
|
|
|
104
|
|
|
.. versionadded:: 0.0.1 |
105
|
|
|
""" |
106
|
|
|
raise NotImplementedError |
107
|
|
|
|
108
|
|
|
@property |
109
|
|
|
@abstractmethod |
110
|
|
|
def model(self) -> M: |
111
|
|
|
""" |
112
|
|
|
Get the underlying model. |
113
|
|
|
|
114
|
|
|
Returns |
115
|
|
|
------- |
116
|
|
|
the underlying model : :class:`M` |
117
|
|
|
|
118
|
|
|
.. versionadded:: 0.0.1 |
119
|
|
|
""" |
120
|
|
|
raise NotImplementedError |
121
|
|
|
|
122
|
|
|
def __contains__(self, element: Union[X, A]): |
123
|
|
|
""" |
124
|
|
|
Check if an individual or an attribute is in this context. |
125
|
|
|
|
126
|
|
|
Parameters |
127
|
|
|
---------- |
128
|
|
|
element : Union[:class:`X`, :class:`A`] |
129
|
|
|
the element to check |
130
|
|
|
|
131
|
|
|
Returns |
132
|
|
|
------- |
133
|
|
|
the membership of the element to the context : :class:`bool` |
134
|
|
|
|
135
|
|
|
.. versionadded:: 0.0.1 |
136
|
|
|
""" |
137
|
|
|
return element.context == self |
138
|
|
|
|
139
|
|
|
def __bool__(self): |
140
|
|
|
""" |
141
|
|
|
Check if this context is not empty. |
142
|
|
|
|
143
|
|
|
Returns |
144
|
|
|
------- |
145
|
|
|
True if it contains some attribute and some individuals : :class:`bool` |
146
|
|
|
|
147
|
|
|
|
148
|
|
|
.. versionadded:: 0.0.1 |
149
|
|
|
""" |
150
|
|
|
return bool(self.population) and bool(self.model) |
151
|
|
|
|
152
|
|
|
def __str__(self): |
153
|
|
|
""" |
154
|
|
|
Convert this context to a readable string. |
155
|
|
|
|
156
|
|
|
Returns |
157
|
|
|
------- |
158
|
|
|
the user friendly readable string of this context : :class:`str` |
159
|
|
|
|
160
|
|
|
.. versionadded:: 0.0.1 |
161
|
|
|
""" |
162
|
|
|
return str({ |
163
|
|
|
'population': [identifier for identifier in self.population], |
164
|
|
|
'model': {name: attribute.type for name, attribute in self.model.items()} |
165
|
|
|
}) |
166
|
|
|
|
167
|
|
|
|
168
|
|
|
# pylint: disable=too-few-public-methods,function-redefined |
169
|
|
|
class Population(Generic[C, M, X], Mapping[str, X]): |
170
|
|
|
""" |
171
|
|
|
A :class:`Population` is a container for individuals. |
172
|
|
|
|
173
|
|
|
It's possible to access to the population :attr:`context` attribute. |
174
|
|
|
|
175
|
|
|
Example |
176
|
|
|
------- |
177
|
|
|
|
178
|
|
|
>>> from galactic.context.memory import MemoryContext |
179
|
|
|
>>> context = MemoryContext( |
180
|
|
|
... definition={'mybool': bool, 'myint': int}, |
181
|
|
|
... individuals=['0', '1'] |
182
|
|
|
... ) |
183
|
|
|
>>> population = context.population |
184
|
|
|
>>> print(population.context) |
185
|
|
|
{'population': ['0', '1'], 'model': {'mybool': <class 'bool'>, 'myint': <class 'int'>}} |
186
|
|
|
|
187
|
|
|
It's possible to get a readable representation of a population. |
188
|
|
|
|
189
|
|
|
Example |
190
|
|
|
------- |
191
|
|
|
|
192
|
|
|
>>> print(population) |
193
|
|
|
['0', '1'] |
194
|
|
|
|
195
|
|
|
It's possible to check if a population is not empty using the python builtin :func:`bool` |
196
|
|
|
function. |
197
|
|
|
|
198
|
|
|
Example |
199
|
|
|
------- |
200
|
|
|
|
201
|
|
|
>>> bool(population) |
202
|
|
|
True |
203
|
|
|
|
204
|
|
|
It's possible to access to an individual with its identifier using the python array access |
205
|
|
|
construct. |
206
|
|
|
|
207
|
|
|
Example |
208
|
|
|
------- |
209
|
|
|
|
210
|
|
|
>>> print(population['0']) |
211
|
|
|
{'mybool': False, 'myint': 0} |
212
|
|
|
|
213
|
|
|
It's possible to check if an individual belongs to a population using the python |
214
|
|
|
:keyword:`in` keyword. |
215
|
|
|
|
216
|
|
|
Example |
217
|
|
|
------- |
218
|
|
|
|
219
|
|
|
>>> '0' in population |
220
|
|
|
True |
221
|
|
|
|
222
|
|
|
It's possible to iterate over a population using the python :keyword:`for` keyword. |
223
|
|
|
|
224
|
|
|
Example |
225
|
|
|
------- |
226
|
|
|
|
227
|
|
|
>>> {ident: str(individual) for ident, individual in population.items()} |
228
|
|
|
{'0': "{'mybool': False, 'myint': 0}", '1': "{'mybool': False, 'myint': 0}"} |
229
|
|
|
|
230
|
|
|
It's possible to get the length of a population using the python builtin :func:`len` |
231
|
|
|
function. |
232
|
|
|
|
233
|
|
|
Example |
234
|
|
|
------- |
235
|
|
|
|
236
|
|
|
>>> len(population) |
237
|
|
|
2 |
238
|
|
|
|
239
|
|
|
.. versionadded:: 0.0.1 |
240
|
|
|
""" |
241
|
|
|
|
242
|
|
|
@property |
243
|
|
|
@abstractmethod |
244
|
|
|
def context(self) -> C: |
245
|
|
|
""" |
246
|
|
|
Get the underlying context. |
247
|
|
|
|
248
|
|
|
Returns |
249
|
|
|
------- |
250
|
|
|
the underlying context : :class:`C` |
251
|
|
|
|
252
|
|
|
.. versionadded:: 0.0.1 |
253
|
|
|
""" |
254
|
|
|
raise NotImplementedError |
255
|
|
|
|
256
|
|
|
@property |
257
|
|
|
def model(self) -> M: |
258
|
|
|
""" |
259
|
|
|
Get the underlying model. |
260
|
|
|
|
261
|
|
|
Returns |
262
|
|
|
------- |
263
|
|
|
the underlying model : :class:`M` |
264
|
|
|
|
265
|
|
|
.. versionadded:: 0.0.1 |
266
|
|
|
""" |
267
|
|
|
return self.context.model |
268
|
|
|
|
269
|
|
|
@abstractmethod |
270
|
|
|
def __bool__(self): |
271
|
|
|
""" |
272
|
|
|
Check if this population is not empty. |
273
|
|
|
|
274
|
|
|
Returns |
275
|
|
|
------- |
276
|
|
|
True if this population is not empty : :class:`bool` |
277
|
|
|
|
278
|
|
|
.. versionadded:: 0.0.1 |
279
|
|
|
""" |
280
|
|
|
raise NotImplementedError |
281
|
|
|
|
282
|
|
|
def __str__(self): |
283
|
|
|
""" |
284
|
|
|
Convert this population to a readable string. |
285
|
|
|
|
286
|
|
|
Returns |
287
|
|
|
------- |
288
|
|
|
the user friendly readable string of this population : :class:`str` |
289
|
|
|
|
290
|
|
|
.. versionadded:: 0.0.1 |
291
|
|
|
""" |
292
|
|
|
return str([identifier for identifier in self]) |
293
|
|
|
|
294
|
|
|
|
295
|
|
|
class Model(Generic[C, P, A], |
296
|
|
|
Mapping[str, A]): # pylint: disable=too-few-public-methods,function-redefined |
297
|
|
|
""" |
298
|
|
|
A :class:`Model` is a container for attributes. |
299
|
|
|
|
300
|
|
|
It's possible to access to the model :attr:`context` attribute. |
301
|
|
|
|
302
|
|
|
Example |
303
|
|
|
------- |
304
|
|
|
|
305
|
|
|
>>> from galactic.context.memory import MemoryContext |
306
|
|
|
>>> context = MemoryContext( |
307
|
|
|
... definition={'mybool': bool, 'myint': int}, |
308
|
|
|
... individuals=['0', '1'] |
309
|
|
|
... ) |
310
|
|
|
>>> model = context.model |
311
|
|
|
>>> print(model.context) |
312
|
|
|
{'population': ['0', '1'], 'model': {'mybool': <class 'bool'>, 'myint': <class 'int'>}} |
313
|
|
|
|
314
|
|
|
It's possible to get a readable representation of a model using the python builtin :func:`str` |
315
|
|
|
function. |
316
|
|
|
|
317
|
|
|
Example |
318
|
|
|
------- |
319
|
|
|
|
320
|
|
|
>>> print(model) |
321
|
|
|
{'mybool': <class 'bool'>, 'myint': <class 'int'>} |
322
|
|
|
|
323
|
|
|
It's possible to check if a model is not empty using the python builtin :func:`bool` function. |
324
|
|
|
|
325
|
|
|
Example |
326
|
|
|
------- |
327
|
|
|
|
328
|
|
|
>>> bool(model) |
329
|
|
|
True |
330
|
|
|
|
331
|
|
|
It's possible to access to an attribute with its name using the python array access |
332
|
|
|
construct. |
333
|
|
|
|
334
|
|
|
Example |
335
|
|
|
------- |
336
|
|
|
|
337
|
|
|
>>> print(model['mybool']) |
338
|
|
|
{'name': 'mybool', 'type': <class 'bool'>} |
339
|
|
|
|
340
|
|
|
It's possible to check if an attribute belongs to a model using the python |
341
|
|
|
:keyword:`in` keyword. |
342
|
|
|
|
343
|
|
|
Example |
344
|
|
|
------- |
345
|
|
|
|
346
|
|
|
>>> 'mybool' in model |
347
|
|
|
True |
348
|
|
|
|
349
|
|
|
It's possible to iterate over a model using the python :keyword:`for` keyword. |
350
|
|
|
|
351
|
|
|
Example |
352
|
|
|
------- |
353
|
|
|
|
354
|
|
|
>>> {name: attribute.type for name, attribute in model.items()} |
355
|
|
|
{'mybool': <class 'bool'>, 'myint': <class 'int'>} |
356
|
|
|
|
357
|
|
|
It's possible to get the length of a population using the python builtin :func:`len` |
358
|
|
|
function. |
359
|
|
|
|
360
|
|
|
Example |
361
|
|
|
------- |
362
|
|
|
|
363
|
|
|
>>> len(model) |
364
|
|
|
2 |
365
|
|
|
|
366
|
|
|
.. versionadded:: 0.0.1 |
367
|
|
|
""" |
368
|
|
|
|
369
|
|
|
@property |
370
|
|
|
@abstractmethod |
371
|
|
|
def context(self) -> C: |
372
|
|
|
""" |
373
|
|
|
Get the underlying context. |
374
|
|
|
|
375
|
|
|
Returns |
376
|
|
|
------- |
377
|
|
|
the underlying context : :class:`C` |
378
|
|
|
|
379
|
|
|
.. versionadded:: 0.0.1 |
380
|
|
|
""" |
381
|
|
|
raise NotImplementedError |
382
|
|
|
|
383
|
|
|
@property |
384
|
|
|
def population(self) -> P: |
385
|
|
|
""" |
386
|
|
|
Get the underlying population. |
387
|
|
|
|
388
|
|
|
Returns |
389
|
|
|
------- |
390
|
|
|
the underlying population : :class:`P` |
391
|
|
|
|
392
|
|
|
.. versionadded:: 0.0.1 |
393
|
|
|
""" |
394
|
|
|
return self.context.population |
395
|
|
|
|
396
|
|
|
@abstractmethod |
397
|
|
|
def __bool__(self): |
398
|
|
|
""" |
399
|
|
|
Check if this model is not empty. |
400
|
|
|
|
401
|
|
|
Returns |
402
|
|
|
------- |
403
|
|
|
True if this model is not empty : :class:`bool` |
404
|
|
|
|
405
|
|
|
.. versionadded:: 0.0.1 |
406
|
|
|
""" |
407
|
|
|
raise NotImplementedError |
408
|
|
|
|
409
|
|
|
def __str__(self): |
410
|
|
|
""" |
411
|
|
|
Convert this model to a readable string. |
412
|
|
|
|
413
|
|
|
Returns |
414
|
|
|
------- |
415
|
|
|
the user friendly readable string of this model : :class:`str` |
416
|
|
|
|
417
|
|
|
.. versionadded:: 0.0.1 |
418
|
|
|
""" |
419
|
|
|
return str({name: attribute.type for name, attribute in self.items()}) |
420
|
|
|
|
421
|
|
|
|
422
|
|
|
# pylint: disable=too-few-public-methods,function-redefined |
423
|
|
|
class Individual(Generic[C, P, M, X, A], Mapping[str, object]): |
424
|
|
|
""" |
425
|
|
|
A :class:`Individual` is a container for values. |
426
|
|
|
|
427
|
|
|
It's possible to access to the individual :attr:`identifier` attribute. |
428
|
|
|
|
429
|
|
|
Example |
430
|
|
|
------- |
431
|
|
|
|
432
|
|
|
>>> from galactic.context.memory import MemoryContext |
433
|
|
|
>>> context = MemoryContext( |
434
|
|
|
... definition={'mybool': bool, 'myint': int}, |
435
|
|
|
... individuals=['0', '1'] |
436
|
|
|
... ) |
437
|
|
|
>>> individual = context.population['0'] |
438
|
|
|
>>> individual.identifier |
439
|
|
|
'0' |
440
|
|
|
|
441
|
|
|
It's possible to access to the individual :attr:`context` attribute. |
442
|
|
|
|
443
|
|
|
Example |
444
|
|
|
------- |
445
|
|
|
|
446
|
|
|
>>> print(individual.context) |
447
|
|
|
{'population': ['0', '1'], 'model': {'mybool': <class 'bool'>, 'myint': <class 'int'>}} |
448
|
|
|
|
449
|
|
|
It's possible to access to the individual :attr:`model` attribute. |
450
|
|
|
|
451
|
|
|
Example |
452
|
|
|
------- |
453
|
|
|
|
454
|
|
|
>>> print(individual.model) |
455
|
|
|
{'mybool': <class 'bool'>, 'myint': <class 'int'>} |
456
|
|
|
|
457
|
|
|
It's possible to access to the individual :attr:`population` attribute. |
458
|
|
|
|
459
|
|
|
Example |
460
|
|
|
------- |
461
|
|
|
|
462
|
|
|
>>> print(individual.population) |
463
|
|
|
['0', '1'] |
464
|
|
|
|
465
|
|
|
It's possible to get a readable representation of an individual using the python builtin |
466
|
|
|
:func:`str` function. |
467
|
|
|
|
468
|
|
|
Example |
469
|
|
|
------- |
470
|
|
|
|
471
|
|
|
>>> print(individual) |
472
|
|
|
{'mybool': False, 'myint': 0} |
473
|
|
|
|
474
|
|
|
It's possible to access to the individual values using the :meth:`value` method. |
475
|
|
|
|
476
|
|
|
Example |
477
|
|
|
------- |
478
|
|
|
|
479
|
|
|
>>> attribute = individual.model['mybool'] |
480
|
|
|
>>> individual.value(attribute) |
481
|
|
|
False |
482
|
|
|
|
483
|
|
|
It's possible to access to the individual values using the python array access |
484
|
|
|
construct. |
485
|
|
|
|
486
|
|
|
Example |
487
|
|
|
------- |
488
|
|
|
|
489
|
|
|
>>> individual['mybool'] |
490
|
|
|
False |
491
|
|
|
|
492
|
|
|
It's possible to get the length of an individual using the python builtin :func:`len` |
493
|
|
|
function. |
494
|
|
|
|
495
|
|
|
Example |
496
|
|
|
------- |
497
|
|
|
|
498
|
|
|
>>> len(individual) |
499
|
|
|
2 |
500
|
|
|
|
501
|
|
|
It's possible to iterate over an individual using the python :keyword:`for` keyword. |
502
|
|
|
|
503
|
|
|
Example |
504
|
|
|
------- |
505
|
|
|
|
506
|
|
|
>>> {name: value for name, value in individual.items()} |
507
|
|
|
{'mybool': False, 'myint': 0} |
508
|
|
|
|
509
|
|
|
.. versionadded:: 0.0.1 |
510
|
|
|
""" |
511
|
|
|
|
512
|
|
|
@property |
513
|
|
|
@abstractmethod |
514
|
|
|
def identifier(self) -> str: |
515
|
|
|
""" |
516
|
|
|
Get this individual identifier. |
517
|
|
|
|
518
|
|
|
Returns |
519
|
|
|
------- |
520
|
|
|
the individual identifier : :class:`str` |
521
|
|
|
|
522
|
|
|
.. versionadded:: 0.0.1 |
523
|
|
|
""" |
524
|
|
|
raise NotImplementedError |
525
|
|
|
|
526
|
|
|
@property |
527
|
|
|
@abstractmethod |
528
|
|
|
def population(self) -> P: |
529
|
|
|
""" |
530
|
|
|
Get the underlying population. |
531
|
|
|
|
532
|
|
|
Returns |
533
|
|
|
------- |
534
|
|
|
the underlying population : :class:`P` |
535
|
|
|
|
536
|
|
|
.. versionadded:: 0.0.1 |
537
|
|
|
""" |
538
|
|
|
raise NotImplementedError |
539
|
|
|
|
540
|
|
|
@property |
541
|
|
|
def context(self) -> C: |
542
|
|
|
""" |
543
|
|
|
Get the underlying context. |
544
|
|
|
|
545
|
|
|
Returns |
546
|
|
|
------- |
547
|
|
|
the underlying context : :class:`C` |
548
|
|
|
|
549
|
|
|
.. versionadded:: 0.0.1 |
550
|
|
|
""" |
551
|
|
|
return self.population.context |
552
|
|
|
|
553
|
|
|
@property |
554
|
|
|
def model(self) -> M: |
555
|
|
|
""" |
556
|
|
|
Get the underlying model. |
557
|
|
|
|
558
|
|
|
Returns |
559
|
|
|
------- |
560
|
|
|
the underlying model : :class:`M` |
561
|
|
|
|
562
|
|
|
.. versionadded:: 0.0.1 |
563
|
|
|
""" |
564
|
|
|
return self.context.model |
565
|
|
|
|
566
|
|
|
def value(self, attribute: A): |
567
|
|
|
""" |
568
|
|
|
Get the attribute value for this individual. |
569
|
|
|
|
570
|
|
|
Parameters |
571
|
|
|
---------- |
572
|
|
|
attribute : :class:`A` |
573
|
|
|
the attribute |
574
|
|
|
|
575
|
|
|
Returns |
576
|
|
|
------- |
577
|
|
|
the value : :class:`object` |
578
|
|
|
|
579
|
|
|
Raises |
580
|
|
|
------ |
581
|
|
|
ValueError |
582
|
|
|
if the attribute does not belong to the underlying model. |
583
|
|
|
|
584
|
|
|
.. versionadded:: 0.0.1 |
585
|
|
|
""" |
586
|
|
|
if attribute.context == self.context: |
587
|
|
|
return self[attribute.name] |
588
|
|
|
else: |
589
|
|
|
raise ValueError |
590
|
|
|
|
591
|
|
|
@abstractmethod |
592
|
|
|
def __getitem__(self, name: str): |
593
|
|
|
""" |
594
|
|
|
Get the value of this individual for the given attribute. The :param name: is the attribute |
595
|
|
|
name. |
596
|
|
|
|
597
|
|
|
Parameters |
598
|
|
|
---------- |
599
|
|
|
name : :class:`str` |
600
|
|
|
the attribute name |
601
|
|
|
|
602
|
|
|
Returns |
603
|
|
|
------- |
604
|
|
|
the associated value : :class:`object` |
605
|
|
|
|
606
|
|
|
Raises |
607
|
|
|
------ |
608
|
|
|
KeyError |
609
|
|
|
if the attribute does not belong to the underlying model. |
610
|
|
|
|
611
|
|
|
.. versionadded:: 0.0.1 |
612
|
|
|
""" |
613
|
|
|
raise NotImplementedError |
614
|
|
|
|
615
|
|
|
def __iter__(self) -> Iterator[str]: |
616
|
|
|
""" |
617
|
|
|
Get an iterator for this individual. |
618
|
|
|
|
619
|
|
|
Returns |
620
|
|
|
------- |
621
|
|
|
an iterator : :class:`<Iterator[str] <python:collections.abc.iterator>` |
622
|
|
|
|
623
|
|
|
.. versionadded:: 0.0.1 |
624
|
|
|
""" |
625
|
|
|
return iter(self.model) |
626
|
|
|
|
627
|
|
|
def __len__(self): |
628
|
|
|
""" |
629
|
|
|
Get the length of this individual. |
630
|
|
|
|
631
|
|
|
Returns |
632
|
|
|
------- |
633
|
|
|
the length of this individual : :class:`int` |
634
|
|
|
|
635
|
|
|
.. versionadded:: 0.0.1 |
636
|
|
|
""" |
637
|
|
|
return len(self.model) |
638
|
|
|
|
639
|
|
|
def __eq__(self, other: X): |
640
|
|
|
""" |
641
|
|
|
Check if two individuals are equal. |
642
|
|
|
|
643
|
|
|
Parameters |
644
|
|
|
---------- |
645
|
|
|
other : :class:`X` |
646
|
|
|
the individual to test the equality with |
647
|
|
|
|
648
|
|
|
Returns |
649
|
|
|
------- |
650
|
|
|
self == other : :class:`bool` |
651
|
|
|
|
652
|
|
|
.. versionadded:: 0.0.1 |
653
|
|
|
""" |
654
|
|
|
return self.identifier == other.identifier and self.context == other.context |
655
|
|
|
|
656
|
|
|
def __hash__(self): |
657
|
|
|
""" |
658
|
|
|
Use specific hashing. |
659
|
|
|
|
660
|
|
|
Returns |
661
|
|
|
------- |
662
|
|
|
the hash number : :class:`int` |
663
|
|
|
|
664
|
|
|
.. versionadded:: 0.0.1 |
665
|
|
|
""" |
666
|
|
|
return hash((self.identifier, self.context)) |
667
|
|
|
|
668
|
|
|
def __str__(self): |
669
|
|
|
""" |
670
|
|
|
Convert this individual to a readable string. |
671
|
|
|
|
672
|
|
|
Returns |
673
|
|
|
------- |
674
|
|
|
the user friendly readable string of this individual : :class:`str` |
675
|
|
|
|
676
|
|
|
.. versionadded:: 0.0.1 |
677
|
|
|
""" |
678
|
|
|
return str({name: value for name, value in self.items()}) |
679
|
|
|
|
680
|
|
|
|
681
|
|
|
# pylint: disable=too-few-public-methods,function-redefined |
682
|
|
|
class Attribute(Generic[C, P, M, X, A], Mapping[str, object]): |
683
|
|
|
""" |
684
|
|
|
A :class:`Attribute` is described by a :attr:`name` and a :attr:`type`. |
685
|
|
|
|
686
|
|
|
It's possible to access to the attribute :attr:`identifier` and :attr:`type` attributes. |
687
|
|
|
|
688
|
|
|
Example |
689
|
|
|
------- |
690
|
|
|
|
691
|
|
|
>>> from galactic.context.memory import MemoryContext |
692
|
|
|
>>> context = MemoryContext( |
693
|
|
|
... definition={'mybool': bool, 'myint': int}, |
694
|
|
|
... individuals=['0', '1'] |
695
|
|
|
... ) |
696
|
|
|
>>> attribute = context.model['mybool'] |
697
|
|
|
>>> attribute.name |
698
|
|
|
'mybool' |
699
|
|
|
>>> attribute.type |
700
|
|
|
<class 'bool'> |
701
|
|
|
|
702
|
|
|
It's possible to access to the individual :attr:`context` attribute. |
703
|
|
|
|
704
|
|
|
Example |
705
|
|
|
------- |
706
|
|
|
|
707
|
|
|
>>> print(attribute.context) |
708
|
|
|
{'population': ['0', '1'], 'model': {'mybool': <class 'bool'>, 'myint': <class 'int'>}} |
709
|
|
|
|
710
|
|
|
It's possible to access to the individual :attr:`model` attribute. |
711
|
|
|
|
712
|
|
|
Example |
713
|
|
|
------- |
714
|
|
|
|
715
|
|
|
>>> print(attribute.model) |
716
|
|
|
{'mybool': <class 'bool'>, 'myint': <class 'int'>} |
717
|
|
|
|
718
|
|
|
It's possible to access to the individual :attr:`population` attribute. |
719
|
|
|
|
720
|
|
|
Example |
721
|
|
|
------- |
722
|
|
|
|
723
|
|
|
>>> print(attribute.population) |
724
|
|
|
['0', '1'] |
725
|
|
|
|
726
|
|
|
It's possible to get a readable representation of an attribute using the python builtin |
727
|
|
|
:func:`str` function. |
728
|
|
|
|
729
|
|
|
Example |
730
|
|
|
------- |
731
|
|
|
|
732
|
|
|
>>> print(attribute) |
733
|
|
|
{'name': 'mybool': 'type': <class 'bool'>} |
734
|
|
|
|
735
|
|
|
It's possible to access to the attribute values using the :meth:`value` method. |
736
|
|
|
|
737
|
|
|
Example |
738
|
|
|
------- |
739
|
|
|
|
740
|
|
|
>>> individual = attribute.population['0'] |
741
|
|
|
>>> attribute.value(individual) |
742
|
|
|
False |
743
|
|
|
|
744
|
|
|
It's possible to access to the attribute values using the python array access |
745
|
|
|
construct. |
746
|
|
|
|
747
|
|
|
Example |
748
|
|
|
------- |
749
|
|
|
|
750
|
|
|
>>> attribute['0'] |
751
|
|
|
False |
752
|
|
|
|
753
|
|
|
It's possible to get the length of an attribute using the python builtin :func:`len` |
754
|
|
|
function. |
755
|
|
|
|
756
|
|
|
Example |
757
|
|
|
------- |
758
|
|
|
|
759
|
|
|
>>> len(attribute) |
760
|
|
|
2 |
761
|
|
|
|
762
|
|
|
It's possible to iterate over an attribute using the python :keyword:`for` keyword. |
763
|
|
|
|
764
|
|
|
Example |
765
|
|
|
------- |
766
|
|
|
|
767
|
|
|
>>> {identifier: value for identifier, value in attribute.items()} |
768
|
|
|
{'0': False, '1': False} |
769
|
|
|
|
770
|
|
|
.. versionadded:: 0.0.1 |
771
|
|
|
""" |
772
|
|
|
|
773
|
|
|
@property |
774
|
|
|
@abstractmethod |
775
|
|
|
def name(self) -> str: |
776
|
|
|
""" |
777
|
|
|
Get the attribute name. |
778
|
|
|
|
779
|
|
|
Returns |
780
|
|
|
------- |
781
|
|
|
the attribute name : :class:`str` |
782
|
|
|
|
783
|
|
|
.. versionadded:: 0.0.1 |
784
|
|
|
""" |
785
|
|
|
raise NotImplementedError |
786
|
|
|
|
787
|
|
|
@property |
788
|
|
|
@abstractmethod |
789
|
|
|
def type(self) -> type: |
790
|
|
|
""" |
791
|
|
|
Get the attribute type. |
792
|
|
|
|
793
|
|
|
Returns |
794
|
|
|
------- |
795
|
|
|
the attribute type : :class:`type <python:type>` |
796
|
|
|
|
797
|
|
|
.. versionadded:: 0.0.1 |
798
|
|
|
""" |
799
|
|
|
raise NotImplementedError |
800
|
|
|
|
801
|
|
|
@property |
802
|
|
|
@abstractmethod |
803
|
|
|
def model(self) -> M: |
804
|
|
|
""" |
805
|
|
|
Get the underlying model. |
806
|
|
|
|
807
|
|
|
Returns |
808
|
|
|
------- |
809
|
|
|
the underlying model : :class:`M` |
810
|
|
|
|
811
|
|
|
.. versionadded:: 0.0.1 |
812
|
|
|
""" |
813
|
|
|
raise NotImplementedError |
814
|
|
|
|
815
|
|
|
@property |
816
|
|
|
def context(self) -> C: |
817
|
|
|
""" |
818
|
|
|
Get the underlying context. |
819
|
|
|
|
820
|
|
|
Returns |
821
|
|
|
------- |
822
|
|
|
the underlying context : :class:`C` |
823
|
|
|
|
824
|
|
|
.. versionadded:: 0.0.1 |
825
|
|
|
""" |
826
|
|
|
return self.model.context |
827
|
|
|
|
828
|
|
|
@property |
829
|
|
|
def population(self) -> P: |
830
|
|
|
""" |
831
|
|
|
Get the underlying population. |
832
|
|
|
|
833
|
|
|
Returns |
834
|
|
|
------- |
835
|
|
|
the underlying population : :class:`P` |
836
|
|
|
|
837
|
|
|
.. versionadded:: 0.0.1 |
838
|
|
|
""" |
839
|
|
|
return self.context.population |
840
|
|
|
|
841
|
|
|
def value(self, individual: X): |
842
|
|
|
""" |
843
|
|
|
Get the individual value for this attribute. |
844
|
|
|
|
845
|
|
|
Parameters |
846
|
|
|
---------- |
847
|
|
|
individual : :class:`X` |
848
|
|
|
the individual |
849
|
|
|
|
850
|
|
|
Returns |
851
|
|
|
------- |
852
|
|
|
the value : :class:`object` |
853
|
|
|
|
854
|
|
|
Raises |
855
|
|
|
------ |
856
|
|
|
ValueError |
857
|
|
|
if the individual does not belong to the underlying population. |
858
|
|
|
|
859
|
|
|
.. versionadded:: 0.0.1 |
860
|
|
|
""" |
861
|
|
|
return individual.value(self) |
862
|
|
|
|
863
|
|
|
def __getitem__(self, identifier: str): |
864
|
|
|
""" |
865
|
|
|
Get the value of this attribute for the given individual. |
866
|
|
|
|
867
|
|
|
Parameters |
868
|
|
|
---------- |
869
|
|
|
identifier : :class:`str` |
870
|
|
|
the individual identifier |
871
|
|
|
|
872
|
|
|
Returns |
873
|
|
|
------- |
874
|
|
|
the value : :class:`object` |
875
|
|
|
|
876
|
|
|
Raises |
877
|
|
|
------ |
878
|
|
|
KeyError |
879
|
|
|
if the individual does not belong to the underlying population. |
880
|
|
|
|
881
|
|
|
.. versionadded:: 0.0.1 |
882
|
|
|
""" |
883
|
|
|
return self.population[identifier][self.name] |
884
|
|
|
|
885
|
|
|
def __iter__(self) -> Iterator[str]: |
886
|
|
|
""" |
887
|
|
|
Get the iterator of this attribute. |
888
|
|
|
|
889
|
|
|
Returns |
890
|
|
|
------- |
891
|
|
|
an iterator : :class:`Iterator[Individual] <python:collections.abc.iterator>` |
892
|
|
|
|
893
|
|
|
.. versionadded:: 0.0.1 |
894
|
|
|
""" |
895
|
|
|
return iter(self.population) |
896
|
|
|
|
897
|
|
|
def __len__(self): |
898
|
|
|
""" |
899
|
|
|
Get the length of this attribute. |
900
|
|
|
|
901
|
|
|
Returns |
902
|
|
|
------- |
903
|
|
|
the length of this attribute : :class:`int` |
904
|
|
|
|
905
|
|
|
.. versionadded:: 0.0.1 |
906
|
|
|
""" |
907
|
|
|
return len(self.population) |
908
|
|
|
|
909
|
|
|
def __eq__(self, other: A): |
910
|
|
|
""" |
911
|
|
|
Check if two attributes are equal. |
912
|
|
|
|
913
|
|
|
Parameters |
914
|
|
|
---------- |
915
|
|
|
other : :class:`A` |
916
|
|
|
the attribute to test the equality with |
917
|
|
|
|
918
|
|
|
Returns |
919
|
|
|
------- |
920
|
|
|
self == other : :class:`bool` |
921
|
|
|
|
922
|
|
|
.. versionadded:: 0.0.1 |
923
|
|
|
""" |
924
|
|
|
return self.name == other.name and self.context == other.context |
925
|
|
|
|
926
|
|
|
def __hash__(self): |
927
|
|
|
""" |
928
|
|
|
Use specific hashing. |
929
|
|
|
|
930
|
|
|
Returns |
931
|
|
|
------- |
932
|
|
|
the hash number : :class:`int` |
933
|
|
|
|
934
|
|
|
.. versionadded:: 0.0.1 |
935
|
|
|
""" |
936
|
|
|
return hash((self.name, self.context)) |
937
|
|
|
|
938
|
|
|
def __str__(self): |
939
|
|
|
""" |
940
|
|
|
Convert this attribute to a printable string. |
941
|
|
|
|
942
|
|
|
Returns |
943
|
|
|
------- |
944
|
|
|
the user friendly readable string of this attribute : :class:`str` |
945
|
|
|
|
946
|
|
|
.. versionadded:: 0.0.1 |
947
|
|
|
""" |
948
|
|
|
return str({'name': self.name, 'type': self.type}) |
949
|
|
|
|