Skip to main content

programming - Struct equivalent in Mathematica?


I really miss having something like a struct in Mathematica. I know of (and regularly use) a couple of programming techniques which feel like a struct (e.g., using downvalues), but are ultimately unsatisfactory (perhaps I'm using downvalues incorrectly). What programming approaches are available which provide similar functionality to a struct?


Here's an abbreviated (and hopefully not too obtuse) example of how I use downvalues to emulate a struct. In this case, I'm distinguishing between TLC and TEC (these are sets of parameters for two different phases of a Moon mission, trans-lunar cruise and trans-earth cruise):


deadBandWidth[X][TLC] ^= 10. \[Degree];
deadBandWidth[Y][TLC] ^= 10. \[Degree];
deadBandWidth[Z][TLC] ^= 20. \[Degree];
sunSearchAngle[Z][TLC] ^= 230. \[Degree];
sunSearchRate[Z][TLC] ^= 1. \[Degree]/Second;
sunSearchAngle[X][TLC] ^= 75. \[Degree];
sunSearchRate[X][TLC] ^= 1. \[Degree]/Second;

safingSpinRate[TLC] ^= (360. \[Degree])/Day;
sunVector[TLC] ^= {-Cos[45. \[Degree]], 0., Sin[45. \[Degree]]};
safingSpinAxis[TLC] ^= sunVector[TLC];

deadBandWidth[X][TEC] ^= 20. \[Degree];
deadBandWidth[Y][TEC] ^= 20. \[Degree];
deadBandWidth[Z][TEC] ^= 20. \[Degree];
sunSearchAngle[Z][TEC] ^= 230. \[Degree];
sunSearchRate[Z][TEC] ^= 1. \[Degree]/Second;
sunSearchAngle[X][TEC] ^= 75. \[Degree];

sunSearchRate[X][TEC] ^= 1. \[Degree]/Second;
safingSpinRate[TEC] ^= (360. \[Degree])/Hour;
sunVector[TEC] ^= {0., 0., +1.};
safingSpinAxis[TEC] ^= sunVector[TEC];

?TLC
Global`TLC
safingSpinAxis[TLC]^={-0.707107,0.,0.707107}
safingSpinRate[TLC]^=6.28319/Day
sunVector[TLC]^={-0.707107,0.,0.707107}

deadBandWidth[X][TLC]^=0.174533
deadBandWidth[Y][TLC]^=0.174533
deadBandWidth[Z][TLC]^=0.349066
sunSearchAngle[X][TLC]^=1.309
sunSearchAngle[Z][TLC]^=4.01426
sunSearchRate[X][TLC]^=0.0174533/Second
sunSearchRate[Z][TLC]^=0.0174533/Second

Answer



Update: Mathematica 10 has introduced Association, which can be used as a close equivalent of structs.


params = <| "par1" -> 1, "par2" -> 2 |>


params["par1"]
(* ==> 1 *)

In version 10 pure functions can have named arguments, and can be effectively used as expression templates where the slots can be populated from an association. This is similar to the technique I describe in the original version of this post (below the line).


#par1 + #par2 & [params]

will evaluate to 1 + 2 then to 3.


That said, my personal workflow still fits better with the approach described below the line (withRules). The reason for this is that I tend to build up calculations interactively and incrementally. This means that I do not start by writing the equivalent of an expression template (which would require thinking ahead...). Instead I start with all the values explicitly written out, and later I replace them with a global variable. This global variable can be simply Unset, and given a local value using withRules, then eventually changed into a function argument.





Quoting the OP's comment:



Most of the work I do involves constructing mathematical models and then testing various scenarios against those models. I'd like to be able to populate a particular scenario and then pass that scenario to a model. I'd also like to be able to copy that scenario, modify one or more parameters, and then pass the new scenario to the model.



The requirement, as I understand, is to be able to pass many parameter values around in a structured way. Lists of rules are convenient for this:


params = {par1 -> 1, par2 -> 2, par3 -> {x,y,z}}

They can be extracted like this:


par1 /. params


(* ==> 1 *)

Once I wrote a function for substituting such parameter lists into bigger pieces of code:


ClearAll[withRules]
SetAttributes[withRules, HoldAll]
withRules[rules_, expr_] :=
First@PreemptProtect@Internal`InheritedBlock[
{Rule, RuleDelayed},
SetAttributes[{Rule, RuleDelayed}, HoldFirst];
Hold[expr] /. rules

]

It can be used like this:


withRules[params,
par1 + par2
]

(* ==> 3 *)

withRules can contain complex code inside, and all occurrences of par1, par2, etc. will be substituted with the values from the parameter list.



We can also write a function for easily modifying only a single parameter (from the whole list), and returning a new parameter list. Here's a simple implementation:


setParam[paramList_, newRules_] :=
DeleteDuplicates[Join[newRules, paramList],
First[#1] === First[#2] &]

Example usage:


setParam[params, {par2 -> 10}]

(* ==> {par2 -> 10, par1 -> 1, par3 -> {x, y, z}} *)


Another list which has a different value for par2 is returned.




If needed, this could be extended to support more complex, structured lists such as { par1 -> 1, group1 -> {par2x -> 10, par2y -> 20}}, much how like the built-in option-handling works.




Addendum by celtschk: It's possible to extract a value from a list of rules using OptionValue as well: OptionValue[params, par1].


Comments

Popular posts from this blog

plotting - Plot 4D data with color as 4th dimension

I have a list of 4D data (x position, y position, amplitude, wavelength). I want to plot x, y, and amplitude on a 3D plot and have the color of the points correspond to the wavelength. I have seen many examples using functions to define color but my wavelength cannot be expressed by an analytic function. Is there a simple way to do this? Answer Here a another possible way to visualize 4D data: data = Flatten[Table[{x, y, x^2 + y^2, Sin[x - y]}, {x, -Pi, Pi,Pi/10}, {y,-Pi,Pi, Pi/10}], 1]; You can use the function Point along with VertexColors . Now the points are places using the first three elements and the color is determined by the fourth. In this case I used Hue, but you can use whatever you prefer. Graphics3D[ Point[data[[All, 1 ;; 3]], VertexColors -> Hue /@ data[[All, 4]]], Axes -> True, BoxRatios -> {1, 1, 1/GoldenRatio}]

plotting - Filling between two spheres in SphericalPlot3D

Manipulate[ SphericalPlot3D[{1, 2 - n}, {θ, 0, Pi}, {ϕ, 0, 1.5 Pi}, Mesh -> None, PlotPoints -> 15, PlotRange -> {-2.2, 2.2}], {n, 0, 1}] I cant' seem to be able to make a filling between two spheres. I've already tried the obvious Filling -> {1 -> {2}} but Mathematica doesn't seem to like that option. Is there any easy way around this or ... Answer There is no built-in filling in SphericalPlot3D . One option is to use ParametricPlot3D to draw the surfaces between the two shells: Manipulate[ Show[SphericalPlot3D[{1, 2 - n}, {θ, 0, Pi}, {ϕ, 0, 1.5 Pi}, PlotPoints -> 15, PlotRange -> {-2.2, 2.2}], ParametricPlot3D[{ r {Sin[t] Cos[1.5 Pi], Sin[t] Sin[1.5 Pi], Cos[t]}, r {Sin[t] Cos[0 Pi], Sin[t] Sin[0 Pi], Cos[t]}}, {r, 1, 2 - n}, {t, 0, Pi}, PlotStyle -> Yellow, Mesh -> {2, 15}]], {n, 0, 1}]

plotting - Mathematica: 3D plot based on combined 2D graphs

I have several sigmoidal fits to 3 different datasets, with mean fit predictions plus the 95% confidence limits (not symmetrical around the mean) and the actual data. I would now like to show these different 2D plots projected in 3D as in but then using proper perspective. In the link here they give some solutions to combine the plots using isometric perspective, but I would like to use proper 3 point perspective. Any thoughts? Also any way to show the mean points per time point for each series plus or minus the standard error on the mean would be cool too, either using points+vertical bars, or using spheres plus tubes. Below are some test data and the fit function I am using. Note that I am working on a logit(proportion) scale and that the final vertical scale is Log10(percentage). (* some test data *) data = Table[Null, {i, 4}]; data[[1]] = {{1, -5.8}, {2, -5.4}, {3, -0.8}, {4, -0.2}, {5, 4.6}, {1, -6.4}, {2, -5.6}, {3, -0.7}, {4, 0.04}, {5, 1.0}, {1, -6.8}, {2, -4.7}, {3, -1....