#============================================================
#
#   Given a reaction scheme the following Maple procedures determine the
#   system of differential equations, the associated conservation laws,
#   and some of the species that have a zero steady state.
# 
#   This program works for chemical reaction schemes containing unary, 
#   binary or tertiary reactions.
# 
#   The ideas underlying the algorithms used here can be found in the 
#   Maple Technical Newsletter article 'An application of Maple to 
#   chemical kinetics,' by M. Holmes and J. Bell (number 7) and the 
#   article 'The application of symbolic computing to chemical kinetic 
#   reaction schemes,' by the same authors in the Journal of 
#   Computational Chemistry (Dec, 1991).
#
#   Thanks to Jon Bell for helping develop the theory on which the 
#   algorithms are based, to Aditya Nath for his help in developing the 
#   code for odes(), and to Joseph (Web) Stayman for his help in 
#   developing the code for laws() and steady().  Also, thanks to
#   Michael Monagan and Greg Fee for their suggestions.
#  
#		by 
#  
#		Mark H. Holmes
#		Dept of Mathematical Sciences, RPI, Troy, NY  12180
#		holmes@rpi.edu
#   
#		last changed 8/18/92
#
#============================================================

#============================================================
#                on-line help pages
#============================================================
 
`help/text/kinetics` := TEXT(
`FUNCTION: kinetics - procedures for analyzing chemical reaction schemes`,
``,
`SYNOPSIS: - The procedures included in this file are:`,
`  1. odes() - this procedure finds the ODE's from a list of reactions and`,
`              it must be called before the other two procedures`,
`  2. laws() - this procedure finds the independent conservation laws`,
`  3. steady() - procedure for determining some of the species that have a`,
`                zero steady state`,
``,
`COMMENTS:`,
`   1) There are no limitations on the number of reactions (other`,
`      than memory)`,
`   2) The letter  R  is reserved for the list of reactions. Others reserved`,
`      by the program are:`,
`      i) species[1], species[2], species[3], ..., species[nspecies]`,
`             [these are the species in the reactions]`,
`      ii) ode.1, ode.2, ode.3, ..., ode.nspecies`,
`             [these are the ODE's]`,
`      iii) law.1, law.2, law.3, ..., law.nlaws`,
`             [these are the conservation laws]`,
`      iv) nreacts, nspecies, nzeros, nlaws`,
`   3) As a reminder, Maple reserves the symbols E, I, O, and W. Others`,
`      are listed under initially-known functions and initially-known names.`,
`   4) There are certain restrictions on the individual reactions and the`,
`      Maple Newsletter article 'An application of Maple to chemical`,
`      kinetics' by M. Holmes and J. Bell (number 7) should be consulted`,
`      for specifics.`,
``,
`SEE ALSO:  odes, laws, steady`
):

`help/text/odes` := TEXT(
`FUNCTION: odes - procedure for finding the ODE's from a list of reactions`,
``,
`CALLING SEQUENCE:`,
`   odes();`,
``,
`SYNOPSIS: - this procedure finds the ODE's from a list of reactions.`,
`    It does this in four steps:`,
`    1) First determine the species in the reactions; these are denoted as:`,
`         species[i]   for  i = 1, ..., nspecies`,
`    2) Determine the mapping matrix M[nreacts, nspecies]`,
`    3) Determine the overall rate for each reaction.`,
`    4) Determine the ODE for each species. These are denoted as:`,
`         ode.i   for  i = 1, ..., nspecies`,
` -  Individual differential equations can be printed using the command`,
`    print(ode.i);  where  i  is a specified integer (i = 1, 2, ..., nspecies)`,
` -  The ith species can be displayed using the command  print(species[i]);`,
` -  The input for this procedure is simply a list of reactions  R  in`,
`    the form`,
`	           R := [ a + b > c, b > d + w, ...];`,
``,
`EXAMPLES:`,
``,
`R:= [S + E > C, C > S + E, C > P + E];`,
`odes();`,
``,
`R:= [O2+hv>2*Ox, Ox+O2+M>O3+M, O3+hv>O2+Ox, O3+Ox>2*O2];`,
`odes();`,
``,
`SEE ALSO:  kinetics, laws, steady`
): 

