Passed
Branch master (0f5d95)
by Christophe
01:59 queued 01:08
created

Individual.context()   A

Complexity

Conditions 1

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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