Repository: sympy/sympy

Issue:
`PrimeIdeal` generator should be reduced
We represent prime ideals by a pair of generators `(p, alpha)`, where `p` is a rational prime,
and `alpha` is an algebraic integer.

It's important that `alpha` be reduced relative to the principal ideal `(p)`, in order to get
an appropriate representation. Currently we are not doing this.

In particular, `alpha` should be exactly `0` if it is equivalent to `0` mod `(p)`.

For example, we currently have this:

```python
>>> from sympy import QQ, Poly, symbols
>>> t = symbols('t')
>>> k = QQ.alg_field_from_poly(Poly(t**3 + t**2 - 2*t + 8))
>>> P = k.primes_above(3)
>>> P[0].alpha.as_expr(t)
3*t - 9
```

In other words, we're representing this prime ideal as `(3, 3*t - 9)`, with a second generator that
contributes nothing. `3*t - 9` should be reduced mod 3, giving 0.

Discussion:

Patch:
diff --git a/.mailmap b/.mailmap
index 78bf552bd3..aa099638f1 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1126,7 +1126,6 @@ Sai Nikhil <tsnlegend@gmail.com>
 Saket Kumar Singh <saketkumar1202@gmail.com>
 Saketh <alurusaisaketh@gmail.com>
 Sakirul Alam <binarysakir@gmail.com>
-Saksham Alok <sakshamalok13@gmail.com> Saksham-13 <sakshamalok13@gmail.com>
 Salil Vishnu Kapur <salilvishnukapur@gmail.com>
 Salmista-94 <alejandrogroso@hotmail.com> alejandrogroso@hotmail.com <Salmista-94>
 Saloni Jain <tosalonijain@gmail.com>
@@ -1306,7 +1305,6 @@ V1krant <46847915+V1krant@users.noreply.github.com>
 Vaibhav Bhat <vaibhav.bhat2097@gmail.com> VBhat97 <vaibhav.bhat2097@gmail.com>
 Vaishnav Damani <vaishnavdamani3496@gmail.com>
 Valeriia Gladkova <valeriia.gladkova@gmail.com>
-Varenyam Bhardwaj <varenyambhardwaj123@gmail.com>
 Varun Garg <varun.garg03@gmail.com>
 Varun Joshi <joshi.142@osu.edu>
 Vasileios Kalos <kalosbasileios@gmail.com> Basileios Kalos <kalosbasileios@gmail.com>
diff --git a/LICENSE b/LICENSE
index 7ace01f27a..6d61068576 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2006-2022 SymPy Development Team
+Copyright (c) 2006-2021 SymPy Development Team
 
 All rights reserved.
 
@@ -33,7 +33,7 @@ Patches that were taken from the Diofant project (https://github.com/diofant/dio
 are licensed as:
 
 Copyright (c) 2006-2018 SymPy Development Team,
-              2013-2022 Sergey B Kirpichev
+              2013-2021 Sergey B Kirpichev
 
 All rights reserved.
 
@@ -101,7 +101,7 @@ DAMAGE.
 The files under the directory sympy/parsing/autolev/tests/pydy-example-repo
 are directly copied from PyDy project and are licensed as:
 
-Copyright (c) 2009-2022, PyDy Authors
+Copyright (c) 2009-2021, PyDy Authors
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
diff --git a/doc/src/modules/evalf.rst b/doc/src/modules/evalf.rst
index 68d6025109..42482116f5 100644
--- a/doc/src/modules/evalf.rst
+++ b/doc/src/modules/evalf.rst
@@ -343,7 +343,7 @@ digits in a fraction of a second with a simple command:
     >>> f = factorial
     >>> n = Symbol('n', integer=True)
     >>> R = 9801/sqrt(8)/Sum(f(4*n)*(1103+26390*n)/f(n)**4/396**(4*n),
-    ...                         (n, 0, oo))
+    ...                         (n, 0, oo)) #doctest: +SKIP
     >>> N(R, 10000) #doctest: +SKIP
     3.141592653589793238462643383279502884197169399375105820974944592307816406286208
     99862803482534211706798214808651328230664709384460955058223172535940812848111745
diff --git a/doc/src/tutorial/gotchas.rst b/doc/src/tutorial/gotchas.rst
index f3d497123e..df6b96d584 100644
--- a/doc/src/tutorial/gotchas.rst
+++ b/doc/src/tutorial/gotchas.rst
@@ -258,11 +258,10 @@ division of two Integers gives a Rational:
 
 But in Python ``/`` represents either integer division or floating point
 division, depending on whether you are in Python 2 or Python 3, and depending
-on whether or not you have run ``from __future__ import division`` in Python 2
-which is no longer supported from versions above SymPy 1.5.1:
+on whether or not you have run ``from __future__ import division``:
 
     >>> from __future__ import division
-    >>> 1/2
+    >>> 1/2 #doctest: +SKIP
     0.5
 
 To avoid this, we can construct the rational object explicitly
@@ -273,7 +272,7 @@ To avoid this, we can construct the rational object explicitly
 This problem also comes up whenever we have a larger symbolic expression with
 ``int/int`` in it.  For example:
 