`help/text/laws` := TEXT(
`FUNCTION: laws - procedure for finding the conservation laws`,
``,
`CALLING SEQUENCE:`,
`   laws();`,
``,
`SYNOPSIS: - This procedure finds the independent conservation laws of`,
`     a reaction scheme using the results obtained from the procedure odes().`,
`     The number of independent conservation laws is denoted as  nlaws`,
`     and the individual laws are denoted as:`,
`		law.i   for  i = 1, ..., nlaws`,
`  -  Individual conservation laws can be printed using the command`,
`     print(law.i);  where  i = 1, 2, ..., nlaws.`,
``,
`EXAMPLES:`,
``,
`R:= [S + E > C, C > S + E, C > P + E];`,
`odes();`,
`laws();`,
``,
`R:= [O2+hv>2*Ox, Ox+O2+M>O3+M, O3+hv>O2+Ox, O3+Ox>2*O2];`,
`odes();`,
`laws();`,
`print(law.2);`,
``,
`SEE ALSO:  kinetics, odes, steady`
): 

`help/text/steady` := TEXT(
`FUNCTION: steady - procedure for finding species with a zero steady state`,
``,
`CALLING SEQUENCE:`,
`   steady();`,
``,
`SYNOPSIS:`,
`   -  This determines some of the species which have a zero steady state.`,
`      The number of such species is denoted as  nzeros  and the species are`,
`  	     zeros[i]   for  i = 1, ..., nzeros`,
`   -  Individual species can be printed using the command`,
`      print(zeros[i]);  where  i = 1, 2, ..., nzeros.`,
`   -  This procedure uses the results of the procedure odes()`,
``,
`EXAMPLES:`,
``,
`R:= [S + E > C, C > S + E, C > P + E];`,
`odes();`,
`steady();`,
`print(zeros[1]);`,
``,
`R:= [O2+hv>2*Ox, Ox+O2+M>O3+M, O3+hv>O2+Ox, O3+Ox>2*O2];`,
`odes();`,
`steady();`,
``,
`SEE ALSO:  kinetics, odes, laws`
): 

#============================================================
#                procedure odes
#============================================================

macro( var=`kinetics/var`, reduce=`kinetics/reduce`,
       Mentry=`kinetics/Mentry`  );

odes:=proc()
local i,j,k,l,ll,ii,jj,fnd,vc,num,part,pp,r,rr,sparse,t,
	M,kappa0,kappa1,kappa2,kappa3,kappa4,kappa5:
global nreacts, species, nspecies, eqU_subs;
options `Copyright 1992 by Mark H. Holmes`;

#
# STEP 1:  Determine the variables (i.e., the species in the reaction scheme)
#
nreacts:=nops(R):

# Determine species[1] from the first reaction.
species:='species':
nspecies:=1:
l:=op(2,R[1]):
if nops(l)=1 then
    species[1]:=l 
elif nops(l)=2 then
    ll:=op(1,l):
    if  (ll=2) or (ll=3)  then
        species[1]:=op(2,l)  
    elif nops(ll)=2 then
        species[1]:=op(2,ll)
    else
        species[1]:=ll
    fi:
elif nops(l)=3 then
    species[1]:=op(1,l)
fi:
#
# Check the LHS & RHS for new variables.
# For every reaction, each of the reactants and products are
# singled out and are sent to procedure  var()  to update the
# list of variables.
#
# loop over each of the reactions and loop over each side of a reaction
#
for i from 1 to nreacts do
    for pp from 1 to 2 do
        l:=op(pp,R[i]):
        if nops(l)=1 then
            var(l)  
        elif nops(l)=2 then
            ll:=op(1,l): 
            if (ll=2) or (ll=3) then
                var(op(2,l))
            else
                for part from 1 to 2 do
                    ll:=op(part,l):
                    if nops(ll)=2 then
                        var(op(2,ll))
                    else
                        var(op(1,ll))
                    fi:
                od:
            fi:
        elif nops(l)=3 then
            for k to 3 do
                var(op(k,l))
            od:
        fi:
    od:
