Population   A
last analyzed

Complexity

Total Complexity 5

Size/Duplication

Total Lines 124
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 124
rs 10
wmc 5

4 Methods

Rating   Name   Duplication   Size   Complexity  
A context() 0 13 1
A __str__() 0 11 2
A model() 0 12 1
A __bool__() 0 12 1
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