-    >>> x + 1/2
+    >>> x + 1/2 #doctest: +SKIP
     x + 0.5
 
 This happens because Python first evaluates ``1/2`` into ``0.5``, and then
diff --git a/sympy/combinatorics/perm_groups.py b/sympy/combinatorics/perm_groups.py
index bf70f9a296..8f8b39de3b 100644
--- a/sympy/combinatorics/perm_groups.py
+++ b/sympy/combinatorics/perm_groups.py
@@ -3694,7 +3694,7 @@ def schreier_sims_random(self, base=None, gens=None, consec_succ=10,
         >>> from sympy.combinatorics.named_groups import SymmetricGroup
         >>> S = SymmetricGroup(5)
         >>> base, strong_gens = S.schreier_sims_random(consec_succ=5)
-        >>> _verify_bsgs(S, base, strong_gens)
+        >>> _verify_bsgs(S, base, strong_gens) #doctest: +SKIP
         True
 
         Notes
diff --git a/sympy/core/expr.py b/sympy/core/expr.py
index 8b836fa5cf..99426b90c8 100644
--- a/sympy/core/expr.py
+++ b/sympy/core/expr.py
@@ -18,7 +18,6 @@
 from mpmath.libmp import mpf_log, prec_to_dps
 from mpmath.libmp.libintmath import giant_steps
 
-
 if TYPE_CHECKING:
     from .numbers import Number
 
@@ -226,16 +225,16 @@ def __rmul__(self, other):
     def _pow(self, other):
         return Pow(self, other)
 
-    def __pow__(self, other, mod=None) -> Expr:
+    def __pow__(self, other, mod=None):
         if mod is None:
             return self._pow(other)
         try:
             _self, other, mod = as_int(self), as_int(other), as_int(mod)
             if other >= 0:
-                return _sympify(pow(_self, other, mod))
+                return pow(_self, other, mod)
             else:
                 from .numbers import mod_inverse
-                return _sympify(mod_inverse(pow(_self, -other, mod), mod))
+                return mod_inverse(pow(_self, -other, mod), mod)
         except ValueError:
             power = self._pow(other)
             try:
diff --git a/sympy/parsing/mathematica.py b/sympy/parsing/mathematica.py
index 5001fb5a0b..7a817b26cc 100644
--- a/sympy/parsing/mathematica.py
+++ b/sympy/parsing/mathematica.py
@@ -622,6 +622,16 @@ def _get_tokenizer(self):
     def _from_mathematica_to_tokens(self, code: str):
         tokenizer = self._get_tokenizer()
 
+        # Remove comments:
+        while True:
+            pos_comment_start = code.find("(*")
+            if pos_comment_start == -1:
+                break
+            pos_comment_end = code.find("*)")
+            if pos_comment_end == -1 or pos_comment_end < pos_comment_start:
+                raise SyntaxError("mismatch in comment (*  *) code")
+            code = code[:pos_comment_start] + code[pos_comment_end+2:]
+
         # Find strings:
         code_splits: List[typing.Union[str, list]] = []
         while True:
@@ -639,21 +649,6 @@ def _from_mathematica_to_tokens(self, code: str):
             code_splits.append(["_Str", code[string_start+1:string_end].replace('\\"', '"')])
             code = code[string_end+1:]
 
-        # Remove comments:
-        for i, code_split in enumerate(code_splits):
-            if isinstance(code_split, list):
-                continue
-            while True:
-                pos_comment_start = code_split.find("(*")
-                if pos_comment_start == -1:
-                    break
-                pos_comment_end = code_split.find("*)")
-                if pos_comment_end == -1 or pos_comment_end < pos_comment_start:
-                    raise SyntaxError("mismatch in comment (*  *) code")
-                code_split = code_split[:pos_comment_start] + code_split[pos_comment_end+2:]
-            code_splits[i] = code_split
-
-        # Tokenize the input strings with a regular expression:
         token_lists = [tokenizer.findall(i) if isinstance(i, str) else [i] for i in code_splits]
         tokens = [j for i in token_lists for j in i]
 
diff --git a/sympy/polys/numberfields/primes.py b/sympy/polys/numberfields/primes.py
index a2ab81e6f2..dc0b689d0f 100644
--- a/sympy/polys/numberfields/primes.py
+++ b/sympy/polys/numberfields/primes.py
@@ -70,10 +70,18 @@ def __init__(self, ZK, p, alpha, f, e=None):
         self.e = e if e is not None else self.valuation(p * ZK)
 
     def __str__(self):
-        if self.alpha.is_rational:
+        if self.is_inert:
             return f'({self.p})'
         return f'({self.p}, {self.alpha.as_expr()})'
 
+    @property
+    def is_inert(self):
+        """
+        Say whether the rational prime we divide is inert, i.e. stays prime in
+        our ring of integers.
+        """
+        return self.f == self.ZK.n
+
     def repr(self, field_gen=None, just_gens=False):
         """
         Print a representation of this prime ideal.
@@ -561,6 +569,8 @@ def _prime_decomp_easy_case(p, ZK):
     T = ZK.parent.T
     T_bar = Poly(T, modulus=p)
     lc, fl = T_bar.factor_list()
+    if len(fl) == 1 and fl[0][1] == 1:
+        return [PrimeIdeal(ZK, p, ZK.parent.zero(), ZK.n, 1)]
     return [PrimeIdeal(ZK, p,
                        ZK.parent.element_from_poly(Poly(t, domain=ZZ)),
                        t.degree(), e)
diff --git a/sympy/printing/latex.py b/sympy/printing/latex.py
index 381d21a0da..ff34fe46d7 100644
--- a/sympy/printing/latex.py
+++ b/sympy/printing/latex.py
@@ -632,7 +632,7 @@ def _print_AlgebraicNumber(self, expr):
 
     def _print_PrimeIdeal(self, expr):
         p = self._print(expr.p)
-        if expr.alpha.is_rational:
+        if expr.is_inert:
             return rf'\left({p}\right)'
         alpha = self._print(expr.alpha.as_expr())
         return rf'\left({p}, {alpha}\right)'
@@ -790,7 +790,7 @@ def _print_Idx(self, expr):
                 lower = self._print(expr.lower)
             else:
                 lower = self._print(S.Zero)
-            interval = '{lower}\\mathrel{{..}}\\nobreak {upper}'.format(
+            interval = '{lower}\\mathrel{{..}}\\nobreak{upper}'.format(
                     lower = lower, upper = upper)
             return '{{{label}}}_{{{interval}}}'.format(
                 label = label, interval = interval)
diff --git a/sympy/stats/rv.py b/sympy/stats/rv.py
index dc0daf2a0d..affd2aaeab 100644
--- a/sympy/stats/rv.py
+++ b/sympy/stats/rv.py
@@ -1768,7 +1768,7 @@ def sample_stochastic_process(process):
     >>> from sympy import Matrix
     >>> T = Matrix([[0.5, 0.2, 0.3],[0.2, 0.5, 0.3],[0.2, 0.3, 0.5]])
     >>> Y = DiscreteMarkovChain("Y", [0, 1, 2], T)
-    >>> next(sample_stochastic_process(Y)) in Y.state_space
+    >>> next(sample_stochastic_process(Y)) in Y.state_space # doctest: +SKIP
     True
     >>> next(sample_stochastic_process(Y))  # doctest: +SKIP
     0


Test patch:
diff --git a/sympy/parsing/tests/test_mathematica.py b/sympy/parsing/tests/test_mathematica.py
index f89b532ef1..4e045ebfc9 100644
--- a/sympy/parsing/tests/test_mathematica.py
+++ b/sympy/parsing/tests/test_mathematica.py
@@ -236,9 +236,6 @@ def test_parser_mathematica_tokenizer():
     assert chain('"a\\"b"') == ["_Str", 'a"b']
     # This expression does not make sense mathematically, it's just testing the parser:
     assert chain('x + "abc" ^ 3') == ["Plus", "x", ["Power", ["_Str", "abc"], "3"]]
-    assert chain('"a (* b *) c"') == ["_Str", "a (* b *) c"]
-    assert chain('"a" (* b *) ') == ["_Str", "a"]
-    assert chain('"a [ b] "') == ["_Str", "a [ b] "]
     raises(SyntaxError, lambda: chain('"'))
     raises(SyntaxError, lambda: chain('"\\"'))
     raises(SyntaxError, lambda: chain('"abc'))
diff --git a/sympy/polys/numberfields/tests/test_primes.py b/sympy/polys/numberfields/tests/test_primes.py
index 42bc33cf2e..d5cc6567f2 100644
--- a/sympy/polys/numberfields/tests/test_primes.py
+++ b/sympy/polys/numberfields/tests/test_primes.py
@@ -287,3 +287,9 @@ def test_PrimeIdeal_reduce():
     a_bar_expected = k.to_alg_num(a_bar_expected)
     a_bar = frp.reduce_alg_num(a)
     assert a_bar == a_bar_expected
+
+
+def test_issue_23402():
+    k = QQ.alg_field_from_poly(Poly(x ** 3 + x ** 2 - 2 * x + 8))
+    P = k.primes_above(3)
+    assert P[0].alpha.equiv(0)
diff --git a/sympy/printing/tests/test_latex.py b/sympy/printing/tests/test_latex.py
index af67e2b0a0..aac065ca05 100644
--- a/sympy/printing/tests/test_latex.py
+++ b/sympy/printing/tests/test_latex.py
@@ -751,7 +751,7 @@ def test_latex_indexed():
     assert indexed_latex == r'\overline{{\Psi}_{0}} {\Psi}_{0}'
 
     # Symbol('gamma') gives r'\gamma'
-    interval = '\\mathrel{..}\\nobreak '
+    interval = '\\mathrel{..}\\nobreak'
     assert latex(Indexed('x1', Symbol('i'))) == r'{x_{1}}_{i}'
     assert latex(Indexed('x2', Idx('i'))) == r'{x_{2}}_{i}'
     assert latex(Indexed('x3', Idx('i', Symbol('N')))) == r'{x_{3}}_{{i}_{0'+interval+'N - 1}}'