od:

# STEP 2:  Construct the Mapping Matrix  M[nreacts, nspecies]
# Determine which species are on the LHS of the reactions
# (i.e., the reactants) and temporarily fill the  M  matrix with the
# coefficient corresponding to that species.

M:=array(sparse,1..nreacts,1..nspecies):
for i from 1 to nreacts do
    l:=op(2,R[i]):
    if nops(l)=1 then
        for j to nspecies do
            if l=species[j] then break fi 
        od:
        M[i,j]:=-1:
    elif nops(l)=2 then
        ll:=op(1,l):
        if (ll=2) or (ll=3) then
            for j to nspecies do
                if op(2,l)=species[j] then break fi
            od:
            M[i,j]:=-1*ll:
        else
            for part from 1 to 2 do
                ll:=op(part,l):
                if nops(ll)=2 then
                    for j to nspecies do
                        if op(2,ll)=species[j] then break fi
                    od:
                    M[i,j]:=-2:
                else
                    for j to nspecies do
                        if ll=species[j] then break fi
                    od:
                    M[i,j]:=-1:
                fi:
            od:
        fi:
    else
        for k to 3 do
            for j to nspecies do
                if op(k,l)=species[j] then break fi
            od:
            M[i,j]:=-1:
        od:
    fi:
od:

# Check the variables on the RHS of reactions (the products) and
# fill up the  M  matrix keeping the entries due to the reactants
# in consideration. The procedure  'Mentry'  is used to place the
# appropriate value in the  M  matrix.

for i from 1 to nreacts do
    r:=op(1,R[i]):
    if nops(r)=1 then
        for j to nspecies do
            if r=species[j] then break fi:
        od:
        Mentry(1,i,j,M,kappa0,kappa1,kappa2,kappa3,kappa4,kappa5):
    elif nops(r)=2 then
        rr:=op(1,r):
        if (rr=2) or (rr=3) then
            for j to nspecies do
                if op(2,r)=species[j] then break fi
            od:
            if rr=2 then
                    Mentry(2,i,j,M,kappa0,kappa1,kappa2,kappa3,kappa4,kappa5)
                else
                    Mentry(3,i,j,M,kappa0,kappa1,kappa2,kappa3,kappa4,kappa5)
            fi:
        else 
            for part from 1 to 2 do
                rr:=op(part,r):
                if nops(rr)=2 then 
                    for j to nspecies do
                        if op(2,rr)=species[j] then break fi
                    od:
                    Mentry(2,i,j,M,kappa0,kappa1,kappa2,kappa3,kappa4,kappa5):
                else
                    for j to nspecies do
                        if rr=species[j] then break fi
                    od:
                    Mentry(1,i,j,M,kappa0,kappa1,kappa2,kappa3,kappa4,kappa5):
                fi:
            od:
        fi:
    elif nops(r)=3 then
        for k to 3 do
            for j to nspecies do
                if op(k,r)=species[j] then break fi
            od:
            Mentry(1,i,j,M,kappa0,kappa1,kappa2,kappa3,kappa4,kappa5):
        od:
    fi:
od:
#
# STEP 3:  Determine the rate of each reaction;
# the rate constant for the ith reaction is:  k.i 
#
for i from 1 to nreacts do
    r.i:=k.i:
    for j from 1 to nspecies do
        if M[i,j]=-1 or M[i,j]=kappa0 or
            M[i,j]=kappa1 or M[i,j]=kappa2 then
                r.i:=r.i * species[j]
        elif M[i,j]=-2 or M[i,j]=-kappa1 or
            M[i,j]=kappa4 or M[i,j]=kappa3 then
                r.i:=r.i * (species[j]**2)
        elif M[i,j]=-3 or M[i,j]=-kappa2 or
            M[i,j]=-kappa3 or M[i,j]=kappa5 then
                r.i:=r.i * (species[j]**3)
        fi:
    od:
