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

MemoryPopulation   A

Complexity

Total Complexity 6

Size/Duplication

Total Lines 90
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 90
rs 10
wmc 6

2 Methods

Rating   Name   Duplication   Size   Complexity  
B __setitem__() 0 29 4
A __init__() 0 20 2
1
# This Python file uses the following encoding: utf-8
2
3
"""
4
The :mod:`galactic.context.memory` module give the ability to define
5
:class:`Context <galactic.context.Context>` that resides in memory.
6
"""
7
8
from typing import Mapping, Iterable, Union
9
10
from galactic.context import Context, Model, Population, Attribute, Individual
11
from galactic.context.mixins import ContextHolder, PopulationHolder, ModelHolder, \
12
    ConcreteAttribute, ConcreteIndividual, AttributesHolder, IndividualsHolder, ValuesHolder
13
14
15
# pylint: disable=too-few-public-methods,abstract-method,super-init-not-called
16
class MemoryContext(
17
        PopulationHolder['MemoryPopulation'],
18
        ModelHolder['MemoryModel'],
19
        Context['MemoryPopulation', 'MemoryModel', 'MemoryIndividual', 'MemoryAttribute']
20
):
21
    """
22
    The :class:`MemoryContext` class is designed to define contexts in memory. It inherits of all
23
    the behavior from the :class:`Context <galactic.context.Context>` class and allows direct
24
    creation and modification of a context.
25
26
    It's possible to create a context without nothing.
27
28
    Example
29
    -------
30
31
        >>> from galactic.context.memory import MemoryContext
32
        >>> context = MemoryContext()
33
        >>> print(context)
34
        {'population': [], 'model': {}}
35
36
    It's possible to create a context specifying the model.
37
38
    Example
39
    -------
40
41
        >>> from galactic.context.memory import MemoryContext
42
        >>> context = MemoryContext({'mybool': bool, 'myint': int})
43
        >>> print(context)
44
        {'population': [], 'model': {'mybool': <class 'bool'>, 'myint': <class 'int'>}}
45
46
    It's possible to create a context specifying the model and the list of individual identifiers.
47
48
    Example
49
    -------
50
51
        >>> from galactic.context.memory import MemoryContext
52
        >>> context = MemoryContext({'mybool': bool, 'myint': int}, ['0', '1'])
53
        >>> print(context)
54
        {'population': ['0', '1'], 'model': {'mybool': <class 'bool'>, 'myint': <class 'int'>}}
55
56
    It's possible to create a context specifying the model and the individual values.
57
58
    Example
59
    -------
60
61
        >>> from galactic.context.memory import MemoryContext
62
        >>> context = MemoryContext(
63
        ...     {'mybool': bool, 'myint': int},
64
        ...     {'0': {'mybool': True}, '1':{'myint': 1}}
65
        ... )
66
        >>> {ident: str(context.population[ident]) for ident in context.population}
67
        {'0': "{'mybool': True, 'myint': 0}", '1': "{'mybool': False, 'myint': 1}"}
68
69
    .. versionadded:: 0.0.1
70
    """
71
72
    def __init__(
73
            self,
74
            definition: Mapping[str, type] = None,
75
            individuals: Union[Iterable[str], Mapping[str, Mapping[str, object]]] = None
76
    ):
77
        """
78
        Initialise a context in memory.
79
80
        Parameters
81
        ----------
82
            definition : :class:`Mapping[str, type] <python:collections.abc.Mapping>`
83
                definition of the context by a mapping from name of attributes to their type
84
            individuals : :class:`Union[Iterable[str], Mapping[str, Mapping[str, object]]]`
85
                initial iterable of individual identifiers or a mapping from individual
86
                identifiers to individual values
87
88
        Raises
89
        ------
90
            KeyError
91
                if an attribute is not in the definition
92
            ValueError
93
                if a value does not correspond to an attribute type
94
95
        .. versionadded:: 0.0.1
96
        """
97
        super().__init__(
98
            model=MemoryModel(self, {} if definition is None else definition),
99
            population=MemoryPopulation(self, [])
100
        )
101
102
        if isinstance(individuals, Mapping):
