The compiler does a pretty good job of finding and eliminating common subexpressions. We can leverage that functionality.
Starting from the provided expression...
$expr = (1/Sqrt[2](Sqrt[((Sqrt[((-x3 - (1/2 Cos[a] (m-h Cos[a]) + x3 Tan[a] - x3 (-m Sec[a]/(x2-x3) + Tan[a]) - Tan[b])/(-m Sec[a]/(x2-x3) + Tan[a]))^2 + (-1/2 Cos[a] (m-h Cos[a]) - x3 Tan[a] + Tan[b])^2)] + Sqrt[((-x3 - Cos[a] (m-h Cos[a])/(2 (Tan[a]-Tan[b])))^2 + (-1/2 Cos[a] (m-h Cos[a]) - x3 Tan[a] - (Cos[a] (m-h Cos[a]) Tan[b])/(2 (Tan[a]-Tan[b])))^2)] + Sqrt[((-(Cos[a] (m-h Cos[a]))/(2 (Tan[a]-Tan[b])) + (1/2 Cos[a] (m-h Cos[a]) + x3 Tan[a] - x3 (-m Sec[a]/(x2-x3) + Tan[a]) - Tan[b])/(-m Sec[a]/(x2-x3) + Tan[a]))^2 + (-Tan[b] - (Cos[a] (m-h Cos[a]) Tan[b])/(2 (Tan[a]-Tan[b])))^2)])(-Sqrt[((-x3 - (1/2 Cos[a] (m-h Cos[a]) + x3 Tan[a] - x3 (-m Sec[a]/(x2-x3) + Tan[a]) - Tan[b])/(-m Sec[a]/(x2-x3) + Tan[a]))^2 + (-1/2 Cos[a] (m-h Cos[a]) - x3 Tan[a] + Tan[b])^2)] + 1/2 (Sqrt[((-x3 - (1/2 Cos[a] (m-h Cos[a]) + x3 Tan[a] - x3 (- m Sec[a]/(x2-x3) + Tan[a])-Tan[b])/(-m Sec[a]/(x2-x3) + Tan[a]))^2 + (-1/2 Cos[a] (m-h Cos[a]) -x3 Tan[a] + Tan[b])^2)] + Sqrt[((-x3 - (Cos[a] (m-h Cos[a]))/ (2 (Tan[a]-Tan[b])))^2 + (-1/2 Cos[a] (m-h Cos[a]) - x3 Tan[a] -(Cos[a] (m-h Cos[a]) Tan[b])/(2 (Tan[a]-Tan[b])))^2)] +Sqrt[((-(Cos[a] (m-h Cos[a]))/(2 (Tan[a]-Tan[b])) +(1/2 Cos[a] (m-h Cos[a]) + x3 Tan[a] - x3 (-m Sec[a]/(x2-x3) + Tan[a])-Tan[b])/(-m Sec[a]/(x2-x3) + Tan[a]))^2 + (-Tan[b] -(Cos[a] (m-h Cos[a]) Tan[b])/(2 (Tan[a]-Tan[b])))^2)])))]));
We extract the variable names:
$variables = Cases[$expr, _Symbol, Infinity] // Union
(* {a, b, h, m, x2, x3} *)
Now we compile the expression. The result is an object which can be destructured. We are interested in the component which represents the compiled Function
expression:
$function =
Cases[
Compile[##]&[$variables, $expr]
, x_Function :> x
] // First
(*
Function[{a,b,h,m,x2,x3},
Block[{Compile`$398,Compile`$406,[...snip...],Compile`$441},
Compile`$398=-3;
Compile`$406=Cos[a];
Compile`$403=Tan[a];
Compile`$399=x2+Compile`$398;
[...snip...]
Compile`$441=Compile`$423+Compile`$433+Compile`$440;
Sqrt[Compile`$441 (-Compile`$423+Compile`$441/2)]/Sqrt[2]]]
*)
In this result, we can see that the compiler has found all of the common subexpressions and assigned them to variables with generated names.
If the output is intended for machine consumption, we are done. All of the symbols have been safely localized. But as humans, we might find all of the generated symbol names distracting. We can apply a few more transformations in the interest of readability.
We will change all of the variables of the form Compile`$nnn
to p[n]
, where p
is carefully chosen as a symbol that does not appear in the original expression:
FreeQ[$expr, p]
(* True *)
The following transformations do the trick:
$cse =
Cases[$function, s_Symbol /; "Compile`" === Context[s], Infinity] //
DeleteDuplicates //
MapIndexed[#1 -> p@@#2 &, #]& //
$function /.
# /.
HoldPattern[Function[_, Block[_, b_]]] :> Hold@\[FormalM][{p}, b] /.
\[FormalM] -> Module &
(*
Hold[Module[{p},
p[1] = -x3;p[2] = Cos[a]; p[3] = Tan[a];
p[4] = x2 + p[1]; p[5] = 1/p[4]; p[6] = Sec[a]; p[7] = -m p[5] p[6];
[...snip...]
p[44] = p[30] + p[36] + p[43];
Sqrt[p[44] (-p[30] + p[44]/2)]/Sqrt[2]]]
*)
The final expression expands to the original expression when evaluated:
$expr === ReleaseHold[$cse]
(* True *)
If desired, another transformation will give us a base expression and replacement rules:
$rep =
$cse /. Hold[Module[_, CompoundExpression[s___, f_]]] :> Hold[f /. {s}] /. Set -> Rule
(*
Hold[Sqrt[p[44](-p[30]+p[44]/2)]/Sqrt[2] /. {p[1]->-x3, p[2]->Cos[a], [...snip...]}]
*)
Inspection of this result reveals that the compiler sometimes introduces temporary variables even for expressions that are only used once. Such variables appear exactly twice in the expression -- once to define it and once for its use:
$unnecessary = Cases[Cases[$rep, p[_], Infinity] // Tally, {_, 2}][[All, 1]]
(*
{p[4],p[5],p[6],p[7],p[9],p[15],p[19],p[20],p[21],p[24],p[25],p[26],p[27],p[28],
p[29],p[31],p[32],p[33],p[34],p[35],p[36],p[37],p[38],p[39],p[40],p[41],p[42],p[43]}
*)
We can further simplify the compiler's expression by removing these unnecessary variables:
$cse2 =
Verbatim[Rule][Alternatives @@ $unnecessary, _] //
DeleteCases[$rep, #, Infinity] //. Cases[$rep, #, Infinity] &
Once again, this expression is equivalent to the original when evaluated:
ReleaseHold[$cse2] === $expr
(* True *)
Just For Fun
Sometimes a layered graph plot can come in handy when trying to visualize the structure of complex expressions:
edge[x:_[___]] := (Scan[If[!AtomQ[#], Sow[x -> #]]& , x]; x)
edge[x_] := x
Reap[edge //@ $expr][[2, 1]] // DeleteDuplicates //
LayeredGraphPlot[#, Top, DirectedEdges -> False]&
The expression at each node is revealed as a tooltip when the mouse hovers over a vertex.
subexpression == expression
the count comes down to 1. So perhaps other restrictions are in place ... and ought to be specified $\endgroup$