(* ::Package:: *) (* ::Text:: *) (*Chemical Reaction Network (CRN) Simulator package is developed by David Soloveichik. Copyright 2009-2012. *) (*http://www.dna.caltech.edu/~davids/*) (* ::Section:: *) (*Public interface specification*) Notation`AutoLoadNotationPalette = False; Needs["Notation`"] BeginPackage["CRNSimulator`", {"Notation`"}]; rxn::usage="Represents an irreversible reaction. eg. rxn[a+b,c,1]"; revrxn::usage="Represents a reversible reaction. eg. revrxn[a+b,c,1,1]"; conc::usage="Initial concentration: conc[x,10] or conc[{x,y},10]."; term::usage="Represents an additive term in the ODE for species x. \ Species concentrations must be expressed in x[t] form. eg. term[x, -2 x[t]*y[t]]"; SimulateRxnsys::usage= "SimulateRxnsys[rxnsys,endtime] simulates the reaction system rxnsys for time 0 \ to endtime. In rxnsys, initial concentrations are specified by conc statements. \ If no initial condition is set for a species, its initial concentration is set to 0. \ Rxnsys can include term[] statements (eg. [x, -2 x[t]]) which are additively combined \ together with term[]s derived from rxn[] statements. \ Rxnsys can also include ODEs directly for some species (Eg x'[t]==...) that are \ passed on to NDSolve without modification. Any options specified (eg WorkingPrecision->30) \ are passed to NDSolve."; SpeciesInRxnsys::usage= "SpeciesInRxnsys[rxnsys] returns the species in reaction system rxnsys. \ SpeciesInRxnsys[rxnsys,pttrn] returns the species in reaction system rxnsys \ matching Mathematica pattern pttrn (eg x[1,_])."; SpeciesInRxnsysStringPattern::usage= "SpeciesInRxnsysPattern[rxnsys,pttrn] returns the species in reaction system rxnsys \ matching Mathematica string pattern pttrn. \ (Eg \"g$*\" matches all species names starting with \"g$\" ; \ can also do RegularExpression[\"o..d.\$.*\"].)"; RxnsysToOdesys::usage= "RxnsysToOdesys[rxnsys,t] returns the ODEs corresponding to reaction system rxnsys, \ with initial conditions. If no initial condition is set for a species, its initial \ concentration is set to 0. \ The time variable is given as the second argument; if omitted it is set to Global`t."; (*To use instead of Sequence in functions with Hold attribute but not HoldSequence, like Module, If, etc*) Seq:=Sequence (* ::Section:: *) (*Private*) Begin["`Private`"]; Notation[ParsedBoxWrapper[RowBox[{"r_", " ", OverscriptBox["\[RightArrow]", RowBox[{" ", "k_", " "}]], " ", "p_", " "}]] \[DoubleLongLeftRightArrow] ParsedBoxWrapper[RowBox[{"rxn", "[", RowBox[{"r_", ",", "p_", ",", "k_"}], "]"}]]] Notation[ParsedBoxWrapper[RowBox[{"r_", " ", UnderoverscriptBox["\[RightArrowLeftArrow]", "k2_", "k1_"], " ", "p_", " "}]] \[DoubleLongLeftRightArrow] ParsedBoxWrapper[RowBox[{"revrxn", "[", RowBox[{"r_", ",", "p_", ",", "k1_", ",", "k2_"}], "]"}]]] AddInputAlias[ParsedBoxWrapper[RowBox[{"\[Placeholder]", " ", OverscriptBox["\[RightArrow]", RowBox[{" ", "\[Placeholder]", " "}]], " ", "\[Placeholder]", " "}]],"rxn"] AddInputAlias[ParsedBoxWrapper[RowBox[{"\[Placeholder]", " ", UnderoverscriptBox["\[RightArrowLeftArrow]", "\[Placeholder]", "\[Placeholder]"], " ", "\[Placeholder]", " "}]],"revrxn"] (*We want rxn[a+b,c,1] to be different from rxn[b+a,c,1], so we have to set attribute HoldAll. But we also want to evaluate if any variables can be evaluated.*) SetAttributes[{rxn,revrxn}, HoldAll] rxn[rs_Plus,ps_,k_]:= ReleaseHold[ReplacePart[rxn[1,ps,k],1->Hold[Plus]@@List@@Unevaluated[rs]]]/; Hold@@Unevaluated[rs] =!= Hold@@List@@Unevaluated[rs] rxn[rs_,ps_Plus,k_]:= ReleaseHold[ReplacePart[rxn[rs,1,k],2->Hold[Plus]@@List@@Unevaluated[ps]]]/; Hold@@Unevaluated[ps] =!= Hold@@List@@Unevaluated[ps] rxn[rs_,ps_,k_]:= (With[{rse=rs},rxn[rse,ps,k]])/;Head[rs]=!=Plus&&Unevaluated[rs]=!=rs rxn[rs_,ps_,k_]:= (With[{pse=ps},rxn[rs,pse,k]])/;Head[ps]=!=Plus&&Unevaluated[ps]=!=ps rxn[rs_,ps_,k_]:= (With[{ke=k},rxn[rs,ps,ke]])/;Unevaluated[k]=!=k revrxn[r_,p_,k1_,k2_]:=Sequence[rxn[r,p,k1],rxn[p,r,k2]] conc[xs_List,c_]:=Seq@@(conc[#,c]&/@xs) (* Species as products or reactants in rxn[] statements, as well as defined in x'[t]== or x[t]== statements \ or term statements, or conc statements *) SpeciesInRxnsys[rxnsys_]:= Union[ Cases[Cases[rxnsys,rxn[r_,p_,_]:>Seq[r,p]]/.Times|Plus->Seq,s_Symbol|s_Symbol[__]], Cases[rxnsys, x_'[_]==_ | x_[_]==_ | term[x_,__] | conc[x_,_] :> x]] SpeciesInRxnsys[rsys_,pattern_]:=Cases[SpeciesInRxnsys[rsys],pattern] SpeciesInRxnsysStringPattern[rsys_,pattern_]:=Select[SpeciesInRxnsys[rsys],StringMatchQ[ToString[#],pattern]&] (* Check if a species' initial value is set in a odesys *) InitialValueSetQ[odesys_,x_]:= MemberQ[odesys,x[_]==_] (* Check if a species is missing an ODE or a direct definition (x[t]=_) in odesys. *) MissingODEQ[odesys_,x_,t_Symbol]:= !MemberQ[odesys, D[x[t],t]==_ | x[t]==_] RxnsysToOdesys[rxnsys_,t_Symbol:Global`t]:= Module[ {spcs=SpeciesInRxnsys[rxnsys], termssys, odesys, eqsFromTerms, eqsFromConcs}, (* Convert rxn[] to term[] statements *) termssys=rxnsys /. rxn:rxn[__]:>Seq@@ProcessRxnToTerms[rxn,t]; (* ODEs from parsing terms *) eqsFromTerms = ProcessTermsToOdes[Cases[termssys,term[__]],t]; (* initial values from parsing conc statements *) eqsFromConcs = Cases[termssys,conc[x_,c_]:>x[0]==c]; (* Remove term and conc statements from rxnsys and add eqs generated from them. If there is a conflict, use pass-through equations *) odesys = DeleteCases[termssys, term[__]|conc[__]]; odesys = Join[odesys, DeleteCases[eqsFromTerms, Alternatives@@(#'[t]==_& /@ Cases[odesys,(x_'[t]|x_[t])==_:>x])], eqsFromConcs]; (* For species still without initial values, add zeros *) odesys = Join[odesys, #[0]==0& /@ Select[spcs, !InitialValueSetQ[odesys,#]&]]; (* For species still without ODE or direct definition, add zero time derivative *) (* This can happen for example if conc is defined, but nothing else *) Join[odesys, D[#[t],t]==0& /@ Select[spcs, MissingODEQ[odesys,#,t]&]] ] (* Create list of ODEs from parsing term statements. terms should be list of term[] statements. *) ProcessTermsToOdes[terms_,t_Symbol]:= Module[{spcs=Union[Cases[terms,term[s_,_]:>s]]}, #'[t]==Total[Cases[terms,term[#,rate_]:>rate]] & /@ spcs]; (* Create list of term[] statements from parsing a rxn statement *) ProcessRxnToTerms[reaction:rxn[r_,p_,k_],t_Symbol]:= Module[{spcs=SpeciesInRxnsys[{reaction}], rrate, spccoeffs,terms}, (* compute rate of this reaction *) rrate = k (r/.{Times[b_,s_]:>s^b,Plus->Times}); (*for each species, get a net coefficient*) spccoeffs=Coefficient[p-r,#]& /@ spcs; (*create term for each species*) terms=MapThread[term[#1,#2*rrate]&,{spcs, spccoeffs}]; (*change all species variables in the second arg in term[] to be functions of t*) terms/.term[spc_,rate_]:>term[spc,rate/.s_/;MemberQ[spcs,s]:>s[t]]]; SimulateRxnsys[rxnsys_,endtime_,opts:OptionsPattern[NDSolve]]:= Module[{spcs=SpeciesInRxnsys[rxnsys],odesys=RxnsysToOdesys[rxnsys,Global`t]}, Quiet[NDSolve[odesys, spcs, {Global`t,0,endtime},opts,MaxSteps->Infinity,AccuracyGoal->MachinePrecision],{NDSolve::"precw"}][[1]]] End[]; EndPackage[];