103
            for identifier, values in individuals.items():
104
                self.population[identifier] = values
105
        else:
106
            if individuals is not None:
107
                for identifier in individuals:
108
                    self.population[identifier] = {}
109
110
111
# pylint: disable=too-few-public-methods,abstract-method,super-init-not-called
112
class MemoryModel(
113
        ContextHolder[MemoryContext],
114
        AttributesHolder['MemoryAttribute'],
115
        Model[MemoryContext, 'MemoryPopulation', 'MemoryAttribute']
116
):
117
    """
118
    The :class:`MemoryModel` class is designed to define models that resides in memory. It inherits
119
    of all the behavior from the :class:`Model <galactic.context.Model>` class.
120
121
    It's possible to change or to set attribute values.
122
123
    Example
124
    -------
125
126
        >>> from galactic.context.memory import MemoryContext
127
        >>> context = MemoryContext({'mybool': bool, 'myint': int}, ['0', '1'])
128
        >>> model = context.model
129
        >>> model['mybool'] = int
130
        >>> model['myint2'] = int
131
        >>> print(context.population['0'])
132
        {'mybool': 0, 'myint': 0, 'myint2': 0}
133
134
    It's possible to delete an attribute using its name.
135
136
    Example
137
    -------
138
139
        >>> from galactic.context.memory import MemoryContext
140
        >>> context = MemoryContext({'mybool': bool, 'myint': int}, ['0', '1'])
141
        >>> model = context.model
142
        >>> del model['mybool']
143
        >>> {ident: str(context.population[ident]) for ident in context.population}
144
        {'0': "{'myint': 0}", '1': "{'myint': 0}"}
145
146
147
    .. versionadded:: 0.0.1
148
    """
149
150
    def __init__(
151
            self,
152
            context: MemoryContext,
153
            definition: Mapping[str, 'type']
154
    ):
155
        """
156
        Initialise a model in memory.
157
158
        Parameters
159
        ----------
160
            context : :class:`MemoryContext`
161
                the underlying context
162
            definition : :class:`Mapping[str, type] <python:collections.abc.Mapping>`
163
                the attributes definition
164
165
        .. versionadded:: 0.0.1
166
        """
167
        super().__init__(
168
            context=context,
169
            attributes=[MemoryAttribute(self, name, cls) for name, cls in definition.items()]
170
        )
171
172
    def __delitem__(self, name: str):
173
        """
174
        Delete an attribute from a :class:`MemoryModel`.
175
176
        Parameters
177
        ----------
178
            name : :class:`str`
179
                attribute name
180
181
        Raises
182
        ------
183
            KeyError
184
                if the attribute is not in the model
185
186
        .. versionadded:: 0.0.1
187
        """
188
        super().__delitem__(name)
189
        for identifier in self.population:
190
            del self.population[identifier][name]
191
192
    def __setitem__(self, name: str, cls: type):
193
        """
194
        Set an attribute for a :class:`MemoryModel`.
195
196
        Parameters
197
        ----------
198
            name : :class:`str`
199
                the attribute name
200
            cls : :class:`python:type`
201
                the attribute type
202
203
        .. versionadded:: 0.0.1
204
        """
205
        if name in self:
206
            if self[name].type != cls:
207
                super().__setitem__(name, MemoryAttribute(self, name, cls))
208
                for identifier in self.population:
209
                    try:
210
                        self.population[identifier][name] = cls(self.population[identifier][name])
211
                    except (ValueError, TypeError):
212
                        self.population[identifier][name] = cls()
213
        else:
214
            super().__setitem__(name, MemoryAttribute(self, name, cls))
215
            for identifier in self.population:
216
                self.population[identifier][name] = cls()
