8
$\begingroup$

I am not able to figure why this replacement using side-relation works in one case but not the other in the following

ClearAll[x, y, z, A]
Simplify[(x^2 + y^2 + z^2), {x^2 + y^2 == A}]
Simplify[(x + y + z), {x + y == A}]

Any idea why it works in one case but not the other?

Screen shot

Mathematica graphics

$\endgroup$
4
  • 1
    $\begingroup$ It happens the same in version 11.3 $\endgroup$
    – mattiav27
    Dec 21, 2022 at 11:14
  • $\begingroup$ Is this the problem of lexical order? If you do Simplify[(a + b + c), {a + b == A}] you get what you want. See also this post $\endgroup$
    – Hugh
    Dec 21, 2022 at 13:00
  • 1
    $\begingroup$ I honestly don't understand the downvote. In principle, I don't understand any downvote without any explanation, except for cases where it is obvious that there is no effort, or the question is not well posed. Just leaving this comment here. Oh yes, and I upvoted just to fix the situation before reading the question $\endgroup$
    – bmf
    Dec 21, 2022 at 13:13
  • 2
    $\begingroup$ Thanks to all answers. All upvoted. I choose bmf's because it had the shortest trick to get it work which I was able to understand. But all answers are great and wish I can accept them all. $\endgroup$
    – Nasser
    Dec 21, 2022 at 20:32

3 Answers 3

5
$\begingroup$

A possible workaround

Simplify[x + y + z, {x + y == Unevaluated[A]}]

res

Motivated by the answer @Hugh gave I checked the LeafCount, because I thought that it made a difference:

LeafCount /@ {x, y, z, A}
LeafCount /@ {x^2 + y^2 + z^2, x^2 + y^2 == A}
LeafCount /@ {x + y + z, x + y == A}

res2

So, it seems -at least naively- that in the case that Simplify worked properly the expression that is to simplified has greater LeafCount than the assumption.

But, in the workaround which worked that is not the case so this situation seems a bit weird to me.

$\endgroup$
1
  • $\begingroup$ The LeafCounts in Simplify[x*y*z, {x*y == A}] are the same as in Simplify[x + y + z, {x + y == A}] and yet the first one works and not the second. It's the option MonomialOrder -> DegreeReverseLexicographic in PolynomialReduce that Simplify calls internally that leads to different behavior depending on the degrees of the monomials. I wrote in my answer a section about why I think your answer works. $\endgroup$ Dec 23, 2022 at 9:51
8
$\begingroup$

TL;DR

Simplify uses PolynomialReduce[poly,polyList,variables] whose output can be sensitive to the order of the elements in variables. The order of variables is determined by the automatic lexicographic sorting of terms with head Plus after applying Subtract to the lhs==rhs in the second argument of Simplify. Hence, automatic lexicographic sorting of an expression and order sensitivity of PolynomialReduce leads Simplify to be sensitive to the names of variables.

