Skip to content

Commit c5a836f

Browse files
authored
Merge pull request #176 from ReactionMechanismGenerator/modelreduction
Enabling simulation of reduced model
2 parents cf156ab + 43afde2 commit c5a836f

15 files changed

Lines changed: 662 additions & 65 deletions

.github/workflows/documentation.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
- uses: actions/checkout@v2
1515
- uses: julia-actions/setup-julia@latest
1616
with:
17-
version: 1.5
17+
version: 1.6
1818
- name: Install dependencies
1919
run: |
2020
wget https://repo.anaconda.com/miniconda/Miniconda2-latest-Linux-x86_64.sh -O miniconda.sh;

Project.toml

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@ authors = ["Matt Johnson <mjohnson541@gmail.com>"]
44
version = "0.4.0"
55

66
[deps]
7-
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
87
Calculus = "49dc2e85-a5d0-5ad3-a950-438e2897f1b9"
98
Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
109
Conda = "8f4d0f93-b110-5947-807f-2305c1781a2d"
11-
DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e"
1210
DiffEqSensitivity = "41bf760c-e81c-5289-8e54-58b1f1f8abe2"
1311
FastGaussQuadrature = "442a2c76-b920-505d-bb47-c5924d526838"
1412
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
@@ -21,47 +19,51 @@ LsqFit = "2fda8390-95c7-5789-9bda-21331edee243"
2119
ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78"
2220
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
2321
Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a"
22+
PreallocationTools = "d236fae5-4411-538c-8e31-a6e3d9e00b46"
2423
PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0"
2524
PyPlot = "d330b81b-6aea-500a-939a-2ce795aea3ee"
2625
QuartzImageIO = "dca85d43-d64c-5e67-8c65-017450d5d020"
2726
RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd"
2827
ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267"
28+
SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462"
2929
SmoothingSplines = "102930c3-cf33-599f-b3b1-9a29a5acab30"
3030
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
3131
SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"
3232
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
3333
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
3434
Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4"
35+
Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
3536
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
3637
Tracker = "9f7883ad-71c0-57eb-9f7f-b5c9e6d3789c"
3738
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
3839
YAML = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6"
3940
Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f"
4041

4142
[compat]
42-
CSV = "0.7,0.8"
4343
Calculus = "0.4,0.5"
4444
Colors = "0.11,0.12"
4545
Conda = "1"
46-
DiffEqBase = "6"
4746
DiffEqSensitivity = "6"
4847
ForwardDiff = "0.10"
49-
Images = "0.23"
48+
Images = "0.24"
5049
IncompleteLU = "0.2.0"
5150
IterTools = "1.3.0"
5251
LsqFit = "0.12"
53-
ModelingToolkit = "3"
54-
OrdinaryDiffEq = "5"
52+
ModelingToolkit = "8"
53+
OrdinaryDiffEq = "6"
5554
Parameters = "0.12"
55+
PreallocationTools = "0"
5656
PyCall = "1"
5757
PyPlot = "2"
5858
QuartzImageIO = "0.7"
5959
RecursiveArrayTools = "2.17"
6060
ReverseDiff = "1.9"
61-
SmoothingSplines = "0.2.1"
62-
SpecialFunctions = "0.10"
63-
StaticArrays = "0.12"
61+
SciMLBase = "1"
62+
SmoothingSplines = "0.3"
63+
SpecialFunctions = "1"
64+
StaticArrays = "1"
6465
Sundials = "4"
66+
Symbolics = "4"
6567
Tracker = "0.2"
6668
Unitful = "1.3.0"
6769
YAML = "0.4"

iJulia/Quasi-Steady State Assumptions and Isomer Lumping.ipynb

Lines changed: 274 additions & 0 deletions
Large diffs are not rendered by default.

src/Domain.jl

Lines changed: 35 additions & 35 deletions
Large diffs are not rendered by default.

src/EdgeAnalysis.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Tools for model edge analysis for automatic mechanism generation
55
using Logging
66
using Sundials
77
using SparseArrays
8-
using DiffEqBase: build_solution
8+
using SciMLBase: build_solution
99
using Base.Iterators: flatten
1010

