Skip to content

Commit ba1a4fb

Browse files
authored
Merge pull request #149 from ReactionMechanismGenerator/edge_analysis_improvements
Edge analysis improvements
2 parents 0e05b6b + 1a944d9 commit ba1a4fb

File tree

6 files changed

+83
-59
lines changed

6 files changed

+83
-59
lines changed

src/Domain.jl

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,8 @@ function ConstantTPDomain(;phase::E2,initialconds::Dict{X,X2},constantspecies::A
9696
jacobian=zeros(typeof(T),length(phase.species),length(phase.species))
9797
end
9898
rxnarray = getreactionindices(phase)
99-
return ConstantTPDomain(phase,[phase.species[1].index,phase.species[end].index,phase.species[end].index+1],[1,length(phase.species)+length(phase.reactions)],constspcinds,
100-
T,P,kfs,krevs,efficiencyinds,Gs,rxnarray,mu,diffs,jacobian,sensitivity,false,MVector(false),MVector(0.0),p, Dict("V"=>phase.species[end].index+1)), y0, p
99+
return ConstantTPDomain(phase,[1,length(phase.species),length(phase.species)+1],[1,length(phase.species)+length(phase.reactions)],constspcinds,
100+
T,P,kfs,krevs,efficiencyinds,Gs,rxnarray,mu,diffs,jacobian,sensitivity,false,MVector(false),MVector(0.0),p, Dict("V"=>length(phase.species)+1)), y0, p
101101
end
102102
export ConstantTPDomain
103103

@@ -164,8 +164,8 @@ function ConstantVDomain(;phase::Z,initialconds::Dict{X,E},constantspecies::Arra
164164
jacobian=zeros(typeof(T),length(phase.species)+2,length(phase.species)+2)
165165
end
166166
rxnarray = getreactionindices(phase)
167-
return ConstantVDomain(phase,[phase.species[1].index,phase.species[end].index,phase.species[end].index+1,phase.species[end].index+2],[1,length(phase.species)+length(phase.reactions)],constspcinds,
168-
V,efficiencyinds,rxnarray,jacobian,sensitivity,MVector(false),MVector(0.0),p,Dict("T"=>phase.species[end].index+1,"P"=>phase.species[end].index+2)), y0, p
167+
return ConstantVDomain(phase,[1,length(phase.species),length(phase.species)+1,length(phase.species)+2],[1,length(phase.species)+length(phase.reactions)],constspcinds,
168+
V,efficiencyinds,rxnarray,jacobian,sensitivity,MVector(false),MVector(0.0),p,Dict("T"=>length(phase.species)+1,"P"=>length(phase.species)+2)), y0, p
169169
end
170170
export ConstantVDomain
171171

@@ -232,8 +232,8 @@ function ConstantPDomain(;phase::Z,initialconds::Dict{X,E},constantspecies::Arra
232232
jacobian=zeros(typeof(T),length(phase.species)+2,length(phase.species)+2)
233233
end
234234
rxnarray = getreactionindices(phase)
235-
return ConstantPDomain(phase,[phase.species[1].index,phase.species[end].index,phase.species[end].index+1,phase.species[end].index+2],[1,length(phase.species)+length(phase.reactions)],constspcinds,
236-
P,efficiencyinds,rxnarray,jacobian,sensitivity,MVector(false),MVector(0.0),p,Dict("T"=>phase.species[end].index+1,"V"=>phase.species[end].index+2)), y0, p
235+
return ConstantPDomain(phase,[1,length(phase.species),length(phase.species)+1,length(phase.species)+2],[1,length(phase.species)+length(phase.reactions)],constspcinds,
236+
P,efficiencyinds,rxnarray,jacobian,sensitivity,MVector(false),MVector(0.0),p,Dict("T"=>length(phase.species)+1,"V"=>length(phase.species)+2)), y0, p
237237
end
238238
export ConstantPDomain
239239

@@ -297,8 +297,8 @@ function ParametrizedTPDomain(;phase::Z,initialconds::Dict{X,Any},constantspecie
297297
N = sum(ns)
298298
V = N*R*Tfcn(0.0)/Pfcn(0.0)
299299
y0 = zeros(length(phase.species)+1)
300-
y0[phase.species[1].index:phase.species[end].index] = ns
301-
y0[phase.species[end].index+1] = V
300+
y0[1:length(phase.species)] = ns
301+
y0[length(phase.species)+1] = V
302302
p = vcat(zeros(length(phase.species)),ones(length(phase.reactions)))
303303
if length(constantspecies) > 0
304304
spcnames = getfield.(phase.species,:name)
@@ -313,8 +313,8 @@ function ParametrizedTPDomain(;phase::Z,initialconds::Dict{X,Any},constantspecie
313313
jacobian=zeros(typeof(V),length(phase.species)+1,length(phase.species)+1)
314314
end
315315
rxnarray = getreactionindices(phase)
316-
return ParametrizedTPDomain(phase,[phase.species[1].index,phase.species[end].index,phase.species[end].index+1],[1,length(phase.species)+length(phase.reactions)],constspcinds,
317-
Tfcn,Pfcn,efficiencyinds,rxnarray,jacobian,sensitivity,MVector(false),MVector(0.0),p,Dict("V"=>phase.species[end].index+1)), y0, p
316+
return ParametrizedTPDomain(phase,[1,length(phase.species),length(phase.species)+1],[1,length(phase.species)+length(phase.reactions)],constspcinds,
317+
Tfcn,Pfcn,efficiencyinds,rxnarray,jacobian,sensitivity,MVector(false),MVector(0.0),p,Dict("V"=>length(phase.species)+1)), y0, p
318318
end
319319
export ParametrizedTPDomain
320320

@@ -390,8 +390,8 @@ function ParametrizedVDomain(;phase::Z,initialconds::Dict{X,Any},constantspecies
390390
jacobian=zeros(typeof(T),length(phase.species)+2,length(phase.species)+2)
391391
end
392392
rxnarray = getreactionindices(phase)
393-
return ParametrizedVDomain(phase,[phase.species[1].index,phase.species[end].index,phase.species[end].index+1,phase.species[end].index+2],[1,length(phase.species)+length(phase.reactions)],constspcinds,
394-
Vfcn,efficiencyinds,rxnarray,jacobian,sensitivity,MVector(false),MVector(0.0),p,Dict("T"=>phase.species[end].index+1,"P"=>phase.species[end].index+2)), y0, p
393+
return ParametrizedVDomain(phase,[1,length(phase.species),length(phase.species)+1,length(phase.species)+2],[1,length(phase.species)+length(phase.reactions)],constspcinds,
394+
Vfcn,efficiencyinds,rxnarray,jacobian,sensitivity,MVector(false),MVector(0.0),p,Dict("T"=>length(phase.species)+1,"P"=>length(phase.species)+2)), y0, p
395395
end
396396
export ParametrizedVDomain
397397

@@ -467,8 +467,8 @@ function ParametrizedPDomain(;phase::Z,initialconds::Dict{X,Any},constantspecies
467467
jacobian=zeros(typeof(T),length(phase.species)+2,length(phase.species)+2)
468468
end
469469
rxnarray = getreactionindices(phase)
470-
return ParametrizedPDomain(phase,[phase.species[1].index,phase.species[end].index,phase.species[end].index+1,phase.species[end].index+2],[1,length(phase.species)+length(phase.reactions)],constspcinds,
471-
Pfcn,efficiencyinds,rxnarray,jacobian,sensitivity,MVector(false),MVector(0.0),p,Dict("T"=>phase.species[end].index+1,"V"=>phase.species[end].index+2)), y0, p
470+
return ParametrizedPDomain(phase,[1,length(phase.species),length(phase.species)+1,length(phase.species)+2],[1,length(phase.species)+length(phase.reactions)],constspcinds,
471+
Pfcn,efficiencyinds,rxnarray,jacobian,sensitivity,MVector(false),MVector(0.0),p,Dict("T"=>length(phase.species)+1,"V"=>length(phase.species)+2)), y0, p
472472
end
473473
export ParametrizedPDomain
474474

@@ -550,7 +550,7 @@ function ConstantTVDomain(;phase::Z,initialconds::Dict{X,E},constantspecies::Arr
550550
jacobian=zeros(typeof(T),length(phase.species),length(phase.species))
551551
end
552552
rxnarray = getreactionindices(phase)
553-
return ConstantTVDomain(phase,[phase.species[1].index,phase.species[end].index],[1,length(phase.species)+length(phase.reactions)],constspcinds,
553+
return ConstantTVDomain(phase,[1,length(phase.species)],[1,length(phase.species)+length(phase.reactions)],constspcinds,
554554
T,V,kfs,krevs,kfsnondiff,efficiencyinds,Gs,rxnarray,mu,diffs,jacobian,sensitivity,false,MVector(false),MVector(0.0),p,Dict{String,Int64}()), y0, p
555555
end
556556
export ConstantTVDomain
@@ -605,7 +605,7 @@ function ParametrizedTConstantVDomain(;phase::IdealDiluteSolution,initialconds::
605605
end
606606
N = sum(ns)
607607
y0 = zeros(length(phase.species))
608-
y0[phase.species[1].index:phase.species[end].index] = ns
608+
y0[1:length(phase.species)] = ns
609609
p = vcat(zeros(length(phase.species)),ones(length(phase.reactions)))
610610
if length(constantspecies) > 0
611611
spcnames = getfield.(phase.species,:name)
@@ -620,7 +620,7 @@ function ParametrizedTConstantVDomain(;phase::IdealDiluteSolution,initialconds::
620620
jacobian=zeros(typeof(V),length(phase.species)+1,length(phase.species)+1)
621621
end
622622
rxnarray = getreactionindices(phase)
623-
return ParametrizedTConstantVDomain(phase,[phase.species[1].index,phase.species[end].index],[1,length(phase.species)+length(phase.reactions)],constspcinds,
623+
return ParametrizedTConstantVDomain(phase,[1,length(phase.species)],[1,length(phase.species)+length(phase.reactions)],constspcinds,
624624
Tfcn,V,efficiencyinds,rxnarray,jacobian,sensitivity,MVector(false),MVector(0.0),p,Dict{String,Int64}()), y0, p
625625
end
626626
export ParametrizedTConstantVDomain
@@ -697,7 +697,7 @@ function ConstantTAPhiDomain(;phase::E2,initialconds::Dict{X,X2},constantspecies
697697
jacobian=zeros(typeof(T),length(phase.species),length(phase.species))
698698
end
699699
rxnarray = getreactionindices(phase)
700-
return ConstantTAPhiDomain(phase,[phase.species[1].index,phase.species[end].index],[1,length(phase.species)+length(phase.reactions)],constspcinds,
700+
return ConstantTAPhiDomain(phase,[1,length(phase.species)],[1,length(phase.species)+length(phase.reactions)],constspcinds,
701701
T,A,phi,kfs,krevs,efficiencyinds,Gs,rxnarray,mu,Array{Float64,1}(),jacobian,sensitivity,false,MVector(false),MVector(0.0),p,Dict{String,Int64}()), y0, p
702702
end
703703
export ConstantTAPhiDomain

src/EdgeAnalysis.jl

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,7 @@ function identifyobjects!(sim,corespcsinds,corerxninds,edgespcsinds,
482482
numcorerxns = length(corerxninds)
483483
invalidobjectsprintboolean = true
484484
terminated = false
485+
conversion = 0.0
485486

486487
(dydt,rts,frts,rrts,cs,corespeciesratse,charrate,edgespeciesrates,
487488
edgereactionrates,corespeciesrateratios,edgespeciesrateratios,
@@ -596,10 +597,10 @@ function identifyobjects!(sim,corespcsinds,corerxninds,edgespcsinds,
596597
invalidobjectsprintboolean = false
597598
end
598599
num = maxnumobjsperiter - length(invalidobjects)
599-
newobjects = newobjects[:num]
600-
newobjectinds = newobjectinds[:num]
601-
newobjectvals = newobjectvals[:num]
602-
newobjecttype = newobjecttype[:num]
600+
newobjects = newobjects[1:num]
601+
newobjectinds = newobjectinds[1:num]
602+
newobjectvals = newobjectvals[1:num]
603+
newobjecttype = newobjecttype[1:num]
603604
end
604605

605606
if terminateatmaxobjects && length(invalidobjects) + length(newobjects) >= maxnumobjsperiter
@@ -640,7 +641,7 @@ function identifyobjects!(sim,corespcsinds,corerxninds,edgespcsinds,
640641
@info "At time $t sec, reached target termination rate ratio $ratio"
641642
end
642643
else isa(term, TerminationConversion)
643-
index = findfirst(isequal(term.species),sim.species)
644+
index = findfirst(isequal(term.species.name),sim.names)
644645
conversion = 1 - (y[index] / y0[index])
645646
name = sim.species[index].name
646647
if conversion >= term.conversion
@@ -653,15 +654,15 @@ function identifyobjects!(sim,corespcsinds,corerxninds,edgespcsinds,
653654
if terminated
654655
for term in termination
655656
if isa(term, TerminationConversion)
656-
index = findfirst(isequal(term.species),sim.species)
657+
index = findfirst(isequal(term.species.name),sim.names)
657658
conversion = 1 - (y[index] / y0[index])
658659
name = sim.species[index].name
659660
@info "$name conversion: $conversion"
660661
end
661662
end
662663
end
663664

664-
return (terminated,interrupt)
665+
return (terminated,interrupt,conversion)
665666
end
666667

667668
export identifyobjects!
@@ -670,7 +671,7 @@ export identifyobjects!
670671
run edge analysis to determine objects (species/reactions) that should be added to model core
671672
"""
672673
function selectobjects(react,coreedgedomains,coreedgeinters,domains,inters,
673-
p,tolmovetocore,tolinterruptsimulation,ignoreoverallfluxcriterion,filterreactions,
674+
corep,coreedgep,tolmovetocore,tolinterruptsimulation,ignoreoverallfluxcriterion,filterreactions,
674675
maxnumobjsperiter,tolbranchrxntocore,branchingratiomax,
675676
branchingindex,terminateatmaxobjects,termination,
676677
filterthreshold;
@@ -685,6 +686,8 @@ function selectobjects(react,coreedgedomains,coreedgeinters,domains,inters,
685686
maxedgespeciesrateratios = zeros(length(edgespcsinds))
686687
invalidobjects = []
687688
terminated = false
689+
conversion = 0.0
690+
code = :Success
688691

689692
if tolbranchrxntocore != 0.0
690693
branchfactor = 1.0/tolbranchrxntocore
@@ -696,20 +699,21 @@ function selectobjects(react,coreedgedomains,coreedgeinters,domains,inters,
696699
inte = init(react.ode,solver,abstol=atol,reltol=rtol);
697700

698701
t = inte.t
699-
sim = getsim(inte,react,coreedgedomains,inters,p,coretoedgespcmap)
702+
sim = getsim(inte,react,coreedgedomains,inters,corep,coretoedgespcmap)
700703

701704
y0 = sim.sol[end]
702705
spcsaddindices = Array{Int64,1}()
703706
firsttime = true
704707

705708
n = 1
706-
while t < tf
709+
while t < tf && code == :Success
707710
for i = 1:n
708711
step!(inte)
709712
end
713+
code = check_error(inte)
710714
t = inte.t
711-
sim = getsim(inte,react,coreedgedomains,inters,p,coretoedgespcmap)
712-
terminated,interrupt = identifyobjects!(sim,corespcsinds,corerxninds,edgespcsinds,
715+
sim = getsim(inte,react,coreedgedomains,inters,coreedgep,coretoedgespcmap)
716+
terminated,interrupt,conversion = identifyobjects!(sim,corespcsinds,corerxninds,edgespcsinds,
713717
edgerxninds,reactantindices,productindices,unimolecularthreshold,bimolecularthreshold,
714718
trimolecularthreshold,maxedgespeciesrateratios,tolmovetocore,tolinterruptsimulation,ignoreoverallfluxcriterion,filterreactions,
715719
maxnumobjsperiter,branchfactor,branchingratiomax,
@@ -722,11 +726,24 @@ function selectobjects(react,coreedgedomains,coreedgeinters,domains,inters,
722726
break
723727
end
724728
end
725-
return (terminated,invalidobjects,unimolecularthreshold,
726-
bimolecularthreshold,trimolecularthreshold,maxedgespeciesrateratios)
729+
730+
if code == :Success
731+
return (terminated,false,invalidobjects,unimolecularthreshold,
732+
bimolecularthreshold,trimolecularthreshold,maxedgespeciesrateratios,t,conversion)
733+
else
734+
@error "Solver failed with code $code resurrecting job"
735+
dydt,rts,frts,rrts,cs,corespeciesrates,charrate,edgespeciesrates,edgereactionrates,
736+
corespeciesrateratios,edgespeciesrateratios,corereactionrates,corespeciesconcentrations,
737+
corespeciesproductionrates,corespeciesconsumptionrates = processfluxes(sim,corespcsinds,corerxninds,edgespcsinds,edgerxninds)
738+
ind = edgespcsinds[argmax(edgespeciesrates)]
739+
invalidobjects = [sim.species[ind]]
740+
return (terminated,true,invalidobjects,unimolecularthreshold,
741+
bimolecularthreshold,trimolecularthreshold,maxedgespeciesrateratios,t,conversion)
742+
end
743+
727744
end
728745

729-
export selectspecies
746+
export selectobjects
730747

731748
"""
732749
calculate threshold rate constants for different numbers of reactants
@@ -746,4 +763,4 @@ function getthresholdrateconstants(sim::Simulation,phase::IdealDiluteSolution,fi
746763
return 2.08366122e10*T,22.2*T/mu,0.11*T/mu
747764
end
748765

749-
export getthresholdrateeconstant
766+
export getthresholdrateconstants

src/Parse.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ export getatomdictinchi
160160
getatomdictadjlist(adjlist) = getatomdictfromrmg(molecule.Molecule().from_adjacency_list(adjlist))
161161
export getatomdictadjlist
162162

163-
function getspeciesradius(atomdict::Dict{String,Int64},nbonds::Int64)
163+
function getspeciesradius(atomdict,nbonds::Int64)
164164
"""
165165
estimates the McGowan radius by calculating the McGowan Volume
166166
from the number of each type of atom and the number of bonds

src/Phase.jl

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,17 @@ function IdealGas(species,reactions; name="",diffusionlimited=false)
3939
rxns = [ElementaryReaction(index=i,reactants=rxn.reactants,reactantinds=rxn.reactantinds,products=rxn.products,
4040
productinds=rxn.productinds,kinetics=rxn.kinetics,radicalchange=rxn.radicalchange,reversible=rxn.reversible,pairs=rxn.pairs) for (i,rxn) in enumerate(rxns)]
4141
therm = getvecthermo(species)
42-
M,Nrp = getstoichmatrix(species,rxns)
42+
rxnarray = getreactionindices(species,rxns)
43+
M,Nrp = getstoichmatrix(rxnarray,species)
4344
echangevec = getfield.(rxns,:electronchange)
4445
if all(echangevec .== 0)
4546
electronchange = nothing
4647
else
4748
electronchange = convert(echangevec,Array{Float64,1})
4849
end
4950
reversibility = getfield.(rxns,:reversible)
50-
rxnarray = getreactionindices(species,rxns)
5151
return IdealGas(species=species,reactions=rxns,name=name,
52-
spcdict=Dict([sp.name=>sp.index for sp in species]),stoichmatrix=M,Nrp=Nrp,rxnarray=rxnarray,veckinetics=vectuple,
52+
spcdict=Dict([sp.name=>i for (i,sp) in enumerate(species)]),stoichmatrix=M,Nrp=Nrp,rxnarray=rxnarray,veckinetics=vectuple,
5353
veckineticsinds=posinds, vecthermo=therm, otherreactions=otherrxns, electronchange=electronchange,
5454
reversibility=reversibility,diffusionlimited=diffusionlimited,)
5555
end
@@ -79,17 +79,18 @@ function IdealDiluteSolution(species,reactions,solvent; name="",diffusionlimited
7979
rxns = [ElementaryReaction(index=i,reactants=rxn.reactants,reactantinds=rxn.reactantinds,products=rxn.products,
8080
productinds=rxn.productinds,kinetics=rxn.kinetics,radicalchange=rxn.radicalchange,reversible=rxn.reversible,pairs=rxn.pairs) for (i,rxn) in enumerate(rxns)]
8181
therm = getvecthermo(species)
82-
M,Nrp = getstoichmatrix(species,rxns)
82+
rxnarray = getreactionindices(species,rxns)
83+
M,Nrp = getstoichmatrix(rxnarray,species)
8384
echangevec = getfield.(rxns,:electronchange)
8485
if all(echangevec .== 0)
8586
electronchange = nothing
8687
else
8788
electronchange = convert(echangevec,Array{Float64,1})
8889
end
8990
reversibility = getfield.(rxns,:reversible)
90-
rxnarray = getreactionindices(species,rxns)
91+
9192
return IdealDiluteSolution(species=species,reactions=rxns,solvent=solvent,name=name,
92-
spcdict=Dict([sp.name=>sp.index for sp in species]),stoichmatrix=M,Nrp=Nrp,rxnarray=rxnarray,veckinetics=vectuple,
93+
spcdict=Dict([sp.name=>i for (i,sp) in enumerate(species)]),stoichmatrix=M,Nrp=Nrp,rxnarray=rxnarray,veckinetics=vectuple,
9394
veckineticsinds=posinds,vecthermo=therm,otherreactions=otherrxns,electronchange=electronchange,
9495
reversibility=reversibility,diffusionlimited=diffusionlimited)
9596
end
@@ -119,17 +120,17 @@ function IdealSurface(species,reactions,sitedensity;name="",diffusionlimited=fal
119120
rxns = [ElementaryReaction(index=i,reactants=rxn.reactants,reactantinds=rxn.reactantinds,products=rxn.products,
120121
productinds=rxn.productinds,kinetics=rxn.kinetics,radicalchange=rxn.radicalchange,electronchange=rxn.electronchange,reversible=rxn.reversible,pairs=rxn.pairs) for (i,rxn) in enumerate(rxns)]
121122
therm = getvecthermo(species)
122-
M,Nrp = getstoichmatrix(species,rxns)
123+
rxnarray = getreactionindices(species,rxns)
124+
M,Nrp = getstoichmatrix(rxnarray,species)
123125
echangevec = getfield.(rxns,:electronchange).*F
124126
if all(echangevec .== 0)
125127
electronchange = nothing
126128
else
127129
electronchange = convert(typeof(Nrp),echangevec)
128130
end
129131
reversibility = getfield.(rxns,:reversible)
130-
rxnarray = getreactionindices(species,rxns)
131132
return IdealSurface(species=species,reactions=rxns,name=name,
132-
spcdict=Dict([sp.name=>sp.index for sp in species]),stoichmatrix=M,Nrp=Nrp,rxnarray=rxnarray,veckinetics=vectuple,
133+
spcdict=Dict([sp.name=>i for (i,sp) in enumerate(species)]),stoichmatrix=M,Nrp=Nrp,rxnarray=rxnarray,veckinetics=vectuple,
133134
veckineticsinds=posinds,vecthermo=therm,otherreactions=otherrxns,electronchange=electronchange,
134135
reversibility=reversibility,sitedensity=sitedensity,diffusionlimited=diffusionlimited)
135136
end
@@ -138,17 +139,23 @@ export IdealSurface
138139
"""
139140
construct the stochiometric matrix for the reactions and the reaction molecule # change
140141
"""
141-
function getstoichmatrix(spcs,rxns)
142-
M = spzeros(length(rxns),length(spcs))
143-
Nrp = zeros(length(rxns))
144-
for (i,rxn) in enumerate(rxns)
145-
Nrp[i] = Float64(length(rxn.productinds) - length(rxn.reactantinds))
146-
for ind in rxn.reactantinds
147-
M[i,ind] += 1.0
148-
end
149-
for ind in rxn.productinds
150-
M[i,ind] -= 1.0
142+
function getstoichmatrix(rxnarray,spcs)
143+
M = spzeros(size(rxnarray)[2],length(spcs))
144+
Nrp = zeros(size(rxnarray)[2])
145+
for i in 1:size(rxnarray)[2]
146+
n = 0.0
147+
for (j,ind) in enumerate(rxnarray[:,i])
148+
if ind == 0
149+
continue
150+
elseif j > 3
151+
M[i,ind] -= 1.0
152+
n += 1.0
153+
else
154+
M[i,ind] += 1.0
155+
n -= 1.0
156+
end
151157
end
158+
Nrp[i] = n
152159
end
153160
return M,Nrp
154161
end

0 commit comments

Comments
 (0)