In the case of Simplify[(x^2 + y^2 + z^2), {x^2 + y^2 == A}] the simplification still occurs presumably because PolynomialReduce uses the option MonomialOrder -> DegreeReverseLexicographic and the degree of A is 1 and so smaller than any other monomial in the expressions above according to DegreeReverseLexicographic (see https://en.wikipedia.org/wiki/Lexicographic_order#Monomials). The name for A is then irrelevant due to the lower degree of the monomial and the computation seems to focus on the higher degree monomials like x^2 and y^2.


What went wrong in my previous answer (can be skipped)

Seems like my previous "not an answer" really was not an answer at all. The major issues in my previous answer was that UniqueElements seemed to not really pick up well on unique elements in a ragged list and the results were not the same the first time and the second time due to Mathematica saving information in the system cache. So I gave UniqueElements a better chance by flattening the lists and used ClearSystemCache[];.


What happened with Simplify[(x + y + z), {x + y == A}] ?

We can now compare:

Trace[Simplify[x+y+z,x+y==A],TraceInternal->True]//Flatten

with

Trace[Simplify[a+b+z,a+b==A],TraceInternal->True]/.{a->x,b->y}//Flatten

using

either

Note: //Flatten[#,{{2},{1}}]& is used for transposition of ragged lists see Flatten command: matrix as second argument

ClearSystemCache[];
(
UniqueElements[
{
Trace[Simplify[a+b+z,a+b==A],TraceInternal->True]/.{a->x,b->y}//Flatten
,
Trace[Simplify[x+y+z,x+y==A],TraceInternal->True]//Flatten
}
]
//Map[Map[Column[{#,ReleaseHold@#},Dividers->Center]&]]
//Flatten[#,{{2},{1}}]&
//Part[#,1;;19]&
//Grid[#,Frame->All]&
)

(I explain the important parts below if you want to skip looking at the table)

Below:

Comparison of the differences between the traces of Simplify[x+y+z,x+y==A] (on the right) and Simplify[a+b+z,a+b==A] (on the left) after substituting a with x and b with y in the trace. Each line corresponds to a step Mathematica takes in the computation. Each line contains a horizontal delimiter with code in HoldForm and its evaluation at the bottom.

enter image description here

or if you rather see the original a and b on the left

ClearSystemCache[];
(
UniqueElements[
{
Trace[Simplify[a+b+z,a+b==A],TraceInternal->True]/.{a->x,b->y}//Flatten
,
Trace[Simplify[x+y+z,x+y==A],TraceInternal->True]//Flatten
}
]
//MapAt[ReplaceAll[{x->a,y->b}],#,1]&
//Map[Map[Column[{#,ReleaseHold@#},Dividers->Center]&]]
//Flatten[#,{{2},{1}}]&
//Part[#,1;;19]&
//Grid[#,Frame->All]&
)

enter image description here

The important points above are :

  • the difference in ordering of the additions and substraction due to how Mathematica sorts variables in a sum of terms according to Sort[variables] (recall that before the substitution {a->x,b->y} in the trace of evaluations, a,b,z,A was used in one case and x,y,z,A in the other as shown explicitly in the steps below)

  • the usage of PolynomialReduce. We will not get into the details of this function.

We can simulate the steps of Simplify below:

  • Step 1 : turn the second argument of Simplify lhs==rhs to lhs-rhs for polynomial reduction in step 2

so

Subtract @@ (a + b == A )
(* a - A + b *)

Subtract @@ (x + y == A)
(* -A + x + y *)

Step 2 : polynomial substitution

The documentation for PolynomialReduce shows that PolynomialReduce can be used to

Replace variables in a polynomial using equations relating old and new variables

This is what Simplify uses. The documentation for PolynomialReduce and Simplify use GroebenerBasis as an intermediate but this does not seem important for the explanation of why the substitution does not occur in the example given by OP.

  • Sub-step 1: get the variables for the substitution

The variables are obtained using:

Variables[a-A+b]
(* {a,A,b} *)

Variables[-A+x+y]
(* {A,x,y} *)

Sub-step 2: substitute polynomials using PolynomialReduce

The syntax below is:

PolynomialReduce[expression to simplify, {left hand side of step 1 (the constraint)}, variables from sub-step 1,MonomialOrder->DegreeReverseLexicographic]

compare :

PolynomialReduce[a+b+z,{a-A+b},{a,A,b},MonomialOrder->DegreeReverseLexicographic]
(* {{1},A+z} *)

with :

PolynomialReduce[x+y+z,{A-x-y},{A,x,y},MonomialOrder->DegreeReverseLexicographic]
(* {{0},x+y+z} *)

Why did PolynomialReduce fail ?

Is it the A-x-y instead of x-A+y ?

PolynomialReduce[x+y+z,{x-A+y},{A,x,y},MonomialOrder->DegreeReverseLexicographic]
(* {{0},x+y+z} *)

No, it's actually just the order of the variables {A,x,y} found using Variables[-A+x+y] previously.

With the automatic ordering of variables within an expression with head Plus, that is the same order of variables as Sort[{x,A,y}].

To show that the problem is the order of the variables, we can change the order of the variables to

{x,A,y}

PolynomialReduce[x+y+z,{A-x-y},{x,A,y},MonomialOrder->DegreeReverseLexicographic]
(* {{-1},A+z} *)

or

PolynomialReduce[x+y+z,{A-x-y},{x,y,A},MonomialOrder->DegreeReverseLexicographic]
(* {{-1},A+z} *)

It seems that when all monomials have the same degree, PolynomialReduce tries the polynomial replacement by substituting the first variable in the variable list. As A is not present in x+y+z nothing happens. To reinforce this possibility compare:

PolynomialReduce[x+y+2*A,{-A-x-y},{A,y,x},MonomialOrder->DegreeReverseLexicographic]
(* {{-2},-x-y} *)

PolynomialReduce[x+2*y+A,{-A-x-y},{y,A,x},MonomialOrder->DegreeReverseLexicographic]
(* {{-2},-A-x} *)

where we notice that the first variable is systematically substituted.


Why did Simplify work with Simplify[(x^2 + y^2 + z^2), {x^2 + y^2 == A}] ?

First notice that if we do not specify MonomialOrder, then we have the same problem as before where the result depends on the order of list of variables in the third argument:

PolynomialReduce[
  x^2 + y^2 + z^2, {-A + x^2 + y^2}, 
  { A,x, y}]
(* {{0},x^2+y^2+z^2} *)

PolynomialReduce[
  x^2 + y^2 + z^2, {-A + x^2 + y^2}, 
  { x,A, y}]
(* {{1},A+z^2} *)

This behavior seems to be overridden when one specifies MonomialOrder -> DegreeReverseLexicographic in which case A is smaller than any other monomial in the expressions above (see https://en.wikipedia.org/wiki/Lexicographic_order#Monomials). The name of A is then irrelevant but not necessarily the names of x and y. To see why consider:

PolynomialReduce[
  -x^2 + y^2 + z^2+A, {-A + x^2 + y^2}, 
  {A,x,y},MonomialOrder->DegreeReverseLexicographic]
(* {{-1},2 y^2+z^2} *)

PolynomialReduce[
  -x^2 + y^2 + z^2+A, {-A + x^2 + y^2}, 
  {x,A,y},MonomialOrder->DegreeReverseLexicographic]
(* {{-1},2 y^2+z^2} *)

PolynomialReduce[
   -x^2 + y^2 + z^2+A, {-A + x^2 + y^2}, 
  {x,y,A},MonomialOrder->DegreeReverseLexicographic]
(* {{-1},2 y^2+z^2} *)

PolynomialReduce[
  -x^2 + y^2 + z^2+A, {-A + x^2 + y^2}, 
  {y,x,A},MonomialOrder->DegreeReverseLexicographic]
(* {{1},2 A-2 x^2+z^2} *)

Why does Unevaluated work in the answer by @bmf ?

I am not sure about the details because of the usage of Unevaluated which could affect some of the internal computations but here is the trace comparison:

ClearSystemCache[];
(
UniqueElements[
{
Trace[Simplify[x+y+z,x+y==Unevaluated@A],TraceInternal->True]/.HoldPattern[Unevaluated@A]->A//Flatten
,
Trace[Simplify[x+y+z,x+y==A],TraceInternal->True]//Flatten
}
]
//MapAt[ToExpression@*StringReplace["A"->"Unevaluated@A"]@*ToString@*InputForm,#,1]&
//Map[Map[Column[{#,Evaluate@ReleaseHold@#},Dividers->Center]&]]
//Flatten[#,{{2},{1}}]&

//Part[#,1;;23]&
//Grid[#,Frame->All]&
)

enter image description here

It seems that Unevaluated works because :

Sort[{x,A,Unevaluated@A,Unevaluated@B,a,b,Infinity,_}]
(* {a,A,b,x,_,∞,Unevaluated[A],Unevaluated[B]} *)

So Unevaluated@A was the rightmost variable in the list of variables given to PolynomialReduce. There could be more subtleties I am not sure.


Previous "answer"

Not an answer just an extended comment/exploration:


TL;DR

TraceInternal shows some differences that might be linked to the presence of DegreeReverseLexicographic in the trace but the output from trace does not seem to explain why there is a difference. The problem seems deeper than what TraceInternal shows.


We can compare some of the differences of what Mathematica is doing depending on the choice of variables using TraceInternal and UniqueElements (Mathematica version 13.1).

There is very little difference and so I imagine the root of the problem is even deeper than what TraceInternal shows. This is shown by how the same change in symbols for the power case does not change the result.

As mentioned, the difference in the traces below might be related to lexicographic order which is hinted on at the end but it does not explain really where the problem is.

Consider first the linear case:

l1 = Trace[Simplify[(a + b + z), {a + b == A}], 
   TraceInternal -> True];

l2 = Trace[Simplify[(x + y + z), {x + y == A}], 
   TraceInternal -> True];

To compare the two lists we change a with x and b with y after obtaining l1:

l1Mod = l1 /. {a -> x, b -> y};

Now we can check what is unique to each trace:

UniqueElements[{l1Mod, l2}] // Map[Map[MatrixForm]] // 
  Map[Column] // Row

enter image description here

The reason for the partially different elements seems related to :

Select[TextWords[ToString@InputForm@l2], 
StringContainsQ["exicogra"]] // DeleteDuplicates

(* {"DegreeReverseLexicographic"} *)

In the power case using a and b instead of x and y the difference between the outputs is similar and yet somehow the results are the same which may contradict some conclusions from the linear case (the final outputs are not shown because they are the same):

enter image description here

I did not notice much of a different in the case of power expressions when compared to the linear case.

The following shows a list of the functions that were called and that are shown in TraceInternal:

Note: Below there is a change of notation where l1Mod=lab•linear•xy and lab•power•xy is the equivalent of lab•linear•xy for the power/square case.

• List of functions called in the trace for the power case (x^2+y^+z^2 and x^2+y^2=A):

e = Cases[lab•power•xy, h_[___] :> h, All] // 
  DeleteDuplicates

(* {Power, Plus, Equal, List, Simplify, HoldForm, N, Floor, Mod, Times, 
 Ceiling, And, MessageName, Off, Expand, PolynomialQ, Set, Blank, 
 Integrate`FakeIntervalElement, Sequence, RuleDelayed, ReplaceAll, 
 FactorList, Sign, Variables, Rule, GroebnerBasis, Flatten, 
 Solve`SolvVar, Together, On, $Off} *)

• List of functions called in the trace for the linear case (x+y+z and x+y=A)

j = Cases[lab•linear•xy, h_[___] :> h, All] // 
  DeleteDuplicates

(* {Plus, Equal, List, Simplify, HoldForm, N, Floor, Mod, Times, Ceiling, 
 And, MessageName, Off, Expand, PolynomialQ, Set, Blank, 
 Integrate`FakeIntervalElement, Sequence, RuleDelayed, ReplaceAll, 
 FactorList, Power, Sign, Variables, Rule, GroebnerBasis, Flatten, 
 Solve`SolvVar, Together, On, $Off} *)

Other than the order and maybe some duplicates (I did not compare duplicates) the functions called are the same :

UniqueElements[{e, j}]

(* {{}, {}} *)

$\endgroup$
6
  • $\begingroup$ Very good detective work -well done. I have not seen a more detailed account before. What are your conclusions regarding the workings of VOISimplify in my post? Does this work or are there still some problems in that approach? $\endgroup$
    – Hugh
    Dec 23, 2022 at 11:28
  • $\begingroup$ @Hugh Thank you I spent an unreasonable amount of time trying to understand what was going on haha. I had left a comment about VOISimplify below your answer as one of the duplicates in the link you gave shows an example where it did not work mathematica.stackexchange.com/q/83756/86543. However, I could not reproduce the problem with FullSimplify in Mathemtica 13.1 so I left it alone. That example uses !=. I am not sure how Simplify handles !=. A priori, I see no reason why VOISimplify would not work when the second argument of Simplify involves only ==. But trying ... $\endgroup$ Dec 23, 2022 at 12:10
  • $\begingroup$ ... all permutations sounds very costly. Knowing or testing PolynomialReduce and knowing Lexicographic order seems sufficient for avoiding the surprises I checked so far on small expressions. Not sure about big expressions. $\endgroup$ Dec 23, 2022 at 12:12
  • $\begingroup$ Basically knowing that the smallest variable in terms of lexicographic order might get substituted into the expression first unless it has a low degree. And that if that lowest variable is not immediately present then nothing happens. $\endgroup$ Dec 23, 2022 at 12:17
  • $\begingroup$ bmf's Unevaluated solution seems like a good way to make sure a desired variable appears in the expression as it will be the last variable PolynomialReduce tries but I am at unease with how Simplify might handle the Unevaluated in the general case. Using a variable like ZZ and then using /.ZZ->A for example feels safer. I think SolveValues[Eliminate[{g == x + y + z, x + y == A}, {x, y}], g][[1]] is more reliable then one can use Simplify after that with no second argument in a more complicated expression. $\endgroup$ Dec 23, 2022 at 12:32
5
$\begingroup$

Not a complete answer but may be part of the problem. Is this a problem of lexical order? See also this post.

If you do

Simplify[(a + b + c), {a + b == A}]

You get

(* A + c *)

If you use this code from Adam Strzebonski see previous post

  VOISimplify[vars_, expr_, assum_ : True] := 
     Module[{perm, ee, best}, perm = Permutations[vars];
      ee = (FullSimplify @@ ({expr, assum} /. Thread[vars -> #])) & /@ 
        perm;
      best = Sort[Transpose[{LeafCount /@ ee, ee, perm}]][[1]];
      best[[2]] /. Thread[best[[3]] -> vars]]

you get what you need

VOISimplify[{x, y, z, A}, (x + y + z), x + y == A]

output

 (*  A + z *)

I am just showing my age because I raised this on mathgroup in 2005. The code is rather too complex for me to work out what is happening but you may be able to sort this out. Hope that helps.

$\endgroup$
0

Your Answer

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

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