od:

# STEP 3.5: Express each RHS as a linear expression (to be used
# by "laws"). This is done by denoting each group of species as
# u.num . To do this the different groups must be identified and
# this is done below. In Step 4 the linear equations are 
# constructed. 

num:=1:
vc[1]:=num:
for ii from 1 to nreacts do
    fnd:=0:
    for jj from 1 to ii-1 do
        if (r.ii/k.ii=r.jj/k.jj)and(fnd=0) then
            vc[ii]:=vc[jj]: fnd:=1:
        fi:
    od:
    if fnd=0 then
        vc[ii]:=num:
        num:=num+1:
    fi:
od: 


# STEP 4: Determine the ODE for Each Species
# The ODE for the species is computed based upon the entries of the
# mapping matrix and the rate of the reactions.
#
# loop over all the species and over all the reactions

eqU_subs:='eqU_subs':
for j from 1 to nspecies do
    rhs.j:=0: eqU_subs[j]:=0:
    for i from 1 to nreacts do
        if M[i,j]=-1 or M[i,j]=-kappa1
            or M[i,j]=-kappa3 then
                rhs.j:= rhs.j - r.i: 
                eqU_subs[j]:=eqU_subs[j]-(u.(vc[i]))*k.i: 
        elif M[i,j]=-2 or M[i,j]=-kappa2  then
                rhs.j:= rhs.j - r.i*2:
                eqU_subs[j]:=eqU_subs[j]-(u.(vc[i]))*k.i*2:
        elif M[i,j]=1 or M[i,j]=kappa1
            or M[i,j]=kappa3 then
                rhs.j:= rhs.j + r.i:
                eqU_subs[j]:=eqU_subs[j]+(u.(vc[i]))*k.i:
        elif M[i,j]=2 or M[i,j]=kappa2 then
            rhs.j:= rhs.j + r.i*2:
            eqU_subs[j]:=eqU_subs[j]+(u.(vc[i]))*k.i*2:
        elif M[i,j]=3 then
            rhs.j:= rhs.j + r.i*3:
            eqU_subs[j]:=eqU_subs[j]+(u.(vc[i]))*k.i*3:
        elif M[i,j]=-3 then
            rhs.j:= rhs.j - r.i*3:
            eqU_subs[j]:=eqU_subs[j]-(u.(vc[i]))*k.i*3: 
        else 
            rhs.j:= rhs.j + 0: 
            eqU_subs[j]:=eqU_subs[j]+0: 
        fi:
    od:
od:
#
for i from 1 to nspecies do
    ode.i:=diff(species[i](t),t)=rhs.i:
od:
#
# print out the differential equations and the CPU time taken
# to compute the equations

if printlevel > 0 then
print();
lprint(`The differential equations are:`);
print();
for i from 1 to nspecies do
    print(ode.i)
od:
print();
fi;
#
# end of procedure odes()
#
end:
 
# The input to this procedure is a species name.
# The existing list of species names is checked and if the species
# does not appear in the list then it is added on.

var:=proc(side)
local it,j:
global nspecies, species;
options `Copyright 1992 by Mark H. Holmes`;
it:=0:
for j to nspecies do
    if side=species[j] then it:=1 fi
od:
if it=0 then
    nspecies:=nspecies + 1:
    species[nspecies]:=side:
fi:
end:

# Procedure 'Mentry'  determines the entries of the mapping matrix.
# Before this procedure is called, the mapping matrix has entries
# corresponding to the LHS of the reactions only.  Based upon these
# entries and the species of the RHS, new values are entered into the
# mapping matrix.
# The input parameters to this procedure are the position of the
# mapping matrix entry (i and j) and the coefficient of the 
# species on RHS.

