# "Smart" substitution of subexpressions

I have the following question.

An expression, which I want to simplify contains several subexpressions which appear quite frequently all over the place. To optimize simplification I would like to use abbreviations for some of them. Is there any way to do it in a "smart" way, i.e. to account for subexpressions which differ only by sign/multiplication by a number or a variable? Here is an example to illustrate what I mean.

For example, the adverted subexpression is:

-a^2 + b^2/(c^2 - d^2)


and I want to use variable A1 everywhere instead it:

-a^2 + b^2/(c^2 - d^2) -> A1


Now, I want Mathematica to substitute the expressions which are essentially equal to this one, but are simply written in another form like:

-a^2 - b^2/(d^2 - c^2)
-a^2 + (-b^2/(d^2 - c^2))


Also it would be great to use this rule for expressions like

-2*a^2 + 2*b^2/(c^2 - d^2) (*2*A1*)


or

a^2 - b^2/(c^2 - d^2) (*-A1*)


or even

-x*a^2 + x*b^2/(c^2 - d^2) (*x*A1*)


Is there a way to do it?

## 2 Answers

Outline

• Manual selective substitution

• Automatic detection and substitution of subexpressions

## Manual selective substitution

TL;DR : Simplify[-2*a^2 + 2*b^2/(c^2 - d^2), A1 == -a^2 + b^2/(c^2 - d^2)]

Maybe see also the links at the end of this section

In this scenario, the user has identified subexpressions that they would like to substitute with variables.

Advantage:

• Fine-grained control

Disadvantage:

• Can be difficult to find the right substitutions in a complicated expression

• One might have to do multiple (recursive) substitutions manually which can be tedious

In the example given in the comment below the answer by andre314,

 b/(-a^2 + b^2/(c^2 - d^2))


can be replaced by

 b/A1


using a replacement rule directly:

b/(-a^2 + b^2/(c^2 - d^2)) /. -a^2 + b^2/(c^2 - d^2) -> A1


However, this will not work when extra work is needed before making the substitution.

For example in :

-2*a^2 + 2*b^2/(c^2 - d^2)


one has to first tell Mathematica to factor out the 2 which is difficult as FactorTerms factors a -2 and the -1 is distributed across terms in the subexpression which then makes the substitution not obvious for Mathematica.

A quick fix here is to use Simplify with a side relation as shown here:

Simplify[-2*a^2 + 2*b^2/(c^2 - d^2), A1 == -a^2 + b^2/(c^2 - d^2)]


(* 2 A1 *)

Other methods are shown here:

## Automatic detection and substitution of subexpressions

These are functions that automatically detect subexpressions and define variables for them.

Advantage:

• Simplifies a complicated expression without having to identify sub-expressions

Disadvantage:

• Less control over which subexpressions are replaced by variables

some related functions:

test:

$$\frac{-2 a^2+\frac{2 b^2}{c^2-d^2}+4}{-2 a^2+\frac{2 b^2}{c^2-d^2}+5}$$

test=(4 + (-2*a^2 +
2*b^2/(c^2 - d^2)))/(5 + (-2*a^2 + 2*b^2/(c^2 - d^2)))


ExperimentalOptimizeExpression[test]


out:

ExperimentalOptimizedExpression[
Block[{Compile$$12, Compile$$13, Compile$14, Compile$$15, Compile$$16, Compile$17, Compile$$18, Compile$$19, Compile$30}, Compile$$12 = a^2; Compile$$13 = -2 Compile$12; Compile$$14 = b^2; Compile$$15 = c^2; Compile$16 = d^2; Compile$$17 = -Compile$$16; Compile$18 = Compile$$15 + Compile$$17;
Compile$19 = 1/Compile$$18; Compile$$30 = 2 Compile$14 Compile$$19; ( 4 + Compile$$13 + Compile$30)/(5 + Compile$$13 + Compile$$30)]]  ResourceFunction[ "SimplifyRepeatedSubexpressions"][test]  {(4 +$83 + $84)/( 5 +$83 + $84), {$83 -> (2 b^2)/(c^2 - d^2), \$84 -> -2 a^2}}

res = ResourceFunction[
"RecursiveRewrite"][test];
Nest[ReplaceAll[Last@res], First[res], 3]


(4 + "c1" "c10" + "c11" "c17" "c3")/("c14" + "c18" + "c5")

ResourceFunction["RecursiveRewrite"] tends to consider everything as subexpressions whereas ResourceFunction["SimplifyRepeatedSubexpressions"] seems to focus on repeated subexpressions and ExperimentalOptimizedExpression recursively rewrites those subexpressions.

