def set_primitive_set(self): """ Set up deap set of primitives, needed for genetic programming """ self.pset = gp.PrimitiveSet(self.get_unique_str(), 1) self.pset.renameArguments(ARG0=str(self.var)) # add these symbols into primitive set for S in self.series_to_sum.free_symbols - {self.var}: self.pset.addTerminal(S, name=str(S)) # Add basic operations into the self.use_func( (operator.mul, 2), (operator.div, 2), (operator.sub, 2), (operator.add, 2) ) # find unique number from series unique_num = set() for s_term in self.partitioned_series: unique_num.update(S for S in sympy.postorder_traversal(s_term) if S.is_Number) # convert numbers into fractions and extract nominator and denominator separately unique_num = itertools.chain(*(sympy.fraction(S) for S in unique_num)) self.unique_num = sorted(set(unique_num)) return self
def parse(text, splittable_symbols=set(), local_dict=genereate_local_dict()): from sympy.core.assumptions import ManagedProperties from sympy.core.function import FunctionClass, UndefinedFunction from sympy import Tuple from types import FunctionType, MethodType from sympy.logic.boolalg import BooleanTrue, BooleanFalse text = ' '.join(operator_splitter.split(text)) expr = parse_expr(text, local_dict=local_dict, global_dict={}, transformations=get_transformations(splittable_symbols), evaluate=False) if any( map( lambda x: type(x) in (BooleanTrue, BooleanFalse, ManagedProperties, FunctionClass, FunctionType, MethodType, Tuple, tuple, float, int, str, bool) or type(type(x)) in (UndefinedFunction, ), postorder_traversal(expr))): raise NameError() for symbol in all_symbols(expr): if '_' in symbol and '' not in symbol.split('_'): continue if len(symbol) > 2: raise NameError() if len(symbol) == 2 and not symbol[-1].isdigit(): raise NameError() return expr
def __new__(cls, *args, **kwargs): evaluate = kwargs.get('evaluate', True) if not args: return VectorZero() args = list(map(sympify, args)) # If for any reasons we create VecAdd(0), I want 0 to be returned. # Skipping this check, VectorZero() will be returned instead if len(args) == 1 and args[0] == S.Zero: return S.Zero if evaluate: # remove instances of S.Zero and VectorZero args = [ a for a in args if not (isinstance(a, VectorZero) or (a == S.Zero) or (a == Vector.zero)) ] if len(args) == 0: return VectorZero() elif len(args) == 1: # doesn't make any sense to have 1 argument in VecAdd if # evaluate=True return args[0] obj = AssocOp.__new__(cls, *args, evaluate=evaluate) obj = _sanitize_args(obj) # print("VecAdd OBJ", obj.func, obj) if not isinstance(obj, cls): return obj # are there any scalars or vectors? any_vectors = any([a.is_Vector for a in obj.args]) all_vectors = all([a.is_Vector for a in obj.args]) # addition of mixed scalars and vectors is not supported. # If there are scalars in the addition, either all arguments # are scalars (hence not a vector expression) or there are # mixed arguments, hence throw an error obj.is_Vector = all_vectors if (any_vectors and (not all_vectors)): asd = obj.args[1] print("####", asd.is_Vector, asd.args) print("####", [a.is_Vector for a in asd.args]) for a in postorder_traversal(asd): print(a.func, a.is_Vector, a) raise TypeError("VecAdd: Mix of Vector and Scalar symbols:\n\t" + "\n\t".join( str(a.func) + ", " + str(a.is_Vector) + ", " + str(a) for a in obj.args)) return obj
def _compute_transform(self, F, s, x, **hints): from sympy import postorder_traversal global _allowed if _allowed is None: from sympy import (exp, gamma, sin, cos, tan, cot, cosh, sinh, tanh, coth, factorial, rf) _allowed = set([exp, gamma, sin, cos, tan, cot, cosh, sinh, tanh, coth, factorial, rf]) for f in postorder_traversal(F): if f.is_Function and f.has(s) and f.func not in _allowed: raise IntegralTransformError('Inverse Mellin', F, 'Component %s not recognised.' % f) strip = self.fundamental_strip return _inverse_mellin_transform(F, s, x, strip, **hints)
def _compute_transform(self, F, s, x, **hints): from sympy import postorder_traversal global _allowed if _allowed is None: from sympy import (exp, gamma, sin, cos, tan, cot, cosh, sinh, tanh, coth, factorial, rf) _allowed = set([ exp, gamma, sin, cos, tan, cot, cosh, sinh, tanh, coth, factorial, rf ]) for f in postorder_traversal(F): if f.is_Function and f.has(s) and f.func not in _allowed: raise IntegralTransformError( 'Inverse Mellin', F, 'Component %s not recognised.' % f) strip = self.fundamental_strip return _inverse_mellin_transform(F, s, x, strip, **hints)
def expr_to_postfix(expr, mul_add_arity_fixed=False): """ Returns postorder traversal (i.e. in polish notation) of the symbolic expression """ post = [] post_arity = [] for expr_node in snp.postorder_traversal(expr): if expr_node.func.is_symbol: post.append(expr_node.name) post_arity.append(len(expr_node.args)) elif expr_node.is_Function or expr_node.is_Add or expr_node.is_Mul or expr_node.is_Pow: if mul_add_arity_fixed and (expr_node.is_Add or expr_node.is_Mul): for i in range(len(expr_node.args) - 1): post.append(type(expr_node).__name__) post_arity.append(2) else: post.append(type(expr_node).__name__) post_arity.append(len(expr_node.args)) elif expr_node.is_constant(): post.append(float(expr_node)) post_arity.append(len(expr_node.args)) return post, post_arity
def symbolic_norm(C): """ Get "norm" of sympy expression to characterize complexity of complexity of C """ return sum(not(S.is_Atom) for S in sympy.postorder_traversal(C))
def all_symbols(expr): for sub_expr in postorder_traversal(expr): if hasattr(sub_expr, 'is_symbol') and sub_expr.is_symbol: yield str(sub_expr)
def general_symbolic(target, eqn=None, arg_map=None): r''' A general function to interpret a sympy equation and evaluate the linear components of the source term. Parameters ---------- target : OpenPNM object The OpenPNM object where the result will be applied. eqn : sympy symbolic expression for the source terms e.g. y = a*x**b + c arg_map : Dict mapping the symbols in the expression to OpenPNM data on the target. Must contain 'x' which is the independent variable. e.g. arg_map={'a':'pore.a', 'b':'pore.b', 'c':'pore.c', 'x':'pore.x'} Example ---------- >>> import openpnm as op >>> from openpnm.models.physics import generic_source_term as gst >>> import scipy as sp >>> import sympy >>> pn = op.network.Cubic(shape=[5, 5, 5], spacing=0.0001) >>> water = op.phases.Water(network=pn) >>> water['pore.a'] = 1 >>> water['pore.b'] = 2 >>> water['pore.c'] = 3 >>> water['pore.x'] = sp.random.random(water.Np) >>> a, b, c, x = sympy.symbols('a,b,c,x') >>> y = a*x**b + c >>> arg_map = {'a':'pore.a', 'b':'pore.b', 'c':'pore.c', 'x':'pore.x'} >>> water.add_model(propname='pore.general', ... model=gst.general_symbolic, ... eqn=y, arg_map=arg_map, ... regen_mode='normal') >>> assert 'pore.general.rate' in water.props() >>> assert 'pore.general.S1' in water.props() >>> assert 'pore.general.S1' in water.props() ''' # First make sure all the symbols have been allocated dict items for arg in postorder_traversal(eqn): if srepr(arg)[:6] == 'Symbol': key = srepr(arg)[7:].strip('(').strip(')').strip("'") if key not in arg_map.keys(): raise Exception('argument mapping incomplete, missing '+key) if 'x' not in arg_map.keys(): raise Exception('argument mapping must contain "x" for the ' + 'independent variable') # Get the data data = {} args = {} for key in arg_map.keys(): data[key] = target[arg_map[key]] # Callable functions args[key] = symbols(key) r, s1, s2 = _build_func(eqn, **args) r_val = r(*data.values()) s1_val = s1(*data.values()) s2_val = s2(*data.values()) values = {'S1': s1_val, 'S2': s2_val, 'rate': r_val} return values
def general_symbolic(target, eqn=None, arg_map=None): r''' A general function to interpret a sympy equation and evaluate the linear components of the source term. Parameters ---------- target : OpenPNM object The OpenPNM object where the result will be applied. eqn : sympy symbolic expression for the source terms e.g. y = a*x**b + c arg_map : Dict mapping the symbols in the expression to OpenPNM data on the target. Must contain 'x' which is the independent variable. e.g. arg_map={'a':'pore.a', 'b':'pore.b', 'c':'pore.c', 'x':'pore.x'} Example ---------- >>> import openpnm as op >>> from openpnm.models.physics import generic_source_term as gst >>> import scipy as sp >>> import sympy as _syp >>> pn = op.network.Cubic(shape=[5, 5, 5], spacing=0.0001) >>> water = op.phases.Water(network=pn) >>> water['pore.a'] = 1 >>> water['pore.b'] = 2 >>> water['pore.c'] = 3 >>> water['pore.x'] = sp.random.random(water.Np) >>> a, b, c, x = _syp.symbols('a,b,c,x') >>> y = a*x**b + c >>> arg_map = {'a':'pore.a', 'b':'pore.b', 'c':'pore.c', 'x':'pore.x'} >>> water.add_model(propname='pore.general', ... model=gst.general_symbolic, ... eqn=y, arg_map=arg_map, ... regen_mode='normal') >>> assert 'pore.general.rate' in water.props() >>> assert 'pore.general.S1' in water.props() >>> assert 'pore.general.S1' in water.props() ''' # First make sure all the symbols have been allocated dict items for arg in _syp.postorder_traversal(eqn): if _syp.srepr(arg)[:6] == 'Symbol': key = _syp.srepr(arg)[7:].strip('(').strip(')').strip("'") if key not in arg_map.keys(): raise Exception('argument mapping incomplete, missing '+key) if 'x' not in arg_map.keys(): raise Exception('argument mapping must contain "x" for the ' + 'independent variable') # Get the data data = {} args = {} for key in arg_map.keys(): data[key] = target[arg_map[key]] # Callable functions args[key] = _syp.symbols(key) r, s1, s2 = _build_func(eqn, **args) r_val = r(*data.values()) s1_val = s1(*data.values()) s2_val = s2(*data.values()) values = {'S1': s1_val, 'S2': s2_val, 'rate': r_val} return values
def __boolean_rule_decode__(self, variable, expression): # Results of sympy's expression parsing: # gene = X and 1 => X # gene = X or 1 => 1 (sympy's BooleanTrue) # gene = X and 0 => 0 (sympy's BooleanFalse) # gene = X or 0 => X # gene = 1 => 1 (sympy's one) # gene = 0 => 0 (sympy's zero) # gene = X > 10 => (X, 1 (sympy's number)), namely two args # if expression is atom and defines a variable always On or Off, add a On/Off row if expression.is_Atom: if expression.is_Boolean or expression.is_Number: if isinstance(expression, BooleanFalse) or isinstance( expression, Zero): # add row and set mip bounds to 0;0 self.__add_row__([(variable, 1)], a_ub=0) return elif isinstance(expression, BooleanTrue) or isinstance( expression, One): # add row and set mip bounds to 1;1 self.__add_row__([(variable, 1)], a_lb=1) return else: print("Either the target result is undetermined or " "something is wrong with expression: {}".format( str(expression))) # add row and set mip bounds to 0;1 self.__add_row__([(variable, 1)]) return # elif expression.is_Number: # # if isinstance(expression, Zero): # # # add row and set mip bounds to 0;0 # self.__add_row__([(variable, 1)], a_ub=0) # return # # elif isinstance(expression, One): # # # add row and set mip bounds to 1;1 # self.__add_row__([(variable, 1)], a_lb=1) # return # # else: # print("Either the target result is undetermined or " # "something is wrong with expression: {}".format(str(expression))) # # # add row and set mip bounds to 0;1 # self.__add_row__([(variable, 1)]) # return else: # Else it must be a symbol or nothing # The rest of the function can handle it pass traversal_results = {} last_index = 0 # postorder_traversal recursively iterates an expression in the reverse order # e.g. expr = A and (B or C) # list(postorder_traversal(expr)) # [C, B, A, B | C, A & (B | C)] for arg in postorder_traversal(expression): if isinstance(arg, Or): # Following Boolean algebra, an Or (a = b or c) can be translated as: a = b + c - b*c # Alternatively, an Or can be written as lb < b + c - a < ub # So, for the mid term expression a = b or c # We have therefore the equation: -2 <= 2*b + 2*c – 4*a <= 1 # add Or variable / column # or_col_idx = self.__add_column__('mid_term_' + str(self.A.shape[1])) or_col_idx = self.__add_column__('mid_term_' + str(len(self.A[0]))) # Or left and right operators op_l = traversal_results[arg.args[0]] op_r = traversal_results[arg.args[1]] # add Or row and set mip bounds to -2;1 self.__add_row__([(or_col_idx, -4), (op_l, 2), (op_r, 2)], a_lb=-2, a_ub=1) traversal_results[arg] = or_col_idx last_index = or_col_idx # building a nested Or subexpression for sub_arg in range(2, len(arg.args)): # add Or variable / column # or_col_idx = self.__add_column__('mid_term_' + str(self.A.shape[1])) or_col_idx = self.__add_column__('mid_term_' + str(len(self.A[0]))) # Or left (last Or) and right operators op_l = traversal_results[arg] op_r = traversal_results[arg.args[sub_arg]] # add Or row and set mip bounds to -2;1 self.__add_row__([(or_col_idx, -4), (op_l, 2), (op_r, 2)], a_lb=-2, a_ub=1) traversal_results[arg] = or_col_idx last_index = or_col_idx elif isinstance(arg, And): # Following Boolean algebra, an And (a = b and c) can be translated as: a = b*c # Alternatively, an And can be written as lb < b + c - a < ub # So, for the mid term expression a = b and c # We have therefore the equation: -1 <= 2*b + 2*c – 4*a <= 3 # add And variable / column # and_col_idx = self.__add_column__('mid_term_' + str(self.A.shape[1])) and_col_idx = self.__add_column__('mid_term_' + str(len(self.A[0]))) # And left and right operators op_l = traversal_results[arg.args[0]] op_r = traversal_results[arg.args[1]] # add And row and set mip bounds to -1;3 self.__add_row__([(and_col_idx, -4), (op_l, 2), (op_r, 2)], a_lb=-1, a_ub=3) traversal_results[arg] = and_col_idx last_index = and_col_idx # building a nested And subexpression for sub_arg in range(2, len(arg.args)): # add And variable / column # and_col_idx = self.__add_column__('mid_term_' + str(self.A.shape[1])) and_col_idx = self.__add_column__('mid_term_' + str(len(self.A[0]))) # And left (last And) and right operators op_l = traversal_results[arg] op_r = traversal_results[arg.args[sub_arg]] # add Or row and set mip bounds to -2;1 self.__add_row__([(and_col_idx, -4), (op_l, 2), (op_r, 2)], a_lb=-1, a_ub=3) traversal_results[arg] = and_col_idx last_index = and_col_idx elif isinstance(arg, Not): # Following Boolean algebra, an Not (a = not b) can be translated as: a = 1 - b # Alternatively, an Not can be written as a + b = 1 # So, for the mid term expression a = not b # We have therefore the equation: 1 < a + b < 1 # add Not variable / column # not_col_idx = self.__add_column__('mid_term_' + str(self.A.shape[1])) not_col_idx = self.__add_column__('mid_term_' + str(len(self.A[0]))) # Not right operators op_r = traversal_results[arg.args[0]] # add Not row and set mip bounds to 1;1 self.__add_row__([(not_col_idx, 1), (op_r, 1)], a_lb=1) traversal_results[arg] = not_col_idx last_index = not_col_idx elif isinstance(arg, StrictGreaterThan) or isinstance( arg, GreaterThan): # Following Propositional logic, a predicate (a => r > value) can be translated as: r - value > 0 # Alternatively, flux predicate a => r>value can be a(value + tolerance - r_UB) + r < value + tolerance # & a(r_LB - value - tolerance) + r > r_LB # So, for the mid term expression a => r > value # We have therefore the equations: a(value + tolerance - r_UB) + r < value + tolerance # & a(r_LB - value - tolerance) + r > r_LB # In matlab: c * (Vmax - const) + v <= Vmax # In matlab: const <= c * (const - Vmin) + v # In matlab: compareVal = -compareVal - epsilon # In matlab: matrix_values = [vmax - compare_vale, 1] (-inf, vmax) # In matlab: matrix_values = [compare_vale - vmin, 1] (compareVal, inf) # add Greater variable / column # greater_col_idx = self.__add_column__('mid_term_' + str(self.A.shape[1])) greater_col_idx = self.__add_column__('mid_term_' + str(len(self.A[0]))) # Greater left operators # op_l can be growth, ph, ... # otherwise, it can be a reaction or a boundary reaction associated with a metabolite # Either way, they should be already in the matrix if arg.args[0].is_Symbol: op_l = traversal_results[arg.args[0]] c_val = arg.args[1] else: op_l = traversal_results[arg.args[1]] c_val = arg.args[0] _lb, _ub = self.lbs[op_l], self.ubs[op_l] # add Greater row (a(value + tolerance - r_UB) + r < value + tolerance) and set mip bounds to # -inf;comparison_val self.__add_row__([(greater_col_idx, c_val + _SRFBA_TOL - _ub), (op_l, 1)], a_lb=-999999999, a_ub=c_val + _SRFBA_TOL) # self.__add_row__([(greater_col_idx, _ub + c_val), (op_l, 1)], a_lb=-999999, a_ub=_ub) # add Greater row (a(r_LB - value - tolerance) + r > r_LB) and set mip bounds to lb;inf self.__add_row__([(greater_col_idx, _lb - c_val - _SRFBA_TOL), (op_l, 1)], a_lb=_lb, a_ub=999999999) # self.__add_row__([(greater_col_idx, - c_val - _lb), (op_l, 1)], a_lb=-c_val, a_ub=999999) traversal_results[arg] = greater_col_idx last_index = greater_col_idx elif isinstance(arg, StrictLessThan) or isinstance(arg, LessThan): # Following Propositional logic, a predicate (a => r < value) can be translated as: r - value < 0 # Alternatively, flux predicate a => r<value can be a(value + tolerance - r_LB) + r > value + tolerance # & a(r_UB - value - tolerance) + r < r_UB # So, for the mid term expression a => r > value # We have therefore the equations: a(value + tolerance - r_LB) + r > value + tolerance # & a(r_UB - value - tolerance) + r < r_UB # In matlab: c * (const - Vmax) + v <= const # In matlab: Vmin <= c * (Vmin - const) + v # In matlab: compareVal = -compareVal + epsilon # In matlab: matrix_values = [compareVal-vmax, 1] (-inf, compareVal) # In matlab: matrix_values = [vmin-compareVal, 1] (vmin, inf) # add Less variable / column # less_col_idx = self.__add_column__('mid_term_' + str(self.A.shape[1])) less_col_idx = self.__add_column__('mid_term_' + str(len(self.A[0]))) # Less left operators # op_l can be growth, ph, ... # otherwise, it can be a reaction or a boundary reaction associated with a metabolite # Either way, they should be already in the matrix if arg.args[0].is_Symbol: op_l = traversal_results[arg.args[0]] c_val = arg.args[1] else: op_l = traversal_results[arg.args[1]] c_val = arg.args[0] _lb, _ub = self.lbs[op_l], self.ubs[op_l] # add Less row (a(value + tolerance - r_LB) + r > value + tolerance) and set mip bounds to # -inf;-comparison_val self.__add_row__([(less_col_idx, c_val + _SRFBA_TOL - _lb), (op_l, 1)], a_lb=c_val + _SRFBA_TOL, a_ub=999999999) # self.__add_row__([(less_col_idx, -c_val - _ub), (op_l, 1)], a_lb=-999999, a_ub=-c_val) # add Less row (a(r_UB - value - tolerance) + r < r_UB) and set mip bounds to lb;inf self.__add_row__([(less_col_idx, _ub - c_val - _SRFBA_TOL), (op_l, 1)], a_lb=-999999999, a_ub=_ub) # self.__add_row__([(less_col_idx, _lb + c_val), (op_l, 1)], a_lb=_lb, a_ub=999999) traversal_results[arg] = less_col_idx last_index = less_col_idx else: # If the argument is not an And, Or, Not or Relational # it must be a symbol or a number from the flux predicates # If it is a symbol, it must be added to the matrix if arg.is_Symbol: # However, some things should be handle before adding the variable: # special case of ph # ph can be treated as a reaction # as other reactions, the lower and upper bounds can be set to the initial state or vary between # 0 and 14 # the row bounds (a_lb and a_ub) must be equal to zero as it is done on the reactions if arg.name.lower() == 'ph': if arg.name in self.initial_state: arg_idx = self.__add_column__( arg.name, self.initial_state[arg.name], self.initial_state[arg.name], v_type='continuous') traversal_results[arg] = arg_idx last_index = arg_idx else: arg_idx = self.__add_column__(arg.name, 0, 14, v_type='continuous') traversal_results[arg] = arg_idx last_index = arg_idx # special case of growth (always positive) # growth can be treated as a reaction # as other reactions, the lower and upper bounds can be set to the initial state or vary between # 0.1 and infinite (always positive) # the row bounds (a_lb and a_ub) must be equal to zero as it is done on the reactions elif arg.name.lower() == 'growth': if arg.name in self.initial_state: arg_idx = self.__add_column__( arg.name, self.initial_state[arg.name], self.initial_state[arg.name], v_type='continuous') traversal_results[arg] = arg_idx last_index = arg_idx else: arg_idx = self.__add_column__(arg.name, 0 + _SRFBA_TOL, 999999, v_type='continuous') traversal_results[arg] = arg_idx last_index = arg_idx else: var_id = arg.name if var_id in self._aliases_map: var_id = self._aliases_map[var_id] if var_id in self.metabolic_regulatory_reactions: # if the arg is a reaction, the real reaction index should be returned reaction = self._metabolic_regulatory_reactions[ var_id].cbm_model_id rxn_idx = self.__add_column__(reaction) traversal_results[arg] = rxn_idx last_index = rxn_idx elif var_id in self.metabolic_regulatory_metabolites: # if the arg is a metabolite, the associated reaction index should be returned reaction = self.cbm_simulation_interface.get_boundary_reaction( self._metabolic_regulatory_metabolites[ arg.name].cbm_model_id) if not reaction: # The metabolite does not have a reaction associated with it. This is bad sign though reaction = var_id rxn_idx = self.__add_column__(reaction) traversal_results[arg] = rxn_idx last_index = rxn_idx elif var_id in self.targets: # if the arg is in targets only the column is added, as in the next iterations the row # will be created. tg_idx = self.__add_column__(var_id) traversal_results[arg] = tg_idx last_index = tg_idx else: # if the args of a sympy's expression are regulators or genes, # which are not defined in self.targets or self.reactions, they will never be created # elsewhere they must be added to the MILP matrix as columns # The state can be inferred from the initial state if the variables are in the initial # state else, the variables are considered as unknown if var_id in self.initial_state: arg_idx = self.__add_column__( var_id, self.initial_state[var_id], self.initial_state[var_id]) traversal_results[arg] = arg_idx last_index = arg_idx else: arg_idx = self.__add_column__(var_id) traversal_results[arg] = arg_idx last_index = arg_idx # add gene row which means that the gene variable in the mip matrix is associated with the last mid term # expression, namely the whole expression # set mip bounds to 0;0 self.__add_row__([(variable, 1), (last_index, -1)], a_ub=0) return