Esempio n. 1
0
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
Esempio n. 5
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

        # 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
Esempio n. 6
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
    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
Esempio n. 10
0
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()
Esempio n. 13
0
    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
Esempio n. 16
0
    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