217
218
219
# pylint: disable=too-few-public-methods,abstract-method,super-init-not-called
220
class MemoryPopulation(
221
        ContextHolder[MemoryContext],
222
        IndividualsHolder['MemoryIndividual'],
223
        Population[MemoryContext, MemoryModel, 'MemoryIndividual']
224
):
225
    """
226
    The :class:`MemoryPopulation` class is designed to define populations that resides in memory. It
227
    inherits of all the behavior from the :class:`Population <galactic.context.Population>` class.
228
229
    It's possible to change or to set individual values.
230
231
    Example
232
    -------
233
234
        >>> from galactic.context.memory import MemoryContext
235
        >>> context = MemoryContext({'mybool': bool, 'myint': int}, ['0', '1'])
236
        >>> population = context.population
237
        >>> population['0'] = {'mybool': True}
238
        >>> population['2'] = {'myint': 1}
239
        >>> print(population['0'])
240
        {'mybool': True, 'myint': 0}
241
        >>> print(population['2'])
242
        {'mybool': False, 'myint': 1}
243
244
    It's possible to delete an individual using its identifier.
245
246
    Example
247
    -------
248
249
        >>> from galactic.context.memory import MemoryContext
250
        >>> context = MemoryContext({'mybool': bool, 'myint': int}, ['0', '1'])
251
        >>> population = context.population
252
        >>> del population['0']
253
        >>> {ident: str(context.population[ident]) for ident in context.population}
254
        {'1': "{'mybool': False, 'myint': 0}"}
255
256
    .. versionadded:: 0.0.1
257
    """
258
259
    def __init__(
260
            self,
261
            context: MemoryContext,
262
            identifiers: Iterable[str]
263
    ):
264
        """
265
        Initialise a population in memory.
266
267
        Parameters
268
        ----------
269
            context : :class:`MemoryContext`
270
                the underlying context
271
            identifiers : :class:`Iterable[str] <python:collections.abc.Iterable>`
272
                an iterable of identifiers
273
274
        .. versionadded:: 0.0.1
275
        """
276
        super().__init__(
277
            context=context,
278
            individuals=[MemoryIndividual(self, identifier) for identifier in identifiers]
279
        )
280
281
    def __setitem__(self, identifier: str, values: Mapping[str, object]):
282
        """
283
        Set an individual for a :class:`MemoryPopulation`.
284
285
        Parameters
286
        ----------
287
            identifier : :class:`str`
288
                the individual identifier
289
            values : :class:`Mapping[str, object] <<python:collections.abc.mapping>>`
290
                the initial values for some attributes
291
292
        Raises
293
        ------
294
            KeyError
295
                if an attribute name does not belong to the underlying model.
296
            ValueError
297
                if an attribute value does not correspond to its type
298
299
        .. versionadded:: 0.0.1
300
        """
301
        try:
302
            individual = self[identifier]
303
        except KeyError:
304
            individual = MemoryIndividual(self, identifier)
305
            super().__setitem__(identifier, individual)
306
        for name, value in values.items():
307
            if name not in self.model:
308
                raise KeyError
309
            individual[name] = value
310
311
312
# pylint: disable=too-few-public-methods,abstract-method,super-init-not-called
313
class MemoryIndividual(
314
        PopulationHolder[MemoryPopulation],
315
        ValuesHolder,
316
        ConcreteIndividual,
317
        Individual[
318
            MemoryContext,
319
            MemoryPopulation,
320
            MemoryModel,
321
            'MemoryIndividual',
322
            'MemoryAttribute'
323
        ]
324
):
325
    """
326
    The :class:`MemoryIndividual` is designed to define individuals that resides in memory. It
327
    inherits of all the behavior from the :class:`Individual <galactic.context.Individual>` class.
328
329
    It's possible to modify a value for an individual using an attribute name.
330
331
    Example
332
    -------
333
334
        >>> from galactic.context.memory import MemoryContext
335
        >>> context = MemoryContext({'mybool': bool, 'myint': int}, ['0', '1'])
336
        >>> individual = context.population['0']
337
        >>> individual['mybool'] = True
338
        >>> individual['myint'] = 1
339
        >>> {ident: str(context.population[ident]) for ident in context.population}
340
        {'0': "{'mybool': True, 'myint': 1}", '1': "{'mybool': False, 'myint': 0}"}
341
342
    .. versionadded:: 0.0.1
343
    """
344
345
    def __init__(
346
            self,
347
            population: MemoryPopulation,
348
            identifier: str
349
    ):
