From 011b9431f78ca190257f9e77bd0ee0968b0ee5c9 Mon Sep 17 00:00:00 2001
From: "Vladimir V. Kisil" <V.Kisilv@leeds.ac.uk>
Date: Tue, 30 Mar 2021 13:34:11 +0100
Subject: [PATCH] Improve normalisation of negative exponents.

If an expression contains exponents with opposite signs,
then the respective symbolic substitutions need to be properly
attributed to numerator/denominator slots.

Signed-off-by: Vladimir V. Kisil <V.Kisilv@leeds.ac.uk>
---
 check/exam_normalization.cpp | 20 ++++++++++++++++++++
 ginac/normal.cpp             | 10 +++++++---
 2 files changed, 27 insertions(+), 3 deletions(-)

diff --git a/check/exam_normalization.cpp b/check/exam_normalization.cpp
index 7ed7464d..ea2a89f4 100644
--- a/check/exam_normalization.cpp
+++ b/check/exam_normalization.cpp
@@ -252,6 +252,20 @@ static unsigned exam_exponent_law()
 		result += check_normal(e, den);
 	}
 
+	// Negative exponents
+	e = (exp(2*x)-exp(-2*x))/(exp(x)-exp(-x));
+	ex en = e.normal();
+	// Either exp(x) or exp(-x) can be viewed as a "symbol" during run-time
+	// thus two different forms of the result are possible
+	ex r1 = (exp(2*x)+1)/exp(x) ;
+	ex r2 = (exp(-2*x)+1)/exp(-x);
+
+	if (!en.is_equal(r1) and !en.is_equal(r2)) {
+		clog << "normal form of " << e << " erroneously returned "
+		     << en << " (should be " << r1 << " or " << r2 << ")" << endl;
+		result += 1;
+	}
+
 	return result;
 }
 
@@ -305,6 +319,12 @@ static unsigned exam_power_law()
 		e /= 2*pow(b, y/2)-3*pow(b, z/2);
 		d = 2*pow(b, y/2)+3*pow(b, z/2);
 		result += check_normal(e, d);
+
+		// Negative powers
+		e = (b -pow(b,-1));
+		e /= (pow(b, numeric(1,2)) - pow(b, numeric(-1,2)));
+		d = (b+1)*pow(b, numeric(-1,2));
+		result += check_normal(e, d);
 	}
 
 	return result;
diff --git a/ginac/normal.cpp b/ginac/normal.cpp
index ce606f08..818aa808 100644
--- a/ginac/normal.cpp
+++ b/ginac/normal.cpp
@@ -2196,14 +2196,18 @@ ex basic::normal(exmap & repl, exmap & rev_lookup, lst & modifier) const
 
 	normal_map_function map_normal;
 	int nmod = modifier.nops(); // To watch new modifiers to the replacement list
-	lst result = dynallocate<lst>({replace_with_symbol(map(map_normal), repl, rev_lookup, modifier), _ex1});
+	ex result = replace_with_symbol(map(map_normal), repl, rev_lookup, modifier);
 	for (int imod = nmod; imod < modifier.nops(); ++imod) {
 		exmap this_repl;
 		this_repl.insert(std::make_pair(modifier.op(imod).op(0), modifier.op(imod).op(1)));
-		result = ex_to<lst>(result.subs(this_repl, subs_options::no_pattern));
+		result = result.subs(this_repl, subs_options::no_pattern);
 	}
 
-	return result;
+	// Sometimes we may obtain negative powers, they need to be placed to denominator
+	if (is_a<power>(result) && result.op(1).info(info_flags::negative))
+		return dynallocate<lst>({_ex1, power(result.op(0), -result.op(1))});
+	else
+		return dynallocate<lst>({result, _ex1});
 }
 
 
-- 
2.30.2

