4

What's a good pattern to turn expression+replacement rules into a function which automatically applies these replacement rules?

IE, I'm finding a common pattern in my code, have some programmatically constructed formula with indexed symbols, and then evaluate it using replacement rules

n = 2;
amat = Array[a, {n, n}];
amat0 = RandomReal[{0, 1}, {n, n}];
expr = a[1, 1] + a[1, 2] + a[2, 1] + a[2, 2];

expr /. Flatten[Thread /@ Thread[amat -> amat0]]

It would be convenient to have a utility to turn expr into function, so instead of cumbersome replacement syntax I could do

func[amat0]

What's a good pattern to achieve this?

I've made an attempt below discovering following blockers:

  • Function wants a flat list of parameters, I can't give it a matrix
  • Function does not allow indexed symbols as parameters
A = {{1, 2}, {3, 4}, {5, 6}};

{m, n} = Dimensions@A; wvec = Array[w, n]; norm2[vec_] := Total[vec*vec];

w0 = ConstantArray[0, n]; yhat = {1, 2, 3};

(turns w[1],w[2] into www1,www2) removeIndices[ l_] := (MapIndexed[Symbol["www" <> ToString[First@#2]] &, l]); (turns expression in terms of wvec into function) functify[expr_] := With[{params = removeIndices[wvec]}, Function @@ {params, expr /. Thread[wvec -> params]}] F = functify[1/m 1/2 norm2[A . wvec - yhat]^2] F @@ w0

Yaroslav Bulatov
  • 7,793
  • 1
  • 19
  • 44
  • Could you maybe describe what you're trying to achieve in words? Are you just trying to set all entries of an existing indexed variable simultaneously to random values? Or is the random thing just an illustration? I'm struggling to even understand what your attempted update is doing. Also, your assertions about Function just aren't true. At least not as stated. Maybe something unexpected is happening because of the way you've structured your attempt, but I don't think any limitations of Function are the problem here. – lericr Feb 25 '23 at 18:49
  • Is the final result (the output of F @@ w0) the result you want, or is this illustrating the blockers? – lericr Feb 25 '23 at 18:50
  • In words I want a utility which takes an expression in terms of entries of symbolic matrix Array[c, ...] and turns it into a function which I can call by passing a numeric matrix as argument like this: f[RandomReal...] – Yaroslav Bulatov Feb 25 '23 at 18:53
  • 1
    If you have a list of expressions-to-replace and a list of replacement-values, then you can create the replacement rules with MapThread. So, like MapThread[Rule, {amat, amat0}, 2]. – lericr Feb 25 '23 at 18:53
  • 1
    how about ClearAll[makeFunc]; makeFunc[expr_] := vals |-> ReplaceAll[Head[First@Variables[expr]] -> (vals[[##]] &)][expr]; makeFunc[expr]@amat0? – kglr Feb 25 '23 at 19:21

2 Answers2

2

Maybe something like this:

MyFunctify[sym_Symbol, positions_, values_] :=
  ReplaceAll[MapThread[Rule, {sym @@@ positions, Extract[values, positions]}]]

Sample usages (using the variables you provided in your question):

This gives the function:

MyFunctify[a, {{1, 1}, {1, 2}, {2, 1}, {2, 2}}, amat0]

This applies the function:

MyFunctify[a, {{1, 1}, {1, 2}, {2, 1}, {2, 2}}, amat0][amat]

{{0.369357, 0.283924, a[1, 3]}, {0.645261, 0.980033, a[2, 3]}, {a[3, 1], a[3, 2], a[3, 3]}}

Or alternatively, if the problem is to extract the indexed bits from a given expression (instead of providing the positions as an argument), you could try something like this.

MyFunctify2[sym_Symbol, expr_, values_] :=
  With[
    {keys = Cases[expr, Blank[sym]]},
    ReplaceAll[MapThread[Rule, {keys, Extract[values, List @@@ keys]}]]]

Usage:

MyFunctify2[a, a[1, 1] + a[1, 2] + a[2, 1] + a[2, 2], amat0][amat]

{{0.53101, 0.995705, a[1, 3]}, {0.396673, 0.0883133, a[2, 3]}, {a[3, 1], a[3, 2], a[3, 3]}}

Or:

MyFunctify2[a, a[1, 1] + a[1, 2] + a[2, 1] + a[2, 2], amat0][a[1, 1] + a[1, 2] + a[2, 1] + a[2, 2]]

1.66298

(amat was re-evaluated between usages, so the values are different.)

lericr
  • 27,668
  • 1
  • 18
  • 64
2

The following is much simpler and also works for scalar variables

makeCallable[expr_] := 
 Function @@ {Variables@Level[expr, {-1}], expr}

kglr gave a useful pattern in comments. A good name for this pattern is makeCallable, since it automatically makes any expression callable.

Cleaning up a bit with some error checking:

(*

Takes an expression involving a single indexed variable, and makes it callable . Example usage :

 n = 2;

amat = Array[a, {n, n}]; amat0 = RandomReal[{0, 1}, {n, n}]; expr = a[1, 1] + a[1, 2] + a[2, 1] + a[2, 2]; expr2 = makeCallable[expr];

expr2[amat] expr2[amat0]

*)

makeCallable[expr_] := Module[{}, vars = DeleteDuplicates[Head /@ Variables[expr]]; Assert @@ {Length[vars] == 1, "expr must contain single variable, but see "~Join~ ToString[vars]}; vals |-> ReplaceAll[First@vars -> (vals[[##]] &)][expr]];

Yaroslav Bulatov
  • 7,793
  • 1
  • 19
  • 44