2

I wonder if there is any way to make functions defined within the main function be local, in a similar way to local variables. For example, in this function that calculates the gradient of a scalar function,

grad(var,f) := block([aux],
    aux : [gradient, DfDx[i]],
    gradient : [],
    DfDx[i] := diff(f(x_1,x_2,x_3),var[i],1),
    for i in [1,2,3] do (
        gradient : append(gradient, [DfDx[i]])
    ),
    return(gradient)
)$

The variable gradient that has been defined inside the main function grad(var,f) has no effect outside the main function, as it is inside the aux list. However, I have observed that the function DfDx, despite being inside the aux list, does have an effect outside the main function.

Is there any way to make the sub-functions defined inside the main function to be local only, in a similar way to what can be made with local variables? (I know that one can kill them once they have been used, but perhaps there is a more elegant way)

Robert Dodier
  • 16,905
  • 2
  • 31
  • 48
User1234321
  • 321
  • 1
  • 10

2 Answers2

3

To address the problem you are needing to solve here, another way to compute the gradient is to say

grad(var, e) := makelist(diff(e, var1), var1, var);

and then you can say for example

grad([x, y, z], sin(x)*y/z);

to get

             cos(x) y  sin(x)    sin(x) y
            [--------, ------, - --------]
                z        z           2
                                    z

(There isn't a built-in gradient function; this is an oversight.)

About local functions, bear in mind that all function definitions are global. However you can approximate a local function definition via local, which saves and restores all properties of a symbol. Since the function definition is a property, local has the effect of temporarily wiping out an existing function definition and later restoring it. In between you can create a temporary function definition. E.g.

foo(x) := 2*x;

bar(y) := block(local(foo), foo(x) := x - 1, foo(y));

bar(100); /* output is 99 */

foo(100); /* output is 200 */

However, I don't this you need to use local -- just makelist plus diff is enough to compute the gradient.

There is more to say about Maxima's scope rules, named and unnamed functions, etc. I'll try to come back to this question tomorrow.

Robert Dodier
  • 16,905
  • 2
  • 31
  • 48
  • What is the reason that something as fundamental as computing the gradient of a scalar quantity w.r.t a list is not among the enormous amount of features that Maxima provides? I just had to look this up and I think it seriously impairs the accessibility of Maxima. – Felix Crazzolara Jun 29 '22 at 14:30
  • Yes, the lack of a `grad` function is a problem ... However, note that there is a built-in `jacobian`, so a simpler way to solve the original problem is to say `DF: jacobian(...); mygrad: DF[1];` (since `jacobian` returns a matrix and the gradient is just 1 row). I agree there should be a built-in `grad` function. – Robert Dodier Jun 29 '22 at 17:27
3

To compute the gradient, my advice is to call makelist and diff as shown in my first answer. Let me take this opportunity to address some related topics.

I'll paste the definition of grad shown in the problem statement and use that to make some comments.

grad(var,f) := block([aux],
    aux : [gradient, DfDx[i]],
    gradient : [],
    DfDx[i] := diff(f(x_1,x_2,x_3),var[i],1),
    for i in [1,2,3] do (
        gradient : append(gradient, [DfDx[i]])
    ),
    return(gradient)
)$

(1) Maxima works mostly with expressions as opposed to functions. That's not causing a problem here, I just want to make it clear. E.g. in general one has to say diff(f(x), x) when f is a function, instead of diff(f, x), likewise integrate(f(x), ...) instead of integrate(f, ...).

(2) When gradient and Dfdx are to be the local variables, you have to name them in the list of variables for block. E.g. block([gradient, Dfdx], ...) -- Maxima won't understand block([aux], aux: ...).

(3) Note that a function defined with square brackets instead of parentheses, e.g. f[x] := ... instead of f(x) := ..., is a so-called array function in Maxima. An array function is a memoizing function, i.e. if f[x] is called two or more times, the return value is only computed once, and then returned every time thereafter. Sometimes that's a useful optimization when the domain of the function comprises a finite set.

(4) Bear in mind that x_1, x_2, x_3, are distinct symbols, not related to each other, and not related to x[1], x[2], x[3], even if they are displayed the same. My advice is to work with subscripted symbols x[i] when i is a variable.

(5) About building up return values, try to arrange to compute the whole thing at one go, instead of growing the result incrementally. In this case, makelist is preferable to for plus append.

(6) The return function in Maxima acts differently than in other programming languages; it's a little hard to explain. A function returns the value of the last expression which was evaluated, so if gradient is that last expression, you can just write grad(var, f) := block(..., gradient).

Hope this helps, I know it's obscure and complex. The Maxima programming language was not designed before being implemented, and some of the decisions are clearly questionable at the long interval of more than 50 years (!) later. That's okay, they were figuring it out as they went along. There was not a body of established results which could provide a point of reference; the original authors were contributing to what's considered common knowledge today.

Robert Dodier
  • 16,905
  • 2
  • 31
  • 48