I would like to test whether an argument is a valid variable for functions like Solve and DSolve. For instance several functions generate a "dsvar" or "ivar" message on bad input:

DSolve::dsvar: 2 x cannot be used as a variable. >>
Integrate::ivar: Sin[x] is not a valid variable. >>

I would like to check an argument before passing it onto one of these functions.

I used the function withBlockedVars in this answer to come up with what seems like a maybe-not-very-bad way. I block the variable and attempt to assign a value to it.

SetAttributes[withBlockedVars, HoldRest];
withBlockedVars[Hold[expr_], code_] :=
 With[{heldVars =
        s_Symbol /; Context[s] === "Global`" && DownValues[s] === {} :> HoldComplete[s],
        Heads -> True],
  heldVars /. HoldComplete[vars_List] :> Block[vars, code]]

SetAttributes[variableQ, HoldAll];
variableQ[x_] := withBlockedVars[Hold[x], Quiet@Check[x = 0; True, False]];


t = 2;
(* False *)

t = 2; x = 3;
(* True *)

(* True *)

variableQ[Subscript[t, 2]]
(* True *)

My use-case is for defining functions, something like this:

f[eqn_Equal, var_?variableQ] := code

The code might call NDSolve or Plot and so on.

Is there a better way to check var? Perhaps there is a built-in function I missed?

[Edit: Any solution involving Pattern, PatternTest, Condition etc. would be acceptable. I'm not sure I can think of all the alternative possibilities.]

  • $\begingroup$ I thought perhaps "bulletproofing" or "package-writing" might be a good tag, but I found none similar. $\endgroup$ – Michael E2 Jan 10 '14 at 21:18
  • $\begingroup$ Are you looking for another variableQ or are you OK with alternate approaches? $\endgroup$ – rm -rf Jan 10 '14 at 21:26
  • $\begingroup$ @rm-rf I'm ok with alternate approaches. $\endgroup$ – Michael E2 Jan 10 '14 at 21:29
  • $\begingroup$ If you define a variable as an expression which can be assigned a value (is an L-value), then the test based on Block alone will miss some cases, e.g. Protected symbols. $\endgroup$ – Leonid Shifrin Jan 10 '14 at 21:32
  • 3
    $\begingroup$ What about using the built-in checks? Something like variableQ[var_] := Quiet[Check[Solve[0, var]~Quiet~Solve::naqs; True, False, Solve::ivar], Solve::ivar] $\endgroup$ – Rojo Jan 11 '14 at 0:59

It seems to me that for the basic case described in the question it would be best to simply check if a System function considers it valid, as I did for Pattern that matches colors. Therefore:

variableQ = Quiet @ ListQ @ Solve[{}, #] &;
| improve this answer | |
  • $\begingroup$ +1 Thanks, I like this (similar to Rojo's comment above). The problem is for me that the requirements of the functions mentioned in the question, Solve, DSolve, Plot, etc., are different. And I don't think I fully appreciated the differences when I posed the question. For instance, Plot tries to use something like Set on the plot variable. So while {E[1], D[x], x[1]} all pass your variableQ but only x[1] works with Plot. Solve seems very forgiving of what sort of expression may be used as a variable: Solve[Plot[x^2, {x, 0, 1}]^2 == 4, Plot[x^2, {x, 0, 1}]] $\endgroup$ – Michael E2 Jul 9 '14 at 4:12
  • $\begingroup$ @MichaelE2 Perhaps a separate test for each kind of function? (solveVariableQ etc.) Or how about using the Message itself to check for the error? Would that be acceptable to you? $\endgroup$ – Mr.Wizard Jul 9 '14 at 4:44
  • $\begingroup$ Perhaps a plotVariableQ would be the most restrictive and would work for the other functions. Plot[0, {var, 0, 1}, PlotPoints -> 2, MaxRecursion -> 0] is quick to fail and not too slow to succeed. I wanted to avoid a long computation that ends in a message that the input was invalid. For instance a long (N)DSolve followed by Plot. Perhaps my withBlockedVars method is ok? (My Q really was a generalization based on the observation that M checks for valid variables for some alg./calc. functions. In the case of Plot it does it early.) $\endgroup$ – Michael E2 Jul 9 '14 at 15:29
  • $\begingroup$ This seems good: variableQ = Quiet@Check[Table[0, {#, 1}]; True, False] & - what do you think? It's fast to fail or succeed and it tries to write the variable, like Plot. $\endgroup$ – Michael E2 Jul 9 '14 at 15:52
  • $\begingroup$ @MichaelE2 Table is no good for your purpose because it does not respect Protected on bare Symbols, e.g.: Table[E, {E, 1}]. (Enigmatically it complains with E[1].) Then again Plot accepts input such as Plot[E, {E, 0, 15}] without issuing a message but doesn't work properly, yielding a flat line. Does your original method return the correct True/False for all cases? If so I can base a method on those results. $\endgroup$ – Mr.Wizard Jul 9 '14 at 19:26

Instead of a variableQ, how about constraining var to be a Symbol, giving your function the HoldAll attribute and then Blocking the variable? I use this idiom/pattern all the time. As an example:

SetAttributes[f, HoldAll]
f[expr_, var_Symbol] := Block[{var}, Solve[expr, var] /. var -> Defer@var]
f[expr_, var_[n___]] := Block[{var}, f[expr /. var[n] -> var, var] /. var -> var@n]

Now try the following:

x = 1;
f[x^2 + 2 x + 1 == 0, x]
(* {{x -> -1}, {x -> -1}} *)

a[1] = 2;
f[a[1]^2 + 2 a[1] + 1 == 0, a[1]]
(* {{a[1] -> -1}, {a[1] -> -1}} *)

No ivar error. This also takes care of cases like x^2 or 2 x, because these are not Symbols.

| improve this answer | |
  • 1
    $\begingroup$ You don't have to use Block - if the symbol evaluates, it is not a valid variable (does not match _Symbol any more) :). Otherwise, that's what I'd also do - +1. $\endgroup$ – Leonid Shifrin Jan 10 '14 at 21:35
  • 1
    $\begingroup$ @LeonidShifrin The Block takes care of cases where there might be stray assignments in Global that you don't want evaluating inside the body. (for instance, in my example above, removing the Block will give you an ivar error) Given Michael's use case, I suspect this would be a desirable option. $\endgroup$ – rm -rf Jan 10 '14 at 21:37
  • $\begingroup$ I see. As an alternative, you could simply use HoldFirst, and omit the Block. But I agree, you construct is generally more useful, and this is also what I usually do. $\endgroup$ – Leonid Shifrin Jan 10 '14 at 21:42
  • $\begingroup$ This was the first thing I thought of, too, mainly for simplicity (for myself). But I'm writing it for students and I anticipate their wanting to do things their own way and having "stray assigments" about. We also will have systems and I wondered if it would be hard to handle x[1] etc. If I do it this way, I could write my own error message. +1 $\endgroup$ – Michael E2 Jan 10 '14 at 21:44
  • $\begingroup$ @MichaelE2 x[1] can be handled with a second definition: f[expr_, var_[n___]] := Block[{var}, f[expr /. var[n] -> var, var] /. var -> var@n]. $\endgroup$ – rm -rf Jan 10 '14 at 21:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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