Repository: sagemath/sage

Issue:
Elliptic curve addition performance has regressed
### Steps To Reproduce

#### Prime fields

Small characteristic 
```py
┌────────────────────────────────────────────────────────────────────┐
│ SageMath version 10.3.rc3, Release Date: 2024-03-10                │
│ Using Python 3.11.4. Type "help()" for help.                       │
└────────────────────────────────────────────────────────────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Warning: this is a prerelease version, and it may be unstable.     ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
sage: p = 11
....: E = EllipticCurve(GF(p), [1,1])
....: set_random_seed(0)
....: P = E.random_point()
....: Q = E.random_point()
....: %timeit P + P
....: %timeit P + Q
18.8 µs ± 1.14 µs per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
16.7 µs ± 365 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
```

```py
┌────────────────────────────────────────────────────────────────────┐
│ SageMath version 10.2, Release Date: 2023-12-03                    │
│ Using Python 3.11.4. Type "help()" for help.                       │
└────────────────────────────────────────────────────────────────────┘
sage: p = 11
....: E = EllipticCurve(GF(p), [1,1])
....: set_random_seed(0)
....: P = E.random_point()
....: Q = E.random_point()
....: %timeit P + P
....: %timeit P + Q
11.1 µs ± 41.4 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
9.75 µs ± 99.6 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
```

Large characteristic

```py
┌────────────────────────────────────────────────────────────────────┐
│ SageMath version 10.3.rc3, Release Date: 2024-03-10                │
│ Using Python 3.11.4. Type "help()" for help.                       │
└────────────────────────────────────────────────────────────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Warning: this is a prerelease version, and it may be unstable.     ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
sage: p = 2**127 - 1
....: E = EllipticCurve(GF(p), [1,1])
....: set_random_seed(0)
....: P = E.random_point()
....: Q = E.random_point()
....: %timeit P + P
....: %timeit P + Q
26.9 µs ± 637 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
23 µs ± 698 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)                                                                                                                                                                                                      
```

```py
┌────────────────────────────────────────────────────────────────────┐
│ SageMath version 10.2, Release Date: 2023-12-03                    │
│ Using Python 3.11.4. Type "help()" for help.                       │
└────────────────────────────────────────────────────────────────────┘
sage: p = 2**127 - 1
....: E = EllipticCurve(GF(p), [1,1])
....: set_random_seed(0)
....: P = E.random_point()
....: Q = E.random_point()
....: %timeit P + P
....: %timeit P + Q
17.2 µs ± 200 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
13.7 µs ± 145 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
```

#### Non prime fields

Small characteristic

```py
┌────────────────────────────────────────────────────────────────────┐
│ SageMath version 10.3.rc3, Release Date: 2024-03-10                │
│ Using Python 3.11.4. Type "help()" for help.                       │
└────────────────────────────────────────────────────────────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Warning: this is a prerelease version, and it may be unstable.     ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
sage: p = 11
....: E = EllipticCurve(GF(p^12), [1,1])
....: set_random_seed(0)
....: P = E.random_point()
....: Q = E.random_point()
....: %timeit P + P
....: %timeit P + Q
58.6 µs ± 3.85 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
41.1 µs ± 3.63 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
sage: 
sage: %timeit 2**100 * P
533 µs ± 4.82 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
```

```py                                                                                                                                                                                                        
┌────────────────────────────────────────────────────────────────────┐
│ SageMath version 10.2, Release Date: 2023-12-03                    │
│ Using Python 3.11.4. Type "help()" for help.                       │
└────────────────────────────────────────────────────────────────────┘
sage: p = 11
....: E = EllipticCurve(GF(p^12), [1,1])
....: set_random_seed(0)
....: P = E.random_point()
....: Q = E.random_point()
....: %timeit P + P
....: %timeit P + Q
36.4 µs ± 856 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
23.7 µs ± 111 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
sage: 
sage: %timeit 2**100 * P
496 µs ± 4.92 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
```

Large characteristic

