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.NLODEProblemType
NLODEProblem{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.
source

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.NLODEContProblemType
NLODEContProblem{F,PRTYPE,T,D,Z,CS}

A struct that holds the continuous problem. It has the following fields:

  • prname: The name of the problem
  • prtype: The type of the problem
  • a: The size of the problem
  • c: The number of discrete events
  • b: The number of zero crossing functions
  • cacheSize: The size of the cache
  • initConditions: The initial conditions of the problem
  • discreteVars # to match the differentialEqation.jl interface that wants the parameter p to be part of the problem
  • eqs: The function that holds all the ODEs
  • jac: The Jacobian dependency
  • SD: The state derivative dependency
  • exactJac: The exact Jacobian function
  • closureFuncs::Vector{F} # function that holds closure function inside system defined by user
source
QuantizedSystemSolver.NLODEContProblemSpanType
NLODEContProblemSpan{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 problem
  • prtype: The type of the problem
  • a: The size of the problem
  • c: The number of discrete vars
  • b: The number of zero crossing functions
  • cacheSize: The size of the cache
  • initConditions: The initial conditions of the problem
  • discreteVars # to match the differentialEqation.jl interface that wants the parameter p to be part of the problem
  • eqs: The function that holds all the ODEs
  • jac: The Jacobian dependency
  • SD: The state derivative dependency
  • exactJac: The exact Jacobian function
  • tspan::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
source
QuantizedSystemSolver.NLODEDiscProblemType
NLODEDiscProblem{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 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
  • closureFuncs::Vector{F} # function that holds closure function inside system defined by user
source
QuantizedSystemSolver.NLODEDiscProblemSpanType
NLODEDiscProblemSpan{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

source

Problem construction

The examples of the continuous problem

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.NLodeProblemFuncMethod
NLodeProblemFunc(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.
source
QuantizedSystemSolver.NLodeProblemFuncMethod
NLodeProblemFunc(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.
source

Problem construction helpers

QuantizedSystemSolver.prepareInfoMethod
prepareInfo(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).
source
QuantizedSystemSolver.probHelperType
probHelper

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.
source
QuantizedSystemSolver.arrangeProbMethod
arrangeProb(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), .
source
QuantizedSystemSolver.changeExprToFirstValueMethod
changeExprToFirstValue(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])
source
QuantizedSystemSolver.restoreRefMethod
restoreRef(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 changed
  • symDict::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]) 
source
QuantizedSystemSolver.changeVarNames_paramsMethod
changeVarNames_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 changed
  • stateVarName::Symbol: the name of the state variable
  • muteVar::Symbol: the name of the mute variable
  • param::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))
source
QuantizedSystemSolver.changeVarNames_paramsMethod
changeVarNames_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.

source
QuantizedSystemSolver.handleEventsMethod

handleEvents(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.
source
QuantizedSystemSolver.transformFSimplecaseMethod
transformFSimplecase(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])) 
source
QuantizedSystemSolver.transformFMethod
transformF(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))
source
QuantizedSystemSolver.extractJacDepNormalMethod
extractJacDepNormal(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])))
source
QuantizedSystemSolver.extractJacDepLoopMethod
extractJacDepLoop(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])))
source
QuantizedSystemSolver.createJacVectMethod
createJacVect(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]]"
source
QuantizedSystemSolver.createSDVectMethod
createSDVect(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]]"
source
QuantizedSystemSolver.createExactJacFunMethod
createExactJacFun(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)
source
QuantizedSystemSolver.createContEqFunMethod
createContEqFun(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)
source
QuantizedSystemSolver.extractJacDepNormalDiscreteMethod
extractJacDepNormalDiscrete(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])))
source
QuantizedSystemSolver.extractJacDepLoopDiscreteMethod
extractJacDepLoopDiscrete(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))])))
source
QuantizedSystemSolver.extractZCJacDepNormalMethod
extractZCJacDepNormal(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])))
source
QuantizedSystemSolver.EventDependencyStructType
EventDependencyStruct

A struct that holds the event dependency information. It has the following fields:

  • id::Int: the id of the event
  • evCont::Vector{Int}: the index tracking used for HD & HZ. Also it is used to update q,quantum,recomputeNext when x is modified in an event
  • evDisc::Vector{Int}: the index tracking used for HD & HZ.
  • evContRHS::Vector{Int}: the index tracking used to update other Qs before executing the event
source
QuantizedSystemSolver.createSZVectMethod
createSZVect(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[]]"
source
QuantizedSystemSolver.createdDVectMethod
createdDVect(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]]"
source
QuantizedSystemSolver.createDependencyToEventsDiscrMethod
createDependencyToEventsDiscr(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 vector
  • dZ::Dict{Int64, Set{Int64}}: the dependency of zero-crossing functions to discrete variables as a dictionary
  • eventDep::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 variables

  • HD1::Vector{Vector{Int}}: the dependency of differential equations to events using only discrete variables

    Example:

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[]])
source
QuantizedSystemSolver.createDependencyToEventsContMethod
createDependencyToEventsCont(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 vector
  • sZ::Dict{Int64, Set{Int64}}: the dependency of zero-crossing functions to continuous variables as a dictionary
  • eventDep::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 variables

  • HD2::Vector{Vector{Int}}: the dependency of differential equations to events using only continuous variables

    Example:

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[]])
source
QuantizedSystemSolver.unionDependencyMethod
unionDependency(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[]]"
source

Index