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

mathematical optimization - Minimizing using indices, error: Part::pkspec1: The expression cannot be used as a part specification

I want to use Minimize where the variables to minimize are indices pointing into an array. Here a MWE that hopefully shows what my problem is. vars = u@# & /@ Range[3]; cons = Flatten@ { Table[(u[j] != #) & /@ vars[[j + 1 ;; -1]], {j, 1, 3 - 1}], 1 vec1 = {1, 2, 3}; vec2 = {1, 2, 3}; Minimize[{Total@((vec1[[#]] - vec2[[u[#]]])^2 & /@ Range[1, 3]), cons}, vars, Integers] The error I get: Part::pkspec1: The expression u[1] cannot be used as a part specification. >> Answer Ok, it seems that one can get around Mathematica trying to evaluate vec2[[u[1]]] too early by using the function Indexed[vec2,u[1]] . The working MWE would then look like the following: vars = u@# & /@ Range[3]; cons = Flatten@{ Table[(u[j] != #) & /@ vars[[j + 1 ;; -1]], {j, 1, 3 - 1}], 1 vec1 = {1, 2, 3}; vec2 = {1, 2, 3}; NMinimize[ {Total@((vec1[[#]] - Indexed[vec2, u[#]])^2 & /@ R...

functions - Get leading series expansion term?

Given a function f[x] , I would like to have a function leadingSeries that returns just the leading term in the series around x=0 . For example: leadingSeries[(1/x + 2)/(4 + 1/x^2 + x)] x and leadingSeries[(1/x + 2 + (1 - 1/x^3)/4)/(4 + x)] -(1/(16 x^3)) Is there such a function in Mathematica? Or maybe one can implement it efficiently? EDIT I finally went with the following implementation, based on Carl Woll 's answer: lds[ex_,x_]:=( (ex/.x->(x+O[x]^2))/.SeriesData[U_,Z_,L_List,Mi_,Ma_,De_]:>SeriesData[U,Z,{L[[1]]},Mi,Mi+1,De]//Quiet//Normal) The advantage is, that this one also properly works with functions whose leading term is a constant: lds[Exp[x],x] 1 Answer Update 1 Updated to eliminate SeriesData and to not return additional terms Perhaps you could use: leadingSeries[expr_, x_] := Normal[expr /. x->(x+O[x]^2) /. a_List :> Take[a, 1]] Then for your examples: leadingSeries[(1/x + 2)/(4 + 1/x^2 + x), x] leadingSeries[Exp[x], x] leadingSeries[(1/x + 2 + (1 - 1/x...

What is and isn't a valid variable specification for Manipulate?

I have an expression whose terms have arguments (representing subscripts), like this: myExpr = A[0] + V[1,T] I would like to put it inside a Manipulate to see its value as I move around the parameters. (The goal is eventually to plot it wrt one of the variables inside.) However, Mathematica complains when I set V[1,T] as a manipulated variable: Manipulate[Evaluate[myExpr], {A[0], 0, 1}, {V[1, T], 0, 1}] (*Manipulate::vsform: Manipulate argument {V[1,T],0,1} does not have the correct form for a variable specification. >> *) As a workaround, if I get rid of the symbol T inside the argument, it works fine: Manipulate[ Evaluate[myExpr /. T -> 15], {A[0], 0, 1}, {V[1, 15], 0, 1}] Why this behavior? Can anyone point me to the documentation that says what counts as a valid variable? And is there a way to get Manpiulate to accept an expression with a symbolic argument as a variable? Investigations I've done so far: I tried using variableQ from this answer , but it says V[1...