As I understand, ExperimentalOptimizedExpression is used internally by Compile. If the output from ExperimentalOptimizedExpression is a bit hard to read, maybe you can use this answer to make it more readable. The answer there uses Compile directly instead but the manipulations are analogous in both cases.

we can also filter out the subexpressions we want. The code counts the number of times a subexpression occurs which can allow us to focus more on certain subexpressions. We can also filter by complexity (for example by LeafCount) and remove subexpressions of subexpressions using the code below (to be used with the code of the link mentioned before):

Note: •=\[Bullet]

•SubexpressionQ[a_, b_] := MemberQ[Cases[a, _, All], b]
•FilterOutSubexpressions[x_] :=
Select[x,
s |-> Not@
AnyTrue[x[[All, 1]] //
DeleteCases[s[[1]]], •SubexpressionQ[#, s[[1]]] &]]


The code from that link wrapped into a function:

    •SubExpressionCount[s_] :=
Module[{index,value,indices,values,items,indexQ,counts},
index[downvalue_, dict_] := (downvalue[[1]] /. HoldPattern[dict[x_]] -> x) // ReleaseHold;
value[downvalue_] := downvalue[[-1]];
indices[dict_] := Map[#[[1]] /. {HoldPattern[dict[x_]] -> x} &, DownValues[dict]] // ReleaseHold;
values[dict_] := Map[#[[-1]] &, DownValues[dict]];
items[dict_] := Map[{index[#, dict], value[#]} &, DownValues[dict]];
indexQ[dict_, index_] := If[MatchQ[dict[index], HoldPattern[dict[index]]], False, True];
Map[(counts[#]=If[indexQ[counts,#],counts[#]+1,1];#)&,s,Infinity];
items[counts]
]


Note: It is unclear to me whether the output of •SubExpressionCount can be different to Tally@Cases[#, _, All] & after ordering .

usage:

•SubExpressionCount[expr] //
Select[#[[2]] > 1 && LeafCount[#] > 10 &] //
ReverseSortBy[Last] // Column


$$\begin{array}{l} \left\{c^2-d^2,2\right\} \\ \left\{\frac{2 b^2}{c^2-d^2},2\right\} \\ \left\{\frac{1}{c^2-d^2},2\right\} \\ \end{array}$$

If we filter out subexpressions that are subexpressions of subexpressions in the list then we obtain:

•SubExpressionCount[expr] //
Select[#[[2]] > 1 &&
LeafCount[#] > 10 &] // •FilterOutSubexpressions //
ReverseSortBy[Last]


{{(2 b^2)/(c^2 - d^2), 2}}

Short version :

When one wants to do f[a+b] /. a+b->c, it is often more efficient to write f[a+b] /. a-> c-b and simplify the result ( with Simplify, Expand...).

Long version :

You can apply the rule b -> -Sqrt[a^2 + A1] Sqrt[c^2 - d^2] (equivalent to -a^2 + b^2/(c^2 - d^2) -> A1) and afterward try to simplify.

In fact, your example is a little bit more complex because there are to possible rules b-> Sqrt[...] and b-> -Sqrt[...], but it works fine :

rule = Solve[-a^2 + b^2/(c^2 - d^2) == A1, b]

transfomation[x_] := x /. rule // ExpandAll // Together

-a^2 - b^2/(d^2 - c^2) // transfomation
-a^2 + (-b^2/(d^2 - c^2)) // transfomation
-2*a^2 + 2*b^2/(c^2 - d^2) (*2*A1*) // transfomation
a^2 - b^2/(c^2 - d^2) (*-A1*)// transfomation
-x*a^2 + x*b^2/(c^2 - d^2) (*x*A1*)  // transfomation


{{b -> -Sqrt[a^2 + A1] Sqrt[c^2 - d^2]}, {b -> Sqrt[a^2 + A1] Sqrt[c^2 - d^2]}}

{A1, A1}

{A1, A1}

{2 A1, 2 A1}

{-A1, -A1}

{A1 x, A1 x}

• Thanks a lot for your answer, but I'm afraid it is not precisely what I needed. Yes, it works in these cases but I also would like it to leave b variable as it is when it does not appear in the combination: -a^2 + b^2/(c^2 - d^2) For example: b/(-a^2 + b^2/(c^2 - d^2)) = b/A1. Secondly, this subexpression was chosen just as an illustration. I think, this method might not work when it is hard (if possible) to express one of the variables through the others. Jan 5, 2019 at 21:54
• I understand, but I have nothing better to propose (These kinds of apparently trivial algebric manipulations are often very frustating). Jan 5, 2019 at 22:00
• I you want, you can add further more complicated examples in your question. Generally speaking, it is not recommended to change the question, but as I'm the only one who has given a answer I can delete it. (I don't mind the +50 of reputation) Jan 5, 2019 at 22:34