diff --git a/afaire.md b/afaire.md index 1b88ec5a62c52570ad0c7b41c2b4380e02283efe..c3df5cea59e1f5c43bc27e4beff54382d9e81fc6 100644 --- a/afaire.md +++ b/afaire.md @@ -6,3 +6,39 @@ - [ ] New table politic, c'est la variable qui est rentrée en paramètre. - [x] Assertions que le system est bien run - [ ] Deux change politic marche pas +- [ ] Ajouter shéma de documentation +- [ ] Fonctions graphiques et tout de World3 à mettre dans System +- [ ] add_function ça fonctionne ça ? +- [ ] Les new politic dans system directement. +- [ ] Demander: trop de fonctions dans System ? +- [ ] Demander: les dictionnaires de forme fixée, mieux avec plusieurs noms ? +- [ ] Demander: doublons d'informations pour rechercher plus vite, oui ou non ? +- [ ] Clarifier dans l'explication de simulation de System les appels et ordre +- [ ] Demander: ça vaut le coup de faire une documentaiton shématique etc. ? + +## La documentation +### From equations to system. +Pydynamo equations are parsed to create a System object, through the function `new_system`. + +1. We start with a list of string pydynamo equations. +2. The `parse_system.get_nodes_eqs_dicts` retrieve from this list all informations that a System needs to be created. +3. It uses functions of `parse_equations`, which analyse an equation to retrieve all relevant information about one equation: + - If it's a constant, update or initialisation equation + - What parameters are needed in the equation (constants, variables and their indices, special functions) +4. It also uses functions from `parse_dynamo_functions` to handle the case of special DYNAMO functions (clip, tabhl, etc.) +5. With all this informations, contained in the *main dictionnnaries*, a System object is created. + +### System object +A system object only contains the informations retrieved by the above . Any user can modify the object: +- Add new equations or change it +- Change constant values +- Add new politic (which changes some equations) +Then, each time a System object is run: +1. The *main dictionnnaries* are updated, because maybe some dependencies has been changed. +2. All variables are initialized as empty arrays. +3. All constants are set at their values. +4. All special functions are set. +5. Thanks to the graph of dependencies, an updating order is defined. +6. All variables are initialized with the initialisation functions. +7. For each step, all variables are updated, and their new value set in the arrays. +8. The results can be plotted with new functions. diff --git a/pydynamo/core/old.py b/pydynamo/core/old.py new file mode 100644 index 0000000000000000000000000000000000000000..285622ac2d4b6f854a9051fe39d777354d553b64 --- /dev/null +++ b/pydynamo/core/old.py @@ -0,0 +1,29 @@ +"""Depreciated functions. +""" +# Modifiers +def add_comments(self, comments): + for node, comment in comments.items(): + self.comments[node] = comment + +def add_function(self, fun, name=None): + if not name: + name = fun.__name__ + for s in ('_', 'update', 'init', 'set'): + assert not name.startswith(s), "Fun name shouldn't start with '{s}'" + setattr(self, fun.__name__, fun) + +def add_functions(self, *args, **kwargs): + for fun in args: + self.add_function(fun) + for name, fun in kwargs.items(): + self.add_function(fun, name) + +def add_system_function(self, fun, name=None): + if not name: + name = fun.__name__ + if any(name.startswith(s) for s in ('update', 'init', 'set')): + setattr(self, name, fun) + fun.__okdic__ = False + fun.__doc__ = f"User defined function\n{fun.__doc__}" + return + assert False, "Invalid function name. Should starts with 'init', 'update' or 'set'" diff --git a/pydynamo/core/system.py b/pydynamo/core/system.py index d26bc2b53e780ef85b739dcd50d8c9ee0e0718d2..7ddc09ba453c28e00bcb419e5415c2d45e2bd3ab 100644 --- a/pydynamo/core/system.py +++ b/pydynamo/core/system.py @@ -18,9 +18,10 @@ class System: """ Base class for system dynamics. - A System stores two dictionnaries containing all + A System stores 3 dictionnaries containing all nodes (constants, variables, and functions) and equations (constan values, updating equations and initialisation equations) + and comments about nodes. From this dictionnaries, it generates the updating pattern and run the simulation. @@ -63,7 +64,7 @@ class System: >>> >>> s = System(equations_function) """ - + self.comments = {} # If code is given, create a System with equations if code: if isinstance(code, list): @@ -88,7 +89,6 @@ class System: 'update': dict(), 'init': dict() } - self.comments = {} self.code_lines = [] def add_equations(self, new_code_lines): @@ -105,13 +105,27 @@ class System: def reset_eqs(self, prepare=True): """Set all nodes, equations and comments. """ - self.nodes, self.eqs, self.comments = get_system_dicts(self.code_lines) + self.nodes, self.eqs, new_comments = get_system_dicts(self.code_lines) + + # Keep old comments + for node, comment in new_comments.items(): + if comment != '': + self.comments[node] = comment + if prepare: self.prepare() def add_comments(self, comments): + """Add comments to the System. + + Parameters + ---------- + comments : dict(str: str) + Each node name and its comment. + """ + for node, comment in comments.items(): self.comments[node] = comment @@ -140,7 +154,7 @@ class System: np.array(float): Array of values of the variable for the last run. """ - + assert name in self.nodes['var'], f"{name} is not a variable" return getattr(self, name) @@ -228,11 +242,11 @@ class System: Array of system time. """ return self.time - + def get_tabhl_args(self, name): """ Get indications about a tabhl function. - + Parameters ---------- name: str @@ -243,7 +257,7 @@ class System: np.array, np.array, str, str, str: x, f(x), x label, y label, title """ - + aa = list(self.eqs['update'][name]['args']['var']) argname = aa[0][0].split('.')[0] assert 'tabhl_' + name in dir(self), 'Error, no such tabhl function' @@ -563,7 +577,7 @@ class System: if '__iter__' in dir(value): value = np.array(value) return value - + except Exception as e: raise(Exception(f"Error setting {cst}\n" f" wih function {fun.__custom_repr__}\n" @@ -586,7 +600,7 @@ class System: Values of each arguments. """ setattr(self, cst, self.get_cst_val(cst, args)) - + def set_all_csts(self): """Set every constant constant according to its equation and arguments ONLY IF the constant is not set yet. """ @@ -604,7 +618,7 @@ class System: if not cst in dir(self): self.set_cst(cst, args) - + def generate_var(self, var, N): """Initialise an empty array for a variable. @@ -651,7 +665,7 @@ class System: table = getattr(self, p['table']) fun = Interpol(x_low, x_high, x_incr, table) setattr(self, p['fun'], fun) - + if f_type == 'sample': isam = eval(p['isam']) fun = Sample(isam, self.time) @@ -659,7 +673,7 @@ class System: if f_type == 'step' or type =='clip': pass - + def step(self, hght, sttm, k): """Step function. See specials.step. """ @@ -703,11 +717,11 @@ class System: fun_args[p['type']] = getattr(self, p['fun']) fun_args['k'] = 0 cst_args = {c: getattr(self, c) for c in args['cst']} - + # Assert that there is no variables considered as constants etc. tps = {'cst', 'var', 'fun'} dd = locals() - + for t, n, tt in ((t, n, tt) for t in tps for tt in tps.difference({t}) for n in dd[tt + '_args']): @@ -739,7 +753,7 @@ class System: u = {'var': var} try: args = self.eqs['update'][var]['args'] - + u['cst_args'] = {c: getattr(self, c) for c in args['cst']} u['var_args'] = {v + '_' + ni: (getattr(self, v), 0 if ni == 'k' else -1) for v, ni in args['var']} @@ -748,10 +762,10 @@ class System: p = self.eqs['update'][var]['args']['fun'][fun_type] u['fun_args'][p['type']] = getattr(self, p['fun']) u['fun_args']['k'] = None - + u['update_fun'] = getattr(self, 'update_' + var) self._update_loop.append(u) - + except AttributeError as e: line = self.eqs['update'][var]['line'] raise(AttributeError(f"In updating {var}:\n" @@ -890,7 +904,7 @@ class System: msg += f"{j}.i = {line} \n" msg +="Please design an initialisation scheme that is not cyclic." raise AssertionError(msg) - + def run(self, N=None, dt=1): """ After preparing and before running, @@ -906,10 +920,10 @@ class System: >>> system.update_v1 = new_update # New fct for updating v1 >>> s.run(10, 1) # Run with new parameters """ - + self.change_functions_in_dict() self.assert_update_acyclic() - self.assert_init_acyclic() + self.assert_init_acyclic() try: if not N: @@ -918,7 +932,7 @@ class System: self.final_time = self.initial_time + N * dt except: raise Exception("Either pass N or define both final_time and initial_time") - + self.time = self.initial_time + np.arange(N)*dt self.dt = dt self.generate_all_vars(N) diff --git a/pydynamo/world3/world3_class.py b/pydynamo/world3/world3_class.py index a5df591dde096d59d8ceb2d1c6eb9144581a141c..488cea03d6b09d60082c04a76d6d890a8e20c852 100644 --- a/pydynamo/world3/world3_class.py +++ b/pydynamo/world3/world3_class.py @@ -14,11 +14,11 @@ class World3(System): def __init__(self, scenario_number=2, sys=None): """Initialise a World3 object. By default, the scenario number is the second one, because it's the most "realistic" when we compare to the current situation (in 2022). """ - self.code_lines = w3_code + ccode = w3_code.copy() changes = scenarios[scenario_number - 1]['changes'] for cst, eq in changes.items(): - self.code_lines.append(f'{cst} = {eq}') - self.reset_eqs() + ccode.append(f'{cst} = {eq}') + super().__init__(ccode, True) self.add_comments(w3_defs) def copy(self): diff --git a/test.py b/test.py new file mode 100644 index 0000000000000000000000000000000000000000..c7e8b97a4324b8d19bcca70ed051fddeba047ecf --- /dev/null +++ b/test.py @@ -0,0 +1,15 @@ +from pydynamo import * +import matplotlib.pyplot as plt + +w = World3() +w.run() +w4 = World3(4) +w.plot_world() +w4.run() +plt.show() +plt.show() +w.new_politic('amti', 2000, w.amti/2) +w.run() +w.plot('amti') +plt.show() +w.show_influence_graph().show('haha.html')