From 3c3a3de3e524e1969a7199193c102f74b3e482fe Mon Sep 17 00:00:00 2001 From: BAUCHER Achille <achille.baucher@inria.fr> Date: Tue, 15 Mar 2022 10:34:16 +0100 Subject: [PATCH] Completed afaire --- afaire.md | 35 +++++++++++++++- pydynamo/core/system.py | 92 ++++++++++++++++++++--------------------- 2 files changed, 80 insertions(+), 47 deletions(-) diff --git a/afaire.md b/afaire.md index 965750a8..fb1b6571 100644 --- a/afaire.md +++ b/afaire.md @@ -6,4 +6,37 @@ - [ ] New table politic, c'est la variable qui est rentréer en paramètre. - [ ] Ajouter shéma de documentation - [ ] Fonctions graphiques et tout de World3 à mettre dans System -- [ ] add_function ça fonctionne ça ? \ No newline at end of file +- [ ] 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/system.py b/pydynamo/core/system.py index 5f5ac752..0137735c 100644 --- a/pydynamo/core/system.py +++ b/pydynamo/core/system.py @@ -40,13 +40,13 @@ class System: 'update': dict(), 'init': dict() } - + self.comments = {} # ---------- Modifiers ---------- - + def add_node(self, node, node_type): - """Add a node to the System. + """Add a node to the System. Parameters ---------- @@ -73,7 +73,7 @@ class System: self.add_node(node, node_type) def add_eq(self, node, eq_type, arguments): - """Add an equation to the System. + """Add an equation to the System. Parameters ---------- @@ -87,12 +87,12 @@ class System: 'var': {( variable name, index name)}, 'fun': {dict_of_function_infomations}} """ - + # Check if some special functions (clip, step) are in arguments: for fun in set(arguments['args']['fun']): if fun not in instance_fun_names and fun in globals(): del arguments['args']['fun'][fun] - + self.eqs[eq_type][node] = arguments @@ -113,13 +113,13 @@ class System: 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 @@ -130,13 +130,14 @@ class System: # Following functions are not used yet. # def add_function(self, fun, name=None): - # """Add or reset a function to the system. + # """Add or reset a function to the system. # The function should be any of init, update or set, and take the same arguments as defined in the system equations. - + # Parameters # ---------- # fun : function - # Function taking appropriate arguments and return a value. If name is None, the function name should be one of `init_var`, `upadte_var`, `set_cst` with `var` a variable name and `cst` a constant name. + # Function taking arguments with syntax: (cst, var1_k, var2_j, fun) with cst names of constants, var1 or var2 names of variables, and fun names of functions, and return a value. + # If name is None, the function name should be one of `init_var`, `upadte_var`, `set_cst` with `var` a variable name and `cst` a constant name. # name : str # Name of the function in case we want it different than fun.__name__. # """ @@ -145,7 +146,7 @@ class System: # 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) @@ -167,9 +168,9 @@ class System: # self.add_system_function(fun) # for fun, name in kwargs: # sefl.add_system_function(fun, name) - + # ---------- Getters ---------- - + def iter_all_nodes(self): return chain(*self.nodes.values()) @@ -178,7 +179,7 @@ class System: return set(self.iter_all_nodes()) return self.nodes[node_type] - + def get_all_variable_names(self): """ Returns @@ -187,7 +188,7 @@ class System: List of name of all variables """ return self.nodes['var'] - + def get_var(self, name): """ @@ -201,11 +202,11 @@ 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) - - + + def get_time(self): """ @@ -215,11 +216,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 @@ -230,7 +231,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' @@ -462,7 +463,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" @@ -475,7 +476,7 @@ class System: def set_cst(self, cst, args): setattr(self, cst, self.get_cst_val(cst, args)) - + def set_all_csts(self): G = self.get_cst_graph() for cst in nx.topological_sort(G): @@ -491,7 +492,7 @@ class System: if not cst in dir(self): self.set_cst(cst, args) - + def generate_var(self, var, N): setattr(self, var, np.full(N, np.NAN)) @@ -516,7 +517,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) @@ -524,10 +525,10 @@ class System: if f_type == 'step': pass - + def step(self, hght, sttm, k): return step(hght, sttm, self.time[k]) - + def set_all_special_functions(self): """ Generate every special dynamo functions that are stored. @@ -559,11 +560,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']): @@ -580,7 +581,7 @@ class System: f'{var} = ', line)) raise(AssertionError(msg)) - + self.update(var, 0, update_fun, {**var_args, **cst_args, **fun_args}) def set_update_loop(self): @@ -595,7 +596,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']} @@ -604,10 +605,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" @@ -719,7 +720,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, @@ -735,10 +736,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: @@ -747,7 +748,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) @@ -777,17 +778,17 @@ class System: ---------- node: str Name of the node - + with_definitions: bool If yes, returns a dictionnary with each node definition. """ - + out_nodes = [b for (a, b) in self.get_influence_graph().out_edges(node)] if with_definitions: return {a: self.get_comment(a) for a in out_nodes} else: return out_nodes - + def get_in_nodes(self, node, with_definitions=False): """Returns the list of the nodes that this node needs to be computed. @@ -795,17 +796,17 @@ class System: ---------- node: str Name of the node - + with_definitions: bool If yes, returns a dictionnary with each node definition. """ - + in_nodes = [a for (a, b) in self.get_influence_graph().in_edges(node)] if with_definitions: return {a: self.get_comment(a) for a in in_nodes} else: return in_nodes - + def get_at(self, var, t): """Returns the value of var at time t, or an interpolation if between rwo timestep values.""" assert var in self.nodes['var'], f"{var} is not a variable" @@ -813,7 +814,7 @@ class System: if t == self.final_time: return getattr(self, var)[-1] - + idx1 = np.arange(len(self.time))[self.time <= t][-1] dd = (self.time[idx1] - t)/self.dt v = getattr(self, var) @@ -833,4 +834,3 @@ class System: return False except: return any(a!=b) return {cst: (v1, v2) for cst, (v1, v2) in both.items() if diff(v1, v2)} - -- GitLab