From d89dd45cae8cb4883e305acccc54f91a659ad3d4 Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Mon, 2 May 2022 11:40:24 +0200 Subject: [PATCH 01/33] Added structure for group entity Adding global group entity: - Added necessary group.py files - Changed all necessary files (e.g. __init__) - Study scripts run smoothly (more testing) - So far no functionality to group (is mostly copy of social system) --- .../data_model/master_data_model/__init__.py | 3 + .../data_model/master_data_model/group.py | 27 +++ .../model_components/abstract/__init__.py | 1 + .../model_components/abstract/group.py | 25 ++ .../base/implementation/__init__.py | 1 + .../base/implementation/group.py | 226 ++++++++++++++++++ .../model_components/base/interface.py | 54 +++++ pycopancore/model_components/base/model.py | 4 +- 8 files changed, 339 insertions(+), 2 deletions(-) create mode 100644 pycopancore/data_model/master_data_model/group.py create mode 100644 pycopancore/model_components/abstract/group.py create mode 100644 pycopancore/model_components/base/implementation/group.py diff --git a/pycopancore/data_model/master_data_model/__init__.py b/pycopancore/data_model/master_data_model/__init__.py index 4deb317a..f962b122 100644 --- a/pycopancore/data_model/master_data_model/__init__.py +++ b/pycopancore/data_model/master_data_model/__init__.py @@ -37,5 +37,8 @@ from .individual import Individual as I from .individual import Individual as individual from .individual import Individual +from .group import Group as G +from .group import Group as group +from .group import Group from .. import unity diff --git a/pycopancore/data_model/master_data_model/group.py b/pycopancore/data_model/master_data_model/group.py new file mode 100644 index 00000000..c636fff6 --- /dev/null +++ b/pycopancore/data_model/master_data_model/group.py @@ -0,0 +1,27 @@ +"""Master data model for group.""" + +"""https://github.com/pik-copan/pycopancore/blob/master/docs/framework_documentation/abstract_level/entity_types/group.rst""" + +#from . import MET +from .. import Variable + + +class Group: + + #TODO: add a group network possibility (in culture.py (?)) + + + has_leader = \ + Variable("has a leader", + "whether the group has a leader", + scale="ordinal", levels=[False, True], default=False) + + has_headquarter = \ + Variable("has a headquarter", + "whether the group has a headquarter located in a cell", + scale="ordinal", levels=[False, True], default=False) + + + + + diff --git a/pycopancore/model_components/abstract/__init__.py b/pycopancore/model_components/abstract/__init__.py index 6ee0e07a..e383543b 100644 --- a/pycopancore/model_components/abstract/__init__.py +++ b/pycopancore/model_components/abstract/__init__.py @@ -16,6 +16,7 @@ from .social_system import SocialSystem from .cell import Cell from .individual import Individual +from .group import Group from .environment import Environment from .metabolism import Metabolism diff --git a/pycopancore/model_components/abstract/group.py b/pycopancore/model_components/abstract/group.py new file mode 100644 index 00000000..f919b38e --- /dev/null +++ b/pycopancore/model_components/abstract/group.py @@ -0,0 +1,25 @@ +"""Abstract Group entity type class, inherited by base model component.""" + +# This file is part of pycopancore. +# +# Copyright (C) 2016-2017 by COPAN team at Potsdam Institute for Climate +# Impact Research +# +# URL: +# Contact: core@pik-potsdam.de +# License: BSD 2-clause license + +from ...private import _AbstractEntityMixin +from ...data_model import OrderedSet + + +class Group (_AbstractEntityMixin): + """Abstract Group entity type class. + + Inherited by base model component. + """ + + variables = OrderedSet() + """All variables occurring in this entity type""" + + diff --git a/pycopancore/model_components/base/implementation/__init__.py b/pycopancore/model_components/base/implementation/__init__.py index f782a260..a14081c7 100644 --- a/pycopancore/model_components/base/implementation/__init__.py +++ b/pycopancore/model_components/base/implementation/__init__.py @@ -17,6 +17,7 @@ from .social_system import SocialSystem from .cell import Cell from .individual import Individual +from .group import Group from .environment import Environment from .metabolism import Metabolism diff --git a/pycopancore/model_components/base/implementation/group.py b/pycopancore/model_components/base/implementation/group.py new file mode 100644 index 00000000..78c71a41 --- /dev/null +++ b/pycopancore/model_components/base/implementation/group.py @@ -0,0 +1,226 @@ +""" """ + +# This file is part of pycopancore. +# +# Copyright (C) 2016-2017 by COPAN team at Potsdam Institute for Climate +# Impact Research +# +# URL: +# Contact: core@pik-potsdam.de +# License: BSD 2-clause license + +# only used in this component, not in others: +from ... import abstract +from .... import master_data_model as D +from ....private import unknown + +from .. import interface as I + + +class Group (I.Group, abstract.Group): + """Gropp entity type mixin implementation class. + + Base component's Group mixin that every model must use in composing + their Group class. Inherits from I.Group as the interface with all + necessary variables and parameters. + """ + + # standard methods: + + def __init__(self, + *, + socialsystem, + next_higher_group=None, + **kwargs + ): + """Initialize an instance of Group. + + Parameters + ---------- + socialsystem: obj + SocialSystem the Group belongs to (default is None) + next_higher_group: obj + Optional Group the Group belongs to (default is None) + **kwargs + keyword arguments passed to super() + + """ + super().__init__(**kwargs) # must be the first line + + # init caches: + self._next_lower_group = set() + self._direct_cells = set() + + # init and set variables implemented via properties: + self._socialsystem = None + self.socialsystem = socialsystem + self._next_higher_group = None + self.next_higher_group = next_higher_group + + # getters and setters for references: + + @property + def socialsystem(self): + """Get the World the SocialSystem is part of.""" + return self._socialsystem + + @socialsystem.setter + def socialsystem(self, w): + """Set the World the SocialSystem is part of.""" + if self._socialsystem is not None: + self._socialsystem._groups.remove(self) + assert isinstance(w, I.SocialSystem), "socialsystem must be of entity type SocialSystem" + w._groups.add(self) + self._socialsystem = w + + @property + def next_higher_group(self): + """Get next higher group.""" + return self._next_higher_group + + @next_higher_group.setter + def next_higher_group(self, s): + """Set next higher group.""" + if self._next_higher_group is not None: + self._next_higher_group._next_lower_groups.remove(self) + # reset dependent cache: + self._next_higher_group.cells = unknown + if s is not None: + assert isinstance(s, I.Group), \ + "next_higher_group must be of entity type group" + s._next_lower_groups.add(self) + # reset dependent cache: + s.cells = unknown + self._next_higher_group = s + # reset dependent caches: + self.higher_groups = unknown + + # getters for backwards references and convenience variables: + + @property # read-only + def environment(self): + """Get the Environment of which the Group is a part.""" + return self._world.environment + + @property # read-only + def metabolism(self): + """Get the Metabolism of which the Group is a part.""" + return self._world.metabolism + + @property # read-only + def culture(self): + """Get the Culture of which the Group is a part.""" + return self._world.culture + + _higher_groups = unknown + """cache, depends on self.next_higher_group + and self.next_higher_group.higher_groups""" + @property # read-only + def higher_groups(self): + """Get higher groups.""" + if self._higher_groups is unknown: + # find recursively: + self._higher_groups = [] if self.next_higher_group is None \ + else ([self.next_higher_group] + + self.next_higher_group.groups) + return self._higher_groups + + @higher_groups.setter + def higher_groups(self, u): + """Set higher groups.""" + assert u == unknown, "setter can only be used to reset cache" + self._higher_groups = unknown + # reset dependent caches: + for s in self._next_lower_groups: + s.higher_groups = unknown + for c in self._direct_cells: + c.groups = unknown + + @property # read-only + def next_lower_groups(self): + """Get next lower groups.""" + return self._next_lower_groups + + @property # read-only + def lower_groups(self): + """Get lower groups.""" + # aggregate recursively: + l = self._next_lower_groups + for s in self._next_lower_groups: + l.update(s.lower_groups) + return l + + @property # read-only + def direct_cells(self): + """Get cells that directly belong to the SocialSystem.""" + return self._direct_cells + + _cells = unknown + """cache, depends on self.direct_cells, self._next_lower_social_systems, + and lowersocial_system.cells""" + @property # read-only + def cells(self): + """Get cells that directly abd indirectly belong to the SocialSystem.""" + if self._cells is unknown: + # aggregate recursively: + self._cells = self.direct_cells + for s in self._next_lower_social_systems: + self._cells.update(s.cells) + return self._cells + + @cells.setter + def cells(self, u): + """Set cells that directly and indirectly belong to the SocialSystem.""" + assert u == unknown, "setter can only be used to reset cache" + self._cells = unknown + # reset dependent caches: + if self.next_higher_social_system is not None: + self.next_higher_social_system.cells = unknown + + _direct_individuals = unknown + """cache, depends on _direct_cells, directcell.individuals""" + @property # read-only + def direct_individuals(self): + """Get resident Individuals not in subsocial_systems.""" + if self._direct_individuals is unknown: + # aggregate from direct_cells: + self._direct_individuals = set() + for c in self._direct_cells: + self._direct_individuals.update(c.individuals) + return self._direct_individuals + + @direct_individuals.setter + def direct_individuals(self, u): + """Set resident Individuals not in subsocial_systems.""" + assert u == unknown, "setter can only be used to reset cache" + self._direct_individuals = unknown + # reset dependent caches: + pass + + _individuals = unknown + """cache, depends on self.cells, cell.individuals""" + @property # read-only + def individuals(self): + """Get direct and indirect resident Individuals.""" + if self._individuals is unknown: + # aggregate from cells: + self._individuals = set() + for c in self.cells: + self._individuals.update(c.individuals) + return self._individuals + + @individuals.setter + def individuals(self, u): + """Set direct and indirect resident Individuals.""" + assert u == unknown, "setter can only be used to reset cache" + self._individuals = unknown + # reset dependent caches: + pass + + # TODO: helper methods for mergers, splits, etc. + + # no process-related methods + + processes = [] # no processes in base + + diff --git a/pycopancore/model_components/base/interface.py b/pycopancore/model_components/base/interface.py index b3424218..15f491fd 100644 --- a/pycopancore/model_components/base/interface.py +++ b/pycopancore/model_components/base/interface.py @@ -288,3 +288,57 @@ class Individual (object, metaclass=_MixinType): SocialSystem.individuals.type = Individual Cell.individuals.type = Individual Individual.acquaintances.type = Individual + + + +class Group (object, metaclass=_MixinType): + """Basic Group interface. + + It contains all variables specified as mandatory ("base variables"). + """ + + # references: + socialsystem = ReferenceVariable("socialsystem", "", type=SocialSystem) + next_higher_group = ReferenceVariable("next higher group", "optional", + allow_none=True) # type is Group, hence it can only be specified after class Group is defined, see below + + + # read-only attributes storing redundant information: + environment = ReferenceVariable("environment", "", type=Environment, + readonly=True) + metabolism = ReferenceVariable("metabolism", "", type=Metabolism, + readonly=True) + culture = ReferenceVariable("culture", "", type=Culture, + readonly=True) + higher_groups = SetVariable( + "higher groups", + "upward list of (in)direct super-Groups", + readonly=True) + next_lower_groups = SetVariable( + "next lower groups", + "set of sub-Groups of next lower level", + readonly=True) + lower_groups = SetVariable( + "lower groups", + "set of all direct and indirect sub-Groups", + readonly=True) + direct_cells = SetVariable("direct cells", "set of direct territory Cells", + readonly=True) + cells = SetVariable("cells", "set of direct and indirect territory Cells", + readonly=True) + direct_individuals = SetVariable( + "direct individuals", + "set of resident Individuals not in subgroups", + readonly=True) + individuals = SetVariable("individuals", + "set of direct or indirect resident Individuals", + readonly=True) + + +# specified only now to avoid recursion errors: +Group.next_higher_group.type = Group +Group.higher_groups.type = Group +Group.next_lower_groups.type = Group +Group.lower_groups.type = Group +#Group.groups.type = Group +#SocialSystem.top_level_groups.type = Group diff --git a/pycopancore/model_components/base/model.py b/pycopancore/model_components/base/model.py index 0a417c29..b354528d 100644 --- a/pycopancore/model_components/base/model.py +++ b/pycopancore/model_components/base/model.py @@ -20,7 +20,7 @@ from .. import abstract from . import interface as I from . import World, Cell, Environment, Individual, Culture, SocialSystem, \ - Metabolism + Metabolism, Group class Model (I.Model, abstract.Model, ModelLogics): @@ -34,5 +34,5 @@ class Model (I.Model, abstract.Model, ModelLogics): # specify entity types and process taxon classes # defined in the base component: - entity_types = [World, Cell, Individual, SocialSystem] + entity_types = [World, Cell, Individual, SocialSystem, Group] process_taxa = [Environment, Culture, Metabolism] From ffc4927e2a85b7f0046c41b67a96be866dd28c16 Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Mon, 2 May 2022 12:15:20 +0200 Subject: [PATCH 02/33] Update group.py Added a possible group_network logic --- pycopancore/data_model/master_data_model/group.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pycopancore/data_model/master_data_model/group.py b/pycopancore/data_model/master_data_model/group.py index c636fff6..5bb23744 100644 --- a/pycopancore/data_model/master_data_model/group.py +++ b/pycopancore/data_model/master_data_model/group.py @@ -2,15 +2,22 @@ """https://github.com/pik-copan/pycopancore/blob/master/docs/framework_documentation/abstract_level/entity_types/group.rst""" -#from . import MET from .. import Variable +from networkx import Graph class Group: #TODO: add a group network possibility (in culture.py (?)) - + group_network = \ + Variable("group network", + """Basic undirected social network between + Groups.""", + ref="https://en.wikipedia.org/wiki/Social_network#Meso_level", + scale='nominal', + datatype=Graph) + has_leader = \ Variable("has a leader", "whether the group has a leader", From 7aebe13745f6e5e8ff274d8b91a7f07749189f8a Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Fri, 13 May 2022 11:20:52 +0200 Subject: [PATCH 03/33] social_system --- .../base/implementation/group.py | 32 +-- .../base/implementation/social_system.py | 12 ++ pycopancore/models/groups_seven_dwarfs.py | 89 ++++++++ studies/run_groups_test_seven_dwarfs.py | 193 ++++++++++++++++++ 4 files changed, 311 insertions(+), 15 deletions(-) create mode 100644 pycopancore/models/groups_seven_dwarfs.py create mode 100644 studies/run_groups_test_seven_dwarfs.py diff --git a/pycopancore/model_components/base/implementation/group.py b/pycopancore/model_components/base/implementation/group.py index 78c71a41..f3aeacd1 100644 --- a/pycopancore/model_components/base/implementation/group.py +++ b/pycopancore/model_components/base/implementation/group.py @@ -29,7 +29,7 @@ class Group (I.Group, abstract.Group): def __init__(self, *, - socialsystem, + social_system, next_higher_group=None, **kwargs ): @@ -47,31 +47,33 @@ def __init__(self, """ super().__init__(**kwargs) # must be the first line + # init and set variables implemented via properties: + self._social_system = None + self.social_system = social_system + self._next_higher_group = None + self.next_higher_group = next_higher_group + # init caches: self._next_lower_group = set() self._direct_cells = set() - # init and set variables implemented via properties: - self._socialsystem = None - self.socialsystem = socialsystem - self._next_higher_group = None - self.next_higher_group = next_higher_group + # getters and setters for references: @property - def socialsystem(self): + def social_system(self): """Get the World the SocialSystem is part of.""" - return self._socialsystem + return self._social_system - @socialsystem.setter - def socialsystem(self, w): + @social_system.setter + def social_system(self, s): """Set the World the SocialSystem is part of.""" - if self._socialsystem is not None: - self._socialsystem._groups.remove(self) - assert isinstance(w, I.SocialSystem), "socialsystem must be of entity type SocialSystem" - w._groups.add(self) - self._socialsystem = w + if self._social_system is not None: + self._social_system._groups.remove(self) + assert isinstance(s, I.SocialSystem), "socialsystem must be of entity type SocialSystem" + s._groups.add(self) + self._social_system = s @property def next_higher_group(self): diff --git a/pycopancore/model_components/base/implementation/social_system.py b/pycopancore/model_components/base/implementation/social_system.py index 33b5ce68..b8dac8e3 100644 --- a/pycopancore/model_components/base/implementation/social_system.py +++ b/pycopancore/model_components/base/implementation/social_system.py @@ -219,6 +219,18 @@ def individuals(self, u): # reset dependent caches: pass + @property # read-only + def groups(self): + """Get the set of all Groups in this SocialSystem.""" + return self._groups + @groups.setter + def groups(self, g): + """Set the World the SocialSystem is part of.""" + if self._groups is not None: + self._groups._social_system.remove(self) + assert isinstance(g, I.Culture), "groups must be of taxon type Group" + g._worlds.add(self) + self._culture = g # TODO: helper methods for mergers, splits, etc. # no process-related methods diff --git a/pycopancore/models/groups_seven_dwarfs.py b/pycopancore/models/groups_seven_dwarfs.py new file mode 100644 index 00000000..31be4f35 --- /dev/null +++ b/pycopancore/models/groups_seven_dwarfs.py @@ -0,0 +1,89 @@ +"""Model class seven_dwarfs.""" + +# This file is part of pycopancore. +# +# Copyright (C) 2016-2017 by COPAN team at Potsdam Institute for Climate +# Impact Research +# +# URL: +# Contact: core@pik-potsdam.de +# License: BSD 2-clause license + +# +# Imports +# + +from .. import base # all models must use the base component + +from ..model_components import seven_dwarfs as sd +from ..model_components import snowwhite as sw + +# entity types: + +# by mixing the above model components' mixin classes of the same name. +# Only compose those entity types and process taxons that the model needs, +# delete the templates for the unneeded ones, and add those for missing ones: + + +class World(sd.World, + base.World): + """World entity type.""" + + pass + + +class SocialSystem(base.SocialSystem): + """SocialSystem entity type.""" + + pass + + +class Cell(sd.Cell, + sw.Cell, + base.Cell): + """Cell entity type.""" + + pass + + +class Individual(sd.Individual, + base.Individual): + """Individual entity type.""" + + pass + + +class SocialSystem(sd.SocialSystem, + base.SocialSystem): + """SocialSystem entity type.""" + + pass + +class Group(base.Group): + """Groups entity type""" + +# process taxa: + +class Culture(sd.Culture, + base.Culture): + """Culture process taxon.""" + + pass + + +# Model class: + +class Model(sd.Model, + sw.Model, + base.Model): + """Class representing the whole model.""" + + name = "Seven dwarfs" + """Name of the model""" + description = "Tutorial model" + """Longer description""" + + entity_types = [World, SocialSystem, Cell, Individual] + """List of entity types used in the model""" + process_taxa = [Culture] + """List of process taxa used in the model""" diff --git a/studies/run_groups_test_seven_dwarfs.py b/studies/run_groups_test_seven_dwarfs.py new file mode 100644 index 00000000..1c241a9b --- /dev/null +++ b/studies/run_groups_test_seven_dwarfs.py @@ -0,0 +1,193 @@ +"""This is the test script for the seven dwarfs step by step tutorial. + +In this version only the Step-process 'aging' of entitytype 'Individual' is +implemented, such that the only relevant attributes of 'Individual' are 'age' +and 'cell'. +""" + +# This file is part of pycopancore. +# +# Copyright (C) 2016-2017 by COPAN team at Potsdam Institute for Climate +# Impact Research +# +# URL: +# Contact: core@pik-potsdam.de +# License: BSD 2-clause license + +import numpy as np +from time import time +import datetime as dt + +import networkx as nx +import matplotlib.pyplot as plt + +import plotly.offline as py +import plotly.graph_objs as go + +import pycopancore.models.groups_seven_dwarfs as M +from pycopancore.runners.runner import Runner + + +# setting timeinterval for run method 'Runner.run()' +timeinterval = 10 +# setting time step to hand to 'Runner.run()' +timestep = .1 +nc = 1 # number of caves +dwarfs = 7 # number of dwarfs + +# instantiate model M (needs to be done in the beginning of each script). +# This configures the model M through 'ModelLogics' in module +# 'base.model_logics' such that initialisation of attributes and entities gets +# possible +model = M.Model() + +# instantiate process taxa culture: +# In this certain case we need 'M.Culture()' for the acquaintance network. +culture = M.Culture() + +# instantiate world: +world = M.World(culture=culture) + +# instantiate one social system: +social_system = M.SocialSystem(world=world) + +# instantiate cells (the caves): +cells = [M.Cell(social_system=social_system, + eating_stock=100 + ) + for c in range(nc) + ] + +# instantiate dwarfs and assigning initial conditions +individuals = [M.Individual(cell=cells[0], + age=0, + beard_length=0, + beard_growth_parameter=0.5, + eating_parameter=.1 + ) for i in range(dwarfs) + ] + +# assigning individuals to cell is not necessary since it is done by +# initializing the individuals in 'base.Individuals' with the 'cell' method + +#instantiate groups +ng = 2 #number of groups (group 1 hates snowwhite, group 2 loves her) +groups = [M.Group(social_system=social_system) + for i in range (ng) + ] + + + +start = time() + +print("done ({})".format(dt.timedelta(seconds=(time() - start)))) + +print('\n runner starting') + +# Define termination signals as list [ signal_method, object_method_works_on ] +# the termination method 'check_for_extinction' must return a boolean +termination_signal = [M.Culture.check_for_extinction, + culture] + +# Define termination_callables as list of all signals +termination_callables = [termination_signal] + +nx.draw(culture.acquaintance_network) +plt.show() + +# Runner is instantiated +r = Runner(model=model, + termination_calls=termination_callables + ) + +start = time() +# run the Runner and saving the return dict in traj +traj = r.run(t_1=timeinterval, dt=timestep, add_to_output=[M.Culture.acquaintance_network]) +runtime = dt.timedelta(seconds=(time() - start)) +print('runtime: {runtime}'.format(**locals())) + +# saving time values to t +t = np.array(traj['t']) +print("max. time step", (t[1:]-t[:-1]).max()) + + +# proceeding for plotting + +# Create List of all dwarfes, not only the ones instantiated before the run, +# but also the one created during the run. + +if M.Individual.idle_entities: + all_dwarfs = M.Individual.instances + M.Individual.idle_entities +else: + all_dwarfs = M.Individual.instances + +individuals_age = np.array([traj[M.Individual.age][dwarf] + for dwarf in all_dwarfs]) + + +individuals_beard_length = np.array([traj[M.Individual.beard_length][dwarf] + for dwarf in all_dwarfs]) + +cell_stock = np.array(traj[M.Cell.eating_stock][cells[0]]) + +t = np.array(traj['t']) + +data_age = [] +print('data age', data_age) +for i, dwarf in enumerate(all_dwarfs): + data_age.append(go.Scatter( + x=t, + y=individuals_age[i], + mode="lines", + name="age of dwarf no. {}".format(i), + line=dict( + color="green", + width=4 + ) + )) + +data_beard_length = [] +print('data beard', data_beard_length) +for i, dwarf in enumerate(all_dwarfs): + data_beard_length.append(go.Scatter( + x=t, + y=individuals_beard_length[i], + mode="lines", + name="beard length of dwarf no. {}".format(i), + line=dict( + color="red", + width=4 + ) + )) + +data_stock = [] +data_stock.append(go.Scatter( + x=t, + y=cell_stock, + mode="lines", + name="stock of cell", + line=dict(color="blue", + width=4 + ) + )) + + +layout = dict(title='seven dwarfs', + xaxis=dict(title='time [yr]'), + yaxis=dict(title='value'), + ) + + +# getting plots of two dwarfs: +fig = dict(data=[data_age[0], data_beard_length[0], data_stock[0]], + layout=layout) +py.plot(fig, filename="our-model-result{}.html".format(0)) + +fig = dict(data=[data_age[5], data_beard_length[5], data_stock[0]], + layout=layout) +py.plot(fig, filename="our-model-result{}.html".format(5)) + +#nx.draw(traj[M.Culture.acquaintance_network][culture][1]) +#plt.show() +for i in range(len(traj['t'])): + print(list(traj[M.Culture.acquaintance_network][culture][i].nodes())) From 710242abdead92887ada253299b5c26f90d398eb Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Tue, 31 May 2022 18:20:55 +0200 Subject: [PATCH 04/33] typo cleanup minor typo and some todos --- pycopancore/data_model/master_data_model/group.py | 4 +++- pycopancore/model_components/base/implementation/group.py | 4 +++- .../model_components/base/implementation/individual.py | 1 + pycopancore/model_components/base/interface.py | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pycopancore/data_model/master_data_model/group.py b/pycopancore/data_model/master_data_model/group.py index 5bb23744..26cf6ce5 100644 --- a/pycopancore/data_model/master_data_model/group.py +++ b/pycopancore/data_model/master_data_model/group.py @@ -8,7 +8,9 @@ class Group: - #TODO: add a group network possibility (in culture.py (?)) + #TODO: add a group network possibility (in culture.py (!)) + #TODO: inter/intra group network + #TODO: specify edges group_network = \ Variable("group network", diff --git a/pycopancore/model_components/base/implementation/group.py b/pycopancore/model_components/base/implementation/group.py index f3aeacd1..0aa1d663 100644 --- a/pycopancore/model_components/base/implementation/group.py +++ b/pycopancore/model_components/base/implementation/group.py @@ -29,7 +29,7 @@ class Group (I.Group, abstract.Group): def __init__(self, *, - social_system, + social_system=None, next_higher_group=None, **kwargs ): @@ -179,6 +179,8 @@ def cells(self, u): if self.next_higher_social_system is not None: self.next_higher_social_system.cells = unknown + #TODO: direct_inds to members + _direct_individuals = unknown """cache, depends on _direct_cells, directcell.individuals""" @property # read-only diff --git a/pycopancore/model_components/base/implementation/individual.py b/pycopancore/model_components/base/implementation/individual.py index 7f9eb2a3..e82c5fc5 100644 --- a/pycopancore/model_components/base/implementation/individual.py +++ b/pycopancore/model_components/base/implementation/individual.py @@ -16,6 +16,7 @@ from .. import interface as I +#TODO: set variable membership class Individual (I.Individual, abstract.Individual): """Individual entity type mixin implementation class. diff --git a/pycopancore/model_components/base/interface.py b/pycopancore/model_components/base/interface.py index 15f491fd..deb77458 100644 --- a/pycopancore/model_components/base/interface.py +++ b/pycopancore/model_components/base/interface.py @@ -298,7 +298,7 @@ class Group (object, metaclass=_MixinType): """ # references: - socialsystem = ReferenceVariable("socialsystem", "", type=SocialSystem) + #social_system = ReferenceVariable("socialsystem", "", type=SocialSystem) next_higher_group = ReferenceVariable("next higher group", "optional", allow_none=True) # type is Group, hence it can only be specified after class Group is defined, see below From 1ffd331b144fb15de98a5429f2c84d022f9e4932 Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Wed, 1 Jun 2022 14:22:50 +0200 Subject: [PATCH 05/33] Fix Social Update had error in runner: TypeError: 'dict_keyiterator' object cannot be interpreted as an integer [...] in numpy.random.mtrand.RandomState.choice ValueError: a must be 1-dimensional or an integer File "C:\Users\bigma\PycharmProjects\pycopancore\pycopancore\model_components\exploit_social_learning\implementation\culture.py", line 42, in social_update agent_j = np.random.choice(self.acquaintance_network.neighbors(agent_i)) with (self.acquaintance_network.neighbors(agent_i)) being FIX: extracting list from key with list() agent_j = np.random.choice(list(self.acquaintance_network.neighbors(agent_i))) --- .../exploit_social_learning/implementation/culture.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycopancore/model_components/exploit_social_learning/implementation/culture.py b/pycopancore/model_components/exploit_social_learning/implementation/culture.py index 4cf957c5..734b0ccf 100644 --- a/pycopancore/model_components/exploit_social_learning/implementation/culture.py +++ b/pycopancore/model_components/exploit_social_learning/implementation/culture.py @@ -40,7 +40,7 @@ def social_update(self, t): # Step (1) if self.acquaintance_network.neighbors(agent_i): agent_j = np.random.choice( - self.acquaintance_network.neighbors(agent_i)) + list(self.acquaintance_network.neighbors(agent_i))) # Step (2): Compare strategies of i and j: # If they are the same, do nothing. Else change i's strategy. if agent_i.strategy != agent_j.strategy: From c0994c4009820713550cb13135e2a7fafb42c5c8 Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Wed, 8 Jun 2022 15:52:00 +0200 Subject: [PATCH 06/33] Implementing Individual to Group relationships via a group_membership_network. Error while adding group to a culture (group needs to "live" somewhere, similar to cells for individuals that then give memberships to worlds and so on. --- .../data_model/master_data_model/culture.py | 19 ++- .../data_model/master_data_model/group.py | 9 +- .../base/implementation/culture.py | 13 +- .../base/implementation/group.py | 148 ++++++++---------- .../base/implementation/individual.py | 10 +- .../model_components/base/interface.py | 27 ++-- studies/run_groups_test_seven_dwarfs.py | 99 ++---------- 7 files changed, 132 insertions(+), 193 deletions(-) diff --git a/pycopancore/data_model/master_data_model/culture.py b/pycopancore/data_model/master_data_model/culture.py index 755b8047..d89d2c82 100644 --- a/pycopancore/data_model/master_data_model/culture.py +++ b/pycopancore/data_model/master_data_model/culture.py @@ -94,7 +94,24 @@ class Culture: scale='nominal', datatype=set) - + + # group entity related networks + + inter_group_network = \ + Variable("inter group network", + """Basic undirected social network between + Groups.""", + ref="https://en.wikipedia.org/wiki/Social_network#Meso_level", + scale='nominal', + datatype=Graph) + + group_membership_network = \ + Variable("group membership network", + """Directed network from individual to group that + signifies membership (to avoid problems due to n to n relation)""", + scale='nominal', + datatype=DiGraph) + # socio-cultural traits that may occur on different levels: is_environmentally_friendly = \ diff --git a/pycopancore/data_model/master_data_model/group.py b/pycopancore/data_model/master_data_model/group.py index 26cf6ce5..cd7354cb 100644 --- a/pycopancore/data_model/master_data_model/group.py +++ b/pycopancore/data_model/master_data_model/group.py @@ -8,15 +8,12 @@ class Group: - #TODO: add a group network possibility (in culture.py (!)) - #TODO: inter/intra group network #TODO: specify edges - group_network = \ - Variable("group network", + intra_group_network = \ + Variable("intra group network", """Basic undirected social network between - Groups.""", - ref="https://en.wikipedia.org/wiki/Social_network#Meso_level", + Group members.""", scale='nominal', datatype=Graph) diff --git a/pycopancore/model_components/base/implementation/culture.py b/pycopancore/model_components/base/implementation/culture.py index 6f342fcc..79cf5799 100644 --- a/pycopancore/model_components/base/implementation/culture.py +++ b/pycopancore/model_components/base/implementation/culture.py @@ -14,8 +14,7 @@ from .. import interface as I -from networkx import Graph - +from networkx import Graph, DiGraph class Culture (I.Culture, abstract.Culture): """Culture process taxon mixin implementation class.""" @@ -25,6 +24,7 @@ class Culture (I.Culture, abstract.Culture): def __init__(self, *, acquaintance_network=None, + group_membership_network=None, **kwargs): """Initialize the unique instance of Culture. @@ -33,6 +33,9 @@ def __init__(self, acquaintance_network: Graph The Network of acquaintances which is managed by Culture (default is None) + group_membership_network: DiGraph + The Network between Individiuals and groups, which is managed + by Culture (default is None) **kwargs keyword arguments passed to super() @@ -43,6 +46,12 @@ def __init__(self, acquaintance_network = Graph() assert isinstance(acquaintance_network, Graph) self.acquaintance_network = acquaintance_network + + if group_membership_network is None: + group_membership_network = DiGraph() + assert isinstance(group_membership_network, DiGraph) + self.group_membership_network = group_membership_network + self._worlds = set() # make sure all variable values are valid: diff --git a/pycopancore/model_components/base/implementation/group.py b/pycopancore/model_components/base/implementation/group.py index 0aa1d663..22ce9ebd 100644 --- a/pycopancore/model_components/base/implementation/group.py +++ b/pycopancore/model_components/base/implementation/group.py @@ -29,7 +29,7 @@ class Group (I.Group, abstract.Group): def __init__(self, *, - social_system=None, + culture=None, next_higher_group=None, **kwargs ): @@ -48,8 +48,10 @@ def __init__(self, super().__init__(**kwargs) # must be the first line # init and set variables implemented via properties: - self._social_system = None - self.social_system = social_system + self._culture = None + self.culture = culture + # self._social_system = None + # self.social_system = social_system self._next_higher_group = None self.next_higher_group = next_higher_group @@ -57,23 +59,48 @@ def __init__(self, self._next_lower_group = set() self._direct_cells = set() + if self.culture: + self.culture.group_membership_network.add_node(self) + + def deactivate(self): + """Deactivate an individual. + + In particular, deregister from all networks. + + """ + # deregister from all networks: + if self.culture: + self.culture.group_membership_network.remove_node(self) + super().deactivate() # must be the last line + + def reactivate(self): + """Reactivate an individual. + + In particular, deregister with all mandatory networks. + + """ + super().reactivate() # must be the first line + # reregister with all mandatory networks: + if self.culture: + self.culture.group_membership_network.add_node(self) # getters and setters for references: - @property - def social_system(self): - """Get the World the SocialSystem is part of.""" - return self._social_system - - @social_system.setter - def social_system(self, s): - """Set the World the SocialSystem is part of.""" - if self._social_system is not None: - self._social_system._groups.remove(self) - assert isinstance(s, I.SocialSystem), "socialsystem must be of entity type SocialSystem" - s._groups.add(self) - self._social_system = s + + # @property + # def social_system(self): + # """Get the SocialSystem the Group is part of.""" + # return self._social_system + + # @social_system.setter + # def social_system(self, s): + # """Set the SocialSystem the Group is part of.""" + # if self._social_system is not None: + # self._social_system._groups.remove(self) + # assert isinstance(s, I.SocialSystem), "socialsystem must be of entity type SocialSystem" + # s._groups.add(self) + # self._social_system = s @property def next_higher_group(self): @@ -109,10 +136,22 @@ def metabolism(self): """Get the Metabolism of which the Group is a part.""" return self._world.metabolism - @property # read-only + @property def culture(self): - """Get the Culture of which the Group is a part.""" - return self._world.culture + """Get groups's culture.""" + return self._culture + + @culture.setter + def culture(self, c): + """Set groups's culture.""" + if self._culture is not None: + # first deregister from previous culture's list of worlds: + self._culture.groups.remove(self) + if c is not None: + assert isinstance(c, I.Culture), \ + "Culture must be taxon type Culture" + c._groups.add(self) + self._culture = c _higher_groups = unknown """cache, depends on self.next_higher_group @@ -152,74 +191,11 @@ def lower_groups(self): l.update(s.lower_groups) return l - @property # read-only - def direct_cells(self): - """Get cells that directly belong to the SocialSystem.""" - return self._direct_cells - - _cells = unknown - """cache, depends on self.direct_cells, self._next_lower_social_systems, - and lowersocial_system.cells""" - @property # read-only - def cells(self): - """Get cells that directly abd indirectly belong to the SocialSystem.""" - if self._cells is unknown: - # aggregate recursively: - self._cells = self.direct_cells - for s in self._next_lower_social_systems: - self._cells.update(s.cells) - return self._cells - - @cells.setter - def cells(self, u): - """Set cells that directly and indirectly belong to the SocialSystem.""" - assert u == unknown, "setter can only be used to reset cache" - self._cells = unknown - # reset dependent caches: - if self.next_higher_social_system is not None: - self.next_higher_social_system.cells = unknown - - #TODO: direct_inds to members - - _direct_individuals = unknown - """cache, depends on _direct_cells, directcell.individuals""" - @property # read-only - def direct_individuals(self): - """Get resident Individuals not in subsocial_systems.""" - if self._direct_individuals is unknown: - # aggregate from direct_cells: - self._direct_individuals = set() - for c in self._direct_cells: - self._direct_individuals.update(c.individuals) - return self._direct_individuals - - @direct_individuals.setter - def direct_individuals(self, u): - """Set resident Individuals not in subsocial_systems.""" - assert u == unknown, "setter can only be used to reset cache" - self._direct_individuals = unknown - # reset dependent caches: - pass + @property + def group_members(self): + """Get the set of Individuals associated with this Group.""" + return self.culture.group_membership_network.predecessors(self) # .predeccessors as network is directed from inds to groups - _individuals = unknown - """cache, depends on self.cells, cell.individuals""" - @property # read-only - def individuals(self): - """Get direct and indirect resident Individuals.""" - if self._individuals is unknown: - # aggregate from cells: - self._individuals = set() - for c in self.cells: - self._individuals.update(c.individuals) - return self._individuals - - @individuals.setter - def individuals(self, u): - """Set direct and indirect resident Individuals.""" - assert u == unknown, "setter can only be used to reset cache" - self._individuals = unknown - # reset dependent caches: - pass # TODO: helper methods for mergers, splits, etc. diff --git a/pycopancore/model_components/base/implementation/individual.py b/pycopancore/model_components/base/implementation/individual.py index e82c5fc5..5eb11073 100644 --- a/pycopancore/model_components/base/implementation/individual.py +++ b/pycopancore/model_components/base/implementation/individual.py @@ -16,8 +16,6 @@ from .. import interface as I -#TODO: set variable membership - class Individual (I.Individual, abstract.Individual): """Individual entity type mixin implementation class. @@ -56,6 +54,7 @@ def __init__(self, # register with all mandatory networks: if self.culture: self.culture.acquaintance_network.add_node(self) + self.culture.group_membership_network.add_node(self) # TODO: does this work with DiGraph? def deactivate(self): """Deactivate an individual. @@ -66,6 +65,7 @@ def deactivate(self): # deregister from all networks: if self.culture: self.culture.acquaintance_network.remove_node(self) + self.culture.group_membership_network.remove_node(self) super().deactivate() # must be the last line def reactivate(self): @@ -78,6 +78,7 @@ def reactivate(self): # reregister with all mandatory networks: if self.culture: self.culture.acquaintance_network.add_node(self) + self.culture.group_membership_network.add_node(self) # getters and setters for references: @@ -157,6 +158,11 @@ def acquaintances(self): """Get the set of Individuals the Individual is acquainted with.""" return self.culture.acquaintance_network.neighbors(self) + @property + def group_memberships(self): + """Get the set of Groups the Individual is associated with.""" + return self.culture.group_membership_network.successors(self) # .successors as network is directed from inds to groups + # no process-related methods processes = [] # no processes in base diff --git a/pycopancore/model_components/base/interface.py b/pycopancore/model_components/base/interface.py index deb77458..5d1676be 100644 --- a/pycopancore/model_components/base/interface.py +++ b/pycopancore/model_components/base/interface.py @@ -69,6 +69,8 @@ class Culture (object): acquaintance_network = CUL.acquaintance_network + group_membership_network = CUL.group_membership_network + # read-only attributes storing redundant information: worlds = SetVariable("worlds", "Set of Worlds governed by this Culture", @@ -271,6 +273,10 @@ class Individual (object, metaclass=_MixinType): "set of Individuals this one is acquainted with", readonly=True) + group_memberships = SetVariable("group memberships", + "set of Groups this one is associated with", + readonly=True) + # TODO: specify Variable objects for the following: population_share = None """share of social_system's direct population represented by this individual""" @@ -291,6 +297,7 @@ class Individual (object, metaclass=_MixinType): + class Group (object, metaclass=_MixinType): """Basic Group interface. @@ -322,17 +329,13 @@ class Group (object, metaclass=_MixinType): "lower groups", "set of all direct and indirect sub-Groups", readonly=True) - direct_cells = SetVariable("direct cells", "set of direct territory Cells", - readonly=True) - cells = SetVariable("cells", "set of direct and indirect territory Cells", - readonly=True) - direct_individuals = SetVariable( - "direct individuals", - "set of resident Individuals not in subgroups", - readonly=True) - individuals = SetVariable("individuals", - "set of direct or indirect resident Individuals", - readonly=True) + + group_members = SetVariable( + "group members", + "set of all individuals associated with the group", + readonly=True + ) + # specified only now to avoid recursion errors: @@ -340,5 +343,7 @@ class Group (object, metaclass=_MixinType): Group.higher_groups.type = Group Group.next_lower_groups.type = Group Group.lower_groups.type = Group +# Individual.group_memberships.type = Group # TODO: assert this is at the right place +# Group.group_members.type = Individual #Group.groups.type = Group #SocialSystem.top_level_groups.type = Group diff --git a/studies/run_groups_test_seven_dwarfs.py b/studies/run_groups_test_seven_dwarfs.py index 1c241a9b..933a0aba 100644 --- a/studies/run_groups_test_seven_dwarfs.py +++ b/studies/run_groups_test_seven_dwarfs.py @@ -67,16 +67,20 @@ ) for i in range(dwarfs) ] +print("\n The individuals: \n") +print(individuals) +print("\n \n") + # assigning individuals to cell is not necessary since it is done by # initializing the individuals in 'base.Individuals' with the 'cell' method #instantiate groups -ng = 2 #number of groups (group 1 hates snowwhite, group 2 loves her) -groups = [M.Group(social_system=social_system) - for i in range (ng) - ] - +ng = 5 #number of groups +groups = [M.Group(culture=culture) for i in range (ng)] +print("\n The groups: \n") +print(groups) +print("\n \n") start = time() @@ -84,6 +88,11 @@ print('\n runner starting') +# first test for group network +nx.draw(culture.group_membership_network) + +plt.show() + # Define termination signals as list [ signal_method, object_method_works_on ] # the termination method 'check_for_extinction' must return a boolean termination_signal = [M.Culture.check_for_extinction, @@ -111,83 +120,3 @@ print("max. time step", (t[1:]-t[:-1]).max()) -# proceeding for plotting - -# Create List of all dwarfes, not only the ones instantiated before the run, -# but also the one created during the run. - -if M.Individual.idle_entities: - all_dwarfs = M.Individual.instances + M.Individual.idle_entities -else: - all_dwarfs = M.Individual.instances - -individuals_age = np.array([traj[M.Individual.age][dwarf] - for dwarf in all_dwarfs]) - - -individuals_beard_length = np.array([traj[M.Individual.beard_length][dwarf] - for dwarf in all_dwarfs]) - -cell_stock = np.array(traj[M.Cell.eating_stock][cells[0]]) - -t = np.array(traj['t']) - -data_age = [] -print('data age', data_age) -for i, dwarf in enumerate(all_dwarfs): - data_age.append(go.Scatter( - x=t, - y=individuals_age[i], - mode="lines", - name="age of dwarf no. {}".format(i), - line=dict( - color="green", - width=4 - ) - )) - -data_beard_length = [] -print('data beard', data_beard_length) -for i, dwarf in enumerate(all_dwarfs): - data_beard_length.append(go.Scatter( - x=t, - y=individuals_beard_length[i], - mode="lines", - name="beard length of dwarf no. {}".format(i), - line=dict( - color="red", - width=4 - ) - )) - -data_stock = [] -data_stock.append(go.Scatter( - x=t, - y=cell_stock, - mode="lines", - name="stock of cell", - line=dict(color="blue", - width=4 - ) - )) - - -layout = dict(title='seven dwarfs', - xaxis=dict(title='time [yr]'), - yaxis=dict(title='value'), - ) - - -# getting plots of two dwarfs: -fig = dict(data=[data_age[0], data_beard_length[0], data_stock[0]], - layout=layout) -py.plot(fig, filename="our-model-result{}.html".format(0)) - -fig = dict(data=[data_age[5], data_beard_length[5], data_stock[0]], - layout=layout) -py.plot(fig, filename="our-model-result{}.html".format(5)) - -#nx.draw(traj[M.Culture.acquaintance_network][culture][1]) -#plt.show() -for i in range(len(traj['t'])): - print(list(traj[M.Culture.acquaintance_network][culture][i].nodes())) From 28952090465e016a27acb4988eb3c3e5cc7ab4a3 Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Wed, 8 Jun 2022 16:06:00 +0200 Subject: [PATCH 07/33] Implement Network Solution for Group Memberships Using an directed from Individual to Group Network to handle n to n group memberships. --- .../data_model/master_data_model/culture.py | 19 ++- .../data_model/master_data_model/group.py | 9 +- .../base/implementation/culture.py | 13 +- .../base/implementation/group.py | 148 ++++++++---------- .../base/implementation/individual.py | 10 +- .../model_components/base/interface.py | 27 ++-- 6 files changed, 118 insertions(+), 108 deletions(-) diff --git a/pycopancore/data_model/master_data_model/culture.py b/pycopancore/data_model/master_data_model/culture.py index 755b8047..d89d2c82 100644 --- a/pycopancore/data_model/master_data_model/culture.py +++ b/pycopancore/data_model/master_data_model/culture.py @@ -94,7 +94,24 @@ class Culture: scale='nominal', datatype=set) - + + # group entity related networks + + inter_group_network = \ + Variable("inter group network", + """Basic undirected social network between + Groups.""", + ref="https://en.wikipedia.org/wiki/Social_network#Meso_level", + scale='nominal', + datatype=Graph) + + group_membership_network = \ + Variable("group membership network", + """Directed network from individual to group that + signifies membership (to avoid problems due to n to n relation)""", + scale='nominal', + datatype=DiGraph) + # socio-cultural traits that may occur on different levels: is_environmentally_friendly = \ diff --git a/pycopancore/data_model/master_data_model/group.py b/pycopancore/data_model/master_data_model/group.py index 26cf6ce5..cd7354cb 100644 --- a/pycopancore/data_model/master_data_model/group.py +++ b/pycopancore/data_model/master_data_model/group.py @@ -8,15 +8,12 @@ class Group: - #TODO: add a group network possibility (in culture.py (!)) - #TODO: inter/intra group network #TODO: specify edges - group_network = \ - Variable("group network", + intra_group_network = \ + Variable("intra group network", """Basic undirected social network between - Groups.""", - ref="https://en.wikipedia.org/wiki/Social_network#Meso_level", + Group members.""", scale='nominal', datatype=Graph) diff --git a/pycopancore/model_components/base/implementation/culture.py b/pycopancore/model_components/base/implementation/culture.py index 6f342fcc..79cf5799 100644 --- a/pycopancore/model_components/base/implementation/culture.py +++ b/pycopancore/model_components/base/implementation/culture.py @@ -14,8 +14,7 @@ from .. import interface as I -from networkx import Graph - +from networkx import Graph, DiGraph class Culture (I.Culture, abstract.Culture): """Culture process taxon mixin implementation class.""" @@ -25,6 +24,7 @@ class Culture (I.Culture, abstract.Culture): def __init__(self, *, acquaintance_network=None, + group_membership_network=None, **kwargs): """Initialize the unique instance of Culture. @@ -33,6 +33,9 @@ def __init__(self, acquaintance_network: Graph The Network of acquaintances which is managed by Culture (default is None) + group_membership_network: DiGraph + The Network between Individiuals and groups, which is managed + by Culture (default is None) **kwargs keyword arguments passed to super() @@ -43,6 +46,12 @@ def __init__(self, acquaintance_network = Graph() assert isinstance(acquaintance_network, Graph) self.acquaintance_network = acquaintance_network + + if group_membership_network is None: + group_membership_network = DiGraph() + assert isinstance(group_membership_network, DiGraph) + self.group_membership_network = group_membership_network + self._worlds = set() # make sure all variable values are valid: diff --git a/pycopancore/model_components/base/implementation/group.py b/pycopancore/model_components/base/implementation/group.py index 0aa1d663..22ce9ebd 100644 --- a/pycopancore/model_components/base/implementation/group.py +++ b/pycopancore/model_components/base/implementation/group.py @@ -29,7 +29,7 @@ class Group (I.Group, abstract.Group): def __init__(self, *, - social_system=None, + culture=None, next_higher_group=None, **kwargs ): @@ -48,8 +48,10 @@ def __init__(self, super().__init__(**kwargs) # must be the first line # init and set variables implemented via properties: - self._social_system = None - self.social_system = social_system + self._culture = None + self.culture = culture + # self._social_system = None + # self.social_system = social_system self._next_higher_group = None self.next_higher_group = next_higher_group @@ -57,23 +59,48 @@ def __init__(self, self._next_lower_group = set() self._direct_cells = set() + if self.culture: + self.culture.group_membership_network.add_node(self) + + def deactivate(self): + """Deactivate an individual. + + In particular, deregister from all networks. + + """ + # deregister from all networks: + if self.culture: + self.culture.group_membership_network.remove_node(self) + super().deactivate() # must be the last line + + def reactivate(self): + """Reactivate an individual. + + In particular, deregister with all mandatory networks. + + """ + super().reactivate() # must be the first line + # reregister with all mandatory networks: + if self.culture: + self.culture.group_membership_network.add_node(self) # getters and setters for references: - @property - def social_system(self): - """Get the World the SocialSystem is part of.""" - return self._social_system - - @social_system.setter - def social_system(self, s): - """Set the World the SocialSystem is part of.""" - if self._social_system is not None: - self._social_system._groups.remove(self) - assert isinstance(s, I.SocialSystem), "socialsystem must be of entity type SocialSystem" - s._groups.add(self) - self._social_system = s + + # @property + # def social_system(self): + # """Get the SocialSystem the Group is part of.""" + # return self._social_system + + # @social_system.setter + # def social_system(self, s): + # """Set the SocialSystem the Group is part of.""" + # if self._social_system is not None: + # self._social_system._groups.remove(self) + # assert isinstance(s, I.SocialSystem), "socialsystem must be of entity type SocialSystem" + # s._groups.add(self) + # self._social_system = s @property def next_higher_group(self): @@ -109,10 +136,22 @@ def metabolism(self): """Get the Metabolism of which the Group is a part.""" return self._world.metabolism - @property # read-only + @property def culture(self): - """Get the Culture of which the Group is a part.""" - return self._world.culture + """Get groups's culture.""" + return self._culture + + @culture.setter + def culture(self, c): + """Set groups's culture.""" + if self._culture is not None: + # first deregister from previous culture's list of worlds: + self._culture.groups.remove(self) + if c is not None: + assert isinstance(c, I.Culture), \ + "Culture must be taxon type Culture" + c._groups.add(self) + self._culture = c _higher_groups = unknown """cache, depends on self.next_higher_group @@ -152,74 +191,11 @@ def lower_groups(self): l.update(s.lower_groups) return l - @property # read-only - def direct_cells(self): - """Get cells that directly belong to the SocialSystem.""" - return self._direct_cells - - _cells = unknown - """cache, depends on self.direct_cells, self._next_lower_social_systems, - and lowersocial_system.cells""" - @property # read-only - def cells(self): - """Get cells that directly abd indirectly belong to the SocialSystem.""" - if self._cells is unknown: - # aggregate recursively: - self._cells = self.direct_cells - for s in self._next_lower_social_systems: - self._cells.update(s.cells) - return self._cells - - @cells.setter - def cells(self, u): - """Set cells that directly and indirectly belong to the SocialSystem.""" - assert u == unknown, "setter can only be used to reset cache" - self._cells = unknown - # reset dependent caches: - if self.next_higher_social_system is not None: - self.next_higher_social_system.cells = unknown - - #TODO: direct_inds to members - - _direct_individuals = unknown - """cache, depends on _direct_cells, directcell.individuals""" - @property # read-only - def direct_individuals(self): - """Get resident Individuals not in subsocial_systems.""" - if self._direct_individuals is unknown: - # aggregate from direct_cells: - self._direct_individuals = set() - for c in self._direct_cells: - self._direct_individuals.update(c.individuals) - return self._direct_individuals - - @direct_individuals.setter - def direct_individuals(self, u): - """Set resident Individuals not in subsocial_systems.""" - assert u == unknown, "setter can only be used to reset cache" - self._direct_individuals = unknown - # reset dependent caches: - pass + @property + def group_members(self): + """Get the set of Individuals associated with this Group.""" + return self.culture.group_membership_network.predecessors(self) # .predeccessors as network is directed from inds to groups - _individuals = unknown - """cache, depends on self.cells, cell.individuals""" - @property # read-only - def individuals(self): - """Get direct and indirect resident Individuals.""" - if self._individuals is unknown: - # aggregate from cells: - self._individuals = set() - for c in self.cells: - self._individuals.update(c.individuals) - return self._individuals - - @individuals.setter - def individuals(self, u): - """Set direct and indirect resident Individuals.""" - assert u == unknown, "setter can only be used to reset cache" - self._individuals = unknown - # reset dependent caches: - pass # TODO: helper methods for mergers, splits, etc. diff --git a/pycopancore/model_components/base/implementation/individual.py b/pycopancore/model_components/base/implementation/individual.py index e82c5fc5..5eb11073 100644 --- a/pycopancore/model_components/base/implementation/individual.py +++ b/pycopancore/model_components/base/implementation/individual.py @@ -16,8 +16,6 @@ from .. import interface as I -#TODO: set variable membership - class Individual (I.Individual, abstract.Individual): """Individual entity type mixin implementation class. @@ -56,6 +54,7 @@ def __init__(self, # register with all mandatory networks: if self.culture: self.culture.acquaintance_network.add_node(self) + self.culture.group_membership_network.add_node(self) # TODO: does this work with DiGraph? def deactivate(self): """Deactivate an individual. @@ -66,6 +65,7 @@ def deactivate(self): # deregister from all networks: if self.culture: self.culture.acquaintance_network.remove_node(self) + self.culture.group_membership_network.remove_node(self) super().deactivate() # must be the last line def reactivate(self): @@ -78,6 +78,7 @@ def reactivate(self): # reregister with all mandatory networks: if self.culture: self.culture.acquaintance_network.add_node(self) + self.culture.group_membership_network.add_node(self) # getters and setters for references: @@ -157,6 +158,11 @@ def acquaintances(self): """Get the set of Individuals the Individual is acquainted with.""" return self.culture.acquaintance_network.neighbors(self) + @property + def group_memberships(self): + """Get the set of Groups the Individual is associated with.""" + return self.culture.group_membership_network.successors(self) # .successors as network is directed from inds to groups + # no process-related methods processes = [] # no processes in base diff --git a/pycopancore/model_components/base/interface.py b/pycopancore/model_components/base/interface.py index deb77458..5d1676be 100644 --- a/pycopancore/model_components/base/interface.py +++ b/pycopancore/model_components/base/interface.py @@ -69,6 +69,8 @@ class Culture (object): acquaintance_network = CUL.acquaintance_network + group_membership_network = CUL.group_membership_network + # read-only attributes storing redundant information: worlds = SetVariable("worlds", "Set of Worlds governed by this Culture", @@ -271,6 +273,10 @@ class Individual (object, metaclass=_MixinType): "set of Individuals this one is acquainted with", readonly=True) + group_memberships = SetVariable("group memberships", + "set of Groups this one is associated with", + readonly=True) + # TODO: specify Variable objects for the following: population_share = None """share of social_system's direct population represented by this individual""" @@ -291,6 +297,7 @@ class Individual (object, metaclass=_MixinType): + class Group (object, metaclass=_MixinType): """Basic Group interface. @@ -322,17 +329,13 @@ class Group (object, metaclass=_MixinType): "lower groups", "set of all direct and indirect sub-Groups", readonly=True) - direct_cells = SetVariable("direct cells", "set of direct territory Cells", - readonly=True) - cells = SetVariable("cells", "set of direct and indirect territory Cells", - readonly=True) - direct_individuals = SetVariable( - "direct individuals", - "set of resident Individuals not in subgroups", - readonly=True) - individuals = SetVariable("individuals", - "set of direct or indirect resident Individuals", - readonly=True) + + group_members = SetVariable( + "group members", + "set of all individuals associated with the group", + readonly=True + ) + # specified only now to avoid recursion errors: @@ -340,5 +343,7 @@ class Group (object, metaclass=_MixinType): Group.higher_groups.type = Group Group.next_lower_groups.type = Group Group.lower_groups.type = Group +# Individual.group_memberships.type = Group # TODO: assert this is at the right place +# Group.group_members.type = Individual #Group.groups.type = Group #SocialSystem.top_level_groups.type = Group From 9c473e1ed26d3f23b5a254b6b93446e9fbf241a4 Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Wed, 8 Jun 2022 16:08:15 +0200 Subject: [PATCH 08/33] Update run_groups_test_seven_dwarfs.py --- studies/run_groups_test_seven_dwarfs.py | 99 ++++--------------------- 1 file changed, 14 insertions(+), 85 deletions(-) diff --git a/studies/run_groups_test_seven_dwarfs.py b/studies/run_groups_test_seven_dwarfs.py index 1c241a9b..933a0aba 100644 --- a/studies/run_groups_test_seven_dwarfs.py +++ b/studies/run_groups_test_seven_dwarfs.py @@ -67,16 +67,20 @@ ) for i in range(dwarfs) ] +print("\n The individuals: \n") +print(individuals) +print("\n \n") + # assigning individuals to cell is not necessary since it is done by # initializing the individuals in 'base.Individuals' with the 'cell' method #instantiate groups -ng = 2 #number of groups (group 1 hates snowwhite, group 2 loves her) -groups = [M.Group(social_system=social_system) - for i in range (ng) - ] - +ng = 5 #number of groups +groups = [M.Group(culture=culture) for i in range (ng)] +print("\n The groups: \n") +print(groups) +print("\n \n") start = time() @@ -84,6 +88,11 @@ print('\n runner starting') +# first test for group network +nx.draw(culture.group_membership_network) + +plt.show() + # Define termination signals as list [ signal_method, object_method_works_on ] # the termination method 'check_for_extinction' must return a boolean termination_signal = [M.Culture.check_for_extinction, @@ -111,83 +120,3 @@ print("max. time step", (t[1:]-t[:-1]).max()) -# proceeding for plotting - -# Create List of all dwarfes, not only the ones instantiated before the run, -# but also the one created during the run. - -if M.Individual.idle_entities: - all_dwarfs = M.Individual.instances + M.Individual.idle_entities -else: - all_dwarfs = M.Individual.instances - -individuals_age = np.array([traj[M.Individual.age][dwarf] - for dwarf in all_dwarfs]) - - -individuals_beard_length = np.array([traj[M.Individual.beard_length][dwarf] - for dwarf in all_dwarfs]) - -cell_stock = np.array(traj[M.Cell.eating_stock][cells[0]]) - -t = np.array(traj['t']) - -data_age = [] -print('data age', data_age) -for i, dwarf in enumerate(all_dwarfs): - data_age.append(go.Scatter( - x=t, - y=individuals_age[i], - mode="lines", - name="age of dwarf no. {}".format(i), - line=dict( - color="green", - width=4 - ) - )) - -data_beard_length = [] -print('data beard', data_beard_length) -for i, dwarf in enumerate(all_dwarfs): - data_beard_length.append(go.Scatter( - x=t, - y=individuals_beard_length[i], - mode="lines", - name="beard length of dwarf no. {}".format(i), - line=dict( - color="red", - width=4 - ) - )) - -data_stock = [] -data_stock.append(go.Scatter( - x=t, - y=cell_stock, - mode="lines", - name="stock of cell", - line=dict(color="blue", - width=4 - ) - )) - - -layout = dict(title='seven dwarfs', - xaxis=dict(title='time [yr]'), - yaxis=dict(title='value'), - ) - - -# getting plots of two dwarfs: -fig = dict(data=[data_age[0], data_beard_length[0], data_stock[0]], - layout=layout) -py.plot(fig, filename="our-model-result{}.html".format(0)) - -fig = dict(data=[data_age[5], data_beard_length[5], data_stock[0]], - layout=layout) -py.plot(fig, filename="our-model-result{}.html".format(5)) - -#nx.draw(traj[M.Culture.acquaintance_network][culture][1]) -#plt.show() -for i in range(len(traj['t'])): - print(list(traj[M.Culture.acquaintance_network][culture][i].nodes())) From fbb26f6e6a3295b578c62ab93174b0dcd689d3dc Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Wed, 8 Jun 2022 16:38:05 +0200 Subject: [PATCH 09/33] Group now gets culture and further things over being part of a world. --- .../base/implementation/group.py | 161 ++++++++++-------- .../model_components/base/interface.py | 39 +++-- studies/run_groups_test_seven_dwarfs.py | 17 +- 3 files changed, 123 insertions(+), 94 deletions(-) diff --git a/pycopancore/model_components/base/implementation/group.py b/pycopancore/model_components/base/implementation/group.py index 22ce9ebd..37684ac7 100644 --- a/pycopancore/model_components/base/implementation/group.py +++ b/pycopancore/model_components/base/implementation/group.py @@ -29,7 +29,8 @@ class Group (I.Group, abstract.Group): def __init__(self, *, - culture=None, + world, + # culture=None, next_higher_group=None, **kwargs ): @@ -48,8 +49,11 @@ def __init__(self, super().__init__(**kwargs) # must be the first line # init and set variables implemented via properties: - self._culture = None - self.culture = culture + # self._culture = None + # self.culture = culture + self._world = None + self.world = world + # self._social_system = None # self.social_system = social_system self._next_higher_group = None @@ -87,6 +91,19 @@ def reactivate(self): # getters and setters for references: + @property + def world(self): + """Get the World the Group is part of.""" + return self._world + + @world.setter + def world(self, w): + """Set the World the Group is part of.""" + if self._world is not None: + self._world._social_systems.remove(self) + assert isinstance(w, I.World), "world must be of entity type World" + w._social_systems.add(self) + self._world = w # @property # def social_system(self): @@ -102,27 +119,27 @@ def reactivate(self): # s._groups.add(self) # self._social_system = s - @property - def next_higher_group(self): - """Get next higher group.""" - return self._next_higher_group - - @next_higher_group.setter - def next_higher_group(self, s): - """Set next higher group.""" - if self._next_higher_group is not None: - self._next_higher_group._next_lower_groups.remove(self) - # reset dependent cache: - self._next_higher_group.cells = unknown - if s is not None: - assert isinstance(s, I.Group), \ - "next_higher_group must be of entity type group" - s._next_lower_groups.add(self) - # reset dependent cache: - s.cells = unknown - self._next_higher_group = s + # @property + # def next_higher_group(self): + # """Get next higher group.""" + # return self._next_higher_group + + # @next_higher_group.setter + # def next_higher_group(self, s): + # """Set next higher group.""" + # if self._next_higher_group is not None: + # self._next_higher_group._next_lower_groups.remove(self) + # reset dependent cache: + # self._next_higher_group.cells = unknown + # if s is not None: + # assert isinstance(s, I.Group), \ + # "next_higher_group must be of entity type group" + # s._next_lower_groups.add(self) + # reset dependent cache: + # s.cells = unknown + # self._next_higher_group = s # reset dependent caches: - self.higher_groups = unknown + # self.higher_groups = unknown # getters for backwards references and convenience variables: @@ -136,60 +153,60 @@ def metabolism(self): """Get the Metabolism of which the Group is a part.""" return self._world.metabolism - @property - def culture(self): - """Get groups's culture.""" - return self._culture - - @culture.setter - def culture(self, c): - """Set groups's culture.""" - if self._culture is not None: - # first deregister from previous culture's list of worlds: - self._culture.groups.remove(self) - if c is not None: - assert isinstance(c, I.Culture), \ - "Culture must be taxon type Culture" - c._groups.add(self) - self._culture = c - - _higher_groups = unknown + # @property + # def culture(self): + # """Get groups's culture.""" + # return self._culture + + # @culture.setter + # def culture(self, c): + # """Set groups's culture.""" + # if self._culture is not None: + # first deregister from previous culture's list of worlds: + # self._culture.groups.remove(self) + # if c is not None: + # assert isinstance(c, I.Culture), \ + # "Culture must be taxon type Culture" + # c._groups.add(self) + # self._culture = c + + # _higher_groups = unknown """cache, depends on self.next_higher_group and self.next_higher_group.higher_groups""" - @property # read-only - def higher_groups(self): - """Get higher groups.""" - if self._higher_groups is unknown: + # @property # read-only + # def higher_groups(self): + # """Get higher groups.""" + # if self._higher_groups is unknown: # find recursively: - self._higher_groups = [] if self.next_higher_group is None \ - else ([self.next_higher_group] - + self.next_higher_group.groups) - return self._higher_groups - - @higher_groups.setter - def higher_groups(self, u): - """Set higher groups.""" - assert u == unknown, "setter can only be used to reset cache" - self._higher_groups = unknown + # self._higher_groups = [] if self.next_higher_group is None \ + # else ([self.next_higher_group] + # + self.next_higher_group.groups) + # return self._higher_groups + + # @higher_groups.setter + # def higher_groups(self, u): + # """Set higher groups.""" + # assert u == unknown, "setter can only be used to reset cache" + # self._higher_groups = unknown # reset dependent caches: - for s in self._next_lower_groups: - s.higher_groups = unknown - for c in self._direct_cells: - c.groups = unknown - - @property # read-only - def next_lower_groups(self): - """Get next lower groups.""" - return self._next_lower_groups - - @property # read-only - def lower_groups(self): - """Get lower groups.""" + # for s in self._next_lower_groups: + # s.higher_groups = unknown + # for c in self._direct_cells: + # c.groups = unknown + + # @property # read-only + # def next_lower_groups(self): + # """Get next lower groups.""" + # return self._next_lower_groups + + # @property # read-only + # def lower_groups(self): + # """Get lower groups.""" # aggregate recursively: - l = self._next_lower_groups - for s in self._next_lower_groups: - l.update(s.lower_groups) - return l + # l = self._next_lower_groups + # for s in self._next_lower_groups: + # l.update(s.lower_groups) + # return l @property def group_members(self): diff --git a/pycopancore/model_components/base/interface.py b/pycopancore/model_components/base/interface.py index 5d1676be..5d8d3975 100644 --- a/pycopancore/model_components/base/interface.py +++ b/pycopancore/model_components/base/interface.py @@ -122,6 +122,9 @@ class World (object, metaclass=_MixinType): individuals = SetVariable("individuals", "Set of Individuals residing on this world", readonly=True) + groups = SetVariable("groups", + "Set of Groups existing on this world", + readonly=True) # specified only now to avoid recursion errors: @@ -306,8 +309,8 @@ class Group (object, metaclass=_MixinType): # references: #social_system = ReferenceVariable("socialsystem", "", type=SocialSystem) - next_higher_group = ReferenceVariable("next higher group", "optional", - allow_none=True) # type is Group, hence it can only be specified after class Group is defined, see below + # next_higher_group = ReferenceVariable("next higher group", "optional", + # allow_none=True) # type is Group, hence it can only be specified after class Group is defined, see below # read-only attributes storing redundant information: @@ -317,18 +320,18 @@ class Group (object, metaclass=_MixinType): readonly=True) culture = ReferenceVariable("culture", "", type=Culture, readonly=True) - higher_groups = SetVariable( - "higher groups", - "upward list of (in)direct super-Groups", - readonly=True) - next_lower_groups = SetVariable( - "next lower groups", - "set of sub-Groups of next lower level", - readonly=True) - lower_groups = SetVariable( - "lower groups", - "set of all direct and indirect sub-Groups", - readonly=True) + # higher_groups = SetVariable( + # "higher groups", + # "upward list of (in)direct super-Groups", + # readonly=True) + # next_lower_groups = SetVariable( + # "next lower groups", + # "set of sub-Groups of next lower level", + # readonly=True) + # lower_groups = SetVariable( + # "lower groups", + # "set of all direct and indirect sub-Groups", + # readonly=True) group_members = SetVariable( "group members", @@ -339,10 +342,10 @@ class Group (object, metaclass=_MixinType): # specified only now to avoid recursion errors: -Group.next_higher_group.type = Group -Group.higher_groups.type = Group -Group.next_lower_groups.type = Group -Group.lower_groups.type = Group +# Group.next_higher_group.type = Group +# Group.higher_groups.type = Group +# Group.next_lower_groups.type = Group +# Group.lower_groups.type = Group # Individual.group_memberships.type = Group # TODO: assert this is at the right place # Group.group_members.type = Individual #Group.groups.type = Group diff --git a/studies/run_groups_test_seven_dwarfs.py b/studies/run_groups_test_seven_dwarfs.py index 933a0aba..f2ce6424 100644 --- a/studies/run_groups_test_seven_dwarfs.py +++ b/studies/run_groups_test_seven_dwarfs.py @@ -15,6 +15,7 @@ # License: BSD 2-clause license import numpy as np +from numpy.random import uniform from time import time import datetime as dt @@ -33,7 +34,7 @@ # setting time step to hand to 'Runner.run()' timestep = .1 nc = 1 # number of caves -dwarfs = 7 # number of dwarfs +dwarfs = 10 # number of dwarfs # instantiate model M (needs to be done in the beginning of each script). # This configures the model M through 'ModelLogics' in module @@ -75,8 +76,8 @@ # initializing the individuals in 'base.Individuals' with the 'cell' method #instantiate groups -ng = 5 #number of groups -groups = [M.Group(culture=culture) for i in range (ng)] +ng = 10 #number of groups +groups = [M.Group(world=world) for i in range (ng)] print("\n The groups: \n") print(groups) @@ -89,8 +90,16 @@ print('\n runner starting') # first test for group network -nx.draw(culture.group_membership_network) +#nx.draw(culture.group_membership_network) +#plt.show() + +# initialize some network: +for i in enumerate(individuals): + for j in enumerate(groups): + culture.group_membership_network.add_edge(i, j) + +nx.draw(culture.group_membership_network) plt.show() # Define termination signals as list [ signal_method, object_method_works_on ] From bdeb1282ddfb15fa112b2a18ecc668dfb60eddfc Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Wed, 8 Jun 2022 16:44:25 +0200 Subject: [PATCH 10/33] little fix --- pycopancore/model_components/base/implementation/group.py | 4 ++-- pycopancore/model_components/base/implementation/world.py | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pycopancore/model_components/base/implementation/group.py b/pycopancore/model_components/base/implementation/group.py index 37684ac7..b8b6b3d1 100644 --- a/pycopancore/model_components/base/implementation/group.py +++ b/pycopancore/model_components/base/implementation/group.py @@ -100,9 +100,9 @@ def world(self): def world(self, w): """Set the World the Group is part of.""" if self._world is not None: - self._world._social_systems.remove(self) + self._world._groups.remove(self) assert isinstance(w, I.World), "world must be of entity type World" - w._social_systems.add(self) + w._groups.add(self) self._world = w # @property diff --git a/pycopancore/model_components/base/implementation/world.py b/pycopancore/model_components/base/implementation/world.py index 05dea718..9457f44e 100644 --- a/pycopancore/model_components/base/implementation/world.py +++ b/pycopancore/model_components/base/implementation/world.py @@ -59,6 +59,7 @@ def __init__(self, self.culture = culture self._social_systems = set() self._cells = set() + self._groups = set() # make sure all variable values are valid: self.assert_valid() @@ -164,6 +165,11 @@ def individuals(self, u): # reset dependent caches: pass + @property # read-only + def groups(self): + """Get the set of all Groups on this World.""" + return self._groups + processes = [ # TODO: convert this into an Implicit equation once supported: From 9747ce5c9707551b03598983972fa19dae3da63f Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Thu, 9 Jun 2022 14:30:59 +0200 Subject: [PATCH 11/33] run_group_test_seven_dwarfs.py now demonstrates minimum features of the new group entity --- studies/run_groups_test_seven_dwarfs.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/studies/run_groups_test_seven_dwarfs.py b/studies/run_groups_test_seven_dwarfs.py index f2ce6424..f03ec4a6 100644 --- a/studies/run_groups_test_seven_dwarfs.py +++ b/studies/run_groups_test_seven_dwarfs.py @@ -95,13 +95,21 @@ # initialize some network: -for i in enumerate(individuals): +for i in individuals: for j in enumerate(groups): culture.group_membership_network.add_edge(i, j) nx.draw(culture.group_membership_network) plt.show() +# print("Group Members: \n") +# for i in groups: +# print(i.group_members) +print("Individual 1 Group Memberships: \n") +print(list(individuals[0].group_memberships)) +# for i in individuals: +# print(i.group_memberships) + # Define termination signals as list [ signal_method, object_method_works_on ] # the termination method 'check_for_extinction' must return a boolean termination_signal = [M.Culture.check_for_extinction, From 032528caa54d4a13d87779c573afce7112c09122 Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Mon, 13 Jun 2022 16:51:31 +0200 Subject: [PATCH 12/33] run_group_test_seven_dwarfs.py now demonstrates minimum features of the new group entity --- .../base/implementation/group.py | 19 +++++++++++------ .../base/implementation/individual.py | 2 +- studies/run_groups_test_seven_dwarfs.py | 21 ++++++++++++------- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/pycopancore/model_components/base/implementation/group.py b/pycopancore/model_components/base/implementation/group.py index b8b6b3d1..012b15a0 100644 --- a/pycopancore/model_components/base/implementation/group.py +++ b/pycopancore/model_components/base/implementation/group.py @@ -38,8 +38,8 @@ def __init__(self, Parameters ---------- - socialsystem: obj - SocialSystem the Group belongs to (default is None) + world: obj + World the Group belongs to next_higher_group: obj Optional Group the Group belongs to (default is None) **kwargs @@ -67,7 +67,7 @@ def __init__(self, self.culture.group_membership_network.add_node(self) def deactivate(self): - """Deactivate an individual. + """Deactivate a group. In particular, deregister from all networks. @@ -78,7 +78,7 @@ def deactivate(self): super().deactivate() # must be the last line def reactivate(self): - """Reactivate an individual. + """Reactivate a group. In particular, deregister with all mandatory networks. @@ -88,6 +88,12 @@ def reactivate(self): if self.culture: self.culture.group_membership_network.add_node(self) + def member_mean(self, state): + """ + Calculate the arithmetic mean of a state of all members of a groups. + """ + + # getters and setters for references: @@ -211,10 +217,11 @@ def metabolism(self): @property def group_members(self): """Get the set of Individuals associated with this Group.""" - return self.culture.group_membership_network.predecessors(self) # .predeccessors as network is directed from inds to groups + return self.culture.group_membership_network.neighbors(self) # .predeccessors as network is directed from inds to groups + + - # TODO: helper methods for mergers, splits, etc. # no process-related methods diff --git a/pycopancore/model_components/base/implementation/individual.py b/pycopancore/model_components/base/implementation/individual.py index 5eb11073..8ae255f4 100644 --- a/pycopancore/model_components/base/implementation/individual.py +++ b/pycopancore/model_components/base/implementation/individual.py @@ -161,7 +161,7 @@ def acquaintances(self): @property def group_memberships(self): """Get the set of Groups the Individual is associated with.""" - return self.culture.group_membership_network.successors(self) # .successors as network is directed from inds to groups + return self.culture.group_membership_network.neighbors(self) # .successors as network is directed from inds to groups # no process-related methods diff --git a/studies/run_groups_test_seven_dwarfs.py b/studies/run_groups_test_seven_dwarfs.py index f03ec4a6..f200cd1e 100644 --- a/studies/run_groups_test_seven_dwarfs.py +++ b/studies/run_groups_test_seven_dwarfs.py @@ -99,16 +99,23 @@ for j in enumerate(groups): culture.group_membership_network.add_edge(i, j) + +print("Individual 1 Group Memberships:") +print(list(individuals[0].group_memberships)) +print("\n") + +print("Group Members:") +print(groups[0]) +# print(groups[0].group_members) +print("\n") + +#draw network of one group +nx.draw(individuals[0].culture.group_membership_network) + nx.draw(culture.group_membership_network) plt.show() -# print("Group Members: \n") -# for i in groups: -# print(i.group_members) -print("Individual 1 Group Memberships: \n") -print(list(individuals[0].group_memberships)) -# for i in individuals: -# print(i.group_memberships) + # Define termination signals as list [ signal_method, object_method_works_on ] # the termination method 'check_for_extinction' must return a boolean From b3bc2c6b3c029a9c89d8c03116b12048515041c8 Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Thu, 16 Jun 2022 11:09:18 +0200 Subject: [PATCH 13/33] - Change group from living in World to living in Culture - Add visualisation of layered group network in run_group_test_seven_dwarfs.py - Fix minor issues --- .../base/implementation/culture.py | 6 +++ .../base/implementation/group.py | 54 +++++++++---------- .../base/implementation/individual.py | 4 +- .../base/implementation/world.py | 7 --- .../model_components/base/interface.py | 9 ++-- studies/run_exploit.py | 1 + studies/run_groups_test_seven_dwarfs.py | 42 ++++++++++++--- 7 files changed, 76 insertions(+), 47 deletions(-) diff --git a/pycopancore/model_components/base/implementation/culture.py b/pycopancore/model_components/base/implementation/culture.py index 79cf5799..1d2239af 100644 --- a/pycopancore/model_components/base/implementation/culture.py +++ b/pycopancore/model_components/base/implementation/culture.py @@ -53,6 +53,7 @@ def __init__(self, self.group_membership_network = group_membership_network self._worlds = set() + self._groups = set() # make sure all variable values are valid: self.assert_valid() @@ -63,6 +64,11 @@ def worlds(self): """Get the set of all Worlds this Culture acts in.""" return self._worlds + @property # read-only + def groups(self): + """Get the set of all Groups in this Culture.""" + return self._groups + # no process-related methods processes = [] # no processes in base diff --git a/pycopancore/model_components/base/implementation/group.py b/pycopancore/model_components/base/implementation/group.py index 012b15a0..063b7342 100644 --- a/pycopancore/model_components/base/implementation/group.py +++ b/pycopancore/model_components/base/implementation/group.py @@ -29,8 +29,7 @@ class Group (I.Group, abstract.Group): def __init__(self, *, - world, - # culture=None, + culture, next_higher_group=None, **kwargs ): @@ -38,8 +37,8 @@ def __init__(self, Parameters ---------- - world: obj - World the Group belongs to + culture: obj + Culture the Group belongs to next_higher_group: obj Optional Group the Group belongs to (default is None) **kwargs @@ -49,10 +48,10 @@ def __init__(self, super().__init__(**kwargs) # must be the first line # init and set variables implemented via properties: - # self._culture = None - # self.culture = culture - self._world = None - self.world = world + self._culture = None + self.culture = culture + # self._world = None + # self.world = world # self._social_system = None # self.social_system = social_system @@ -64,7 +63,7 @@ def __init__(self, self._direct_cells = set() if self.culture: - self.culture.group_membership_network.add_node(self) + self.culture.group_membership_network.add_node(self, type="Group", color="green") def deactivate(self): """Deactivate a group. @@ -86,7 +85,7 @@ def reactivate(self): super().reactivate() # must be the first line # reregister with all mandatory networks: if self.culture: - self.culture.group_membership_network.add_node(self) + self.culture.group_membership_network.add_node(self, type="Group", color="green") def member_mean(self, state): """ @@ -159,22 +158,22 @@ def metabolism(self): """Get the Metabolism of which the Group is a part.""" return self._world.metabolism - # @property - # def culture(self): - # """Get groups's culture.""" - # return self._culture - - # @culture.setter - # def culture(self, c): - # """Set groups's culture.""" - # if self._culture is not None: - # first deregister from previous culture's list of worlds: - # self._culture.groups.remove(self) - # if c is not None: - # assert isinstance(c, I.Culture), \ - # "Culture must be taxon type Culture" - # c._groups.add(self) - # self._culture = c + @property + def culture(self): + """Get culture group is part of.""" + return self._culture + + @culture.setter + def culture(self, c): + """Set culture group is part of.""" + if self._culture is not None: + # first deregister from previous culture's list of worlds: + self._culture.groups.remove(self) + if c is not None: + assert isinstance(c, I.Culture), \ + "Culture must be taxon type Culture" + c._groups.add(self) + self._culture = c # _higher_groups = unknown """cache, depends on self.next_higher_group @@ -217,7 +216,8 @@ def metabolism(self): @property def group_members(self): """Get the set of Individuals associated with this Group.""" - return self.culture.group_membership_network.neighbors(self) # .predeccessors as network is directed from inds to groups + # return self.culture.group_membership_network.neighbors(self) + return self.culture.group_membership_network.predecessors(self) # .predecessors as network is directed from inds to groups diff --git a/pycopancore/model_components/base/implementation/individual.py b/pycopancore/model_components/base/implementation/individual.py index 8ae255f4..dcaa0ffb 100644 --- a/pycopancore/model_components/base/implementation/individual.py +++ b/pycopancore/model_components/base/implementation/individual.py @@ -54,7 +54,7 @@ def __init__(self, # register with all mandatory networks: if self.culture: self.culture.acquaintance_network.add_node(self) - self.culture.group_membership_network.add_node(self) # TODO: does this work with DiGraph? + self.culture.group_membership_network.add_node(self, type="Individual", color="yellow") def deactivate(self): """Deactivate an individual. @@ -78,7 +78,7 @@ def reactivate(self): # reregister with all mandatory networks: if self.culture: self.culture.acquaintance_network.add_node(self) - self.culture.group_membership_network.add_node(self) + self.culture.group_membership_network.add_node(self, type="Individual", color="yellow") # getters and setters for references: diff --git a/pycopancore/model_components/base/implementation/world.py b/pycopancore/model_components/base/implementation/world.py index 9457f44e..3e12fe08 100644 --- a/pycopancore/model_components/base/implementation/world.py +++ b/pycopancore/model_components/base/implementation/world.py @@ -59,7 +59,6 @@ def __init__(self, self.culture = culture self._social_systems = set() self._cells = set() - self._groups = set() # make sure all variable values are valid: self.assert_valid() @@ -165,12 +164,6 @@ def individuals(self, u): # reset dependent caches: pass - @property # read-only - def groups(self): - """Get the set of all Groups on this World.""" - return self._groups - - processes = [ # TODO: convert this into an Implicit equation once supported: Explicit("aggregate cell carbon stocks", diff --git a/pycopancore/model_components/base/interface.py b/pycopancore/model_components/base/interface.py index 5d8d3975..6ac69ebb 100644 --- a/pycopancore/model_components/base/interface.py +++ b/pycopancore/model_components/base/interface.py @@ -78,6 +78,9 @@ class Culture (object): # entity types: + groups = SetVariable("groups", + "Set of Groups existing on this world", + readonly=True) # TODO: clarify whether it is necessary to specify the metaclass here! class World (object, metaclass=_MixinType): @@ -122,9 +125,7 @@ class World (object, metaclass=_MixinType): individuals = SetVariable("individuals", "Set of Individuals residing on this world", readonly=True) - groups = SetVariable("groups", - "Set of Groups existing on this world", - readonly=True) + # specified only now to avoid recursion errors: @@ -339,7 +340,7 @@ class Group (object, metaclass=_MixinType): readonly=True ) - +Culture.groups.type = Group # specified only now to avoid recursion errors: # Group.next_higher_group.type = Group diff --git a/studies/run_exploit.py b/studies/run_exploit.py index 3275f221..23ab8837 100755 --- a/studies/run_exploit.py +++ b/studies/run_exploit.py @@ -18,6 +18,7 @@ from time import time import datetime as dt from numpy import random +import argparse import plotly.offline as py import plotly.graph_objs as go diff --git a/studies/run_groups_test_seven_dwarfs.py b/studies/run_groups_test_seven_dwarfs.py index f200cd1e..e751ec4c 100644 --- a/studies/run_groups_test_seven_dwarfs.py +++ b/studies/run_groups_test_seven_dwarfs.py @@ -20,6 +20,7 @@ import datetime as dt import networkx as nx +from networkx.algorithms import bipartite import matplotlib.pyplot as plt import plotly.offline as py @@ -77,7 +78,7 @@ #instantiate groups ng = 10 #number of groups -groups = [M.Group(world=world) for i in range (ng)] +groups = [M.Group(culture=culture) for i in range (ng)] print("\n The groups: \n") print(groups) @@ -96,9 +97,8 @@ # initialize some network: for i in individuals: - for j in enumerate(groups): - culture.group_membership_network.add_edge(i, j) - + for g in groups: + culture.group_membership_network.add_edge(i, g) print("Individual 1 Group Memberships:") print(list(individuals[0].group_memberships)) @@ -106,13 +106,41 @@ print("Group Members:") print(groups[0]) -# print(groups[0].group_members) +print(list(groups[0].group_members)) print("\n") #draw network of one group -nx.draw(individuals[0].culture.group_membership_network) +# nx.draw(individuals[0].culture.group_membership_network) +# plt.show() + +GM = culture.group_membership_network + +print(GM.nodes.data()) + + +color_map = [] +shape_map = [] +for node in list(GM.nodes): + print(node) + print(GM.nodes[node]) + color_map.append(GM.nodes[node]["color"]) + + if GM.nodes[node]["type"] == "Group": + shape_map.append("o") + else: + shape_map.append("^") + + + +top_nodes = {n for n, d in GM.nodes(data=True) if d["type"] == "Group"} +bottom_nodes = set(GM) - top_nodes + +print(list(top_nodes)) + +nx.draw(GM, node_color=color_map, with_labels=True, + pos=nx.bipartite_layout(GM, bottom_nodes, align="horizontal", aspect_ratio=4/1)) -nx.draw(culture.group_membership_network) +# nx.draw(culture.group_membership_network) plt.show() From 4019e10024aa5432115f9af2a023abb44507c090 Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Mon, 20 Jun 2022 15:33:54 +0200 Subject: [PATCH 14/33] add/adjust documentation --- .../abstract_level/entity_types/group.rst | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/docs/framework_documentation/abstract_level/entity_types/group.rst b/docs/framework_documentation/abstract_level/entity_types/group.rst index 7ef8158c..173ad33e 100644 --- a/docs/framework_documentation/abstract_level/entity_types/group.rst +++ b/docs/framework_documentation/abstract_level/entity_types/group.rst @@ -11,7 +11,7 @@ may not be sufficient and a distinction of social strata or other social groups "indigenous people", "social democrats", a certain NGO, ...) that is transverse to the former partitioning is helpful in addition. -For this, we will in the future provide an entity-type "group" +For this, the entity-type "group" is provided which is meant to represent any grouping of individuals (that may come from one or several social systems) by meaningful cultural or social-metabolic aspects. @@ -33,19 +33,24 @@ Basic relationships to other entity-types A group will usually... -- have several member :doc:`individuals` +- have several members :doc:`individuals`, + which is represented by a group membership directed network owned by the culture taxon In addition, a group may... -- have one or several "leader" :doc:`individuals`, - of which one may be the dominant leader +- have an "intra" group network between members :doc:`individuals` + (this feature is not completely implemented yet) -- have a "headquarters" :doc:`cell` +- have one or several "leader" :doc:`individuals`, + of which one may be the dominant leader (this feature is not implemented yet) -- be related to other groups via some network owned by the culture taxon +- have a "headquarters" :doc:`cell` (this feature is not implemented yet) + +- be related to other groups via an "inter" network owned by the culture taxon (which will typically interact with the network of personal acquaintance between member individuals) + - (this feature is not completely implemented yet) -- act as the current "elite" in some :doc:`social system` +- act as the current "elite" in some :doc:`social system` (this feature is not implemented yet) All these relationships may be dynamic. @@ -53,5 +58,5 @@ Finally, a group may... - be a permanent subgroup of a larger group or :doc:`social system` *by definition* (rather than by coincidence, e.g., "scientists" are by definition a subgroup of the group "academics", - and "German workers" may be by definition a subgroup of the social system "Germany") + and "German workers" may be by definition a subgroup of the social system "Germany") - (this feature is not implemented yet) From 30409daf4f8bf858288989c94acd6e180dee8bf6 Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Mon, 2 May 2022 11:40:24 +0200 Subject: [PATCH 15/33] Added structure for group entity Adding global group entity: - Added necessary group.py files - Changed all necessary files (e.g. __init__) - Study scripts run smoothly (more testing) - So far no functionality to group (is mostly copy of social system) --- .../data_model/master_data_model/__init__.py | 3 + .../data_model/master_data_model/group.py | 27 +++ .../model_components/abstract/__init__.py | 1 + .../model_components/abstract/group.py | 25 ++ .../base/implementation/__init__.py | 1 + .../base/implementation/group.py | 226 ++++++++++++++++++ .../model_components/base/interface.py | 54 +++++ pycopancore/model_components/base/model.py | 4 +- 8 files changed, 339 insertions(+), 2 deletions(-) create mode 100644 pycopancore/data_model/master_data_model/group.py create mode 100644 pycopancore/model_components/abstract/group.py create mode 100644 pycopancore/model_components/base/implementation/group.py diff --git a/pycopancore/data_model/master_data_model/__init__.py b/pycopancore/data_model/master_data_model/__init__.py index 4deb317a..f962b122 100644 --- a/pycopancore/data_model/master_data_model/__init__.py +++ b/pycopancore/data_model/master_data_model/__init__.py @@ -37,5 +37,8 @@ from .individual import Individual as I from .individual import Individual as individual from .individual import Individual +from .group import Group as G +from .group import Group as group +from .group import Group from .. import unity diff --git a/pycopancore/data_model/master_data_model/group.py b/pycopancore/data_model/master_data_model/group.py new file mode 100644 index 00000000..c636fff6 --- /dev/null +++ b/pycopancore/data_model/master_data_model/group.py @@ -0,0 +1,27 @@ +"""Master data model for group.""" + +"""https://github.com/pik-copan/pycopancore/blob/master/docs/framework_documentation/abstract_level/entity_types/group.rst""" + +#from . import MET +from .. import Variable + + +class Group: + + #TODO: add a group network possibility (in culture.py (?)) + + + has_leader = \ + Variable("has a leader", + "whether the group has a leader", + scale="ordinal", levels=[False, True], default=False) + + has_headquarter = \ + Variable("has a headquarter", + "whether the group has a headquarter located in a cell", + scale="ordinal", levels=[False, True], default=False) + + + + + diff --git a/pycopancore/model_components/abstract/__init__.py b/pycopancore/model_components/abstract/__init__.py index 6ee0e07a..e383543b 100644 --- a/pycopancore/model_components/abstract/__init__.py +++ b/pycopancore/model_components/abstract/__init__.py @@ -16,6 +16,7 @@ from .social_system import SocialSystem from .cell import Cell from .individual import Individual +from .group import Group from .environment import Environment from .metabolism import Metabolism diff --git a/pycopancore/model_components/abstract/group.py b/pycopancore/model_components/abstract/group.py new file mode 100644 index 00000000..f919b38e --- /dev/null +++ b/pycopancore/model_components/abstract/group.py @@ -0,0 +1,25 @@ +"""Abstract Group entity type class, inherited by base model component.""" + +# This file is part of pycopancore. +# +# Copyright (C) 2016-2017 by COPAN team at Potsdam Institute for Climate +# Impact Research +# +# URL: +# Contact: core@pik-potsdam.de +# License: BSD 2-clause license + +from ...private import _AbstractEntityMixin +from ...data_model import OrderedSet + + +class Group (_AbstractEntityMixin): + """Abstract Group entity type class. + + Inherited by base model component. + """ + + variables = OrderedSet() + """All variables occurring in this entity type""" + + diff --git a/pycopancore/model_components/base/implementation/__init__.py b/pycopancore/model_components/base/implementation/__init__.py index f782a260..a14081c7 100644 --- a/pycopancore/model_components/base/implementation/__init__.py +++ b/pycopancore/model_components/base/implementation/__init__.py @@ -17,6 +17,7 @@ from .social_system import SocialSystem from .cell import Cell from .individual import Individual +from .group import Group from .environment import Environment from .metabolism import Metabolism diff --git a/pycopancore/model_components/base/implementation/group.py b/pycopancore/model_components/base/implementation/group.py new file mode 100644 index 00000000..78c71a41 --- /dev/null +++ b/pycopancore/model_components/base/implementation/group.py @@ -0,0 +1,226 @@ +""" """ + +# This file is part of pycopancore. +# +# Copyright (C) 2016-2017 by COPAN team at Potsdam Institute for Climate +# Impact Research +# +# URL: +# Contact: core@pik-potsdam.de +# License: BSD 2-clause license + +# only used in this component, not in others: +from ... import abstract +from .... import master_data_model as D +from ....private import unknown + +from .. import interface as I + + +class Group (I.Group, abstract.Group): + """Gropp entity type mixin implementation class. + + Base component's Group mixin that every model must use in composing + their Group class. Inherits from I.Group as the interface with all + necessary variables and parameters. + """ + + # standard methods: + + def __init__(self, + *, + socialsystem, + next_higher_group=None, + **kwargs + ): + """Initialize an instance of Group. + + Parameters + ---------- + socialsystem: obj + SocialSystem the Group belongs to (default is None) + next_higher_group: obj + Optional Group the Group belongs to (default is None) + **kwargs + keyword arguments passed to super() + + """ + super().__init__(**kwargs) # must be the first line + + # init caches: + self._next_lower_group = set() + self._direct_cells = set() + + # init and set variables implemented via properties: + self._socialsystem = None + self.socialsystem = socialsystem + self._next_higher_group = None + self.next_higher_group = next_higher_group + + # getters and setters for references: + + @property + def socialsystem(self): + """Get the World the SocialSystem is part of.""" + return self._socialsystem + + @socialsystem.setter + def socialsystem(self, w): + """Set the World the SocialSystem is part of.""" + if self._socialsystem is not None: + self._socialsystem._groups.remove(self) + assert isinstance(w, I.SocialSystem), "socialsystem must be of entity type SocialSystem" + w._groups.add(self) + self._socialsystem = w + + @property + def next_higher_group(self): + """Get next higher group.""" + return self._next_higher_group + + @next_higher_group.setter + def next_higher_group(self, s): + """Set next higher group.""" + if self._next_higher_group is not None: + self._next_higher_group._next_lower_groups.remove(self) + # reset dependent cache: + self._next_higher_group.cells = unknown + if s is not None: + assert isinstance(s, I.Group), \ + "next_higher_group must be of entity type group" + s._next_lower_groups.add(self) + # reset dependent cache: + s.cells = unknown + self._next_higher_group = s + # reset dependent caches: + self.higher_groups = unknown + + # getters for backwards references and convenience variables: + + @property # read-only + def environment(self): + """Get the Environment of which the Group is a part.""" + return self._world.environment + + @property # read-only + def metabolism(self): + """Get the Metabolism of which the Group is a part.""" + return self._world.metabolism + + @property # read-only + def culture(self): + """Get the Culture of which the Group is a part.""" + return self._world.culture + + _higher_groups = unknown + """cache, depends on self.next_higher_group + and self.next_higher_group.higher_groups""" + @property # read-only + def higher_groups(self): + """Get higher groups.""" + if self._higher_groups is unknown: + # find recursively: + self._higher_groups = [] if self.next_higher_group is None \ + else ([self.next_higher_group] + + self.next_higher_group.groups) + return self._higher_groups + + @higher_groups.setter + def higher_groups(self, u): + """Set higher groups.""" + assert u == unknown, "setter can only be used to reset cache" + self._higher_groups = unknown + # reset dependent caches: + for s in self._next_lower_groups: + s.higher_groups = unknown + for c in self._direct_cells: + c.groups = unknown + + @property # read-only + def next_lower_groups(self): + """Get next lower groups.""" + return self._next_lower_groups + + @property # read-only + def lower_groups(self): + """Get lower groups.""" + # aggregate recursively: + l = self._next_lower_groups + for s in self._next_lower_groups: + l.update(s.lower_groups) + return l + + @property # read-only + def direct_cells(self): + """Get cells that directly belong to the SocialSystem.""" + return self._direct_cells + + _cells = unknown + """cache, depends on self.direct_cells, self._next_lower_social_systems, + and lowersocial_system.cells""" + @property # read-only + def cells(self): + """Get cells that directly abd indirectly belong to the SocialSystem.""" + if self._cells is unknown: + # aggregate recursively: + self._cells = self.direct_cells + for s in self._next_lower_social_systems: + self._cells.update(s.cells) + return self._cells + + @cells.setter + def cells(self, u): + """Set cells that directly and indirectly belong to the SocialSystem.""" + assert u == unknown, "setter can only be used to reset cache" + self._cells = unknown + # reset dependent caches: + if self.next_higher_social_system is not None: + self.next_higher_social_system.cells = unknown + + _direct_individuals = unknown + """cache, depends on _direct_cells, directcell.individuals""" + @property # read-only + def direct_individuals(self): + """Get resident Individuals not in subsocial_systems.""" + if self._direct_individuals is unknown: + # aggregate from direct_cells: + self._direct_individuals = set() + for c in self._direct_cells: + self._direct_individuals.update(c.individuals) + return self._direct_individuals + + @direct_individuals.setter + def direct_individuals(self, u): + """Set resident Individuals not in subsocial_systems.""" + assert u == unknown, "setter can only be used to reset cache" + self._direct_individuals = unknown + # reset dependent caches: + pass + + _individuals = unknown + """cache, depends on self.cells, cell.individuals""" + @property # read-only + def individuals(self): + """Get direct and indirect resident Individuals.""" + if self._individuals is unknown: + # aggregate from cells: + self._individuals = set() + for c in self.cells: + self._individuals.update(c.individuals) + return self._individuals + + @individuals.setter + def individuals(self, u): + """Set direct and indirect resident Individuals.""" + assert u == unknown, "setter can only be used to reset cache" + self._individuals = unknown + # reset dependent caches: + pass + + # TODO: helper methods for mergers, splits, etc. + + # no process-related methods + + processes = [] # no processes in base + + diff --git a/pycopancore/model_components/base/interface.py b/pycopancore/model_components/base/interface.py index b3424218..15f491fd 100644 --- a/pycopancore/model_components/base/interface.py +++ b/pycopancore/model_components/base/interface.py @@ -288,3 +288,57 @@ class Individual (object, metaclass=_MixinType): SocialSystem.individuals.type = Individual Cell.individuals.type = Individual Individual.acquaintances.type = Individual + + + +class Group (object, metaclass=_MixinType): + """Basic Group interface. + + It contains all variables specified as mandatory ("base variables"). + """ + + # references: + socialsystem = ReferenceVariable("socialsystem", "", type=SocialSystem) + next_higher_group = ReferenceVariable("next higher group", "optional", + allow_none=True) # type is Group, hence it can only be specified after class Group is defined, see below + + + # read-only attributes storing redundant information: + environment = ReferenceVariable("environment", "", type=Environment, + readonly=True) + metabolism = ReferenceVariable("metabolism", "", type=Metabolism, + readonly=True) + culture = ReferenceVariable("culture", "", type=Culture, + readonly=True) + higher_groups = SetVariable( + "higher groups", + "upward list of (in)direct super-Groups", + readonly=True) + next_lower_groups = SetVariable( + "next lower groups", + "set of sub-Groups of next lower level", + readonly=True) + lower_groups = SetVariable( + "lower groups", + "set of all direct and indirect sub-Groups", + readonly=True) + direct_cells = SetVariable("direct cells", "set of direct territory Cells", + readonly=True) + cells = SetVariable("cells", "set of direct and indirect territory Cells", + readonly=True) + direct_individuals = SetVariable( + "direct individuals", + "set of resident Individuals not in subgroups", + readonly=True) + individuals = SetVariable("individuals", + "set of direct or indirect resident Individuals", + readonly=True) + + +# specified only now to avoid recursion errors: +Group.next_higher_group.type = Group +Group.higher_groups.type = Group +Group.next_lower_groups.type = Group +Group.lower_groups.type = Group +#Group.groups.type = Group +#SocialSystem.top_level_groups.type = Group diff --git a/pycopancore/model_components/base/model.py b/pycopancore/model_components/base/model.py index 0a417c29..b354528d 100644 --- a/pycopancore/model_components/base/model.py +++ b/pycopancore/model_components/base/model.py @@ -20,7 +20,7 @@ from .. import abstract from . import interface as I from . import World, Cell, Environment, Individual, Culture, SocialSystem, \ - Metabolism + Metabolism, Group class Model (I.Model, abstract.Model, ModelLogics): @@ -34,5 +34,5 @@ class Model (I.Model, abstract.Model, ModelLogics): # specify entity types and process taxon classes # defined in the base component: - entity_types = [World, Cell, Individual, SocialSystem] + entity_types = [World, Cell, Individual, SocialSystem, Group] process_taxa = [Environment, Culture, Metabolism] From 03ff3a0814cf0392d76f35d2fee33df1f6401719 Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Mon, 2 May 2022 12:15:20 +0200 Subject: [PATCH 16/33] Update group.py Added a possible group_network logic --- pycopancore/data_model/master_data_model/group.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pycopancore/data_model/master_data_model/group.py b/pycopancore/data_model/master_data_model/group.py index c636fff6..5bb23744 100644 --- a/pycopancore/data_model/master_data_model/group.py +++ b/pycopancore/data_model/master_data_model/group.py @@ -2,15 +2,22 @@ """https://github.com/pik-copan/pycopancore/blob/master/docs/framework_documentation/abstract_level/entity_types/group.rst""" -#from . import MET from .. import Variable +from networkx import Graph class Group: #TODO: add a group network possibility (in culture.py (?)) - + group_network = \ + Variable("group network", + """Basic undirected social network between + Groups.""", + ref="https://en.wikipedia.org/wiki/Social_network#Meso_level", + scale='nominal', + datatype=Graph) + has_leader = \ Variable("has a leader", "whether the group has a leader", From 9b4aaa9557d1ed8cd6b489dfbfd0e28094fd92cf Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Fri, 13 May 2022 11:20:52 +0200 Subject: [PATCH 17/33] social_system --- .../base/implementation/group.py | 32 +-- .../base/implementation/social_system.py | 12 ++ pycopancore/models/groups_seven_dwarfs.py | 89 ++++++++ studies/run_groups_test_seven_dwarfs.py | 193 ++++++++++++++++++ 4 files changed, 311 insertions(+), 15 deletions(-) create mode 100644 pycopancore/models/groups_seven_dwarfs.py create mode 100644 studies/run_groups_test_seven_dwarfs.py diff --git a/pycopancore/model_components/base/implementation/group.py b/pycopancore/model_components/base/implementation/group.py index 78c71a41..f3aeacd1 100644 --- a/pycopancore/model_components/base/implementation/group.py +++ b/pycopancore/model_components/base/implementation/group.py @@ -29,7 +29,7 @@ class Group (I.Group, abstract.Group): def __init__(self, *, - socialsystem, + social_system, next_higher_group=None, **kwargs ): @@ -47,31 +47,33 @@ def __init__(self, """ super().__init__(**kwargs) # must be the first line + # init and set variables implemented via properties: + self._social_system = None + self.social_system = social_system + self._next_higher_group = None + self.next_higher_group = next_higher_group + # init caches: self._next_lower_group = set() self._direct_cells = set() - # init and set variables implemented via properties: - self._socialsystem = None - self.socialsystem = socialsystem - self._next_higher_group = None - self.next_higher_group = next_higher_group + # getters and setters for references: @property - def socialsystem(self): + def social_system(self): """Get the World the SocialSystem is part of.""" - return self._socialsystem + return self._social_system - @socialsystem.setter - def socialsystem(self, w): + @social_system.setter + def social_system(self, s): """Set the World the SocialSystem is part of.""" - if self._socialsystem is not None: - self._socialsystem._groups.remove(self) - assert isinstance(w, I.SocialSystem), "socialsystem must be of entity type SocialSystem" - w._groups.add(self) - self._socialsystem = w + if self._social_system is not None: + self._social_system._groups.remove(self) + assert isinstance(s, I.SocialSystem), "socialsystem must be of entity type SocialSystem" + s._groups.add(self) + self._social_system = s @property def next_higher_group(self): diff --git a/pycopancore/model_components/base/implementation/social_system.py b/pycopancore/model_components/base/implementation/social_system.py index 33b5ce68..b8dac8e3 100644 --- a/pycopancore/model_components/base/implementation/social_system.py +++ b/pycopancore/model_components/base/implementation/social_system.py @@ -219,6 +219,18 @@ def individuals(self, u): # reset dependent caches: pass + @property # read-only + def groups(self): + """Get the set of all Groups in this SocialSystem.""" + return self._groups + @groups.setter + def groups(self, g): + """Set the World the SocialSystem is part of.""" + if self._groups is not None: + self._groups._social_system.remove(self) + assert isinstance(g, I.Culture), "groups must be of taxon type Group" + g._worlds.add(self) + self._culture = g # TODO: helper methods for mergers, splits, etc. # no process-related methods diff --git a/pycopancore/models/groups_seven_dwarfs.py b/pycopancore/models/groups_seven_dwarfs.py new file mode 100644 index 00000000..31be4f35 --- /dev/null +++ b/pycopancore/models/groups_seven_dwarfs.py @@ -0,0 +1,89 @@ +"""Model class seven_dwarfs.""" + +# This file is part of pycopancore. +# +# Copyright (C) 2016-2017 by COPAN team at Potsdam Institute for Climate +# Impact Research +# +# URL: +# Contact: core@pik-potsdam.de +# License: BSD 2-clause license + +# +# Imports +# + +from .. import base # all models must use the base component + +from ..model_components import seven_dwarfs as sd +from ..model_components import snowwhite as sw + +# entity types: + +# by mixing the above model components' mixin classes of the same name. +# Only compose those entity types and process taxons that the model needs, +# delete the templates for the unneeded ones, and add those for missing ones: + + +class World(sd.World, + base.World): + """World entity type.""" + + pass + + +class SocialSystem(base.SocialSystem): + """SocialSystem entity type.""" + + pass + + +class Cell(sd.Cell, + sw.Cell, + base.Cell): + """Cell entity type.""" + + pass + + +class Individual(sd.Individual, + base.Individual): + """Individual entity type.""" + + pass + + +class SocialSystem(sd.SocialSystem, + base.SocialSystem): + """SocialSystem entity type.""" + + pass + +class Group(base.Group): + """Groups entity type""" + +# process taxa: + +class Culture(sd.Culture, + base.Culture): + """Culture process taxon.""" + + pass + + +# Model class: + +class Model(sd.Model, + sw.Model, + base.Model): + """Class representing the whole model.""" + + name = "Seven dwarfs" + """Name of the model""" + description = "Tutorial model" + """Longer description""" + + entity_types = [World, SocialSystem, Cell, Individual] + """List of entity types used in the model""" + process_taxa = [Culture] + """List of process taxa used in the model""" diff --git a/studies/run_groups_test_seven_dwarfs.py b/studies/run_groups_test_seven_dwarfs.py new file mode 100644 index 00000000..1c241a9b --- /dev/null +++ b/studies/run_groups_test_seven_dwarfs.py @@ -0,0 +1,193 @@ +"""This is the test script for the seven dwarfs step by step tutorial. + +In this version only the Step-process 'aging' of entitytype 'Individual' is +implemented, such that the only relevant attributes of 'Individual' are 'age' +and 'cell'. +""" + +# This file is part of pycopancore. +# +# Copyright (C) 2016-2017 by COPAN team at Potsdam Institute for Climate +# Impact Research +# +# URL: +# Contact: core@pik-potsdam.de +# License: BSD 2-clause license + +import numpy as np +from time import time +import datetime as dt + +import networkx as nx +import matplotlib.pyplot as plt + +import plotly.offline as py +import plotly.graph_objs as go + +import pycopancore.models.groups_seven_dwarfs as M +from pycopancore.runners.runner import Runner + + +# setting timeinterval for run method 'Runner.run()' +timeinterval = 10 +# setting time step to hand to 'Runner.run()' +timestep = .1 +nc = 1 # number of caves +dwarfs = 7 # number of dwarfs + +# instantiate model M (needs to be done in the beginning of each script). +# This configures the model M through 'ModelLogics' in module +# 'base.model_logics' such that initialisation of attributes and entities gets +# possible +model = M.Model() + +# instantiate process taxa culture: +# In this certain case we need 'M.Culture()' for the acquaintance network. +culture = M.Culture() + +# instantiate world: +world = M.World(culture=culture) + +# instantiate one social system: +social_system = M.SocialSystem(world=world) + +# instantiate cells (the caves): +cells = [M.Cell(social_system=social_system, + eating_stock=100 + ) + for c in range(nc) + ] + +# instantiate dwarfs and assigning initial conditions +individuals = [M.Individual(cell=cells[0], + age=0, + beard_length=0, + beard_growth_parameter=0.5, + eating_parameter=.1 + ) for i in range(dwarfs) + ] + +# assigning individuals to cell is not necessary since it is done by +# initializing the individuals in 'base.Individuals' with the 'cell' method + +#instantiate groups +ng = 2 #number of groups (group 1 hates snowwhite, group 2 loves her) +groups = [M.Group(social_system=social_system) + for i in range (ng) + ] + + + +start = time() + +print("done ({})".format(dt.timedelta(seconds=(time() - start)))) + +print('\n runner starting') + +# Define termination signals as list [ signal_method, object_method_works_on ] +# the termination method 'check_for_extinction' must return a boolean +termination_signal = [M.Culture.check_for_extinction, + culture] + +# Define termination_callables as list of all signals +termination_callables = [termination_signal] + +nx.draw(culture.acquaintance_network) +plt.show() + +# Runner is instantiated +r = Runner(model=model, + termination_calls=termination_callables + ) + +start = time() +# run the Runner and saving the return dict in traj +traj = r.run(t_1=timeinterval, dt=timestep, add_to_output=[M.Culture.acquaintance_network]) +runtime = dt.timedelta(seconds=(time() - start)) +print('runtime: {runtime}'.format(**locals())) + +# saving time values to t +t = np.array(traj['t']) +print("max. time step", (t[1:]-t[:-1]).max()) + + +# proceeding for plotting + +# Create List of all dwarfes, not only the ones instantiated before the run, +# but also the one created during the run. + +if M.Individual.idle_entities: + all_dwarfs = M.Individual.instances + M.Individual.idle_entities +else: + all_dwarfs = M.Individual.instances + +individuals_age = np.array([traj[M.Individual.age][dwarf] + for dwarf in all_dwarfs]) + + +individuals_beard_length = np.array([traj[M.Individual.beard_length][dwarf] + for dwarf in all_dwarfs]) + +cell_stock = np.array(traj[M.Cell.eating_stock][cells[0]]) + +t = np.array(traj['t']) + +data_age = [] +print('data age', data_age) +for i, dwarf in enumerate(all_dwarfs): + data_age.append(go.Scatter( + x=t, + y=individuals_age[i], + mode="lines", + name="age of dwarf no. {}".format(i), + line=dict( + color="green", + width=4 + ) + )) + +data_beard_length = [] +print('data beard', data_beard_length) +for i, dwarf in enumerate(all_dwarfs): + data_beard_length.append(go.Scatter( + x=t, + y=individuals_beard_length[i], + mode="lines", + name="beard length of dwarf no. {}".format(i), + line=dict( + color="red", + width=4 + ) + )) + +data_stock = [] +data_stock.append(go.Scatter( + x=t, + y=cell_stock, + mode="lines", + name="stock of cell", + line=dict(color="blue", + width=4 + ) + )) + + +layout = dict(title='seven dwarfs', + xaxis=dict(title='time [yr]'), + yaxis=dict(title='value'), + ) + + +# getting plots of two dwarfs: +fig = dict(data=[data_age[0], data_beard_length[0], data_stock[0]], + layout=layout) +py.plot(fig, filename="our-model-result{}.html".format(0)) + +fig = dict(data=[data_age[5], data_beard_length[5], data_stock[0]], + layout=layout) +py.plot(fig, filename="our-model-result{}.html".format(5)) + +#nx.draw(traj[M.Culture.acquaintance_network][culture][1]) +#plt.show() +for i in range(len(traj['t'])): + print(list(traj[M.Culture.acquaintance_network][culture][i].nodes())) From f08842309d99eac62e49f6cb632d03a0e7fa3737 Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Tue, 31 May 2022 18:20:55 +0200 Subject: [PATCH 18/33] typo cleanup minor typo and some todos --- pycopancore/data_model/master_data_model/group.py | 4 +++- pycopancore/model_components/base/implementation/group.py | 4 +++- .../model_components/base/implementation/individual.py | 1 + pycopancore/model_components/base/interface.py | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pycopancore/data_model/master_data_model/group.py b/pycopancore/data_model/master_data_model/group.py index 5bb23744..26cf6ce5 100644 --- a/pycopancore/data_model/master_data_model/group.py +++ b/pycopancore/data_model/master_data_model/group.py @@ -8,7 +8,9 @@ class Group: - #TODO: add a group network possibility (in culture.py (?)) + #TODO: add a group network possibility (in culture.py (!)) + #TODO: inter/intra group network + #TODO: specify edges group_network = \ Variable("group network", diff --git a/pycopancore/model_components/base/implementation/group.py b/pycopancore/model_components/base/implementation/group.py index f3aeacd1..0aa1d663 100644 --- a/pycopancore/model_components/base/implementation/group.py +++ b/pycopancore/model_components/base/implementation/group.py @@ -29,7 +29,7 @@ class Group (I.Group, abstract.Group): def __init__(self, *, - social_system, + social_system=None, next_higher_group=None, **kwargs ): @@ -179,6 +179,8 @@ def cells(self, u): if self.next_higher_social_system is not None: self.next_higher_social_system.cells = unknown + #TODO: direct_inds to members + _direct_individuals = unknown """cache, depends on _direct_cells, directcell.individuals""" @property # read-only diff --git a/pycopancore/model_components/base/implementation/individual.py b/pycopancore/model_components/base/implementation/individual.py index 7f9eb2a3..e82c5fc5 100644 --- a/pycopancore/model_components/base/implementation/individual.py +++ b/pycopancore/model_components/base/implementation/individual.py @@ -16,6 +16,7 @@ from .. import interface as I +#TODO: set variable membership class Individual (I.Individual, abstract.Individual): """Individual entity type mixin implementation class. diff --git a/pycopancore/model_components/base/interface.py b/pycopancore/model_components/base/interface.py index 15f491fd..deb77458 100644 --- a/pycopancore/model_components/base/interface.py +++ b/pycopancore/model_components/base/interface.py @@ -298,7 +298,7 @@ class Group (object, metaclass=_MixinType): """ # references: - socialsystem = ReferenceVariable("socialsystem", "", type=SocialSystem) + #social_system = ReferenceVariable("socialsystem", "", type=SocialSystem) next_higher_group = ReferenceVariable("next higher group", "optional", allow_none=True) # type is Group, hence it can only be specified after class Group is defined, see below From 329ab84304eafbd6b2341475ce71a0c03dffad4f Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Wed, 8 Jun 2022 15:52:00 +0200 Subject: [PATCH 19/33] Implementing Individual to Group relationships via a group_membership_network. Error while adding group to a culture (group needs to "live" somewhere, similar to cells for individuals that then give memberships to worlds and so on. --- .../data_model/master_data_model/culture.py | 19 ++- .../data_model/master_data_model/group.py | 9 +- .../base/implementation/culture.py | 13 +- .../base/implementation/group.py | 148 ++++++++---------- .../base/implementation/individual.py | 10 +- .../model_components/base/interface.py | 27 ++-- studies/run_groups_test_seven_dwarfs.py | 99 ++---------- 7 files changed, 132 insertions(+), 193 deletions(-) diff --git a/pycopancore/data_model/master_data_model/culture.py b/pycopancore/data_model/master_data_model/culture.py index 755b8047..d89d2c82 100644 --- a/pycopancore/data_model/master_data_model/culture.py +++ b/pycopancore/data_model/master_data_model/culture.py @@ -94,7 +94,24 @@ class Culture: scale='nominal', datatype=set) - + + # group entity related networks + + inter_group_network = \ + Variable("inter group network", + """Basic undirected social network between + Groups.""", + ref="https://en.wikipedia.org/wiki/Social_network#Meso_level", + scale='nominal', + datatype=Graph) + + group_membership_network = \ + Variable("group membership network", + """Directed network from individual to group that + signifies membership (to avoid problems due to n to n relation)""", + scale='nominal', + datatype=DiGraph) + # socio-cultural traits that may occur on different levels: is_environmentally_friendly = \ diff --git a/pycopancore/data_model/master_data_model/group.py b/pycopancore/data_model/master_data_model/group.py index 26cf6ce5..cd7354cb 100644 --- a/pycopancore/data_model/master_data_model/group.py +++ b/pycopancore/data_model/master_data_model/group.py @@ -8,15 +8,12 @@ class Group: - #TODO: add a group network possibility (in culture.py (!)) - #TODO: inter/intra group network #TODO: specify edges - group_network = \ - Variable("group network", + intra_group_network = \ + Variable("intra group network", """Basic undirected social network between - Groups.""", - ref="https://en.wikipedia.org/wiki/Social_network#Meso_level", + Group members.""", scale='nominal', datatype=Graph) diff --git a/pycopancore/model_components/base/implementation/culture.py b/pycopancore/model_components/base/implementation/culture.py index 6f342fcc..79cf5799 100644 --- a/pycopancore/model_components/base/implementation/culture.py +++ b/pycopancore/model_components/base/implementation/culture.py @@ -14,8 +14,7 @@ from .. import interface as I -from networkx import Graph - +from networkx import Graph, DiGraph class Culture (I.Culture, abstract.Culture): """Culture process taxon mixin implementation class.""" @@ -25,6 +24,7 @@ class Culture (I.Culture, abstract.Culture): def __init__(self, *, acquaintance_network=None, + group_membership_network=None, **kwargs): """Initialize the unique instance of Culture. @@ -33,6 +33,9 @@ def __init__(self, acquaintance_network: Graph The Network of acquaintances which is managed by Culture (default is None) + group_membership_network: DiGraph + The Network between Individiuals and groups, which is managed + by Culture (default is None) **kwargs keyword arguments passed to super() @@ -43,6 +46,12 @@ def __init__(self, acquaintance_network = Graph() assert isinstance(acquaintance_network, Graph) self.acquaintance_network = acquaintance_network + + if group_membership_network is None: + group_membership_network = DiGraph() + assert isinstance(group_membership_network, DiGraph) + self.group_membership_network = group_membership_network + self._worlds = set() # make sure all variable values are valid: diff --git a/pycopancore/model_components/base/implementation/group.py b/pycopancore/model_components/base/implementation/group.py index 0aa1d663..22ce9ebd 100644 --- a/pycopancore/model_components/base/implementation/group.py +++ b/pycopancore/model_components/base/implementation/group.py @@ -29,7 +29,7 @@ class Group (I.Group, abstract.Group): def __init__(self, *, - social_system=None, + culture=None, next_higher_group=None, **kwargs ): @@ -48,8 +48,10 @@ def __init__(self, super().__init__(**kwargs) # must be the first line # init and set variables implemented via properties: - self._social_system = None - self.social_system = social_system + self._culture = None + self.culture = culture + # self._social_system = None + # self.social_system = social_system self._next_higher_group = None self.next_higher_group = next_higher_group @@ -57,23 +59,48 @@ def __init__(self, self._next_lower_group = set() self._direct_cells = set() + if self.culture: + self.culture.group_membership_network.add_node(self) + + def deactivate(self): + """Deactivate an individual. + + In particular, deregister from all networks. + + """ + # deregister from all networks: + if self.culture: + self.culture.group_membership_network.remove_node(self) + super().deactivate() # must be the last line + + def reactivate(self): + """Reactivate an individual. + + In particular, deregister with all mandatory networks. + + """ + super().reactivate() # must be the first line + # reregister with all mandatory networks: + if self.culture: + self.culture.group_membership_network.add_node(self) # getters and setters for references: - @property - def social_system(self): - """Get the World the SocialSystem is part of.""" - return self._social_system - - @social_system.setter - def social_system(self, s): - """Set the World the SocialSystem is part of.""" - if self._social_system is not None: - self._social_system._groups.remove(self) - assert isinstance(s, I.SocialSystem), "socialsystem must be of entity type SocialSystem" - s._groups.add(self) - self._social_system = s + + # @property + # def social_system(self): + # """Get the SocialSystem the Group is part of.""" + # return self._social_system + + # @social_system.setter + # def social_system(self, s): + # """Set the SocialSystem the Group is part of.""" + # if self._social_system is not None: + # self._social_system._groups.remove(self) + # assert isinstance(s, I.SocialSystem), "socialsystem must be of entity type SocialSystem" + # s._groups.add(self) + # self._social_system = s @property def next_higher_group(self): @@ -109,10 +136,22 @@ def metabolism(self): """Get the Metabolism of which the Group is a part.""" return self._world.metabolism - @property # read-only + @property def culture(self): - """Get the Culture of which the Group is a part.""" - return self._world.culture + """Get groups's culture.""" + return self._culture + + @culture.setter + def culture(self, c): + """Set groups's culture.""" + if self._culture is not None: + # first deregister from previous culture's list of worlds: + self._culture.groups.remove(self) + if c is not None: + assert isinstance(c, I.Culture), \ + "Culture must be taxon type Culture" + c._groups.add(self) + self._culture = c _higher_groups = unknown """cache, depends on self.next_higher_group @@ -152,74 +191,11 @@ def lower_groups(self): l.update(s.lower_groups) return l - @property # read-only - def direct_cells(self): - """Get cells that directly belong to the SocialSystem.""" - return self._direct_cells - - _cells = unknown - """cache, depends on self.direct_cells, self._next_lower_social_systems, - and lowersocial_system.cells""" - @property # read-only - def cells(self): - """Get cells that directly abd indirectly belong to the SocialSystem.""" - if self._cells is unknown: - # aggregate recursively: - self._cells = self.direct_cells - for s in self._next_lower_social_systems: - self._cells.update(s.cells) - return self._cells - - @cells.setter - def cells(self, u): - """Set cells that directly and indirectly belong to the SocialSystem.""" - assert u == unknown, "setter can only be used to reset cache" - self._cells = unknown - # reset dependent caches: - if self.next_higher_social_system is not None: - self.next_higher_social_system.cells = unknown - - #TODO: direct_inds to members - - _direct_individuals = unknown - """cache, depends on _direct_cells, directcell.individuals""" - @property # read-only - def direct_individuals(self): - """Get resident Individuals not in subsocial_systems.""" - if self._direct_individuals is unknown: - # aggregate from direct_cells: - self._direct_individuals = set() - for c in self._direct_cells: - self._direct_individuals.update(c.individuals) - return self._direct_individuals - - @direct_individuals.setter - def direct_individuals(self, u): - """Set resident Individuals not in subsocial_systems.""" - assert u == unknown, "setter can only be used to reset cache" - self._direct_individuals = unknown - # reset dependent caches: - pass + @property + def group_members(self): + """Get the set of Individuals associated with this Group.""" + return self.culture.group_membership_network.predecessors(self) # .predeccessors as network is directed from inds to groups - _individuals = unknown - """cache, depends on self.cells, cell.individuals""" - @property # read-only - def individuals(self): - """Get direct and indirect resident Individuals.""" - if self._individuals is unknown: - # aggregate from cells: - self._individuals = set() - for c in self.cells: - self._individuals.update(c.individuals) - return self._individuals - - @individuals.setter - def individuals(self, u): - """Set direct and indirect resident Individuals.""" - assert u == unknown, "setter can only be used to reset cache" - self._individuals = unknown - # reset dependent caches: - pass # TODO: helper methods for mergers, splits, etc. diff --git a/pycopancore/model_components/base/implementation/individual.py b/pycopancore/model_components/base/implementation/individual.py index e82c5fc5..5eb11073 100644 --- a/pycopancore/model_components/base/implementation/individual.py +++ b/pycopancore/model_components/base/implementation/individual.py @@ -16,8 +16,6 @@ from .. import interface as I -#TODO: set variable membership - class Individual (I.Individual, abstract.Individual): """Individual entity type mixin implementation class. @@ -56,6 +54,7 @@ def __init__(self, # register with all mandatory networks: if self.culture: self.culture.acquaintance_network.add_node(self) + self.culture.group_membership_network.add_node(self) # TODO: does this work with DiGraph? def deactivate(self): """Deactivate an individual. @@ -66,6 +65,7 @@ def deactivate(self): # deregister from all networks: if self.culture: self.culture.acquaintance_network.remove_node(self) + self.culture.group_membership_network.remove_node(self) super().deactivate() # must be the last line def reactivate(self): @@ -78,6 +78,7 @@ def reactivate(self): # reregister with all mandatory networks: if self.culture: self.culture.acquaintance_network.add_node(self) + self.culture.group_membership_network.add_node(self) # getters and setters for references: @@ -157,6 +158,11 @@ def acquaintances(self): """Get the set of Individuals the Individual is acquainted with.""" return self.culture.acquaintance_network.neighbors(self) + @property + def group_memberships(self): + """Get the set of Groups the Individual is associated with.""" + return self.culture.group_membership_network.successors(self) # .successors as network is directed from inds to groups + # no process-related methods processes = [] # no processes in base diff --git a/pycopancore/model_components/base/interface.py b/pycopancore/model_components/base/interface.py index deb77458..5d1676be 100644 --- a/pycopancore/model_components/base/interface.py +++ b/pycopancore/model_components/base/interface.py @@ -69,6 +69,8 @@ class Culture (object): acquaintance_network = CUL.acquaintance_network + group_membership_network = CUL.group_membership_network + # read-only attributes storing redundant information: worlds = SetVariable("worlds", "Set of Worlds governed by this Culture", @@ -271,6 +273,10 @@ class Individual (object, metaclass=_MixinType): "set of Individuals this one is acquainted with", readonly=True) + group_memberships = SetVariable("group memberships", + "set of Groups this one is associated with", + readonly=True) + # TODO: specify Variable objects for the following: population_share = None """share of social_system's direct population represented by this individual""" @@ -291,6 +297,7 @@ class Individual (object, metaclass=_MixinType): + class Group (object, metaclass=_MixinType): """Basic Group interface. @@ -322,17 +329,13 @@ class Group (object, metaclass=_MixinType): "lower groups", "set of all direct and indirect sub-Groups", readonly=True) - direct_cells = SetVariable("direct cells", "set of direct territory Cells", - readonly=True) - cells = SetVariable("cells", "set of direct and indirect territory Cells", - readonly=True) - direct_individuals = SetVariable( - "direct individuals", - "set of resident Individuals not in subgroups", - readonly=True) - individuals = SetVariable("individuals", - "set of direct or indirect resident Individuals", - readonly=True) + + group_members = SetVariable( + "group members", + "set of all individuals associated with the group", + readonly=True + ) + # specified only now to avoid recursion errors: @@ -340,5 +343,7 @@ class Group (object, metaclass=_MixinType): Group.higher_groups.type = Group Group.next_lower_groups.type = Group Group.lower_groups.type = Group +# Individual.group_memberships.type = Group # TODO: assert this is at the right place +# Group.group_members.type = Individual #Group.groups.type = Group #SocialSystem.top_level_groups.type = Group diff --git a/studies/run_groups_test_seven_dwarfs.py b/studies/run_groups_test_seven_dwarfs.py index 1c241a9b..933a0aba 100644 --- a/studies/run_groups_test_seven_dwarfs.py +++ b/studies/run_groups_test_seven_dwarfs.py @@ -67,16 +67,20 @@ ) for i in range(dwarfs) ] +print("\n The individuals: \n") +print(individuals) +print("\n \n") + # assigning individuals to cell is not necessary since it is done by # initializing the individuals in 'base.Individuals' with the 'cell' method #instantiate groups -ng = 2 #number of groups (group 1 hates snowwhite, group 2 loves her) -groups = [M.Group(social_system=social_system) - for i in range (ng) - ] - +ng = 5 #number of groups +groups = [M.Group(culture=culture) for i in range (ng)] +print("\n The groups: \n") +print(groups) +print("\n \n") start = time() @@ -84,6 +88,11 @@ print('\n runner starting') +# first test for group network +nx.draw(culture.group_membership_network) + +plt.show() + # Define termination signals as list [ signal_method, object_method_works_on ] # the termination method 'check_for_extinction' must return a boolean termination_signal = [M.Culture.check_for_extinction, @@ -111,83 +120,3 @@ print("max. time step", (t[1:]-t[:-1]).max()) -# proceeding for plotting - -# Create List of all dwarfes, not only the ones instantiated before the run, -# but also the one created during the run. - -if M.Individual.idle_entities: - all_dwarfs = M.Individual.instances + M.Individual.idle_entities -else: - all_dwarfs = M.Individual.instances - -individuals_age = np.array([traj[M.Individual.age][dwarf] - for dwarf in all_dwarfs]) - - -individuals_beard_length = np.array([traj[M.Individual.beard_length][dwarf] - for dwarf in all_dwarfs]) - -cell_stock = np.array(traj[M.Cell.eating_stock][cells[0]]) - -t = np.array(traj['t']) - -data_age = [] -print('data age', data_age) -for i, dwarf in enumerate(all_dwarfs): - data_age.append(go.Scatter( - x=t, - y=individuals_age[i], - mode="lines", - name="age of dwarf no. {}".format(i), - line=dict( - color="green", - width=4 - ) - )) - -data_beard_length = [] -print('data beard', data_beard_length) -for i, dwarf in enumerate(all_dwarfs): - data_beard_length.append(go.Scatter( - x=t, - y=individuals_beard_length[i], - mode="lines", - name="beard length of dwarf no. {}".format(i), - line=dict( - color="red", - width=4 - ) - )) - -data_stock = [] -data_stock.append(go.Scatter( - x=t, - y=cell_stock, - mode="lines", - name="stock of cell", - line=dict(color="blue", - width=4 - ) - )) - - -layout = dict(title='seven dwarfs', - xaxis=dict(title='time [yr]'), - yaxis=dict(title='value'), - ) - - -# getting plots of two dwarfs: -fig = dict(data=[data_age[0], data_beard_length[0], data_stock[0]], - layout=layout) -py.plot(fig, filename="our-model-result{}.html".format(0)) - -fig = dict(data=[data_age[5], data_beard_length[5], data_stock[0]], - layout=layout) -py.plot(fig, filename="our-model-result{}.html".format(5)) - -#nx.draw(traj[M.Culture.acquaintance_network][culture][1]) -#plt.show() -for i in range(len(traj['t'])): - print(list(traj[M.Culture.acquaintance_network][culture][i].nodes())) From 696eb7447a63c434a8de27d8b8377401c2297ca1 Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Wed, 8 Jun 2022 16:38:05 +0200 Subject: [PATCH 20/33] Group now gets culture and further things over being part of a world. --- .../base/implementation/group.py | 161 ++++++++++-------- .../model_components/base/interface.py | 39 +++-- studies/run_groups_test_seven_dwarfs.py | 17 +- 3 files changed, 123 insertions(+), 94 deletions(-) diff --git a/pycopancore/model_components/base/implementation/group.py b/pycopancore/model_components/base/implementation/group.py index 22ce9ebd..37684ac7 100644 --- a/pycopancore/model_components/base/implementation/group.py +++ b/pycopancore/model_components/base/implementation/group.py @@ -29,7 +29,8 @@ class Group (I.Group, abstract.Group): def __init__(self, *, - culture=None, + world, + # culture=None, next_higher_group=None, **kwargs ): @@ -48,8 +49,11 @@ def __init__(self, super().__init__(**kwargs) # must be the first line # init and set variables implemented via properties: - self._culture = None - self.culture = culture + # self._culture = None + # self.culture = culture + self._world = None + self.world = world + # self._social_system = None # self.social_system = social_system self._next_higher_group = None @@ -87,6 +91,19 @@ def reactivate(self): # getters and setters for references: + @property + def world(self): + """Get the World the Group is part of.""" + return self._world + + @world.setter + def world(self, w): + """Set the World the Group is part of.""" + if self._world is not None: + self._world._social_systems.remove(self) + assert isinstance(w, I.World), "world must be of entity type World" + w._social_systems.add(self) + self._world = w # @property # def social_system(self): @@ -102,27 +119,27 @@ def reactivate(self): # s._groups.add(self) # self._social_system = s - @property - def next_higher_group(self): - """Get next higher group.""" - return self._next_higher_group - - @next_higher_group.setter - def next_higher_group(self, s): - """Set next higher group.""" - if self._next_higher_group is not None: - self._next_higher_group._next_lower_groups.remove(self) - # reset dependent cache: - self._next_higher_group.cells = unknown - if s is not None: - assert isinstance(s, I.Group), \ - "next_higher_group must be of entity type group" - s._next_lower_groups.add(self) - # reset dependent cache: - s.cells = unknown - self._next_higher_group = s + # @property + # def next_higher_group(self): + # """Get next higher group.""" + # return self._next_higher_group + + # @next_higher_group.setter + # def next_higher_group(self, s): + # """Set next higher group.""" + # if self._next_higher_group is not None: + # self._next_higher_group._next_lower_groups.remove(self) + # reset dependent cache: + # self._next_higher_group.cells = unknown + # if s is not None: + # assert isinstance(s, I.Group), \ + # "next_higher_group must be of entity type group" + # s._next_lower_groups.add(self) + # reset dependent cache: + # s.cells = unknown + # self._next_higher_group = s # reset dependent caches: - self.higher_groups = unknown + # self.higher_groups = unknown # getters for backwards references and convenience variables: @@ -136,60 +153,60 @@ def metabolism(self): """Get the Metabolism of which the Group is a part.""" return self._world.metabolism - @property - def culture(self): - """Get groups's culture.""" - return self._culture - - @culture.setter - def culture(self, c): - """Set groups's culture.""" - if self._culture is not None: - # first deregister from previous culture's list of worlds: - self._culture.groups.remove(self) - if c is not None: - assert isinstance(c, I.Culture), \ - "Culture must be taxon type Culture" - c._groups.add(self) - self._culture = c - - _higher_groups = unknown + # @property + # def culture(self): + # """Get groups's culture.""" + # return self._culture + + # @culture.setter + # def culture(self, c): + # """Set groups's culture.""" + # if self._culture is not None: + # first deregister from previous culture's list of worlds: + # self._culture.groups.remove(self) + # if c is not None: + # assert isinstance(c, I.Culture), \ + # "Culture must be taxon type Culture" + # c._groups.add(self) + # self._culture = c + + # _higher_groups = unknown """cache, depends on self.next_higher_group and self.next_higher_group.higher_groups""" - @property # read-only - def higher_groups(self): - """Get higher groups.""" - if self._higher_groups is unknown: + # @property # read-only + # def higher_groups(self): + # """Get higher groups.""" + # if self._higher_groups is unknown: # find recursively: - self._higher_groups = [] if self.next_higher_group is None \ - else ([self.next_higher_group] - + self.next_higher_group.groups) - return self._higher_groups - - @higher_groups.setter - def higher_groups(self, u): - """Set higher groups.""" - assert u == unknown, "setter can only be used to reset cache" - self._higher_groups = unknown + # self._higher_groups = [] if self.next_higher_group is None \ + # else ([self.next_higher_group] + # + self.next_higher_group.groups) + # return self._higher_groups + + # @higher_groups.setter + # def higher_groups(self, u): + # """Set higher groups.""" + # assert u == unknown, "setter can only be used to reset cache" + # self._higher_groups = unknown # reset dependent caches: - for s in self._next_lower_groups: - s.higher_groups = unknown - for c in self._direct_cells: - c.groups = unknown - - @property # read-only - def next_lower_groups(self): - """Get next lower groups.""" - return self._next_lower_groups - - @property # read-only - def lower_groups(self): - """Get lower groups.""" + # for s in self._next_lower_groups: + # s.higher_groups = unknown + # for c in self._direct_cells: + # c.groups = unknown + + # @property # read-only + # def next_lower_groups(self): + # """Get next lower groups.""" + # return self._next_lower_groups + + # @property # read-only + # def lower_groups(self): + # """Get lower groups.""" # aggregate recursively: - l = self._next_lower_groups - for s in self._next_lower_groups: - l.update(s.lower_groups) - return l + # l = self._next_lower_groups + # for s in self._next_lower_groups: + # l.update(s.lower_groups) + # return l @property def group_members(self): diff --git a/pycopancore/model_components/base/interface.py b/pycopancore/model_components/base/interface.py index 5d1676be..5d8d3975 100644 --- a/pycopancore/model_components/base/interface.py +++ b/pycopancore/model_components/base/interface.py @@ -122,6 +122,9 @@ class World (object, metaclass=_MixinType): individuals = SetVariable("individuals", "Set of Individuals residing on this world", readonly=True) + groups = SetVariable("groups", + "Set of Groups existing on this world", + readonly=True) # specified only now to avoid recursion errors: @@ -306,8 +309,8 @@ class Group (object, metaclass=_MixinType): # references: #social_system = ReferenceVariable("socialsystem", "", type=SocialSystem) - next_higher_group = ReferenceVariable("next higher group", "optional", - allow_none=True) # type is Group, hence it can only be specified after class Group is defined, see below + # next_higher_group = ReferenceVariable("next higher group", "optional", + # allow_none=True) # type is Group, hence it can only be specified after class Group is defined, see below # read-only attributes storing redundant information: @@ -317,18 +320,18 @@ class Group (object, metaclass=_MixinType): readonly=True) culture = ReferenceVariable("culture", "", type=Culture, readonly=True) - higher_groups = SetVariable( - "higher groups", - "upward list of (in)direct super-Groups", - readonly=True) - next_lower_groups = SetVariable( - "next lower groups", - "set of sub-Groups of next lower level", - readonly=True) - lower_groups = SetVariable( - "lower groups", - "set of all direct and indirect sub-Groups", - readonly=True) + # higher_groups = SetVariable( + # "higher groups", + # "upward list of (in)direct super-Groups", + # readonly=True) + # next_lower_groups = SetVariable( + # "next lower groups", + # "set of sub-Groups of next lower level", + # readonly=True) + # lower_groups = SetVariable( + # "lower groups", + # "set of all direct and indirect sub-Groups", + # readonly=True) group_members = SetVariable( "group members", @@ -339,10 +342,10 @@ class Group (object, metaclass=_MixinType): # specified only now to avoid recursion errors: -Group.next_higher_group.type = Group -Group.higher_groups.type = Group -Group.next_lower_groups.type = Group -Group.lower_groups.type = Group +# Group.next_higher_group.type = Group +# Group.higher_groups.type = Group +# Group.next_lower_groups.type = Group +# Group.lower_groups.type = Group # Individual.group_memberships.type = Group # TODO: assert this is at the right place # Group.group_members.type = Individual #Group.groups.type = Group diff --git a/studies/run_groups_test_seven_dwarfs.py b/studies/run_groups_test_seven_dwarfs.py index 933a0aba..f2ce6424 100644 --- a/studies/run_groups_test_seven_dwarfs.py +++ b/studies/run_groups_test_seven_dwarfs.py @@ -15,6 +15,7 @@ # License: BSD 2-clause license import numpy as np +from numpy.random import uniform from time import time import datetime as dt @@ -33,7 +34,7 @@ # setting time step to hand to 'Runner.run()' timestep = .1 nc = 1 # number of caves -dwarfs = 7 # number of dwarfs +dwarfs = 10 # number of dwarfs # instantiate model M (needs to be done in the beginning of each script). # This configures the model M through 'ModelLogics' in module @@ -75,8 +76,8 @@ # initializing the individuals in 'base.Individuals' with the 'cell' method #instantiate groups -ng = 5 #number of groups -groups = [M.Group(culture=culture) for i in range (ng)] +ng = 10 #number of groups +groups = [M.Group(world=world) for i in range (ng)] print("\n The groups: \n") print(groups) @@ -89,8 +90,16 @@ print('\n runner starting') # first test for group network -nx.draw(culture.group_membership_network) +#nx.draw(culture.group_membership_network) +#plt.show() + +# initialize some network: +for i in enumerate(individuals): + for j in enumerate(groups): + culture.group_membership_network.add_edge(i, j) + +nx.draw(culture.group_membership_network) plt.show() # Define termination signals as list [ signal_method, object_method_works_on ] From 75580f453ce9a9a438d28cbe49ae63f2ed6ab31e Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Wed, 8 Jun 2022 16:44:25 +0200 Subject: [PATCH 21/33] little fix --- pycopancore/model_components/base/implementation/group.py | 4 ++-- pycopancore/model_components/base/implementation/world.py | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pycopancore/model_components/base/implementation/group.py b/pycopancore/model_components/base/implementation/group.py index 37684ac7..b8b6b3d1 100644 --- a/pycopancore/model_components/base/implementation/group.py +++ b/pycopancore/model_components/base/implementation/group.py @@ -100,9 +100,9 @@ def world(self): def world(self, w): """Set the World the Group is part of.""" if self._world is not None: - self._world._social_systems.remove(self) + self._world._groups.remove(self) assert isinstance(w, I.World), "world must be of entity type World" - w._social_systems.add(self) + w._groups.add(self) self._world = w # @property diff --git a/pycopancore/model_components/base/implementation/world.py b/pycopancore/model_components/base/implementation/world.py index 05dea718..9457f44e 100644 --- a/pycopancore/model_components/base/implementation/world.py +++ b/pycopancore/model_components/base/implementation/world.py @@ -59,6 +59,7 @@ def __init__(self, self.culture = culture self._social_systems = set() self._cells = set() + self._groups = set() # make sure all variable values are valid: self.assert_valid() @@ -164,6 +165,11 @@ def individuals(self, u): # reset dependent caches: pass + @property # read-only + def groups(self): + """Get the set of all Groups on this World.""" + return self._groups + processes = [ # TODO: convert this into an Implicit equation once supported: From 244a6018c68b872e4c517299fca448999d1ebe2d Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Thu, 9 Jun 2022 14:30:59 +0200 Subject: [PATCH 22/33] run_group_test_seven_dwarfs.py now demonstrates minimum features of the new group entity --- studies/run_groups_test_seven_dwarfs.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/studies/run_groups_test_seven_dwarfs.py b/studies/run_groups_test_seven_dwarfs.py index f2ce6424..f03ec4a6 100644 --- a/studies/run_groups_test_seven_dwarfs.py +++ b/studies/run_groups_test_seven_dwarfs.py @@ -95,13 +95,21 @@ # initialize some network: -for i in enumerate(individuals): +for i in individuals: for j in enumerate(groups): culture.group_membership_network.add_edge(i, j) nx.draw(culture.group_membership_network) plt.show() +# print("Group Members: \n") +# for i in groups: +# print(i.group_members) +print("Individual 1 Group Memberships: \n") +print(list(individuals[0].group_memberships)) +# for i in individuals: +# print(i.group_memberships) + # Define termination signals as list [ signal_method, object_method_works_on ] # the termination method 'check_for_extinction' must return a boolean termination_signal = [M.Culture.check_for_extinction, From 53f58d98e0cd6942d5deffe95308465f3d626a9f Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Mon, 13 Jun 2022 16:51:31 +0200 Subject: [PATCH 23/33] run_group_test_seven_dwarfs.py now demonstrates minimum features of the new group entity --- .../base/implementation/group.py | 19 +++++++++++------ .../base/implementation/individual.py | 2 +- studies/run_groups_test_seven_dwarfs.py | 21 ++++++++++++------- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/pycopancore/model_components/base/implementation/group.py b/pycopancore/model_components/base/implementation/group.py index b8b6b3d1..012b15a0 100644 --- a/pycopancore/model_components/base/implementation/group.py +++ b/pycopancore/model_components/base/implementation/group.py @@ -38,8 +38,8 @@ def __init__(self, Parameters ---------- - socialsystem: obj - SocialSystem the Group belongs to (default is None) + world: obj + World the Group belongs to next_higher_group: obj Optional Group the Group belongs to (default is None) **kwargs @@ -67,7 +67,7 @@ def __init__(self, self.culture.group_membership_network.add_node(self) def deactivate(self): - """Deactivate an individual. + """Deactivate a group. In particular, deregister from all networks. @@ -78,7 +78,7 @@ def deactivate(self): super().deactivate() # must be the last line def reactivate(self): - """Reactivate an individual. + """Reactivate a group. In particular, deregister with all mandatory networks. @@ -88,6 +88,12 @@ def reactivate(self): if self.culture: self.culture.group_membership_network.add_node(self) + def member_mean(self, state): + """ + Calculate the arithmetic mean of a state of all members of a groups. + """ + + # getters and setters for references: @@ -211,10 +217,11 @@ def metabolism(self): @property def group_members(self): """Get the set of Individuals associated with this Group.""" - return self.culture.group_membership_network.predecessors(self) # .predeccessors as network is directed from inds to groups + return self.culture.group_membership_network.neighbors(self) # .predeccessors as network is directed from inds to groups + + - # TODO: helper methods for mergers, splits, etc. # no process-related methods diff --git a/pycopancore/model_components/base/implementation/individual.py b/pycopancore/model_components/base/implementation/individual.py index 5eb11073..8ae255f4 100644 --- a/pycopancore/model_components/base/implementation/individual.py +++ b/pycopancore/model_components/base/implementation/individual.py @@ -161,7 +161,7 @@ def acquaintances(self): @property def group_memberships(self): """Get the set of Groups the Individual is associated with.""" - return self.culture.group_membership_network.successors(self) # .successors as network is directed from inds to groups + return self.culture.group_membership_network.neighbors(self) # .successors as network is directed from inds to groups # no process-related methods diff --git a/studies/run_groups_test_seven_dwarfs.py b/studies/run_groups_test_seven_dwarfs.py index f03ec4a6..f200cd1e 100644 --- a/studies/run_groups_test_seven_dwarfs.py +++ b/studies/run_groups_test_seven_dwarfs.py @@ -99,16 +99,23 @@ for j in enumerate(groups): culture.group_membership_network.add_edge(i, j) + +print("Individual 1 Group Memberships:") +print(list(individuals[0].group_memberships)) +print("\n") + +print("Group Members:") +print(groups[0]) +# print(groups[0].group_members) +print("\n") + +#draw network of one group +nx.draw(individuals[0].culture.group_membership_network) + nx.draw(culture.group_membership_network) plt.show() -# print("Group Members: \n") -# for i in groups: -# print(i.group_members) -print("Individual 1 Group Memberships: \n") -print(list(individuals[0].group_memberships)) -# for i in individuals: -# print(i.group_memberships) + # Define termination signals as list [ signal_method, object_method_works_on ] # the termination method 'check_for_extinction' must return a boolean From b602e719e0f26bef8a66e9ab5c9e896a931eaae8 Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Thu, 16 Jun 2022 11:09:18 +0200 Subject: [PATCH 24/33] - Change group from living in World to living in Culture - Add visualisation of layered group network in run_group_test_seven_dwarfs.py - Fix minor issues --- .../base/implementation/culture.py | 6 +++ .../base/implementation/group.py | 54 +++++++++---------- .../base/implementation/individual.py | 4 +- .../base/implementation/world.py | 7 --- .../model_components/base/interface.py | 9 ++-- studies/run_exploit.py | 1 + studies/run_groups_test_seven_dwarfs.py | 42 ++++++++++++--- 7 files changed, 76 insertions(+), 47 deletions(-) diff --git a/pycopancore/model_components/base/implementation/culture.py b/pycopancore/model_components/base/implementation/culture.py index 79cf5799..1d2239af 100644 --- a/pycopancore/model_components/base/implementation/culture.py +++ b/pycopancore/model_components/base/implementation/culture.py @@ -53,6 +53,7 @@ def __init__(self, self.group_membership_network = group_membership_network self._worlds = set() + self._groups = set() # make sure all variable values are valid: self.assert_valid() @@ -63,6 +64,11 @@ def worlds(self): """Get the set of all Worlds this Culture acts in.""" return self._worlds + @property # read-only + def groups(self): + """Get the set of all Groups in this Culture.""" + return self._groups + # no process-related methods processes = [] # no processes in base diff --git a/pycopancore/model_components/base/implementation/group.py b/pycopancore/model_components/base/implementation/group.py index 012b15a0..063b7342 100644 --- a/pycopancore/model_components/base/implementation/group.py +++ b/pycopancore/model_components/base/implementation/group.py @@ -29,8 +29,7 @@ class Group (I.Group, abstract.Group): def __init__(self, *, - world, - # culture=None, + culture, next_higher_group=None, **kwargs ): @@ -38,8 +37,8 @@ def __init__(self, Parameters ---------- - world: obj - World the Group belongs to + culture: obj + Culture the Group belongs to next_higher_group: obj Optional Group the Group belongs to (default is None) **kwargs @@ -49,10 +48,10 @@ def __init__(self, super().__init__(**kwargs) # must be the first line # init and set variables implemented via properties: - # self._culture = None - # self.culture = culture - self._world = None - self.world = world + self._culture = None + self.culture = culture + # self._world = None + # self.world = world # self._social_system = None # self.social_system = social_system @@ -64,7 +63,7 @@ def __init__(self, self._direct_cells = set() if self.culture: - self.culture.group_membership_network.add_node(self) + self.culture.group_membership_network.add_node(self, type="Group", color="green") def deactivate(self): """Deactivate a group. @@ -86,7 +85,7 @@ def reactivate(self): super().reactivate() # must be the first line # reregister with all mandatory networks: if self.culture: - self.culture.group_membership_network.add_node(self) + self.culture.group_membership_network.add_node(self, type="Group", color="green") def member_mean(self, state): """ @@ -159,22 +158,22 @@ def metabolism(self): """Get the Metabolism of which the Group is a part.""" return self._world.metabolism - # @property - # def culture(self): - # """Get groups's culture.""" - # return self._culture - - # @culture.setter - # def culture(self, c): - # """Set groups's culture.""" - # if self._culture is not None: - # first deregister from previous culture's list of worlds: - # self._culture.groups.remove(self) - # if c is not None: - # assert isinstance(c, I.Culture), \ - # "Culture must be taxon type Culture" - # c._groups.add(self) - # self._culture = c + @property + def culture(self): + """Get culture group is part of.""" + return self._culture + + @culture.setter + def culture(self, c): + """Set culture group is part of.""" + if self._culture is not None: + # first deregister from previous culture's list of worlds: + self._culture.groups.remove(self) + if c is not None: + assert isinstance(c, I.Culture), \ + "Culture must be taxon type Culture" + c._groups.add(self) + self._culture = c # _higher_groups = unknown """cache, depends on self.next_higher_group @@ -217,7 +216,8 @@ def metabolism(self): @property def group_members(self): """Get the set of Individuals associated with this Group.""" - return self.culture.group_membership_network.neighbors(self) # .predeccessors as network is directed from inds to groups + # return self.culture.group_membership_network.neighbors(self) + return self.culture.group_membership_network.predecessors(self) # .predecessors as network is directed from inds to groups diff --git a/pycopancore/model_components/base/implementation/individual.py b/pycopancore/model_components/base/implementation/individual.py index 8ae255f4..dcaa0ffb 100644 --- a/pycopancore/model_components/base/implementation/individual.py +++ b/pycopancore/model_components/base/implementation/individual.py @@ -54,7 +54,7 @@ def __init__(self, # register with all mandatory networks: if self.culture: self.culture.acquaintance_network.add_node(self) - self.culture.group_membership_network.add_node(self) # TODO: does this work with DiGraph? + self.culture.group_membership_network.add_node(self, type="Individual", color="yellow") def deactivate(self): """Deactivate an individual. @@ -78,7 +78,7 @@ def reactivate(self): # reregister with all mandatory networks: if self.culture: self.culture.acquaintance_network.add_node(self) - self.culture.group_membership_network.add_node(self) + self.culture.group_membership_network.add_node(self, type="Individual", color="yellow") # getters and setters for references: diff --git a/pycopancore/model_components/base/implementation/world.py b/pycopancore/model_components/base/implementation/world.py index 9457f44e..3e12fe08 100644 --- a/pycopancore/model_components/base/implementation/world.py +++ b/pycopancore/model_components/base/implementation/world.py @@ -59,7 +59,6 @@ def __init__(self, self.culture = culture self._social_systems = set() self._cells = set() - self._groups = set() # make sure all variable values are valid: self.assert_valid() @@ -165,12 +164,6 @@ def individuals(self, u): # reset dependent caches: pass - @property # read-only - def groups(self): - """Get the set of all Groups on this World.""" - return self._groups - - processes = [ # TODO: convert this into an Implicit equation once supported: Explicit("aggregate cell carbon stocks", diff --git a/pycopancore/model_components/base/interface.py b/pycopancore/model_components/base/interface.py index 5d8d3975..6ac69ebb 100644 --- a/pycopancore/model_components/base/interface.py +++ b/pycopancore/model_components/base/interface.py @@ -78,6 +78,9 @@ class Culture (object): # entity types: + groups = SetVariable("groups", + "Set of Groups existing on this world", + readonly=True) # TODO: clarify whether it is necessary to specify the metaclass here! class World (object, metaclass=_MixinType): @@ -122,9 +125,7 @@ class World (object, metaclass=_MixinType): individuals = SetVariable("individuals", "Set of Individuals residing on this world", readonly=True) - groups = SetVariable("groups", - "Set of Groups existing on this world", - readonly=True) + # specified only now to avoid recursion errors: @@ -339,7 +340,7 @@ class Group (object, metaclass=_MixinType): readonly=True ) - +Culture.groups.type = Group # specified only now to avoid recursion errors: # Group.next_higher_group.type = Group diff --git a/studies/run_exploit.py b/studies/run_exploit.py index 3275f221..23ab8837 100755 --- a/studies/run_exploit.py +++ b/studies/run_exploit.py @@ -18,6 +18,7 @@ from time import time import datetime as dt from numpy import random +import argparse import plotly.offline as py import plotly.graph_objs as go diff --git a/studies/run_groups_test_seven_dwarfs.py b/studies/run_groups_test_seven_dwarfs.py index f200cd1e..e751ec4c 100644 --- a/studies/run_groups_test_seven_dwarfs.py +++ b/studies/run_groups_test_seven_dwarfs.py @@ -20,6 +20,7 @@ import datetime as dt import networkx as nx +from networkx.algorithms import bipartite import matplotlib.pyplot as plt import plotly.offline as py @@ -77,7 +78,7 @@ #instantiate groups ng = 10 #number of groups -groups = [M.Group(world=world) for i in range (ng)] +groups = [M.Group(culture=culture) for i in range (ng)] print("\n The groups: \n") print(groups) @@ -96,9 +97,8 @@ # initialize some network: for i in individuals: - for j in enumerate(groups): - culture.group_membership_network.add_edge(i, j) - + for g in groups: + culture.group_membership_network.add_edge(i, g) print("Individual 1 Group Memberships:") print(list(individuals[0].group_memberships)) @@ -106,13 +106,41 @@ print("Group Members:") print(groups[0]) -# print(groups[0].group_members) +print(list(groups[0].group_members)) print("\n") #draw network of one group -nx.draw(individuals[0].culture.group_membership_network) +# nx.draw(individuals[0].culture.group_membership_network) +# plt.show() + +GM = culture.group_membership_network + +print(GM.nodes.data()) + + +color_map = [] +shape_map = [] +for node in list(GM.nodes): + print(node) + print(GM.nodes[node]) + color_map.append(GM.nodes[node]["color"]) + + if GM.nodes[node]["type"] == "Group": + shape_map.append("o") + else: + shape_map.append("^") + + + +top_nodes = {n for n, d in GM.nodes(data=True) if d["type"] == "Group"} +bottom_nodes = set(GM) - top_nodes + +print(list(top_nodes)) + +nx.draw(GM, node_color=color_map, with_labels=True, + pos=nx.bipartite_layout(GM, bottom_nodes, align="horizontal", aspect_ratio=4/1)) -nx.draw(culture.group_membership_network) +# nx.draw(culture.group_membership_network) plt.show() From 07035ab3cd7a83eddcbd7ce044fae0f9a596f011 Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Mon, 20 Jun 2022 15:33:54 +0200 Subject: [PATCH 25/33] add/adjust documentation --- .../abstract_level/entity_types/group.rst | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/docs/framework_documentation/abstract_level/entity_types/group.rst b/docs/framework_documentation/abstract_level/entity_types/group.rst index 7ef8158c..173ad33e 100644 --- a/docs/framework_documentation/abstract_level/entity_types/group.rst +++ b/docs/framework_documentation/abstract_level/entity_types/group.rst @@ -11,7 +11,7 @@ may not be sufficient and a distinction of social strata or other social groups "indigenous people", "social democrats", a certain NGO, ...) that is transverse to the former partitioning is helpful in addition. -For this, we will in the future provide an entity-type "group" +For this, the entity-type "group" is provided which is meant to represent any grouping of individuals (that may come from one or several social systems) by meaningful cultural or social-metabolic aspects. @@ -33,19 +33,24 @@ Basic relationships to other entity-types A group will usually... -- have several member :doc:`individuals` +- have several members :doc:`individuals`, + which is represented by a group membership directed network owned by the culture taxon In addition, a group may... -- have one or several "leader" :doc:`individuals`, - of which one may be the dominant leader +- have an "intra" group network between members :doc:`individuals` + (this feature is not completely implemented yet) -- have a "headquarters" :doc:`cell` +- have one or several "leader" :doc:`individuals`, + of which one may be the dominant leader (this feature is not implemented yet) -- be related to other groups via some network owned by the culture taxon +- have a "headquarters" :doc:`cell` (this feature is not implemented yet) + +- be related to other groups via an "inter" network owned by the culture taxon (which will typically interact with the network of personal acquaintance between member individuals) + - (this feature is not completely implemented yet) -- act as the current "elite" in some :doc:`social system` +- act as the current "elite" in some :doc:`social system` (this feature is not implemented yet) All these relationships may be dynamic. @@ -53,5 +58,5 @@ Finally, a group may... - be a permanent subgroup of a larger group or :doc:`social system` *by definition* (rather than by coincidence, e.g., "scientists" are by definition a subgroup of the group "academics", - and "German workers" may be by definition a subgroup of the social system "Germany") + and "German workers" may be by definition a subgroup of the social system "Germany") - (this feature is not implemented yet) From 356df4be9580ede7c52c969e2cc8b6b10027164b Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Thu, 28 Jul 2022 16:28:30 +0200 Subject: [PATCH 26/33] - bypassed AttributeErrors in base implementation for group by getting world, environment and metabolism the group lives in via necessary object world that is passed in study script along with culture: M.Group(culture=culture, world=world) --- .../base/implementation/group.py | 57 ++++++++-------- .../base/implementation/world.py | 6 ++ .../maxploit_individual_layer/__init__.py | 18 +++++ .../implementation/__init__.py | 22 +++++++ .../maxploit_social_norms/__init__.py | 18 +++++ .../implementation/__init__.py | 22 +++++++ .../seven_dwarfs/implementation/__init__.py | 1 + .../seven_dwarfs/implementation/group.py | 66 +++++++++++++++++++ .../seven_dwarfs/interface.py | 7 ++ .../model_components/seven_dwarfs/model.py | 4 +- pycopancore/models/groups_seven_dwarfs.py | 2 +- studies/run_groups_test_seven_dwarfs.py | 15 +++-- 12 files changed, 202 insertions(+), 36 deletions(-) create mode 100644 pycopancore/model_components/maxploit_individual_layer/__init__.py create mode 100644 pycopancore/model_components/maxploit_individual_layer/implementation/__init__.py create mode 100644 pycopancore/model_components/maxploit_social_norms/__init__.py create mode 100644 pycopancore/model_components/maxploit_social_norms/implementation/__init__.py create mode 100644 pycopancore/model_components/seven_dwarfs/implementation/group.py diff --git a/pycopancore/model_components/base/implementation/group.py b/pycopancore/model_components/base/implementation/group.py index 063b7342..6e39c053 100644 --- a/pycopancore/model_components/base/implementation/group.py +++ b/pycopancore/model_components/base/implementation/group.py @@ -30,6 +30,7 @@ class Group (I.Group, abstract.Group): def __init__(self, *, culture, + world, next_higher_group=None, **kwargs ): @@ -39,6 +40,8 @@ def __init__(self, ---------- culture: obj Culture the Group belongs to + world: obj + World the Group belongs to (to bypass AttributeErrors for now) next_higher_group: obj Optional Group the Group belongs to (default is None) **kwargs @@ -50,17 +53,17 @@ def __init__(self, # init and set variables implemented via properties: self._culture = None self.culture = culture - # self._world = None - # self.world = world + self._world = None + self.world = world # self._social_system = None # self.social_system = social_system - self._next_higher_group = None - self.next_higher_group = next_higher_group + # self._next_higher_group = None + # self.next_higher_group = next_higher_group # init caches: - self._next_lower_group = set() - self._direct_cells = set() + # self._next_lower_group = set() + # self._direct_cells = set() if self.culture: self.culture.group_membership_network.add_node(self, type="Group", color="green") @@ -87,14 +90,26 @@ def reactivate(self): if self.culture: self.culture.group_membership_network.add_node(self, type="Group", color="green") - def member_mean(self, state): - """ - Calculate the arithmetic mean of a state of all members of a groups. - """ + # getters and setters for references: + #culture needs to be before world, as group gets its world etc. over its culture + @property + def culture(self): + """Get culture group is part of.""" + return self._culture - # getters and setters for references: + @culture.setter + def culture(self, c): + """Set culture group is part of.""" + if self._culture is not None: + # first deregister from previous culture's list of worlds: + self._culture.groups.remove(self) + if c is not None: + assert isinstance(c, I.Culture), \ + "Culture must be taxon type Culture" + c._groups.add(self) + self._culture = c @property def world(self): @@ -105,7 +120,8 @@ def world(self): def world(self, w): """Set the World the Group is part of.""" if self._world is not None: - self._world._groups.remove(self) + # first deregister from previous world's list of cells: + self._world.groups.remove(self) assert isinstance(w, I.World), "world must be of entity type World" w._groups.add(self) self._world = w @@ -158,23 +174,6 @@ def metabolism(self): """Get the Metabolism of which the Group is a part.""" return self._world.metabolism - @property - def culture(self): - """Get culture group is part of.""" - return self._culture - - @culture.setter - def culture(self, c): - """Set culture group is part of.""" - if self._culture is not None: - # first deregister from previous culture's list of worlds: - self._culture.groups.remove(self) - if c is not None: - assert isinstance(c, I.Culture), \ - "Culture must be taxon type Culture" - c._groups.add(self) - self._culture = c - # _higher_groups = unknown """cache, depends on self.next_higher_group and self.next_higher_group.higher_groups""" diff --git a/pycopancore/model_components/base/implementation/world.py b/pycopancore/model_components/base/implementation/world.py index 3e12fe08..fa6851bb 100644 --- a/pycopancore/model_components/base/implementation/world.py +++ b/pycopancore/model_components/base/implementation/world.py @@ -59,6 +59,7 @@ def __init__(self, self.culture = culture self._social_systems = set() self._cells = set() + self._groups = set() # make sure all variable values are valid: self.assert_valid() @@ -145,6 +146,11 @@ def cells(self): """Get the set of Cells on this World.""" return self._cells + @property # read-only + def groups(self): + """Get the set of Groups on this World.""" + return self._groups + _individuals = unknown """cache, depends on self.cells, cell.individuals""" @property # read-only diff --git a/pycopancore/model_components/maxploit_individual_layer/__init__.py b/pycopancore/model_components/maxploit_individual_layer/__init__.py new file mode 100644 index 00000000..1e1226a7 --- /dev/null +++ b/pycopancore/model_components/maxploit_individual_layer/__init__.py @@ -0,0 +1,18 @@ +"""Model component package simple_extraction.""" + +# This file is part of pycopancore. +# +# Copyright (C) 2016-2017 by COPAN team at Potsdam Institute for Climate +# Impact Research +# +# URL: +# Contact: core@pik-potsdam.de +# License: BSD 2-clause license + +from . import interface + +# export all implementation classes: +from .implementation import * + +# export model component mixin class: +from .model import Model diff --git a/pycopancore/model_components/maxploit_individual_layer/implementation/__init__.py b/pycopancore/model_components/maxploit_individual_layer/implementation/__init__.py new file mode 100644 index 00000000..f990b981 --- /dev/null +++ b/pycopancore/model_components/maxploit_individual_layer/implementation/__init__.py @@ -0,0 +1,22 @@ +""" +Model component implementation subpackage exploit_social_learning. +""" + +# This file is part of pycopancore. +# +# Copyright (C) 2016-2017 by COPAN team at Potsdam Institute for Climate +# Impact Research +# +# URL: +# Contact: core@pik-potsdam.de +# License: BSD 2-clause license + +# TODO: adjust the following lists to your needs: + +# export all provided entity type implementation mixin classes: +from .world import World +from .individual import Individual + +# export all provided process taxon implementation mixin classes: + +from .culture import Culture diff --git a/pycopancore/model_components/maxploit_social_norms/__init__.py b/pycopancore/model_components/maxploit_social_norms/__init__.py new file mode 100644 index 00000000..1e1226a7 --- /dev/null +++ b/pycopancore/model_components/maxploit_social_norms/__init__.py @@ -0,0 +1,18 @@ +"""Model component package simple_extraction.""" + +# This file is part of pycopancore. +# +# Copyright (C) 2016-2017 by COPAN team at Potsdam Institute for Climate +# Impact Research +# +# URL: +# Contact: core@pik-potsdam.de +# License: BSD 2-clause license + +from . import interface + +# export all implementation classes: +from .implementation import * + +# export model component mixin class: +from .model import Model diff --git a/pycopancore/model_components/maxploit_social_norms/implementation/__init__.py b/pycopancore/model_components/maxploit_social_norms/implementation/__init__.py new file mode 100644 index 00000000..3c7f3304 --- /dev/null +++ b/pycopancore/model_components/maxploit_social_norms/implementation/__init__.py @@ -0,0 +1,22 @@ +""" +Model component implementation subpackage exploit_social_learning. +""" + +# This file is part of pycopancore. +# +# Copyright (C) 2016-2017 by COPAN team at Potsdam Institute for Climate +# Impact Research +# +# URL: +# Contact: core@pik-potsdam.de +# License: BSD 2-clause license + +# TODO: adjust the following lists to your needs: + +# export all provided entity type implementation mixin classes: +from .individual import Individual +from .group import Group + +# export all provided process taxon implementation mixin classes: + +from .culture import Culture diff --git a/pycopancore/model_components/seven_dwarfs/implementation/__init__.py b/pycopancore/model_components/seven_dwarfs/implementation/__init__.py index e28f51f0..044ca48a 100644 --- a/pycopancore/model_components/seven_dwarfs/implementation/__init__.py +++ b/pycopancore/model_components/seven_dwarfs/implementation/__init__.py @@ -17,3 +17,4 @@ from .individual import Individual from .culture import Culture from .social_system import SocialSystem +from .group import Group diff --git a/pycopancore/model_components/seven_dwarfs/implementation/group.py b/pycopancore/model_components/seven_dwarfs/implementation/group.py new file mode 100644 index 00000000..2e9ab8fc --- /dev/null +++ b/pycopancore/model_components/seven_dwarfs/implementation/group.py @@ -0,0 +1,66 @@ +"""Group entity type class template. + +TODO: adjust or fill in code and documentation wherever marked by "TODO:", +then remove these instructions +""" + +# This file is part of pycopancore. +# +# Copyright (C) 2016-2017 by COPAN team at Potsdam Institute for Climate +# Impact Research +# +# URL: +# Contact: core@pik-potsdam.de +# License: BSD 2-clause license + +from .. import interface as I +# from .... import master_data_model as D + +# TODO: uncomment this if you need ref. variables such as B.Individual.cell: +#from ...base import interface as B + +# TODO: import those process types you need: +from .... import Explicit + +class Group (I.Group): + """Group entity type mixin implementation class.""" + + # standard methods: + # TODO: only uncomment when adding custom code! + +# def __init__(self, +# # *, # TODO: uncomment when adding named args behind here +# **kwargs): +# """Initialize an instance of Individual.""" +# super().__init__(**kwargs) # must be the first line +# # TODO: add custom code here: +# pass +# +# def deactivate(self): +# """Deactivate a Group.""" +# # TODO: add custom code here: +# pass +# super().deactivate() # must be the last line +# +# def reactivate(self): +# """Reactivate a Group.""" +# super().reactivate() # must be the first line +# # TODO: add custom code here: +# pass + + # process-related methods: + + def check_if_member(self, unused_t): + """Check if dwarf 1 is member of any group.""" + for w in self.worlds: + return w.individuals[0].group_memberships + + + + + + # TODO: add some if needed... + + processes = [ + Explicit("simple test", [I.Group.having_members], check_if_member) + ] # TODO: instantiate and list process objects here \ No newline at end of file diff --git a/pycopancore/model_components/seven_dwarfs/interface.py b/pycopancore/model_components/seven_dwarfs/interface.py index 62198fb4..4a417398 100644 --- a/pycopancore/model_components/seven_dwarfs/interface.py +++ b/pycopancore/model_components/seven_dwarfs/interface.py @@ -98,3 +98,10 @@ class SocialSystem(object): class Culture (object): """Interface for Culture mixin""" pass + +class Group(object): + """Interface for Group mixin""" + + having_members = ("test", """test""") + + pass diff --git a/pycopancore/model_components/seven_dwarfs/model.py b/pycopancore/model_components/seven_dwarfs/model.py index 73f9cddd..a68be807 100644 --- a/pycopancore/model_components/seven_dwarfs/model.py +++ b/pycopancore/model_components/seven_dwarfs/model.py @@ -15,7 +15,7 @@ from . import interface as I # import all needed entity type implementation classes: -from .implementation import World, Cell, Individual, Culture, SocialSystem +from .implementation import World, Cell, Individual, Culture, SocialSystem, Group # import all needed process taxon implementation classes: @@ -24,7 +24,7 @@ class Model(I.Model): # mixins provided by this model component: - entity_types = [World, Cell, Individual, SocialSystem] + entity_types = [World, Cell, Individual, SocialSystem, Group] """list of entity types augmented by this component""" process_taxa = [Culture] """list of process taxa augmented by this component""" diff --git a/pycopancore/models/groups_seven_dwarfs.py b/pycopancore/models/groups_seven_dwarfs.py index 31be4f35..f0d124d0 100644 --- a/pycopancore/models/groups_seven_dwarfs.py +++ b/pycopancore/models/groups_seven_dwarfs.py @@ -83,7 +83,7 @@ class Model(sd.Model, description = "Tutorial model" """Longer description""" - entity_types = [World, SocialSystem, Cell, Individual] + entity_types = [World, SocialSystem, Cell, Individual, Group] """List of entity types used in the model""" process_taxa = [Culture] """List of process taxa used in the model""" diff --git a/studies/run_groups_test_seven_dwarfs.py b/studies/run_groups_test_seven_dwarfs.py index e751ec4c..1e7e3b66 100644 --- a/studies/run_groups_test_seven_dwarfs.py +++ b/studies/run_groups_test_seven_dwarfs.py @@ -29,6 +29,10 @@ import pycopancore.models.groups_seven_dwarfs as M from pycopancore.runners.runner import Runner +from studies import plot_multilayer as pm +from mpl_toolkits.mplot3d import Axes3D +from mpl_toolkits.mplot3d.art3d import Line3DCollection + # setting timeinterval for run method 'Runner.run()' timeinterval = 10 @@ -78,7 +82,7 @@ #instantiate groups ng = 10 #number of groups -groups = [M.Group(culture=culture) for i in range (ng)] +groups = [M.Group(culture=culture, world=world) for i in range (ng)] print("\n The groups: \n") print(groups) @@ -137,11 +141,11 @@ print(list(top_nodes)) -nx.draw(GM, node_color=color_map, with_labels=True, +nx.draw(GM, node_color=color_map, with_labels=False, pos=nx.bipartite_layout(GM, bottom_nodes, align="horizontal", aspect_ratio=4/1)) # nx.draw(culture.group_membership_network) -plt.show() +# plt.show() @@ -154,7 +158,7 @@ termination_callables = [termination_signal] nx.draw(culture.acquaintance_network) -plt.show() +# plt.show() # Runner is instantiated r = Runner(model=model, @@ -171,4 +175,7 @@ t = np.array(traj['t']) print("max. time step", (t[1:]-t[:-1]).max()) +#save and print membership +membercheck = np.array(traj['check_if_member']) + From 76474cd15701b8f05abe5e524e5a0e09edd0d0e3 Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Fri, 5 Aug 2022 12:23:25 +0200 Subject: [PATCH 27/33] Add simple test function and plot groupmembership for a single dwarf over time --- .../seven_dwarfs/implementation/group.py | 10 +++++--- .../seven_dwarfs/interface.py | 5 ++-- pycopancore/models/groups_seven_dwarfs.py | 3 ++- studies/run_groups_test_seven_dwarfs.py | 24 ++++++++++--------- 4 files changed, 25 insertions(+), 17 deletions(-) diff --git a/pycopancore/model_components/seven_dwarfs/implementation/group.py b/pycopancore/model_components/seven_dwarfs/implementation/group.py index 2e9ab8fc..4da3f6b7 100644 --- a/pycopancore/model_components/seven_dwarfs/implementation/group.py +++ b/pycopancore/model_components/seven_dwarfs/implementation/group.py @@ -17,7 +17,7 @@ # from .... import master_data_model as D # TODO: uncomment this if you need ref. variables such as B.Individual.cell: -#from ...base import interface as B +from ...base import interface as B # TODO: import those process types you need: from .... import Explicit @@ -52,8 +52,12 @@ class Group (I.Group): def check_if_member(self, unused_t): """Check if dwarf 1 is member of any group.""" - for w in self.worlds: - return w.individuals[0].group_memberships + group_members = list(self.group_members) + i_0 = list(self.world.individuals)[0] + if i_0 in group_members: + self.having_members = True + else: + self.having_members = False diff --git a/pycopancore/model_components/seven_dwarfs/interface.py b/pycopancore/model_components/seven_dwarfs/interface.py index 4a417398..7176b1a8 100644 --- a/pycopancore/model_components/seven_dwarfs/interface.py +++ b/pycopancore/model_components/seven_dwarfs/interface.py @@ -90,7 +90,6 @@ class Individual (object): "eating speed of dwarf", default=1) - class SocialSystem(object): """Interface for SocialSystem mixin""" pass @@ -102,6 +101,8 @@ class Culture (object): class Group(object): """Interface for Group mixin""" - having_members = ("test", """test""") + having_members = Variable("test", + "test", + default=True) pass diff --git a/pycopancore/models/groups_seven_dwarfs.py b/pycopancore/models/groups_seven_dwarfs.py index f0d124d0..6c823150 100644 --- a/pycopancore/models/groups_seven_dwarfs.py +++ b/pycopancore/models/groups_seven_dwarfs.py @@ -59,7 +59,8 @@ class SocialSystem(sd.SocialSystem, pass -class Group(base.Group): +class Group(sd.Group, + base.Group): """Groups entity type""" # process taxa: diff --git a/studies/run_groups_test_seven_dwarfs.py b/studies/run_groups_test_seven_dwarfs.py index 1e7e3b66..fabf7154 100644 --- a/studies/run_groups_test_seven_dwarfs.py +++ b/studies/run_groups_test_seven_dwarfs.py @@ -29,9 +29,9 @@ import pycopancore.models.groups_seven_dwarfs as M from pycopancore.runners.runner import Runner -from studies import plot_multilayer as pm -from mpl_toolkits.mplot3d import Axes3D -from mpl_toolkits.mplot3d.art3d import Line3DCollection +# from studies import plot_multilayer as pm +# from mpl_toolkits.mplot3d import Axes3D +# from mpl_toolkits.mplot3d.art3d import Line3DCollection # setting timeinterval for run method 'Runner.run()' @@ -114,8 +114,8 @@ print("\n") #draw network of one group -# nx.draw(individuals[0].culture.group_membership_network) -# plt.show() +nx.draw(individuals[0].culture.group_membership_network) +plt.show() GM = culture.group_membership_network @@ -143,9 +143,10 @@ nx.draw(GM, node_color=color_map, with_labels=False, pos=nx.bipartite_layout(GM, bottom_nodes, align="horizontal", aspect_ratio=4/1)) +plt.show() -# nx.draw(culture.group_membership_network) -# plt.show() +nx.draw(culture.group_membership_network) +plt.show() @@ -158,7 +159,7 @@ termination_callables = [termination_signal] nx.draw(culture.acquaintance_network) -# plt.show() +plt.show() # Runner is instantiated r = Runner(model=model, @@ -175,7 +176,8 @@ t = np.array(traj['t']) print("max. time step", (t[1:]-t[:-1]).max()) -#save and print membership -membercheck = np.array(traj['check_if_member']) - +# save and print membership +membercheck = traj[M.Group.having_members] +plt.plot(t, membercheck[groups[0]]) # plot if dwarf 0 was member of group 0 +plt.show() From 447b6b8a81e3f2a2f03fc419e609cb30483630c0 Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Wed, 10 Aug 2022 13:36:43 +0200 Subject: [PATCH 28/33] Make illustration study more clear --- ...n_dwarfs.py => run_groups_illustration.py} | 42 +++++++------------ 1 file changed, 16 insertions(+), 26 deletions(-) rename studies/{run_groups_test_seven_dwarfs.py => run_groups_illustration.py} (88%) diff --git a/studies/run_groups_test_seven_dwarfs.py b/studies/run_groups_illustration.py similarity index 88% rename from studies/run_groups_test_seven_dwarfs.py rename to studies/run_groups_illustration.py index fabf7154..0fb64c54 100644 --- a/studies/run_groups_test_seven_dwarfs.py +++ b/studies/run_groups_illustration.py @@ -1,8 +1,4 @@ -"""This is the test script for the seven dwarfs step by step tutorial. - -In this version only the Step-process 'aging' of entitytype 'Individual' is -implemented, such that the only relevant attributes of 'Individual' are 'age' -and 'cell'. +"""This is an illustration script for some of the features that come with the group entity based on the seven dwarfs model. """ # This file is part of pycopancore. @@ -73,7 +69,7 @@ ) for i in range(dwarfs) ] -print("\n The individuals: \n") +print("\n The individuals:") print(individuals) print("\n \n") @@ -84,7 +80,7 @@ ng = 10 #number of groups groups = [M.Group(culture=culture, world=world) for i in range (ng)] -print("\n The groups: \n") +print("\n The groups:") print(groups) print("\n \n") @@ -99,29 +95,34 @@ #plt.show() -# initialize some network: +# initialize the network: for i in individuals: for g in groups: culture.group_membership_network.add_edge(i, g) -print("Individual 1 Group Memberships:") +print("\n Individual 1 Group Memberships:") print(list(individuals[0].group_memberships)) print("\n") -print("Group Members:") -print(groups[0]) +print("\n Group Members:") print(list(groups[0].group_members)) print("\n") #draw network of one group -nx.draw(individuals[0].culture.group_membership_network) -plt.show() +# nx.draw(individuals[0].culture.group_membership_network) +# plt.show() GM = culture.group_membership_network +print("\n The data structure in gm network:") print(GM.nodes.data()) +print("\n") +# How networkx would classicaly plot the network +nx.draw(culture.group_membership_network) +plt.show() +# How to plot more intuitively using the type and color information color_map = [] shape_map = [] for node in list(GM.nodes): @@ -133,23 +134,12 @@ shape_map.append("o") else: shape_map.append("^") - - - top_nodes = {n for n, d in GM.nodes(data=True) if d["type"] == "Group"} bottom_nodes = set(GM) - top_nodes - -print(list(top_nodes)) - nx.draw(GM, node_color=color_map, with_labels=False, pos=nx.bipartite_layout(GM, bottom_nodes, align="horizontal", aspect_ratio=4/1)) plt.show() -nx.draw(culture.group_membership_network) -plt.show() - - - # Define termination signals as list [ signal_method, object_method_works_on ] # the termination method 'check_for_extinction' must return a boolean termination_signal = [M.Culture.check_for_extinction, @@ -158,8 +148,8 @@ # Define termination_callables as list of all signals termination_callables = [termination_signal] -nx.draw(culture.acquaintance_network) -plt.show() +# nx.draw(culture.acquaintance_network) +# plt.show() # Runner is instantiated r = Runner(model=model, From 34904c641c9da2fe1638f8455deb13182cfbb6d4 Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Thu, 11 Aug 2022 11:13:08 +0200 Subject: [PATCH 29/33] Make illustration study more clear --- studies/run_groups_illustration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studies/run_groups_illustration.py b/studies/run_groups_illustration.py index 0fb64c54..c17fb1d6 100644 --- a/studies/run_groups_illustration.py +++ b/studies/run_groups_illustration.py @@ -76,7 +76,7 @@ # assigning individuals to cell is not necessary since it is done by # initializing the individuals in 'base.Individuals' with the 'cell' method -#instantiate groups +# instantiate groups ng = 10 #number of groups groups = [M.Group(culture=culture, world=world) for i in range (ng)] From bf446bf198685f1750433fc76a4990e64dfbc994 Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Wed, 22 Nov 2023 12:43:15 +0100 Subject: [PATCH 30/33] resolve #175 --- .../abstract_level/entity_types/group.rst | 10 +-- .../base/implementation/group.py | 89 ------------------- .../model_components/base/interface.py | 23 ----- 3 files changed, 4 insertions(+), 118 deletions(-) diff --git a/docs/framework_documentation/abstract_level/entity_types/group.rst b/docs/framework_documentation/abstract_level/entity_types/group.rst index 173ad33e..44106df7 100644 --- a/docs/framework_documentation/abstract_level/entity_types/group.rst +++ b/docs/framework_documentation/abstract_level/entity_types/group.rst @@ -39,18 +39,16 @@ A group will usually... In addition, a group may... - have an "intra" group network between members :doc:`individuals` - (this feature is not completely implemented yet) - have one or several "leader" :doc:`individuals`, - of which one may be the dominant leader (this feature is not implemented yet) + of which one may be the dominant leader -- have a "headquarters" :doc:`cell` (this feature is not implemented yet) +- have a "headquarters" :doc:`cell` - be related to other groups via an "inter" network owned by the culture taxon (which will typically interact with the network of personal acquaintance between member individuals) - - (this feature is not completely implemented yet) -- act as the current "elite" in some :doc:`social system` (this feature is not implemented yet) +- act as the current "elite" in some :doc:`social system` All these relationships may be dynamic. @@ -58,5 +56,5 @@ Finally, a group may... - be a permanent subgroup of a larger group or :doc:`social system` *by definition* (rather than by coincidence, e.g., "scientists" are by definition a subgroup of the group "academics", - and "German workers" may be by definition a subgroup of the social system "Germany") - (this feature is not implemented yet) + and "German workers" may be by definition a subgroup of the social system "Germany") diff --git a/pycopancore/model_components/base/implementation/group.py b/pycopancore/model_components/base/implementation/group.py index 6e39c053..e28a71e0 100644 --- a/pycopancore/model_components/base/implementation/group.py +++ b/pycopancore/model_components/base/implementation/group.py @@ -31,7 +31,6 @@ def __init__(self, *, culture, world, - next_higher_group=None, **kwargs ): """Initialize an instance of Group. @@ -42,8 +41,6 @@ def __init__(self, Culture the Group belongs to world: obj World the Group belongs to (to bypass AttributeErrors for now) - next_higher_group: obj - Optional Group the Group belongs to (default is None) **kwargs keyword arguments passed to super() @@ -56,15 +53,6 @@ def __init__(self, self._world = None self.world = world - # self._social_system = None - # self.social_system = social_system - # self._next_higher_group = None - # self.next_higher_group = next_higher_group - - # init caches: - # self._next_lower_group = set() - # self._direct_cells = set() - if self.culture: self.culture.group_membership_network.add_node(self, type="Group", color="green") @@ -126,42 +114,6 @@ def world(self, w): w._groups.add(self) self._world = w - # @property - # def social_system(self): - # """Get the SocialSystem the Group is part of.""" - # return self._social_system - - # @social_system.setter - # def social_system(self, s): - # """Set the SocialSystem the Group is part of.""" - # if self._social_system is not None: - # self._social_system._groups.remove(self) - # assert isinstance(s, I.SocialSystem), "socialsystem must be of entity type SocialSystem" - # s._groups.add(self) - # self._social_system = s - - # @property - # def next_higher_group(self): - # """Get next higher group.""" - # return self._next_higher_group - - # @next_higher_group.setter - # def next_higher_group(self, s): - # """Set next higher group.""" - # if self._next_higher_group is not None: - # self._next_higher_group._next_lower_groups.remove(self) - # reset dependent cache: - # self._next_higher_group.cells = unknown - # if s is not None: - # assert isinstance(s, I.Group), \ - # "next_higher_group must be of entity type group" - # s._next_lower_groups.add(self) - # reset dependent cache: - # s.cells = unknown - # self._next_higher_group = s - # reset dependent caches: - # self.higher_groups = unknown - # getters for backwards references and convenience variables: @property # read-only @@ -174,44 +126,6 @@ def metabolism(self): """Get the Metabolism of which the Group is a part.""" return self._world.metabolism - # _higher_groups = unknown - """cache, depends on self.next_higher_group - and self.next_higher_group.higher_groups""" - # @property # read-only - # def higher_groups(self): - # """Get higher groups.""" - # if self._higher_groups is unknown: - # find recursively: - # self._higher_groups = [] if self.next_higher_group is None \ - # else ([self.next_higher_group] - # + self.next_higher_group.groups) - # return self._higher_groups - - # @higher_groups.setter - # def higher_groups(self, u): - # """Set higher groups.""" - # assert u == unknown, "setter can only be used to reset cache" - # self._higher_groups = unknown - # reset dependent caches: - # for s in self._next_lower_groups: - # s.higher_groups = unknown - # for c in self._direct_cells: - # c.groups = unknown - - # @property # read-only - # def next_lower_groups(self): - # """Get next lower groups.""" - # return self._next_lower_groups - - # @property # read-only - # def lower_groups(self): - # """Get lower groups.""" - # aggregate recursively: - # l = self._next_lower_groups - # for s in self._next_lower_groups: - # l.update(s.lower_groups) - # return l - @property def group_members(self): """Get the set of Individuals associated with this Group.""" @@ -219,9 +133,6 @@ def group_members(self): return self.culture.group_membership_network.predecessors(self) # .predecessors as network is directed from inds to groups - - - # no process-related methods processes = [] # no processes in base diff --git a/pycopancore/model_components/base/interface.py b/pycopancore/model_components/base/interface.py index 6ac69ebb..e2fb0de1 100644 --- a/pycopancore/model_components/base/interface.py +++ b/pycopancore/model_components/base/interface.py @@ -321,19 +321,6 @@ class Group (object, metaclass=_MixinType): readonly=True) culture = ReferenceVariable("culture", "", type=Culture, readonly=True) - # higher_groups = SetVariable( - # "higher groups", - # "upward list of (in)direct super-Groups", - # readonly=True) - # next_lower_groups = SetVariable( - # "next lower groups", - # "set of sub-Groups of next lower level", - # readonly=True) - # lower_groups = SetVariable( - # "lower groups", - # "set of all direct and indirect sub-Groups", - # readonly=True) - group_members = SetVariable( "group members", "set of all individuals associated with the group", @@ -341,13 +328,3 @@ class Group (object, metaclass=_MixinType): ) Culture.groups.type = Group - -# specified only now to avoid recursion errors: -# Group.next_higher_group.type = Group -# Group.higher_groups.type = Group -# Group.next_lower_groups.type = Group -# Group.lower_groups.type = Group -# Individual.group_memberships.type = Group # TODO: assert this is at the right place -# Group.group_members.type = Individual -#Group.groups.type = Group -#SocialSystem.top_level_groups.type = Group From 519cac8408f8de7ca38b93d000323ccbff78c597 Mon Sep 17 00:00:00 2001 From: MaxBechthold Date: Tue, 18 Jun 2024 10:38:31 +0200 Subject: [PATCH 31/33] resolve #175 fully resolved comments (documentation only sporadically, since still coherent with rest of docs) --- pycopancore/data_model/master_data_model/group.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pycopancore/data_model/master_data_model/group.py b/pycopancore/data_model/master_data_model/group.py index cd7354cb..a114e4d9 100644 --- a/pycopancore/data_model/master_data_model/group.py +++ b/pycopancore/data_model/master_data_model/group.py @@ -1,7 +1,5 @@ """Master data model for group.""" -"""https://github.com/pik-copan/pycopancore/blob/master/docs/framework_documentation/abstract_level/entity_types/group.rst""" - from .. import Variable from networkx import Graph From 098512cc4cabf49df22c15c777b045eacf2945ff Mon Sep 17 00:00:00 2001 From: Max Bechthold <64842372+zugnachpankow@users.noreply.github.com> Date: Thu, 4 Jul 2024 11:41:30 +0200 Subject: [PATCH 32/33] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 859db329..13679089 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ # This creates a link insteaed of copying the files, so modifications in this directory are modifications in the installed package. setup(name="pycopancore", - version="0.6.1", + version="0.7.0", description="Reference implementation of the copan:CORE World-Earth modelling framework", url="https://github.com/pik-copan/pycopancore", author="COPAN team @ PIK", From 7c31ec4eb48239873c8729c57af13788d072bd89 Mon Sep 17 00:00:00 2001 From: Max Bechthold <64842372+zugnachpankow@users.noreply.github.com> Date: Thu, 4 Jul 2024 11:42:45 +0200 Subject: [PATCH 33/33] Update CITATION.cff --- CITATION.cff | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 722a524a..2ea7e7af 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -2,8 +2,8 @@ cff-version: 1.2.0 message: If you use this software, please cite it using the metadata from this file. type: software title: 'pycopancore: Reference implementation of the copan:CORE World-Earth modelling framework' -version: 0.6.0 -date-released: '2020-04-28' +version: 0.7.0 +date-released: '2024-07-04' abstract: The pycopancore package is a python implementation of the copan:CORE modeling framework as described in this paper. The framework is designed to allow an easy implementation of World-Earth (i.e., global social-ecological) @@ -14,6 +14,10 @@ abstract: The pycopancore package is a python implementation of the copan:CORE stochastic and deterministic events and therefore allows to compare different model and component types and implementations. authors: +- family-names: Bechthold + given-names: Max + email: maxbecht@pik-potsdam.de + orcid: 0009-0007-7113-4814 - family-names: Heitzig given-names: Jobst email: jobst.heitzig@pik-potsdam.de