Mentry:=proc(x,i,j,M,kappa0,kappa1,kappa2,kappa3,kappa4,kappa5)
options `Copyright 1992 by Mark H. Holmes`;
if x=1 then
    if M[i,j]=-1 then M[i,j]:=kappa0
    elif M[i,j]=-2 then M[i,j]:=-kappa1
    elif M[i,j]=-3 then M[i,j]:=-kappa2
    else M[i,j]:=M[i,j] + x
    fi:
elif x=2 then
    if M[i,j]=-1 then M[i,j]:=kappa1
    elif M[i,j]=-2 then M[i,j]:=kappa4
    elif M[i,j]=-3 then M[i,j]:=-kappa3
    else M[i,j]:=M[i,j] + x
    fi:
elif x=3 then
    if M[i,j]=-1 then M[i,j]:=kappa2
    elif M[i,j]=-2 then M[i,j]:=kappa3
    elif M[i,j]=-3 then M[i,j]:=kappa5
    else M[i,j]:=M[i,j] + x
    fi:
fi:
end:
 
#============================================================
#             procedure laws
#============================================================

laws:=proc()
local num,X,eqt,cof,i,varams,variables,vars:
with(linalg):

# Create the variables u1, u2, ..., u.nreacts for use in genmatrix()

for num from 1 to nreacts do
   vars[num]:=u.num:
od:

# Generate a matrix from the linear equations from odes().
# Find the kernal of the matrix and use the vectors found to
# produce the conservation laws.

eqt:=convert(eqU_subs,list):
variables:=convert(vars,list):
X:=transpose(genmatrix(eqt,variables)): 
cof:=kernel(X,'nlaws'):
varams:=convert(species,list):

# Generate a report on what conservation laws were found and
# assign conservation laws to law.i, i=1..nlaws

print();
if nlaws=0 then
  lprint(`    There are no conservation laws.`);
  print();
else
  lprint(`    The conservation laws are:`);
  print();
fi:
for i from 1 to nlaws do
  law.i:=numer(simplify(dotprod(cof[i],varams))):
  lprint(`   `,i*1.,` constant =`,law.i);
od:

end:
 

#============================================================
#             procedure steady
#============================================================

steady:=proc()
local i,ii,j,jj,k,chk,sp,nrhs,nzerosp,zerospos,num:
global nzeros,zeros;

# Create a new set of equations to manipulate.

for jj from 1 to nspecies do
  nrhs.jj:=rhs.jj:
od:
nzerosp:=-1:  nzeros:=0:

# Find zero steady states by testing if there is a single 
# species in an equation.  Then replace these species with
# zero values.  Repeat until no more species are found.

while nzeros>nzerosp do
  for i from 1 to nspecies do
    sp:=0: chk:=0:
    for j from 1 to nspecies do
      if member(species[j],indets(nrhs.i))=true then
        sp:=j:  chk:=chk+1:
      fi:
    od:
    if chk=1 then
      zerospos[sp]:=0:
    fi:
  od:
  nzerosp:=nzeros:
  for ii from 1 to nspecies do
    nzeros:=0:
    for j from 1 to nspecies do
      if zerospos[j]=0 then
        nrhs.ii:=subs(species[j]=0,nrhs.ii):
        nzeros:=nzeros+1:
      fi:
    od:
  od:
od:

# Generate a report on what zeros were found and create the
# array zeros[] to hold the zero steady states.

num:=1:
print();
if nzeros=0 then
  lprint(`    No species were found to have a zero steady state.`): 
else
  lprint(`    There are`,nzeros,`species found to have a zero steady state`):
  lprint(`    They are:`):
  for k from 1 to nspecies do
    if zerospos[k]=0 then
      zeros[num]:=species[k]:  num:=num+1:
	lprint(`           `,species[k]):
    fi:
  od:
fi:
print();
end:

macro( var=var, reduce=reduce, Mentry=Mentry );
#============================================================
#				The End
#============================================================