350
        """
351
        Initialise an individual.
352
353
        Parameters
354
        ----------
355
            population : :class:`MemoryPopulation`
356
                the population
357
            identifier : :class:`str`
358
                the individual identifier
359
360
        .. versionadded:: 0.0.1
361
        """
362
        super().__init__(
363
            population=population,
364
            identifier=identifier,
365
            values={name: attribute.type() for name, attribute in population.model.items()}
366
        )
367
368
    def __setitem__(self, name: str, value):
369
        """
370
        Set an individual's attribute value using either the attribute or its name.
371
372
        Parameters
373
        ----------
374
            name: :class:`str`
375
                the attribute name
376
            value : :class:`object`
377
                the value
378
379
        Raises
380
        ------
381
            KeyError
382
                if the attribute does not belong to the underlying context
383
            ValueError
384
                if the value passed in argument has its type different of the attribute type
385
386
        .. versionadded:: 0.0.1
387
        """
388
        attribute = self.model[name]
389
        if not isinstance(value, attribute.type):
390
            value = attribute.type(value)
391
        super().__setitem__(name, value)
392
393
    def __delitem__(self, name: str):
394
        """
395
        Delete an individual value.
396
397
        Parameters
398
        ----------
399
            name: :class:`str`
400
                the attribute name
401
402
        Raises
403
        ------
404
            ValueError
405
                if the attribute is in the model (which should be always the case)
406
407
        .. versionadded:: 0.0.1
408
        """
409
        if name in self.model:
410
            raise ValueError
411
        else:
412
            super().__delitem__(name)
413
414
415
# pylint: disable=too-few-public-methods,abstract-method,super-init-not-called
416
class MemoryAttribute(
417
        ModelHolder[MemoryModel],
418
        ConcreteAttribute,
419
        Attribute[
420
            MemoryContext,
421
            MemoryPopulation,
422
            MemoryModel,
423
            MemoryIndividual,
424
            'MemoryAttribute'
425
        ]
426
):
427
    """
428
    The :class:`MemoryAttribute` is designed to define attributes that resides in memory. It
429
    inherits of all the behavior from the :class:`Attribute <galactic.context.Attribute>` class.
430
431
    It's possible to modify a value for an attribute using an individual identifier.
432
433
    Example
434
    -------
435
436
        >>> from galactic.context.memory import MemoryContext
437
        >>> context = MemoryContext({'mybool': bool, 'myint': int}, ['0', '1'])
438
        >>> attribute = context.model['myint']
439
        >>> attribute['0'] = 3
440
        >>> attribute['1'] = 4
441
        >>> {ident: str(context.population[ident]) for ident in context.population}
442
        {'0': "{'mybool': False, 'myint': 3}", '1': "{'mybool': False, 'myint': 4}"}
443
444
    .. versionadded:: 0.0.1
445
    """
446
447
    def __init__(
448
            self,
449
            model: MemoryModel,
450
            name: str,
451
            cls: 'type'
452
    ):
453
        """
454
        Initialise an attribute.
455
456
        Parameters
457
        ----------
458
            model : :class:`MemoryModel`
459
                the underlying model
460
            name : :class:`str`
461
                the attribute name
462
            cls : `type <python:type>`
463
                the attribute type
464
465
        .. versionadded:: 0.0.1
466
        """
467
        super().__init__(
468
            model=model,
469
            name=name,
470
            type=cls
471
        )
472
473
    def __setitem__(self, identifier: str, value):
474
        """
475
        Set an individual's attribute value using either the individual or its identifier.
476
477
        This method raises a :class:`KeyError` exception if the attribute does not belong to
478
        the underlying context and a :class:`ValueError` exception if the value passed in argument
479
        has its type different of the attribute type.
480
481
        Parameters
482
        ----------
483
            identifier : :class:`str`
484
                the individual identifier
485
            value : :class:`object`
486
                the value
487
488
        Raises
489
        ------
490
            KeyError
491
                if the individual does not exists
492
            ValueError
493
                if the value is not of the attribute type
494
495
        .. versionadded:: 0.0.1
496
        """
497
        self.population[identifier][self.name] = value
498