Problem
The NLodeProblem function is the entry point for defining a new problem to be solved by the QSS solver. It takes user-provided code, which includes system parameters, variables, equations, and event logic, and constructs a Problem object that encapsulates all the necessary information for the solver to simulate the system such as problem dimensions, dependencies, and equations. The function works by parsing the user code and extracting relevant data to populate the Problem object.
Problem extension
Problem extension can be achieved easily via PRTYPE which is of type Val, or another subtype of this superclass can be created.
QuantizedSystemSolver.NLODEProblem
— TypeNLODEProblem{F,PRTYPE,T,D,Z,CS}
This is a superclass for all ODE problems. It is parametric on:
- The problem type PRTYPE.
- The number of continuous variables T
- The number of discrete events D
- The number of events (zero crossing functions) Z
- The cache size CS.
What is needed with a new problem:
The more different the new problem from the NLODEContProblem
, the more functions are needed to be extended. In general the following functions need to be extended.
- The
NLodeProblemFunc
method to handle this problem. - The
integrate
method for this new type of problem. - The
custom_Solve
method if needed.
Example
struct SmallODEProblem{CS}<: NLODEProblem{0,1,1,0,0,CS}
cacheSize::Val{CS}# CS= cache size
initConditions::Float64
eq::Function#function that holds the differential equation
end
This new problem type takes care of one differential equation. There is no need for the Jacobian nor for the dependencies. This needs an extension of the custom_Solve method that just removes the references to the jac
and the SD
. An extension of the integrate method is also needed since the implementation is a lot simpler than what is currently implemented.
Further reading about the functions creating the problem
NLODEDiscProblem{F,PRTYPE,T,D,Z,CS}: This is the struct that holds all the necessary data for a nonlinear ordinary differential equation (ODE) problem with discrete events. The structure includes various fields such as initial conditions, discrete variables, Jacobians, event dependencies, and other data related to how the problem is formulated. This structure serves as the core data holder for the problem and will be used in the solver. It is a parametric abstract type that has the following parameters:
PRTYPE: The type of the problem (to distinguish between various types, and allow future extension of the solver to handle new types).
T: The number of continuous variables (state variables).
Z: The number of zero-crossing functions, which are used to detect events.
Y: The actual number of events.
CS: Cache size, which is used to store intermediate operations.
The use of abstract types in this context allows for flexibility and extensibility in the solver. By defining these abstract types, the code can be easily adapted to handle different types of problems, algorithms, and solutions without needing to modify the core solver logic. This design choice enhances the maintainability and scalability of the solver, making it easier to add new features or support additional problem types in the future.
NLodeProblemFunc: After an initial preparation performed by the The NLodeProblem function, The function NLodeProblemFunc takes the resulting expressions to continue constructing an instance of the NLODEDiscProblem structure. It works in several key stages:
Initialization: The function begins by initializing vectors and dictionaries that will hold equations (equs), Jacobian dependencies (jac), zero-crossing functions (ZCjac), and event dependencies. These serve to store the different types of equations and their relationships.
Processing ODEs: It loops through each of the ODE expressions provided by the user. Depending on the type of expression (discrete variables, differential equations, or loop constructs), it processes the right-hand side (RHS) of the equation. For differential equations, it extracts dependencies to build the Jacobian and transform the equations into a more appropriate form for further use. Special cases are handled, such as if the RHS is a number or a symbol.
Handling Events: The function also processes event-related constructs (if conditions) that correspond to different points where the system might undergo discrete changes. It process the RHS of the event equations, transforms them into a suitable form, and builds the necessary dependency structures. Specifically, it constructs how discrete and continuous variables influence one another through the events.
Constructing the Function Code: After processing all ODEs and events, the function dynamically generates a Julia function code needed to store the system of ODEs and events. This code is built into a function that handles different cases (i.e., which equation to evaluate based on an index of a state change or an event).
Building Dependencies: Several helper functions that build the dependencies between variables, events. They build dependency vectors that track how discrete and continous variables influence the system. This is used to know what variables to update and determine when specific events should be checked. By tracking the relationships between variables and events, the solver can determine the appropriate actions to take at each time step. The dependencies are stored in the following vectors:
-$jac$: It determines which variables affect a derivative.
-$ZCjac$: It determines which variables affect a zero-crossing function.
-$SD$: It determines which derivatives that are affected by a given variable.
-$SZ$: It determines which zero-crossing functions that are affected by a given variable.
-$HZ$: It tells which Zero-crossing functions influenced by a given event.
-$HD$: It tells which derivatives influenced by a given event.
Here's a quick summary and what each helper function is doing:
extractJacDepNormal: It Extracts the dependencies for normal (non-loop) expressions. It updates the Jacobian matrix $jac$ and a dictionary $dD$ for tracking dependencies of derivatives to discrete variables.
extractJacDepLoop: Similar to extractJacDepNormal, but specifically for loop expressions. It tracks dependencies across loop iterations.
extractZCJacDepNormal: It Extracts zero-crossing Jacobian dependencies for discrete variables ($dZ$), and it updates $zcjac$, $SZ$.
createDependencyToEventsDiscr: It maps discrete dependencies (dD, dZ) to specific events, it and constructs dependency matrices HZ and HD from the discrete variables only.
createDependencyToEventsCont: Similar to createDependencyToEventsDiscr, but for continuous dependencies (SD, sZ), and it updates the matrices HZ and HD from the continuous variables only.
unionDependency: Merges the two previous sets of dependencies (continuous and discrete) into the final matrices HZ and HD.
Helper packages
The 𝑝𝑜𝑠𝑡𝑤𝑎𝑙𝑘 function from the MacroTools.jl (Copyright (c) 2015: Mike Innes) package plugs parameters and helper functions directly into the equations, and traverses the right-hand side of differential equations and zero-crossing functions, facilitating the construction of the Jacobian matrix and identifying variable dependencies. It also transforms specific expressions like 𝑞[1] into 𝑞[1] [0] within events, and converts 𝑞[𝑖] to 𝑞𝑖, making the equations more tractable for differentiation and Jacobian construction. Additionally, the @𝑐𝑎𝑝𝑡𝑢𝑟𝑒 macro efficiently handles cases where differential equations are defined within a for loop.
The diff(basi, symarg) function from the SymEngine.jl (Copyright (c) 2015-2017 Isuru Fernando) package is applied to perform symbolic differentiation, where basi is an expression and symarg is the symbol with respect to which the derivative is taken. This returns the partial derivative of the expression, making it particularly useful for deriving system Jacobians.
@code_string macro from the CodeTracking.jl (Copyright (c) 2019 Tim Holy) is used to get the body expression of the function that holds the problem given by the user.
@RuntimeGeneratedFunction from the RuntimeGeneratedFunctions.jl package (Copyright (c) 2020 Chris Rackauckas) is used to avoid world-age issues with the generated functions.
Internals
Problem definition
QuantizedSystemSolver.NLODEContProblem
— TypeNLODEContProblem{F,PRTYPE,T,D,Z,CS}
A struct that holds the continuous problem. It has the following fields:
prname
: The name of the problemprtype
: The type of the problema
: The size of the problemc
: The number of discrete eventsb
: The number of zero crossing functionscacheSize
: The size of the cacheinitConditions
: The initial conditions of the problemdiscreteVars
# to match the differentialEqation.jl interface that wants the parameter p to be part of the problemeqs
: The function that holds all the ODEsjac
: The Jacobian dependencySD
: The state derivative dependencyexactJac
: The exact Jacobian functionclosureFuncs::Vector{F}
# function that holds closure function inside system defined by user
QuantizedSystemSolver.NLODEContProblemSpan
— TypeNLODEContProblemSpan{F,PRTYPE,T,D,Z,CS}
A struct that holds the continuous problem with tspan. It has the following fields:
prname
: The name of the problemprtype
: The type of the problema
: The size of the problemc
: The number of discrete varsb
: The number of zero crossing functionscacheSize
: The size of the cacheinitConditions
: The initial conditions of the problemdiscreteVars
# to match the differentialEqation.jl interface that wants the parameter p to be part of the problemeqs
: The function that holds all the ODEsjac
: The Jacobian dependencySD
: The state derivative dependencyexactJac
: The exact Jacobian functiontspan::Tuple{Float64, Float64}
: This field variable did not exist in the original NLODEContProblem as this simulation time should part of the problem. However, to match the differentialEqation.jl interface, the tspan is added to the definition of the problem.closureFuncs::Vector{F}
# function that holds closure function inside system defined by user
QuantizedSystemSolver.NLODEDiscProblem
— TypeNLODEDiscProblem{F,PRTYPE,T,D,Z,CS}
A struct that holds the Problem of a system of ODEs with a set of events. It has the following fields:
prname::Symbol
prtype::Val{PRTYPE}
a::Val{T}
c::Val{D}
b::Val{Z}
cacheSize::Val{CS}
initConditions::Vector{Float64}
discreteVars::Vector{Float64}
jac::Vector{Vector{Int}}
#Jacobian dependency..I have a der and I want to know which vars affect it...opposite of SDZCjac::Vector{Vector{Int}}
# to update other Qs before checking ZCfunctioneqs::Function
#function that holds all ODEseventDependencies::Vector{EventDependencyStruct}
SD::Vector{Vector{Int}}
# I have a var and I want the der that are affected by itHZ::Vector{Vector{Int}}
# an ev occured and I want the ZC that are affected by itHD::Vector{Vector{Int}}
# an ev occured and I want the der that are affected by itSZ::Vector{Vector{Int}}
# I have a var and I want the ZC that are affected by itexactJac::Function
#used only in the implicit integration: linear approximationclosureFuncs::Vector{F}
# function that holds closure function inside system defined by user
QuantizedSystemSolver.NLODEDiscProblemSpan
— TypeNLODEDiscProblemSpan{F,PRTYPE,T,D,Z,CS}
A struct that holds the Problem of a system of ODEs with a set of events with tspan. It has the following fields: -prname::Symbol
-prtype::Val{PRTYPE}
-a::Val{T}
-c::Val{D}
-b::Val{Z}
-cacheSize::Val{CS}
-initConditions::Vector{Float64}
-discreteVars::Vector{Float64}
-jac::Vector{Vector{Int}}
#Jacobian dependency..I have a der and I want to know which vars affect it...opposite of SD -ZCjac::Vector{Vector{Int}}
# to update other Qs before checking ZCfunction -eqs::Function
#function that holds all ODEs -eventDependencies::Vector{EventDependencyStruct}
-SD::Vector{Vector{Int}}
# I have a var and I want the der that are affected by it -HZ::Vector{Vector{Int}}
# an ev occured and I want the ZC that are affected by it -HD::Vector{Vector{Int}}
# an ev occured and I want the der that are affected by it -SZ::Vector{Vector{Int}}
# I have a var and I want the ZC that are affected by it -exactJac::Function
#used only in the implicit integration: linear approximation -tspan::Tuple{Float64, Float64}
# This field variable did not exist in the original NLODEDiscProblem as this simulation time should part of the problem. However, to match the differentialEqation.jl interface, the tspan is added to the definition of the problem. -closureFuncs::Vector{F}
# function that holds closure function inside system defined by user
Problem construction
All the examples that explain the problem construction functions use the following problem. The examples are reproducible as they are shown. To see the problem construction process in one step, add print()
statements inside the functions of the package while solving this problem as shown in the tutorial section.
du[1] = u[2]-2.0*u[1]*u[2]
for k in 2:9
du[k]=u[k]*u[k-1];
end
du[10]=u[1]-u[10]
QuantizedSystemSolver.NLodeProblemFunc
— MethodNLodeProblemFunc(odeExprs::Expr,::Val{T},::Val{D},::Val{0},initConditions::Vector{Float64},du::Symbol,tspan::Tuple{Float64, Float64},discrVars::Union{Vector{EM}, Tuple{Vararg{EM}}},prbName::Symbol) where {T,D,EM}
This function continues building a continuous problem. it receives an expression and useful info from the main interface. it calls the transform function from the taylorEquationConstruction.jl file to change the AST of all operations to personlized ones and update the needed cache size. It also construct via helper functions the Exact jacobian function, the jacobian dependecy and the state-derivative dependency (opposite of jacobian) as vectors. Finally, it groups all differential equations in one function, and constructs a continous problem from the qssProblemDefinition.jl file.
Arguments
odeExprs::Expr
: The expression of the whole user code in the function defining the problem with names modified and parameters plugged in.Val{T}
: the dimensions of the system of differential equations.Val{0}
: No zero-crossing functions. pure continous problem.Val{0}
: No events functions. pure continous problem.initConditions::Vector{Float64}
: No zero-crossing functions. pure continous problem.du::Symbol
: to distinguish the start of a differential equations.symDict::Dict{Symbol,Expr}
: maps a reference expression to a symbol (qi->q[i]).tspan::Tuple{Float64, Float64}
: stores the initial time and final time of the simulation.prbName::Symbol
: The problem name as chosen by the user to be carried to the solution for displaying purposes.
QuantizedSystemSolver.NLodeProblemFunc
— MethodNLodeProblemFunc(odeExprs::Expr,::Val{T},::Val{D},::Val{Z},initCond::Vector{Float64},du::Symbol,tspan::Tuple{Float64, Float64},discrVars::Union{Vector{EM}, Tuple{Vararg{EM}}},prbName::Symbol) where {T,D,Z,EM}
continues building a discrete problem.
It receives an expression and useful info from the main interface. It calls the transform function from the taylorEquationConstruction.jl file to change the AST of all operations to personlized ones and update the needed cache size. It also construct via helper functions the Exact jacobian function, the jacobian dependecy (jac) and the state-derivative dependency (SD:opposite of jacobian), the state to zero-crossing dependency (SZ) and events to derivative and zero-crossing (HD and HZ) as vectors. Finally, it groups all differential equations and events in one function, and constructs a discrete problem from the qssProblemDefinition.jl file.
Problem construction helpers
QuantizedSystemSolver.prepareInfo
— MethodprepareInfo(odeExprs::Expr,stateVarName::Symbol,discrParamName::Symbol)
Prepares information about the ODE problem by replacing symbols and parameters, and extracting information about size, symbols, and initial conditions.
Arguments
odeExprs::Expr
: The expressions defining the ODE.stateVarName::Symbol
: The symbol representing the continuous variables.
Returns
- A
probInfo
struct containing the number of zero-crossings (numZC
) and a dictionary of symbols and expressions (symDict
).
QuantizedSystemSolver.probHelper
— TypeprobHelper
In old interface, this holds information about the ODE problem. It helps the arrangeProb
function to return the number of zero-crossings, a dictionary of symbols and expressions, and other information.
Fields
problemSize::Int
: The size of the problem.discreteSize::Int
: The size of the discrete variables.numZC::Int
: The number of zero-crossings.savedInitCond::Dict{Union{Int,Expr},Float64}
: A dictionary of initial conditions.initConditions::Vector{Float64}
: The initial conditions.du::Symbol
: The symbol representing the derivative of the continuous variables.symDict::Dict{Symbol,Expr}
: A dictionary of symbols and expressions.
QuantizedSystemSolver.arrangeProb
— MethodarrangeProb(x::Expr)
in old interface, this prepares information about the ODE problem by replacing symbols and parameters, and extracting information about size, symbols, and initial conditions.
Returns
- A
probHelper
struct containing the number of zero-crossings (numZC
), a dictionary of symbols and expressions (symDict
), .
QuantizedSystemSolver.changeExprToFirstValue
— MethodchangeExprToFirstValue(ex::Expr)
changes an expression in the form u[1] to an expression in the form u[1][0] inside exact jacobian expressions and inside events, because linear coefficients (a_{ii}) do not have derivatives, and updates in events affect the value of a variable directly and there is no need to update its higher derivatives. It is called by the restoreRef
function for jacobian expressions, and called by the handleEvents
function for events.
Example:
using QuantizedSystemSolver
ex=:(q[i - 1])
newEx=QuantizedSystemSolver.changeExprToFirstValue(ex)
# output
:((q[i - 1])[0])
QuantizedSystemSolver.symbolFromRef
— MethodsymbolFromRef(el::Symbol,refEx::Union{Int64,Expr,Symbol})
gets a symbol qi, qiplusNumber, qiminusNumber, or qitimesNumber from a symbol i or expressions like i+Number, i+Number, i+Number. It is called by the changeVarNames_params
, the extractJacDepNormal
and the extractJacDepLoop
functions
Example:
using QuantizedSystemSolver
ex=:(i - 1)
newEx=QuantizedSystemSolver.symbolFromRef(:q,ex)
(ex,newEx)
# output
(:(i - 1), :qiminus1)
QuantizedSystemSolver.restoreRef
— MethodrestoreRef(coefExpr,symDict)
This function is the opposite of symbolFromRef. After using the symbols in symbolic differentiation, it gets back expressions like p[i+Number] and q[i+Number][0] from symbols diplusNumber and qiplusNumber. Adding a zero to q variables is beacause q is a taylor variable while p is a vector.
arguments:
coefExpr::Expr
: the expression to be changedsymDict::Dict{Symbol,Expr}
: the dictionary to store the translation of symbols of continous and discrete variables (q[i] <-> qi)
Example:
using QuantizedSystemSolver
symDict= Dict{Symbol, Expr}(:qi => :(q[i]), :q10 => :(q[10]), :q2 => :(q[2]), :qiminus1 => :(q[i - 1]), :q1 => :(q[1]))
coefExpr=:(1.5qiminus1)
newEx=QuantizedSystemSolver.restoreRef(coefExpr, symDict)
# output
:(1.5 * (q[i - 1])[0])
QuantizedSystemSolver.changeVarNames_params
— MethodchangeVarNames_params(ex::Expr,stateVarName::Symbol,discrParamName::Symbol,muteVar::Symbol,param::Dict{Symbol,Union{Float64,Int64,Expr,Symbol}})
As the name suggests, this changes the continuous variables names to :q and the discrete variable name to :p and any mute variables to :i. It also plugs the parameters values from a parameter dictionary into the differential equations. The function changeVarNames_params has three methods. One for RHS of equations, one for if-statements when RHS is an expression, and one for if-statements when RHS is a symbol. This is method one. It has an additional symDict::Dict{Symbol,Expr} to collect the translation of symbols of continous and discrete variables (q[i] <-> qi).
arguments:
ex::Expr
: the expression to be changedstateVarName::Symbol
: the name of the state variablemuteVar::Symbol
: the name of the mute variableparam::Dict{Symbol,Union{Float64,Int64,Expr,Symbol}}
: the dictionary of parameters
Example:
using QuantizedSystemSolver
(ex, stateVarName, discrParamName,muteVar, param) = (:(du[k] = u[k] * u[k - 1] * coef2), :u,:p, :k, Dict{Symbol, Union{Float64, Int64,Expr,Symbol}}(:coef1 => 2.0, :coef2 => 1.5))
newEx=QuantizedSystemSolver.changeVarNames_params(ex, stateVarName,discrParamName, muteVar, param)
(newEx, stateVarName, muteVar, param)
# output
(:(du[i] = q[i] * q[i - 1] * 1.5), :u, :k, Dict{Symbol, Union{Float64, Int64, Expr, Symbol}}(:coef1 => 2.0, :coef2 => 1.5))
QuantizedSystemSolver.changeVarNames_params
— MethodchangeVarNames_params(element::Symbol,stateVarName::Symbol,discrParamName::Symbol,muteVar::Symbol,param::Dict{Symbol,Union{Float64,Int64,Expr,Symbol}})
This is method three of the function changeVarNames_params. It is for if-statements when RHS is a symbol. Again, it changes the symbol to :q if it is a continuous variable, to :p if it is a discrete variable, to :i if it is a mute variable, and to its corresponding value if it is a parameter. It is called by the prepareInfo
function.
QuantizedSystemSolver.handleEvents
— MethodhandleEvents(argI::Expr,eventequs::Vector{Expr},length_zcequs::Int64,evsArr::Vector{EventDependencyStruct})
Handles events in the quantized system solver.
Arguments
argI::Expr
: An expression representing the 'if-statement'.eventequs::Vector{Expr}
: A vector of expressions representing the event equations.length_zcequs::Int64
: current number of treated zero-crossing equations. Usezd as index to store the events in order.evsArr
: An array containing EventDependencyStruct objects.
QuantizedSystemSolver.transformFSimplecase
— MethodtransformFSimplecase(ex::Union{Float64,Int64,Expr,Symbol})
transforms expressions of the right hand side of differential equations and zero-crossing functions to personalized ones that use caching to a form that can be used in the createT
function. The right hand side of the equations should be a number or a q[i] term.
Example:
using QuantizedSystemSolver
ex=:(q[2])
newEx=QuantizedSystemSolver.transformFSimplecase(ex);
# output
:(createT(q[2], cache[1]))
QuantizedSystemSolver.transformF
— MethodtransformF(ex::Expr)
transforms expressions of the right hand side of differential equations and zero-crossing functions to personalized ones that use caching to a form that can be used in functions like addT
, subT
, mulT
, muladdT
. The right hand side of the equations can be any form of expression.
Example:
using QuantizedSystemSolver
ex=:(q[2] - 2.0*q[1]*q[2],1)
newEx=QuantizedSystemSolver.transformF(ex);
# output
:((subT(q[2], mulTT(2.0, q[1], q[2], cache[2], cache[3]), cache[1]), 3))
QuantizedSystemSolver.extractJacDepNormal
— MethodextractJacDepNormal(varNum::Int,rhs::Union{Int,Expr},jac :: Dict{Union{Int,Expr},Set{Union{Int,Symbol,Expr}}}, exactJacExpr :: Dict{Expr,Union{Float64,Int,Symbol,Expr}},symDict::Dict{Symbol,Expr})
Extract the jacobian dependency as well as the exacte symbolic jacobian expression, in the form of dictionaries, from the simple differential equations.
The function sarts by looking for the 'i' in q[i] in the RHS and storing this 'i' in a jacSet for the varNum. Then, it changes q[i] to qi for symbolic differentiation. After finding $\frac{\partial f_i}{\partial q_i}$ as the exact jacobian entry, it changes back qi to q[i]. Also, any mute variable from the differential equations is changed to 'i' and the symbol for the variable is changed to 'q'.
example:
using QuantizedSystemSolver
jac = Dict{Union{Int,Expr},Set{Union{Int,Symbol,Expr}}}()
exacteJacExpr = Dict{Expr,Union{Float64,Int,Symbol,Expr}}()
symDict=Dict(:q2 => :(q[2]), :q1 => :(q[1]))
varNum=1;rhs=:(q[2] - 2.0*q[1]*q[2]);
QuantizedSystemSolver.extractJacDepNormal(varNum,rhs,jac,exacteJacExpr ,symDict )
(jac,exacteJacExpr)
# output
(Dict{Union{Int64, Expr}, Set{Union{Int64, Expr, Symbol}}}(1 => Set([2, 1])), Dict{Expr, Union{Float64, Int64, Expr, Symbol}}(:((1, 1)) => :(-2.0 * (q[2])[0]), :((1, 2)) => :(1 - 2.0 * (q[1])[0])))
QuantizedSystemSolver.extractJacDepLoop
— MethodextractJacDepLoop(b::Int,niter::Int,rhs::Union{Int,Expr},jac :: Dict{Union{Int,Expr},Set{Union{Int,Symbol,Expr}}} ,exactJacExpr :: Dict{Expr,Union{Float64,Int,Symbol,Expr}},symDict::Dict{Symbol,Expr})
This function is similar to the extractJacDepNormal
function by using the tuple (b,niter) instead of the integer varNum. It extracts the jacobian dependency as well as the exacte symbolic jacobian expression, in the form of dictionaries, from the differential equations that are written in a loop.
The keys of the exactJacExpr dictionary are more complex than in the case of simple differential equations. This complexity accounts for the fact the 'for' loop contains many simple differential equations. This approach makes parsing of the problem agnostic of the problem size. This function sarts by looking for the 'i' in q[i] in the RHS and storing this 'i' in a jacSet. Then, it changes q[i] to qi for symbolic differentiation. After finding $\frac{\partial rhs}{\partial qi}$ as the exact jacobian entry, it changes back qi to q[i].
Example:
using QuantizedSystemSolver
jac = Dict{Union{Int,Expr},Set{Union{Int,Symbol,Expr}}}()
exacteJacExpr = Dict{Expr,Union{Float64,Int,Symbol,Expr}}()
b,niter=2,9;
rhs=:(q[i] * q[i - 1]);
symDict=Dict(:qi => :(q[i]), :q2 => :(q[2]), :q9 => :(q[9]), :qiminus1 => :(q[i - 1]), :q10 => :(q[10]), :q1 => :(q[1]))
QuantizedSystemSolver.extractJacDepLoop(b,niter,rhs,jac,exacteJacExpr ,symDict )
(jac,exacteJacExpr)
# output
(Dict{Union{Int64, Expr}, Set{Union{Int64, Expr, Symbol}}}(:((2, 9)) => Set([:(i - 1), :i])), Dict{Expr, Union{Float64, Int64, Expr, Symbol}}(:(((2, 9), i - 1)) => :((q[i])[0]), :(((2, 9), i)) => :((q[i - 1])[0])))
QuantizedSystemSolver.createJacVect
— MethodcreateJacVect(jac:: Dict{Union{Int,Expr},Set{Union{Int,Symbol,Expr}}},::Val{T}) where {T}
constructs the jacobian dependency as a vector from the existing dictionary jac resulted from extractJacDepNormal
and extractJacDepLoop
functions.
This function just collects the data from the value of the dictionary if the key of the dictionary is an integer. (a dictionary contains(key=>value),...). In the case it is an expression :(b,niter), the function uses a 'for' loop to replace each 'b' by its corresponding integer. This approach depends on the size of the problem, but it runs one time.
Example:
using QuantizedSystemSolver
jac=Dict{Union{Int64, Expr}, Set{Union{Int64, Expr, Symbol}}}(1 => Set([2, 1]),:((2, 9)) => Set([:(i - 1), :i]),10 => Set([1, 10]))
jacVect=QuantizedSystemSolver.createJacVect(jac,Val(10) )
string(jacVect)
# output
"[[2, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8], [8, 9], [10, 1]]"
QuantizedSystemSolver.createSDVect
— MethodcreateSDVect(jac:: Dict{Union{Int,Expr},Set{Union{Int,Symbol,Expr}}},::Val{T}) where {T}
constructs the State to derivative dependency (opposite of jacobian dependency) as a vector from the existing dictionary jac resulted from the extractJacDepNormal
and the extractJacDepLoop
functions. It is the opposite in the sense that here we collect the keys into some vectors whereas in the jacobian dependency we collect the values of the dictionary in some vectors.
If the key of the dictionary is an integer, then for all elements 'k' in the value of the dictionary (a set), the key is pushed into a new vector indexed at 'k'. In the case the key is an expression :(b,niter), the function uses a 'for' loop to replace each 'b' by its corresponding integer. This approach depends on the size of the problem, but it runs one time.
Example:
using QuantizedSystemSolver
jac=Dict{Union{Int64, Expr}, Set{Union{Int64, Expr, Symbol}}}(1 => Set([2, 1]),:((2, 9)) => Set([:(i - 1), :i]),10 => Set([1, 10]));
SD=QuantizedSystemSolver.createSDVect(jac,Val(10) );
string(SD)
# output
"[[10, 2, 1], [2, 3, 1], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8], [8, 9], [9], [10]]"
QuantizedSystemSolver.createExactJacFun
— MethodcreateExactJacFun(otherCode::Expr,Exactjac:: Dict{Expr,Union{Float64,Int,Symbol,Expr}},funName::Symbol,f::F) where{F}
constructs the exact jacobian entries as a function from the existing dictionary Exactjac resulted from extractJacDepNormal and extractJacDepLoop functions.
From the input dictionary exactJac, we see that the key is always a expression that hold a tuple of size 2: the first element is going to be the index 'i' and the second element if the index 'j'. The value corresponding to this key, which is the exact jacobian entry, is put in a cache. i.e the function maps the keys of the dictionary to their values using an 'if-statement.. This approach does not depend on the size of the problem. # Example:
using QuantizedSystemSolver
exacteJacExpr=Dict{Expr,Union{Float64,Int,Symbol,Expr}}(:((1, 1)) => :(-2.0 * (q[2])[0]), :((1, 2)) => :(1 - 2.0 * (q[1])[0]),:(((2, 9), i - 1)) => :((q[i])[0]), :(((2, 9), i)) => :((q[i - 1])[0]),:((10, 10)) => -1, :((10, 1)) => 1);
exactJac=QuantizedSystemSolver.createExactJacFun(:(),exacteJacExpr,:f,0);
exactJac
# output
:(function exactJacf(q::Vector{Taylor0}, p::Vector{Float64}, cache::MVector{1, Float64}, i::Int, j::Int, t::Float64, f_::F)
(if i == 0
return nothing
elseif i == 1 && j == 1
cache[1] = -2.0 * (q[2])[0]
return nothing
elseif i == 10 && j == 10
cache[1] = -1
return nothing
elseif 2 <= i <= 9 && j == i - 1
cache[1] = (q[i])[0]
return nothing
elseif i == 1 && j == 2
cache[1] = 1 - 2.0 * (q[1])[0]
return nothing
elseif 2 <= i <= 9 && j == i
cache[1] = (q[i - 1])[0]
return nothing
elseif i == 10 && j == 1
cache[1] = 1
return nothing
end,)
end)
QuantizedSystemSolver.createContEqFun
— MethodcreateContEqFun(otherCode::Expr,equs::Dict{Union{Int,Expr},Union{Int,Symbol,Expr}},fname::Symbol,f::F) where{F}
constructs one function from all differential equations in the problem, which are transformed and stored in a dictionary in the NLodeProblemFunc function.
The function maps the keys of the dictionary to their values using an 'if-statement. # Example:
using QuantizedSystemSolver
equs = Dict{Union{Int,Expr},Union{Int,Symbol,Expr}}(10 => :(subT(q[1], q[10], cache[1])), :((2, 9)) => :(mulT(q[i], q[i - 1], cache[1])), 1 => :(subT(q[2], mulTT(2.0, q[1], q[2], cache[2], cache[3]), cache[1])));
diffEqfun=QuantizedSystemSolver.createContEqFun(:(),equs,:f,0);
diffEqfun
# output
:(function f(i::Int, q::Vector{Taylor0}, t::Taylor0, p::Vector{Float64}, cache::Vector{Taylor0}, f_::F)
(if i == 0
return nothing
elseif i == 10
subT(q[1], q[10], cache[1])
return nothing
elseif 2 <= i <= 9
mulT(q[i], q[i - 1], cache[1])
return nothing
elseif i == 1
subT(q[2], mulTT(2.0, q[1], q[2], cache[2], cache[3]), cache[1])
return nothing
end,)
end)
QuantizedSystemSolver.extractJacDepNormalDiscrete
— MethodextractJacDepNormalDiscrete(varNum::Int,rhs::Union{Symbol,Int,Expr},jac :: Dict{Union{Int,Expr},Set{Union{Int,Symbol,Expr}}},exactJacExpr :: Dict{Expr,Union{Float64,Int,Symbol,Expr}},symDict::Dict{Symbol,Expr},dD :: Dict{Union{Int,Expr},Set{Union{Int,Symbol,Expr}}})
Extracts the jacobian dependency (jac) as well as the exacte symbolic jacobian expression (exactJacExpr) and the dependency of state derivatives to discrete variables (dD), in the form of dictionaries, from the simple differential equations.
For the continuous part, similar to extractJacDepNormal
function, this function sarts by looking for the 'i' in q[i] in the RHS and storing this 'i' in a jacSet for the varNum. Then, it changes q[i] to qi for symbolic differentiation. After finding $\frac{\partial f_i}{\partial q_i}$ as the exact jacobian entry, it changes back qi to q[i]. Also, any mute variable from the differential equations is changed to 'i'.
For the discrete part, the function puts the index of the differential equation in a set, and stores this set in a dictionary dD with the key being the index of the discrete variable.
Example:
using QuantizedSystemSolver
(varNum, rhs, jac, exactJacExpr, symDict, dD) = (1, :(p[2] - 2.0 * q[1] * p[2]), Dict{Union{Int64, Expr}, Set{Union{Int64, Expr, Symbol}}}(), Dict{Expr, Union{Float64, Int64, Expr, Symbol}}(), Dict{Symbol, Expr}(:q10 => :(q[10]), :p2 => :(p[2]), :qiminus1 => :(q[i - 1]), :p1 => :(p[1]), :q1 => :(q[1])), Dict{Union{Int64, Expr}, Set{Union{Int64, Expr, Symbol}}}())
QuantizedSystemSolver.extractJacDepNormalDiscrete(varNum, rhs, jac, exactJacExpr, symDict, dD )
(jac, exactJacExpr, dD)
# output
(Dict{Union{Int64, Expr}, Set{Union{Int64, Expr, Symbol}}}(1 => Set([1])), Dict{Expr, Union{Float64, Int64, Expr, Symbol}}(:((1, 1)) => :(-2.0 * p[2])), Dict{Union{Int64, Expr}, Set{Union{Int64, Expr, Symbol}}}(2 => Set([1])))
QuantizedSystemSolver.extractJacDepLoopDiscrete
— MethodextractJacDepLoopDiscrete(b::Int,niter::Int,rhs::Union{Symbol,Int,Expr},jac :: Dict{Union{Int,Expr},Set{Union{Int,Symbol,Expr}}},exactJacExpr :: Dict{Expr,Union{Float64,Int,Symbol,Expr}},symDict::Dict{Symbol,Expr},dD :: Dict{Union{Int,Expr},Set{Union{Int,Symbol,Expr}}})
This function is similar to the extractJacDepNormalDiscrete
function by using the tuple (b,niter) instead of the integer varNum. Itextracts the jacobian dependency (jac) as well as the exacte symbolic jacobian expression (exactJacExpr) and the dependency of state derivatives to discrete variables (dD), in the form of dictionaries, from the differential equations that are written in a loop.
For the continuous part, it sarts by looking for the 'i' in q[i] in the RHS and storing this 'i' in a jacSet. Then, it changes q[i] to qi for symbolic differentiation. After finding $\frac{\partial f_i}{\partial q_i}$ as the exact jacobian entry, it changes back qi to q[i]. Also, any mute variable from the differential equations is changed to 'i'.
For the discrete part, the function puts the the tuple (b,niter) in a set, and stores this set in a dictionary dD with the key being the index of the discrete variable. # Example:
using QuantizedSystemSolver
(b, niter, rhs, jac, exactJacExpr, symDict, dD) = (2, 9, :(p[1] * q[i - 1] * 1.5), Dict{Union{Int64, Expr}, Set{Union{Int64, Expr, Symbol}}}(1 => Set([1])), Dict{Expr, Union{Float64, Int64, Expr, Symbol}}(:((1, 1)) => :(-2.0 * p[2])), Dict{Symbol, Expr}(:q10 => :(q[10]), :p2 => :(p[2]), :qiminus1 => :(q[i - 1]), :p1 => :(p[1]), :q1 => :(q[1])), Dict{Union{Int64, Expr}, Set{Union{Int64, Expr, Symbol}}}(2 => Set([1])))
QuantizedSystemSolver.extractJacDepLoopDiscrete(b, niter, rhs, jac, exactJacExpr, symDict, dD )
(jac, exactJacExpr, dD)
# output
(Dict{Union{Int64, Expr}, Set{Union{Int64, Expr, Symbol}}}(:((2, 9)) => Set([:(i - 1)]), 1 => Set([1])), Dict{Expr, Union{Float64, Int64, Expr, Symbol}}(:((1, 1)) => :(-2.0 * p[2]), :(((2, 9), i - 1)) => :(1.5 * p[1])), Dict{Union{Int64, Expr}, Set{Union{Int64, Expr, Symbol}}}(2 => Set([1]), 1 => Set([:((2, 9))])))
QuantizedSystemSolver.extractZCJacDepNormal
— MethodextractZCJacDepNormal(counter::Int,zcf::Expr,zcjac :: Vector{Vector{Int}},SZ ::Dict{Int,Set{Int}},dZ :: Dict{Int,Set{Int}})
Extracts the zero-crossing jacobian dependency as a vector (zcjac), the dependency of the zero-crossing functions to continuous (SZ) and discrete variables (dZ) in the form of dictionaries, from the 'if-statements' (zcf).
The zcjac is a vector of vectors, where each vector contains the indices of the continuous variables that the zero-crossing function depends on. The SZ dictionary contains the indices of the zero-crossing functions as values and the indices of the continuous variables as keys. Similarly, the dZ dictionary contains the indices of the zero-crossing functions as values and the indices of the discrete variables as keys.
Example:
using QuantizedSystemSolver
(counter, zcf, zcjac, SZ, dZ) = (2, :(q[2] - p[1]), [[1]], Dict{Int64, Set{Int64}}(1 => Set([1])), Dict{Int64, Set{Int64}}())
QuantizedSystemSolver.extractZCJacDepNormal(counter, zcf, zcjac, SZ, dZ)
(zcjac, SZ, dZ)
# output
([[1], [2]], Dict{Int64, Set{Int64}}(2 => Set([2]), 1 => Set([1])), Dict{Int64, Set{Int64}}(1 => Set([2])))
QuantizedSystemSolver.EventDependencyStruct
— TypeEventDependencyStruct
A struct that holds the event dependency information. It has the following fields:
id::Int:
the id of the eventevCont::Vector{Int}:
the index tracking used for HD & HZ. Also it is used to update q,quantum,recomputeNext when x is modified in an eventevDisc::Vector{Int}:
the index tracking used for HD & HZ.evContRHS::Vector{Int}:
the index tracking used to update other Qs before executing the event
QuantizedSystemSolver.createSZVect
— MethodcreateSZVect(SZ :: Dict{Int64, Set{Int64}},::Val{T}) where {T}
constructs the zero-crossing dependency to state variables as a vector from the existing dictionary SZ resulted from the extractZCJacDepNormal
function. The continuous variables are the keys and the zero-crossing are the values.
Example:
using QuantizedSystemSolver
(SZ, T) = (Dict{Int64, Set{Int64}}(2 => Set([2]), 1 => Set([1])), 10)
szVec=QuantizedSystemSolver.createSZVect(SZ, Val(T))
string(szVec)
# output
"[[1], [2], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[]]"
QuantizedSystemSolver.createdDVect
— MethodcreatedDVect(dD::Dict{Union{Int64, Expr}, Set{Union{Int64, Expr, Symbol}}},::Val{D}) where {D}
constructs the State to derivative dependency to discrete variables as a vector from the existing dictionary dD resulted from the extractJacDepNormalDiscrete
and the extractJacDepLoopDiscrete
functions. The discrete variables are the keys and the differential equations are the values. This dependency is needed only in createDependencyToEventsDiscr
.
Example:
using QuantizedSystemSolver
(dD, D) = (Dict{Union{Int64, Expr}, Set{Union{Int64, Expr, Symbol}}}(2 => Set([10, 1]), 1 => Set([:((2, 9))])), 2)
dDVect=QuantizedSystemSolver.createdDVect(dD, Val(D) )
string(dDVect)
# output
"[[2, 3, 4, 5, 6, 7, 8, 9], [10, 1]]"
QuantizedSystemSolver.createDependencyToEventsDiscr
— MethodcreateDependencyToEventsDiscr(dD::Vector{Vector{Int}},dZ::Dict{Int64, Set{Int64}},eventDep::Vector{EventDependencyStruct})
constructs the dependency of zero-crossing functions and state derivatives to events using only discrete variables.
arguments
dD::Vector{Vector{Int}}:
the dependency of state derivatives to discrete variables as a vectordZ::Dict{Int64, Set{Int64}}:
the dependency of zero-crossing functions to discrete variables as a dictionaryeventDep::Vector{EventDependencyStruct}:
the event dependency information as a vector of structs
returns
HZ1::Vector{Vector{Int}}:
the dependency of zero-crossing functions to events using only discrete variablesHD1::Vector{Vector{Int}}:
the dependency of differential equations to events using only discrete variablesExample:
using QuantizedSystemSolver
(dD, dZ, eventDep) = ([[2, 3, 4, 5, 6, 7, 8, 9], [10, 1]], Dict{Int64, Set{Int64}}(1 => Set([2])), QuantizedSystemSolver.EventDependencyStruct[QuantizedSystemSolver.EventDependencyStruct(1, Int64[], [1], Int64[]), QuantizedSystemSolver.EventDependencyStruct(2, Int64[], Int64[], Int64[]), QuantizedSystemSolver.EventDependencyStruct(3, [3], [2], [3, 1, 2]), QuantizedSystemSolver.EventDependencyStruct(4, Int64[], Int64[], Int64[])])
(HZ1, HD1) =QuantizedSystemSolver.createDependencyToEventsDiscr(dD, dZ, eventDep )
(HZ1, HD1)
# output
([[2], Int64[], Int64[], Int64[]], [[5, 4, 6, 7, 2, 9, 8, 3], Int64[], [10, 1], Int64[]])
QuantizedSystemSolver.createDependencyToEventsCont
— MethodcreateDependencyToEventsCont(SD::Vector{Vector{Int}},sZ::Dict{Int64, Set{Int64}},eventDep::Vector{EventDependencyStruct})
constructs the dependency of zero-crossing functions and state derivatives to events using only continuous variables.
arguments
SD::Vector{Vector{Int}}:
the dependency of state derivatives to continuous variables as a vectorsZ::Dict{Int64, Set{Int64}}:
the dependency of zero-crossing functions to continuous variables as a dictionaryeventDep::Vector{EventDependencyStruct}:
the event dependency information as a vector of structs
returns
HZ2::Vector{Vector{Int}}:
the dependency of zero-crossing functions to events using only continuous variablesHD2::Vector{Vector{Int}}:
the dependency of differential equations to events using only continuous variablesExample:
using QuantizedSystemSolver
(SD, sZ, eventDep) = ([[10, 2, 1], [3], [4], [5], [6], [7], [8], [9], Int64[], [10]], Dict{Int64, Set{Int64}}(2 => Set([2]), 1 => Set([1])), QuantizedSystemSolver.EventDependencyStruct[QuantizedSystemSolver.EventDependencyStruct(1, Int64[], [1], Int64[]), QuantizedSystemSolver.EventDependencyStruct(2, Int64[], Int64[], Int64[]), QuantizedSystemSolver.EventDependencyStruct(3, [3], [2], [3, 1, 2]), QuantizedSystemSolver.EventDependencyStruct(4, Int64[], Int64[], Int64[])])
(HZ2, HD2) =QuantizedSystemSolver.createDependencyToEventsCont(SD, sZ, eventDep)
(HZ2, HD2)
# output
([Int64[], Int64[], Int64[], Int64[]], [Int64[], Int64[], [4], Int64[]])
QuantizedSystemSolver.unionDependency
— MethodunionDependency(HZD1::Vector{Vector{Int}},HZD2::Vector{Vector{Int}})
merges the state derivatives and zero-crossing functions dependencies to events using both continuous and discrete variables.
Example:
using QuantizedSystemSolver
(HD1, HD2) = ([[5, 4, 6, 7, 2, 9, 8, 3], Int64[], [10, 1], Int64[]], [Int64[], Int64[], [4], Int64[]])
HD=QuantizedSystemSolver.unionDependency(HD1, HD2)
string(HD)
# output
"[[5, 4, 6, 7, 2, 9, 8, 3], Int64[], [4, 10, 1], Int64[]]"
Index
QuantizedSystemSolver.EventDependencyStruct
QuantizedSystemSolver.NLODEContProblem
QuantizedSystemSolver.NLODEContProblemSpan
QuantizedSystemSolver.NLODEDiscProblem
QuantizedSystemSolver.NLODEDiscProblemSpan
QuantizedSystemSolver.NLODEProblem
QuantizedSystemSolver.probHelper
QuantizedSystemSolver.NLodeProblemFunc
QuantizedSystemSolver.NLodeProblemFunc
QuantizedSystemSolver.arrangeProb
QuantizedSystemSolver.changeExprToFirstValue
QuantizedSystemSolver.changeVarNames_params
QuantizedSystemSolver.changeVarNames_params
QuantizedSystemSolver.createContEqFun
QuantizedSystemSolver.createDependencyToEventsCont
QuantizedSystemSolver.createDependencyToEventsDiscr
QuantizedSystemSolver.createExactJacFun
QuantizedSystemSolver.createJacVect
QuantizedSystemSolver.createSDVect
QuantizedSystemSolver.createSZVect
QuantizedSystemSolver.createdDVect
QuantizedSystemSolver.extractJacDepLoop
QuantizedSystemSolver.extractJacDepLoopDiscrete
QuantizedSystemSolver.extractJacDepNormal
QuantizedSystemSolver.extractJacDepNormalDiscrete
QuantizedSystemSolver.extractZCJacDepNormal
QuantizedSystemSolver.handleEvents
QuantizedSystemSolver.prepareInfo
QuantizedSystemSolver.restoreRef
QuantizedSystemSolver.symbolFromRef
QuantizedSystemSolver.transformF
QuantizedSystemSolver.transformFSimplecase
QuantizedSystemSolver.unionDependency