API Documentation
Create a model
ReactiveDynamics.@ReactionNetwork
— MacroMacro that takes an expression corresponding to a reaction network and outputs an instance of TheoryReactionNetwork
that can be converted to a DiscreteProblem
or solved directly.
Most arrows accepted (both right, left, and bi-drectional arrows). Use 0 or ∅ for annihilation/creation to/from nothing.
Custom functions and sampleable objects can be used as numeric parameters. Note that these have to be accessible from ReactiveDynamics's source code.
Examples
acs = @ReactionNetwork begin
+API Documentation · ReactiveDynamics.jl API Documentation API Documentation
Create a model
ReactiveDynamics.@ReactionNetwork
— MacroMacro that takes an expression corresponding to a reaction network and outputs an instance of TheoryReactionNetwork
that can be converted to a DiscreteProblem
or solved directly.
Most arrows accepted (both right, left, and bi-drectional arrows). Use 0 or ∅ for annihilation/creation to/from nothing.
Custom functions and sampleable objects can be used as numeric parameters. Note that these have to be accessible from ReactiveDynamics's source code.
Examples
acs = @ReactionNetworkSchema begin
1.0, X ⟶ Y
1.0, X ⟶ Y, priority=>6., prob=>.7, capacity=>3.
1.0, ∅ --> (Poisson(.3γ)X, Poisson(.5)Y)
@@ -8,7 +8,7 @@
@push acs 1.0 X ⟶ Y
@prob_init acs X=1 Y=2 XY=α
@prob_params acs γ=1 α=4
-@solve_and_plot acs
Modify a model
We list common transition attributes:
attribute interpretation transPriority
priority of a transition (influences resource allocation) transProbOfSuccess
probability that a transition terminates successfully transCapacity
maximum number of concurrent instances of the transition transCycleTime
duration of a transition's instance (adjusted by resource allocation) transMaxLifeTime
maximal duration of a transition's instance transPostAction
action to be executed once a transition's instance terminates transName
name of a transition
We list common species attributes:
attribute interpretation specInitUncertainty
uncertainty about variable's initial state (modelled as Gaussian standard deviation) specInitVal
initial value of a variable
Moreover, it is possible to specify the semantics of the "rate" term. By default, at each time step n ~ Poisson(rate * dt)
instances of a given transition will be spawned. If you want to specify the rate in terms of a cycle time, you may want to use @ct(cycle_time)
, e.g., @ct(ex), A --> B, ...
. This is a shorthand for 1/ex, A --> B, ...
.
For deterministic "rates", use @per_step(ex)
. Here, ex
evaluates to a deterministic number (ceiled to the nearest integer) of a transition's instances to spawn per a single integrator's step. However, note that in this case, the number doesn't scale with the step length! Moreover
ReactiveDynamics.@add_species
— MacroAdd new species to a model.
Examples
@add_species acs S I R
ReactiveDynamics.@aka
— MacroAlias object name in an acs.
Default names
name short name species S transition T action A event E param P meta M
Examples
@aka acs species=resource transition=reaction
ReactiveDynamics.@mode
— MacroSet species modality.
Supported modalities
- nonblock
- conserved
- rate
Examples
@mode acs (r"proj\w+", r"experimental\w+") conserved
+@solve_and_plot acs
Modify a model
We list common transition attributes:
attribute interpretation transPriority
priority of a transition (influences resource allocation) transProbOfSuccess
probability that a transition terminates successfully transCapacity
maximum number of concurrent instances of the transition transCycleTime
duration of a transition's instance (adjusted by resource allocation) transMaxLifeTime
maximal duration of a transition's instance transPostAction
action to be executed once a transition's instance terminates transName
name of a transition
We list common species attributes:
attribute interpretation specInitUncertainty
uncertainty about variable's initial state (modelled as Gaussian standard deviation) specInitVal
initial value of a variable
Moreover, it is possible to specify the semantics of the "rate" term. By default, at each time step n ~ Poisson(rate * dt)
instances of a given transition will be spawned. If you want to specify the rate in terms of a cycle time, you may want to use @ct(cycle_time)
, e.g., @ct(ex), A --> B, ...
. This is a shorthand for 1/ex, A --> B, ...
.
For deterministic "rates", use @deterministic(ex)
. Here, ex
evaluates to a deterministic number (ceiled to the nearest integer) of a transition's instances to spawn per a single integrator's step. However, note that in this case, the number doesn't scale with the step length! Moreover
ReactiveDynamics.@add_species
— MacroAdd new species to a model.
Examples
@add_species acs S I R
ReactiveDynamics.@aka
— MacroAlias object name in an acs.
Default names
name short name species S transition T action A event E param P meta M
Examples
@aka acs species=resource transition=reaction
ReactiveDynamics.@mode
— MacroSet species modality.
Supported modalities
- nonblock
- conserved
- rate
Examples
@mode acs (r"proj\w+", r"experimental\w+") conserved
@mode acs (S, I) conserved
@mode acs S conserved
ReactiveDynamics.@name_transition
— MacroSet name of a transition in the model.
Examples
@name_transition acs 1="name"
@name_transition acs name="transition_name"
diff --git a/docs/build/search_index.js b/docs/build/search_index.js
index addd92b..7c23342 100644
--- a/docs/build/search_index.js
+++ b/docs/build/search_index.js
@@ -1,3 +1,3 @@
var documenterSearchIndex = {"docs":
-[{"location":"index.html#API-Documentation","page":"API Documentation","title":"API Documentation","text":"","category":"section"},{"location":"index.html#Create-a-model","page":"API Documentation","title":"Create a model","text":"","category":"section"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"@ReactionNetwork","category":"page"},{"location":"index.html#ReactiveDynamics.@ReactionNetwork","page":"API Documentation","title":"ReactiveDynamics.@ReactionNetwork","text":"Macro that takes an expression corresponding to a reaction network and outputs an instance of TheoryReactionNetwork that can be converted to a DiscreteProblem or solved directly.\n\nMost arrows accepted (both right, left, and bi-drectional arrows). Use 0 or ∅ for annihilation/creation to/from nothing.\n\nCustom functions and sampleable objects can be used as numeric parameters. Note that these have to be accessible from ReactiveDynamics's source code.\n\nExamples\n\nacs = @ReactionNetwork begin\n 1.0, X ⟶ Y\n 1.0, X ⟶ Y, priority=>6., prob=>.7, capacity=>3.\n 1.0, ∅ --> (Poisson(.3γ)X, Poisson(.5)Y)\n (XY > 100) && (XY -= 1)\nend\n@push acs 1.0 X ⟶ Y \n@prob_init acs X=1 Y=2 XY=α\n@prob_params acs γ=1 α=4\n@solve_and_plot acs\n\n\n\n\n\n","category":"macro"},{"location":"index.html#Modify-a-model","page":"API Documentation","title":"Modify a model","text":"","category":"section"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"We list common transition attributes:","category":"page"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"attribute interpretation\ntransPriority priority of a transition (influences resource allocation)\ntransProbOfSuccess probability that a transition terminates successfully\ntransCapacity maximum number of concurrent instances of the transition\ntransCycleTime duration of a transition's instance (adjusted by resource allocation)\ntransMaxLifeTime maximal duration of a transition's instance\ntransPostAction action to be executed once a transition's instance terminates\ntransName name of a transition","category":"page"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"We list common species attributes:","category":"page"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"attribute interpretation\nspecInitUncertainty uncertainty about variable's initial state (modelled as Gaussian standard deviation)\nspecInitVal initial value of a variable","category":"page"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"Moreover, it is possible to specify the semantics of the \"rate\" term. By default, at each time step n ~ Poisson(rate * dt) instances of a given transition will be spawned. If you want to specify the rate in terms of a cycle time, you may want to use @ct(cycle_time), e.g., @ct(ex), A --> B, .... This is a shorthand for 1/ex, A --> B, ....","category":"page"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"For deterministic \"rates\", use @per_step(ex). Here, ex evaluates to a deterministic number (ceiled to the nearest integer) of a transition's instances to spawn per a single integrator's step. However, note that in this case, the number doesn't scale with the step length! Moreover","category":"page"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"@add_species\n@aka\n@mode\n@name_transition","category":"page"},{"location":"index.html#ReactiveDynamics.@add_species","page":"API Documentation","title":"ReactiveDynamics.@add_species","text":"Add new species to a model.\n\nExamples\n\n@add_species acs S I R\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@aka","page":"API Documentation","title":"ReactiveDynamics.@aka","text":"Alias object name in an acs.\n\nDefault names\n\nname short name\nspecies S\ntransition T\naction A\nevent E\nparam P\nmeta M\n\nExamples\n\n@aka acs species=resource transition=reaction\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@mode","page":"API Documentation","title":"ReactiveDynamics.@mode","text":"Set species modality.\n\nSupported modalities\n\nnonblock\nconserved\nrate\n\nExamples\n\n@mode acs (r\"proj\\w+\", r\"experimental\\w+\") conserved\n@mode acs (S, I) conserved\n@mode acs S conserved\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@name_transition","page":"API Documentation","title":"ReactiveDynamics.@name_transition","text":"Set name of a transition in the model.\n\nExamples\n\n@name_transition acs 1=\"name\"\n@name_transition acs name=\"transition_name\"\n@name_transition acs \"name\"=\"transition_name\"\n\n\n\n\n\n","category":"macro"},{"location":"index.html#Resource-costs","page":"API Documentation","title":"Resource costs","text":"","category":"section"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"@cost\n@valuation\n@reward","category":"page"},{"location":"index.html#ReactiveDynamics.@cost","page":"API Documentation","title":"ReactiveDynamics.@cost","text":"Set cost.\n\nExamples\n\n@cost model experimental1=2 experimental2=3\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@valuation","page":"API Documentation","title":"ReactiveDynamics.@valuation","text":"Set valuation.\n\nExamples\n\n@valuation model experimental1=2 experimental2=3\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@reward","page":"API Documentation","title":"ReactiveDynamics.@reward","text":"Set reward.\n\nExamples\n\n@reward model experimental1=2 experimental2=3\n\n\n\n\n\n","category":"macro"},{"location":"index.html#Add-reactions","page":"API Documentation","title":"Add reactions","text":"","category":"section"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"@push\n@jump\n@periodic","category":"page"},{"location":"index.html#ReactiveDynamics.@push","page":"API Documentation","title":"ReactiveDynamics.@push","text":"Add reactions to an acset.\n\nExamples\n\n@push sir_acs β*S*I*tdecay(@time()) S+I --> 2I name=>SI2I\n@push sir_acs begin \n ν*I, I --> R, name=>I2R\n γ, R --> S, name=>R2S\nend\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@jump","page":"API Documentation","title":"ReactiveDynamics.@jump","text":"Add a jump process (with specified Poisson intensity per unit time step) to a model.\n\nExamples\n\n@jump acs λ Z += rand(Poisson(1.))\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@periodic","page":"API Documentation","title":"ReactiveDynamics.@periodic","text":"Add a periodic callback to a model.\n\nExamples\n\n@periodic acs 1. X += 1\n\n\n\n\n\n","category":"macro"},{"location":"index.html#Set-initial-values,-uncertainty,-and-solver-arguments","page":"API Documentation","title":"Set initial values, uncertainty, and solver arguments","text":"","category":"section"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"@prob_init\n@prob_uncertainty\n@prob_params\n@prob_meta","category":"page"},{"location":"index.html#ReactiveDynamics.@prob_init","page":"API Documentation","title":"ReactiveDynamics.@prob_init","text":"Set initial values of species in an acset.\n\nExamples\n\n@prob_init acs X=1 Y=2 Z=h(α)\n@prob_init acs [1., 2., 3.]\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@prob_uncertainty","page":"API Documentation","title":"ReactiveDynamics.@prob_uncertainty","text":"Set uncertainty in initial values of species in an acset (stderr).\n\nExamples\n\n@prob_uncertainty acs X=.1 Y=.2\n@prob_uncertainty acs [.1, .2,]\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@prob_params","page":"API Documentation","title":"ReactiveDynamics.@prob_params","text":"Set parameter values in an acset.\n\nExamples\n\n@prob_params acs α=1. β=2.\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@prob_meta","page":"API Documentation","title":"ReactiveDynamics.@prob_meta","text":"Set model metadata (e.g. solver arguments)\n\nExamples\n\n@prob_meta acs tspan=(0, 100.) schedule=schedule_weighted!\n@prob_meta sir_acs tspan=250 tstep=1\n\n\n\n\n\n","category":"macro"},{"location":"index.html#Model-unions","page":"API Documentation","title":"Model unions","text":"","category":"section"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"@join\n@equalize","category":"page"},{"location":"index.html#ReactiveDynamics.@join","page":"API Documentation","title":"ReactiveDynamics.@join","text":"@join models... [equalize...]\n\nPerforms join of models and identifies model variables, as specified.\n\nModel variables / parameter values and metadata are propagated; the last model takes precedence.\n\nExamples\n\n@join acs1 acs2 @catchall(A)=acs2.Z @catchall(XY) @catchall(B)\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@equalize","page":"API Documentation","title":"ReactiveDynamics.@equalize","text":"Identify (collapse) a set of species in a model.\n\nExamples\n\n@join acs acs1.A=acs2.A B=C\n\n\n\n\n\n","category":"macro"},{"location":"index.html#Model-import-and-export","page":"API Documentation","title":"Model import and export","text":"","category":"section"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"@import_network\n@export_network","category":"page"},{"location":"index.html#ReactiveDynamics.@import_network","page":"API Documentation","title":"ReactiveDynamics.@import_network","text":"Import a model from a file: this can be either a single TOML file encoding the entire model, or a batch of CSV files (a root file and a number of files, each per a class of objects).\n\nSee tutorials/loadsave for an example.\n\nExamples\n\n@import_network \"model.toml\"\n@import_network \"csv/model.toml\"\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@export_network","page":"API Documentation","title":"ReactiveDynamics.@export_network","text":"Export model to a file: this can be either a single TOML file encoding the entire model, or a batch of CSV files (a root file and a number of files, each per a class of objects).\n\nSee tutorials/loadsave for an example.\n\nExamples\n\n@export_network acs \"acs_data.toml\" # as a TOML\n@export_network acs \"csv/model.csv\" # as a CSV\n\n\n\n\n\n","category":"macro"},{"location":"index.html#Solution-import-and-export","page":"API Documentation","title":"Solution import and export","text":"","category":"section"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"@import_solution\n@export_solution_as_table\n@export_solution_as_csv\n@export_solution","category":"page"},{"location":"index.html#ReactiveDynamics.@import_solution","page":"API Documentation","title":"ReactiveDynamics.@import_solution","text":"@import_solution \"sol.jld2\"\n@import_solution \"sol.jld2\" sol\n\nImport a solution from a file.\n\nExamples\n\n@import_solution \"sir_acs_sol/serialized/sol.jld2\"\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@export_solution_as_table","page":"API Documentation","title":"ReactiveDynamics.@export_solution_as_table","text":"@export_solution_as_table sol\n\nExport a solution as a DataFrame.\n\nExamples\n\n@export_solution_as_table sol\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@export_solution_as_csv","page":"API Documentation","title":"ReactiveDynamics.@export_solution_as_csv","text":"@export_solution_as_csv sol\n@export_solution_as_csv sol \"sol.csv\"\n\nExport a solution to a file.\n\nExamples\n\n@export_solution_as_csv sol \"sol.csv\"\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@export_solution","page":"API Documentation","title":"ReactiveDynamics.@export_solution","text":"@export_solution sol\n@export_solution sol \"sol.jld2\"\n\nExport a solution to a file.\n\nExamples\n\n@export_solution sol \"sol.jdl2\"\n\n\n\n\n\n","category":"macro"},{"location":"index.html#Problematize,sSolve,-and-plot","page":"API Documentation","title":"Problematize,sSolve, and plot","text":"","category":"section"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"@problematize\n@solve\n@plot","category":"page"},{"location":"index.html#ReactiveDynamics.@problematize","page":"API Documentation","title":"ReactiveDynamics.@problematize","text":"Convert a model to a DiscreteProblem. If passed a problem instance, return the instance.\n\nExamples\n\n@problematize acs tspan=1:100\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@solve","page":"API Documentation","title":"ReactiveDynamics.@solve","text":"Solve the problem. Solverargs passed at the calltime take precedence.\n\nExamples\n\n@solve prob\n@solve prob tspan=1:100\n@solve prob tspan=100 trajectories=20\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@plot","page":"API Documentation","title":"ReactiveDynamics.@plot","text":"Plot the solution (summary).\n\nExamples\n\n@plot sol plot_type=summary\n@plot sol plot_type=allocation # not supported for ensemble solutions!\n@plot sol plot_type=valuations # not supported for ensemble solutions!\n@plot sol plot_type=new_transitions # not supported for ensemble solutions!\n\n\n\n\n\n","category":"macro"},{"location":"index.html#Optimization-and-fitting","page":"API Documentation","title":"Optimization and fitting","text":"","category":"section"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"@optimize\n@fit\n@fit_and_plot\n@build_solver","category":"page"},{"location":"index.html#ReactiveDynamics.@optimize","page":"API Documentation","title":"ReactiveDynamics.@optimize","text":"@optimize acset objective ... ... opts...\n\nTake an acset and optimize given functional.\n\nObjective is an expression which may reference the model's variables and parameters, i.e., A+β. The values to optimized are listed using their symbolic names; unless specified, the initial value is inferred from the model. The vector of free variables passed to the NLopt solver has the form [free_vars; free_params]; order of vars and params, respectively, is preserved. \n\nBy default, the functional is minimized. Specify objective=max to perform maximization. \n\nPropagates NLopt solver arguments; see NLopt documentation.\n\nExamples\n\n@optimize acs abs(A-B) A B=20. α=2. lower_bounds=0 upper_bounds=100\n@optimize acss abs(A-B) A B=20. α=2. upper_bounds=[200,300,400] maxeval=200 objective=min\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@fit","page":"API Documentation","title":"ReactiveDynamics.@fit","text":"@fit acset data_points time_steps empiric_variables ... ... opts...\n\nTake an acset and fit initial values and parameters to empirical data.\n\nThe values to optimized are listed using their symbolic names; unless specified, the initial value is inferred from the model. The vector of free variables passed to the NLopt solver has the form [free_vars; free_params]; order of vars and params, respectively, is preserved. \n\nPropagates NLopt solver arguments; see NLopt documentation.\n\nExamples\n\nt = [1, 50, 100]\ndata = [80 30 20]\n@fit acs data t vars=A B=20 A α # fit B, A, α; empirical data is for variable A\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@fit_and_plot","page":"API Documentation","title":"ReactiveDynamics.@fit_and_plot","text":"@fit acset data_points time_steps empiric_variables ... ... opts...\n\nTake an acset, fit initial values and parameters to empirical data, and plot the result.\n\nThe values to optimized are listed using their symbolic names; unless specified, the initial value is inferred from the model. The vector of free variables passed to the NLopt solver has the form [free_vars; free_params]; order of vars and params, respectively, is preserved. \n\nPropagates NLopt solver arguments; see NLopt documentation.\n\nExamples\n\nt = [1, 50, 100]\ndata = [80 30 20]\n@fit acs data t vars=A B=20 A α # fit B, A, α; empirical data is for variable A\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@build_solver","page":"API Documentation","title":"ReactiveDynamics.@build_solver","text":"@build_solver acset ... ... opts...\n\nTake an acset and export a solution as a function of free vars and free parameters.\n\nExamples\n\nsolver = @build_solver acs S α β # function of variable S and parameters α, β\nsolver([S, α, β])\n\n\n\n\n\n","category":"macro"}]
+[{"location":"index.html#API-Documentation","page":"API Documentation","title":"API Documentation","text":"","category":"section"},{"location":"index.html#Create-a-model","page":"API Documentation","title":"Create a model","text":"","category":"section"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"@ReactionNetwork","category":"page"},{"location":"index.html#ReactiveDynamics.@ReactionNetwork","page":"API Documentation","title":"ReactiveDynamics.@ReactionNetwork","text":"Macro that takes an expression corresponding to a reaction network and outputs an instance of TheoryReactionNetwork that can be converted to a DiscreteProblem or solved directly.\n\nMost arrows accepted (both right, left, and bi-drectional arrows). Use 0 or ∅ for annihilation/creation to/from nothing.\n\nCustom functions and sampleable objects can be used as numeric parameters. Note that these have to be accessible from ReactiveDynamics's source code.\n\nExamples\n\nacs = @ReactionNetworkSchema begin\n 1.0, X ⟶ Y\n 1.0, X ⟶ Y, priority=>6., prob=>.7, capacity=>3.\n 1.0, ∅ --> (Poisson(.3γ)X, Poisson(.5)Y)\n (XY > 100) && (XY -= 1)\nend\n@push acs 1.0 X ⟶ Y \n@prob_init acs X=1 Y=2 XY=α\n@prob_params acs γ=1 α=4\n@solve_and_plot acs\n\n\n\n\n\n","category":"macro"},{"location":"index.html#Modify-a-model","page":"API Documentation","title":"Modify a model","text":"","category":"section"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"We list common transition attributes:","category":"page"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"attribute interpretation\ntransPriority priority of a transition (influences resource allocation)\ntransProbOfSuccess probability that a transition terminates successfully\ntransCapacity maximum number of concurrent instances of the transition\ntransCycleTime duration of a transition's instance (adjusted by resource allocation)\ntransMaxLifeTime maximal duration of a transition's instance\ntransPostAction action to be executed once a transition's instance terminates\ntransName name of a transition","category":"page"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"We list common species attributes:","category":"page"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"attribute interpretation\nspecInitUncertainty uncertainty about variable's initial state (modelled as Gaussian standard deviation)\nspecInitVal initial value of a variable","category":"page"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"Moreover, it is possible to specify the semantics of the \"rate\" term. By default, at each time step n ~ Poisson(rate * dt) instances of a given transition will be spawned. If you want to specify the rate in terms of a cycle time, you may want to use @ct(cycle_time), e.g., @ct(ex), A --> B, .... This is a shorthand for 1/ex, A --> B, ....","category":"page"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"For deterministic \"rates\", use @deterministic(ex). Here, ex evaluates to a deterministic number (ceiled to the nearest integer) of a transition's instances to spawn per a single integrator's step. However, note that in this case, the number doesn't scale with the step length! Moreover","category":"page"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"@add_species\n@aka\n@mode\n@name_transition","category":"page"},{"location":"index.html#ReactiveDynamics.@add_species","page":"API Documentation","title":"ReactiveDynamics.@add_species","text":"Add new species to a model.\n\nExamples\n\n@add_species acs S I R\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@aka","page":"API Documentation","title":"ReactiveDynamics.@aka","text":"Alias object name in an acs.\n\nDefault names\n\nname short name\nspecies S\ntransition T\naction A\nevent E\nparam P\nmeta M\n\nExamples\n\n@aka acs species=resource transition=reaction\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@mode","page":"API Documentation","title":"ReactiveDynamics.@mode","text":"Set species modality.\n\nSupported modalities\n\nnonblock\nconserved\nrate\n\nExamples\n\n@mode acs (r\"proj\\w+\", r\"experimental\\w+\") conserved\n@mode acs (S, I) conserved\n@mode acs S conserved\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@name_transition","page":"API Documentation","title":"ReactiveDynamics.@name_transition","text":"Set name of a transition in the model.\n\nExamples\n\n@name_transition acs 1=\"name\"\n@name_transition acs name=\"transition_name\"\n@name_transition acs \"name\"=\"transition_name\"\n\n\n\n\n\n","category":"macro"},{"location":"index.html#Resource-costs","page":"API Documentation","title":"Resource costs","text":"","category":"section"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"@cost\n@valuation\n@reward","category":"page"},{"location":"index.html#ReactiveDynamics.@cost","page":"API Documentation","title":"ReactiveDynamics.@cost","text":"Set cost.\n\nExamples\n\n@cost model experimental1=2 experimental2=3\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@valuation","page":"API Documentation","title":"ReactiveDynamics.@valuation","text":"Set valuation.\n\nExamples\n\n@valuation model experimental1=2 experimental2=3\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@reward","page":"API Documentation","title":"ReactiveDynamics.@reward","text":"Set reward.\n\nExamples\n\n@reward model experimental1=2 experimental2=3\n\n\n\n\n\n","category":"macro"},{"location":"index.html#Add-reactions","page":"API Documentation","title":"Add reactions","text":"","category":"section"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"@push\n@jump\n@periodic","category":"page"},{"location":"index.html#ReactiveDynamics.@push","page":"API Documentation","title":"ReactiveDynamics.@push","text":"Add reactions to an acset.\n\nExamples\n\n@push sir_acs β*S*I*tdecay(@time()) S+I --> 2I name=>SI2I\n@push sir_acs begin \n ν*I, I --> R, name=>I2R\n γ, R --> S, name=>R2S\nend\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@jump","page":"API Documentation","title":"ReactiveDynamics.@jump","text":"Add a jump process (with specified Poisson intensity per unit time step) to a model.\n\nExamples\n\n@jump acs λ Z += rand(Poisson(1.))\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@periodic","page":"API Documentation","title":"ReactiveDynamics.@periodic","text":"Add a periodic callback to a model.\n\nExamples\n\n@periodic acs 1. X += 1\n\n\n\n\n\n","category":"macro"},{"location":"index.html#Set-initial-values,-uncertainty,-and-solver-arguments","page":"API Documentation","title":"Set initial values, uncertainty, and solver arguments","text":"","category":"section"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"@prob_init\n@prob_uncertainty\n@prob_params\n@prob_meta","category":"page"},{"location":"index.html#ReactiveDynamics.@prob_init","page":"API Documentation","title":"ReactiveDynamics.@prob_init","text":"Set initial values of species in an acset.\n\nExamples\n\n@prob_init acs X=1 Y=2 Z=h(α)\n@prob_init acs [1., 2., 3.]\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@prob_uncertainty","page":"API Documentation","title":"ReactiveDynamics.@prob_uncertainty","text":"Set uncertainty in initial values of species in an acset (stderr).\n\nExamples\n\n@prob_uncertainty acs X=.1 Y=.2\n@prob_uncertainty acs [.1, .2,]\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@prob_params","page":"API Documentation","title":"ReactiveDynamics.@prob_params","text":"Set parameter values in an acset.\n\nExamples\n\n@prob_params acs α=1. β=2.\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@prob_meta","page":"API Documentation","title":"ReactiveDynamics.@prob_meta","text":"Set model metadata (e.g. solver arguments)\n\nExamples\n\n@prob_meta acs tspan=(0, 100.) schedule=schedule_weighted!\n@prob_meta sir_acs tspan=250 tstep=1\n\n\n\n\n\n","category":"macro"},{"location":"index.html#Model-unions","page":"API Documentation","title":"Model unions","text":"","category":"section"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"@join\n@equalize","category":"page"},{"location":"index.html#ReactiveDynamics.@join","page":"API Documentation","title":"ReactiveDynamics.@join","text":"@join models... [equalize...]\n\nPerforms join of models and identifies model variables, as specified.\n\nModel variables / parameter values and metadata are propagated; the last model takes precedence.\n\nExamples\n\n@join acs1 acs2 @catchall(A)=acs2.Z @catchall(XY) @catchall(B)\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@equalize","page":"API Documentation","title":"ReactiveDynamics.@equalize","text":"Identify (collapse) a set of species in a model.\n\nExamples\n\n@join acs acs1.A=acs2.A B=C\n\n\n\n\n\n","category":"macro"},{"location":"index.html#Model-import-and-export","page":"API Documentation","title":"Model import and export","text":"","category":"section"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"@import_network\n@export_network","category":"page"},{"location":"index.html#ReactiveDynamics.@import_network","page":"API Documentation","title":"ReactiveDynamics.@import_network","text":"Import a model from a file: this can be either a single TOML file encoding the entire model, or a batch of CSV files (a root file and a number of files, each per a class of objects).\n\nSee tutorials/loadsave for an example.\n\nExamples\n\n@import_network \"model.toml\"\n@import_network \"csv/model.toml\"\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@export_network","page":"API Documentation","title":"ReactiveDynamics.@export_network","text":"Export model to a file: this can be either a single TOML file encoding the entire model, or a batch of CSV files (a root file and a number of files, each per a class of objects).\n\nSee tutorials/loadsave for an example.\n\nExamples\n\n@export_network acs \"acs_data.toml\" # as a TOML\n@export_network acs \"csv/model.csv\" # as a CSV\n\n\n\n\n\n","category":"macro"},{"location":"index.html#Solution-import-and-export","page":"API Documentation","title":"Solution import and export","text":"","category":"section"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"@import_solution\n@export_solution_as_table\n@export_solution_as_csv\n@export_solution","category":"page"},{"location":"index.html#ReactiveDynamics.@import_solution","page":"API Documentation","title":"ReactiveDynamics.@import_solution","text":"@import_solution \"sol.jld2\"\n@import_solution \"sol.jld2\" sol\n\nImport a solution from a file.\n\nExamples\n\n@import_solution \"sir_acs_sol/serialized/sol.jld2\"\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@export_solution_as_table","page":"API Documentation","title":"ReactiveDynamics.@export_solution_as_table","text":"@export_solution_as_table sol\n\nExport a solution as a DataFrame.\n\nExamples\n\n@export_solution_as_table sol\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@export_solution_as_csv","page":"API Documentation","title":"ReactiveDynamics.@export_solution_as_csv","text":"@export_solution_as_csv sol\n@export_solution_as_csv sol \"sol.csv\"\n\nExport a solution to a file.\n\nExamples\n\n@export_solution_as_csv sol \"sol.csv\"\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@export_solution","page":"API Documentation","title":"ReactiveDynamics.@export_solution","text":"@export_solution sol\n@export_solution sol \"sol.jld2\"\n\nExport a solution to a file.\n\nExamples\n\n@export_solution sol \"sol.jdl2\"\n\n\n\n\n\n","category":"macro"},{"location":"index.html#Problematize,sSolve,-and-plot","page":"API Documentation","title":"Problematize,sSolve, and plot","text":"","category":"section"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"@problematize\n@solve\n@plot","category":"page"},{"location":"index.html#ReactiveDynamics.@problematize","page":"API Documentation","title":"ReactiveDynamics.@problematize","text":"Convert a model to a DiscreteProblem. If passed a problem instance, return the instance.\n\nExamples\n\n@problematize acs tspan=1:100\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@solve","page":"API Documentation","title":"ReactiveDynamics.@solve","text":"Solve the problem. Solverargs passed at the calltime take precedence.\n\nExamples\n\n@solve prob\n@solve prob tspan=1:100\n@solve prob tspan=100 trajectories=20\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@plot","page":"API Documentation","title":"ReactiveDynamics.@plot","text":"Plot the solution (summary).\n\nExamples\n\n@plot sol plot_type=summary\n@plot sol plot_type=allocation # not supported for ensemble solutions!\n@plot sol plot_type=valuations # not supported for ensemble solutions!\n@plot sol plot_type=new_transitions # not supported for ensemble solutions!\n\n\n\n\n\n","category":"macro"},{"location":"index.html#Optimization-and-fitting","page":"API Documentation","title":"Optimization and fitting","text":"","category":"section"},{"location":"index.html","page":"API Documentation","title":"API Documentation","text":"@optimize\n@fit\n@fit_and_plot\n@build_solver","category":"page"},{"location":"index.html#ReactiveDynamics.@optimize","page":"API Documentation","title":"ReactiveDynamics.@optimize","text":"@optimize acset objective ... ... opts...\n\nTake an acset and optimize given functional.\n\nObjective is an expression which may reference the model's variables and parameters, i.e., A+β. The values to optimized are listed using their symbolic names; unless specified, the initial value is inferred from the model. The vector of free variables passed to the NLopt solver has the form [free_vars; free_params]; order of vars and params, respectively, is preserved. \n\nBy default, the functional is minimized. Specify objective=max to perform maximization. \n\nPropagates NLopt solver arguments; see NLopt documentation.\n\nExamples\n\n@optimize acs abs(A-B) A B=20. α=2. lower_bounds=0 upper_bounds=100\n@optimize acss abs(A-B) A B=20. α=2. upper_bounds=[200,300,400] maxeval=200 objective=min\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@fit","page":"API Documentation","title":"ReactiveDynamics.@fit","text":"@fit acset data_points time_steps empiric_variables ... ... opts...\n\nTake an acset and fit initial values and parameters to empirical data.\n\nThe values to optimized are listed using their symbolic names; unless specified, the initial value is inferred from the model. The vector of free variables passed to the NLopt solver has the form [free_vars; free_params]; order of vars and params, respectively, is preserved. \n\nPropagates NLopt solver arguments; see NLopt documentation.\n\nExamples\n\nt = [1, 50, 100]\ndata = [80 30 20]\n@fit acs data t vars=A B=20 A α # fit B, A, α; empirical data is for variable A\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@fit_and_plot","page":"API Documentation","title":"ReactiveDynamics.@fit_and_plot","text":"@fit acset data_points time_steps empiric_variables ... ... opts...\n\nTake an acset, fit initial values and parameters to empirical data, and plot the result.\n\nThe values to optimized are listed using their symbolic names; unless specified, the initial value is inferred from the model. The vector of free variables passed to the NLopt solver has the form [free_vars; free_params]; order of vars and params, respectively, is preserved. \n\nPropagates NLopt solver arguments; see NLopt documentation.\n\nExamples\n\nt = [1, 50, 100]\ndata = [80 30 20]\n@fit acs data t vars=A B=20 A α # fit B, A, α; empirical data is for variable A\n\n\n\n\n\n","category":"macro"},{"location":"index.html#ReactiveDynamics.@build_solver","page":"API Documentation","title":"ReactiveDynamics.@build_solver","text":"@build_solver acset ... ... opts...\n\nTake an acset and export a solution as a function of free vars and free parameters.\n\nExamples\n\nsolver = @build_solver acs S α β # function of variable S and parameters α, β\nsolver([S, α, β])\n\n\n\n\n\n","category":"macro"}]
}
diff --git a/docs/src/index.md b/docs/src/index.md
index 87ec4e7..7104f87 100644
--- a/docs/src/index.md
+++ b/docs/src/index.md
@@ -28,7 +28,7 @@ We list common species attributes:
Moreover, it is possible to specify the semantics of the "rate" term. By default, at each time step `n ~ Poisson(rate * dt)` instances of a given transition will be spawned. If you want to specify the rate in terms of a cycle time, you may want to use `@ct(cycle_time)`, e.g., `@ct(ex), A --> B, ...`. This is a shorthand for `1/ex, A --> B, ...`.
-For deterministic "rates", use `@per_step(ex)`. Here, `ex` evaluates to a deterministic number (ceiled to the nearest integer) of a transition's instances to spawn per a single integrator's step. However, note that in this case, the number doesn't scale with the step length! Moreover
+For deterministic "rates", use `@deterministic(ex)`. Here, `ex` evaluates to a deterministic number (ceiled to the nearest integer) of a transition's instances to spawn per a single integrator's step. However, note that in this case, the number doesn't scale with the step length! Moreover
```@docs
@add_species
diff --git a/readme.md b/readme.md
index e09ef3e..ae2cf07 100644
--- a/readme.md
+++ b/readme.md
@@ -68,7 +68,7 @@ Follow the SIR model's reactions:
using ReactiveDynamics
# model dynamics
-sir_acs = @ReactionNetwork begin
+sir_acs = @ReactionNetworkSchema begin
α*S*I, S+I --> 2I, name=>I2R
β*I, I --> R, name=>R2S
end
@@ -196,7 +196,7 @@ To harness the capabilities of **GeneratedExpressions.jl**, let us first declare
end
# generate submodel dynamics
-push!(rd_models, @ReactionNetwork begin
+push!(rd_models, @ReactionNetworkSchema begin
M[$i][$m, $n], state[$m] + {demand[$i][$m, $n, $l]*resource[$l], l=1:$r, dlm=+} --> state[$n] +
{production[$i][$m, $n, $l]*resource[$l], l=1:$r, dlm=+}, cycle_time=>cycle_times[$i][$m, $n], probability_of_success=>$m*$n/(n[$i])^2
end m=1:ReactiveDynamics.ns[$i] n=1:ReactiveDynamics.ns[$i]
@@ -292,7 +292,7 @@ end
Next we set up a simple dynamics and supply initial parameters.
```julia
-acs = @ReactionNetwork begin
+acs = @ReactionNetworkSchema begin
function_to_learn(A, B, C, params), A --> B+C
1., B --> C
2., C --> B
diff --git a/src/ReactiveDynamics.jl b/src/ReactiveDynamics.jl
index 083152f..7d4991d 100644
--- a/src/ReactiveDynamics.jl
+++ b/src/ReactiveDynamics.jl
@@ -1,9 +1,8 @@
module ReactiveDynamics
-using Catlab, Catlab.CategoricalAlgebra, Catlab.Present
+using ACSets
using Reexport
using MacroTools
-using NLopt
using ComponentArrays
@reexport using GeneratedExpressions
@@ -28,60 +27,64 @@ Base.@kwdef mutable struct FoldedObservable
on::Vector{SampleableValues} = SampleableValues[]
end
-@present TheoryReactionNetwork(FreeSchema) begin
- (S, T)::Ob # species, transitions
-
- (
- SymbolicAttributeT,
- DescriptiveAttributeT,
- SampleableAttributeT,
- ModalityAttributeT,
- PcsOptT,
- PrmAttributeT,
- )::AttrType
-
- specName::Attr(S, SymbolicAttributeT)
- specModality::Attr(S, ModalityAttributeT)
- specInitVal::Attr(S, SampleableAttributeT)
- specInitUncertainty::Attr(S, SampleableAttributeT)
- (specCost, specReward, specValuation)::Attr(S, SampleableAttributeT)
-
- trans::Attr(T, SampleableAttributeT)
- transPriority::Attr(T, SampleableAttributeT)
- transRate::Attr(T, SampleableAttributeT)
- transCycleTime::Attr(T, SampleableAttributeT)
- transProbOfSuccess::Attr(T, SampleableAttributeT)
- transCapacity::Attr(T, SampleableAttributeT)
- transMaxLifeTime::Attr(T, SampleableAttributeT)
- transPostAction::Attr(T, SampleableAttributeT)
- transMultiplier::Attr(T, SampleableAttributeT)
- transName::Attr(T, DescriptiveAttributeT)
-
- E::Ob # events
- (eventTrigger, eventAction)::Attr(E, SampleableAttributeT)
-
- obs::Ob # processes (observables)
- obsName::Attr(obs, SymbolicAttributeT)
- obsOpts::Attr(obs, PcsOptT)
-
- (P, M)::Ob # model params, solver args
-
- prmName::Attr(P, SymbolicAttributeT)
- prmVal::Attr(P, PrmAttributeT)
-
- metaKeyword::Attr(M, SymbolicAttributeT)
- metaVal::Attr(M, SampleableAttributeT)
-end
+TheoryReactionNetwork = BasicSchema(
+ [:S, :T, :E, :obs, :P, :M], # species, transitions, events, processes (observables), model params, solver args
+ [], # no homs
+ [
+ :SymbolicAttributeT,
+ :DescriptiveAttributeT,
+ :SampleableAttributeT,
+ :ModalityAttributeT,
+ :PcsOptT,
+ :PrmAttributeT,
+ :BoolAttributeT,
+ ], # AttrTypes
+ [
+ # species
+ (:specName, :S, :SymbolicAttributeT),
+ (:specModality, :S, :ModalityAttributeT),
+ (:specInitVal, :S, :SampleableAttributeT),
+ (:specInitUncertainty, :S, :SampleableAttributeT),
+ (:specCost, :S, :SampleableAttributeT),
+ (:specReward, :S, :SampleableAttributeT),
+ (:specValuation, :S, :SampleableAttributeT),
+ (:specStructured, :S, :BoolAttributeT),
+ # transitions
+ (:trans, :T, :SampleableAttributeT),
+ (:transPriority, :T, :SampleableAttributeT),
+ (:transRate, :T, :SampleableAttributeT),
+ (:transCycleTime, :T, :SampleableAttributeT),
+ (:transProbOfSuccess, :T, :SampleableAttributeT),
+ (:transCapacity, :T, :SampleableAttributeT),
+ (:transMaxLifeTime, :T, :SampleableAttributeT),
+ (:transPreAction, :T, :SampleableAttributeT),
+ (:transPostAction, :T, :SampleableAttributeT),
+ (:transMultiplier, :T, :SampleableAttributeT),
+ (:transName, :T, :DescriptiveAttributeT),
+ # events
+ (:eventTrigger, :E, :SampleableAttributeT),
+ (:eventAction, :E, :SampleableAttributeT),
+ # observables
+ (:obsName, :obs, :SymbolicAttributeT),
+ (:obsOpts, :obs, :PcsOptT),
+ # params, args
+ (:prmName, :P, :SymbolicAttributeT),
+ (:prmVal, :P, :PrmAttributeT),
+ (:metaKeyword, :M, :SymbolicAttributeT),
+ (:metaVal, :M, :SampleableAttributeT),
+ ],
+)
@acset_type FoldedReactionNetworkType(TheoryReactionNetwork)
-const ReactionNetwork = FoldedReactionNetworkType{
+const ReactionNetworkSchema = FoldedReactionNetworkType{
Symbol,
Union{String,Symbol,Missing},
SampleableValues,
Set{Symbol},
FoldedObservable,
Any,
+ Bool,
}
Base.convert(::Type{Symbol}, ex::String) = Symbol(ex)
@@ -94,12 +97,14 @@ Base.convert(::Type{Union{String,Symbol,Missing}}, ex::String) =
end
Base.convert(::Type{SampleableValues}, ex::String) = MacroTools.striplines(Meta.parse(ex))
+
Base.convert(::Type{Set{Symbol}}, ex::String) = eval(Meta.parse(ex))
Base.convert(::Type{FoldedObservable}, ex::String) = eval(Meta.parse(ex))
prettynames = Dict(
:transRate => [:rate],
:specInitUncertainty => [:uncertainty, :stoch, :stochasticity],
+ :transPreAction => [:preAction, :action, :pre],
:transPostAction => [:postAction, :post],
:transName => [:name, :interpretation],
:transPriority => [:priority],
@@ -117,6 +122,7 @@ defargs = Dict(
:transCycleTime => 0.0,
:transMaxLifeTime => Inf,
:transMultiplier => 1,
+ :transPreAction => :(),
:transPostAction => :(),
:transName => missing,
),
@@ -126,47 +132,43 @@ defargs = Dict(
:specCost => 0.0,
:specReward => 0.0,
:specValuation => 0.0,
+ :specStructured => false,
),
:P => Dict{Symbol,Any}(:prmVal => missing),
:M => Dict{Symbol,Any}(:metaVal => missing),
)
compilable_attrs =
- filter(attr -> eltype(attr) == SampleableValues, propertynames(ReactionNetwork()))
+ filter(attr -> eltype(attr) == SampleableValues, propertynames(ReactionNetworkSchema()))
species_modalities = [:nonblock, :conserved, :rate]
-function assign_defaults!(acs::ReactionNetwork)
+function assign_defaults!(acs::ReactionNetworkSchema)
for (_, v_) in defargs, (k, v) in v_
- for i = 1:length(subpart(acs, k))
- isnothing(acs[i, k]) && (subpart(acs, k)[i] = v)
+ for i in dom_parts(acs, k)
+ isnothing(acs[i, k]) && (acs[i, k] = v)
end
end
foreach(
- i ->
- !isnothing(acs[i, :specModality]) ||
- (subpart(acs, :specModality)[i] = Set{Symbol}()),
- 1:nparts(acs, :S),
+ i -> !isnothing(acs[i, :specModality]) || (acs[i, :specModality] = Set{Symbol}()),
+ parts(acs, :S),
)
k = [:specCost, :specReward, :specValuation]
foreach(
- k -> foreach(
- i -> !isnothing(acs[i, k]) || (subpart(acs, k)[i] = 0.0),
- 1:nparts(acs, :S),
- ),
+ k -> foreach(i -> !isnothing(acs[i, k]) || (acs[i, k] = 0.0), parts(acs, :S)),
k,
)
return acs
end
-function ReactionNetwork(transitions, reactants, obs, events)
- return merge_acs!(ReactionNetwork(), transitions, reactants, obs, events)
+function ReactionNetworkSchema(transitions, reactants, obs, events)
+ return merge_acs!(ReactionNetworkSchema(), transitions, reactants, obs, events)
end
-function ReactionNetwork(transitions, reactants, obs)
- return merge_acs!(ReactionNetwork(), transitions, reactants, obs, [])
+function ReactionNetworkSchema(transitions, reactants, obs)
+ return merge_acs!(ReactionNetworkSchema(), transitions, reactants, obs, [])
end
function add_obs!(acs, obs)
@@ -195,7 +197,7 @@ function add_obs!(acs, obs)
return acs
end
-function merge_acs!(acs::ReactionNetwork, transitions, reactants, obs, events)
+function merge_acs!(acs::ReactionNetworkSchema, transitions, reactants, obs, events)
foreach(
t -> add_part!(acs, :T; trans = t[1][2], transRate = t[1][1], t[2]...),
transitions,
@@ -220,7 +222,7 @@ include.(readdir(joinpath(@__DIR__, "interface"); join = true))
include.(readdir(joinpath(@__DIR__, "utils"); join = true))
include.(readdir(joinpath(@__DIR__, "operators"); join = true))
include("solvers.jl")
-include("optim.jl")
+#include("optim.jl")
include("loadsave.jl")
end
diff --git a/src/compilers.jl b/src/compilers.jl
index d1b5314..49ecffd 100644
--- a/src/compilers.jl
+++ b/src/compilers.jl
@@ -26,16 +26,17 @@ end
Recursively substitute model variables. Subsitution pairs are specified in `varmap`.
"""
function recursively_substitute_vars!(varmap, ex)
- ex isa Symbol && return (haskey(varmap, ex) ? varmap[ex] : ex)
- ex isa Expr && for i = 1:length(ex.args)
- if ex.args[i] isa Expr
- recursively_substitute_vars!(varmap, ex.args[i])
- else
- (
- ex.args[i] isa Symbol &&
- haskey(varmap, ex.args[i]) &&
- (ex.args[i] = varmap[ex.args[i]])
- )
+ if ex isa Symbol
+ return haskey(varmap, ex) ? varmap[ex] : ex
+ elseif ex isa Expr
+ for i = 1:length(ex.args)
+ if ex.args[i] isa Expr
+ ex.args[i] = recursively_substitute_vars!(varmap, ex.args[i])
+ else
+ if ex.args[i] isa Symbol && haskey(varmap, ex.args[i])
+ ex.args[i] = varmap[ex.args[i]]
+ end
+ end
end
end
@@ -63,9 +64,7 @@ function recursively_expand_dots_in_ex!(ex, vars)
return ex
end
-reserved_names =
- [:t, :state, :obs, :resample, :solverarg, :take, :log, :periodic, :set_params]
-push!(reserved_names, :state)
+reserved_names = [:t, :obs, :resample, :solverarg, :take, :log, :periodic, :set_params]
function escape_ref(ex, species)
return if ex isa Symbol
@@ -92,11 +91,16 @@ function wrap_expr(fex, species_names, prm_names, varmap)
let
end
)
+
# expression walking (MacroTools): visit each expression, subsitute with the body's return value
fex = prewalk(fex) do x
# here we convert the query metalanguage: @t() -> time(state) etc.
if isexpr(x, :macrocall) && (macroname(x) ∈ reserved_names)
Expr(:call, macroname(x), :state, x.args[3:end]...)
+ elseif isexpr(x, :macrocall) && (macroname(x) == :transition)
+ :transition
+ elseif isexpr(x, :macrocall) && (macroname(x) == :state)
+ :state
else
x
end
@@ -112,12 +116,17 @@ function wrap_expr(fex, species_names, prm_names, varmap)
)
push!(letex.args[2].args, fex)
- # the function shall be a function of the dynamic ReactiveDynamicsState structure: letex -> :(state -> $letex)
+ # the function shall be a function of the dynamic ReactionNetworkSchema structure: letex -> :(state -> $letex)
# eval the expression to a Julia function, save that function into the "compiled" acset
- return eval(:(state -> $letex))
+
+ return eval(quote
+ function (state, transition)
+ return $letex
+ end
+ end)
end
-function get_wrap_fun(acs::ReactionNetwork)
+function get_wrap_fun(acs::ReactionNetworkSchema)
species_names = collect(acs[:, :specName])
prm_names = collect(acs[:, :prmName])
varmap = Dict([name => :(state.u[$i]) for (i, name) in enumerate(species_names)])
@@ -133,8 +142,9 @@ function skip_compile(attr)
(string(attr) == "trans")
end
-function compile_attrs(acs::ReactionNetwork)
- species_names = collect(acs[:, :specName])
+function compile_attrs(acs::ReactionNetworkSchema, structured_token)
+ species_names = collect(acs[:, :specName])#setdiff(collect(acs[:, :specName]), structured_token)
+
prm_names = collect(acs[:, :prmName])
varmap = Dict([name => :(state.u[$i]) for (i, name) in enumerate(species_names)])
for name in prm_names
@@ -164,12 +174,12 @@ function compile_attrs(acs::ReactionNetwork)
transitions[:transActivated] = fill(true, nparts(acs, :T))
transitions[:transToSpawn] = zeros(nparts(acs, :T))
transitions[:transHash] =
- [coalesce(acs[i, :transName], gensym()) for i = 1:nparts(acs, :T)]
+ [coalesce(acs[i, :transName], gensym()) for i in parts(acs, :T)]
return attrs, transitions, wrap_fun
end
-function remove_choose(acs::ReactionNetwork)
+function remove_choose(acs::ReactionNetworkSchema)
acs = deepcopy(acs)
pcs = []
for attr in propertynames(acs.subparts)
diff --git a/src/interface/agents.jl b/src/interface/agents.jl
new file mode 100644
index 0000000..fcd5558
--- /dev/null
+++ b/src/interface/agents.jl
@@ -0,0 +1,71 @@
+export AbstractStructuredToken, BaseStructuredToken
+export @structured_token
+export add_structured_token!
+
+# Abstract supertype of all structured species.
+abstract type AbstractStructuredToken <: AbstractAlgebraicAgent end
+
+# It comes handy to keep track of the transition the entity is assigned to (if).
+# In general, we will probably assume that each "structured agent" type implements this field.
+# Otherwise, it would be possible to implement getter and setter interface and use it from within ReaDyn.
+@aagent FreeAgent struct BaseStructuredToken
+ species::Union{Nothing,Symbol}
+ bound_transition::Union{Nothing,ReactiveDynamics.Transition}
+ past_bonds::Vector{Tuple{Symbol,Float64,Transition}}
+end
+
+# We use this to let the network know that the type is structured.
+function register_structured_species!(reaction_network, type)
+ if !(type ∈ reaction_network[:, :specName])
+ add_part!(reaction_network, :S; specName = type)
+ end
+
+ i = first(incident(reaction_network, type, :specName))
+ reaction_network[i, :specStructured] = true
+
+ return nothing
+end
+
+# Convenience macro to define structured species.
+macro structured_token(network, type)
+ quote
+ $(AlgebraicAgents.aagent(
+ BaseStructuredToken,
+ AbstractStructuredToken,
+ type,
+ ReactiveDynamics,
+ ))
+ end
+end
+
+# Add a structured agent instance to an instance of a reaction network.
+function add_structured_token!(problem::ReactionNetworkProblem, agent)
+ return entangle!(getagent(problem, "structured"), agent)
+end
+
+import AlgebraicAgents
+
+# By default, structured agents have no evolutionary rule.
+AlgebraicAgents._projected_to(::AbstractStructuredToken) = nothing
+AlgebraicAgents._step!(::AbstractStructuredToken) = nothing
+
+# Tell if an agent is assigned to a transition, as a resource.
+isblocked(a::AbstractStructuredToken) = !isnothing(get_bound_transition(a))
+
+# Add a record that an agent was used as "species" in a "transition".
+function add_to_log!(a::AbstractStructuredToken, species::Symbol, t, transition::Transition)
+ return push!(a.past_bonds, (species, Float64(t), transition))
+end
+
+# Set the transition a token is bound to.
+get_bound_transition(a::AbstractStructuredToken) = a.bound_transition
+function set_bound_transition!(a::AbstractStructuredToken, t::Union{Nothing,Transition})
+ return a.bound_transition = t
+end
+
+# Priority with which an unbound agent will be assigned to a transition.
+priority(a::AbstractStructuredToken, transition) = 0.0
+
+# What species (place) is an agent currently assigned to.
+get_species(a::AbstractStructuredToken) = a.species
+set_species!(a::AbstractStructuredToken, species::Symbol) = a.species = species
diff --git a/src/interface/create.jl b/src/interface/create.jl
index 1a82374..faf5bdf 100644
--- a/src/interface/create.jl
+++ b/src/interface/create.jl
@@ -1,6 +1,6 @@
# reaction network DSL: CREATE part; reaction line and event parsing
-export @ReactionNetwork
+export @ReactionNetworkSchema
using MacroTools: prewalk, postwalk, striplines, isexpr
using Symbolics: build_function, get_variables
@@ -45,7 +45,7 @@ Custom functions and sampleable objects can be used as numeric parameters. Note
# Examples
```julia
-acs = @ReactionNetwork begin
+acs = @ReactionNetworkSchema begin
1.0, X ⟶ Y
1.0, X ⟶ Y, priority => 6.0, prob => 0.7, capacity => 3.0
1.0, ∅ --> (Poisson(0.3γ)X, Poisson(0.5)Y)
@@ -57,17 +57,17 @@ end
@solve_and_plot acs
```
"""
-macro ReactionNetwork end
+macro ReactionNetworkSchema end
-macro ReactionNetwork()
+macro ReactionNetworkSchema()
return make_ReactionNetwork(:())
end
-macro ReactionNetwork(ex)
+macro ReactionNetworkSchema(ex)
return make_ReactionNetwork(ex; eval_module = __module__)
end
-macro ReactionNetwork(ex, args...)
+macro ReactionNetworkSchema(ex, args...)
return make_ReactionNetwork(
generate(Expr(:braces, ex, args...); eval_module = __module__);
eval_module = __module__,
@@ -78,7 +78,7 @@ function make_ReactionNetwork(ex::Expr; eval_module = @__MODULE__)
blockex = generate(ex; eval_module)
blockex = unblock_shallow!(blockex)
- return :(ReactionNetwork(get_data($(QuoteNode(blockex)))...))
+ return :(ReactionNetworkSchema(get_data($(QuoteNode(blockex)))...))
end
### Functions that process the input and rephrase it as a reaction system ###
@@ -146,8 +146,8 @@ function recursively_expand_actions!(evs, condex, event)
end
function expand_rate(rate)
- rate = if !(isexpr(rate, :macrocall) && (macroname(rate) == :per_step))
- :(rand(Poisson(max($rate, 0))))
+ rate = if !(isexpr(rate, :macrocall) && (macroname(rate) == :deterministic))
+ :(rand(Poisson(max(state.dt * $rate, 0))))
else
rate.args[3]
end
@@ -271,7 +271,7 @@ function prune_reaction_line!(pcs, reactants, line)
return line
end
-function recursively_find_reactants!(reactants, pcs, ex::SampleableValues)
+function recursively_find_reactants!(reactants, pcs, ex)
if typeof(ex) != Expr || isexpr(ex, :.) || (ex.head == :escape)
if (ex == 0 || in(ex, empty_set))
return :∅
@@ -293,8 +293,13 @@ function recursively_find_reactants!(reactants, pcs, ex::SampleableValues)
isexpr(ex.args[i], :tuple) ? ex.args[i].args[2] : ex.args[i],
)
end
+ elseif isexpr(ex, :macrocall) && macroname(ex) ∈ [:structured, :move]
+ return ex
elseif isexpr(ex, :macrocall)
- recursively_find_reactants!(reactants, pcs, ex.args[3])
+ pass_value = ex.args[3] isa QuoteNode ? ex.args[3].value : ex.args[3]
+ recursively_find_reactants!(reactants, pcs, pass_value)
+ elseif isexpr(ex, :call)
+ push!(reactants, ex.args[1])
else
push!(reactants, underscorize(ex))
end
diff --git a/src/interface/plots.jl b/src/interface/plots.jl
new file mode 100644
index 0000000..23892e0
--- /dev/null
+++ b/src/interface/plots.jl
@@ -0,0 +1,31 @@
+using Plots
+
+function plot_df(df::DataFrames.DataFrame, t_ix = 1)
+ data = Matrix(df)
+ t = @view data[:, t_ix]
+ data_ = @view data[:, setdiff(1:size(data, 2), (t_ix,))]
+ colnames = reshape(DataFrames.names(df)[setdiff(1:size(data, 2), (t_ix,))], 1, :)
+
+ return Plots.plot(t, data_; labels = colnames, xlabel = "t")
+end
+
+# plot reduction
+function AlgebraicAgents._draw(
+ prob::ReactionNetworkProblem,
+ vars = string.(prob.acs[:, :specName]);
+ kwargs...,
+)
+ p = plot()
+ for var in vars
+ p = plot!(
+ p,
+ prob.sol[!, "t"],
+ prob.sol[!, var];
+ label = "$var",
+ xlabel = "time",
+ ylabel = "quantity",
+ kwargs...,
+ )
+ end
+ return p
+end
diff --git a/src/interface/reaction_parser.jl b/src/interface/reaction_parser.jl
index ae92ae3..1f83113 100644
--- a/src/interface/reaction_parser.jl
+++ b/src/interface/reaction_parser.jl
@@ -3,7 +3,7 @@
using MacroTools: postwalk
struct FoldedReactant
- species::Symbol
+ species::Union{Expr,Symbol}
stoich::SampleableValues
modality::Set{Symbol}
end
@@ -29,7 +29,7 @@ function recursively_choose(r_line, state)
end
end
-function extract_reactants(r_line, state::ReactiveDynamicsState)
+function extract_reactants(r_line, state::ReactionNetworkProblem)
r_line = recursively_choose(r_line, state)
return recursive_find_reactants!(
@@ -63,6 +63,9 @@ function recursive_find_reactants!(
for i = 2:length(ex.args)
recursive_find_reactants!(ex.args[i], mult, mods, reactants)
end
+ elseif isexpr(ex, :call) ||
+ (ex.head == :macrocall && macroname(ex) ∈ [:structured, :move])
+ push!(reactants, FoldedReactant(ex, mult, mods))
elseif ex.head == :macrocall
mods = copy(mods)
macroname(ex) in species_modalities && push!(mods, macroname(ex))
diff --git a/src/interface/solve.jl b/src/interface/solve.jl
index a7484e9..e38956c 100644
--- a/src/interface/solve.jl
+++ b/src/interface/solve.jl
@@ -1,64 +1,8 @@
-export @problematize, @solve, @plot
-export @optimize, @fit, @fit_and_plot, @build_solver
+export @agentize
-using DifferentialEquations: DiscreteProblem, EnsembleProblem, FunctionMap, EnsembleSolution
import MacroTools
import Plots
-"""
-Convert a model to a `DiscreteProblem`. If passed a problem instance, return the instance.
-
-# Examples
-
-```julia
-@problematize acs tspan = 1:100
-```
-"""
-macro problematize(acsex, args...)
- args, kwargs = args_kwargs(args)
- quote
- if $(esc(acsex)) isa DiscreteProblem
- $(esc(acsex))
- else
- DiscreteProblem($(esc(acsex)), $(args...); $(kwargs...))
- end
- end
-end
-
-"""
-Solve the problem. Solverargs passed at the calltime take precedence.
-
-# Examples
-
-```julia
-@solve prob
-@solve prob tspan = 1:100
-@solve prob tspan = 100 trajectories = 20
-```
-"""
-macro solve(probex, args...)
- args, kwargs = args_kwargs(args)
- mode = find_kwargex_delete!(kwargs, :mode, nothing)
- !isnothing(findfirst(el -> el.args[1] == :trajectories, kwargs)) && (mode = :ensemble)
-
- quote
- prob = if $(esc(probex)) isa DiscreteProblem
- $(esc(probex))
- else
- DiscreteProblem($(esc(probex)), $(args...); $(kwargs...))
- end
- if $(preserve_sym(mode)) == :ensemble
- solve(
- EnsembleProblem(prob; prob_func = get_prob_func(prob)),
- FunctionMap(),
- $(kwargs...),
- )
- else
- solve(prob)
- end
- end
-end
-
# auxiliary plotting functions
function plot_summary(s, labels, ixs; kwargs...)
isempty(ixs) && return @warn "Set of species to plot must be non-empty!"
@@ -255,248 +199,3 @@ function plot_from_log(state, record_type, ixs; kwargs...)
kwargs...,
)
end
-
-"""
- @optimize acset objective ... ... opts...
-
-Take an acset and optimize given functional.
-
-Objective is an expression which may reference the model's variables and parameters, i.e., `A+β`.
-The values to optimized are listed using their symbolic names; unless specified, the initial value is inferred from the model.
-The vector of free variables passed to the `NLopt` solver has the form `[free_vars; free_params]`; order of vars and params, respectively, is preserved.
-
-By default, the functional is minimized. Specify `objective=max` to perform maximization.
-
-Propagates `NLopt` solver arguments; see [NLopt documentation](https://github.com/JuliaOpt/NLopt.jl).
-
-# Examples
-
-```julia
-@optimize acs abs(A - B) A B = 20.0 α = 2.0 lower_bounds = 0 upper_bounds = 100
-@optimize acss abs(A - B) A B = 20.0 α = 2.0 upper_bounds = [200, 300, 400] maxeval = 200 objective =
- min
-```
-"""
-macro optimize(acsex, obex, args...)
- args_all = args
- args, kwargs = args_kwargs(args)
- min_t = find_kwargex_delete!(kwargs, :min_t, -Inf)
- max_t = find_kwargex_delete!(kwargs, :max_t, Inf)
- final_only = find_kwargex_delete!(kwargs, :final_only, false)
- okwargs = filter(ex -> ex.args[1] in [:loss, :trajectories], kwargs)
-
- quote
- u0, p = get_free_vars($(esc(acsex)), $(QuoteNode(args_all)))
- prob_ = DiscreteProblem($(esc(acsex)))
- prep_u0!(u0, prob_)
- prep_params!(p, prob_)
-
- init_p = [k => v for (k, v) in p]
- init_vec = if length(u0) > 0
- ComponentVector{Float64}(; species = collect(wvalues(u0)), init_p...)
- else
- ComponentVector{Float64}(; init_p...)
- end
-
- o = build_loss_objective(
- $(esc(acsex)),
- init_vec,
- u0,
- p,
- $(QuoteNode(obex));
- min_t = $min_t,
- max_t = $max_t,
- final_only = $final_only,
- $(okwargs...),
- )
-
- optim!(o, init_vec; $(kwargs...))
- end
-end
-
-"""
- @fit acset data_points time_steps empiric_variables ... ... opts...
-
-Take an acset and fit initial values and parameters to empirical data.
-
-The values to optimized are listed using their symbolic names; unless specified, the initial value is inferred from the model.
-The vector of free variables passed to the `NLopt` solver has the form `[free_vars; free_params]`; order of vars and params, respectively, is preserved.
-
-Propagates `NLopt` solver arguments; see [NLopt documentation](https://github.com/JuliaOpt/NLopt.jl).
-
-# Examples
-
-```julia
-t = [1, 50, 100]
-data = [80 30 20]
-@fit acs data t vars = A B = 20 A α # fit B, A, α; empirical data is for variable A
-```
-"""
-macro fit(acsex, data, t, args...)
- args_all = args
- args, kwargs = args_kwargs(args)
- okwargs = filter(ex -> ex.args[1] in [:loss, :trajectories], kwargs)
- vars = (ix = findfirst(ex -> ex.args[1] == :vars, kwargs);
- !isnothing(ix) ? (v = kwargs[ix].args[2];
- deleteat!(kwargs, ix);
- v) : :())
-
- quote
- u0, p = get_free_vars($(esc(acsex)), $(QuoteNode(args_all)))
- vars = get_vars($(esc(acsex)), $(QuoteNode(vars)))
- prob_ = DiscreteProblem($(esc(acsex)))
- prep_u0!(u0, prob_)
- prep_params!(p, prob_)
-
- init_p = [k => v for (k, v) in p]
- init_vec = if length(u0) > 0
- ComponentVector{Float64}(; species = collect(wvalues(u0)), init_p...)
- else
- ComponentVector{Float64}(; init_p...)
- end
-
- o = build_loss_objective_datapoints(
- $(esc(acsex)),
- init_vec,
- u0,
- p,
- $(esc(t)),
- $(esc(data)),
- vars;
- $(okwargs...),
- )
-
- optim!(o, init_vec; $(kwargs...))
- end
-end
-
-"""
- @fit acset data_points time_steps empiric_variables ... ... opts...
-
-Take an acset, fit initial values and parameters to empirical data, and plot the result.
-
-The values to optimized are listed using their symbolic names; unless specified, the initial value is inferred from the model.
-The vector of free variables passed to the `NLopt` solver has the form `[free_vars; free_params]`; order of vars and params, respectively, is preserved.
-
-Propagates `NLopt` solver arguments; see [NLopt documentation](https://github.com/JuliaOpt/NLopt.jl).
-
-# Examples
-
-```julia
-t = [1, 50, 100]
-data = [80 30 20]
-@fit acs data t vars = A B = 20 A α # fit B, A, α; empirical data is for variable A
-```
-"""
-macro fit_and_plot(acsex, data, t, args...)
- args_all = args
- trajectories = get_kwarg(args, :trajectories, 1)
- args, kwargs = args_kwargs(args)
- okwargs = filter(ex -> ex.args[1] in [:loss, :trajectories], kwargs)
- vars = (ix = findfirst(ex -> ex.args[1] == :vars, kwargs);
- !isnothing(ix) ? (v = kwargs[ix].args[2];
- deleteat!(kwargs, ix);
- v) : :())
-
- quote
- u0, p = get_free_vars($(esc(acsex)), $(QuoteNode(args_all)))
- vars = get_vars($(esc(acsex)), $(QuoteNode(vars)))
- prob_ = DiscreteProblem($(esc(acsex)); suppress_warning = true)
- prep_u0!(u0, prob_)
- prep_params!(p, prob_)
-
- init_p = [k => v for (k, v) in p]
- init_vec = if length(u0) > 0
- ComponentVector{Float64}(; species = collect(wvalues(u0)), init_p...)
- else
- ComponentVector{Float64}(; init_p...)
- end
-
- o = build_loss_objective_datapoints(
- $(esc(acsex)),
- init_vec,
- u0,
- p,
- $(esc(t)),
- $(esc(data)),
- vars;
- $(okwargs...),
- )
-
- r = optim!(o, init_vec; $(kwargs...))
- if r[3] != :FORCED_STOP
- s_ = build_parametrized_solver(
- $(esc(acsex)),
- init_vec,
- u0,
- p;
- trajectories = $trajectories,
- )
- sol = first(s_(init_vec))
- sol_ = first(s_(r[2]))
-
- p = Plots.plot(
- sol;
- idxs = vars,
- label = "(initial) " .*
- reshape(String.($(esc(acsex))[:, :specName])[vars], 1, :),
- )
- Plots.plot!(
- p,
- $(esc(t)),
- transpose($(esc(data)));
- label = "(empirical) " .*
- reshape(String.($(esc(acsex))[:, :specName])[vars], 1, :),
- )
- Plots.plot!(
- p,
- sol_;
- idxs = vars,
- label = "(fitted) " .*
- reshape(String.($(esc(acsex))[:, :specName])[vars], 1, :),
- )
- p
- else
- :FORCED_STOP
- end
- end
-end
-
-"""
- @build_solver acset ... ... opts...
-
-Take an acset and export a solution as a function of free vars and free parameters.
-
-# Examples
-
-```julia
-solver = @build_solver acs S α β # function of variable S and parameters α, β
-solver([S, α, β])
-```
-"""
-macro build_solver(acsex, args...)
- args_all = args#; args, kwargs = args_kwargs(args)
- trajectories = get_kwarg(args, :trajectories, 1)
-
- quote
- u0, p = get_free_vars($(esc(acsex)), $(QuoteNode(args_all)))
- prob_ = DiscreteProblem($(esc(acsex)))
- prep_u0!(u0, prob_)
- prep_params!(p, prob_)
-
- init_p = [k => v for (k, v) in p]
- init_vec = if length(u0) > 0
- ComponentVector{Float64}(; species = collect(wvalues(u0)), init_p...)
- else
- ComponentVector{Float64}(; init_p...)
- end
-
- build_parametrized_solver_(
- $(esc(acsex)),
- init_vec,
- u0,
- p;
- trajectories = $trajectories,
- )
- end
-end
diff --git a/src/interface/update.jl b/src/interface/update.jl
index 326c3f6..cf3c354 100644
--- a/src/interface/update.jl
+++ b/src/interface/update.jl
@@ -70,7 +70,7 @@ macro name_transition(acsex, exs...)
acs = $(esc(acsex))
ixs = findall(
i -> string(acs[i, :transName]) == $(string(ex.args[1])),
- 1:nparts(acs, :T),
+ parts(acs, :T),
)
foreach(i -> acs[i, :transName] = $(string(ex.args[2])), ixs)
end
@@ -489,7 +489,7 @@ Add a jump process (with specified Poisson intensity per unit time step) to a mo
macro jump(acsex, inex, acex)
return push_to_acs!(
acsex,
- Expr(:&&, Expr(:call, :rand, :(Poisson(max(@solverarg(:tstep) * $inex, 0)))), acex),
+ Expr(:&&, Expr(:call, :rand, :(Poisson(max(state.dt * $inex, 0)))), acex),
)
end
diff --git a/src/loadsave.jl b/src/loadsave.jl
index da3d8b4..4002a24 100644
--- a/src/loadsave.jl
+++ b/src/loadsave.jl
@@ -16,7 +16,7 @@ const objects_aliases = Dict(
:obs => "obs",
)
-const RN_attrs = string.(propertynames(ReactionNetwork().subparts))
+const RN_attrs = string.(propertynames(ReactionNetworkSchema().subparts))
function get_attrs(object)
object = object isa Symbol ? objects_aliases[object] : object
@@ -24,11 +24,11 @@ function get_attrs(object)
return filter(x -> occursin(object, x), RN_attrs)
end
-function export_network(acs::ReactionNetwork)
+function export_network(acs::ReactionNetworkSchema)
dict = Dict()
for (key, val) in objects_aliases
push!(dict, val => [])
- for i = 1:nparts(acs, key)
+ for i in parts(acs, key)
dict_ = Dict()
for attr in get_attrs(val)
attr_val = acs[i, Symbol(attr)]
@@ -44,13 +44,16 @@ function export_network(acs::ReactionNetwork)
end
function load_network(dict::Dict)
- acs = ReactionNetwork()
+ acs = ReactionNetworkSchema()
for (key, val) in objects_aliases
val == "prm" && continue
for row in get(dict, val, [])
i = add_part!(acs, key)
for (attr, attrval) in row
set_subpart!(acs, i, Symbol(attr), attrval)
+ if (acs[i, Symbol(attr)] isa String && !(contains(attr, "name")))
+ acs[i, Symbol(attr)] = MacroTools.striplines(Meta.parse(attrval))
+ end
end
end
end
@@ -113,7 +116,7 @@ function import_network(path::AbstractString)
end
end
-function export_network(acs::ReactionNetwork, path::AbstractString)
+function export_network(acs::ReactionNetworkSchema, path::AbstractString)
if splitext(path)[2] == ".csv"
exported_network = export_network(acs)
paths = DataFrame(; type = [], path = [])
@@ -230,10 +233,10 @@ Export a solution as a `DataFrame`.
```
"""
macro export_solution_as_table(solex, pathex = "sol.jld2")
- return :(DataFrame($(esc(solex))))
+ return :(DataFrame($(esc(solex)).sol))
end
-get_DataFrame(sol) = sol isa EnsembleSolution ? DataFrame(sol)[!, [:u, :t]] : DataFrame(sol)
+get_DataFrame(sol) = sol.sol
"""
@export_solution_as_csv sol
diff --git a/src/operators/equalize.jl b/src/operators/equalize.jl
index cbe357d..f52672f 100644
--- a/src/operators/equalize.jl
+++ b/src/operators/equalize.jl
@@ -1,4 +1,4 @@
-export @equalize
+export equalize!, @equalize
expand_name_ff(ex) =
if ex isa Expr && isexpr(ex, :macrocall)
@@ -21,13 +21,13 @@ function get_eqs_ff(eq)
end
end
-function equalize!(acs::ReactionNetwork, eqs = [])
+function equalize!(acs::ReactionNetworkSchema, eqs = [])
specmap = Dict()
for block in eqs
block_alias = findfirst(e -> e[1] == :alias, block)
block_alias = !isnothing(block_alias) ? block[block_alias][2] : first(block)[2]
species_ixs = Int64[]
- for e in block, i = 1:nparts(acs, :S)
+ for e in block, i in parts(acs, :S)
(
(i == e[2]) ||
(
@@ -53,9 +53,10 @@ function equalize!(acs::ReactionNetwork, eqs = [])
for attr in propertynames(acs.subparts)
attr == :specName && continue
attr_ = acs[:, attr]
- for i = 1:length(attr_)
+ for i in eachindex(attr_)
attr_[i] = escape_ref(attr_[i], collect(keys(specmap)))
attr_[i] = recursively_substitute_vars!(specmap, attr_[i])
+ acs[i, attr] = attr_[i]
end
end
diff --git a/src/operators/joins.jl b/src/operators/joins.jl
index 40ebc73..c0183cb 100644
--- a/src/operators/joins.jl
+++ b/src/operators/joins.jl
@@ -1,24 +1,24 @@
# model joins
-export @join
+export union_acs!, @join
using MacroTools
using MacroTools: prewalk
"""
-Merge `acs2` onto `acs1`, the attributes in `acs2` taking precedence. Identify respective species given `eqs`, renaming species in `acs2`.
+Merge `acs2` into `acs1`, the attributes in `acs2` taking precedence. Identify respective species given `eqs`, renaming species in `acs2`.
"""
-function union_acs!(acs1, acs2, name = gensym("acs_"), eqs = [])
+function union_acs!(acs1, acs2, name = gensym("acs"), eqs = [])
acs2 = deepcopy(acs2)
prepend!(acs2, name, eqs)
- for i = 1:nparts(acs2, :S)
+
+ for i in parts(acs2, :S)
inc = incident(acs1, acs2[i, :specName], :specName)
- isempty(inc) && (inc = add_part!(acs1, :S; specName = acs2[i, :specName]);
- assign_defaults!(acs1))
- return (acs1, acs2)
- println(first(inc))
- println(acs1[first(inc), :specModality])
- println()
- println(acs2[:, :specModality])
+
+ if isempty(inc)
+ inc = add_part!(acs1, :S; specName = acs2[i, :specName])
+ assign_defaults!(acs1)
+ end
+
union!(acs1[first(inc), :specModality], acs2[i, :specModality])
for attr in propertynames(acs1.subparts)
@@ -30,7 +30,9 @@ function union_acs!(acs1, acs2, name = gensym("acs_"), eqs = [])
new_trans_ix = add_parts!(acs1, :T, nparts(acs2, :T))
for attr in propertynames(acs2.subparts)
!occursin("trans", string(attr)) && continue
- acs1[new_trans_ix, attr] .= acs2[:, attr]
+ for (ix1, ix2) in enumerate(new_trans_ix)
+ acs1[ix2, attr] = acs2[ix1, attr]
+ end
end
foreach(
@@ -41,13 +43,13 @@ function union_acs!(acs1, acs2, name = gensym("acs_"), eqs = [])
new_trans_ix,
)
- for i = 1:nparts(acs2, :P)
+ for i in parts(acs2, :P)
inc = incident(acs1, acs2[i, :prmName], :prmName)
isempty(inc) && (inc = add_part!(acs1, :P; prmName = acs2[i, :prmName]))
!ismissing(acs2[i, :prmVal]) && (acs1[first(inc), :prmVal] = acs2[i, :prmVal])
end
- for i = 1:nparts(acs2, :M)
+ for i in parts(acs2, :M)
inc = incident(acs1, acs2[i, :metaKeyword], :metaKeyword)
isempty(inc) && (inc = add_part!(acs1, :M; metaKeyword = acs2[i, :metaKeyword]))
!ismissing(acs2[i, :metaVal]) && (acs1[first(inc), :metaVal] = acs2[i, :metaVal])
@@ -59,9 +61,9 @@ end
"""
Prepend species names with a model identifier (unless a global species name).
"""
-function prepend!(acs::ReactionNetwork, name = gensym("acs"), eqs = [])
+function prepend!(acs::ReactionNetworkSchema, name = gensym("acs"), eqs = [])
specmap = Dict()
- for i = 1:nparts(acs, :S)
+ for i in parts(acs, :S)
new_name = normalize_name(name, i, acs[i, :specName], eqs)
push!(specmap, acs[i, :specName] => (acs[i, :specName] = new_name))
end
@@ -69,10 +71,10 @@ function prepend!(acs::ReactionNetwork, name = gensym("acs"), eqs = [])
for attr in propertynames(acs.subparts)
attr == :specName && continue
attr_ = acs[:, attr]
- for i = 1:length(attr_)
+ for i in eachindex(attr_)
attr_[i] = escape_ref(attr_[i], collect(keys(specmap)))
attr_[i] = recursively_substitute_vars!(specmap, attr_[i])
- attr_[i] isa Expr && (attr_[i] = prepend_obs(attr_[i], name))
+ acs[i, attr] = attr_[i]
end
end
@@ -199,7 +201,7 @@ Model variables / parameter values and metadata are propagated; the last model t
macro join(exs...)
callex = :(
begin
- acs_new = ReactionNetwork()
+ acs_new = ReactionNetworkSchema()
end
)
exs = collect(exs)
diff --git a/src/optim.jl b/src/optim.jl
deleted file mode 100644
index a30e179..0000000
--- a/src/optim.jl
+++ /dev/null
@@ -1,244 +0,0 @@
-function build_parametrized_solver(acs, init_vec, u0, params; trajectories = 1)
- prob = DiscreteProblem(acs)
- vars = prob.p[:__state__][:, :specInitUncertainty]
- init_vec = deepcopy(init_vec)
-
- function (vec)
- vec = vec isa ComponentVector ? vec : (init_vec .= vec)
- data = []
- for _ = 1:trajectories
- prob.p[:__state__] = deepcopy(prob.p[:__state0__])
- for i in eachindex(prob.u0)
- rv = randn() * vars[i]
- prob.u0[i] = if (sign(rv + prob.u0[i]) == sign(prob.u0[i]))
- rv + prob.u0[i]
- else
- prob.u0[i]
- end
- end
-
- for (i, k) in enumerate(wkeys(u0))
- prob.u0[k] = vec.species[i]
- end
- for k in wkeys(params)
- prob.p[k] = vec[k]
- end
-
- sync!(prob.p[:__state__], prob.u0, prob.p)
- push!(data, solve(prob))
- end
-
- return data
- end
-end
-
-function build_parametrized_solver_(acs, init_vec, u0, params; trajectories = 1)
- prob = DiscreteProblem(acs)
- vars = prob.p[:__state__][:, :specInitUncertainty]
- init_vec = deepcopy(init_vec)
-
- function (vec)
- vec = vec isa ComponentVector ? vec : (init_vec .= vec; init_vec)
- data = map(1:trajectories) do _
- prob.p[:__state__] = deepcopy(prob.p[:__state0__])
- for i in eachindex(prob.u0)
- rv = randn() * vars[i]
- prob.u0[i] = if (sign(rv + prob.u0[i]) == sign(prob.u0[i]))
- rv + prob.u0[i]
- else
- prob.u0[i]
- end
- end
-
- for (i, k) in enumerate(wkeys(u0))
- prob.u0[k] = vec.species[i]
- end
- for k in wkeys(params)
- prob.p[k] = vec[k]
- end
-
- sync!(prob.p[:__state__], prob.u0, prob.p)
-
- return solve(prob)
- end
-
- return trajectories == 1 ? data[1] : EnsembleSolution(data, 0.0, true)
- end
-end
-
-## optimization part
-
-BOUND_DEFAULT = 5000
-
-function optim!(obj, init; nlopt_kwargs...)
- nlopt_kwargs = Dict(nlopt_kwargs)
- alg = pop!(nlopt_kwargs, :algorithm, :GN_DIRECT)
-
- opt = Opt(alg, length(init))
-
- # match to a ComponentVector
- foreach(
- o -> setproperty!(opt, o...),
- filter(x -> x[1] in propertynames(opt), nlopt_kwargs),
- )
- if get(nlopt_kwargs, :objective, min) == min
- (opt.min_objective = obj)
- else
- (opt.max_objective = obj)
- end
-
- return optimize(opt, deepcopy(init))
-end
-
-const n_steps = 100
-
-# loss objective given an objective expression
-function build_loss_objective(
- acs,
- init_vec,
- u0,
- params,
- obex;
- loss = identity,
- trajectories = 1,
- min_t = -Inf,
- max_t = Inf,
- final_only = false,
-)
- ob = eval(get_wrap_fun(acs)(obex))
- obj_ = build_parametrized_solver(acs, init_vec, u0, params; trajectories)
-
- function (vec, _)
- ls = []
- for sol in obj_(vec)
- t_points = if final_only
- [last(sol.t)]
- else
- min_t = max(min_t, sol.prob.tspan[1])
- max_t = min(max_t, sol.prob.tspan[2])
-
- range(min_t, max_t; length = n_steps)
- end
-
- push!(
- ls,
- mean(t -> loss(ob(as_state(sol(t), t, sol.prob.p[:__state__]))), t_points),
- )
- end
-
- return mean(ls)
- end
-end
-
-# loss objective given empirical data
-function build_loss_objective_datapoints(
- acs,
- init_vec,
- u0,
- params,
- t,
- data,
- vars;
- loss = abs2,
- trajectories = 1,
-)
- obj_ = build_parametrized_solver(acs, init_vec, u0, params; trajectories)
-
- function (vec, _)
- ls = []
- for sol in obj_(vec)
- push!(
- ls,
- mean(
- t ->
- sum(i -> loss(sol(t[2])[i[2]] - data[i[1], t[1]]), enumerate(vars)),
- enumerate(t),
- ),
- )
- end
-
- return mean(ls)
- end
-end
-
-# set initial model parameter values in an optimization problem
-function prep_params!(params, prob)
- for (k, v) in params
- (v === NaN) && wset!(params, k, get(prob.p, k, NaN))
- end
- any(p -> (p[2] === NaN) && @warn("Uninitialized prm: $p"), params)
-
- return params
-end
-
-# set initial model variable values in an optimization problem
-function prep_u0!(u0, prob)
- for (k, v) in u0
- (v === NaN) && wset!(u0, k, get(prob.u0, k, NaN))
- end
- any(u -> (u[2] === NaN) && @warn("Uninitialized prm: $(u[1])"), u0)
-
- return u0
-end
-
-"""
-Extract symbolic variables referenced in `acs`, `args`.
-"""
-function get_free_vars(acs, args)
- u0_syms = collect(acs[:, :specName])
- p_syms = collect(acs[:, :prmName])
- u0 = []
- p = []
-
- for arg in args
- if arg isa Symbol
- (k, v) = (arg, NaN)
- elseif isexpr(arg, :(=))
- (k, v) = (arg.args[1], arg.args[2])
- else
- continue
- end
-
- if ((k in u0_syms || k isa Number) && !in(k, wkeys(u0)))
- push!(u0, k => v)
- elseif (k in p_syms && !in(k, wkeys(p)))
- push!(p, k => v)
- end
- end
-
- u0_ = []
- for (k, v) in u0
- if k isa Number
- push!(u0_, Int(k) => v)
- else
- for i = 1:length(subpart(acs, :specName))
- (acs[i, :specName] == k) && (push!(u0_, i => v); break)
- end
- end
- end
-
- return u0_, p
-end
-
-"""
-Resolve symbolic / positional model variable names to positional.
-"""
-function get_vars(acs, args)
- (args == :()) && return args
- args_ = []
-
- for arg in (MacroTools.isexpr(args, :vect, :tuple) ? args.args : [args])
- arg = recursively_expand_dots(arg)
- if arg isa Number
- push!(args_, Int(arg))
- else
- for i = 1:length(subpart(acs, :specName))
- !isnothing(acs[i, :specName]) &&
- (acs[i, :specName] == arg) &&
- (push!(args_, i); break)
- end
- end
- end
-
- return args_
-end
diff --git a/src/solvers.jl b/src/solvers.jl
index e2f22f1..46d3e6a 100644
--- a/src/solvers.jl
+++ b/src/solvers.jl
@@ -1,9 +1,7 @@
-# assortment of SciML-compatible problem solvers
-
-export DiscreteProblem
-
-using DiffEqBase, DifferentialEquations
using Distributions
+using Random
+
+export ReactionNetworkProblem
function get_sampled_transition(state, i)
transition = Dict{Symbol,Any}()
@@ -17,7 +15,7 @@ Compute resource requirements given transition quantities.
"""
function get_reqs_init!(reqs, qs, state)
reqs .= 0.0
- for i = 1:size(reqs, 2)
+ for i in axes(reqs, 2)
for tok in state[i, :transLHS]
!any(m -> m in tok.modality, [:rate, :nonblock]) &&
(reqs[tok.index, i] += qs[i] * tok.stoich)
@@ -32,11 +30,16 @@ Compute resource requirements given transition quantities.
"""
function get_reqs_ongoing!(reqs, qs, state)
reqs .= 0.0
- for i = 1:length(state.ongoing_transitions)
+ for i in eachindex(state.ongoing_transitions)
for tok in state.ongoing_transitions[i][:transLHS]
in(:rate, tok.modality) &&
(state.ongoing_transitions[i][:transCycleTime] > 0) &&
- (reqs[tok.index, i] += qs[i] * tok.stoich * state.solverargs[:tstep])
+ (reqs[tok.index, i] += qs[i] * tok.stoich * state.dt)
+ if in(:rate, tok.modality) && in(tok.species, state.structured_token)
+ error(
+ "Modality `:rate` is not supported for structured species in transition $(trans[:transName]).",
+ )
+ end
in(:nonblock, tok.modality) && (reqs[tok.index, i] += qs[i] * tok.stoich)
end
end
@@ -57,7 +60,7 @@ end
function alloc_weighted!(reqs, u, priorities, state)
allocs = zero(reqs)
- for i = 1:size(reqs, 1)
+ for i in axes(reqs, 1)
s = sum(reqs[i, :])
u[i] >= s && (allocs[i, :] .= reqs[i, :]; continue)
foreach(j -> allocs[i, j] = reqs[i, j] * priorities[j], 1:size(reqs, 2))
@@ -71,7 +74,7 @@ end
function alloc_greedy!(reqs, u, priorities, state)
allocs = zero(reqs)
sorted_trans = sort(1:size(reqs, 2); by = i -> -priorities[i])
- for i = 1:size(reqs, 1)
+ for i in axes(reqs, 1)
s = sum(reqs[i, :])
u[i] >= s && (allocs[i, :] .= reqs[i, :]; continue)
a = u[i]
@@ -99,21 +102,25 @@ function get_frac_satisfied(allocs, reqs, state)
return qs
end
+isinteger(x::Number) = x == trunc(x)
+
"""
Given available allocations and qties of transitions requested to spawn, return number of spawned transitions. Update `alloc` to match actual allocation.
"""
function get_init_satisfied(allocs, qs, state)
reqs = zero(allocs)
- for i = 1:size(allocs, 2)
+ for i in axes(allocs, 2)
all(allocs[:, i] .>= 0) || (allocs[:, i] .= 0.0; qs[i] = 0)
for tok in state[i, :transLHS]
!any(m -> m in tok.modality, [:rate, :nonblock]) &&
(reqs[tok.index, i] += tok.stoich)
end
end
+
for i in eachindex(allocs)
allocs[i] = reqs[i] == 0.0 ? Inf : floor(allocs[i] / reqs[i])
end
+
foreach(i -> qs[i] = min(qs[i], minimum(allocs[:, i])), 1:size(reqs, 2))
foreach(i -> allocs[:, i] .= reqs[:, i] * qs[i], 1:size(reqs, 2))
@@ -123,9 +130,8 @@ end
"""
Evolve transitions, spawn new transitions.
"""
-function evolve!(u, state)
- update_u!(state, u)
- actual_allocs = zero(u)
+function evolve!(state)
+ actual_allocs = zero(state.u)
## schedule new transitions
reqs = zeros(nparts(state, :S), nparts(state, :T))
@@ -133,11 +139,12 @@ function evolve!(u, state)
foreach(
i -> qs[i] = state[i, :transRate] * state[i, :transMultiplier],
- 1:nparts(state, :T),
+ parts(state, :T),
)
qs .= ceil.(Ref(Int), qs)
- for i = 1:nparts(state, :T)
- new_instances = state.solverargs[:tstep] * qs[i] + state[i, :transToSpawn]
+
+ for i in parts(state, :T)
+ new_instances = qs[i] + state[i, :transToSpawn]
capacity =
state[i, :transCapacity] -
count(t -> t[:transHash] == state[i, :transHash], state.ongoing_transitions)
@@ -147,8 +154,9 @@ function evolve!(u, state)
end
reqs = get_reqs_init!(reqs, qs, state)
- allocs =
- get_allocs!(reqs, u, state, state[:, :transPriority], state.solverargs[:strategy])
+
+ allocs = get_allocs!(reqs, state.u, state, state[:, :transPriority], state.p[:strategy])
+
qs .= get_init_satisfied(allocs, qs, state)
push!(
@@ -159,18 +167,67 @@ function evolve!(u, state)
[(hash, q) for (hash, q) in zip(state[:, :transHash], qs)]...,
),
)
- u .-= sum(allocs; dims = 2)
+ state.u .-= sum(allocs; dims = 2)
actual_allocs .+= sum(allocs; dims = 2)
+ structured_token = collect(values(inners(getagent(state, "structured"))))
+
# add spawned transitions to the heap
- for i = 1:nparts(state, :T)
- qs[i] != 0 && push!(
- state.ongoing_transitions,
- Transition(get_sampled_transition(state, i), state.t, qs[i], 0.0),
- )
+ for i in parts(state, :T)
+ if qs[i] != 0
+ transition = Transition(
+ string(state[i, :transName]) * "_@$(state.t)",
+ i,
+ get_sampled_transition(state, i),
+ AbstractAlgebraicAgent[],
+ AbstractAlgebraicAgent[],
+ [],
+ state.t,
+ qs[i],
+ 0.0,
+ )
+ push!(state.ongoing_transitions, transition)
+
+ bound = transition.bound_structured_agents
+ structured_to_agents = transition.structured_to_agents
+
+ for (j, type) in enumerate(state.acs[:, :specName])
+ if type ∈ state.structured_token
+ if !isinteger(allocs[j, i])
+ error(
+ "For structured species, stoichiometry coefficient must be integer in transition $i.",
+ )
+ end
+
+ available_species = filter(
+ a -> get_species(a) == type && !isblocked(a),
+ structured_token,
+ )
+
+ sort!(
+ available_species;
+ by = a -> priority(a, state.acs[i, :transName]),
+ rev = true,
+ )
+
+ ix = 1
+ while allocs[j, i] > 0 && ix <= length(available_species)
+ set_bound_transition!(available_species[ix], transition)
+
+ push!(bound, available_species[ix])
+ push!(structured_to_agents, type => available_species[ix])
+ add_to_log!(available_species[ix], type, state.t, transition)
+
+ allocs[j, i] -= 1
+ ix += 1
+ end
+ end
+ end
+
+ context_eval(state, transition, state.wrap_fun(state.acs[i, :transPreAction]))
+ end
end
- update_u!(state, u)
## evolve ongoing transitions
reqs = zeros(nparts(state, :S), length(state.ongoing_transitions))
qs = map(t -> t.q, state.ongoing_transitions)
@@ -178,10 +235,10 @@ function evolve!(u, state)
get_reqs_ongoing!(reqs, qs, state)
allocs = get_allocs!(
reqs,
- u,
+ state.u,
state,
map(t -> t[:transPriority], state.ongoing_transitions),
- state.solverargs[:strategy],
+ state.p[:strategy],
)
qs .= get_frac_satisfied(allocs, reqs, state)
push!(
@@ -195,13 +252,54 @@ function evolve!(u, state)
]...,
),
)
- u .-= sum(allocs; dims = 2)
+ state.u .-= sum(allocs; dims = 2)
actual_allocs .+= sum(allocs; dims = 2)
- foreach(
- i -> state.ongoing_transitions[i].state += qs[i] * state.solverargs[:tstep],
- eachindex(state.ongoing_transitions),
- )
+ for i in eachindex(state.ongoing_transitions)
+ transition = state.ongoing_transitions[i]
+ if qs[i] != 0
+ transition.state += qs[i] * state.dt
+
+ bound = transition.nonblock_structured_agents
+ structured_to_agents = transition.structured_to_agents
+
+ for (j, type) in enumerate(state.acs[:, :specName])
+ if type ∈ state.structured_token
+ if !isinteger(allocs[j, i])
+ error(
+ "For structured species, stoichiometry coefficient must be integer in transition $i.",
+ )
+ end
+
+ available_species = filter(
+ a -> get_species(a) == type && !isblocked(a),
+ structured_token,
+ )
+
+ sort!(
+ available_species;
+ by = a -> priority(a, state.acs[i, :transName]),
+ rev = true,
+ )
+
+ ix = 1
+ while allocs[j, i] > 0 && ix <= length(available_species)
+ set_bound_transition!(
+ available_species[ix].bound_transition,
+ transition,
+ )
+
+ push!(bound, available_species[ix])
+ push!(structured_to_agents, type => available_species[ix])
+ add_to_log!(available_species[ix], type, state.t, transition)
+
+ allocs[j, i] -= 1
+ ix += 1
+ end
+ end
+ end
+ end
+ end
push!(state.log, (:allocation, state.t, actual_allocs))
return push!(
@@ -209,14 +307,14 @@ function evolve!(u, state)
(
:valuation_cost,
state.t,
- actual_allocs' * [state[i, :specCost] for i = 1:nparts(state, :S)],
+ actual_allocs' * [state[i, :specCost] for i in parts(state, :S)],
),
)
end
# execute callbacks
function event_action!(state)
- for i = 1:nparts(state, :E)
+ for i in parts(state, :E)
!isnothing(state[i, :eventTrigger]) && !isnothing(state[i, :eventAction]) ||
continue
v = state[i, :eventTrigger]
@@ -227,9 +325,78 @@ function event_action!(state)
end
end
+function allocate_for_move(t::Transition, s::Symbol)
+ return t.bound_structured_agents ∩
+ map(x -> x[2], filter(x -> x[1] == s, t.structured_to_agents))
+end
+
+function structured_rhs(expr::Expr, state, transition)
+ if isexpr(expr, :macrocall) && macroname(expr) == :structured
+ if length(expr.args) == 3
+ expr = quote
+ return $(expr.args[end])
+ end
+ # write docs
+ token = context_eval(state, transition, state.wrap_fun(expr))
+
+ entangle!(getagent(state, "structured"), token)
+
+ return token, get_species(token)
+ else
+ expr = quote
+ token = $(expr.args[end-1])
+ species = $(expr.args[end])
+
+ return token, species
+ end
+ # write docs
+ token, species = context_eval(state, transition, state.wrap_fun(expr))
+ set_species!(token, Symbol(species))
+
+ entangle!(getagent(state, "structured"), token)
+
+ return token, get_species(token)
+ end
+ elseif isexpr(expr, :macrocall) && macroname(expr) == :move
+ expr = quote
+ species_from = $(expr.args[end-1])
+ species_to = $(expr.args[end])
+
+ return species_from, species_to
+ end
+
+ species_from, species_to =
+ Symbol.(context_eval(state, transition, state.wrap_fun(expr)))
+
+ tokens =
+ filter(x -> get_species(x) == species_from, transition.bound_structured_agents)
+ if !isempty(tokens)
+ token = first(tokens)
+ entangle!(getagent(state, "structured"), token)
+
+ set_species!(token, species_to)
+ ix = findfirst(
+ i -> transition.bound_structured_agents[i] == token,
+ eachindex(transition.bound_structured_agents),
+ )
+ deleteat!(transition.bound_structured_agents, ix)
+ set_bound_transition!(token, nothing)
+
+ return token, species_to
+ else
+ @error "Not enough tokens to allocate for a move."
+ end
+
+ else
+ token = context_eval(state, transition, state.wrap_fun(expr))
+ entangle!(getagent(state, "structured"), token)
+
+ return token, get_species(token)
+ end
+end
+
# collect terminated transitions
-function finish!(u, state)
- update_u!(state, u)
+function finish!(state)
val_reward = 0
terminated_all = Dict{Symbol,Float64}()
terminated_success = Dict{Symbol,Float64}()
@@ -238,46 +405,79 @@ function finish!(u, state)
while ix <= length(state.ongoing_transitions)
trans_ = state.ongoing_transitions[ix]
((state.t - trans_.t) < trans_.trans[:transMaxLifeTime]) &&
- trans_.state < trans_[:transCycleTime] &&
+ (trans_.state < trans_[:transCycleTime]) &&
(ix += 1; continue)
- toks_rhs = []
+
+ q = if trans_.state >= trans_[:transCycleTime]
+ rand(Distributions.Binomial(Int(trans_.q), trans_[:transProbOfSuccess]))
+ else
+ 0
+ end
+
for r in extract_reactants(trans_[:transRHS], state)
- i = find_index(r.species, state)
- push!(
- toks_rhs,
- UnfoldedReactant(
- i,
- r.species,
- context_eval(state, state.wrap_fun(r.stoich)),
- r.modality ∪ state[i, :specModality],
- ),
- )
+ if r.species isa Expr
+ stoich = context_eval(state, trans_, state.wrap_fun(r.stoich))
+
+ for _ = 1:(q*stoich)
+ token, species = structured_rhs(r.species, state, trans_)
+ i = find_index(species, state)
+ state.u[i] += 1
+ val_reward += state[i, :specReward]
+ end
+ else
+ i = find_index(r.species, state)
+ stoich = context_eval(state, trans_, state.wrap_fun(r.stoich))
+
+ state.u[i] += q * stoich
+ val_reward += state[i, :specReward] * q * stoich
+ end
end
+
for tok in trans_[:transLHS]
- in(:conserved, tok.modality) && (
- u[tok.index] +=
+ if in(:conserved, tok.modality)
+ state.u[tok.index] +=
trans_.q *
tok.stoich *
(in(:rate, tok.modality) ? trans_[:transCycleTime] : 1)
- )
- end
- q = if trans_.state >= trans_[:transCycleTime]
- rand(Distributions.Binomial(Int(trans_.q), trans_[:transProbOfSuccess]))
- else
- 0
+ if tok.species ∈ state.structured_token
+ for _ = 1:(trans_.q*tok.stoich)
+ isempty(trans_.bound_structured_agents) && break
+ set_bound_transition!(
+ trans_.bound_structured_agents[begin].bound_transition,
+ nothing,
+ )
+ deleteat!(trans_.bound_structured_agents, 1)
+ end
+ end
+ end
+
+ if in(:nonblock, tok.modality)
+ if in(:conserved, tok.modality)
+ error(
+ "Modalities `:conserved` and `:nonblock` cannot be specified at the same time.",
+ )
+ end
+
+ state.u[tok.index] += trans_.q * tok.stoich
+ if tok.species ∈ state.structured_token
+ for _ = 1:(trans_.q*tok.stoich)
+ set_bound_transition!(
+ trans_.nonblock_structured_agents[begin].bound_transition,
+ nothing,
+ )
+ deleteat!(trans_.nonblock_structured_agents, 1)
+ end
+ end
+ end
end
- foreach(
- tok -> (u[tok.index] += q * tok.stoich;
- val_reward += state[tok.index, :specReward] * q * tok.stoich),
- toks_rhs,
- )
-
- update_u!(state, u)
- context_eval(state, trans_.trans[:transPostAction])
- terminated_all[trans_[:transHash]] =
- get(terminated_all, trans_[:transHash], 0) + trans_.q
- terminated_success[trans_[:transHash]] =
- get(terminated_success, trans_[:transHash], 0) + q
+
+ context_eval(state, trans_, state.wrap_fun(state.acs[trans_.i, :transPostAction]))
+
+ terminated_all[Symbol(trans_[:transHash])] =
+ get(terminated_all, Symbol(trans_[:transHash]), 0) + trans_.q
+
+ terminated_success[Symbol(trans_[:transHash])] =
+ get(terminated_success, Symbol(trans_[:transHash]), 0) + q
ix += 1
end
@@ -288,61 +488,21 @@ function finish!(u, state)
push!(state.log, (:terminated_success, state.t, terminated_success...))
push!(state.log, (:valuation_reward, state.t, val_reward))
- return u
+ return state.u
end
function free_blocked_species!(state)
for trans in state.ongoing_transitions, tok in trans[:transLHS]
in(:nonblock, tok.modality) && (state.u[tok.index] += q * tok.stoich)
end
-end
-
-"""
-Transform an `acs` to a `DiscreteProblem` instance, compatible with standard solvers.
-
-# Examples
-```julia
-transform(DiscreteProblem, acs; schedule = schedule_weighted!)
-```
-"""
-function transform(
- ::Type{DiffEqBase.DiscreteProblem},
- state::ReactiveDynamicsState;
- kwargs...,
-)
- f = function (du, u, p, t)
- state = p[:__state__]
- free_blocked_species!(state)
- du .= state.u
- update_observables(state)
- sample_transitions!(state)
- evolve!(du, state)
- finish!(du, state)
- update_u!(state, du)
- event_action!(state)
-
- du .= state.u
- push!(
- state.log,
- (:valuation, t, du' * [state[i, :specValuation] for i = 1:nparts(state, :S)]),
- )
-
- t = (state.t += state.solverargs[:tstep])
- update_u!(state, du)
- save!(state)
- sync_p!(p, state)
+ for trans in state.ongoing_transitions
+ for a in trans.nonblock_structured_agents
+ a.bound_transition = nothing
+ end
- return du
+ empty!(trans.nonblock_structured_agents)
end
-
- return DiffEqBase.DiscreteProblem(
- f,
- state.u,
- (0.0, 2.0),
- Dict(state.p..., :__state__ => state, :__state0__ => deepcopy(state));
- kwargs...,
- )
end
## resolve tspan, tstep
@@ -352,106 +512,159 @@ function get_tcontrol(tspan, args)
tunit = get(args, :tunit, oneunit(tspan))
tspan = tspan / tunit
- tstep = get(args, :tstep, haskey(args, :tstops) ? tspan / args[:tstops] : tunit) / tunit
+ dt = get(args, :dt, haskey(args, :tstops) ? tspan / args[:tstops] : tunit) / tunit
- return ((0.0, tspan), tstep)
+ return ((0.0, tspan), dt)
end
-"""
-Transform an `acs` to a `DiscreteProblem` instance, compatible with standard solvers.
-
-Optionally accepts initial values and parameters, which take precedence over specifications in `acs`.
-
-# Examples
-
-```julia
-DiscreteProblem(acs, u0, p; tspan = (0.0, 100.0), schedule = schedule_weighted!)
-```
-"""
-function DiffEqBase.DiscreteProblem(
- acs::ReactionNetwork,
+function ReactionNetworkProblem(
+ acs::ReactionNetworkSchema,
u0 = Dict(),
- p = DiffEqBase.NullParameters();
+ p = Dict();
+ name = "reaction_network",
kwargs...,
)
assign_defaults!(acs)
keywords = Dict{Symbol,Any}([
- acs[i, :metaKeyword] => acs[i, :metaVal] for i = 1:nparts(acs, :M) if
+ acs[i, :metaKeyword] => acs[i, :metaVal] for i in parts(acs, :M) if
!isnothing(acs[i, :metaKeyword]) && !isnothing(acs[i, :metaVal])
])
+
merge!(keywords, Dict(collect(kwargs)))
merge!(keywords, Dict(:strategy => get(keywords, :alloc_strategy, :weighted)))
+
keywords[:tspan], keywords[:tstep] = get_tcontrol(keywords[:tspan], keywords)
acs = remove_choose(acs)
- attrs, transitions, wrap_fun = compile_attrs(acs)
- state = ReactiveDynamicsState(
+
+ structured_token_names =
+ acs[filter(i -> acs[i, :specStructured], 1:nparts(acs, :S)), :specName]
+
+ println(acs[:, :specName])
+ println(structured_token_names)
+ attrs, transitions, wrap_fun = compile_attrs(acs, structured_token_names)
+ transition_recipes = transitions
+ u0_init = zeros(nparts(acs, :S))
+
+ for i in parts(acs, :S)
+ if !isnothing(acs[i, :specName]) && haskey(u0, acs[i, :specName])
+ u0_init[i] = u0[acs[i, :specName]]
+ else
+ u0_init[i] = acs[i, :specInitVal]
+ end
+ end
+
+ prms = Dict{Symbol,Any}((
+ acs[i, :prmName] => acs[i, :prmVal] for
+ i in Iterators.filter(i -> !isnothing(acs[i, :prmVal]), 1:nparts(acs, :P))
+ ))
+
+ merge!(p, prms)
+
+ ongoing_transitions = Transition[]
+ log = NamedTuple[]
+ observables = compile_observables(acs)
+ transitions_attrs =
+ setdiff(
+ filter(a -> contains(string(a), "trans"), propertynames(acs.subparts)),
+ (:trans,),
+ ) ∪ [:transLHS, :transRHS, :transToSpawn, :transHash]
+ transitions = Dict{Symbol,Vector}(a => [] for a in transitions_attrs)
+
+ sol = DataFrame(
+ "t" => Float64[],
+ (string(name) => Float64[] for name in acs[:, :specName])...,
+ )
+
+ network = ReactionNetworkProblem(
+ name,
acs,
attrs,
+ transition_recipes,
+ u0_init,
+ merge(p, Dict(:strategy => get(keywords, :alloc_strategy, :weighted))),
+ keywords[:tspan][1],
+ structured_token_names,
+ keywords[:tspan],
+ get(keywords, :tstep, 1),
transitions,
+ ongoing_transitions,
+ log,
+ observables,
wrap_fun,
- keywords[:tspan][1];
- keywords...,
+ sol,
)
- init_u!(state)
- save!(state)
- prob = transform(DiffEqBase.DiscreteProblem, state; kwargs...)
-
- u0 isa Dict && foreach(
- i ->
- prob.u0[i] =
- if !isnothing(acs[i, :specName]) && haskey(u0, acs[i, :specName])
- u0[acs[i, :specName]]
- else
- prob.u0[i]
- end,
- 1:nparts(state, :S),
- )
- p_ = p == DiffEqBase.NullParameters() ? Dict() : Dict(k => v for (k, v) in p)
- prob = remake(
- prob;
- u0 = prob.u0,
- tspan = keywords[:tspan],
- dt = get(keywords, :tstep, 1),
- p = merge(
- prob.p,
- p_,
- Dict(
- :tstep => get(keywords, :tstep, 1),
- :strategy => get(keywords, :alloc_strategy, :weighted),
- ),
- ),
- )
+ entangle!(network, FreeAgent("structured"))
- return prob
-end
+ # save!(network)
-function fetch_params(acs::ReactionNetwork)
- return Dict{Symbol,Any}((
- acs[i, :prmName] => acs[i, :prmVal] for
- i in Iterators.filter(i -> !isnothing(acs[i, :prmVal]), 1:nparts(acs, :P))
- ))
+ return network
end
-# EnsembleProblem's prob_func: sample initial values
-function get_prob_func(prob)
- vars = prob.p[:__state__][:, :specInitUncertainty]
+function AlgebraicAgents._reinit!(state::ReactionNetworkProblem)
+ state.u .= isempty(state.sol) ? state.u : Vector(state.sol[1, 2:end])
+ state.t = state.tspan[1]
+ empty!(state.ongoing_transitions)
+ empty!(state.log)
+ state.observables = compile_observables(state.acs)
+ empty!(state.sol)
- prob_func = function (prob, _, _)
- prob.p[:__state__] = deepcopy(prob.p[:__state0__])
- for i in eachindex(prob.u0)
- rv = randn() * vars[i]
- prob.u0[i] = if (sign(rv + prob.u0[i]) == sign(prob.u0[i]))
- rv + prob.u0[i]
- else
- prob.u0[i]
- end
+ return state
+end
+
+function update_u_structured!(state)
+ structured_tokens = collect(values(inners(getagent(state, "structured"))))
+ for (i, species) in enumerate(state.acs[:, :specName])
+ if state.acs[i, :specStructured]
+ state.u[i] =
+ count(a -> get_species(a) == species && !isblocked(a), structured_tokens)
end
- sync!(prob.p[:__state__], prob.u0, prob.p)
+ end
+
+ return state.u
+end
- return prob
+function AlgebraicAgents._step!(state::ReactionNetworkProblem)
+ update_u_structured!(state)
+ if isempty(state.sol)
+ save!(state)
end
- return prob_func
+ free_blocked_species!(state)
+ update_u_structured!(state)
+ update_observables(state)
+ sample_transitions!(state)
+ evolve!(state)
+ update_u_structured!(state)
+ finish!(state)
+ update_u_structured!(state)
+
+ event_action!(state)
+
+ push!(
+ state.log,
+ (
+ :valuation,
+ state.t,
+ state.u' * [state[i, :specValuation] for i in parts(state, :S)],
+ ),
+ )
+
+ state.t += state.dt
+
+ save!(state)
+
+ return state.t
+end
+
+function AlgebraicAgents._projected_to(state::ReactionNetworkProblem)
+ return state.t > state.tspan[2] ? true : state.t
+end
+
+function fetch_params(acs::ReactionNetworkSchema)
+ return Dict{Symbol,Any}((
+ acs[i, :prmName] => acs[i, :prmVal] for
+ i in Iterators.filter(i -> !isnothing(acs[i, :prmVal]), parts(acs, :P))
+ ))
end
diff --git a/src/state.jl b/src/state.jl
index 9567317..d0eef41 100644
--- a/src/state.jl
+++ b/src/state.jl
@@ -1,4 +1,5 @@
-using DiffEqBase: NullParameters
+@reexport using AlgebraicAgents
+using DataFrames
struct UnfoldedReactant
index::Int
@@ -10,17 +11,24 @@ end
"""
Ongoing transition auxiliary structure.
"""
-mutable struct Transition
+@aagent struct Transition
+ i::Int
+
trans::Dict{Symbol,Any}
+ bound_structured_agents::Vector{AbstractAlgebraicAgent}
+ nonblock_structured_agents::Vector{AbstractAlgebraicAgent}
+ structured_to_agents::Vector
+
t::Float64
q::Float64
state::Float64
end
Base.getindex(state::Transition, key) = state.trans[key]
+Base.setindex!(state::Transition, val, key) = state.trans[key] = val
-mutable struct Observable
+@aagent struct Observable
last::Float64 # last sampling time
range::Vector{Union{Tuple{Float64,SampleableValues},SampleableValues}}
every::Float64
@@ -29,8 +37,8 @@ mutable struct Observable
sampled::Any
end
-mutable struct ReactiveDynamicsState
- acs::ReactionNetwork
+@aagent struct ReactionNetworkProblem
+ acs::ReactionNetworkSchema
attrs::Dict{Symbol,Vector}
transition_recipes::Dict{Symbol,Vector}
@@ -39,79 +47,49 @@ mutable struct ReactiveDynamicsState
p::Any
t::Float64
+ structured_token::Vector{Symbol}
+
+ tspan::Tuple{Float64,Float64}
+ dt::Float64
+
transitions::Dict{Symbol,Vector}
ongoing_transitions::Vector{Transition}
log::Vector{Tuple}
observables::Dict{Symbol,Observable}
- solverargs::Any
wrap_fun::Any
- history_u::Vector{Vector{Float64}}
- history_t::Vector{Float64}
-
- function ReactiveDynamicsState(
- acs::ReactionNetwork,
- attrs,
- transition_recipes,
- wrap_fun,
- t0 = 0;
- kwargs...,
- )
- ongoing_transitions = Transition[]
- log = NamedTuple[]
- observables = compile_observables(acs)
- transitions_attrs =
- setdiff(
- filter(a -> contains(string(a), "trans"), propertynames(acs.subparts)),
- (:trans,),
- ) ∪ [:transLHS, :transRHS, :transToSpawn, :transHash]
- transitions = Dict{Symbol,Vector}(a => [] for a in transitions_attrs)
-
- return new(
- acs,
- attrs,
- transition_recipes,
- zeros(nparts(acs, :S)),
- fetch_params(acs),
- t0,
- transitions,
- ongoing_transitions,
- log,
- observables,
- kwargs,
- wrap_fun,
- Vector{Float64}[],
- Float64[],
- )
- end
+ sol::DataFrame
end
# get value of a numeric expression
# evaluate compiled numeric expression in context of (u, p, t)
-function context_eval(state::ReactiveDynamicsState, o)
- o = o isa Function ? Base.invokelatest(o, state) : o
+function context_eval(state::ReactionNetworkProblem, transition, o)
+ o = o isa Function ? Base.invokelatest(o, state, transition) : o
return o isa Sampleable ? rand(o) : o
end
-function Base.getindex(state::ReactiveDynamicsState, keys...)
- return context_eval(
- state,
- (contains(string(keys[2]), "trans") ? state.transitions : state.attrs)[keys[2]][keys[1]],
- )
+function Base.getindex(state::ReactionNetworkProblem, keys...)
+ if any(occursin.(["transPreAction", "transPostAction"], Ref(string(keys[2]))))
+ return state.acs[keys[1], keys[2]]
+ else
+ return context_eval(
+ state,
+ nothing,
+ (contains(string(keys[2]), "trans") ? state.transitions : state.attrs)[keys[2]][keys[1]],
+ )
+ end
end
-function init_u!(state::ReactiveDynamicsState)
+function init_u!(state::ReactionNetworkProblem)
return (u = fill(0.0, nparts(state, :S));
- foreach(i -> u[i] = state[i, :specInitVal], 1:nparts(state, :S));
+ foreach(i -> u[i] = state[i, :specInitVal], parts(state, :S));
state.u = u)
end
-function save!(state::ReactiveDynamicsState)
- return (push!(state.history_u, state.u); push!(state.history_t, state.t))
-end
+save!(state::ReactionNetworkProblem) = push!(state.sol, (state.t, state.u[:]...))
-function compile_observables(acs::ReactionNetwork)
+function compile_observables(acs::ReactionNetworkSchema)
observables = Dict{Symbol,Observable}()
species_names = collect(acs[:, :specName])
prm_names = collect(acs[:, :prmName])
@@ -127,7 +105,10 @@ function compile_observables(acs::ReactionNetwork)
opts.range,
)
- push!(observables, name => Observable(-Inf, range, opts.every, on, missing))
+ push!(
+ observables,
+ name => Observable(string(name), -Inf, range, opts.every, on, missing),
+ )
end
return observables
@@ -149,16 +130,16 @@ function sample_range(rng, state)
return r isa Sampleable ? rand(r) : r
end
-function resample!(state::ReactiveDynamicsState, o::Observable)
+function resample!(state::ReactionNetworkProblem, o::Observable)
o.last = state.t
isempty(o.range) && (return o.val = missing)
- return o.sampled = context_eval(state, sample_range(o.range, state))
+ return o.sampled = context_eval(state, nothing, sample_range(o.range, state))
end
-resample(state::ReactiveDynamicsState, o::Symbol) = resample!(state, state.observables[o])
+resample(state::ReactionNetworkProblem, o::Symbol) = resample!(state, state.observables[o])
-function update_observables(state::ReactiveDynamicsState)
+function update_observables(state::ReactionNetworkProblem)
return foreach(
o -> (state.t - o.last) >= o.every && resample!(state, o),
values(state.observables),
@@ -167,7 +148,7 @@ end
function prune_r_line(r_line)
return if r_line isa Expr && r_line.args[1] ∈ fwd_arrows
- r_line.args[2:3]
+ r_line.args[[2, 3]]
elseif r_line isa Expr && r_line.args[1] ∈ bwd_arrows
r_line.args[[3, 2]]
elseif isexpr(r_line, :macrocall) && (macroname(r_line) == :choose)
@@ -184,11 +165,11 @@ function prune_r_line(r_line)
end
end
-function find_index(species::Symbol, state::ReactiveDynamicsState)
- return findfirst(i -> state[i, :specName] == species, 1:nparts(state, :S))
+function find_index(species::Symbol, state::ReactionNetworkProblem)
+ return findfirst(i -> state[i, :specName] == species, parts(state, :S))
end
-function sample_transitions!(state::ReactiveDynamicsState)
+function sample_transitions!(state::ReactionNetworkProblem)
for (_, v) in state.transitions
empty!(v)
end
@@ -197,10 +178,13 @@ function sample_transitions!(state::ReactiveDynamicsState)
l_line, r_line = prune_r_line(state.transition_recipes[:trans][i])
for attr in keys(state.transition_recipes)
- attr ∈ [:trans, :transPostAction, :transActivated, :transHash] && continue
+ (
+ attr ∈
+ [:trans, :transPreAction, :transPostAction, :transActivated, :transHash]
+ ) && continue
push!(
state.transitions[attr],
- context_eval(state, state.transition_recipes[attr][i]),
+ context_eval(state, nothing, state.transition_recipes[attr][i]),
)
end
@@ -212,62 +196,57 @@ function sample_transitions!(state::ReactiveDynamicsState)
UnfoldedReactant(
j,
r.species,
- context_eval(state, state.wrap_fun(r.stoich)),
+ context_eval(state, nothing, state.wrap_fun(r.stoich)),
r.modality ∪ state[j, :specModality],
),
)
end
+
push!(state.transitions[:transLHS], reactants)
push!(state.transitions[:transRHS], r_line)
+
foreach(
k -> push!(state.transitions[k], state.transition_recipes[k][i]),
- [:transPostAction, :transToSpawn, :transHash],
+ [:transPreAction, :transPostAction, :transToSpawn, :transHash],
)
+
state.transition_recipes[:transToSpawn] .= 0
end
end
-## sync
-update_u!(state::ReactiveDynamicsState, u) = (state.u .= u)
-update_t!(state::ReactiveDynamicsState, t) = (state.t = t)
-sync_p!(p, state::ReactiveDynamicsState) = merge!(p, state.p)
-
-function sync!(state::ReactiveDynamicsState, u, p)
- state.u .= u
- for k in keys(state.p)
- haskey(p, k) && (state.p[k] = p[k])
- end
+function as_state(u, t, state::ReactionNetworkProblem)
+ return (state = deepcopy(state); state.u .= u; state.t = t; state)
end
-function as_state(u, t, state::ReactiveDynamicsState)
- return (state = deepcopy(state); state.u .= u; state.t = t; state)
+function ACSets.ACSetInterface.nparts(state::ReactionNetworkProblem, obj::Symbol)
+ return nparts(state.acs, obj)
end
-function Catlab.CategoricalAlgebra.nparts(state::ReactiveDynamicsState, obj::Symbol)
- return obj == :T ? length(state.transitions[:transLHS]) : nparts(state.acs, obj)
+function ACSets.ACSetInterface.parts(state::ReactionNetworkProblem, obj::Symbol)
+ return parts(state.acs, obj)
end
## query the state
-t(state::ReactiveDynamicsState) = state.t
-solverarg(state::ReactiveDynamicsState, arg) = state.solverargs[arg]
-take(state::ReactiveDynamicsState, pcs::Symbol) = state.observables[pcs].sampled
-log(state::ReactiveDynamicsState, msg) = (println(msg); push!(state.log, (:log, msg)))
-state(state::ReactiveDynamicsState) = state
+t(state::ReactionNetworkProblem) = state.t
+solverarg(state::ReactionNetworkProblem, arg) = state.p[arg]
+take(state::ReactionNetworkProblem, pcs::Symbol) = state.observables[pcs].sampled
+log(state::ReactionNetworkProblem, msg) = (println(msg); push!(state.log, (:log, msg)))
+state(state::ReactionNetworkProblem) = state
-function periodic(state::ReactiveDynamicsState, period)
+function periodic(state::ReactionNetworkProblem, period)
return period == 0.0 || (
- length(state.history_t) > 1 &&
- (fld(state.t, period) - fld(state.history_t[end-1], period) > 0)
+ length(state.sol.t) > 1 &&
+ (fld(state.t, period) - fld(state.sol.t[end-1], period) > 0)
)
end
-set_params(state::ReactiveDynamicsState, vals...) =
+set_params(state::ReactionNetworkProblem, vals...) =
for (p, v) in vals
state.p[p] = v
end
-function add_to_spawn!(state::ReactiveDynamicsState, hash, n)
+function add_to_spawn!(state::ReactionNetworkProblem, hash, n)
ix = findfirst(ix -> state.transition_recipes[:transHash][ix] == hash)
return !isnothing(ix) && (state.transition_recipes[:transHash][ix] += n)
end
diff --git a/src/utils/safeinclude.jl b/test/safeinclude.jl
similarity index 100%
rename from src/utils/safeinclude.jl
rename to test/safeinclude.jl
diff --git a/test/tutorial_tests.jl b/test/tutorial_tests.jl
index 8ff8b8f..5f55109 100644
--- a/test/tutorial_tests.jl
+++ b/test/tutorial_tests.jl
@@ -1,8 +1,10 @@
using ReactiveDynamics
+include("safeinclude.jl")
+
@safeinclude "example" "../tutorial/example.jl"
@safeinclude "joins" "../tutorial/joins/joins.jl"
@safeinclude "loadsave" "../tutorial/loadsave/loadsave.jl"
-@safeinclude "optimize" "../tutorial/optimize/optimize.jl"
-@safeinclude "solution wrap" "../tutorial/optimize/optimize_custom.jl"
+# @safeinclude "optimize" "../tutorial/optimize/optimize.jl"
+# @safeinclude "solution wrap" "../tutorial/optimize/optimize_custom.jl"
@safeinclude "toy pharma model" "../tutorial/toy_pharma_model.jl"
diff --git a/tutorial/agents-integration/Project.toml b/tutorial/agents-integration/Project.toml
new file mode 100644
index 0000000..dc9c60a
--- /dev/null
+++ b/tutorial/agents-integration/Project.toml
@@ -0,0 +1,16 @@
+[deps]
+AlgebraicAgents = "f6eb0ae3-10fa-40e6-88dd-9006ba45093a"
+CEEDesigns = "e939450b-799e-4198-a5f5-3f2f7fb1c671"
+Copulas = "ae264745-0b69-425e-9d9d-cf662c5eec93"
+DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
+Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
+Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
+JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
+MCTS = "e12ccd36-dcad-5f33-8774-9175229e7b33"
+MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
+POMDPTools = "7588e00f-9cae-40de-98dc-e0c70c48cdd7"
+POMDPs = "a93abf59-7444-517b-a68a-c42f96afdd7d"
+PlotGraphviz = "78a92bc3-407c-4e2f-aae5-75bb47a6fe36"
+ReactiveDynamics = "c7456e7d-545a-4b79-91ea-6e93d96dd4d4"
+ScientificTypes = "321657f4-b219-11e9-178b-2701a2544e81"
+SimpleWeightedGraphs = "47aef6b3-ad0c-573a-a1e2-d07658019622"
diff --git a/tutorial/agents-integration/agents.jl b/tutorial/agents-integration/agents.jl
new file mode 100644
index 0000000..6b6bc4d
--- /dev/null
+++ b/tutorial/agents-integration/agents.jl
@@ -0,0 +1,116 @@
+# --------------------------------------------------------------------------------
+# Structured (agent-based) species
+
+#=
+import Pkg
+Pkg.activate(".")
+Pkg.dev("../..")
+Pkg.add(["AlgebraicAgents"])
+=#
+
+using ReactiveDynamics
+using AlgebraicAgents
+
+# Define the "symbolic" reaction network.
+network = @ReactionNetworkSchema
+
+# Below, we combine
+# - "classical species" (continuous or discrete; considered as pure quantities);
+# - "structured agents" (possibly with custom evolutionary function; these can appear both on LHS and RHS).
+
+@push network begin
+ # With specified intensities, generate experimental resources.
+ ρ1, ∅ --> R1
+ ρ2, ∅ --> R2
+
+ # Generate "Molecule 1" (where the integer corresponds to a "state" of, e.g., experimental triage).
+ ρ3, ∅ --> @structured(M1(@t(), rand(4)))
+
+ # Based on properties of particular "structured agent" assigned to the transition,
+ # we can update the attributes of the instance of a transition (such as probability of success).
+
+ # Transition "Molecule 1" into "Molecule 2."
+ # Update transition probability based on properties of "M1,"
+ # which was assigned as a "resource" to the transition.
+ ρ4,
+ R1 + SM1 --> @structured(M2(@t(), rand(4))),
+ preAction => update_prob_transition(state, transition)
+
+ 5.0, R2 + SM1 --> @structured(M2(@t(), rand(4)), :A)
+ 1.0, R2 + A --> @structured(M2(@t(), rand(4)), f_species(@transition))
+
+ 2.0, R2 + SM1 --> @move(:SM1, :C)
+end
+
+@prob_init network R1 = 10 R2 = 15
+
+# As for structured agents, we will need to instantiate the instances
+# and add them to the instance of a network. But first, we still need to define these types.
+@prob_init network SM1 = 2 SM2 = 0
+
+@prob_params network ρ1 = 2 ρ2 = 1 ρ3 = 3 ρ4 = 4
+
+@prob_meta network tspan = 100 dt = 1.0
+
+# We use `@structured` macro, which is a convenience wrapper around `@aagent`,
+# defined in ReactiveDynamics.jl
+@structured_token network struct M1
+ descriptor::Any
+ time_created::Any
+end
+
+using Random
+
+# Type `M1` lives in the scope of ReactiveDynamics.
+# Accordingly, we have to explicitly declare the scope.
+using ReactiveDynamics: M1
+
+function ReactiveDynamics.M1(time, descriptor)
+ return M1("M1" * randstring(4), :SM1, nothing, [], descriptor, time)
+end
+
+# We define the function which updates the transition probability.
+# This has to be accessible from within the name scope of ReactiveDynamics.
+@register begin
+ update_prob_transition = function (state, transition)
+ if !isnothing(transition) && !isempty(transition.bound_structured_agents)
+ bound_agent = first(transition.bound_structured_agents)
+
+ transition[:transProbOfSuccess] = 1.0#min(1.0, sum(bound_agent.descriptor))
+ end
+ end
+
+ f_species(transition) = :B
+end
+
+# Alternatively, we can define a structured agent type using
+# the usual `@aagent` macro. This must be evaluated inside the scope
+# of ReactiveDynamics.
+@register begin
+ @aagent BaseStructuredToken AbstractStructuredToken struct M2
+ descriptor::Any
+ time_created::Any
+ end
+
+ using Random: randstring
+ M2(time, descriptor) = M2("M2" * randstring(4), :SM2, nothing, [], descriptor, time)
+end
+
+# Let the network know that the species is structured.
+for species in [:SM1, :SM2, :A, :B, :C]
+ ReactiveDynamics.register_structured_species!(network, species)
+end
+
+# --------------------------------------------------------------------------------
+# Instantiate the network.
+network_instance = ReactionNetworkProblem(network)
+
+for i = 1:2
+ add_structured_token!(network_instance, ReactiveDynamics.M1(0.0, rand(4)))
+end
+
+# --------------------------------------------------------------------------------
+# Simulate the network.
+simulate(network_instance, 10)
+
+# tokens = collect(values(inners(getagent(network_instance, "structured"))))
diff --git a/tutorial/agents-integration/agents_integration.jl b/tutorial/agents-integration/agents_integration.jl
new file mode 100644
index 0000000..3279902
--- /dev/null
+++ b/tutorial/agents-integration/agents_integration.jl
@@ -0,0 +1,366 @@
+import Pkg
+
+# Manually add dependencies
+#=
+Pkg.activate(".")
+Pkg.develop(path = "../..")
+Pkg.add(["MacroTools", "AlgebraicAgents", "JSON3", "Distributions"])
+=#
+
+# --------------------------------------------------------------------------------
+# Parametrize the reaction network
+
+# We will have two classes of entities, each of which can be in a number of different states.
+# States "S" and "F" will internally denote some terminal, "success" and "failure" states, respectively.
+
+terminal_states = ["S", "F"]
+M1_states = "M1_" .* ["A", "B", "C", "D", terminal_states...]
+M2_states = "M2_" .* ["A", "B", "C", "D", "E", terminal_states...]
+
+experimental_resources = ["ER1", "ER2", "ER3"]
+
+M1_transition_probs = round.(rand(length(M1_states), length(M1_states)); digits = 2)
+M2_transition_probs = round.(rand(length(M2_states), length(M2_states)); digits = 2)
+
+# Ensure forward flow between states and set zero transition prob to terminal states
+for probs in [M1_transition_probs, M2_transition_probs]
+ for i in axes(probs, 1)
+ probs[i, i:end] .= 0
+ end
+
+ probs[:, end-1] .= 0
+end
+
+M1_priorities = round.(rand(length(M1_states)); digits = 2)
+M2_priorities = 2 * round.(rand(length(M2_states)); digits = 2)
+
+M1_resources = [rand(1:5, length(experimental_resources)) for _ in M1_states]
+M2_resources = [2 * rand(1:5, length(experimental_resources)) for _ in M2_states]
+
+M1_durations = rand(2:5, length(M1_states))
+M2_durations = rand(2:5, length(M2_states))
+
+# --------------------------------------------------------------------------------
+# Add initial quantities
+
+M1_initial = [rand(1:10, length(M1_states) - 2)..., 0, 0]
+M2_initial = [rand(1:10, length(M2_states) - 2)..., 0, 0]
+
+experimental_resources_initial = rand(1e2:3e2, size(experimental_resources))
+
+# --------------------------------------------------------------------------------
+# Export to JSON
+
+# First, we build a dictionary.
+data = Dict("species" => [], "transitions" => [])
+
+species, transitions = data["species"], data["transitions"]
+
+# --------------------------------------------------------------------------------
+# Add species with initial quantities.
+# Later, we may add a couple more attributes (conserved resources, etc.).
+
+for (state, q) in zip(M1_states, M1_initial)
+ push!(species, Dict("name" => "$state", "initial" => q))
+end
+
+for (state, q) in zip(M2_states, M2_initial)
+ push!(species, Dict("name" => "$state", "initial" => q))
+end
+
+for (res, q) in zip(experimental_resources, experimental_resources_initial)
+ push!(species, Dict("name" => "$res", "initial" => q))
+end
+
+# --------------------------------------------------------------------------------
+# Add transitions for entity class M1.
+
+for (i, state) in enumerate(M1_states[begin:end-2])
+ t = Dict{String,Any}(
+ "priority" => M1_priorities[i],
+ "duration" => M1_durations[i],
+ "rate" => state,
+ "name" => "M1",
+ )
+
+ # from (left-hand side)
+ push!(
+ t,
+ "from" => [
+ Dict("name" => res, "q" => q) for
+ (res, q) in zip(experimental_resources, M1_resources[i])
+ ],
+ )
+ push!(t["from"], Dict("name" => state, "q" => 1))
+
+ # to (right-hand side)
+ push!(t, "to" => [])
+ to = t["to"]
+
+ for (j, state2) in enumerate(M1_states[i:end])
+ if M1_transition_probs[j+i-1, i] > 0
+ push!(
+ to,
+ Dict(
+ "probability" => M1_transition_probs[j+i-1, i],
+ "reactants" => [Dict("name" => state2, "q" => 1)],
+ ),
+ )
+ end
+ end
+
+ push!(transitions, t)
+end
+
+# Add transitions for entity class M2.
+
+for (i, state) in enumerate(M2_states[begin:end-2])
+ t = Dict{String,Any}(
+ "priority" => M2_priorities[i],
+ "duration" => M2_durations[i],
+ "rate" => state,
+ "name" => "M2",
+ )
+
+ # from (left-hand side)
+ push!(
+ t,
+ "from" => [
+ Dict("name" => res, "q" => q) for
+ (res, q) in zip(experimental_resources, M2_resources[i])
+ ],
+ )
+ push!(t["from"], Dict("name" => state, "q" => 1))
+
+ # to (right-hand side)
+ push!(t, "to" => [])
+ to = t["to"]
+
+ for (j, state2) in enumerate(M2_states[i:end])
+ if M2_transition_probs[j+i-1, i] > 0
+ push!(
+ to,
+ Dict(
+ "probability" => M2_transition_probs[j+i-1, i],
+ "reactants" => [Dict("name" => state2, "q" => 1)],
+ ),
+ )
+ end
+ end
+
+ push!(transitions, t)
+end
+
+using JSON3
+open("reaction_network.json", "w") do io
+ #JSON3.pretty(io, data)
+end
+
+# --------------------------------------------------------------------------------
+# Import from JSON
+# Eventually move into the module (refactor current CSV interface).
+
+# Species and initial values.
+name = "reaction_network"
+str_init = "@prob_init $name"
+for s in data["species"]
+ str_init *= " $(s["name"])=$(s["initial"])"
+end
+
+function get_subline(d::Vector)
+ if isempty(d)
+ return "∅"
+ elseif haskey(first(d), "probability")
+ sublines = [
+ "($(sd["probability"]), " *
+ join(["$(s["q"]) * $(s["name"])" for s in sd["reactants"]], " + ") *
+ ")" for sd in d
+ ]
+ return "@choose(" * join(sublines, ", ") * ")"
+ else
+ return join(["$(s["q"]) * $(s["name"])" for s in d], " + ")
+ end
+end
+
+# Transitions.
+str_transitions = []
+for t in data["transitions"]
+ line = t["rate"] * ", " * get_subline(t["from"]) * " --> " * get_subline(t["to"])
+ line *= ", name => $(t["name"]), priority => $(t["priority"]), cycletime => $(t["duration"])"
+ push!(str_transitions, line)
+end
+
+push!(str_transitions, "i1, ∅ --> M1_A, name => M1_creation")
+push!(str_transitions, "i2, ∅ --> M2_A, name => M2_creation")
+
+str_params = "@prob_params $name i1 = .3 i2 = .2"
+
+str_network_def = """
+ begin
+ $name = @ReactionNetworkSchema
+ @push $name begin
+ $(join(str_transitions, '\n'))
+ end
+ $str_init
+ $str_params
+ end
+"""
+
+using MacroTools: striplines
+expr_network_def = striplines(Meta.parseall(str_network_def))
+
+using ReactiveDynamics
+
+eval(expr_network_def)
+
+@isdefined reaction_network
+
+# --------------------------------------------------------------------------------
+# Solve problem
+
+@prob_meta reaction_network tspan = 100 dt = 1.0
+
+# Convert network into an AlgAgents hierarchy.
+problem = ReactionNetworkProblem(reaction_network; name = "network")
+
+# AlgAgents: "periodic" callback.
+using AlgebraicAgents
+
+@aagent struct Controller
+ M1_λ::Float64
+ M2_λ::Float64
+
+ log::Vector{String}
+
+ current_time::Float64
+ time_step::Float64
+
+ initial_time::Float64
+end
+
+function Controller(
+ name::String,
+ M1_λ::Float64,
+ M2_λ::Float64,
+ time::T,
+ time_step::T,
+) where {T<:Real}
+ return Controller(name, M1_λ, M2_λ, String[], time, time_step, time)
+end
+
+using Distributions: Poisson
+
+function AlgebraicAgents._step!(c::Controller)
+ n_removed_M1 = rand(Poisson(c.time_step * c.M1_λ))
+ n_removed_M2 = rand(Poisson(c.time_step * (c.M2_λ + c.M1_λ)))
+
+ transitions = getagent(c, "../network").ongoing_transitions
+ M1_transitions = filter(x -> x[:transName] === :M1, transitions)
+ M2_transitions = filter(x -> x[:transName] === :M2, transitions)
+
+ M1_transitions_delete =
+ isempty(M1_transitions) ? [] : unique(rand(M1_transitions, n_removed_M1))
+ for trans in M1_transitions_delete
+ trans.state = trans[:transCycleTime]
+ trans.trans[:transProbOfSuccess] = 0
+ end
+
+ M2_transitions_delete =
+ isempty(M2_transitions) ? [] : unique(rand(M2_transitions, n_removed_M2))
+ for trans in M2_transitions_delete
+ trans.state = trans[:transCycleTime]
+ trans.trans[:transProbOfSuccess] = 0
+ end
+
+ push!(
+ c.log,
+ "t = $(c.current_time) removed compounds: " *
+ join(getname.(union(M1_transitions_delete, M2_transitions_delete)), ", "),
+ )
+
+ return c.current_time += c.time_step
+end
+
+AlgebraicAgents._projected_to(c::Controller) = c.current_time
+
+c = Controller("controller", 1e-1, 2e-1, 0.0, 1.0);
+
+compound_problem = ⊕(problem, c; name = "compound problem");
+
+# Simulate
+simulate(compound_problem, 100);
+
+# Access solution
+compound_problem.inners["network"].sol
+compound_problem.inners["controller"].log
+
+# Plot solution
+draw(compound_problem.inners["network"])
+
+# --------------------------------------------------------------------------------
+# Add resource making part to the reaction network
+
+eval(expr_network_def)
+
+n_primary_resources = 5
+primary_resources = ["R$i" for i = 1:n_primary_resources]
+
+str_resource_making_transitions = [
+ "p_primary_$i, ∅ --> $res, name => primary_resource_maker_$i" for
+ (i, res) in enumerate(primary_resources)
+]
+
+for (i, res) in enumerate(experimental_resources)
+ stoich = rand(1:5, n_primary_resources)
+ rate = "p_$i * (" * join(primary_resources, " + ") * ")"
+ transition_lhs = join(
+ [
+ "$(stoich[i])" * "$primary_res" for
+ (i, primary_res) in enumerate(primary_resources)
+ ],
+ " + ",
+ )
+
+ push!(
+ str_resource_making_transitions,
+ "$rate, $transition_lhs --> $res, name => resource_maker_$i",
+ )
+end
+
+str_resource_making_params =
+ "@prob_params resource_making_network " *
+ join(["p_$i = $(rand(1:3))" for i = 1:length(experimental_resources)], " ") *
+ " " *
+ join(["p_primary_$i = $(rand(1:5))" for i = 1:length(primary_resources)], " ")
+
+str_network_def = """
+ begin
+ resource_making_network = @ReactionNetworkSchema
+ @push resource_making_network begin
+ $(join(str_resource_making_transitions, '\n'))
+ end
+ $str_resource_making_params
+ end
+"""
+
+expr_network_resource_making_def = striplines(Meta.parseall(str_network_def))
+
+eval(expr_network_resource_making_def)
+
+extended_network =
+ union_acs!(reaction_network, resource_making_network, "resource_making_network")
+
+#equalize!(extended_network, [Meta.parseall("$res=resource_making_network.$res") for res in experimental_resources])
+str_equalize =
+ "@equalize extended_network " *
+ join(["$res=resource_making_network.$res" for res in experimental_resources], " ")
+
+equalize_expr = striplines(Meta.parseall(str_equalize))
+eval(equalize_expr)
+
+@prob_meta extended_network tspan = 100 dt = 1.0
+
+# Convert network into an AlgAgents hierarchy.
+extended_problem = ReactionNetworkProblem(extended_network; name = "extended_network")
+
+simulate(extended_problem, 100);
diff --git a/tutorial/agents-integration/ceed_integration.jl b/tutorial/agents-integration/ceed_integration.jl
new file mode 100644
index 0000000..18a29fe
--- /dev/null
+++ b/tutorial/agents-integration/ceed_integration.jl
@@ -0,0 +1,236 @@
+using CEEDesigns, CEEDesigns.GenerativeDesigns
+using DataFrames
+using ScientificTypes
+using Statistics, Copulas
+import POMDPs, POMDPTools, MCTS
+
+import Distributions
+
+# ----- Experimental Setup -----
+
+# We generate a synthetic dataset.
+# This is taken from https://github.com/Merck/CEEDesigns.jl/blob/34588ae0e5563cb93f6818e3a9c8b3a77c5e3c47/tutorials/SimpleGenerative.jl
+
+include("experimental_setup.jl")
+
+# ----- Get a sampling function -----
+
+(; sampler, uncertainty, weights) = DistanceBased(
+ data;
+ target = "y",
+ uncertainty = Variance(),
+ similarity = GenerativeDesigns.Exponential(; λ = 5),
+);
+
+# ----- Set up a reaction network -----
+
+#=
+Pkg.activate(".")
+Pkg.develop(path = "../..")
+=#
+
+using ReactiveDynamics
+
+# Set up parameters that will be used to define a network.
+
+# Experiments and costs
+features_experiments = Dict(["x$i" => "e$i" for i = 1:4])
+
+experiments_costs = Dict([
+ features_experiments[e] => (i, i) => [e] for (i, e) in enumerate(names(data)[1:4])
+])
+
+experiments_costs["ey"] = (100, 100) => ["y"]
+
+# Experimental resources
+experimental_resources = [:ER1, :ER2, :ER3]
+resources_quantities = [rand(1:3, length(experimental_resources)) for _ = 1:5]
+
+# "Compound," which is a structured token.
+# We use `@structured` macro, which is a convenience wrapper around `@aagent`,
+# defined in ReactiveDynamics.jl
+@register begin
+ @aagent BaseStructuredToken AbstractStructuredToken struct Compound
+ state::Any
+ history::Vector{Symbol}
+ end
+
+ get_cmpds = function (transition::Transition) end
+ run_experiment = function (agent::Compound, experiment::Symbol, rng) end
+ assign_to_places = function (state::ReactionNetworkProblem, threshold) end
+end
+
+# Provide a constructor for `Compound` and define functions that will
+# "execute" the experiments.
+import ReactiveDynamics: Compound, get_cmpds, assign_to_places, sample
+using ReactiveDynamics: ReactionNetworkProblem, Transition
+
+function Compound(id::AbstractString, predictions::Dict)
+ state = State((Evidence(predictions...), Tuple(zeros(2))))
+
+ return Compound("Compound $id", :pool, nothing, [], state, String[])
+end
+
+using Random: default_rng
+
+ReactiveDynamics.get_cmpds = function (transition::Transition)
+ if !isnothing(transition) && !isempty(transition.bound_structured_agents)
+ agent_ix = findall(x -> x isa Compound, transition.bound_structured_agents)
+
+ return transition.bound_structured_agents[agent_ix]
+ end
+end
+
+ReactiveDynamics.run_experiment =
+ function (agents::Vector, experiment::Symbol, rng = default_rng())
+ println("running experiment $experiment")
+ for agent in agents
+ push!(agent.history, experiment)
+ experiment = String(experiment)
+
+ observation = sampler(
+ agent.state.evidence,
+ getindex(experiments_costs[experiment], 2),
+ rng,
+ )
+
+ agent.state =
+ merge(agent.state, observation, first(experiments_costs[experiment]))
+ end
+ return agents
+ end
+
+transitions_experiments = String[]
+for i = 1:5
+ experiment = i < 5 ? "e$i" : "ey"
+
+ resource_part = join(
+ [
+ "$(resources_quantities[i][res_i]) * $res" for
+ (res_i, res) in enumerate(experimental_resources)
+ ],
+ " + ",
+ )
+
+ push!(
+ transitions_experiments,
+ """@deterministic($experiment), $experiment + $resource_part --> @move(:$experiment, :pool),
+ action => run_experiment(get_cmpds(@transition), :$experiment)
+ """,
+ )
+end
+
+# Set up a reaction network
+
+network = @ReactionNetworkSchema
+
+# Resource generation part
+@push network begin
+ p1, ∅ --> ER1
+ p2, ∅ --> ER2
+ p3, ∅ --> ER3
+end
+
+@prob_init network ER1 = 1200 ER2 = 1500 ER3 = 1300
+
+@prob_params network p1 = 1 p2 = 1 p3 = 1
+
+# Experiments part
+for species in union(Symbol.(keys(experiments_costs)), [:pool])
+ ReactiveDynamics.register_structured_species!(network, species)
+end
+
+str_network_def = """
+ begin
+ @push network begin
+ $(join(transitions_experiments, '\n'))
+ end
+ end
+"""
+
+using MacroTools: striplines
+expr_network_def = striplines(Meta.parseall(str_network_def))
+
+eval(expr_network_def)
+
+@prob_meta network tspan = 100
+
+problem = ReactionNetworkProblem(network)
+
+using Random: randstring
+
+# Simplified setup: we assume that compounds are already assigned
+# to the "experimental" places.
+for _ = 1:10
+ cmpd = Compound(randstring(4), Dict())
+ cmpd.species = Symbol("e$(rand(1:4))")
+
+ add_structured_token!(problem, cmpd)
+end
+
+simulate(problem, 10)
+
+# To allow for dynamic assignment of compounds to places, we need to create an agent
+# that will move the agent to the corresponding places.
+# This can be expressed in two ways:
+# - Create an "algebraic agent" which will modify the reaction network's state,
+# - Create a "placeholder" transition which will run the mutating function.
+
+# In either case, we need to define the function that will facilitate the assignments.
+evidence = Evidence()
+
+solver = GenerativeDesigns.DPWSolver(; n_iterations = 500, tree_in_info = true)
+repetitions = 5
+mdp_options = (; max_parallel = 1, discount = 1.0, costs_tradeoff = (0.5, 0.5))
+
+ReactiveDynamics.assign_to_places =
+ function (state::ReactionNetworkProblem, threshold = 0.1)
+ compounds = filter(
+ x -> ReactiveDynamics.get_species(x) == :pool,
+ collect(values(inners(inners(state)["structured"]))),
+ )
+
+ for cmpd in compounds
+ e = get_next_experiment(cmpd.state.evidence, threshold)
+ if !isnothing(e)
+ cmpd.species = first(e)
+ end
+ end
+ end
+
+function get_next_experiment(evidence::Evidence, threshold = 0.1)
+ design = efficient_design(
+ experiments_costs;
+ sampler = sampler,
+ uncertainty = uncertainty,
+ threshold,
+ evidence = evidence,
+ solver = solver,
+ repetitions = repetitions,
+ mdp_options = mdp_options,
+ )
+
+ if !haskey(design[2], :arrangement) || isempty(design[2].arrangement)
+ return nothing
+ else
+ return first(design[2].arrangement)
+ end
+end
+
+@push network begin
+ @deterministic(2), ∅ --> @structured(Compound(randstring(4), Dict()))
+ @deterministic(1), ∅ --> ER1, preAction => assign_to_places(@state)
+end
+
+problem = ReactionNetworkProblem(network)
+
+# Simplified setup: we assume that compounds are already assigned
+# to the "experimental" places.
+for _ = 1:10
+ cmpd = Compound(randstring(4), Dict())
+ cmpd.species = Symbol("e$(rand(1:4))")
+
+ add_structured_token!(problem, cmpd)
+end
+
+simulate(problem, 10)
diff --git a/tutorial/agents-integration/experimental_setup.jl b/tutorial/agents-integration/experimental_setup.jl
new file mode 100644
index 0000000..cb5c4d0
--- /dev/null
+++ b/tutorial/agents-integration/experimental_setup.jl
@@ -0,0 +1,56 @@
+# This is taken from https://github.com/Merck/CEEDesigns.jl/blob/34588ae0e5563cb93f6818e3a9c8b3a77c5e3c47/tutorials/SimpleGenerative.jl
+
+make_friedman3 = function (U, noise = 0, friedman3 = true)
+ size(U, 2) == 4 || error("input U must have 4 columns, has $(size(U,2))")
+ n = size(U, 1)
+ X = DataFrame(zeros(Float64, n, 4), :auto)
+ for i = 1:4
+ X[:, i] .= U[:, i]
+ end
+ ϵ = noise > 0 ? rand(Distributions.Normal(0, noise), size(X, 1)) : 0
+ if friedman3
+ X.y = @. atan((X[:, 2] * X[:, 3] - 1 / (X[:, 2] * X[:, 4])) / X[:, 1]) + ϵ
+ else
+ ## Friedman #2
+ X.y = @. (X[:, 1]^2 + (X[:, 2] * X[:, 3] - 1 / (X[:, 2] * X[:, 4]))^2)^0.5 + ϵ
+ end
+ return X
+end
+
+p12, p13, p14, p23, p24, p34 = 0.8, 0.5, 0.3, 0.5, 0.25, 0.4
+Σ = [
+ 1 p12 p13 p14
+ p12 1 p23 p24
+ p13 p23 1 p34
+ p14 p24 p34 1
+]
+
+X1 = Distributions.Uniform(0, 100)
+X2 = Distributions.Uniform(40 * π, 560 * π)
+X3 = Distributions.Uniform(0, 1)
+X4 = Distributions.Uniform(1, 11)
+
+C = GaussianCopula(Σ)
+D = SklarDist(C, (X1, X2, X3, X4))
+
+X = rand(D, 1000)
+
+data = make_friedman3(transpose(X), 0.01)
+
+data[1:10, :]
+
+# We can check that the empirical correlation is roughly the same as the specified theoretical values:
+
+cor(Matrix(data[:, Not(:y)]))
+
+# We ensure that our algorithms know that we have provided data of specified types.
+
+types = Dict(
+ :x1 => ScientificTypes.Continuous,
+ :x2 => ScientificTypes.Continuous,
+ :x3 => ScientificTypes.Continuous,
+ :x4 => ScientificTypes.Continuous,
+ :y => ScientificTypes.Continuous,
+)
+
+data = coerce(data, types);
diff --git a/tutorial/basics.jl b/tutorial/basics.jl
index 3b0267e..bc1ce13 100644
--- a/tutorial/basics.jl
+++ b/tutorial/basics.jl
@@ -1,23 +1,22 @@
using ReactiveDynamics
-# acs = @ReactionNetwork begin
-# 1.0, X ⟺ Y
-# end
-
-acs = @ReactionNetwork begin
- 1.0, X ⟺ Y, name => "transition1"
+# define the network
+acs = @ReactionNetworkSchema begin
+ 1.0, X --> Y, name => "transition1"
end
@prob_init acs X = 10 Y = 20
@prob_params acs
-@prob_meta acs tspan = 250 dt = 0.1
-
-prob = @problematize acs
+@prob_meta acs tspan = 25 dt = 0.10
-# sol = ReactiveDynamics.solve(prob)
+# convert network into an AlgAgents hierarchy
+prob = ReactionNetworkProblem(acs)
-sol = @solve prob trajectories = 20
+# simulate
+simulate(prob)
-using Plots
+# access solution
+prob.sol
-@plot sol plot_type = summary
+# plot solution
+draw(prob)
diff --git a/tutorial/example.jl b/tutorial/example.jl
index b7fb3d1..c41c1cc 100644
--- a/tutorial/example.jl
+++ b/tutorial/example.jl
@@ -1,7 +1,7 @@
using ReactiveDynamics
# acs as a model : incomplete dynamics
-sir_acs = @ReactionNetwork
+sir_acs = @ReactionNetworkSchema
# set up ontology: try ?@aka
@aka sir_acs transition = reaction species = population_group
@@ -30,18 +30,14 @@ u0 = [999, 10, 0] # alternative specification
@prob_params sir_acs β = 0.0001 ν = 0.01 γ = 5
@prob_meta sir_acs tspan = 100
-#prob = @problematize sir_acs
-prob = @problematize sir_acs tspan = 200
+prob = ReactionNetworkProblem(sir_acs; tspan = 200)
-sol = @solve prob trajectories = 20
-@plot sol plot_type = summary
-sol = @solve prob
-@plot sol plot_type = allocation
-@plot sol plot_type = valuations
-@plot sol plot_type = new_transitions
+sol = simulate(prob)
+
+draw(prob)
# acs as a model : incomplete dynamics
-sir_acs = @ReactionNetwork
+sir_acs = @ReactionNetworkSchema
# set up ontology: try ?@aka
@aka sir_acs transition = reaction species = population_group
@@ -65,7 +61,7 @@ u0 = [999, 10, 0] # alternative specification
@prob_params sir_acs β = 0.0001 ν = 0.01 γ = 5
# model dynamics
-sir_acs = @ReactionNetwork begin
+sir_acs = @ReactionNetworkSchema begin
α * S * I, S + I --> 2I, cycle_time => 0, name => I2R
β * I, I --> R, cycle_time => 0, name => R2S
end
@@ -77,16 +73,14 @@ end
@prob_meta sir_acs tspan = 250 dt = 0.1
# batch simulation
-prob = @problematize sir_acs
-sol = @solve prob trajectories = 20
-@plot sol plot_type = summary
-@plot sol plot_type = summary show = :S
-@plot sol plot_type = summary c = :green xlimits = (0.0, 100.0)
+prob = ReactionNetworkProblem(sir_acs)
+sol = simulate(prob)
+draw(prob)
-acs_1 = @ReactionNetwork begin
+acs_1 = @ReactionNetworkSchema begin
1.0, A --> B + C
end
-acs_2 = @ReactionNetwork begin
+acs_2 = @ReactionNetworkSchema begin
1.0, A --> B + C
end
diff --git a/tutorial/joins/joins.jl b/tutorial/joins/joins.jl
index ee87d7a..e7789a9 100644
--- a/tutorial/joins/joins.jl
+++ b/tutorial/joins/joins.jl
@@ -2,7 +2,7 @@ using ReactiveDynamics
## setup the environment
n_models = 5;
r = 2; # number of submodels, resources
-rd_models = ReactiveDynamics.ReactionNetwork[] # submodels
+rd_models = ReactiveDynamics.ReactionNetworkSchema[] # submodels
@register begin
ns = Int[] # size of submodels
@@ -51,8 +51,6 @@ u0 = rand(1:1000, nparts(rd_model, :S))
@prob_meta rd_model tspan = 10
-prob = @problematize rd_model
-sol = @solve prob trajectories = 2
-
-# plot "state" species only
-@plot sol plot_type = summary show = r"state"
+prob = ReactionNetworkProblem(sir_acs)
+sol = simulate(prob)
+draw(sol)
diff --git a/tutorial/joins/submodel.jl b/tutorial/joins/submodel.jl
index 1e8fc43..faf370a 100644
--- a/tutorial/joins/submodel.jl
+++ b/tutorial/joins/submodel.jl
@@ -13,7 +13,7 @@ end
# generate submodel dynamics
push!(
rd_models,
- @ReactionNetwork begin
+ @ReactionNetworkSchema begin
M[$i][$m, $n],
state[$m] + {demand[$i][$m, $n, $l] * resource[$l], l = 1:($r), dlm = +} -->
state[$n] + {production[$i][$m, $n, $l] * resource[$l], l = 1:($r), dlm = +},
diff --git a/tutorial/loadsave/loadsave.jl b/tutorial/loadsave/loadsave.jl
index 23d2be7..d6c8ee6 100644
--- a/tutorial/loadsave/loadsave.jl
+++ b/tutorial/loadsave/loadsave.jl
@@ -5,15 +5,15 @@ using ReactiveDynamics
@assert @isdefined sir_acs
@assert isdefined(ReactiveDynamics, :tdecay)
-prob = @problematize sir_acs
-sol = @solve prob trajectories = 20
+prob = ReactionNetworkProblem(sir_acs)
+sol = simulate(prob)
@import_network "csv/model.csv" sir_acs_
@assert @isdefined sir_acs_
@assert isdefined(ReactiveDynamics, :foo)
-prob_ = @problematize sir_acs_
-sol_ = @solve prob_ trajectories = 20
+prob_ = ReactionNetworkProblem(sir_acs_)
+sol_ = simulate(prob_)
# export, import the solution
@export_solution sol
diff --git a/tutorial/optimize/optimize.jl b/tutorial/optimize/optimize.jl
index 0766369..d8af528 100644
--- a/tutorial/optimize/optimize.jl
+++ b/tutorial/optimize/optimize.jl
@@ -1,7 +1,7 @@
using ReactiveDynamics
# solve for steady state
-acss = @ReactionNetwork begin
+acss = @ReactionNetworkSchema begin
3.0, A --> A, priority => 0.6, name => aa
1.0, B + 0.2 * A --> 2 * α * B, prob => 0.7, priority => 0.6, name => bb
3.0, A + 2 * B --> 2 * C, prob => 0.7, priority => 0.7, name => cc
diff --git a/tutorial/optimize/optimize_custom.jl b/tutorial/optimize/optimize_custom.jl
index 47dc489..a9cb780 100644
--- a/tutorial/optimize/optimize_custom.jl
+++ b/tutorial/optimize/optimize_custom.jl
@@ -8,7 +8,7 @@ using ReactiveDynamics
end
end
-acs = @ReactionNetwork begin
+acs = @ReactionNetworkSchema begin
function_to_learn(A, B, C, params), A --> B + C
1.0, B --> C
2.0, C --> B
@@ -35,7 +35,7 @@ data = [60 30 5]
return [A, B, C]' * params + α # params: 3-element vector
end
end
-acs = @ReactionNetwork begin
+acs = @ReactionNetworkSchema begin
learnt_function(A, B, C, params, α), A --> B + C, priority => 0.6
1.0, B --> C
2.0, C --> B
diff --git a/tutorial/rd_example.jl b/tutorial/rd_example.jl
index 64638b3..a9b1644 100644
--- a/tutorial/rd_example.jl
+++ b/tutorial/rd_example.jl
@@ -15,7 +15,7 @@ rd_model = @ReactionNetwork
transitions .+= 0.1 * rand(n_phase, n_phase)
end
-rd_model = @ReactionNetwork begin
+rd_model = @ReactionNetworkSchema begin
$i, phase[$i] --> phase[$j], cycle_time => $i * $j
end i = 1:3 j = 1:($i)
@@ -46,7 +46,7 @@ sol = @solve prob trajectories = 20
resource = rand(1:10, k, k, r)
end
-rd_model = @ReactionNetwork begin
+rd_model = @ReactionNetworkSchema begin
M[$i, $j],
mod[$i] +
{resource[$i, $j, $k] * resource[$k], k = rand(1:(ReactiveDynamics.r)), dlm = +} -->
diff --git a/tutorial/toy_pharma_model.jl b/tutorial/toy_pharma_model.jl
index 4fc6c7e..c308fe7 100644
--- a/tutorial/toy_pharma_model.jl
+++ b/tutorial/toy_pharma_model.jl
@@ -1,7 +1,7 @@
using ReactiveDynamics
# model dynamics
-toy_pharma_model = @ReactionNetwork begin
+toy_pharma_model = @ReactionNetworkSchema begin
α(candidate_compound, marketed_drug, κ),
3 * @conserved(scientist) + @rate(budget) --> candidate_compound,
name => discovery,
@@ -35,33 +35,29 @@ end
## other arguments passed to the solver
@prob_meta toy_pharma_model tspan = 250 dt = 0.1
-prob = @problematize toy_pharma_model
+prob = ReactionNetworkProblem(toy_pharma_model)
-sol = @solve prob trajectories = 20
+sol = simulate(prob)
-using Plots
-
-@plot sol plot_type = summary
-
-@plot sol plot_type = summary show = :marketed_drug
+draw(sol)
## for deterministic rates
# model dynamics
-toy_pharma_model = @ReactionNetwork begin
- @per_step(α(candidate_compound, marketed_drug, κ)),
+toy_pharma_model = @ReactionNetworkSchema begin
+ @deterministic(α(candidate_compound, marketed_drug, κ)),
3 * @conserved(scientist) + @rate(budget) --> candidate_compound,
name => discovery,
probability => 0.3,
cycletime => 10.0,
priority => 0.5
- @per_step(β(candidate_compound, marketed_drug)),
+ @deterministic(β(candidate_compound, marketed_drug)),
candidate_compound + 5 * @conserved(scientist) + 2 * @rate(budget) -->
marketed_drug + 5 * budget,
name => dx2market,
probability => 0.5 + 0.001 * @t(),
cycletime => 4
- @per_step(γ * marketed_drug), marketed_drug --> ∅, name => drug_killed
+ @deterministic(γ * marketed_drug), marketed_drug --> ∅, name => drug_killed
end
@periodic toy_pharma_model 0.0 budget += 11 * marketed_drug
@@ -82,12 +78,8 @@ end
## other arguments passed to the solver
@prob_meta toy_pharma_model tspan = 250
-prob = @problematize toy_pharma_model
-
-sol = @solve prob trajectories = 20
-
-using Plots
+prob = ReactionNetworkProblem(toy_pharma_model)
-@plot sol plot_type = summary
+sol = simulate(prob)
-@plot sol plot_type = summary show = :marketed_drug
+draw(sol)