2
$\begingroup$

I have a question that would be trivial in OOP, but I am a newbie to programming in Mathematica, so I'm wondering how to deal with it.

Basically, I want to create a package which basically involves a bunch of matrix transformations to a vector. So I have a bunch of functions to create a bunch of different matrices, e.g.

CreateMatrix1[size, param1, param2], 
CreateMatrix2[size, param], 
...

For any given application, the matrix size is constant, but these functions are called many times, and it becomes a hassle to have to pass in the same size parameter every time. In OOP I would solve this by creating an manager object taking in size and implementing the various matrix operations as methods.

In Mathematica, is there some way to make it so all these functions already know what the matrix size is for the given application, thus saving the end-user from having to pass it in all the time? Is it possible to create a temporary context of some kind, but still allow for different contexts in the same notebook, i.e. not a global variable?

$\endgroup$
0

2 Answers 2

7
$\begingroup$

The idea

If you adhere to the convention that the size argument is always first in all your functions, then what you ask for can be achieved with some metaprogramming / dynamic environments. A dynamic environment is a function that takes your code, and generally modifies some definitions locally, only for the code that runs inside it.

Preparation

As a simple example, consider these 3 functions:

diag[size_, diagElement_] := DiagonalMatrix[ConstantArray[diagElement, size]]
identity[size_] := IdentityMatrix[size];
norms[size_, genF_, dotF_] := Outer[dotF, #, #] &[genF /@ Range[size]] 

They take the matrix size and sometimes other parameters, and generate various matrices. For example:

diag[2, 10]

(* {{10, 0}, {0, 10}} *)

identity[2]

(* {{1, 0}, {0, 1}} *)

norms[
  2, 
  Function[x, HermiteH[#, x]] &, 
  Function[{l, r}, Integrate[l[y]*r[y]* Exp[-y^2], {y, -Infinity, Infinity}]]
]

(* {{2 Sqrt[\[Pi]], 0}, {0, 8 Sqrt[\[Pi]]}} *)

where the last matrix is made of dot products of Hermite polynomials.

Automating redefinitions

What I suggest to do is to create a dynamic environment in which all these functions will be redefined to automatically assume the first (in this case) argument size to some fixed value.

Here is the code:

redefine[syms:{___Symbol}, f_]:=
  Module[{inSym},
    Scan[
      Function[sym,
        With[{protected = Unprotect[sym]},
          sym[args___] /; !TrueQ[inSym[sym]] :=
            Internal`InheritedBlock[{inSym},
              inSym[sym] = True;
              f[sym, {args}]
            ];
          Protect[protected]
        ]
      ],
      syms
    ]
  ]

withCurried[fixedParams__][affectedFunctions___Symbol] := Function[
  code
  ,
  Internal`InheritedBlock[{affectedFunctions},
    redefine[
      {affectedFunctions},
      Function[{s, a}, s[fixedParams, Sequence @@ a]]
    ];
    code
  ]
  ,
  HoldAll
]   

The first function takes a number of symbols and a function, that takes the symbol, the argument passed to that symbol, and then uses that to do arbitrary computation with those.

The second function takes a number of fixed argument values in the first group of arguments, and a number of symbols in the second, and creates a dynamic environment where all these symbols are redefined such that these fixed arguments are automatically prepended to the list of passed arguments when these functions are called.

Illustration

Let's assume we want to fix the size argument value to be 3, and we want all 3 functions from the example above, to be affected. We create a dynamic environment, and save it in a variable:

dynenv = withCurried[3][diag, identity, norms]

We are now ready to use it:

dynenv[{
  diag[10], 
  identity[], 
  norms[
    Function[x, HermiteH[#, x]] &, 
    Function[{l, r}, Integrate[l[y]*r[y]* Exp[-y^2], {y, -Infinity, Infinity}]
  ]
}]

(* 
  {
    {{10, 0, 0}, {0, 10, 0}, {0, 0, 10}}, 
    {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}, 
    {{2 Sqrt[\[Pi]], 0, 0}, {0, 8 Sqrt[\[Pi]], 0}, {0, 0, 48 Sqrt[\[Pi]]}}
  }
*)

You can see that now we have omitted the first parameter (size) in the calls to all these functions, yet they got the value 3 automatically passed to them.

You can create as many dynamic environments as you want, with different values of parameters (size in this case) embedded into them. The enviroments only affect the code that runs inside them. The global definitions of functions diag, identity, norms did not change.

This seems to be a more economical solution that creating new symbols in some new contexts. The only problematic case I can see for this approach is when you need to use these functions with different settings of size within the same piece of code.

$\endgroup$
4
  • $\begingroup$ Thanks, I think this effect is what I was after. I'll have to spend some time studying your code to understand how it all works, since there are so many things I haven't seen before. $\endgroup$
    – Paradox
    Commented Jun 20, 2019 at 0:16
  • $\begingroup$ @user2520385 Was glad to help. Thanks for the accept. To give you a few hints, the main building blocks I was using are (a version of) Villegas-Gayley trick (1, 2) and Internal`InheritedBlock (3). The former was used to redefine functions in question (in such a way that allows them calling their old definitions without getting into infinite recursion), and the latter was used to make all these redefinitions local to the dynamic scope of executed code. $\endgroup$ Commented Jun 20, 2019 at 2:00
  • $\begingroup$ Also, the lines protected = Unprotect[sym] and Protect[protected] are not essential for the operation of the code, in the sense that while they make the code more complete, they could also have been left out (particularly if none of the redefined symbols has the Protected attribute). They just allow one to redefine also Protected symbols (whose definitions can't be changed without first Unprotect-ing them), and if the symbol has been originally protected, reconstruct back the Protected attribute after the symbol has been redefined. $\endgroup$ Commented Jun 20, 2019 at 2:09
  • $\begingroup$ The last bits to understand here are that 1. the construct Function[code, body, HoldAll] acts like a macro, injecting unevaluated code into the body and then evaluating body. This is exactly equivalent to taking the actual code you pass into it and manually inserting it into the body for every occurrence of the code variable, and 2. I have modified the standard VG construct to use a single variable inSym to guard all functions at once, and so instead of the standard Block[{inFunction=True}, ...] I use Internal`InheritedBlock[{inSym}, inSym[sym]=True; ...]. $\endgroup$ Commented Jun 20, 2019 at 2:15
3
$\begingroup$

Another idea is to add tagging rules to a section cell, and define size so that it references that tagging rule. For instance:

size := Replace[
    PreviousCell[CellStyle -> "Section"],
    {
    cell_CellObject :> CurrentValue[cell, {TaggingRules, "MatrixSize"}, 5],
    _ -> 5
    }
]

The above code will look for the previous "Section" cell, and if it exists, it will find the current value of the "MatrixSize" tagging rule associated with that cell. If there is no "Section" cell, or there is no "MatrixSize" tagging rule defined, then it will use 5.

In this notebook, there are no previous "Section" cells:

PreviousCell[CellStyle -> "Section"]

None

So, evaluating size will return 5:

size

5

Now, I create a "Section" cell with no tagging rules:

CellPrint @ Cell["Untagged Section", "Section"]
Untagged Section

And, notice that the default is still used:

size

5

Finally, I create a "Section" cell with a tagging rule:

CellPrint @ Cell["Tagged Section", "Section", TaggingRules -> {"MatrixSize" -> 10}]
Tagged Section

This time size uses the tagging information:

size

10

You can add tagging rules to a section by using the menu item Cell | Show Expression or by using the option inspector, or by using CurrentValue. For instance:

CurrentValue[PreviousCell[CellStyle -> "Section"], {TaggingRules, "MatrixSize"}] = 20;

size

20

You could make this a function as well:

SetSectionTaggingRule[tag_ -> value_] := CurrentValue[
    PreviousCell[CellStyle -> "Section"],
    {TaggingRules, tag}
] = value;

Then:

SetSectionTaggingRule["MatrixSize" -> 3];

size

3

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.