```py
┌────────────────────────────────────────────────────────────────────┐
│ SageMath version 10.3.rc3, Release Date: 2024-03-10                │
│ Using Python 3.11.4. Type "help()" for help.                       │
└────────────────────────────────────────────────────────────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Warning: this is a prerelease version, and it may be unstable.     ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
sage: p = 2**127 - 1
....: E = EllipticCurve(GF(p^3), [1,1])
....: set_random_seed(0)
....: P = E.random_point()
....: Q = E.random_point()
....: %timeit P + P
....: %timeit P + Q
67.6 µs ± 704 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
53.9 µs ± 1.06 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
sage: 
sage: %timeit 2**100 * P
1.45 ms ± 23.8 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)                                                                                                                                                                                                      

```py
┌────────────────────────────────────────────────────────────────────┐
│ SageMath version 10.2, Release Date: 2023-12-03                    │
│ Using Python 3.11.4. Type "help()" for help.                       │
└────────────────────────────────────────────────────────────────────┘
sage: p = 2**127 - 1
....: E = EllipticCurve(GF(p^3), [1,1])
....: set_random_seed(0)
....: P = E.random_point()
....: Q = E.random_point()
....: %timeit P + P
....: %timeit P + Q
52 µs ± 1.08 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
37.8 µs ± 834 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
sage: 
sage: %timeit 2**100 * P
1.44 ms ± 25.3 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
```

### Expected Behavior

For performance to improve or stay the same

### Actual Behavior

Performance has got worse?

### Additional Information

_No response_

### Environment

```markdown
- **Sage Version**: SageMath version 10.3.rc3, Release Date: 2024-03-10
```


### Checklist

- [X] I have searched the existing issues for a bug report that matches the one I want to file, without success.
- [X] I have read the documentation and troubleshoot guide


Discussion:

Resolving patch:
diff --git a/src/sage/dynamics/arithmetic_dynamics/wehlerK3.py b/src/sage/dynamics/arithmetic_dynamics/wehlerK3.py
--- a/src/sage/dynamics/arithmetic_dynamics/wehlerK3.py
+++ b/src/sage/dynamics/arithmetic_dynamics/wehlerK3.py
@@ -1668,7 +1668,7 @@ def lambda_plus(self, P, v, N, m, n, prec=100):
             l = L.index(max(L))
             newP = copy(PK)
             #normalize PK
-            newP.scale_by([1/PK[0][i],1])
+            newP.scale_by([~PK[0][i], ZZ.one()])
 
             #Find B and A, helper values for the local height
             if PK[1][j].abs() <= PK[1][l].abs():
@@ -1679,7 +1679,7 @@ def lambda_plus(self, P, v, N, m, n, prec=100):
 
             #Normalize Q
             newQ = copy(Q)
-            newQ.scale_by([1,1/Q[1][l]])
+            newQ.scale_by([ZZ.one(), ~Q[1][l]])
 
             if PK[0][i].abs() <= PK[0][k].abs():
                 A = Ry(W.Gpoly(0, k))(tuple(newQ[1]))*PK[0][i]/PK[0][k]
@@ -1691,7 +1691,7 @@ def lambda_plus(self, P, v, N, m, n, prec=100):
 
             i = k
             j = l
-            newQ.scale_by([1/Q[0][k], 1])
+            newQ.scale_by([~Q[0][k], ZZ.one()])
             PK = newQ
         return local_height
 
@@ -1763,7 +1763,7 @@ def lambda_minus(self, P, v, N, m, n, prec=100):
             k = L.index(max(L))
             #Normalize the point
             newP = copy(PK)
-            newP.scale_by([1, 1/PK[1][i]])
+            newP.scale_by([ZZ.one(), ~PK[1][i]])
             #Find A and B, helper functions for computing local height
             if PK[0][j].abs() <= PK[0][l].abs():
                 B = Ry(W.Gpoly(0, l))(tuple(newP[1]))*PK[0][j]/PK[0][l]
@@ -1773,7 +1773,7 @@ def lambda_minus(self, P, v, N, m, n, prec=100):
 
             #Normalize Q
             newQ = copy(Q)
-            newQ.scale_by([1/Q[0][l], 1])
+            newQ.scale_by([~Q[0][l], ZZ.one()])
 
             if PK[1][i].abs() <= PK[1][k].abs():
                 A = Rx(W.Gpoly(1, k))(tuple(newQ[0]))*PK[1][i]/PK[1][k]
@@ -1785,7 +1785,7 @@ def lambda_minus(self, P, v, N, m, n, prec=100):
             local_height += beta**(-2*R(e)-1)*R(A.abs()).log() + beta**(-2*R(e))*R(B.abs()).log()
             i = k
             j = l
-            newQ.scale_by([1, 1/Q[1][k]])
+            newQ.scale_by([ZZ.one(), ~Q[1][k]])
             PK = newQ
         return local_height
 
diff --git a/src/sage/schemes/elliptic_curves/ell_point.py b/src/sage/schemes/elliptic_curves/ell_point.py
--- a/src/sage/schemes/elliptic_curves/ell_point.py
+++ b/src/sage/schemes/elliptic_curves/ell_point.py
@@ -794,7 +794,7 @@ def _add_(self, right):
         x3 = -x1 - x2 - a2 + m*(m+a1)
         y3 = -y1 - a3 - a1*x3 + m*(x1-x3)
         # See trac #4820 for why we need to coerce 1 into the base ring here:
-        return E.point([x3, y3, E.base_ring()(1)], check=False)
+        return E.point([x3, y3, E.base_ring().one()], check=False)
 
     def _sub_(self, right):
         """
@@ -839,7 +839,7 @@ def __neg__(self):
             return self
         E, x, y = self.curve(), self[0], self[1]
         # See trac #4820 for why we need to coerce 1 into the base ring here:
-        return E.point([x, -y - E.a1()*x - E.a3(), E.base_ring()(1)], check=False)
+        return E.point([x, -y - E.a1()*x - E.a3(), E.base_ring().one()], check=False)
 
     def xy(self):
         """
diff --git a/src/sage/schemes/projective/projective_point.py b/src/sage/schemes/projective/projective_point.py
--- a/src/sage/schemes/projective/projective_point.py
+++ b/src/sage/schemes/projective/projective_point.py
@@ -35,6 +35,7 @@
 from sage.categories.integral_domains import IntegralDomains
 from sage.categories.number_fields import NumberFields
 _NumberFields = NumberFields()
+from sage.rings.integer_ring import ZZ
 from sage.rings.fraction_field import FractionField
 from sage.rings.number_field.order import is_NumberFieldOrder, Order as NumberFieldOrder
 from sage.rings.qqbar import number_field_elements_from_algebraics
@@ -183,7 +184,7 @@ def __init__(self, X, v, check=True):
             R = X.value_ring()
             v = Sequence(v, R)
             if len(v) == d-1:     # very common special case
-                v.append(R(1))
+                v.append(R.one())
 
             if R in IntegralDomains():
                 # Over integral domains, any tuple with at least one
@@ -524,7 +525,7 @@ def scale_by(self, t):
             sage: Q.scale_by(1/2);Q
             (1 : 1 : 1)
         """
-        if t == 0:  #what if R(t) == 0 ?
+        if t.is_zero():  #what if R(t) == 0 ?
             raise ValueError("Cannot scale by 0")
         R = self.codomain().base_ring()
         if isinstance(R, QuotientRing_generic):
@@ -607,7 +608,7 @@ def normalize_coordinates(self):
             return
         R = self.codomain().base_ring()
         if isinstance(R, QuotientRing_generic):
-            index = self.codomain().ambient_space().dimension_relative()
+            index = len(self._coords) - 1
             while not self._coords[index]:
                 index -= 1
             last = self._coords[index].lift()
@@ -620,14 +621,14 @@ def normalize_coordinates(self):
             GCD = R(gcd(self._coords[0], self._coords[1]))
             index = 2
             neg = self._coords[0] <= 0 and self._coords[1] <= 0
-            while GCD != 1 and index < len(self._coords):
+            while not GCD.is_one() and index < len(self._coords):
                 neg = self._coords[index] <= 0
                 GCD = R(gcd(GCD, self._coords[index]))
                 index += 1
-            if GCD != 1:
+            if not GCD.is_one():
                 self.scale_by(~GCD)
             if neg:
-                self.scale_by(-1)
+                self.scale_by(-ZZ.one())
         self._normalized = True
 
     def dehomogenize(self,n):
@@ -676,7 +677,7 @@ def dehomogenize(self,n):
             ...
             ValueError: can...t dehomogenize at 0 coordinate
         """
-        if self[n] == 0:
+        if self[n].is_zero():
             raise ValueError("can't dehomogenize at 0 coordinate")
         PS = self.codomain()
         A = PS.affine_patch(n)
@@ -1159,25 +1160,21 @@ def __init__(self, X, v, check=True):
             R = X.value_ring()
             v = Sequence(v, R)
             if len(v) == d-1:     # very common special case
-                v.append(R(1))
-
-            n = len(v)
-            all_zero = True
-            for i in range(n):
-                last = n-1-i
-                if v[last]:
-                    all_zero = False
-                    c = v[last]
-                    if c == R.one():
-                        break
+                v.append(R.one())
+
+            for last in reversed(range(len(v))):
+                c = v[last]
+                if c.is_one():
+                    break
+                if c:
                     for j in range(last):
                         v[j] /= c
                     v[last] = R.one()
-                    self._normalized = True
                     break
-            if all_zero:
+            else:
                 raise ValueError(f"{v} does not define a valid projective "
                                  "point since all entries are zero")
+            self._normalized = True
 
             X.extended_codomain()._check_satisfies_equations(v)
 
@@ -1224,16 +1221,19 @@ def normalize_coordinates(self):
         """
         if self._normalized:
             return
-        index = self.codomain().ambient_space().dimension_relative()
-        while not self._coords[index]:
-            index -= 1
-        inv = self._coords[index].inverse()
-        new_coords = []
-        for i in range(index):
-            new_coords.append(self._coords[i] * inv)
-        new_coords.append(self.base_ring().one())
-        new_coords.extend(self._coords[index+1:])
-        self._coords = tuple(new_coords)
+        for index in reversed(range(len(self._coords))):
+            c = self._coords[index]
+            if c.is_one():
+                break
+            if c:
+                inv = c.inverse()
+                new_coords = [d * inv for d in self._coords[:index]]
+                new_coords.append(self.base_ring().one())
+                new_coords.extend(self._coords[index+1:])
+                self._coords = tuple(new_coords)
+                break
+        else:
+            assert False, 'bug: invalid projective point'
         self._normalized = True
 
     def _number_field_from_algebraics(self):


Test patch: