In Mathematica trivial removable singularities of like x/x (in full form Times[x, Power[x, -1]]) are replaced by 1 during ordinary evaluation of Times, when appropriate pair of positive and negative Power of same expression is encountered. Similarly 1/(1/x) is replaced by x during evaluation of Power.
To prevent both kinds of replacements, during evaluation, we can dynamically assign dummy head to Power[...] expressions, using Block. Then we can wrap resulting expression with Hold, replace dummy head back with Power, and pass it to FunctionDomain:
f // ClearAll
f[x_] := 1/x
Block[{Power = power}, x f[x]] (* x power[x, -1] *)
Hold@Evaluate@% /. power -> Power (* Hold[x/x] *)
FunctionDomain[%, x] (* x<0 || x>0 *)
This technique evaluates expression, just without removing singularities. In contrast simple passing of expression to FunctionDomain either evaluates expression completely, removing singularities:
FunctionDomain[x f[x], x] (* True *)
or completely prevents evaluation, including evaluation of functions we would like to evaluate:
FunctionDomain[Hold[x f[x]], x]
(* ... FunctionDomain:Unable to find the domain with the available methods. *)
(* FunctionDomain[Hold[x f[x]],x] *)
Package
Function automating above Blocked evaluation is implemented in following package:
BeginPackage@"Domains`"; Unprotect@"`*"; ClearAll@"`*"
StrictFunctionDomain::usage = "\
StrictFunctionDomain[expr, vars, dom] \
finds the largest domain of definition of expression expr, treated as function \
in given variables vars, with arguments and values in domain dom, \
excludes all singularities encountered while evaluating expr, \
even if those singularities would be removed \
during ordinary evaluation of expr. \
vars can be a symbol or list of symbols. Domain dom can be Reals or Complexes.\
StrictFunctionDomain[expr, vars] \
uses Reals as domain.\
StrictFunctionDomain[expr] or StrictFunctionDomain[expr, Automatic, ...] \
uses variables extracted from expr.";
RestrictDomain::usage = "\
RestrictDomain[expr, vars, dom] \
returns expr with sub-expressions subExpr, for which, \
domain can be restricted, replaced with ConditionalExpression[subExpr, cond] \
where cond are conditions restricting domain of subExpr, \
treated as function in given variables vars, \
with arguments and values in domain dom. \
vars can be a symbol or list of symbols. Domain dom can be Reals or Complexes.\
RestrictDomain[expr, vars] \
uses Reals as domain.\
RestrictDomain[expr] or RestrictDomain[expr, Automatic, ...] \
for each replaced sub-expression uses variables extracted from it.";
Begin@"`Private`"; ClearAll@"`*"
StrictFunctionDomain // Attributes = HoldFirst;
StrictFunctionDomain[
expr_, vars_ : Automatic, dom : Reals | Complexes : Reals,
opts : OptionsPattern@FunctionDomain
] :=
With[{eval = evaluateKeepingSingularities@expr},
FunctionDomain @@ Unevaluated /@ Join[
eval,
getHeldVars[vars]@eval,
HoldComplete[dom, opts]
]
]
RestrictDomain // Attributes = HoldFirst;
RestrictDomain[
expr_, vars_ : Automatic, dom : Reals | Complexes : Reals,
opts : OptionsPattern@FunctionDomain
] :=
ReleaseHold@With[{getHeldVars = getHeldVars@vars},
evaluateKeepingSingularities@expr /.
subExpr : (acceptableHeads@dom)[___] :>
ConditionalExpression[subExpr,
FunctionDomain @@ Unevaluated /@ Join[
HoldComplete@subExpr,
getHeldVars@subExpr,
HoldComplete[dom, opts]
]
]
]
evaluateKeepingSingularities = Function[,
Internal`InheritedBlock[{Power},
With[{protected = Unprotect@Power},
Power@args__ /; Not@VectorQ[{args}, NumericQ] := power@args;
Protect@protected
];
HoldComplete@#&@# /. power -> Power
],
HoldAllComplete
];
getHeldVars // Attributes = HoldAllComplete;
getHeldVars@Automatic = Function[,
(HoldComplete[#]&@Union@Cases[Unevaluated@#,
s_Symbol /; Not@MemberQ[Attributes@s, Constant] :> HoldComplete@s,
{-1}
])[[All, All, 1]],
HoldAllComplete
];
getHeldVars@vars_ := Function[, HoldComplete@vars, HoldAllComplete]
acceptableHeads@Complexes =
Abs | AiryAi | AiryAiPrime | AiryBi | AiryBiPrime | AngerJ | ArcCos |
ArcCosh | ArcCot | ArcCoth | ArcCsc | ArcCsch | ArcSec | ArcSech |
ArcSin | ArcSinh | ArcTan | ArcTanh | Arg | BesselI | BesselJ | BesselK |
BesselY | Beta | Binomial | Boole | Ceiling | ConditionalExpression |
Conjugate | Cos | Cosh | CoshIntegral | CosIntegral | Cot | Coth | Csc |
Csch | CubeRoot | DawsonF | DiscreteDelta | Erf | Erfc | Erfi | Exp |
ExpIntegralE | ExpIntegralEi | Factorial | Factorial2 | Fibonacci | Floor |
FractionalPart | FresnelC | FresnelS | Function | Gamma | GammaRegularized |
GegenbauerC | Gudermannian | HarmonicNumber | Haversine | HermiteH |
Hypergeometric0F1 | Hypergeometric0F1Regularized | Hypergeometric1F1 |
Hypergeometric1F1Regularized | Im | IntegerPart | Integrate |
KroneckerDelta | LambertW | List | Log | Log10 | Log2 | LogGamma |
LogIntegral | Max | Min | Mod | Norm | Plus | Pochhammer | PolyGamma |
Power | PrimePi | ProductLog | Quotient | Re | RiemannSiegelTheta |
RiemannSiegelZ | Round | SawtoothWave | Sec | Sech | Sign | Sin | Sinc |
Sinh | SinhIntegral | SinIntegral | Sqrt | SquareWave | Surd | Tan | Tanh |
Times | TriangleWave | UnitStep | WeberE | Zeta;
acceptableHeads@Reals = Union[acceptableHeads@Complexes,
BarnesG | EllipticE | EllipticK | EllipticNomeQ | InverseEllipticNomeQ |
InverseErf | InverseErfc | InverseGammaRegularized
];
End[]; Protect@"`*"; EndPackage[];
Basic Examples
It works with examples from OP:
StrictFunctionDomain[(x^2 - x - 2)/(x^2 + x - 6), x]
StrictFunctionDomain[((x - 2) (x + 1))/((x - 2) (x + 3)), x]
(* x < -3 || -3 < x < 2 || x > 2 *)
(* x < -3 || -3 < x < 2 || x > 2 *)
In contrast to using simple FunctionDomain with held argument, StrictFunctionDomain works no matter when singularities appear during evaluation of expression. Singularities can be present in (nested) function definitions:
ClearAll[f, g]
f[x_] := 1/x
g[x_, y_] := (x - y)/(x - x y f[x])
and they will still be found by StrictFunctionDomain, even if function by itself evaluates to singularity free expression:
g[x, y] (* 1 *)
StrictFunctionDomain[g[x, y], {x, y}] (* x != 0 && x - y != 0 *)
It'll work also for function Composition from previous OP's question:
(f@*f)[x] (* x *)
StrictFunctionDomain[(f@*f)[x], x] (* x < 0 || x > 0 *)
Possible Issues
Using dynamic scoping (Block-like constructs) to change behavior of, as fundamental function as, Power is not entirely safe. It works as long as Power is used only in "mathematical expressions". But if a function uses Power in some procedure, not as "symbolic mathematical expressions" that is returned by this function, then changing behavior of Power can lead to unexpected behavior of this function.
That's why in above package Internal`InheritedBlock is used and only behavior of Power with non-numeric arguments is changed, which is safer, but still not completely safe.
Holdseems to work `In[1]:= FunctionDomain[Hold[((x - 2) (x + 1))/((x - 2) (x + 3))],x]Out[1]= x<-3||-3<x<2||x>2`
– Greg Hurst Feb 05 '17 at 15:35Simplifyis applied to the expression beforeFunctionDomainoperates is false as the two expressions supplied in the question simplify to(1+x)/(3+x)– Jack LaVigne Feb 05 '17 at 16:09Simplifyis not applied.((x - 2) (x + 1))/((x - 2) (x + 3))evaluates directly to(1 + x)/(3 + x), withoutSimplify.FunctionExpandnever even sees the expression the OP entered, only the expression that it evaluated to. – Szabolcs Feb 05 '17 at 16:20FunctionDomainfollows the normal evaluation procedure in Mathematica. Thank you for clarifying. – Jack LaVigne Feb 05 '17 at 16:26FunctionDomain[Hold[expr_]]is the intended use - primarily for use in WolframAlpha. If you type function domain x/x into WolframAlpha, for example, you'll get $x\neq0$. – Mark McClure Mar 07 '17 at 00:27