3
$\begingroup$

If I'm working with many dimensions the amount of variables I have to use may get out of hand. I wonder if there's a quicker way to write something like below for example:

f[{x1_, x2_, x3_, x4_, x5_, x6_, x7_}] := x1^2 + x2^2 + x3^2 + x4^2 + x5^2 + x6^2 + x7^2 < 1
$\endgroup$
3
  • 2
    $\begingroup$ I found multivariable functions irritating when I was first using Mathematica and teaching multivariable calculus. AFAIK, there is no natural way to compose functions and have Derivative work. For a while I would define two paradigms: f[v_List] := f @@ v; f[x_, y_, z_] := {x y, x^2 + z^2, 2 - y z} or whatever. This is not exactly what you're asking about, but I would advise not to define f as a function on a vector/List if you want to differentiate it. $\endgroup$
    – Michael E2
    May 24 at 17:42
  • 1
    $\begingroup$ f = Norm[#] < 1 & is nearly equivalent (is if all numbers are always real). $\endgroup$
    – Michael E2
    May 24 at 18:02
  • $\begingroup$ @MichaelE2 I hate how Wolfram chose to prioritize rows over columns. It causes so many problems. My Initialize notebook, which runs every time I open Mathematica, defines DTranspose = {3, 1, 4, 2}. Then I can do A = Array[a, {2, 2}]; X = Array[x, {2, 2}]; B = Array[b, {2, 2}]; Transpose[D[A.X + X.B, {X}], DTranspose] // MatrixForm. $\endgroup$ May 25 at 19:07

6 Answers 6

6
$\begingroup$
f2[xs_] := Total[xs^2] < 1;
f2[{a, b, c}]
(* a^2 + b^2 + c^2 < 1 *)

Now, you might have just used that sum-the-squared expression as an example, so it may need to be more complicated than this. Generally, though, if your arguments have some structure and some relationship, you can work directly with those semantics rather than destructure everything.

$\endgroup$
3
  • 1
    $\begingroup$ I never know what to make of an OP's "something like below"....Just what patterns are valid inferences? What sort of generalizations are we supposed to show? (+1) $\endgroup$
    – Michael E2
    May 24 at 17:49
  • $\begingroup$ yep. And after reading your comment/answer, it may be that I was too simplistic. $\endgroup$
    – lericr
    May 24 at 17:53
  • 1
    $\begingroup$ It happens all the time (that the OP posts a simpler, more symmetric example subject to efficient short cuts than their actual use case). Of course, it's one of the great reasons for discussing your problems. You see it one way and are stuck; the other person sees it a different way. Between the two, you get a better picture and an increased chance at solution. $\endgroup$
    – Michael E2
    May 24 at 18:00
4
$\begingroup$

If you are bothered by manually writing the pattern for f with many arguments of type $x_i$, you can make an auxiliary function:

SetAttributes[args, HoldFirst];
args[var_, n_] := 
 Pattern[#, _] & /@ 
  Table[Symbol[SymbolName[Unevaluated[var]] <> ToString[i]], {i, n}]

Clear[f]
f[args[x, 50]] := x1 + 5 x20 + x33^5

Definition[f]
(* f[{x1_, x2_, x3_, x4_, x5_, x6_, x7_, x8_, x9_, x10_, x11_, x12_, 
   x13_, x14_, x15_, x16_, x17_, x18_, x19_, x20_, x21_, x22_, x23_, 
   x24_, x25_, x26_, x27_, x28_, x29_, x30_, x31_, x32_, x33_, x34_, 
   x35_, x36_, x37_, x38_, x39_, x40_, x41_, x42_, x43_, x44_, x45_, 
   x46_, x47_, x48_, x49_, x50_}] := x1 + 5 x20 + x33^5 *)
$\endgroup$
3
  • $\begingroup$ In my experience this is more safe than the down values x[i] since the variables are atomic. There are drawbacks though, when contexts and attributes are involved. And one way to solve this is to use the Evaluate In Place shortcuts. $\endgroup$
    – Lacia
    May 25 at 2:17
  • $\begingroup$ Might I suggest args[var_String, n_] := Table[ToExpression[var <> ToString[i], StandardForm, Function[Null, Pattern[#, _], HoldAll]], {i, n}]? Then Clear[f]; x = 7; f[args["x", 50]] := x1 + 5 x20 + x33^5 works as expected. (SymbolName[var] leaks the evaluation of x if var is x. Alternatively, try SymbolName[Unevaluated@var], which is a simpler change from what you have.) $\endgroup$
    – Michael E2
    May 25 at 3:10
  • $\begingroup$ @MichaelE2, thanks! I was trying to avoid strings to make it shorter in the first place, so I'll take your suggestion with Unevaluated :) $\endgroup$
    – Domen
    May 25 at 9:49
2
$\begingroup$

Similar to @lericr's:

f1 // ClearAll;
f1[v_?VectorQ] := v . v < 1

f1[{x1, x2, x3, x4, x5, x6, x7}]

(*  x1^2 + x2^2 + x3^2 + x4^2 + x5^2 + x6^2 + x7^2 < 1  *)

Following my own advice in a comment, plus taking advantage of Mma's obfuscatory strengths and breaking Derivative at the same time (even if the < 1 is removed)(Derivative seems to work if < 1 is removed; test code had a typo):

f2 // ClearAll;
call : f2[__] := Block[{f2 = List}, call . call < 1]

f2[x1, x2, x3, x4, x5, x6, x7]

(*  x1^2 + x2^2 + x3^2 + x4^2 + x5^2 + x6^2 + x7^2 < 1  *)
$\endgroup$
0
1
$\begingroup$

Another way to handle the problem of lots of variables is to use arrays rather than separately naming all the elements. For example, you can name your seven variables:

X = Array[x, 7];

so that

X = {x[1], x[2], x[3], x[4], x[5], x[6], x[7]}

Then define your function in terms of X:

f[z_] := z . z < 1

Then invoking

f[X]
x[1]^2 + x[2]^2 + x[3]^2 + x[4]^2 + x[5]^2 + x[6]^2 + x[7]^2 < 1 
$\endgroup$
1
$\begingroup$

This answer is an expanded comment.

Global variables are not safe and can cause lots of confusions. However, in some situations, using global variables are more convenient and suitable than specifying all the variables in every definition of functions.

For example, suppose we have a single notebook serving as a one-way script to calculate some quantities, and there are lots of pure functions with tree-like dependencies.

f0[args___]:=...

f11[args___]:="codes with f0"
f12[args___]:="codes with f0"

f2[args___]:="codes with f0, f11, f12"
...

Then the common arguments of the functions are better treated as global variables, especially the number of common arguments is quite large, say 20 arguments.

  • This makes the code more readable, and the pattern matching more efficient.

  • It's better to protect the global symbols at the beginning like

reserveSymbol[symbol_Symbol] :=
    (
        Unprotect@symbol;
        ClearAll@symbol;
        symbol::usage = "This symbol has been cleared and reserved.";
        Protect@symbol;
    );
  • To evaluate some global symbols at specific values it's better to use /. and //..
$\endgroup$
0
$\begingroup$

I recommend using Indexed in this case, as it allows to work with both variables and explicit Lists, and it works flawlessly with D/Derivative and other functions. Example:

(* utility to create a list of indexed variables *)
indexed[x_, to_Integer] := Table[Indexed[x, i], {i, to}]
indexed[x_, from_Integer, to_Integer] := Table[Indexed[x, i], {i, from, to}]

(* a couple functions defined with this convention *)
f[x_] := With[{xi = indexed[x, 5]}, xi.xi]
g[x_] := With[{x1 = Indexed[x, 1], x2 = Indexed[x, 2]}, x1 + x2]

(* all the following examples work as expected *)
D[f[x], Indexed[x, 3]]
Derivative[{0, 0, 1, 0, 0}][f][indexed[x, 5]]
Reduce[f[x] < 1]
Reduce[f[{x1, 0, x3, 0, x5}] < 1]
f[{1, 2, 3, 4, 5}]
Minimize[{g[x], f[x] < 1}, x \[Element] Ball[{1, 0, 0, 0, 0}]]

The code

Derivative[{0, 0, 1, 0, 0}][f][x]

requires special treatment, because Derivative insists in using Part instead of Indexed. A possible workaround is

(Derivative[{0, 0, 1, 0, 0}][f] /. Part -> Indexed)[x]

The only thing I regret is that Indexed cannot be inserted in a notebook with two-dimensional layout the same way as Subscript can (using Ctrl + - or Ctrl + _). Unfortunately one has to type Indexed explicitly. Other than that, it's perfect.

$\endgroup$

Your Answer

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

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