def _create_newton_system(ode, theta=1): """ Return the nonlinear F, the Jacobian matrix and a mapping of states to nonero fields for the ODE """ # Lists of symbols states = [state.sym for state in ode.states] states_0 = [state.sym_0 for state in ode.states] vars_ = [var.sym for var in ode.variables] vars_0 = [var.sym_0 for var in ode.variables] def sum_ders(ders): return reduce(lambda x, y: x + y, ders, 0) # Substitution dict between sym and previous sym value subs = [syms for syms in zip(states + vars_, states_0 + vars_0)] # Generate F using theta rule F_expr = [ theta * expr + (1 - theta) * expr.subs(subs) - (sum_ders(ders) - sum_ders(ders).subs(subs)) / ode.dt for ders, expr in ode.get_derivative_expr(True) ] # Create mapping of orginal expression sym_map = OrderedDict() jac_subs = OrderedDict() F = [] for i, expr in enumerate(F_expr): for j, state in enumerate(states): F_ij = expr.diff(state) if F_ij: jac_sym = sp.Symbol(f"j_{i}_{j}") sym_map[i, j] = jac_sym jac_subs[jac_sym] = F_ij # print "[%d,%d] (%d) # [%s, %s] \n%s" \ # % (i,j, len(F_ij.args), states[i], states[j], F_ij) F_i = sp.Symbol(f"F_{i}") sym_map[i, j + 1] = F_i jac_subs[F_i] = expr F.append(F_i) # Create the Jacobian jacobi = sp.SparseMatrix( len(states), len(states), lambda i, j: sym_map.get((i, j), 0), ) # return Symbolic representation of the linear system return sp.Matrix(F), F_expr, jacobi, states, jac_subs
def update_entry(new_count, i, j): global zero_operations if _iszero(b[j, 0] * A[i, j]): zero_operations += 1 return new_count if i != 0: operations.append( "dx[{i}] -= dx[{j}]*jac[{i}*{n}+{j}]".format(i=i, j=j, n=n), ) else: operations.append("dx[{i}] -= dx[{j}]*jac[{j}]".format(i=i, j=j)) # Create new symbol and store the representation new_sym = sp.Symbol(f"F_{i}:{new_count}") new_old[new_sym] = b[i, 0] - b[j, 0] * A[i, j] for old_sym in [b[i, 0], b[j, 0], A[i, j]]: storage = old_new.get(old_sym) if storage is None: storage = set() old_new[old_sym] = storage storage.add(new_sym) # Change entry to the new symbol b[i, 0] = new_sym new_count += 1 return new_count
def __init__(self, name, unit="ms"): super(Time, self).__init__(name, ScalarParam(0.0, unit=unit)) # Add previous value symbol self.sym_0 = sp.Symbol( f"{name}_0", real=True, imaginary=False, commutative=True, hermitian=True, complex=True, )
def symbol_subs(self): """ Return a subs dict for all ODE Objects (states, parameters) """ if self._symbol_subs is None: subs = [] # Deal with parameter subs first if self.optimization.parameter_numerals: subs.extend( (param.sym, param.init) for param in self.ode.parameters) elif not self.optimization.use_parameter_names: subs.extend( (param.sym, sp.Symbol("parameters" + self.index(ind))) for ind, param in enumerate(self.ode.parameters)) elif self._parameter_prefix: subs.extend(( param.sym, sp.Symbol(f"{self._parameter_prefix}{param.name}"), ) for param in self.ode.parameters) # Deal with state subs if not self.optimization.use_state_names: subs.extend((state.sym, sp.Symbol("states" + self.index(ind))) for ind, state in enumerate(self.ode.states)) elif self._state_prefix: subs.extend(( param.sym, sp.Symbol(f"{self._state_prefix}{param.name}"), ) for param in self.ode.states) self._symbol_subs = subs return self._symbol_subs
def update_entry(new_count, i, j): global zero_operations if _iszero(b[j, 0] * A[i, j]): zero_operations += 1 return new_count # Create new symbol and store the representation new_sym = sp.Symbol(f"F_{i}:{new_count}") new_old[new_sym] = b[i, 0] - b[j, 0] * A[i, j] for old_sym in [b[i, 0], b[j, 0], A[i, j]]: storage = old_new.get(old_sym) if storage is None: storage = set() old_new[old_sym] = storage storage.add(new_sym) # Change entry to the new symbol b[i, 0] = new_sym new_count += 1 return new_count
def update_entry(new_count, i, j, k): global zero_operations if _iszero(A[i, k] * A[k, j]): zero_operations += 1 return new_count # Create new symbol and store the representation new_sym = sp.Symbol(f"j_{i}_{j}:{new_count}") new_old[new_sym] = A[i, j] - A[i, k] * A[k, j] for old_sym in [A[i, j], A[i, k], A[k, j]]: storage = old_new.get(old_sym) if storage is None: storage = set() old_new[old_sym] = storage storage.add(new_sym) # Change entry to the new symbol A[i, j] = new_sym new_count += 1 return new_count
def _compute_jacobian(self): if self._jacobian_expr is not None: return ode = self.ode if ode.num_states > 10: info( "Calculating jacobian for {0} states. " "May take some time...".format(ode.num_states), ) sys.stdout.flush() sym_map = OrderedDict() jacobi_expr = OrderedDict() for i, (ders, expr) in enumerate(ode.get_derivative_expr(True)): for j, state in enumerate(ode.states): F_ij = expr.diff(state.sym) # Only collect non zero contributions if F_ij: jacobi_sym = sp.Symbol(f"j_{i}_{j}") sym_map[i, j] = jacobi_sym jacobi_expr[jacobi_sym] = F_ij # print "[%d,%d] (%d) # [%s, %s] \n%s" \ # % (i,j, len(F_ij.args), states[i], states[j], F_ij) # Create the Jacobian self._jacobian_mat = sp.SparseMatrix( ode.num_states, ode.num_states, lambda i, j: sym_map.get((i, j), 0), ) if self.optimization.transposed_jacobian: self._jacobian_mat = self._jacobian_mat.transpose self._jacobian_expr = jacobi_expr if ode.num_states > 10: info(" done")
def __init__(self, name, init, time): """ Create a state variable with an associated initial value Arguments --------- name : str The name of the State init : scalar, ScalarParam The initial value of this state time : Time The time variable """ # Call super class check_arg(init, scalars + (ScalarParam, ), 1, State) super(State, self).__init__(name, init) check_arg(time, Time, 2) self.time = time # Add previous value symbol self.sym_0 = sp.Symbol(f"{name}_0")(time.sym) self.sym_0._assumptions["real"] = True self.sym_0._assumptions["imaginary"] = False self.sym_0._assumptions["commutative"] = True self.sym_0._assumptions["hermitian"] = True self.sym_0._assumptions["complex"] = True self._sym = self._param.sym(self.time.sym) self._sym._assumptions["real"] = True self._sym._assumptions["imaginary"] = False self._sym._assumptions["commutative"] = True self._sym._assumptions["hermitian"] = True self._sym._assumptions["complex"] = True # Flag to determine if State is solved or not self._is_solved = False
def update_entry(new_count, i, j, k): global zero_operations if _iszero(A[i, k] * A[k, j]): zero_operations += 1 return new_count # Store operation if i != 0: operations.append( "jac[{i}*{n}+{j}] -= jac[{i}*{n}+{k}]*jac[{k}*{n}+{j}]". format( i=i, j=j, k=k, n=n, ), ) else: operations.append( "jac[{j}] -= jac[{k}]*jac[{k}*{n}+{j}]".format(j=j, k=k, n=n), ) new_sym = sp.Symbol(f"j_{i}_{j}:{new_count}") new_old[new_sym] = A[i, j] - A[i, k] * A[k, j] for old_sym in [A[i, j], A[i, k], A[k, j]]: storage = old_new.get(old_sym) if storage is None: storage = set() old_new[old_sym] = storage storage.add(new_sym) # Change entry to the new symbol A[i, j] = new_sym new_count += 1 return new_count
def _LU_solve(AA, rhs): """ Returns the symbolic solve of AA*x=rhs """ if not AA.is_square: raise sympy.NonSquareMatrixError() n = AA.rows A = AA[:, :] p = [] nnz = 0 for i in range(n): for j in range(n): nnz += not _iszero(A[i, j]) print("Num non zeros in jacobian:", nnz) # A map between old symbols and new. The values in this dict corresponds to # where an old symbol is used. If the length of the value is 1 it is only # used once and once the value is used it can be freed. old_new = OrderedDict() new_old = OrderedDict() new_count = 0 global zero_operations zero_operations = 0 def update_entry(new_count, i, j, k): global zero_operations if _iszero(A[i, k] * A[k, j]): zero_operations += 1 return new_count # Create new symbol and store the representation new_sym = sp.Symbol(f"j_{i}_{j}:{new_count}") new_old[new_sym] = A[i, j] - A[i, k] * A[k, j] for old_sym in [A[i, j], A[i, k], A[k, j]]: storage = old_new.get(old_sym) if storage is None: storage = set() old_new[old_sym] = storage storage.add(new_sym) # Change entry to the new symbol A[i, j] = new_sym new_count += 1 return new_count # factorization for j in range(n): for i in range(j): for k in range(i): new_count = update_entry(new_count, i, j, k) pivot = -1 for i in range(j, n): for k in range(j): new_count = update_entry(new_count, i, j, k) # find the first non-zero pivot, includes any expression if pivot == -1 and not _iszero(A[i, j]): pivot = i if pivot < 0: # this result is based on iszerofunc's analysis of the # possible pivots, so even though the element may not be # strictly zero, the supplied iszerofunc's evaluation gave # True raise ValueError("No nonzero pivot found; inversion failed.") if pivot != j: # row must be swapped A.row_swap(pivot, j) p.append([pivot, j]) scale = 1 / A[j, j] for i in range(j + 1, n): if _iszero(A[i, j]): zero_operations += 1 continue # Create new symbol and store the representation new_sym = sp.Symbol(f"j_{i}_{j}:{new_count}") new_old[new_sym] = A[i, j] * scale for old_sym in [A[i, j], A[j, j]]: storage = old_new.get(old_sym) if storage is None: storage = set() old_new[old_sym] = storage storage.add(new_sym) # Change entry to the new symbol A[i, j] = new_sym new_count += 1 nnz = 0 for i in range(n): for j in range(n): nnz += not _iszero(A[i, j]) print("Num non zeros in factorized jacobian:", nnz) print("Num non-zero operations while factorizing matrix:", new_count) print("Num zero operations while factorizing matrix:", zero_operations) factorizing_nnz_operations = new_count zero_operations = 0 n = AA.rows b = rhs.permuteFwd(p) def update_entry(new_count, i, j): global zero_operations if _iszero(b[j, 0] * A[i, j]): zero_operations += 1 return new_count # Create new symbol and store the representation new_sym = sp.Symbol(f"F_{i}:{new_count}") new_old[new_sym] = b[i, 0] - b[j, 0] * A[i, j] for old_sym in [b[i, 0], b[j, 0], A[i, j]]: storage = old_new.get(old_sym) if storage is None: storage = set() old_new[old_sym] = storage storage.add(new_sym) # Change entry to the new symbol b[i, 0] = new_sym new_count += 1 return new_count # forward substitution, all diag entries are scaled to 1 for i in range(n): for j in range(i): new_count = update_entry(new_count, i, j) # b.row(i, lambda x,k: x - b[j,k]*A[i,j]) # backward substitution for i in range(n - 1, -1, -1): for j in range(i + 1, n): new_count = update_entry(new_count, i, j) # b.row(i, lambda x,k: x - b[j,k]*A[i,j]) b.row(i, lambda x, k: x / A[i, i]) print( "Num zero operations while forward/backward substituting matrix:", zero_operations, ) print( "Num non-zero operations while factorizing matrix:", new_count - factorizing_nnz_operations, ) return b, new_old, old_new return A, p, old_new, new_old
def __init__( self, factorized, function_name="forward_backward_subst", result_name="dx", residual_name="F", params=None, ): """ Create a JacobianForwardBackwardSubstComponent Arguments --------- factorized : gotran.FactorizedJacobianComponent The factorized jacobian of the ODE function_name : str The name of the function which should be generated result_name : str The name of the result (increment) residual_name : str The name of the residual params : dict Parameters determining how the code should be generated """ timer = Timer( "Computing forward backward substituion component") # noqa: F841 check_arg(factorized, FactorizedJacobianComponent) jacobian_name = list(factorized.shapes.keys())[0] descr = ("Symbolically forward backward substitute linear system " "of {0} ODE".format(factorized.root)) super(ForwardBackwardSubstitutionComponent, self).__init__( "ForwardBackwardSubst", factorized.root, function_name, descr, params=params, use_default_arguments=False, additional_arguments=[residual_name], ) self.add_comment( f"Forward backward substituting factorized linear system {self.root.name}", ) # Recreate jacobian using only sympy Symbols jac_orig = factorized.factorized_jacobian # Size of system n = jac_orig.rows jac = sp.Matrix(n, n, lambda i, j: sp.S.Zero) for i in range(n): for j in range(n): # print jac_orig[i,j] if not jac_orig[i, j].is_zero: name = sympycode(jac_orig[i, j]) jac[i, j] = sp.Symbol( name, real=True, imaginary=False, commutative=True, hermitian=True, complex=True, ) print(jac[i, j]) self.shapes[jacobian_name] = (n, n) self.shapes[residual_name] = (n, ) self.shapes[result_name] = (n, ) F = [] dx = [] # forward substitution, all diag entries are scaled to 1 for i in range(n): F.append(self.add_indexed_object(residual_name, i)) dx.append(self.add_indexed_expression(result_name, i, F[i])) for j in range(i): if jac[i, j].is_zero: continue dx[i] = self.add_indexed_expression( result_name, i, dx[i] - dx[j] * jac[i, j], ) # backward substitution for i in range(n - 1, -1, -1): for j in range(i + 1, n): if jac[i, j].is_zero: continue dx[i] = self.add_indexed_expression( result_name, i, dx[i] - dx[j] * jac[i, j], ) dx[i] = self.add_indexed_expression(result_name, i, dx[i] / jac[i, i]) # No need to call recreate body expressions self.body_expressions = [ obj for obj in self.ode_objects if isinstance(obj, (IndexedExpression, Comment)) ] self.results = [result_name] self.used_states = set() self.used_parameters = set()
def __init__(self, jacobian, function_name="lu_factorize", params=None): """ Create a FactorizedJacobianComponent Arguments --------- jacobian : gotran.JacobianComponent The Jacobian of the ODE function_name : str The name of the function which should be generated params : dict Parameters determining how the code should be generated """ timer = Timer("Computing factorization of jacobian") # noqa: F841 check_arg(jacobian, JacobianComponent) descr = f"Symbolically factorize the jacobian of the {jacobian.root} ODE" super(FactorizedJacobianComponent, self).__init__( "FactorizedJacobian", jacobian.root, function_name, descr, params=params, use_default_arguments=False, additional_arguments=jacobian.results, ) self.add_comment(f"Factorizing jacobian of {self.root.name}") jacobian_name = jacobian.results[0] # Recreate jacobian using only sympy Symbols jac_orig = jacobian.jacobian # Size of system n = jac_orig.rows jac = sp.Matrix(n, n, lambda i, j: sp.S.Zero) for i in range(n): for j in range(n): # print jac_orig[i,j] if not jac_orig[i, j].is_zero: name = sympycode(jac_orig[i, j]) jac[i, j] = sp.Symbol( name, real=True, imaginary=False, commutative=True, hermitian=True, complex=True, ) print(jac[i, j]) p = [] self.shapes[jacobian_name] = (n, n) def add_intermediate_if_changed(jac, jac_ij, i, j): # If item has changed if jac_ij != jac[i, j]: print("jac", i, j, jac_ij) jac[i, j] = self.add_indexed_expression(jacobian_name, (i, j), jac_ij) # Do the factorization for j in range(n): for i in range(j): # Get sympy expr of A_ij jac_ij = jac[i, j] # Build sympy expression for k in range(i): jac_ij -= jac[i, k] * jac[k, j] add_intermediate_if_changed(jac, jac_ij, i, j) pivot = -1 for i in range(j, n): # Get sympy expr of A_ij jac_ij = jac[i, j] # Build sympy expression for k in range(j): jac_ij -= jac[i, k] * jac[k, j] add_intermediate_if_changed(jac, jac_ij, i, j) # find the first non-zero pivot, includes any expression if pivot == -1 and jac[i, j]: pivot = i if pivot < 0: # this result is based on iszerofunc's analysis of the # possible pivots, so even though the element may not be # strictly zero, the supplied iszerofunc's evaluation gave # True error("No nonzero pivot found; symbolic inversion failed.") if pivot != j: # row must be swapped jac.row_swap(pivot, j) p.append([pivot, j]) print("Pivoting!!") # Scale with diagonal if not jac[j, j]: error("Diagonal element of the jacobian is zero. " "Inversion failed") scale = 1 / jac[j, j] for i in range(j + 1, n): # Get sympy expr of A_ij jac_ij = jac[i, j] jac_ij *= scale add_intermediate_if_changed(jac, jac_ij, i, j) # Store factorized jacobian self.factorized_jacobian = jac self.num_nonzero = sum(not jac[i, j].is_zero for i in range(n) for j in range(n)) # No need to call recreate body expressions self.body_expressions = self.ode_objects self.used_states = set() self.used_parameters = set()
def _init_param_state_replace_dict(self): """ Create a parameter state replace dict based on the values in the global parameters """ param_state_replace_dict = {} array_params = self._params["array"] param_repr = self._params["parameters"]["representation"] param_name = self._params["parameters"]["array_name"] param_offset = self._params["parameters"]["add_offset"] field_param_name = self._params["parameters"]["field_array_name"] field_param_offset = self._params["parameters"]["add_field_offset"] field_parameters = self._params["parameters"]["field_parameters"] # If empty # FIXME: Get rid of this by introducing a ListParam type in modelparameters if len(field_parameters) == 1 and field_parameters[0] == "": field_parameters = [] for param in field_parameters: if not isinstance(self.root.present_ode_objects[param], Parameter): error( f"Field parameter '{param}' is not a parameter in the '{self.root}'", ) state_repr = self._params["states"]["representation"] state_name = self._params["states"]["array_name"] state_offset = self._params["states"]["add_offset"] time_name = self._params["time"]["name"] dt_name = self._params["dt"]["name"] # Create a map between states, parameters and the corresponding # IndexedObjects param_state_map = OrderedDict( [("states", OrderedDict()), ("parameters", OrderedDict())], ) # Add states states = param_state_map["states"] for ind, state in enumerate(self.root.full_states): states[state] = StateIndexedObject( state_name, ind, state, (self.root.num_full_states,), array_params, state_offset, ) # Add parameters parameters = param_state_map["parameters"] for ind, param in enumerate(self.root.parameters): if param.name in field_parameters: basename = field_param_name index = field_parameters.index(param.name) shape = (len(field_parameters),) offset = field_param_offset else: basename = param_name index = ind shape = (self.num_parameters,) offset = param_offset parameters[param] = ParameterIndexedObject( basename, index, param, shape, array_params, offset, ) # If not having named parameters if param_repr == "numerals": param_state_replace_dict.update( (param.sym, param.init) for param in self.root.parameters ) elif param_repr == "array": self.shapes[param_name] = (self.root.num_parameters,) if field_parameters: self.shapes["field_" + param_name] = (len(field_parameters),) param_state_replace_dict.update( (param.sym, indexed.sym) for param, indexed in list(param_state_map["parameters"].items()) ) if state_repr == "array": self.shapes[state_name] = (self.root.num_full_states,) param_state_replace_dict.update( (state.sym, indexed.sym) for state, indexed in list(param_state_map["states"].items()) ) param_state_replace_dict[self.root._time.sym] = sp.Symbol(time_name) param_state_replace_dict[self.root._dt.sym] = sp.Symbol(dt_name) self.indexed_map.update(param_state_map) # return dicts return param_state_replace_dict
def _compute_symbolic_fb_substitution(self): if hasattr(self, "_jacobian_fb_substitution_operations"): return if not hasattr(self, "_factorized_jacobian"): self._compute_symbolic_factorization_of_jacobian() global zero_operations zero_operations = 0 A = self._factorized_jacobian n = A.rows b = sp.Matrix(n, 1, lambda i, j: sp.Symbol(f"F_{i}")) old_new = OrderedDict() new_old = OrderedDict() # b = rhs.permuteFwd(p) operations = [] new_count = 0 def update_entry(new_count, i, j): global zero_operations if _iszero(b[j, 0] * A[i, j]): zero_operations += 1 return new_count if i != 0: operations.append( "dx[{i}] -= dx[{j}]*jac[{i}*{n}+{j}]".format(i=i, j=j, n=n), ) else: operations.append("dx[{i}] -= dx[{j}]*jac[{j}]".format(i=i, j=j)) # Create new symbol and store the representation new_sym = sp.Symbol(f"F_{i}:{new_count}") new_old[new_sym] = b[i, 0] - b[j, 0] * A[i, j] for old_sym in [b[i, 0], b[j, 0], A[i, j]]: storage = old_new.get(old_sym) if storage is None: storage = set() old_new[old_sym] = storage storage.add(new_sym) # Change entry to the new symbol b[i, 0] = new_sym new_count += 1 return new_count # forward substitution, all diag entries are scaled to 1 for i in range(n): for j in range(i): new_count = update_entry(new_count, i, j) # b.row(i, lambda x,k: x - b[j,k]*A[i,j]) # backward substitution for i in range(n - 1, -1, -1): for j in range(i + 1, n): new_count = update_entry(new_count, i, j) # b.row(i, lambda x,k: x - b[j,k]*A[i,j]) operations.append("dx[{i}] /= jac[{i}*{n}+{i}]".format(i=i, n=n)) b.row(i, lambda x, k: x / A[i, i]) total_count = zero_operations + new_count * 1.0 if total_count != 0: print("Num operations while forward/backward substituting matrix:") print( "Zero operations:", zero_operations, int(100 * zero_operations / total_count), "%", ) print( "Non-zero operations:", new_count, int(100 * new_count / total_count), "%", ) self._jacobian_fb_substitution_operations = operations
def _compute_symbolic_factorization_of_jacobian(self): if self._jacobian_expr is None: self._compute_jacobian_cse() # Get jacobian AA = self._jacobian_mat if not AA.is_square: raise sympy.NonSquareMatrixError() # Get copy n = AA.rows A = AA[:, :] p = [] n2 = n * n nnz = 0 for i in range(n): # Add dummy value at the diagonal if zero if _iszero(A[i, i]): A[i, i] = 1 for j in range(n): nnz += not _iszero(A[i, j]) print("Num non zeros in jacobian:", nnz, nnz * 1.0 / n2 * 100, "%") # A map between old symbols and new. The values in this dict corresponds to # where an old symbol is used. If the length of the value is 1 it is only # used once and once the value is used it can be freed. old_new = OrderedDict() new_old = OrderedDict() operations = [] new_count = 0 global zero_operations zero_operations = 0 def update_entry(new_count, i, j, k): global zero_operations if _iszero(A[i, k] * A[k, j]): zero_operations += 1 return new_count # Store operation if i != 0: operations.append( "jac[{i}*{n}+{j}] -= jac[{i}*{n}+{k}]*jac[{k}*{n}+{j}]". format( i=i, j=j, k=k, n=n, ), ) else: operations.append( "jac[{j}] -= jac[{k}]*jac[{k}*{n}+{j}]".format(j=j, k=k, n=n), ) new_sym = sp.Symbol(f"j_{i}_{j}:{new_count}") new_old[new_sym] = A[i, j] - A[i, k] * A[k, j] for old_sym in [A[i, j], A[i, k], A[k, j]]: storage = old_new.get(old_sym) if storage is None: storage = set() old_new[old_sym] = storage storage.add(new_sym) # Change entry to the new symbol A[i, j] = new_sym new_count += 1 return new_count # factorization for j in range(n): for i in range(j): for k in range(i): new_count = update_entry(new_count, i, j, k) pivot = -1 for i in range(j, n): for k in range(j): new_count = update_entry(new_count, i, j, k) # find the first non-zero pivot, includes any expression if pivot == -1 and not _iszero(A[i, j]): pivot = i if pivot < 0: # this result is based on iszerofunc's analysis of the # possible pivots, so even though the element may not be # strictly zero, the supplied iszerofunc's evaluation gave # True raise ValueError("No nonzero pivot found; inversion failed.") if pivot != j: # row must be swapped A.row_swap(pivot, j) p.append([pivot, j]) print("Pivoting!!") scale = 1 / A[j, j] for i in range(j + 1, n): if _iszero(A[i, j]): zero_operations += 1 continue # Store operation operations.append( "jac[{i}*{n}+{j}] /= jac[{j}*{n}+{j}]".format(i=i, n=n, j=j), ) # Create new symbol and store the representation new_sym = sp.Symbol(f"j_{i}_{j}:{new_count}") new_old[new_sym] = A[i, j] * scale for old_sym in [A[i, j], A[j, j]]: storage = old_new.get(old_sym) if storage is None: storage = set() old_new[old_sym] = storage storage.add(new_sym) # Change entry to the new symbol A[i, j] = new_sym new_count += 1 nnz = 0 for i in range(n): for j in range(n): nnz += not _iszero(A[i, j]) total_op = new_count + zero_operations if total_op != 0: print( "Num non zeros in factorized jacobian:", nnz, int(nnz * 1.0 / n2 * 100), "%", ) print( "Num non-zero operations while factorizing matrix:", new_count, new_count * 1.0 / total_op * 100, "%", ) print( "Num zero operations while factorizing matrix:", zero_operations, int(zero_operations * 1.0 / total_op * 100), "%", ) self._jacobian_factorization_operations = operations self._factorized_jacobian = A
def __init__( self, basename, indices, shape=None, array_params=None, add_offset="", dependent=None, enum=None, ): """ Create an IndexedExpression with an associated basename Arguments --------- basename : str The basename of the multi index Expression indices : tuple of ints The indices shape : tuple (optional) A tuple with the shape of the indexed object array_params : dict Parameters to create the array name for the indexed object add_offset : bool, str If True a fixed offset is added to the indices dependent : ODEObject If given the count of this IndexedObject will follow as a fractional count based on the count of the dependent object """ array_params = array_params or {} check_arg(basename, str) indices = tuplewrap(indices) check_arg(indices, tuple, itemtypes=int) check_kwarg(array_params, "array_params", dict) # Get index format and index offset from global parameters self._array_params = self.default_parameters() self._array_params.update(array_params) self._enum = enum index_format = self._array_params.index_format index_offset = self._array_params.index_offset flatten = self._array_params.flatten if add_offset and isinstance(add_offset, str): offset_str = add_offset else: offset_str = f"{basename}_offset" if add_offset else "" self._offset_str = offset_str if offset_str: offset_str += "+" # If trying to flatten indices, with a rank larger than 1 a shape needs # to be provided if len(indices) > 1 and flatten and shape is None: error( "A 'shape' need to be provided to generate flatten indices " "for index expressions with rank larger than 1.", ) # Create index format if index_format == "{}": index_format = "{{{0}}}" else: index_format = index_format[0] + "{0}" + index_format[1] orig_indices = indices # If flatten indices if flatten and len(indices) > 1: indices = (sum( reduce(lambda i, j: i * j, shape[i + 1:], 1) * (index + index_offset) for i, index in enumerate(indices)), ) else: indices = tuple(index + index_offset for index in indices) index_str = ",".join(offset_str + str(index) for index in indices) name = basename + index_format.format(index_str) ODEObject.__init__(self, name, dependent) self._basename = basename self._indices = orig_indices self._sym = sp.Symbol( name, real=True, imaginary=False, commutative=True, hermitian=True, complex=True, ) self._shape = shape