1111
abstract type AbstractTerminationCriterion end
@@ -76,7 +76,7 @@ Calculate key flux and concentration related quantities for edge analysis
7676
@inline function calcfluxes(sim::Simulation)
7777
t = sim.sol.t[end]
7878
dydt = zeros(sim.domain.indexes[end])
79-
ns,cs,T,P,V,C,N,mu,kfs,krevs,Hs,Us,Gs,diffs,Cvave,cpdivR,phi = calcthermo(sim.domain,sim.sol.u[end],sim.sol.t[end],DiffEqBase.NullParameters())
79+
ns,cs,T,P,V,C,N,mu,kfs,krevs,Hs,Us,Gs,diffs,Cvave,cpdivR,phi = calcthermo(sim.domain,sim.sol.u[end],sim.sol.t[end],SciMLBase.NullParameters())
8080
rts,frts,rrts = addreactionratecontributionsforwardreverse!(dydt,sim.domain.rxnarray,cs,kfs,krevs,V)
8181
calcdomainderivatives!(sim.domain,dydt,[];t=t,T=T,P=P,Us=Us,Hs=Hs,V=V,C=C,ns=ns,N=N,Cvave=Cvave)
8282
return dydt,rts,frts,rrts,cs
@@ -94,7 +94,7 @@ Calculate key flux and concentration related quantities for edge analysis
9494
domains = [sim.domain for sim in ssys.sims]
9595
interfaces = ssys.interfaces
9696
domain = domains[1]
97-
ns,cs,T,P,V,C,N,mu,kfs,krevs,Hs,Us,Gs,diffs,Cvave,cpdivR,phi = calcthermo(domain,y,t,DiffEqBase.NullParameters())
97+
ns,cs,T,P,V,C,N,mu,kfs,krevs,Hs,Us,Gs,diffs,Cvave,cpdivR,phi = calcthermo(domain,y,t,SciMLBase.NullParameters())
9898
vns = Array{Any,1}(undef,length(domains))
9999
vns[1] = ns
100100
vcs = Array{Any,1}(undef,length(domains))
@@ -136,7 +136,7 @@ Calculate key flux and concentration related quantities for edge analysis
136136
rrtsall = [rrts]
137137
for (i,domain) in enumerate(@views domains[2:end])
138138
k = i + 1
139-
vns[k],vcs[k],vT[k],vP[k],vV[k],vC[k],vN[k],vmu[k],vkfs[k],vkrevs[k],vHs[k],vUs[k],vGs[k],vdiffs[k],vCvave[k],vcpdivR[k],vphi[k] = calcthermo(domain,y,t,DiffEqBase.NullParameters())
139+
vns[k],vcs[k],vT[k],vP[k],vV[k],vC[k],vN[k],vmu[k],vkfs[k],vkrevs[k],vHs[k],vUs[k],vGs[k],vdiffs[k],vCvave[k],vcpdivR[k],vphi[k] = calcthermo(domain,y,t,SciMLBase.NullParameters())
140140
cstot[domain.indexes[1]:domain.indexes[2]] .= vcs[k]
141141
rts,frts,rrts = addreactionratecontributionsforwardreverse!(dydt,domain.rxnarray,cstot,vkfs[k],vkrevs[k],vV[k])
142142
push!(rtsall,rts)

src/Interface.jl

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using LinearAlgebra
2-
using DiffEqBase
2+
using SciMLBase
33
using LsqFit
44

55
abstract type AbstractInterface end
@@ -54,7 +54,7 @@ function getkfskrevs(ri::ReactiveInternalInterface,T1,T2,phi1,phi2,Gs1,Gs2,cstot
5454
return kfs,krevs
5555
end
5656

57-
function evaluate(ri::ReactiveInternalInterface,dydt,domains,T1,T2,phi1,phi2,Gs1,Gs2,cstot,p::W) where {W<:DiffEqBase.NullParameters}
57+
function evaluate(ri::ReactiveInternalInterface,dydt,domains,T1,T2,phi1,phi2,Gs1,Gs2,cstot,p::W) where {W<:SciMLBase.NullParameters}
5858
kfs,krevs = getkfskrevs(ri,T1,T2,phi1,phi2,Gs1,Gs2,cstot)
5959
addreactionratecontributions!(dydt,ri.rxnarray,cstot,kfs,krevs,ri.A)
6060
end
@@ -108,7 +108,7 @@ function getkfskrevs(ri::ReactiveInternalInterfaceConstantTPhi,T1,T2,phi1,phi2,G
108108
return ri.kfs,ri.krevs
109109
end
110110

111-
function evaluate(ri::ReactiveInternalInterfaceConstantTPhi,dydt,domains,T1,T2,phi1,phi2,Gs1,Gs2,cstot,p::W) where {W<:DiffEqBase.NullParameters}
111+
function evaluate(ri::ReactiveInternalInterfaceConstantTPhi,dydt,domains,T1,T2,phi1,phi2,Gs1,Gs2,cstot,p::W) where {W<:SciMLBase.NullParameters}
112112
addreactionratecontributions!(dydt,ri.rxnarray,cstot,ri.kfs,ri.krevs,ri.A)
113113
end
114114

@@ -237,11 +237,25 @@ struct Inlet{Q<:Real,S,V<:AbstractArray,U<:Real,X<:Real,FF<:Function} <: Abstrac
237237
end
238238

239239
function Inlet(domain::V,conddict::Dict{X1,X},F::FF) where {V,X1,X,B<:Real,FF<:Function}
240+
T = 0.0
241+
P = 0.0
242+
240243
y = makespcsvector(domain.phase,conddict)
241-
T = conddict["T"]
242-
P = conddict["P"]
243244
yout = y./sum(y)
244-
H = dot(getEnthalpy.(getfield.(domain.phase.species,:thermo),T),yout)
245+
246+
if haskey(conddict,"T")
247+
T = conddict["T"]
248+
end
249+
if haskey(conddict,"P")
250+
P = conddict["P"]
251+
end
252+
253+
if haskey(conddict,"Hin")
254+
H = conddict["Hin"]
255+
else
256+
@assert T != 0.0 && P != 0.0 "T and P need to be provided if Hin is not provided for Inlet"
257+
H = dot(getEnthalpy.(getfield.(domain.phase.species,:thermo),T),yout)
258+
end
245259
return Inlet(domain,yout,F,T,P,H)
246260
end
247261

src/ModelReduction.jl

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using Symbolics
2+
using ModelingToolkit
3+
4+
struct ReducedModelMappings{F<:Function}
5+
qssindexes::Array{Int64,1}
6+
lumpedindexes::Array{Int64,1}
7+
reducedindexes::Array{Int64,1}
8+
lumpedgroupmapping::Array{Dict{Int64,Float64},1}
9+
qssc!::F
10+
end
11+
12+
export ReducedModelMappings
13+
14+
function getmapping(phase::T,qssnames::A1,isomergroups::A2) where {T<:AbstractPhase,A1<:AbstractArray,A2<:AbstractArray}
15+
16+
spcnames = getfield.(phase.species,:name)
17+
qssindexes = [index for (index, name) in enumerate(spcnames) if name in qssnames]
18+
19+
lumpedgroupmapping = [Dict{Int64,Float64}() for group in isomergroups]
20+
lumpedindexes = Array{Int64,1}()
21+
for (i,group) in enumerate(isomergroups)
22+
for (spcname, weight) in group
23+
index = findfirst(x->x==spcname, spcnames)
24+
lumpedgroupmapping[i][index] = weight
25+
push!(lumpedindexes,index)
26+
end
27+
end
28+
reducedindexes = [index for index in 1:length(spcnames) if !(index in qssindexes) && !(index in lumpedindexes)]
29+
return qssindexes,lumpedindexes,reducedindexes,lumpedgroupmapping
30+
end
31+
export getmapping
32+
33+
function generateqsscmapping(phase::T,qssnames::A1,isomergroups::A2;saveqssc::Bool=false,outputname::String="qssc.jl") where {T<:AbstractPhase,A1<:AbstractArray,A2<:AbstractArray}
34+
35+
qssindexes,lumpedindexes,reducedindexes,lumpedgroupmapping = getmapping(phase, qssnames, isomergroups)
36+
37+
# initialize symbolic variables
38+
@variables symdc[1:length(phase.species)] symc[1:length(phase.species)] symkf[1:length(phase.reactions)] symkrev[1:length(phase.reactions)] symV
39+
40+
# collect them so that we can use setindex
41+
symdc = collect(symdc)
42+
symc = collect(symc)
43+
44+
# setting the derivatives to 0
45+
symdc .= Num(0);
46+
47+
# fill in the symbolic expression for derivatives
48+
addreactionratecontributions!(symdc,phase.rxnarray,symc,symkf,symkrev)
49+
50+
# sett the derivatives to 0 for QSS species
51+
eqs = 0 .~ symdc[qssindexes];
52+
53+
# equate the quadratic nonlinear terms x^2 to 0
54+
nonlinearterms = Dict([])
55+
for i in symc[qssindexes]
56+
nonlinearterms[i*i]=0 #x^2=>0
57+
end
58+
59+
# ideally we would use the following to filter out both x^2 and x*y cases where x, y ∈ symc[qssindexes]
60+
# But current SymbolicUtils doesn't substitute x*y in k*x*y
61+
# nonlinearterms = Dict([])
62+
# for (i,j) in Iterators.product(symc[qssindexes], symc[qssindexes])
63+
# nonlinearterms[i*j]=0.0 #x^2=>0
64+
# end
65+
66+
# a workaround to deal with x*y nonlinear terms, where x, y ∈ symc[qssindexes]
67+
for ind in 1:length(eqs)
68+
if !(Symbolics.linear_expansion([eqs[ind]],symc[qssindexes])[3])
69+
eqs[ind] = 0~substitute(eqs[ind].rhs,nonlinearterms) #substitute x^2 nonlinear terms as 0
70+
filter!(x->length(get_variables(x.first,symc[qssindexes])) < 2,eqs[ind].rhs.dict) #filter out all the terms in the format of k*a*b that doesn't contain more than 1 variables in symc[qssindexes]
71+
eqs[ind].rhs.sorted_args_cache[] = nothing #remove cached arguments
72+
@assert Symbolics.linear_expansion([eqs[ind]],symc[qssindexes])[3] == true #assert the equations are all linear with respect to the QSS species
73+
end
74+
end
75+
76+
qsscsolved = Symbolics.expand(Symbolics.solve_for(eqs,symc[qssindexes];simplify=false)) #solve for the expression of QSS species
77+
78+
symqssc! = build_function(qsscsolved, symc, symkf, symkrev)[2] #convert the symbolic expression to a function
79+
80+
if saveqssc
81+
write(outputname,string(symqssc!)); #save the function to file for future use
82+
end
83+
84+
return ReducedModelMappings(qssindexes,lumpedindexes,reducedindexes,lumpedgroupmapping,eval(symqssc!))
85+
86+
end
87+
export generateqsscmapping
88+

src/PhaseState.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ function makespcsvector(phase,spcdict)
3737
continue
3838
elseif key == "V"
3939
continue
40+
elseif key == "Hin"
41+
continue
4042
else
4143
ind = findfirst(isequal(key),spnames)
4244
@assert typeof(ind)<: Integer "$key not found in species list: $spnames"

src/ReactionMechanismSimulator.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ module ReactionMechanismSimulator
5353
include("Domain.jl")
5454
include("yml.jl")
5555
include("Parse.jl")
56+
include("ModelReduction.jl")
5657
include("Reactor.jl")
5758
include("Simulation.jl")
5859
include("TransitorySensitivities.jl")

0 commit comments

Comments
 (0)