Preamble
I'll throw this out as another approach one could take. It perhaps goes a little beyond the OP's particular question, but I ran into the same problem some time ago. I came up with this approach as the easiest thing I could manage. I'm not an expert on Association
/Dataset
objects, so I present only as the best thing I've come up with so far.
First, let me observe that the reason AssociateTo[]
is HoldFirst
is so that it can find a symbol representing an association to modify. (To me, this makes things awkward in Mathematica, but I am open to being shown that is a result of my lack of expertise.) Evidently, it will do some parsing of the first argument, which is not documented in the AssociateTo
page.
Further, I think a complete example would exhibit what happens to the source associations ode1
etc., namely that ODEs
is changed but not ode1
. This should make sense, since the references to ode1
and ode2
are lost in the definition of ODEs
. However, I either read or misread the question to mean that the modification of ode1
and ode2
was desired, probably because that was the problem was trying to solve for myself.
AssociateTo[ODEs[[1]], "ode" -> sol[[1]]]
ODEs
ode1
(*
{<|"y" -> y, "x" -> x, "ode" -> y[x] == 999|>,
<|"ode" -> x + y[x] == 1, "y" -> y, "x" -> x|>}
{<|"y" -> y, "x" -> x, "ode" -> y[x] == 999|>,
<|"ode" -> x + y[x] == 1, "y" -> y, "x" -> x|>}
<|"ode" -> y[x] == 0, "y" -> y, "x" -> x|>
*)
Alternative approach
The idea is to wrap the symbol reference to the association in a container that holds its arguments. This can then be used to define operations on the data structure. The form is diffEq[ode]
, where ode
is a Symbol
whose value is an Association
consisting of the data for the differential equation. You can then use AssociateTo
on ode
, if you're careful not to let ode
evaluate. Then you can define operations like this:
diffEq[ode_]["solution"] := DSolve[ode["ode"], ode["y"], ode["x"]];
There is a method for creating and updating a diffEq[]
called setupDiffEq
. One could argue that the method for updating should have its own name. Well, you can easily change it.
My data set could be quite large, since it might save things like the results of NDSolve
. It was convenient to format it with a summary form, which I've included.
I also threw in checkOpts[]
to check to see if the rules are valid for our data structure based on some remarks in the comments.
ClearAll[diffEq];
SetAttributes[diffEq, HoldAll];
diffEq /: MakeBoxes[de : diffEq[asc_], form_] /; AssociationQ[asc] :=
Module[{above, below, ivars},
ivars = Lookup[asc, "independentVars", Automatic];
above = {{BoxForm`SummaryItem[{Lookup[asc, "ode", None]}]}};
below = {};
BoxForm`ArrangeSummaryBox[diffEq, de, "ODE", above, below, form]];
(* Check that opts are Options of the symbol sym
* Returns { status (T/F), filtered good opts } *)
ClearAll[checkOpts];
SetAttributes[checkOpts, HoldFirst];
checkOpts[code_, sym_Symbol, opts : OptionsPattern[]] :=
With[{oplist = Flatten@{opts}},
With[{bad = FilterRules[oplist, Except@Options@sym]},
If[Length@bad > 0,
Message[sym::optx, First@bad, HoldForm@code];
{False, FilterRules[oplist, Options@sym]}
,
{True, oplist}
]
]];
ClearAll[setupDiffEq];
(* Create a diffEq[] from rules *)
call : setupDiffEq[opts : OptionsPattern[]] := Module[{ode},
With[{opres = checkOpts[call, setupDiffEq, opts]},
( (* TBD: validate/process option values *)
ode = Association[Last@opres];
diffEq[ode]
) /; First@opres
]];
(* Change an existing diffEq[] *)
setupDiffEq::optx = "Unknown diffEq key `1` in `2`.";
Options@setupDiffEq = {"ode", "y", "x"};
call : setupDiffEq[de : diffEq[asc_], opts : OptionsPattern[]] :=
With[{opres = checkOpts[call, setupDiffEq, opts]},
(AssociateTo[asc, Last@opres]
; de
) /; First@opres
];
Usage:
ode1data = <|"ode" -> y[x] == 0, "y" -> y, "x" -> x|>;
ode1 = diffEq[ode1data]
Or with a Module
-generated symbol.
ode1 = setupDiffEq["ode" -> y[x] == 0, "y" -> y, "x" -> x]
setupDiffEq[ode1, "ic" -> y[0] == 1]
setupDiffEq::optx: Unknown diffEq key ic->y[0]==1 in setupDiffEq[diffEq[ODE y[x]==0],ic->y[0]==1].
setupDiffEq[diffEq[ode1], "ic" -> y[0] == 1]
setupDiffEq[ode1, "ode" -> y[x] == 999]
OP's example
The data can be specified as lists, but I followed the OP's lead. If you prefer working strictly with associations, you could modify the definition of setupDiffEq
or add a definition like setupDiffEq[a_?AssociationQ] := setupDiffEq@Normal@a
.
ode1data = <|"ode" -> y[x] == 0, "y" -> y, "x" -> x|>;
ode2data = <|"ode" -> y[x] + x == 1, "y" -> y, "x" -> x|>;
ODEs = setupDiffEq /@ Normal@{ode1data, ode2data}
$32769], diffEq[ode$32770]}" />
sol = {y[x] == 19, y[x] == 29};(*new values to update with*)
MapThread[
setupDiffEq[#2, "ode" -> #1] &, {sol, ODEs}]
$32769], diffEq[ode$32770]}" />