Repository: mhostetter/galois (language: python)

Issue:
Python >= 3.10.7 breaks some functions with large integers
I was trying to run

```python
import galois
galois.mersenne_primes()
```

And I got the error
> ValueError: Exceeds the limit (4300 digits) for integer string conversion; use sys.set_int_max_str_digits() to increase the limit

This starts to happen after the Mersenne prime 23 in the list, which is the first prime to have more than 4300 digits.

```python
>>> galois.mersenne_primes()[22]
2814112013697373133393152975842584191818662382013600787892419349345515176682276313810715094745633257074198789308535071537342445016418881801789390548709414391857257571565758706478418356747070674633497188053050875416821624325680555826071110691946607460873056965360830571590242774934226866183966309185433462514537484258655982386235046029227507801410907163348439547781093397260096909677091843944555754221115477343760206979650067087884993478012977277878532807432236554020931571802310429923167588432457036104110850960439769038450365514022349625383665751207169661697352732236111926846454751701734527011379148175107820821297628946795631098960767492250494834254073334414121627833939461539212528932010726136689293688815665491671395174710452663709175753603774156855766515313827613727281696692633529666363787286539769941609107777183593336002680124517633451490439598324823836457251219406391432635639225604556042396004307799361927379900586400420763092320813392262492942076312933268033818471555255820639308889948665570202403815856313578949779767046261845327956725767289205262311752014786247813331834015084475386760526612217340579721237414485803725355463022009536301008145867524704604618862039093555206195328240951895107040793284825095462530151872823997171764140663315804309008611942578380931064748991594407476328437785848825423921170614938294029483257162979299388940695877375448948081108345293394327808452729789834135140193912419661799488795210328238112742218700634541149743657287232843426369348804878993471962403393967857676150371600196650252168250117793178488012000505422821362550520509209724459895852366827477851619190503254853115029403132178989005195751194301340277282730390683651120587895060198753121882187788657024007291784186518589977788510306743945896108645258766415692825664174470616153305144852273884549635059255410606458427323864109506687636314447514269094932953219924212594695157655009158521173420923275882063327625408617963032962033572563553604056097832111547535908988433816919747615817161606620557307000377194730013431815560750159027842164901422544571224546936793234970894954668425436412347785376194310030139080568383420772628618722646109707506566928102800033961704343991962002059794565527774913883237756792720065543768640792177441559278272350823092843683534396679150229676101834243787820420087274028617212684576388733605769491224109866592577360666241467280158988605523486345880882227855505706309276349415034547677180618296352866263005509222254318459768194126727603047460344175581029298320171226355234439676816309919127574206334807719021875413891580871529049187829308412133400910419756313021540478436604178446757738998632083586207992234085162634375406771169707323213988284943779122171985953605897902291781768286548287878180415060635460047164104095483777201737468873324068550430695826210304316336385311384093490021332372463463373977427405896673827544203128574874581960335232005637229319592369288171375276702260450911735069504025016667755214932073643654199488477010363909372005757899989580775775126621113057905717449417222016070530243916116705990451304256206318289297738303095152430549772239514964821601838628861446301936017710546777503109263030994747397618576207373447725441427135362428360863669327157635983045447971816718801639869547525146305655571843717916875669140320724978568586718527586602439602335283513944980064327030278104224144971883680541689784796267391476087696392191
>>> galois.mersenne_primes()[23]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: Exceeds the limit (4300 digits) for integer string conversion; use sys.set_int_max_str_digits() to increase the limit
```

I think this issue is documented [here](https://docs.python.org/3/library/stdtypes.html#integer-string-conversion-length-limitation), which was a solution to [CVE-2020-10735](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-10735).

This would be more or less fine if it only affects printing functionality, but unfortunately it also affects creating fields. For example,

```python
>>> p = galois.mersenne_primes()[23]
>>> gf = galois.GF(p)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.11/site-packages/galois/_fields/_factory.py", line 249, in GF
    p, e = factors(order)
           ^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/galois/_prime.py", line 817, in factors
    p, e, n = PrimeFactorsDatabase().fetch(n)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/galois/_databases/_interface.py", line 52, in fetch
    (str(n),),
     ^^^^^^
ValueError: Exceeds the limit (4300 digits) for integer string conversion; use sys.set_int_max_str_digits() to increase the limit
```

The quick solution is to do

```python
import sys
sys.set_int_max_str_digits(0)
```

This is probably not a real issue, since working with this huge finite fields is too slow anyways. What do you think?


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml
index 241e47061..18bed60bd 100644
--- a/.github/workflows/docs.yaml
+++ b/.github/workflows/docs.yaml
@@ -29,10 +29,10 @@ jobs:
           # NOTE: Fake ternary operator, see https://github.com/actions/runner/issues/409
           fetch-depth: 0  # Fetch all commits and tags, needed for intermediate versions
 
-      - name: Set up Python 3.8
+      - name: Set up Python 3.11
         uses: actions/setup-python@v4
         with:
-          python-version: 3.8
+          python-version: 3.11
 
       - name: Upgrade pip
         run: python3 -m pip install --upgrade pip
diff --git a/README.md b/README.md
index 651e2de89..92d6b8101 100644
--- a/README.md
+++ b/README.md
@@ -78,7 +78,7 @@ Import the `galois` package in Python.
 In [1]: import galois
 
 In [2]: galois.__version__
-Out[2]: '0.3.5'
+Out[2]: '0.3.6'
 ```
 
 ### Create a [`FieldArray`](https://mhostetter.github.io/galois/latest/api/galois.FieldArray/) subclass
diff --git a/docs/_static/extra.css b/docs/_static/extra.css
index 889ef1088..8ab1bd937 100644
--- a/docs/_static/extra.css
+++ b/docs/_static/extra.css
@@ -16,6 +16,14 @@
   background-color: #344FA1;
 }
 
+/* @media screen and (max-width: 76.1875em) */
+.md-nav--primary .md-nav__title[for=__drawer] {
+    background-color: #344FA1;
+}
+.md-source {
+  background-color: #344FA1;
+}
+
 /* Fix line numbers in code blocks */
 .linenos {
   background-color: var(--md-default-bg-color--light);
diff --git a/docs/api.rst b/docs/api.rst
index 6c8aa00c1..b527c5e17 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -1,25 +1,22 @@
 .. py:module:: galois
 
-galois
-======
-
 Arrays
-------
+======
 
 .. python-apigen-group:: arrays
 
 Galois fields
--------------
+=============
 
 .. python-apigen-group:: galois-fields
 
 Primitive elements
-..................
+------------------
 
 .. python-apigen-group:: galois-fields-primitive-elements
 
 Polynomials
------------
+===========
 
 .. python-apigen-group:: polys
 
@@ -28,97 +25,97 @@ Polynomials
     The :ref:`number-theory` section contains many functions that operate on polynomials.
 
 Irreducible polynomials
-.......................
+-----------------------
 
 .. python-apigen-group:: polys-irreducible
 
 Primitive polynomials
-.....................
+---------------------
 
 .. python-apigen-group:: polys-primitive
 
 Interpolating polynomials
-.........................
+-------------------------
 
 .. python-apigen-group:: polys-interpolating
 
 Forward error correction
-------------------------
+========================
 
 .. python-apigen-group:: fec
 
 Linear sequences
-----------------
+================
 
 .. python-apigen-group:: linear-sequences
 
 Transforms
-----------
+==========
 
 .. python-apigen-group:: transforms
 
 .. _number-theory:
 
 Number theory
--------------
+=============
 
 Divisibility
-............
+------------
 
 .. python-apigen-group:: number-theory-divisibility
 
 Congruences
-...........
+-----------
 
 .. python-apigen-group:: number-theory-congruences
 
 Primitive roots
-...............
+---------------
 
 .. python-apigen-group:: number-theory-primitive-roots
 
 Integer arithmetic
-..................
+------------------
 
 .. python-apigen-group:: number-theory-integer
 
 Factorization
--------------
+=============
 
 Prime factorization
-...................
+-------------------
 
 .. python-apigen-group:: factorization-prime
 
 Composite factorization
-.......................
+-----------------------
 
 .. python-apigen-group:: factorization-composite
 
 Specific factorization algorithms
-.................................
+---------------------------------
 
 .. python-apigen-group:: factorization-specific
 
 Primes
-------
+======
 
 Prime number generation
-.......................
+-----------------------
 
 .. python-apigen-group:: primes-generation
 
 Primality tests
-...............
+---------------
 
 .. python-apigen-group:: primes-tests
 
 Specific primality tests
-........................
+------------------------
 
 .. python-apigen-group:: primes-specific-tests
 
 Configuration
--------------
+=============
 
 .. python-apigen-group:: config
diff --git a/docs/basic-usage/array-arithmetic.rst b/docs/basic-usage/array-arithmetic.rst
index 92a4e928e..c71fe4b4e 100644
--- a/docs/basic-usage/array-arithmetic.rst
+++ b/docs/basic-usage/array-arithmetic.rst
@@ -4,7 +4,7 @@ Array Arithmetic
 After creating a :obj:`~galois.FieldArray` subclass and one or two arrays, nearly any arithmetic operation can be
 performed using normal NumPy ufuncs or Python operators.
 
-In the sections below, the finite field :math:`\mathrm{GF}(3^5)` and arrays :math:`x` and :math:`y` are used.
+In the sections below, the finite field $\mathrm{GF}(3^5)$ and arrays $x$ and $y$ are used.
 
 .. ipython:: python
 
@@ -81,7 +81,7 @@ Expand any section for more details.
         np.multiply(x, 4)
         x + x + x + x
 
-    In finite fields :math:`\mathrm{GF}(p^m)`, the characteristic :math:`p` is the smallest value when multiplied by
+    In finite fields $\mathrm{GF}(p^m)$, the characteristic $p$ is the smallest value when multiplied by
     any non-zero field element that results in 0.
 
     .. ipython-with-reprs:: int,poly,power
@@ -168,7 +168,7 @@ Expand any section for more details.
 .. example:: Logarithm: `np.log(x)` or `x.log()`
     :collapsible:
 
-    Compute the logarithm base :math:`\alpha`, the primitive element of the field.
+    Compute the logarithm base $\alpha$, the primitive element of the field.
 
     .. ipython-with-reprs:: int,poly,power
 
@@ -177,7 +177,7 @@ Expand any section for more details.
         alpha = GF.primitive_element; alpha
         alpha ** z == y
 
-    Compute the logarithm base :math:`\beta`, a different primitive element of the field. See :func:`FieldArray.log`
+    Compute the logarithm base $\beta$, a different primitive element of the field. See :func:`FieldArray.log`
     for more details.
 
     .. ipython-with-reprs:: int,poly,power
@@ -291,8 +291,8 @@ Advanced arithmetic
 .. example:: FFT: `np.fft.fft(x)`
     :collapsible:
 
-    The Discrete Fourier Transform (DFT) of size :math:`n` over the finite field :math:`\mathrm{GF}(p^m)` exists when
-    there exists a primitive :math:`n`-th root of unity. This occurs when :math:`n\ |\ p^m - 1`.
+    The Discrete Fourier Transform (DFT) of size $n$ over the finite field $\mathrm{GF}(p^m)$ exists when
+    there exists a primitive $n$-th root of unity. This occurs when $n\ |\ p^m - 1$.
 
     .. ipython-with-reprs:: int,poly,power
 
@@ -309,8 +309,8 @@ Advanced arithmetic
 .. example:: Inverse FFT: `np.fft.ifft(X)`
     :collapsible:
 
-    The inverse Discrete Fourier Transform (DFT) of size :math:`n` over the finite field :math:`\mathrm{GF}(p^m)`
-    exists when there exists a primitive :math:`n`-th root of unity. This occurs when :math:`n\ |\ p^m - 1`.
+    The inverse Discrete Fourier Transform (DFT) of size $n$ over the finite field $\mathrm{GF}(p^m)$
+    exists when there exists a primitive $n$-th root of unity. This occurs when $n\ |\ p^m - 1$.
 
     .. ipython-with-reprs:: int,poly,power
 
diff --git a/docs/basic-usage/array-classes.rst b/docs/basic-usage/array-classes.rst
index c598741fc..a9b5f443e 100644
--- a/docs/basic-usage/array-classes.rst
+++ b/docs/basic-usage/array-classes.rst
@@ -8,7 +8,7 @@ The :obj:`galois` library subclasses :obj:`~numpy.ndarray` to provide arithmetic
 
 The main abstract base class is :obj:`~galois.Array`. It has two abstract subclasses: :obj:`~galois.FieldArray` and
 :obj:`~galois.RingArray` (future). None of these abstract classes may be instantiated directly. Instead, specific
-subclasses for :math:`\mathrm{GF}(p^m)` and :math:`\mathrm{GR}(p^e, m)` are created at runtime with :func:`~galois.GF`
+subclasses for $\mathrm{GF}(p^m)$ and $\mathrm{GR}(p^e, m)$ are created at runtime with :func:`~galois.GF`
 and :func:`~galois.GR` (future).
 
 :obj:`~galois.FieldArray` subclasses
@@ -26,7 +26,7 @@ A :obj:`~galois.FieldArray` subclass is created using the class factory function
     :collapsible:
 
     For very large finite fields, the :obj:`~galois.FieldArray` subclass creation time can be reduced by explicitly specifying
-    :math:`p` and :math:`m`. This eliminates the need to factor the order :math:`p^m`.
+    $p$ and $m$. This eliminates the need to factor the order $p^m$.
 
     .. ipython:: python
 
@@ -35,7 +35,7 @@ A :obj:`~galois.FieldArray` subclass is created using the class factory function
 
     Furthermore, if you already know the desired irreducible polynomial is irreducible and the primitive element is a generator of
     the multiplicative group, you can specify `verify=False` to skip the verification step. This eliminates the need to factor
-    :math:`p^m - 1`.
+    $p^m - 1$.
 
     .. ipython:: python
 
@@ -57,8 +57,8 @@ Class singletons
 
 :obj:`~galois.FieldArray` subclasses of the same type (order, irreducible polynomial, and primitive element) are singletons.
 
-Here is the creation (twice) of the field :math:`\mathrm{GF}(3^5)` defined with the default irreducible
-polynomial :math:`x^5 + 2x + 1`. They *are* the same class (a singleton), not just equivalent classes.
+Here is the creation (twice) of the field $\mathrm{GF}(3^5)$ defined with the default irreducible
+polynomial $x^5 + 2x + 1$. They *are* the same class (a singleton), not just equivalent classes.
 
 .. ipython:: python
 
@@ -66,7 +66,7 @@ polynomial :math:`x^5 + 2x + 1`. They *are* the same class (a singleton), not ju
 
 The expense of class creation is incurred only once. So, subsequent calls of `galois.GF(3**5)` are extremely inexpensive.
 
-However, the field :math:`\mathrm{GF}(3^5)` defined with irreducible polynomial :math:`x^5 + x^2 + x + 2`, while isomorphic to the
+However, the field $\mathrm{GF}(3^5)$ defined with irreducible polynomial $x^5 + x^2 + x + 2$, while isomorphic to the
 first field, has different arithmetic. As such, :func:`~galois.GF` returns a unique :obj:`~galois.FieldArray` subclass.
 
 .. ipython:: python
@@ -76,7 +76,7 @@ first field, has different arithmetic. As such, :func:`~galois.GF` returns a uni
 Methods and properties
 ......................
 
-All of the methods and properties related to :math:`\mathrm{GF}(p^m)`, not one of its arrays, are documented as class methods
+All of the methods and properties related to $\mathrm{GF}(p^m)$, not one of its arrays, are documented as class methods
 and class properties in :obj:`~galois.FieldArray`. For example, the irreducible polynomial of the finite field is accessed
 with :obj:`~galois.FieldArray.irreducible_poly`.
 
diff --git a/docs/basic-usage/array-creation.rst b/docs/basic-usage/array-creation.rst
index f4aa45ea8..8ae9a47e0 100644
--- a/docs/basic-usage/array-creation.rst
+++ b/docs/basic-usage/array-creation.rst
@@ -2,7 +2,7 @@ Array Creation
 ==============
 
 This page discusses the multiple ways to create arrays over finite fields. For this discussion, we are working in
-the finite field :math:`\mathrm{GF}(3^5)`.
+the finite field $\mathrm{GF}(3^5)$.
 
 .. ipython-with-reprs:: int,poly,power
 
@@ -41,15 +41,15 @@ A finite field array may also be created by exponentiating the primitive element
 Polynomial coefficients
 .......................
 
-Rather than strings, the polynomial coefficients may be passed into `GF`'s constructor as length-:math:`m` vectors using
+Rather than strings, the polynomial coefficients may be passed into `GF`'s constructor as length-$m$ vectors using
 the :func:`~galois.FieldArray.Vector` classmethod.
 
 .. ipython-with-reprs:: int,poly,power
 
    GF.Vector([[0, 0, 1, 2, 2], [0, 0, 0, 1, 1]])
 
-The :func:`~galois.FieldArray.vector` method is the opposite operation. It converts extension field elements from :math:`\mathrm{GF}(p^m)`
-into length-:math:`m` vectors over :math:`\mathrm{GF}(p)`.
+The :func:`~galois.FieldArray.vector` method is the opposite operation. It converts extension field elements from $\mathrm{GF}(p^m)$
+into length-$m$ vectors over $\mathrm{GF}(p)$.
 
 .. ipython-with-reprs:: int,poly,power
 
@@ -104,7 +104,7 @@ useful for initializing empty arrays.
    :title: There is no :func:`numpy.empty` equivalent.
    :collapsible:
 
-   This is because :obj:`~galois.FieldArray` instances must have values in :math:`[0, p^m)`. Empty NumPy arrays have whatever values
+   This is because :obj:`~galois.FieldArray` instances must have values in $[0, p^m)$. Empty NumPy arrays have whatever values
    are currently in memory, and therefore would fail those bounds checks during instantiation.
 
 Ordered arrays
@@ -153,14 +153,14 @@ able to store all the field elements (in their :ref:`integer representation <int
 Valid data types
 ................
 
-For small finite fields, like :math:`\mathrm{GF}(2^4)`, every NumPy integer data type is supported.
+For small finite fields, like $\mathrm{GF}(2^4)$, every NumPy integer data type is supported.
 
 .. ipython:: python
 
     GF = galois.GF(2**4)
     GF.dtypes
 
-For medium finite fields, like :math:`\mathrm{GF}(2^{10})`, some NumPy integer data types are not supported. Here,
+For medium finite fields, like $\mathrm{GF}(2^{10})$, some NumPy integer data types are not supported. Here,
 :obj:`numpy.uint8` and :obj:`numpy.int8` are not supported.
 
 .. ipython:: python
@@ -168,7 +168,7 @@ For medium finite fields, like :math:`\mathrm{GF}(2^{10})`, some NumPy integer d
     GF = galois.GF(2**10)
     GF.dtypes
 
-For large finite fields, like :math:`\mathrm{GF}(2^{100})`, only the "object" data type (:obj:`numpy.object_`) is
+For large finite fields, like $\mathrm{GF}(2^{100})$, only the "object" data type (:obj:`numpy.object_`) is
 supported. This uses arrays of Python objects, rather than integer data types. The Python objects used are Python integers,
 which have unlimited size.
 
diff --git a/docs/basic-usage/compilation-modes.rst b/docs/basic-usage/compilation-modes.rst
index d9194b261..9a34531b4 100644
--- a/docs/basic-usage/compilation-modes.rst
+++ b/docs/basic-usage/compilation-modes.rst
@@ -31,40 +31,40 @@ to speed up arithmetic computations. These tables are built once at :obj:`~galoi
 during the call to :func:`~galois.GF`.
 
 The exponential and logarithm lookup tables map every finite field element to a power of the primitive element
-:math:`\alpha`.
+$\alpha$.
 
-.. math::
-    x = \alpha^i
+$$x = \alpha^i$$
 
-.. math::
-    \textrm{log}_{\alpha}(x) = i
+$$\textrm{log}_{\alpha}(x) = i$$
 
 With these lookup tables, many arithmetic operations are simplified. For instance, multiplication of two finite field
 elements is reduced to three lookups and one integer addition.
 
-.. math::
-    x \cdot y &= \alpha^m \cdot \alpha^n \\
-              &= \alpha^{m + n}
+$$
+x \cdot y
+&= \alpha^m \cdot \alpha^n \\
+&= \alpha^{m + n}
+$$
 
 The `Zech logarithm <https://en.wikipedia.org/wiki/Zech%27s_logarithm>`_ is defined below. A similar lookup table is
 created for it.
 
-.. math::
-    1 + \alpha^i = \alpha^{Z(i)}
+$$1 + \alpha^i = \alpha^{Z(i)}$$
 
-.. math::
-    Z(i) = \textrm{log}_{\alpha}(1 + \alpha^i)
+$$Z(i) = \textrm{log}_{\alpha}(1 + \alpha^i)$$
 
 With Zech logarithms, addition of two finite field elements becomes three lookups, one integer addition, and one
 integer subtraction.
 
-.. math::
-    x + y &= \alpha^m + \alpha^n \\
-          &= \alpha^m (1 + \alpha^{n - m}) \\
-          &= \alpha^m \alpha^{Z(n - m)} \\
-          &= \alpha^{m + Z(n - m)}
+$$
+x + y
+&= \alpha^m + \alpha^n \\
+&= \alpha^m (1 + \alpha^{n - m}) \\
+&= \alpha^m \alpha^{Z(n - m)} \\
+&= \alpha^{m + Z(n - m)}
+$$
 
-Finite fields with order less than :math:`2^{20}` use lookup tables by default. In the limited cases where explicit calculation
+Finite fields with order less than $2^{20}$ use lookup tables by default. In the limited cases where explicit calculation
 is faster than table lookup, the explicit calculation is used.
 
 .. ipython:: python
@@ -77,7 +77,7 @@ is faster than table lookup, the explicit calculation is used.
 Explicit calculation
 --------------------
 
-Finite fields with order greater than :math:`2^{20}` use explicit calculation by default. This eliminates the need to store large lookup
+Finite fields with order greater than $2^{20}$ use explicit calculation by default. This eliminates the need to store large lookup
 tables. However, explicit calculation is usually slower than table lookup.
 
 .. ipython:: python
@@ -118,7 +118,7 @@ Recompile the ufuncs
 The compilation mode may be explicitly set during creation of the :obj:`~galois.FieldArray` subclass using the
 `compile` keyword argument to :func:`~galois.GF`.
 
-Here, the :obj:`~galois.FieldArray` subclass for :math:`\mathrm{GF}(3^5)` would normally select `"jit-lookup"` as its
+Here, the :obj:`~galois.FieldArray` subclass for $\mathrm{GF}(3^5)$ would normally select `"jit-lookup"` as its
 default compilation mode. However, we can intentionally choose explicit calculation.
 
 .. ipython:: python
diff --git a/docs/basic-usage/element-representation.rst b/docs/basic-usage/element-representation.rst
index 0bcbfb654..56984cfd5 100644
--- a/docs/basic-usage/element-representation.rst
+++ b/docs/basic-usage/element-representation.rst
@@ -4,10 +4,10 @@ Element Representation
 The representation of finite field elements can be set to either their integer (`"int"`), polynomial (`"poly"`),
 or power (`"power"`) representation.
 
-In prime fields :math:`\mathrm{GF}(p)`, elements are integers in :math:`\{0, 1, \dots, p-1\}`. Their two useful representations
+In prime fields $\mathrm{GF}(p)$, elements are integers in $\{0, 1, \dots, p-1\}$. Their two useful representations
 are the integer and power representation.
 
-In extension fields :math:`\mathrm{GF}(p^m)`, elements are polynomials over :math:`\mathrm{GF}(p)` with degree less than :math:`m`.
+In extension fields $\mathrm{GF}(p^m)$, elements are polynomials over $\mathrm{GF}(p)$ with degree less than $m$.
 All element representations are useful. The polynomial representation allows *proper* representation of the element as a polynomial
 over its prime subfield. However, the integer representation is more compact for displaying large arrays.
 
@@ -58,17 +58,17 @@ The element representation can be permanently changed using the :func:`~galois.F
 Integer representation
 ----------------------
 
-The integer representation (the default) displays all finite field elements as integers in :math:`\{0, 1, \dots, p^m-1\}`.
+The integer representation (the default) displays all finite field elements as integers in $\{0, 1, \dots, p^m-1\}$.
 
-In prime fields, the integer representation is simply the integer element in :math:`\{0, 1, \dots, p-1\}`.
+In prime fields, the integer representation is simply the integer element in $\{0, 1, \dots, p-1\}$.
 
 .. ipython:: python
 
     GF = galois.GF(31)
     GF(11)
 
-In extension fields, the integer representation converts and element's degree-:math:`m-1` polynomial over :math:`\mathrm{GF}(p)` into
-its integer equivalent. The integer equivalent of a polynomial is a radix-:math:`p` integer of its coefficients, with the highest-degree
+In extension fields, the integer representation converts and element's degree-$m-1$ polynomial over $\mathrm{GF}(p)$ into
+its integer equivalent. The integer equivalent of a polynomial is a radix-$p$ integer of its coefficients, with the highest-degree
 coefficient as the most-significant digit and zero-degree coefficient as the least-significant digit.
 
 .. ipython:: python
@@ -84,9 +84,9 @@ coefficient as the most-significant digit and zero-degree coefficient as the lea
 Polynomial representation
 -------------------------
 
-The polynomial representation displays all finite field elements as polynomials over their prime subfield with degree less than :math:`m`.
+The polynomial representation displays all finite field elements as polynomials over their prime subfield with degree less than $m$.
 
-In prime fields :math:`m = 1`, therefore the polynomial representation is equivalent to the integer representation because the
+In prime fields $m = 1$, therefore the polynomial representation is equivalent to the integer representation because the
 polynomials all have degree 0.
 
 .. ipython:: python
@@ -116,7 +116,7 @@ This is useful, however it can become cluttered for large arrays.
 Power representation
 --------------------
 
-The power representation displays all finite field elements as powers of the field's primitive element :math:`\alpha`.
+The power representation displays all finite field elements as powers of the field's primitive element $\alpha$.
 
 .. slow-performance::
 
@@ -125,7 +125,7 @@ The power representation displays all finite field elements as powers of the fie
     take a while. However, when using :ref:`lookup tables <lookup-tables>` this representation is just as fast as
     the others.
 
-In prime fields, the elements are displayed as :math:`\{0, 1, \alpha, \alpha^2, \dots, \alpha^{p-2}\}`.
+In prime fields, the elements are displayed as $\{0, 1, \alpha, \alpha^2, \dots, \alpha^{p-2}\}$.
 
 .. ipython:: python
 
@@ -138,7 +138,7 @@ In prime fields, the elements are displayed as :math:`\{0, 1, \alpha, \alpha^2,
     alpha = GF.primitive_element; alpha
     alpha ** 23
 
-In extension fields, the elements are displayed as :math:`\{0, 1, \alpha, \alpha^2, \dots, \alpha^{p^m-2}\}`.
+In extension fields, the elements are displayed as $\{0, 1, \alpha, \alpha^2, \dots, \alpha^{p^m-2}\}$.
 
 .. ipython:: python
 
@@ -165,8 +165,8 @@ The vector representation is accessed using the :func:`~galois.FieldArray.vector
     GF("x^2 + 2x + 2")
     GF("x^2 + 2x + 2").vector()
 
-An N-D array over :math:`\mathrm{GF}(p^m)` is converted to a (N + 1)-D array over :math:`\mathrm{GF}(p)` with the added dimension having
-size :math:`m`. The first value of the vector is the highest-degree coefficient.
+An N-D array over $\mathrm{GF}(p^m)$ is converted to a (N + 1)-D array over $\mathrm{GF}(p)$ with the added dimension having
+size $m$. The first value of the vector is the highest-degree coefficient.
 
 .. ipython:: python
 
@@ -188,7 +188,7 @@ NumPy displays arrays with a default line width of 75 characters. This is proble
 for arrays using the polynomial representation, where each element occupies a lot of space. This can be changed by modifying
 NumPy's print options.
 
-For example, below is a :math:`5 \times 5` matrix over :math:`\mathrm{GF}(3^5)` displayed in the polynomial representation.
+For example, below is a $5 \times 5$ matrix over $\mathrm{GF}(3^5)$ displayed in the polynomial representation.
 With the default line width, the array is quite difficult to read.
 
 .. ipython:: python
diff --git a/docs/basic-usage/poly-arithmetic.rst b/docs/basic-usage/poly-arithmetic.rst
index 8bf7bb218..c25f780c7 100644
--- a/docs/basic-usage/poly-arithmetic.rst
+++ b/docs/basic-usage/poly-arithmetic.rst
@@ -1,7 +1,7 @@
 Polynomial Arithmetic
 =====================
 
-In the sections below, the finite field :math:`\mathrm{GF}(7)` and polynomials :math:`f(x)` and :math:`g(x)` are used.
+In the sections below, the finite field $\mathrm{GF}(7)$ and polynomials $f(x)$ and $g(x)$ are used.
 
 .. ipython:: python
 
@@ -95,7 +95,7 @@ performed using Python operators. Expand any section for more details.
         f * 4
         f + f + f + f
 
-    In finite fields :math:`\mathrm{GF}(p^m)`, the characteristic :math:`p` is the smallest value when multiplied by
+    In finite fields $\mathrm{GF}(p^m)$, the characteristic $p$ is the smallest value when multiplied by
     any non-zero field element that always results in 0.
 
     .. ipython:: python
@@ -220,7 +220,7 @@ Polynomial objects may also be evaluated at scalars, arrays, or square matrices.
     :collapsible:
 
     Polynomials can also be evaluated at square matrices. Note, this is different than element-wise array evaluation. Here,
-    the square matrix indeterminate is exponentiated using matrix multiplication. So :math:`f(x) = x^3` evaluated
+    the square matrix indeterminate is exponentiated using matrix multiplication. So $f(x) = x^3$ evaluated
     at the square matrix `X` equals `X @ X @ X`.
 
     .. ipython:: python
@@ -234,7 +234,7 @@ Polynomial objects may also be evaluated at scalars, arrays, or square matrices.
 .. example:: Composition: `f(g)`
     :collapsible:
 
-    Polynomial composition :math:`f(g(x))` is easily performed using an overload to :func:`~galois.Poly.__call__`.
+    Polynomial composition $f(g(x))$ is easily performed using an overload to :func:`~galois.Poly.__call__`.
 
     .. ipython:: python
 
diff --git a/docs/conf.py b/docs/conf.py
index 72f8158be..c7aa8e6d9 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -54,6 +54,7 @@
     "sphinx_last_updated_by_git",
     "sphinx_immaterial",
     "sphinx_immaterial.apidoc.python.apigen",
+    "sphinx_math_dollar",
     "myst_parser",
     "sphinx_design",
     "IPython.sphinxext.ipython_console_highlighting",
@@ -123,7 +124,6 @@
     "site_url": "https://galois.readthedocs.io/",
     "repo_url": "https://github.com/mhostetter/galois",
     "repo_name": "mhostetter/galois",
-    "repo_type": "github",
     "social": [
         {
             "icon": "fontawesome/brands/github",
@@ -217,6 +217,19 @@
 
 myst_enable_extensions = ["dollarmath"]
 
+mathjax_config = {
+    "tex2jax": {
+        "inlineMath": [["\\(", "\\)"]],
+        "displayMath": [["\\[", "\\]"]],
+    },
+}
+mathjax3_config = {
+    "tex": {
+        "inlineMath": [["\\(", "\\)"]],
+        "displayMath": [["\\[", "\\]"]],
+    }
+}
+
 
 # -- Sphinx Immaterial configs -------------------------------------------------
 
@@ -281,13 +294,13 @@
         "name": "fast-performance",
         "title": "Faster performance",
         "icon": "material/speedometer",
-        "color": (40, 167, 69),  # Green: --sd-color-success
+        "color": (47, 177, 112),  # Green: --md-code-hl-string-color
     },
     {
         "name": "slow-performance",
         "title": "Slower performance",
         "icon": "material/speedometer-slow",
-        "color": (220, 53, 69),  # Red: --sd-color-danger
+        "color": (230, 105, 91),  # Red: --md-code-hl-number-color
     },
 ]
 
diff --git a/docs/getting-started.rst b/docs/getting-started.rst
index 7ed3168bc..e8d543445 100644
--- a/docs/getting-started.rst
+++ b/docs/getting-started.rst
@@ -25,7 +25,7 @@ Create a :obj:`~galois.FieldArray` subclass
 -------------------------------------------
 
 Next, create a :obj:`~galois.FieldArray` subclass for the specific finite field you'd like to work in. This is created using
-the :func:`~galois.GF` class factory. In this example, we are working in :math:`\mathrm{GF}(3^5)`.
+the :func:`~galois.GF` class factory. In this example, we are working in $\mathrm{GF}(3^5)$.
 
 .. ipython:: python
 
@@ -33,7 +33,7 @@ the :func:`~galois.GF` class factory. In this example, we are working in :math:`
    print(GF.properties)
 
 The :obj:`~galois.FieldArray` subclass `GF` is a subclass of :obj:`~numpy.ndarray` that performs all arithmetic in the Galois field
-:math:`\mathrm{GF}(3^5)`, not in :math:`\mathbb{R}`.
+$\mathrm{GF}(3^5)$, not in $\mathbb{R}$.
 
 .. ipython:: python
 
@@ -105,7 +105,7 @@ Standard element-wise array arithmetic -- addition, subtraction, multiplication,
    x * y
    x / y
 
-More complicated arithmetic, like square root and logarithm base :math:`\alpha`, are also supported.
+More complicated arithmetic, like square root and logarithm base $\alpha$, are also supported.
 
 .. ipython:: python
 
diff --git a/docs/index.rst b/docs/index.rst
index 45031eb6f..2d01e8fc6 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -5,7 +5,7 @@ The :obj:`galois` library is a Python 3 package that extends NumPy arrays to ope
 
 The user creates a :obj:`~galois.FieldArray` subclass using `GF = galois.GF(p**m)`. `GF` is a subclass of :obj:`numpy.ndarray`
 and its constructor `x = GF(array_like)` mimics the signature of :func:`numpy.array`. The :obj:`~galois.FieldArray` `x` is operated
-on like any other NumPy array except all arithmetic is performed in :math:`\mathrm{GF}(p^m)`, not :math:`\mathbb{R}`.
+on like any other NumPy array except all arithmetic is performed in $\mathrm{GF}(p^m)$, not $\mathbb{R}$.
 
 Internally, the finite field arithmetic is implemented by replacing `NumPy ufuncs <https://numpy.org/doc/stable/reference/ufuncs.html>`_.
 The new ufuncs are written in pure Python and `just-in-time compiled <https://numba.pydata.org/numba-doc/dev/user/vectorize.html>`_ with
@@ -23,8 +23,8 @@ calculation (for memory savings).
 Features
 --------
 
-- Supports all Galois fields :math:`\mathrm{GF}(p^m)`, even arbitrarily-large fields!
-- **Faster** than native NumPy! `GF(x) * GF(y)` is faster than `(x * y) % p` for :math:`\mathrm{GF}(p)`.
+- Supports all Galois fields $\mathrm{GF}(p^m)$, even arbitrarily-large fields!
+- **Faster** than native NumPy! `GF(x) * GF(y)` is faster than `(x * y) % p` for $\mathrm{GF}(p)$.
 - Seamless integration with NumPy -- normal NumPy functions work on :obj:`~galois.FieldArray` instances.
 - Linear algebra over finite fields using normal :obj:`numpy.linalg` functions.
 - Linear transforms over finite fields, such as the FFT with :func:`numpy.fft.fft` and the NTT with :func:`~galois.ntt`.
@@ -54,8 +54,8 @@ of the finite field arithmetic.
 `Wolfram's compilation <https://datarepository.wolframcloud.com/resources/Primitive-Polynomials/>`_ of primitive polynomials are used
 for efficient polynomial lookup, when possible.
 
-`The Cunningham Book's tables <https://homes.cerias.purdue.edu/~ssw/cun/third/index.html>`_ of prime factorizations, :math:`b^n \pm 1`
-for :math:`b \in \{2, 3, 5, 6, 7, 10, 11, 12\}`, are used to generate factorization lookup tables. These lookup tables speed-up the
+`The Cunningham Book's tables <https://homes.cerias.purdue.edu/~ssw/cun/third/index.html>`_ of prime factorizations, $b^n \pm 1$
+for $b \in \{2, 3, 5, 6, 7, 10, 11, 12\}$, are used to generate factorization lookup tables. These lookup tables speed-up the
 creation of large finite fields by avoiding the need to factor large integers.
 
 `Sage <https://www.sagemath.org/>`_ is used extensively for generating test vectors for finite field arithmetic and polynomial arithmetic.
diff --git a/docs/performance/binary-extension-fields.rst b/docs/performance/binary-extension-fields.rst
index f6ba27241..71ff594e8 100644
--- a/docs/performance/binary-extension-fields.rst
+++ b/docs/performance/binary-extension-fields.rst
@@ -1,10 +1,10 @@
 Binary Extension Fields
 =======================
 
-This page compares the performance of :obj:`galois` performing finite field multiplication in :math:`\mathrm{GF}(2^m)` with
+This page compares the performance of :obj:`galois` performing finite field multiplication in $\mathrm{GF}(2^m)$ with
 native NumPy performing *only* modular multiplication.
 
-Native NumPy cannot easily perform finite field multiplication in :math:`\mathrm{GF}(2^m)` because it involves polynomial multiplication
+Native NumPy cannot easily perform finite field multiplication in $\mathrm{GF}(2^m)$ because it involves polynomial multiplication
 (convolution) followed by reducing modulo the irreducible polynomial. To make a *similar* comparison, NumPy will perform integer
 multiplication followed by integer remainder division.
 
@@ -19,9 +19,9 @@ Lookup table performance
 ------------------------
 
 This section tests :obj:`galois` when using the `"jit-lookup"` compilation mode. For finite fields with order less
-than or equal to :math:`2^{20}`, :obj:`galois` uses lookup tables by default for efficient arithmetic.
+than or equal to $2^{20}$, :obj:`galois` uses lookup tables by default for efficient arithmetic.
 
-Below are examples computing 10 million multiplications in the binary extension field :math:`\mathrm{GF}(2^8)`.
+Below are examples computing 10 million multiplications in the binary extension field $\mathrm{GF}(2^8)$.
 
 .. code-block:: ipython
 
@@ -62,9 +62,9 @@ Explicit calculation performance
 --------------------------------
 
 This section tests :obj:`galois` when using the `"jit-calculate"` compilation mode. For finite fields with order greater
-than :math:`2^{20}`, :obj:`galois` will use explicit arithmetic calculation by default rather than lookup tables.
+than $2^{20}$, :obj:`galois` will use explicit arithmetic calculation by default rather than lookup tables.
 
-Below are examples computing 10 million multiplications in the binary extension field :math:`\mathrm{GF}(2^{32})`.
+Below are examples computing 10 million multiplications in the binary extension field $\mathrm{GF}(2^{32})$.
 
 .. code-block:: ipython
 
@@ -110,8 +110,8 @@ Linear algebra performance in extension fields is definitely slower than native
 with prime fields, it is not possible to use the BLAS/LAPACK implementations. Instead, entirely new JIT-compiled
 ufuncs are generated, which are not as optimized for parallelism or hardware acceleration as BLAS/LAPACK.
 
-Below are examples computing the matrix multiplication of two :math:`100 \times 100` matrices in the binary extension
-field :math:`\mathrm{GF}(2^{32})`.
+Below are examples computing the matrix multiplication of two $100 \times 100$ matrices in the binary extension
+field $\mathrm{GF}(2^{32})$.
 
 .. code-block:: ipython
 
diff --git a/docs/performance/prime-fields.rst b/docs/performance/prime-fields.rst
index a34885a11..3ce69bbe6 100644
--- a/docs/performance/prime-fields.rst
+++ b/docs/performance/prime-fields.rst
@@ -2,16 +2,16 @@ Prime Fields
 ============
 
 This page compares the performance of :obj:`galois` to native NumPy when performing finite field
-multiplication in :math:`\mathrm{GF}(p)`. Native NumPy can perform finite field multiplication in :math:`\mathrm{GF}(p)`
-because prime fields are very simple. Multiplication is simply :math:`xy\ \textrm{mod}\ p`.
+multiplication in $\mathrm{GF}(p)$. Native NumPy can perform finite field multiplication in $\mathrm{GF}(p)$
+because prime fields are very simple. Multiplication is simply $xy\ \textrm{mod}\ p$.
 
 Lookup table performance
 ------------------------
 
 This section tests :obj:`galois` when using the `"jit-lookup"` compilation mode. For finite fields with order less
-than or equal to :math:`2^{20}`, :obj:`galois` uses lookup tables by default for efficient arithmetic.
+than or equal to $2^{20}$, :obj:`galois` uses lookup tables by default for efficient arithmetic.
 
-Below are examples computing 10 million multiplications in the prime field :math:`\mathrm{GF}(31)`.
+Below are examples computing 10 million multiplications in the prime field $\mathrm{GF}(31)$.
 
 .. code-block:: ipython
 
@@ -48,10 +48,10 @@ Explicit calculation performance
 --------------------------------
 
 This section tests :obj:`galois` when using the `"jit-calculate"` compilation mode. For finite fields with order greater
-than :math:`2^{20}`, :obj:`galois` will use explicit arithmetic calculation by default rather than lookup tables. *Even in these cases*,
+than $2^{20}$, :obj:`galois` will use explicit arithmetic calculation by default rather than lookup tables. *Even in these cases*,
 :obj:`galois` is faster than NumPy!
 
-Below are examples computing 10 million multiplications in the prime field :math:`\mathrm{GF}(2097169)`.
+Below are examples computing 10 million multiplications in the prime field $\mathrm{GF}(2097169)$.
 
 .. code-block:: ipython
 
@@ -134,11 +134,11 @@ Linear algebra performance
 Linear algebra performance in prime fields is comparable to the native NumPy implementations, which use BLAS/LAPACK. This is
 because :obj:`galois` uses the native NumPy ufuncs when possible.
 
-If overflow is prevented, dot products in :math:`\mathrm{GF}(p)` can be computed by first computing the dot product in
-:math:`\mathbb{Z}` and then reducing modulo :math:`p`. In this way, the efficient BLAS/LAPACK implementations are used to
+If overflow is prevented, dot products in $\mathrm{GF}(p)$ can be computed by first computing the dot product in
+$\mathbb{Z}$ and then reducing modulo $p$. In this way, the efficient BLAS/LAPACK implementations are used to
 keep finite field linear algebra fast, whenever possible.
 
-Below are examples computing the matrix multiplication of two :math:`100 \times 100` matrices in the prime field :math:`\mathrm{GF}(2097169)`.
+Below are examples computing the matrix multiplication of two $100 \times 100$ matrices in the prime field $\mathrm{GF}(2097169)$.
 
 .. code-block:: ipython
 
diff --git a/docs/release-notes/v0.3.md b/docs/release-notes/v0.3.md
index bf06ff475..c1f9c93da 100644
--- a/docs/release-notes/v0.3.md
+++ b/docs/release-notes/v0.3.md
@@ -245,3 +245,20 @@ tocdepth: 2
 ### Contributors
 
 - Matt Hostetter ([@mhostetter](https://github.com/mhostetter))
+
+## v0.3.6
+
+*Released October 1, 2023*
+
+### Changes
+
+- Added support for NumPy 1.25. ([#507](https://github.com/mhostetter/galois/pull/507))
+- Added support for Numba 0.58. ([#507](https://github.com/mhostetter/galois/pull/507))
+- Fixed rare overflow with computing a large modular exponentiation of polynomials. ([#488](https://github.com/mhostetter/galois/pull/488))
+- Resolved various deprecations warnings with NumPy 1.25. ([#492](https://github.com/mhostetter/galois/pull/492))
+
+### Contributors
+
+- Iyán Méndez Veiga ([@iyanmv](https://github.com/iyanmv))
+- [@Lasagnenator](https://github.com/Lasagnenator)
+- Matt Hostetter ([@mhostetter](https://github.com/mhostetter))
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 55deee999..85884270f 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,6 +1,7 @@
 sphinx < 5.4
-sphinx-immaterial == 0.11.2
+sphinx-immaterial == 0.11.5
 myst-parser
 sphinx-design
 sphinx-last-updated-by-git
+sphinx-math-dollar
 ipykernel
diff --git a/docs/tutorials/intro-to-extension-fields.rst b/docs/tutorials/intro-to-extension-fields.rst
index 069720470..e3d621abd 100644
--- a/docs/tutorials/intro-to-extension-fields.rst
+++ b/docs/tutorials/intro-to-extension-fields.rst
@@ -2,16 +2,16 @@ Intro to Extension Fields
 =========================
 
 As discussed in the :doc:`intro-to-prime-fields` tutorial, a finite field is a finite set that is closed under addition, subtraction, multiplication,
-and division. Galois proved that finite fields exist only when their *order* (or size of the set) is a prime power :math:`p^m`.
+and division. Galois proved that finite fields exist only when their *order* (or size of the set) is a prime power $p^m$.
 
-When the order is prime, the arithmetic is *mostly* computed using integer arithmetic modulo :math:`p`. When the order is a prime power, namely
-extension fields :math:`\mathrm{GF}(p^m)`, the arithmetic is *mostly* computed using polynomial arithmetic modulo the irreducible
-polynomial :math:`f(x)`.
+When the order is prime, the arithmetic is *mostly* computed using integer arithmetic modulo $p$. When the order is a prime power, namely
+extension fields $\mathrm{GF}(p^m)$, the arithmetic is *mostly* computed using polynomial arithmetic modulo the irreducible
+polynomial $f(x)$.
 
 Extension field
 ---------------
 
-In this tutorial, we will consider the extension field :math:`\mathrm{GF}(3^2)`. Using the :obj:`galois` library, the :obj:`~galois.FieldArray` subclass
+In this tutorial, we will consider the extension field $\mathrm{GF}(3^2)$. Using the :obj:`galois` library, the :obj:`~galois.FieldArray` subclass
 `GF9` is created using the class factory :func:`~galois.GF`.
 
 .. ipython-with-reprs:: int,poly,power
@@ -23,7 +23,7 @@ In this tutorial, we will consider the extension field :math:`\mathrm{GF}(3^2)`.
 .. info::
 
    In this tutorial, we suggest using the polynomial representation to display the elements. Although, it is common to use the default
-   integer representation :math:`\{0, 1, \dots, p^m - 1\}` to display the arrays more compactly. Switch the display between the three
+   integer representation $\{0, 1, \dots, p^m - 1\}$ to display the arrays more compactly. Switch the display between the three
    representations using the tabbed sections.
 
    See :doc:`/basic-usage/element-representation` for more details.
@@ -31,9 +31,9 @@ In this tutorial, we will consider the extension field :math:`\mathrm{GF}(3^2)`.
 Elements
 --------
 
-The elements of :math:`\mathrm{GF}(p^m)` are polynomials over :math:`\mathrm{GF}(p)` with degree less than :math:`m`.
-Formally, they are all polynomials :math:`a_{m-1}x^{m-1} + \dots + a_1x^1 +  a_0 \in \mathrm{GF}(p)[x]`. There are
-exactly :math:`p^m` elements.
+The elements of $\mathrm{GF}(p^m)$ are polynomials over $\mathrm{GF}(p)$ with degree less than $m$.
+Formally, they are all polynomials $a_{m-1}x^{m-1} + \dots + a_1x^1 +  a_0 \in \mathrm{GF}(p)[x]$. There are
+exactly $p^m$ elements.
 
 The elements of the finite field are retrieved in a 1-D array using the :func:`~galois.FieldArray.Elements` classmethod.
 
@@ -45,20 +45,20 @@ The elements of the finite field are retrieved in a 1-D array using the :func:`~
 Irreducible polynomial
 ----------------------
 
-Every extension field must be defined with respect to an irreducible polynomial :math:`f(x)`. This polynomial defines the
+Every extension field must be defined with respect to an irreducible polynomial $f(x)$. This polynomial defines the
 arithmetic of the field.
 
 When creating a :obj:`~galois.FieldArray` subclass in :obj:`galois`, if an irreducible polynomial is not explicitly specified, a default
-is chosen. The default is the Conway polynomial :math:`C_{p,m}(x)`, which is irreducible *and* primitive. See :func:`~galois.conway_poly`
+is chosen. The default is the Conway polynomial $C_{p,m}(x)$, which is irreducible *and* primitive. See :func:`~galois.conway_poly`
 for more information.
 
-Notice :math:`f(x)` is over :math:`\mathrm{GF}(3)` with degree 2.
+Notice $f(x)$ is over $\mathrm{GF}(3)$ with degree 2.
 
 .. ipython:: python
 
    f = GF9.irreducible_poly; f
 
-Also note, when factored, :math:`f(x)` has no irreducible factors other than itself -- an analogue of a prime number.
+Also note, when factored, $f(x)$ has no irreducible factors other than itself -- an analogue of a prime number.
 
 .. ipython:: python
 
@@ -68,14 +68,14 @@ Also note, when factored, :math:`f(x)` has no irreducible factors other than its
 Arithmetic
 ----------
 
-Addition, subtraction, and multiplication in :math:`\mathrm{GF}(p^m)` with irreducible polynomial :math:`f(x)` is equivalent to polynomial
-addition, subtraction, and multiplication over :math:`\mathrm{GF}(p)` reduced modulo :math:`f(x)`. Mathematically speaking, this is
-the polynomial ring :math:`\mathrm{GF}(p)[x] / f(x)`.
+Addition, subtraction, and multiplication in $\mathrm{GF}(p^m)$ with irreducible polynomial $f(x)$ is equivalent to polynomial
+addition, subtraction, and multiplication over $\mathrm{GF}(p)$ reduced modulo $f(x)$. Mathematically speaking, this is
+the polynomial ring $\mathrm{GF}(p)[x] / f(x)$.
 
-In this tutorial, consider two field elements :math:`a = x + 2` and :math:`b = x + 1`. We will use :obj:`galois` to perform explicit polynomial
+In this tutorial, consider two field elements $a = x + 2$ and $b = x + 1$. We will use :obj:`galois` to perform explicit polynomial
 calculations and then extension field arithmetic.
 
-Here are :math:`a` and :math:`b` represented using :obj:`~galois.Poly` objects.
+Here are $a$ and $b$ represented using :obj:`~galois.Poly` objects.
 
 .. ipython:: python
 
@@ -83,7 +83,7 @@ Here are :math:`a` and :math:`b` represented using :obj:`~galois.Poly` objects.
    a_poly = galois.Poly([1, 2], field=GF3); a_poly
    b_poly = galois.Poly([1, 1], field=GF3); b_poly
 
-Here are :math:`a` and :math:`b` represented as extension field elements. Extension field elements can be specified as integers
+Here are $a$ and $b$ represented as extension field elements. Extension field elements can be specified as integers
 or polynomial strings. See :doc:`/basic-usage/array-creation` for more details.
 
 .. ipython-with-reprs:: int,poly,power
@@ -95,11 +95,11 @@ or polynomial strings. See :doc:`/basic-usage/array-creation` for more details.
 Addition
 ........
 
-In polynomial addition, the polynomial coefficients add degree-wise in :math:`\mathrm{GF}(p)`. Addition of polynomials with degree
-less than :math:`m` will never result in a polynomial of degree :math:`m` or greater. Therefore, it is unnecessary to reduce modulo
-the degree-:math:`m` polynomial :math:`f(x)`, since the quotient will always be zero.
+In polynomial addition, the polynomial coefficients add degree-wise in $\mathrm{GF}(p)$. Addition of polynomials with degree
+less than $m$ will never result in a polynomial of degree $m$ or greater. Therefore, it is unnecessary to reduce modulo
+the degree-$m$ polynomial $f(x)$, since the quotient will always be zero.
 
-We can see that :math:`a + b = (1 + 1)x + (2 + 1) = 2x`.
+We can see that $a + b = (1 + 1)x + (2 + 1) = 2x$.
 
 .. ipython-with-reprs:: int,poly,power
    :name: GF9
@@ -121,7 +121,7 @@ Subtraction
 
 Subtraction, like addition, is performed on coefficients degree-wise and will never result in a polynomial with greater degree.
 
-We can see that :math:`a - b = (1 - 1)x + (2 - 1) = 1`.
+We can see that $a - b = (1 - 1)x + (2 - 1) = 1$.
 
 .. ipython-with-reprs:: int,poly,power
    :name: GF9
@@ -140,12 +140,12 @@ Here is the entire subtraction table for completeness.
 Multiplication
 ..............
 
-Multiplication of polynomials with degree less than :math:`m`, however, will often result in a polynomial of degree :math:`m`
-or greater. Therefore, it is necessary to reduce the result modulo :math:`f(x)`.
+Multiplication of polynomials with degree less than $m$, however, will often result in a polynomial of degree $m$
+or greater. Therefore, it is necessary to reduce the result modulo $f(x)$.
 
-First compute :math:`ab = (x + 2)(x + 1) = x^2 + 2`. Notice that :math:`x^2 + 2` has degree 2, but the elements of
-:math:`\mathrm{GF}(3^2)` can have degree at most 1. Therefore, reduction modulo :math:`f(x)` is required. After remainder
-division, we see that :math:`ab\ \equiv x\ \textrm{mod}\ f(x)`.
+First compute $ab = (x + 2)(x + 1) = x^2 + 2$. Notice that $x^2 + 2$ has degree 2, but the elements of
+$\mathrm{GF}(3^2)$ can have degree at most 1. Therefore, reduction modulo $f(x)$ is required. After remainder
+division, we see that $ab\ \equiv x\ \textrm{mod}\ f(x)$.
 
 .. ipython-with-reprs:: int,poly,power
    :name: GF9
@@ -165,16 +165,16 @@ Here is the entire multiplication table for completeness.
 Multiplicative inverse
 ......................
 
-As with prime fields, the division :math:`a(x) / b(x)` is reformulated into :math:`a(x) b(x)^{-1}`. So, first we must compute the multiplicative
-inverse :math:`b^{-1}` before continuing onto division.
+As with prime fields, the division $a(x) / b(x)$ is reformulated into $a(x) b(x)^{-1}$. So, first we must compute the multiplicative
+inverse $b^{-1}$ before continuing onto division.
 
 The `Extended Euclidean Algorithm <https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#:~:text=Extended%20Euclidean%20algorithm%20also%20refers,a%20and%20b%20are%20coprime.>`_,
-which was used in prime fields on integers, can be used for extension fields on polynomials. Given two polynomials :math:`a(x)` and
-:math:`b(x)`, the Extended Euclidean Algorithm finds the polynomials :math:`s(x)` and :math:`t(x)` such that
-:math:`a(x)s(x) + b(x)t(x) = \textrm{gcd}(a(x), b(x))`. This algorithm is implemented in :func:`~galois.egcd`.
+which was used in prime fields on integers, can be used for extension fields on polynomials. Given two polynomials $a(x)$ and
+$b(x)$, the Extended Euclidean Algorithm finds the polynomials $s(x)$ and $t(x)$ such that
+$a(x)s(x) + b(x)t(x) = \textrm{gcd}(a(x), b(x))$. This algorithm is implemented in :func:`~galois.egcd`.
 
-If :math:`a(x) = x + 1` is a field element of :math:`\mathrm{GF}(3^2)` and :math:`b(x) = f(x)` is the irreducible polynomial, then
-:math:`s(x) = a^{-1}` in :math:`\mathrm{GF}(3^2)`. Note, the GCD will always be 1 because :math:`f(x)` is irreducible.
+If $a(x) = x + 1$ is a field element of $\mathrm{GF}(3^2)$ and $b(x) = f(x)$ is the irreducible polynomial, then
+$s(x) = a^{-1}$ in $\mathrm{GF}(3^2)$. Note, the GCD will always be 1 because $f(x)$ is irreducible.
 
 .. ipython:: python
 
@@ -182,7 +182,7 @@ If :math:`a(x) = x + 1` is a field element of :math:`\mathrm{GF}(3^2)` and :math
    galois.egcd(b_poly, f)
 
 The :obj:`galois` library uses the Extended Euclidean Algorithm to compute multiplicative inverses (and division) in extension fields.
-The inverse of :math:`x + 1` in :math:`\mathrm{GF}(3^2)` can be easily computed in the following way.
+The inverse of $x + 1$ in $\mathrm{GF}(3^2)$ can be easily computed in the following way.
 
 .. ipython-with-reprs:: int,poly,power
    :name: GF9
@@ -193,10 +193,10 @@ The inverse of :math:`x + 1` in :math:`\mathrm{GF}(3^2)` can be easily computed
 Division
 ........
 
-Now let's return to division in finite fields. As mentioned earlier, :math:`a(x) / b(x)` is equivalent to :math:`a(x) b(x)^{-1}`, and we have
+Now let's return to division in finite fields. As mentioned earlier, $a(x) / b(x)$ is equivalent to $a(x) b(x)^{-1}$, and we have
 already learned multiplication and multiplicative inversion in finite fields.
 
-Let's compute :math:`a / b = (x + 2)(x + 1)^{-1}` in :math:`\mathrm{GF}(3^2)`.
+Let's compute $a / b = (x + 2)(x + 1)^{-1}$ in $\mathrm{GF}(3^2)$.
 
 .. ipython-with-reprs:: int,poly,power
    :name: GF9
@@ -206,7 +206,7 @@ Let's compute :math:`a / b = (x + 2)(x + 1)^{-1}` in :math:`\mathrm{GF}(3^2)`.
    a * b**-1
    a / b
 
-Here is the division table for completeness. Notice that division is not defined for :math:`y = 0`.
+Here is the division table for completeness. Notice that division is not defined for $y = 0$.
 
 .. ipython-with-reprs:: int,poly,power
    :name: GF9
@@ -218,9 +218,9 @@ Primitive elements
 
 A property of finite fields is that some elements produce the non-zero elements of the field by their powers.
 
-A *primitive element* :math:`g` of :math:`\mathrm{GF}(p^m)` is an element such that :math:`\mathrm{GF}(p^m) = \{0, 1, g, g^2, \dots, g^{p^m - 2}\}`.
-The non-zero elements :math:`\{1, g, g^2, \dots, g^{p^m - 2}\}` form the cyclic multiplicative group :math:`\mathrm{GF}(p^m)^{\times}`.
-A primitive element has multiplicative order :math:`\textrm{ord}(g) = p^m - 1`.
+A *primitive element* $g$ of $\mathrm{GF}(p^m)$ is an element such that $\mathrm{GF}(p^m) = \{0, 1, g, g^2, \dots, g^{p^m - 2}\}$.
+The non-zero elements $\{1, g, g^2, \dots, g^{p^m - 2}\}$ form the cyclic multiplicative group $\mathrm{GF}(p^m)^{\times}$.
+A primitive element has multiplicative order $\textrm{ord}(g) = p^m - 1$.
 
 A primitive element
 ...................
@@ -237,7 +237,7 @@ class property.
 The :obj:`galois` package allows you to easily display all powers of an element and their equivalent polynomial, vector, and integer
 representations using :func:`~galois.FieldArray.repr_table`.
 
-Here is the representation table using the default generator :math:`g = x`. Notice its multiplicative order is :math:`p^m - 1`.
+Here is the representation table using the default generator $g = x$. Notice its multiplicative order is $p^m - 1$.
 
 .. ipython:: python
 
@@ -256,12 +256,12 @@ There are multiple primitive elements of any finite field. All primitive element
    GF9.primitive_elements
    g = GF9("2x + 1"); g
 
-This means that :math:`x`, :math:`x + 2`, :math:`2x`, and :math:`2x + 1` all generate the multiplicative
-group :math:`\mathrm{GF}(3^2)^\times`. We can examine this by viewing the representation table using
+This means that $x$, $x + 2$, $2x$, and $2x + 1$ all generate the multiplicative
+group $\mathrm{GF}(3^2)^\times$. We can examine this by viewing the representation table using
 different generators.
 
-Here is the representation table using a different generator :math:`g = 2x + 1`. Notice it also has
-multiplicative order :math:`p^m - 1`.
+Here is the representation table using a different generator $g = 2x + 1$. Notice it also has
+multiplicative order $p^m - 1$.
 
 .. ipython:: python
 
@@ -272,10 +272,10 @@ Non-primitive elements
 ......................
 
 All other elements of the field cannot generate the multiplicative group. They have multiplicative
-orders less than :math:`p^m - 1`.
+orders less than $p^m - 1$.
 
-For example, the element :math:`e = x + 1` is not a primitive element. It has :math:`\textrm{ord}(e) = 4`.
-Notice elements :math:`x`, :math:`x + 2`, :math:`2x`, and :math:`2x + 1` are not represented by the powers of :math:`e`.
+For example, the element $e = x + 1$ is not a primitive element. It has $\textrm{ord}(e) = 4$.
+Notice elements $x$, $x + 2$, $2x$, and $2x + 1$ are not represented by the powers of $e$.
 
 .. ipython-with-reprs:: int,poly,power
    :name: GF9
diff --git a/docs/tutorials/intro-to-prime-fields.rst b/docs/tutorials/intro-to-prime-fields.rst
index a0e7287c8..5a4772278 100644
--- a/docs/tutorials/intro-to-prime-fields.rst
+++ b/docs/tutorials/intro-to-prime-fields.rst
@@ -13,14 +13,14 @@ element from the set. A *finite field* is a field with a finite set of elements.
 
    *- May 29, 1832 (two days before his death)*
 
-Galois proved that finite fields exist only when their *order* (or size of the set) is a prime power :math:`p^m`. Accordingly,
-finite fields can be broken into two categories: prime fields :math:`\mathrm{GF}(p)` and extension fields :math:`\mathrm{GF}(p^m)`.
+Galois proved that finite fields exist only when their *order* (or size of the set) is a prime power $p^m$. Accordingly,
+finite fields can be broken into two categories: prime fields $\mathrm{GF}(p)$ and extension fields $\mathrm{GF}(p^m)$.
 This tutorial will focus on prime fields.
 
 Prime field
 -----------
 
-In this tutorial, we will consider the prime field :math:`\mathrm{GF}(7)`. Using the :obj:`galois` library, the :obj:`~galois.FieldArray`
+In this tutorial, we will consider the prime field $\mathrm{GF}(7)$. Using the :obj:`galois` library, the :obj:`~galois.FieldArray`
 subclass `GF7` is created using the class factory :func:`~galois.GF`.
 
 .. ipython-with-reprs:: int,power
@@ -32,7 +32,7 @@ subclass `GF7` is created using the class factory :func:`~galois.GF`.
 .. info::
 
    In this tutorial, we suggest using the integer representation to display the elements. However, sometimes it is useful to view elements
-   in their power representation :math:`\{0, 1, \alpha, \alpha^2, \dots, \alpha^{p^m - 2}\}`. Switch the display between these two
+   in their power representation $\{0, 1, \alpha, \alpha^2, \dots, \alpha^{p^m - 2}\}$. Switch the display between these two
    representations using the tabbed sections. Note, the polynomial representation is not shown because it is identical to the integer
    representation for prime fields.
 
@@ -41,8 +41,8 @@ subclass `GF7` is created using the class factory :func:`~galois.GF`.
 Elements
 --------
 
-The elements of the finite field :math:`\mathrm{GF}(p)` are naturally represented as the integers
-:math:`\{0, 1, \dots, p - 1\}`.
+The elements of the finite field $\mathrm{GF}(p)$ are naturally represented as the integers
+$\{0, 1, \dots, p - 1\}$.
 
 The elements of the finite field are retrieved in a 1-D array using the :func:`~galois.FieldArray.Elements` classmethod.
 
@@ -54,13 +54,13 @@ The elements of the finite field are retrieved in a 1-D array using the :func:`~
 Arithmetic
 ----------
 
-Addition, subtraction, and multiplication in :math:`\mathrm{GF}(p)` is equivalent to integer addition, subtraction,
-and multiplication reduced modulo :math:`p`. Mathematically speaking, this is the integer ring :math:`\mathbb{Z} / p\mathbb{Z}`.
+Addition, subtraction, and multiplication in $\mathrm{GF}(p)$ is equivalent to integer addition, subtraction,
+and multiplication reduced modulo $p$. Mathematically speaking, this is the integer ring $\mathbb{Z} / p\mathbb{Z}$.
 
-In this tutorial, consider two field elements :math:`a = 3` and :math:`b = 5`. We will use :obj:`galois` to perform explicit modular
+In this tutorial, consider two field elements $a = 3$ and $b = 5$. We will use :obj:`galois` to perform explicit modular
 integer arithmetic and then prime field arithmetic.
 
-Here are :math:`a` and :math:`b` represented as Python integers.
+Here are $a$ and $b$ represented as Python integers.
 
 .. ipython:: python
 
@@ -68,7 +68,7 @@ Here are :math:`a` and :math:`b` represented as Python integers.
    b_int = 5
    p = GF7.characteristic; p
 
-Here are :math:`a` and :math:`b` represented as prime field elements. See :doc:`/basic-usage/array-creation` for more details.
+Here are $a$ and $b$ represented as prime field elements. See :doc:`/basic-usage/array-creation` for more details.
 
 .. ipython-with-reprs:: int,power
    :name: GF7
@@ -80,7 +80,7 @@ Here are :math:`a` and :math:`b` represented as prime field elements. See :doc:`
 Addition
 ........
 
-We can see that :math:`3 + 5 \equiv 1\ (\textrm{mod}\ 7)`. So accordingly, :math:`3 + 5 = 1` in :math:`\mathrm{GF}(7)`.
+We can see that $3 + 5 \equiv 1\ (\textrm{mod}\ 7)$. So accordingly, $3 + 5 = 1$ in $\mathrm{GF}(7)$.
 
 .. ipython-with-reprs:: int,power
    :name: GF7
@@ -101,7 +101,7 @@ answers are correct.
 Subtraction
 ...........
 
-As with addition, we can see that :math:`3 - 5 \equiv 5\ (\textrm{mod}\ 7)`. So accordingly, :math:`3 - 5 = 5` in :math:`\mathrm{GF}(7)`.
+As with addition, we can see that $3 - 5 \equiv 5\ (\textrm{mod}\ 7)$. So accordingly, $3 - 5 = 5$ in $\mathrm{GF}(7)$.
 
 .. ipython-with-reprs:: int,power
    :name: GF7
@@ -119,8 +119,8 @@ Here is the subtraction table for completeness.
 Multiplication
 ..............
 
-Similarly, we can see that :math:`3 \cdot 5 \equiv 1\ (\textrm{mod}\ 7)`. So accordingly, :math:`3 \cdot 5 = 1`
-in :math:`\mathrm{GF}(7)`.
+Similarly, we can see that $3 \cdot 5 \equiv 1\ (\textrm{mod}\ 7)$. So accordingly, $3 \cdot 5 = 1$
+in $\mathrm{GF}(7)$.
 
 .. ipython-with-reprs:: int,power
    :name: GF7
@@ -138,17 +138,17 @@ Here is the multiplication table for completeness.
 Multiplicative inverse
 ......................
 
-Division in :math:`\mathrm{GF}(p)` is a little more difficult. Division can't be as simple as taking :math:`a / b\ (\textrm{mod}\ p)` because
-many integer divisions do not result in integers! The division :math:`a / b` can be reformulated into :math:`a b^{-1}`, where :math:`b^{-1}`
-is the multiplicative inverse of :math:`b`. Let's first learn the multiplicative inverse before returning to division.
+Division in $\mathrm{GF}(p)$ is a little more difficult. Division can't be as simple as taking $a / b\ (\textrm{mod}\ p)$ because
+many integer divisions do not result in integers! The division $a / b$ can be reformulated into $a b^{-1}$, where $b^{-1}$
+is the multiplicative inverse of $b$. Let's first learn the multiplicative inverse before returning to division.
 
 `Euclid <https://en.wikipedia.org/wiki/Euclid>`_ discovered an efficient algorithm to solve the `Bézout Identity <https://en.wikipedia.org/wiki/B%C3%A9zout%27s_identity>`_,
 which is used to find the multiplicative inverse. It is now called the `Extended Euclidean Algorithm <https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#:~:text=Extended%20Euclidean%20algorithm%20also%20refers,a%20and%20b%20are%20coprime.>`_.
-Given two integers :math:`x` and :math:`y`, the Extended Euclidean Algorithm finds the integers :math:`s` and :math:`t` such that
-:math:`xs + yt = \textrm{gcd}(x, y)`. This algorithm is implemented in :func:`~galois.egcd`.
+Given two integers $x$ and $y$, the Extended Euclidean Algorithm finds the integers $s$ and $t$ such that
+$xs + yt = \textrm{gcd}(x, y)$. This algorithm is implemented in :func:`~galois.egcd`.
 
-If :math:`x = 5` is a field element of :math:`\mathrm{GF}(7)` and :math:`y = 7` is the prime characteristic, then :math:`s = x^{-1}`
-in :math:`\mathrm{GF}(7)`. Note, the GCD will always be 1 because :math:`y` is prime.
+If $x = 5$ is a field element of $\mathrm{GF}(7)$ and $y = 7$ is the prime characteristic, then $s = x^{-1}$
+in $\mathrm{GF}(7)$. Note, the GCD will always be 1 because $y$ is prime.
 
 .. ipython:: python
 
@@ -156,7 +156,7 @@ in :math:`\mathrm{GF}(7)`. Note, the GCD will always be 1 because :math:`y` is p
    galois.egcd(b_int, p)
 
 The :obj:`galois` library uses the Extended Euclidean Algorithm to compute multiplicative inverses (and division) in prime fields.
-The inverse of 5 in :math:`\mathrm{GF}(7)` can be easily computed in the following way.
+The inverse of 5 in $\mathrm{GF}(7)$ can be easily computed in the following way.
 
 .. ipython-with-reprs:: int,power
    :name: GF7
@@ -167,10 +167,10 @@ The inverse of 5 in :math:`\mathrm{GF}(7)` can be easily computed in the followi
 Division
 ........
 
-Now let's return to division in finite fields. As mentioned earlier, :math:`a / b` is equivalent to :math:`a b^{-1}`, and we have
+Now let's return to division in finite fields. As mentioned earlier, $a / b$ is equivalent to $a b^{-1}$, and we have
 already learned multiplication and multiplicative inversion in finite fields.
 
-To compute :math:`3 / 5` in :math:`\mathrm{GF}(7)`, we can equivalently compute :math:`3 \cdot 5^{-1}` in :math:`\mathrm{GF}(7)`.
+To compute $3 / 5$ in $\mathrm{GF}(7)$, we can equivalently compute $3 \cdot 5^{-1}$ in $\mathrm{GF}(7)$.
 
 .. ipython-with-reprs:: int,power
    :name: GF7
@@ -180,7 +180,7 @@ To compute :math:`3 / 5` in :math:`\mathrm{GF}(7)`, we can equivalently compute
    a * b**-1
    a / b
 
-Here is the division table for completeness. Notice that division is not defined for :math:`y = 0`.
+Here is the division table for completeness. Notice that division is not defined for $y = 0$.
 
 .. ipython-with-reprs:: int,power
    :name: GF7
@@ -192,20 +192,20 @@ Primitive elements
 
 A property of finite fields is that some elements produce the non-zero elements of the field by their powers.
 
-A *primitive element* :math:`g` of :math:`\mathrm{GF}(p)` is an element such that :math:`\mathrm{GF}(p) = \{0, 1, g, g^2, \dots, g^{p - 2}\}`.
-The non-zero elements :math:`\{1, g, g^2, \dots, g^{p - 2}\}` form the cyclic multiplicative group :math:`\mathrm{GF}(p)^{\times}`.
-A primitive element has multiplicative order :math:`\textrm{ord}(g) = p - 1`.
+A *primitive element* $g$ of $\mathrm{GF}(p)$ is an element such that $\mathrm{GF}(p) = \{0, 1, g, g^2, \dots, g^{p - 2}\}$.
+The non-zero elements $\{1, g, g^2, \dots, g^{p - 2}\}$ form the cyclic multiplicative group $\mathrm{GF}(p)^{\times}$.
+A primitive element has multiplicative order $\textrm{ord}(g) = p - 1$.
 
-In prime fields :math:`\mathrm{GF}(p)`, the generators or primitive elements of :math:`\mathrm{GF}(p)` are *primitive roots mod p*.
+In prime fields $\mathrm{GF}(p)$, the generators or primitive elements of $\mathrm{GF}(p)$ are *primitive roots mod p*.
 
-Primitive roots mod :math:`p`
+Primitive roots mod $p$
 .............................
 
-An integer :math:`g` is a *primitive root mod p* if every number coprime to :math:`p` can be represented as a power of :math:`g`
-mod :math:`p`. Namely, every :math:`a` coprime to :math:`p` can be represented as :math:`g^k \equiv a\ (\textrm{mod}\ p)` for some :math:`k`.
-In prime fields, since :math:`p` is prime, every integer :math:`1 \le a < p` is coprime to :math:`p`.
+An integer $g$ is a *primitive root mod p* if every number coprime to $p$ can be represented as a power of $g$
+mod $p$. Namely, every $a$ coprime to $p$ can be represented as $g^k \equiv a\ (\textrm{mod}\ p)$ for some $k$.
+In prime fields, since $p$ is prime, every integer $1 \le a < p$ is coprime to $p$.
 
-Finding primitive roots mod :math:`p` is implemented in :func:`~galois.primitive_root` and :func:`~galois.primitive_roots`.
+Finding primitive roots mod $p$ is implemented in :func:`~galois.primitive_root` and :func:`~galois.primitive_roots`.
 
 .. ipython:: python
 
@@ -227,7 +227,7 @@ The :obj:`galois` package allows you to easily display all powers of an element
 representations using :func:`~galois.FieldArray.repr_table`. Let's ignore the polynomial and vector representations for now.
 They will become useful for extension fields.
 
-Here is the representation table using the default generator :math:`g = 3`. Notice its multiplicative order is :math:`p - 1`.
+Here is the representation table using the default generator $g = 3$. Notice its multiplicative order is $p - 1$.
 
 .. ipython:: python
 
@@ -247,11 +247,11 @@ There are multiple primitive elements of any finite field. All primitive element
    GF7.primitive_elements
    g = GF7(5); g
 
-This means that 3 and 5 generate the multiplicative group :math:`\mathrm{GF}(7)^\times`.
+This means that 3 and 5 generate the multiplicative group $\mathrm{GF}(7)^\times$.
 We can examine this by viewing the representation table using different generators.
 
-Here is the representation table using a different generator :math:`g = 5`. Notice it also has
-multiplicative order :math:`p- 1`.
+Here is the representation table using a different generator $g = 5$. Notice it also has
+multiplicative order $p- 1$.
 
 .. ipython:: python
 
@@ -262,16 +262,16 @@ Non-primitive elements
 ......................
 
 All other elements of the field cannot generate the multiplicative group. They have multiplicative
-orders less than :math:`p - 1`.
+orders less than $p - 1$.
 
-For example, the element :math:`e = 2` is not a primitive element.
+For example, the element $e = 2$ is not a primitive element.
 
 .. ipython-with-reprs:: int,power
    :name: GF7
 
    e = GF7(2); e
 
-It has :math:`\textrm{ord}(e) = 3`. Notice elements 3, 5, and 6 are not represented by the powers of :math:`e`.
+It has $\textrm{ord}(e) = 3$. Notice elements 3, 5, and 6 are not represented by the powers of $e$.
 
 .. ipython:: python
 
diff --git a/pyproject.toml b/pyproject.toml
index c5d34f01a..9cc952097 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,18 +1,12 @@
 [build-system]
-requires = [
-    "setuptools >= 62",
-    "wheel",
-    "setuptools_scm[toml] >= 6.2"
-]
+requires = ["setuptools >= 62", "wheel", "setuptools_scm[toml] >= 6.2"]
 
 [project]
 name = "galois"
-authors = [
-  {name = "Matt Hostetter", email = "matthostetter@gmail.com"},
-]
+authors = [{ name = "Matt Hostetter", email = "matthostetter@gmail.com" }]
 description = "A performant NumPy extension for Galois fields and their applications"
 readme = "README.md"
-license = {text = "MIT"}
+license = { text = "MIT" }
 keywords = [
     "aes",
     "bch",
@@ -55,9 +49,9 @@ classifiers = [
 ]
 requires-python = ">=3.7"
 dependencies = [
-    "numpy >= 1.21.0, < 1.25",  # v1.21.0 is needed for dtype support of ufuncs, see https://numpy.org/devdocs/release/1.21.0-notes.html#ufunc-signature-and-dtype-generalization-and-casting
-    "numba >= 0.55, < 0.58",  # v0.55 is needed for support of NumPy 1.21
-    "typing_extensions >= 4.0.0",  # v4.0.0 is needed for use of Self (Python 3.11+) and Literal (Python 3.8+)
+    "numpy >= 1.21.0, < 1.26",    # v1.21.0 is needed for dtype support of ufuncs, see https://numpy.org/devdocs/release/1.21.0-notes.html#ufunc-signature-and-dtype-generalization-and-casting
+    "numba >= 0.55, < 0.59",      # v0.55 is needed for support of NumPy 1.21
+    "typing_extensions >= 4.0.0", # v4.0.0 is needed for use of Self (Python 3.11+) and Literal (Python 3.8+)
 ]
 dynamic = ["version"]
 
@@ -70,7 +64,7 @@ dev = [
     "pytest-xdist",
     "pytest-benchmark >= 4.0.0",
     "requests",
-    "pdfminer.six"
+    "pdfminer.six",
 ]
 
 [project.urls]
@@ -96,11 +90,9 @@ where = ["src"]
 universal = false
 
 [tool.pylint]
-ignore-paths = [
-    "src/galois/_version.py",
-]
+ignore-paths = ["src/galois/_version.py"]
 disable = [
-    "comparison-with-callable",  # pylint doesn't understand metaclass properties
+    "comparison-with-callable",     # pylint doesn't understand metaclass properties
     "fixme",
     "global-statement",
     "invalid-name",
@@ -134,9 +126,7 @@ profile = "black"
 [tool.pytest.ini_options]
 minversion = "6.2"
 addopts = "-s --showlocals"
-testpaths = [
-    "tests"
-]
+testpaths = ["tests"]
 
 [tool.coverage.report]
 exclude_lines = [
diff --git a/src/galois/_codes/_bch.py b/src/galois/_codes/_bch.py
index 2675eda4a..4df4cffe7 100644
--- a/src/galois/_codes/_bch.py
+++ b/src/galois/_codes/_bch.py
@@ -24,29 +24,27 @@
 @export
 class BCH(_CyclicCode):
     r"""
-    A general :math:`\textrm{BCH}(n, k)` code over :math:`\mathrm{GF}(q)`.
+    A general $\textrm{BCH}(n, k)$ code over $\mathrm{GF}(q)$.
 
-    A :math:`\textrm{BCH}(n, k)` code is a :math:`[n, k, d]_q` linear block code with codeword size :math:`n`, message
-    size :math:`k`, minimum distance :math:`d`, and symbols taken from an alphabet of size :math:`q`.
+    A $\textrm{BCH}(n, k)$ code is a $[n, k, d]_q$ linear block code with codeword size $n$, message
+    size $k$, minimum distance $d$, and symbols taken from an alphabet of size $q$.
 
     .. info::
         :title: Shortened codes
 
-        To create the shortened :math:`\textrm{BCH}(n-s, k-s)` code, construct the full-sized
-        :math:`\textrm{BCH}(n, k)` code and then pass :math:`k-s` symbols into :func:`encode` and :math:`n-s` symbols
+        To create the shortened $\textrm{BCH}(n-s, k-s)$ code, construct the full-sized
+        $\textrm{BCH}(n, k)$ code and then pass $k-s$ symbols into :func:`encode` and $n-s$ symbols
         into :func:`decode()`. Shortened codes are only applicable for systematic codes.
 
-    A BCH code is a cyclic code over :math:`\mathrm{GF}(q)` with generator polynomial :math:`g(x)`. The generator
-    polynomial is over :math:`\mathrm{GF}(q)` and has :math:`d-1` roots :math:`\alpha^c, \dots, \alpha^{c+d-2}` when
-    evaluated in :math:`\mathrm{GF}(q^m)`. The element :math:`\alpha` is a primitive :math:`n`-th root of unity in
-    :math:`\mathrm{GF}(q^m)`.
+    A BCH code is a cyclic code over $\mathrm{GF}(q)$ with generator polynomial $g(x)$. The generator
+    polynomial is over $\mathrm{GF}(q)$ and has $d-1$ roots $\alpha^c, \dots, \alpha^{c+d-2}$ when
+    evaluated in $\mathrm{GF}(q^m)$. The element $\alpha$ is a primitive $n$-th root of unity in
+    $\mathrm{GF}(q^m)$.
 
-    .. math::
-
-        g(x) = \textrm{LCM}(m_{\alpha^c}(x), \dots, m_{\alpha^{c+d-2}}(x))
+    $$g(x) = \textrm{LCM}(m_{\alpha^c}(x), \dots, m_{\alpha^{c+d-2}}(x))$$
 
     Examples:
-        Construct a binary :math:`\textrm{BCH}(15, 7)` code.
+        Construct a binary $\textrm{BCH}(15, 7)$ code.
 
         .. ipython:: python
 
@@ -93,29 +91,29 @@ def __init__(
         systematic: bool = True,
     ):
         r"""
-        Constructs a general :math:`\textrm{BCH}(n, k)` code over :math:`\mathrm{GF}(q)`.
+        Constructs a general $\textrm{BCH}(n, k)$ code over $\mathrm{GF}(q)$.
 
         Arguments:
-            n: The codeword size :math:`n`. If :math:`n = q^m - 1`, the BCH code is *primitive*.
-            k: The message size :math:`k`.
+            n: The codeword size $n$. If $n = q^m - 1$, the BCH code is *primitive*.
+            k: The message size $k$.
 
                 .. important::
                     Either `k` or `d` must be provided to define the code. Both may be provided as long as they are
                     consistent.
 
-            d: The design distance :math:`d`. This defines the number of roots :math:`d - 1` in the generator
-                polynomial :math:`g(x)` over :math:`\mathrm{GF}(q^m)`.
-            field: The Galois field :math:`\mathrm{GF}(q)` that defines the alphabet of the codeword symbols.
-                The default is `None` which corresponds to :math:`\mathrm{GF}(2)`.
-            extension_field: The Galois field :math:`\mathrm{GF}(q^m)` that defines the syndrome arithmetic.
-                The default is `None` which corresponds to :math:`\mathrm{GF}(q^m)` where
-                :math:`q^{m - 1} \le n < q^m`. The default extension field will use `matlab_primitive_poly(q, m)`
+            d: The design distance $d$. This defines the number of roots $d - 1$ in the generator
+                polynomial $g(x)$ over $\mathrm{GF}(q^m)$.
+            field: The Galois field $\mathrm{GF}(q)$ that defines the alphabet of the codeword symbols.
+                The default is `None` which corresponds to $\mathrm{GF}(2)$.
+            extension_field: The Galois field $\mathrm{GF}(q^m)$ that defines the syndrome arithmetic.
+                The default is `None` which corresponds to $\mathrm{GF}(q^m)$ where
+                $q^{m - 1} \le n < q^m$. The default extension field will use `matlab_primitive_poly(q, m)`
                 for the irreducible polynomial.
-            alpha: A primitive :math:`n`-th root of unity :math:`\alpha` in :math:`\mathrm{GF}(q^m)` that defines the
-                :math:`\alpha^c, \dots, \alpha^{c+d-2}` roots of the generator polynomial :math:`g(x)`.
-            c: The first consecutive power :math:`c` of :math:`\alpha` that defines the
-                :math:`\alpha^c, \dots, \alpha^{c+d-2}` roots of the generator polynomial :math:`g(x)`.
-                The default is 1. If :math:`c = 1`, the BCH code is *narrow-sense*.
+            alpha: A primitive $n$-th root of unity $\alpha$ in $\mathrm{GF}(q^m)$ that defines the
+                $\alpha^c, \dots, \alpha^{c+d-2}$ roots of the generator polynomial $g(x)$.
+            c: The first consecutive power $c$ of $\alpha$ that defines the
+                $\alpha^c, \dots, \alpha^{c+d-2}$ roots of the generator polynomial $g(x)$.
+                The default is 1. If $c = 1$, the BCH code is *narrow-sense*.
             systematic: Indicates if the encoding should be systematic, meaning the codeword is the message with
                 parity appended. The default is `True`.
 
@@ -123,7 +121,7 @@ def __init__(
             matlab_primitive_poly, FieldArray.primitive_root_of_unity
 
         Examples:
-            Construct a binary primitive, narrow-sense :math:`\textrm{BCH}(15, 7)` code.
+            Construct a binary primitive, narrow-sense $\textrm{BCH}(15, 7)$ code.
 
             .. ipython:: python
 
@@ -131,7 +129,7 @@ def __init__(
                 galois.BCH(15, d=5)
                 galois.BCH(15, 7, 5)
 
-            Construct a primitive, narrow-sense :math:`\textrm{BCH}(26, 17)` code over :math:`\mathrm{GF}(3)`.
+            Construct a primitive, narrow-sense $\textrm{BCH}(26, 17)$ code over $\mathrm{GF}(3)$.
 
             .. ipython:: python
 
@@ -140,7 +138,7 @@ def __init__(
                 galois.BCH(26, d=5, field=GF)
                 galois.BCH(26, 17, 5, field=GF)
 
-            Construct a non-primitive, narrow-sense :math:`\textrm{BCH}(13, 4)` code over :math:`\mathrm{GF}(3)`.
+            Construct a non-primitive, narrow-sense $\textrm{BCH}(13, 4)$ code over $\mathrm{GF}(3)$.
 
             .. ipython:: python
 
@@ -149,7 +147,7 @@ def __init__(
                 galois.BCH(13, d=7, field=GF)
                 galois.BCH(13, 4, 7, field=GF)
 
-            Discover primitive BCH codes over :math:`\mathrm{GF}(5)` by looping over the design distance.
+            Discover primitive BCH codes over $\mathrm{GF}(5)$ by looping over the design distance.
 
             .. ipython:: python
 
@@ -222,21 +220,21 @@ def __repr__(self) -> str:
         A terse representation of the BCH code.
 
         Examples:
-            Construct a binary primitive :math:`\textrm{BCH}(15, 7)` code.
+            Construct a binary primitive $\textrm{BCH}(15, 7)$ code.
 
             .. ipython:: python
 
                 bch = galois.BCH(15, 7)
                 bch
 
-            Construct a primitive :math:`\textrm{BCH}(26, 14)` code over :math:`\mathrm{GF}(3)`.
+            Construct a primitive $\textrm{BCH}(26, 14)$ code over $\mathrm{GF}(3)$.
 
             .. ipython:: python
 
                 bch = galois.BCH(26, 14, field=galois.GF(3))
                 bch
 
-            Construct a non-primitive :math:`\textrm{BCH}(13, 4)` code over :math:`\mathrm{GF}(3)`.
+            Construct a non-primitive $\textrm{BCH}(13, 4)$ code over $\mathrm{GF}(3)$.
 
             .. ipython:: python
 
@@ -250,21 +248,21 @@ def __str__(self) -> str:
         A formatted string with relevant properties of the BCH code.
 
         Examples:
-            Construct a binary primitive :math:`\textrm{BCH}(15, 7)` code.
+            Construct a binary primitive $\textrm{BCH}(15, 7)$ code.
 
             .. ipython:: python
 
                 bch = galois.BCH(15, 7)
                 print(bch)
 
-            Construct a primitive :math:`\textrm{BCH}(26, 14)` code over :math:`\mathrm{GF}(3)`.
+            Construct a primitive $\textrm{BCH}(26, 14)$ code over $\mathrm{GF}(3)$.
 
             .. ipython:: python
 
                 bch = galois.BCH(26, 14, field=galois.GF(3))
                 print(bch)
 
-            Construct a non-primitive :math:`\textrm{BCH}(13, 4)` code over :math:`\mathrm{GF}(3)`.
+            Construct a non-primitive $\textrm{BCH}(13, 4)$ code over $\mathrm{GF}(3)$.
 
             .. ipython:: python
 
@@ -291,7 +289,7 @@ def __str__(self) -> str:
 
                 .. md-tab-item:: Vector
 
-                    Encode a single message using the :math:`\textrm{BCH}(15, 7)` code.
+                    Encode a single message using the $\textrm{BCH}(15, 7)$ code.
 
                     .. ipython:: python
 
@@ -308,7 +306,7 @@ def __str__(self) -> str:
 
                 .. md-tab-item:: Vector (shortened)
 
-                    Encode a single message using the shortened :math:`\textrm{BCH}(12, 4)` code.
+                    Encode a single message using the shortened $\textrm{BCH}(12, 4)$ code.
 
                     .. ipython:: python
 
@@ -325,7 +323,7 @@ def __str__(self) -> str:
 
                 .. md-tab-item:: Matrix
 
-                    Encode a matrix of three messages using the :math:`\textrm{BCH}(15, 7)` code.
+                    Encode a matrix of three messages using the $\textrm{BCH}(15, 7)$ code.
 
                     .. ipython:: python
 
@@ -342,7 +340,7 @@ def __str__(self) -> str:
 
                 .. md-tab-item:: Matrix (shortened)
 
-                    Encode a matrix of three messages using the shortened :math:`\textrm{BCH}(12, 4)` code.
+                    Encode a matrix of three messages using the shortened $\textrm{BCH}(12, 4)$ code.
 
                     .. ipython:: python
 
@@ -370,7 +368,7 @@ def encode(self, message: ArrayLike, output: Literal["codeword", "parity"] = "co
 
                 .. md-tab-item:: Vector
 
-                    Encode a single message using the :math:`\textrm{BCH}(15, 7)` code.
+                    Encode a single message using the $\textrm{BCH}(15, 7)$ code.
 
                     .. ipython:: python
 
@@ -385,7 +383,7 @@ def encode(self, message: ArrayLike, output: Literal["codeword", "parity"] = "co
 
                         bch.detect(c)
 
-                    Detect :math:`d_{min}-1` errors in the codeword.
+                    Detect $d_{min}-1$ errors in the codeword.
 
                     .. ipython:: python
 
@@ -395,7 +393,7 @@ def encode(self, message: ArrayLike, output: Literal["codeword", "parity"] = "co
 
                 .. md-tab-item:: Vector (shortened)
 
-                    Encode a single message using the shortened :math:`\textrm{BCH}(12, 4)` code.
+                    Encode a single message using the shortened $\textrm{BCH}(12, 4)$ code.
 
                     .. ipython:: python
 
@@ -410,7 +408,7 @@ def encode(self, message: ArrayLike, output: Literal["codeword", "parity"] = "co
 
                         bch.detect(c)
 
-                    Detect :math:`d_{min}-1` errors in the codeword.
+                    Detect $d_{min}-1$ errors in the codeword.
 
                     .. ipython:: python
 
@@ -420,7 +418,7 @@ def encode(self, message: ArrayLike, output: Literal["codeword", "parity"] = "co
 
                 .. md-tab-item:: Matrix
 
-                    Encode a matrix of three messages using the :math:`\textrm{BCH}(15, 7)` code.
+                    Encode a matrix of three messages using the $\textrm{BCH}(15, 7)$ code.
 
                     .. ipython:: python
 
@@ -435,7 +433,7 @@ def encode(self, message: ArrayLike, output: Literal["codeword", "parity"] = "co
 
                         bch.detect(c)
 
-                    Detect one, two, and :math:`d_{min}-1` errors in the codewords.
+                    Detect one, two, and $d_{min}-1$ errors in the codewords.
 
                     .. ipython:: python
 
@@ -448,7 +446,7 @@ def encode(self, message: ArrayLike, output: Literal["codeword", "parity"] = "co
 
                 .. md-tab-item:: Matrix (shortened)
 
-                    Encode a matrix of three messages using the shortened :math:`\textrm{BCH}(12, 4)` code.
+                    Encode a matrix of three messages using the shortened $\textrm{BCH}(12, 4)$ code.
 
                     .. ipython:: python
 
@@ -463,7 +461,7 @@ def encode(self, message: ArrayLike, output: Literal["codeword", "parity"] = "co
 
                         bch.detect(c)
 
-                    Detect one, two, and :math:`d_{min}-1` errors in the codewords.
+                    Detect one, two, and $d_{min}-1$ errors in the codewords.
 
                     .. ipython:: python
 
@@ -501,32 +499,30 @@ def decode(
         _CyclicCode.decode,
         {},
         r"""
-        In decoding, the syndrome vector :math:`\mathbf{s}` is computed by evaluating the received codeword
-        :math:`\mathbf{r}` in the extension field :math:`\mathrm{GF}(q^m)` at the roots
-        :math:`\alpha^c, \dots, \alpha^{c+d-2}` of the generator polynomial :math:`g(x)`. The equivalent polynomial
-        operation computes the remainder of :math:`r(x)` by :math:`g(x)` in the extension field
-        :math:`\mathrm{GF}(q^m)`.
+        In decoding, the syndrome vector $\mathbf{s}$ is computed by evaluating the received codeword
+        $\mathbf{r}$ in the extension field $\mathrm{GF}(q^m)$ at the roots
+        $\alpha^c, \dots, \alpha^{c+d-2}$ of the generator polynomial $g(x)$. The equivalent polynomial
+        operation computes the remainder of $r(x)$ by $g(x)$ in the extension field
+        $\mathrm{GF}(q^m)$.
 
-        .. math::
-            \mathbf{s} = [r(\alpha^c),\ \dots,\ r(\alpha^{c+d-2})] \in \mathrm{GF}(q^m)^{d-1}
+        $$\mathbf{s} = [r(\alpha^c),\ \dots,\ r(\alpha^{c+d-2})] \in \mathrm{GF}(q^m)^{d-1}$$
 
-        .. math::
-            s(x) = r(x)\ \textrm{mod}\ g(x) \in \mathrm{GF}(q^m)[x]
+        $$s(x) = r(x)\ \textrm{mod}\ g(x) \in \mathrm{GF}(q^m)[x]$$
 
         A syndrome of zeros indicates the received codeword is a valid codeword and there are no errors. If the
-        syndrome is non-zero, the decoder will find an error-locator polynomial :math:`\sigma(x)` and the corresponding
+        syndrome is non-zero, the decoder will find an error-locator polynomial $\sigma(x)$ and the corresponding
         error locations and values.
 
         Note:
-            The :math:`[n, k, d]_q` code has :math:`d_{min} \ge d` minimum distance. It can detect up
-            to :math:`d_{min}-1` errors.
+            The $[n, k, d]_q$ code has $d_{min} \ge d$ minimum distance. It can detect up
+            to $d_{min}-1$ errors.
 
         Examples:
             .. md-tab-set::
 
                 .. md-tab-item:: Vector
 
-                    Encode a single message using the :math:`\textrm{BCH}(15, 7)` code.
+                    Encode a single message using the $\textrm{BCH}(15, 7)$ code.
 
                     .. ipython:: python
 
@@ -535,7 +531,7 @@ def decode(
                         m = GF.Random(bch.k); m
                         c = bch.encode(m); c
 
-                    Corrupt :math:`t` symbols of the codeword.
+                    Corrupt $t$ symbols of the codeword.
 
                     .. ipython:: python
 
@@ -558,7 +554,7 @@ def decode(
 
                 .. md-tab-item:: Vector (shortened)
 
-                    Encode a single message using the shortened :math:`\textrm{BCH}(12, 4)` code.
+                    Encode a single message using the shortened $\textrm{BCH}(12, 4)$ code.
 
                     .. ipython:: python
 
@@ -567,7 +563,7 @@ def decode(
                         m = GF.Random(bch.k - 3); m
                         c = bch.encode(m); c
 
-                    Corrupt :math:`t` symbols of the codeword.
+                    Corrupt $t$ symbols of the codeword.
 
                     .. ipython:: python
 
@@ -590,7 +586,7 @@ def decode(
 
                 .. md-tab-item:: Matrix
 
-                    Encode a matrix of three messages using the :math:`\textrm{BCH}(15, 7)` code.
+                    Encode a matrix of three messages using the $\textrm{BCH}(15, 7)$ code.
 
                     .. ipython:: python
 
@@ -624,7 +620,7 @@ def decode(
 
                 .. md-tab-item:: Matrix (shortened)
 
-                    Encode a matrix of three messages using the shortened :math:`\textrm{BCH}(12, 4)` code.
+                    Encode a matrix of three messages using the shortened $\textrm{BCH}(12, 4)$ code.
 
                     .. ipython:: python
 
@@ -672,7 +668,7 @@ def _decode_codeword(self, codeword: FieldArray) -> tuple[FieldArray, np.ndarray
         {},
         r"""
         Examples:
-            Construct a binary :math:`\textrm{BCH}(15, 7)` code.
+            Construct a binary $\textrm{BCH}(15, 7)$ code.
 
             .. ipython:: python
 
@@ -680,7 +676,7 @@ def _decode_codeword(self, codeword: FieldArray) -> tuple[FieldArray, np.ndarray
                 bch.field
                 print(bch.field.properties)
 
-            Construct a :math:`\textrm{BCH}(26, 14)` code over :math:`\mathrm{GF}(3)`.
+            Construct a $\textrm{BCH}(26, 14)$ code over $\mathrm{GF}(3)$.
 
             .. ipython:: python
 
@@ -695,10 +691,10 @@ def field(self) -> Type[FieldArray]:
     @property
     def extension_field(self) -> Type[FieldArray]:
         r"""
-        The Galois field :math:`\mathrm{GF}(q^m)` that defines the BCH syndrome arithmetic.
+        The Galois field $\mathrm{GF}(q^m)$ that defines the BCH syndrome arithmetic.
 
         Examples:
-            Construct a binary :math:`\textrm{BCH}(15, 7)` code.
+            Construct a binary $\textrm{BCH}(15, 7)$ code.
 
             .. ipython:: python
 
@@ -706,7 +702,7 @@ def extension_field(self) -> Type[FieldArray]:
                 bch.extension_field
                 print(bch.extension_field.properties)
 
-            Construct a :math:`\textrm{BCH}(26, 14)` code over :math:`\mathrm{GF}(3)`.
+            Construct a $\textrm{BCH}(26, 14)$ code over $\mathrm{GF}(3)$.
 
             .. ipython:: python
 
@@ -721,14 +717,14 @@ def extension_field(self) -> Type[FieldArray]:
         {},
         r"""
         Examples:
-            Construct a binary :math:`\textrm{BCH}(15, 7)` code.
+            Construct a binary $\textrm{BCH}(15, 7)$ code.
 
             .. ipython:: python
 
                 bch = galois.BCH(15, 7); bch
                 bch.n
 
-            Construct a :math:`\textrm{BCH}(26, 14)` code over :math:`\mathrm{GF}(3)`.
+            Construct a $\textrm{BCH}(26, 14)$ code over $\mathrm{GF}(3)$.
 
             .. ipython:: python
 
@@ -745,14 +741,14 @@ def n(self) -> int:
         {},
         r"""
         Examples:
-            Construct a binary :math:`\textrm{BCH}(15, 7)` code.
+            Construct a binary $\textrm{BCH}(15, 7)$ code.
 
             .. ipython:: python
 
                 bch = galois.BCH(15, 7); bch
                 bch.k
 
-            Construct a :math:`\textrm{BCH}(26, 14)` code over :math:`\mathrm{GF}(3)`.
+            Construct a $\textrm{BCH}(26, 14)$ code over $\mathrm{GF}(3)$.
 
             .. ipython:: python
 
@@ -769,17 +765,17 @@ def k(self) -> int:
         {},
         r"""
         Notes:
-            The minimum distance of a BCH code may be greater than the design distance, i.e. :math:`d_{min} \ge d`.
+            The minimum distance of a BCH code may be greater than the design distance, i.e. $d_{min} \ge d$.
 
         Examples:
-            Construct a binary :math:`\textrm{BCH}(15, 7)` code.
+            Construct a binary $\textrm{BCH}(15, 7)$ code.
 
             .. ipython:: python
 
                 bch = galois.BCH(15, 7); bch
                 bch.d
 
-            Construct a :math:`\textrm{BCH}(26, 14)` code over :math:`\mathrm{GF}(3)`.
+            Construct a $\textrm{BCH}(26, 14)$ code over $\mathrm{GF}(3)$.
 
             .. ipython:: python
 
@@ -796,14 +792,14 @@ def d(self) -> int:
         {},
         r"""
         Examples:
-            Construct a binary :math:`\textrm{BCH}(15, 7)` code.
+            Construct a binary $\textrm{BCH}(15, 7)$ code.
 
             .. ipython:: python
 
                 bch = galois.BCH(15, 7); bch
                 bch.t
 
-            Construct a :math:`\textrm{BCH}(26, 14)` code over :math:`\mathrm{GF}(3)`.
+            Construct a $\textrm{BCH}(26, 14)$ code over $\mathrm{GF}(3)$.
 
             .. ipython:: python
 
@@ -820,8 +816,8 @@ def t(self) -> int:
         {},
         r"""
         Examples:
-            Construct a binary narrow-sense :math:`\textrm{BCH}(15, 7)` code with first consecutive root
-            :math:`\alpha`.
+            Construct a binary narrow-sense $\textrm{BCH}(15, 7)$ code with first consecutive root
+            $\alpha$.
 
             .. ipython:: python
 
@@ -831,9 +827,9 @@ def t(self) -> int:
                 # Evaluate the generator polynomial at its roots in GF(q^m)
                 bch.generator_poly(bch.roots, field=bch.extension_field)
 
-            Construct a binary non-narrow-sense :math:`\textrm{BCH}(15, 7)` code with first consecutive root
-            :math:`\alpha^3`. Notice the design distance of this code is only 3 and it only has 2 roots
-            in :math:`\mathrm{GF}(2^4)`.
+            Construct a binary non-narrow-sense $\textrm{BCH}(15, 7)$ code with first consecutive root
+            $\alpha^3$. Notice the design distance of this code is only 3 and it only has 2 roots
+            in $\mathrm{GF}(2^4)$.
 
             .. ipython:: python
 
@@ -853,7 +849,7 @@ def generator_poly(self) -> Poly:
         {},
         r"""
         Examples:
-            Construct a binary primitive :math:`\textrm{BCH}(15, 7)` code.
+            Construct a binary primitive $\textrm{BCH}(15, 7)$ code.
 
             .. ipython:: python
 
@@ -861,7 +857,7 @@ def generator_poly(self) -> Poly:
                 bch.parity_check_poly
                 bch.H
 
-            Construct a non-primitive :math:`\textrm{BCH}(13, 4)` code over :math:`\mathrm{GF}(3)`.
+            Construct a non-primitive $\textrm{BCH}(13, 4)$ code over $\mathrm{GF}(3)$.
 
             .. ipython:: python
 
@@ -878,11 +874,11 @@ def parity_check_poly(self) -> Poly:
         _CyclicCode.roots,
         {},
         r"""
-        These are consecutive powers of :math:`\alpha^c`, specifically :math:`\alpha^c, \dots, \alpha^{c+d-2}`.
+        These are consecutive powers of $\alpha^c$, specifically $\alpha^c, \dots, \alpha^{c+d-2}$.
 
         Examples:
-            Construct a binary narrow-sense :math:`\textrm{BCH}(15, 7)` code with first consecutive root
-            :math:`\alpha`.
+            Construct a binary narrow-sense $\textrm{BCH}(15, 7)$ code with first consecutive root
+            $\alpha$.
 
             .. ipython:: python
 
@@ -892,9 +888,9 @@ def parity_check_poly(self) -> Poly:
                 # Evaluate the generator polynomial at its roots in GF(q^m)
                 bch.generator_poly(bch.roots, field=bch.extension_field)
 
-            Construct a binary non-narrow-sense :math:`\textrm{BCH}(15, 7)` code with first consecutive root
-            :math:`\alpha^3`. Notice the design distance of this code is only 3 and it only has 2 roots
-            in :math:`\mathrm{GF}(2^4)`.
+            Construct a binary non-narrow-sense $\textrm{BCH}(15, 7)$ code with first consecutive root
+            $\alpha^3$. Notice the design distance of this code is only 3 and it only has 2 roots
+            in $\mathrm{GF}(2^4)$.
 
             .. ipython:: python
 
@@ -912,12 +908,12 @@ def roots(self) -> FieldArray:
     @property
     def alpha(self) -> FieldArray:
         r"""
-        A primitive :math:`n`-th root of unity :math:`\alpha` in :math:`\mathrm{GF}(q^m)` whose consecutive powers
-        :math:`\alpha^c, \dots, \alpha^{c+d-2}` are roots of the generator polynomial :math:`g(x)`
-        in :math:`\mathrm{GF}(q^m)`.
+        A primitive $n$-th root of unity $\alpha$ in $\mathrm{GF}(q^m)$ whose consecutive powers
+        $\alpha^c, \dots, \alpha^{c+d-2}$ are roots of the generator polynomial $g(x)$
+        in $\mathrm{GF}(q^m)$.
 
         Examples:
-            Construct a binary primitive :math:`\textrm{BCH}(15, 7)` code.
+            Construct a binary primitive $\textrm{BCH}(15, 7)$ code.
 
             .. ipython:: python
 
@@ -926,7 +922,7 @@ def alpha(self) -> FieldArray:
                 bch.roots[0] == bch.alpha ** bch.c
                 bch.alpha.multiplicative_order() == bch.n
 
-            Construct a non-primitive :math:`\textrm{BCH}(13, 7)` code over :math:`\mathrm{GF}(3)`.
+            Construct a non-primitive $\textrm{BCH}(13, 7)$ code over $\mathrm{GF}(3)$.
 
             .. ipython:: python
 
@@ -946,12 +942,12 @@ def alpha(self) -> FieldArray:
     @property
     def c(self) -> int:
         r"""
-        The first consecutive power :math:`c` of :math:`\alpha` that defines the roots
-        :math:`\alpha^c, \dots, \alpha^{c+d-2}` of the generator polynomial :math:`g(x)`.
+        The first consecutive power $c$ of $\alpha$ that defines the roots
+        $\alpha^c, \dots, \alpha^{c+d-2}$ of the generator polynomial $g(x)$.
 
         Examples:
-            Construct a binary narrow-sense :math:`\textrm{BCH}(15, 7)` code with first consecutive root
-            :math:`\alpha`.
+            Construct a binary narrow-sense $\textrm{BCH}(15, 7)$ code with first consecutive root
+            $\alpha$.
 
             .. ipython:: python
 
@@ -959,8 +955,8 @@ def c(self) -> int:
                 bch.c
                 bch.roots[0] == bch.alpha ** bch.c
 
-            Construct a binary non-narrow-sense :math:`\textrm{BCH}(15, 7)` code with first consecutive root
-            :math:`\alpha^3`. Notice the design distance of this code is only 3.
+            Construct a binary non-narrow-sense $\textrm{BCH}(15, 7)$ code with first consecutive root
+            $\alpha^3$. Notice the design distance of this code is only 3.
 
             .. ipython:: python
 
@@ -981,14 +977,14 @@ def c(self) -> int:
         {},
         r"""
         Examples:
-            Construct a binary primitive :math:`\textrm{BCH}(15, 7)` code.
+            Construct a binary primitive $\textrm{BCH}(15, 7)$ code.
 
             .. ipython:: python
 
                 bch = galois.BCH(15, 7); bch
                 bch.G
 
-            Construct a non-primitive :math:`\textrm{BCH}(13, 4)` code over :math:`\mathrm{GF}(3)`.
+            Construct a non-primitive $\textrm{BCH}(13, 4)$ code over $\mathrm{GF}(3)$.
 
             .. ipython:: python
 
@@ -1011,7 +1007,7 @@ def G(self) -> FieldArray:
         {},
         r"""
         Examples:
-            Construct a binary primitive :math:`\textrm{BCH}(15, 7)` code.
+            Construct a binary primitive $\textrm{BCH}(15, 7)$ code.
 
             .. ipython:: python
 
@@ -1019,7 +1015,7 @@ def G(self) -> FieldArray:
                 bch.H
                 bch.parity_check_poly
 
-            Construct a non-primitive :math:`\textrm{BCH}(13, 4)` code over :math:`\mathrm{GF}(3)`.
+            Construct a non-primitive $\textrm{BCH}(13, 4)$ code over $\mathrm{GF}(3)$.
 
             .. ipython:: python
 
@@ -1035,10 +1031,10 @@ def H(self) -> FieldArray:
     @property
     def is_primitive(self) -> bool:
         r"""
-        Indicates if the BCH code is *primitive*, meaning :math:`n = q^m - 1`.
+        Indicates if the BCH code is *primitive*, meaning $n = q^m - 1$.
 
         Examples:
-            Construct a binary primitive :math:`\textrm{BCH}(15, 7)` code.
+            Construct a binary primitive $\textrm{BCH}(15, 7)$ code.
 
             .. ipython:: python
 
@@ -1046,7 +1042,7 @@ def is_primitive(self) -> bool:
                 bch.is_primitive
                 bch.n == bch.extension_field.order - 1
 
-            Construct a non-primitive :math:`\textrm{BCH}(13, 7)` code over :math:`\mathrm{GF}(3)`.
+            Construct a non-primitive $\textrm{BCH}(13, 7)$ code over $\mathrm{GF}(3)$.
 
             .. ipython:: python
 
@@ -1060,11 +1056,11 @@ def is_primitive(self) -> bool:
     def is_narrow_sense(self) -> bool:
         r"""
         Indicates if the BCH code is *narrow-sense*, meaning the roots of the generator polynomial are consecutive
-        powers of :math:`\alpha` starting at 1, that is :math:`\alpha, \dots, \alpha^{d-1}`.
+        powers of $\alpha$ starting at 1, that is $\alpha, \dots, \alpha^{d-1}$.
 
         Examples:
-            Construct a binary narrow-sense :math:`\textrm{BCH}(15, 7)` code with first consecutive root
-            :math:`\alpha`.
+            Construct a binary narrow-sense $\textrm{BCH}(15, 7)$ code with first consecutive root
+            $\alpha$.
 
             .. ipython:: python
 
@@ -1074,8 +1070,8 @@ def is_narrow_sense(self) -> bool:
                 bch.generator_poly
                 bch.roots
 
-            Construct a binary non-narrow-sense :math:`\textrm{BCH}(15, 7)` code with first consecutive root
-            :math:`\alpha^3`. Notice the design distance of this code is only 3.
+            Construct a binary non-narrow-sense $\textrm{BCH}(15, 7)$ code with first consecutive root
+            $\alpha^3$. Notice the design distance of this code is only 3.
 
             .. ipython:: python
 
@@ -1092,7 +1088,7 @@ def is_narrow_sense(self) -> bool:
         {},
         r"""
         Examples:
-            Construct a non-primitive :math:`\textrm{BCH}(13, 4)` systematic code over :math:`\mathrm{GF}(3)`.
+            Construct a non-primitive $\textrm{BCH}(13, 4)$ systematic code over $\mathrm{GF}(3)$.
 
             .. ipython:: python
 
@@ -1100,7 +1096,7 @@ def is_narrow_sense(self) -> bool:
                 bch.is_systematic
                 bch.G
 
-            Construct a non-primitive :math:`\textrm{BCH}(13, 4)` non-systematic code over :math:`\mathrm{GF}(3)`.
+            Construct a non-primitive $\textrm{BCH}(13, 4)$ non-systematic code over $\mathrm{GF}(3)$.
 
             .. ipython:: python
 
diff --git a/src/galois/_codes/_cyclic.py b/src/galois/_codes/_cyclic.py
index 33b2310e4..a76281e91 100644
--- a/src/galois/_codes/_cyclic.py
+++ b/src/galois/_codes/_cyclic.py
@@ -58,33 +58,27 @@ def __init__(
         {},
         r"""
         Notes:
-            The message vector :math:`\mathbf{m}` is a member of :math:`\mathrm{GF}(q)^k`. The corresponding
-            message polynomial :math:`m(x)` is a degree-:math:`k` polynomial over :math:`\mathrm{GF}(q)`.
+            The message vector $\mathbf{m}$ is a member of $\mathrm{GF}(q)^k$. The corresponding
+            message polynomial $m(x)$ is a degree-$k$ polynomial over $\mathrm{GF}(q)$.
 
-            .. math::
-                \mathbf{m} = [m_{k-1},\ \dots,\ m_1,\ m_0] \in \mathrm{GF}(q)^k
+            $$\mathbf{m} = [m_{k-1},\ \dots,\ m_1,\ m_0] \in \mathrm{GF}(q)^k$$
 
-            .. math::
-                m(x) = m_{k-1} x^{k-1} + \dots + m_1 x + m_0 \in \mathrm{GF}(q)[x]
+            $$m(x) = m_{k-1} x^{k-1} + \dots + m_1 x + m_0 \in \mathrm{GF}(q)[x]$$
 
-            The codeword vector :math:`\mathbf{c}` is a member of :math:`\mathrm{GF}(q)^n`. The corresponding
-            codeword polynomial :math:`c(x)` is a degree-:math:`n` polynomial over :math:`\mathrm{GF}(q)`.
+            The codeword vector $\mathbf{c}$ is a member of $\mathrm{GF}(q)^n$. The corresponding
+            codeword polynomial $c(x)$ is a degree-$n$ polynomial over $\mathrm{GF}(q)$.
 
-            .. math::
-                \mathbf{c} = [c_{n-1},\ \dots,\ c_1,\ c_0] \in \mathrm{GF}(q)^n
+            $$\mathbf{c} = [c_{n-1},\ \dots,\ c_1,\ c_0] \in \mathrm{GF}(q)^n$$
 
-            .. math::
-                c(x) = c_{n-1} x^{n-1} + \dots + c_1 x + c_0 \in \mathrm{GF}(q)[x]
+            $$c(x) = c_{n-1} x^{n-1} + \dots + c_1 x + c_0 \in \mathrm{GF}(q)[x]$$
 
             The codeword vector is computed by matrix multiplication of the message vector with the generator matrix.
             The equivalent polynomial operation is multiplication of the message polynomial with the generator
             polynomial.
 
-            .. math::
-                \mathbf{c} = \mathbf{m} \mathbf{G}
+            $$\mathbf{c} = \mathbf{m} \mathbf{G}$$
 
-            .. math::
-                c(x) = m(x) g(x)
+            $$c(x) = m(x) g(x)$$
         """,
     )
     def encode(self, message: ArrayLike, output: Literal["codeword", "parity"] = "codeword") -> FieldArray:
@@ -113,24 +107,20 @@ def decode(
         {},
         r"""
         Notes:
-            The message vector :math:`\mathbf{m}` is a member of :math:`\mathrm{GF}(q)^k`. The corresponding
-            message polynomial :math:`m(x)` is a degree-:math:`k` polynomial over :math:`\mathrm{GF}(q)`.
+            The message vector $\mathbf{m}$ is a member of $\mathrm{GF}(q)^k$. The corresponding
+            message polynomial $m(x)$ is a degree-$k$ polynomial over $\mathrm{GF}(q)$.
 
-            .. math::
-                \mathbf{m} = [m_{k-1},\ \dots,\ m_1,\ m_0] \in \mathrm{GF}(q)^k
+            $$\mathbf{m} = [m_{k-1},\ \dots,\ m_1,\ m_0] \in \mathrm{GF}(q)^k$$
 
-            .. math::
-                m(x) = m_{k-1} x^{k-1} + \dots + m_1 x + m_0 \in \mathrm{GF}(q)[x]
+            $$m(x) = m_{k-1} x^{k-1} + \dots + m_1 x + m_0 \in \mathrm{GF}(q)[x]$$
 
-            The codeword vector :math:`\mathbf{c}` is a member of :math:`\mathrm{GF}(q)^n`. The corresponding
-            codeword polynomial :math:`c(x)` is a degree-:math:`n` polynomial over :math:`\mathrm{GF}(q)`.
-            Each codeword polynomial :math:`c(x)` is divisible by the generator polynomial :math:`g(x)`.
+            The codeword vector $\mathbf{c}$ is a member of $\mathrm{GF}(q)^n$. The corresponding
+            codeword polynomial $c(x)$ is a degree-$n$ polynomial over $\mathrm{GF}(q)$.
+            Each codeword polynomial $c(x)$ is divisible by the generator polynomial $g(x)$.
 
-            .. math::
-                \mathbf{c} = [c_{n-1},\ \dots,\ c_1,\ c_0] \in \mathrm{GF}(q)^n
+            $$\mathbf{c} = [c_{n-1},\ \dots,\ c_1,\ c_0] \in \mathrm{GF}(q)^n$$
 
-            .. math::
-                c(x) = c_{n-1} x^{n-1} + \dots + c_1 x + c_0 \in \mathrm{GF}(q)[x]
+            $$c(x) = c_{n-1} x^{n-1} + \dots + c_1 x + c_0 \in \mathrm{GF}(q)[x]$$
         """,
     )
     def decode(self, codeword, output="message", errors=False):
@@ -158,11 +148,11 @@ def _convert_codeword_to_parity(self, codeword: FieldArray) -> FieldArray:
     @property
     def generator_poly(self) -> Poly:
         r"""
-        The generator polynomial :math:`g(x)` over :math:`\mathrm{GF}(q)`.
+        The generator polynomial $g(x)$ over $\mathrm{GF}(q)$.
 
         Notes:
-            Every codeword :math:`\mathbf{c}` can be represented as a degree-:math:`n` polynomial :math:`c(x)`.
-            Each codeword polynomial :math:`c(x)` is a multiple of :math:`g(x)`.
+            Every codeword $\mathbf{c}$ can be represented as a degree-$n$ polynomial $c(x)$.
+            Each codeword polynomial $c(x)$ is a multiple of $g(x)$.
 
         Group:
             Polynomials
@@ -175,7 +165,7 @@ def generator_poly(self) -> Poly:
     @property
     def parity_check_poly(self) -> Poly:
         r"""
-        The parity-check polynomial :math:`h(x)`.
+        The parity-check polynomial $h(x)$.
 
         Notes:
             The parity-check polynomial is the generator polynomial of the dual code.
@@ -191,7 +181,7 @@ def parity_check_poly(self) -> Poly:
     @property
     def roots(self) -> FieldArray:
         r"""
-        The :math:`d - 1` roots of the generator polynomial :math:`g(x)`.
+        The $d - 1$ roots of the generator polynomial $g(x)$.
 
         Group:
             Polynomials
diff --git a/src/galois/_codes/_linear.py b/src/galois/_codes/_linear.py
index 63ce92290..39b93bcd6 100644
--- a/src/galois/_codes/_linear.py
+++ b/src/galois/_codes/_linear.py
@@ -55,23 +55,23 @@ def __init__(
 
     def encode(self, message: ArrayLike, output: Literal["codeword", "parity"] = "codeword") -> FieldArray:
         r"""
-        Encodes the message :math:`\mathbf{m}` into the codeword :math:`\mathbf{c}`.
+        Encodes the message $\mathbf{m}$ into the codeword $\mathbf{c}$.
 
         Arguments:
-            message: The message as either a :math:`k`-length vector or :math:`(N, k)` matrix, where :math:`N` is the
+            message: The message as either a $k$-length vector or $(N, k)$ matrix, where $N$ is the
                 number of messages.
 
                 .. info::
                     :title: Shortened codes
 
-                    For the shortened :math:`[n-s,\ k-s,\ d]` code (only applicable for systematic codes),
-                    pass :math:`k-s` symbols into :func:`encode` to return the :math:`n-s`-symbol message.
+                    For the shortened $[n-s,\ k-s,\ d]$ code (only applicable for systematic codes),
+                    pass $k-s$ symbols into :func:`encode` to return the $n-s$-symbol message.
 
             output: Specify whether to return the codeword or parity symbols only. The default is `"codeword"`.
 
         Returns:
-            If `output="codeword"`, the codeword as either a :math:`n`-length vector or :math:`(N, n)` matrix.
-            If `output="parity"`, the parity symbols as either a :math:`n-k`-length vector or :math:`(N, n-k)` matrix.
+            If `output="codeword"`, the codeword as either a $n$-length vector or $(N, n)$ matrix.
+            If `output="parity"`, the parity symbols as either a $n-k$-length vector or $(N, n-k)$ matrix.
         """
         verify_literal(output, ["codeword", "parity"])
 
@@ -92,20 +92,20 @@ def encode(self, message: ArrayLike, output: Literal["codeword", "parity"] = "co
 
     def detect(self, codeword: ArrayLike) -> bool | np.ndarray:
         r"""
-        Detects if errors are present in the codeword :math:`\mathbf{c}`.
+        Detects if errors are present in the codeword $\mathbf{c}$.
 
         Arguments:
-            codeword: The codeword as either a :math:`n`-length vector or :math:`(N, n)` matrix, where :math:`N` is the
+            codeword: The codeword as either a $n$-length vector or $(N, n)$ matrix, where $N$ is the
                 number of codewords.
 
                 .. info::
                     :title: Shortened codes
 
-                    For the shortened :math:`[n-s,\ k-s,\ d]` code (only applicable for systematic codes),
-                    pass :math:`n-s` symbols into :func:`detect`.
+                    For the shortened $[n-s,\ k-s,\ d]$ code (only applicable for systematic codes),
+                    pass $n-s$ symbols into :func:`detect`.
 
         Returns:
-            A boolean scalar or :math:`N`-length array indicating if errors were detected in the corresponding codeword.
+            A boolean scalar or $N$-length array indicating if errors were detected in the corresponding codeword.
         """
         codeword, is_codeword_1d = self._check_and_convert_codeword(codeword)
         detected = self._detect_errors(codeword)
@@ -135,28 +135,28 @@ def decode(
 
     def decode(self, codeword, output="message", errors=False):
         r"""
-        Decodes the codeword :math:`\mathbf{c}` into the message :math:`\mathbf{m}`.
+        Decodes the codeword $\mathbf{c}$ into the message $\mathbf{m}$.
 
         Arguments:
-            codeword: The codeword as either a :math:`n`-length vector or :math:`(N, n)` matrix, where :math:`N` is the
+            codeword: The codeword as either a $n$-length vector or $(N, n)$ matrix, where $N$ is the
                 number of codewords.
 
                 .. info::
                     :title: Shortened codes
 
-                    For the shortened :math:`[n-s,\ k-s,\ d]` code (only applicable for systematic codes),
-                    pass :math:`n-s` symbols into :func:`decode` to return the :math:`k-s`-symbol message.
+                    For the shortened $[n-s,\ k-s,\ d]$ code (only applicable for systematic codes),
+                    pass $n-s$ symbols into :func:`decode` to return the $k-s$-symbol message.
 
             output: Specify whether to return the error-corrected message or entire codeword. The default is
                 `"message"`.
             errors: Optionally specify whether to return the number of corrected errors. The default is `False`.
 
         Returns:
-            - If `output="message"`, the error-corrected message as either a :math:`k`-length vector or
-              :math:`(N, k)` matrix. If `output="codeword"`, the error-corrected codeword as either a :math:`n`-length
-              vector or :math:`(N, n)` matrix.
-            - If `errors=True`, returns the number of corrected symbol errors as either a scalar or :math:`N`-length
-              array. Valid number of corrections are in :math:`[0, t]`. If a codeword has too many errors and cannot
+            - If `output="message"`, the error-corrected message as either a $k$-length vector or
+              $(N, k)$ matrix. If `output="codeword"`, the error-corrected codeword as either a $n$-length
+              vector or $(N, n)$ matrix.
+            - If `errors=True`, returns the number of corrected symbol errors as either a scalar or $N$-length
+              array. Valid number of corrections are in $[0, t]$. If a codeword has too many errors and cannot
               be corrected, -1 will be returned.
         """
         verify_literal(output, ["message", "codeword"])
@@ -304,48 +304,47 @@ def _decode_codeword(self, codeword: FieldArray) -> tuple[FieldArray, np.ndarray
     @property
     def field(self) -> Type[FieldArray]:
         r"""
-        The Galois field :math:`\mathrm{GF}(q)` that defines the codeword alphabet.
+        The Galois field $\mathrm{GF}(q)$ that defines the codeword alphabet.
         """
         return self._field
 
     @property
     def n(self) -> int:
         """
-        The codeword size :math:`n` of the :math:`[n, k, d]_q` code. This is also called the code *length*.
+        The codeword size $n$ of the $[n, k, d]_q$ code. This is also called the code *length*.
         """
         return self._n
 
     @property
     def k(self) -> int:
         """
-        The message size :math:`k` of the :math:`[n, k, d]_q` code. This is also called the code *dimension*.
+        The message size $k$ of the $[n, k, d]_q$ code. This is also called the code *dimension*.
         """
         return self._k
 
     @property
     def d(self) -> int:
         r"""
-        The minimum distance :math:`d` of the :math:`[n, k, d]_q` code.
+        The minimum distance $d$ of the $[n, k, d]_q$ code.
         """
         return self._d
 
     @property
     def t(self) -> int:
         r"""
-        The error-correcting capability :math:`t` of the code.
+        The error-correcting capability $t$ of the code.
 
         Notes:
-            The code can correct :math:`t` symbol errors in a codeword.
+            The code can correct $t$ symbol errors in a codeword.
 
-            .. math::
-                t = \bigg\lfloor \frac{d - 1}{2} \bigg\rfloor
+            $$t = \bigg\lfloor \frac{d - 1}{2} \bigg\rfloor$$
         """
         return (self.d - 1) // 2
 
     @property
     def G(self) -> FieldArray:
         r"""
-        The generator matrix :math:`\mathbf{G}` with shape :math:`(k, n)`.
+        The generator matrix $\mathbf{G}$ with shape $(k, n)$.
 
         Group:
             Matrices
@@ -358,7 +357,7 @@ def G(self) -> FieldArray:
     @property
     def H(self) -> FieldArray:
         r"""
-        The parity-check matrix :math:`\mathbf{H}` with shape :math:`(n - k, n)`.
+        The parity-check matrix $\mathbf{H}$ with shape $(n - k, n)$.
 
         Group:
             Matrices
@@ -378,18 +377,18 @@ def is_systematic(self) -> bool:
 
 def generator_to_parity_check_matrix(G: FieldArray) -> FieldArray:
     r"""
-    Converts the generator matrix :math:`\mathbf{G}` of a linear :math:`[n, k]` code into its parity-check matrix
-    :math:`\mathbf{H}`.
+    Converts the generator matrix $\mathbf{G}$ of a linear $[n, k]$ code into its parity-check matrix
+    $\mathbf{H}$.
 
-    The generator and parity-check matrices satisfy the equations :math:`\mathbf{G}\mathbf{H}^T = \mathbf{0}`.
+    The generator and parity-check matrices satisfy the equations $\mathbf{G}\mathbf{H}^T = \mathbf{0}$.
 
     Arguments:
-        G: The :math:`(k, n)` generator matrix :math:`\mathbf{G}` in systematic form
-            :math:`\mathbf{G} = [\mathbf{I}_{k,k}\ |\ \mathbf{P}_{k,n-k}]`.
+        G: The $(k, n)$ generator matrix $\mathbf{G}$ in systematic form
+            $\mathbf{G} = [\mathbf{I}_{k,k}\ |\ \mathbf{P}_{k,n-k}]$.
 
     Returns:
-        The :math:`(n-k, n)` parity-check matrix
-        :math:`\mathbf{H} = [-\mathbf{P}_{k,n-k}^T\ |\ \mathbf{I}_{n-k,n-k}]``.
+        The $(n-k, n)$ parity-check matrix
+        $\mathbf{H} = [-\mathbf{P}_{k,n-k}^T\ |\ \mathbf{I}_{n-k,n-k}]$`.
 
     Examples:
         .. ipython:: python
@@ -418,17 +417,17 @@ def generator_to_parity_check_matrix(G: FieldArray) -> FieldArray:
 
 def parity_check_to_generator_matrix(H: FieldArray) -> FieldArray:
     r"""
-    Converts the parity-check matrix :math:`\mathbf{H}` of a linear :math:`[n, k]` code into its generator matrix
-    :math:`\mathbf{G}`.
+    Converts the parity-check matrix $\mathbf{H}$ of a linear $[n, k]$ code into its generator matrix
+    $\mathbf{G}$.
 
-    The generator and parity-check matrices satisfy the equations :math:`\mathbf{G}\mathbf{H}^T = \mathbf{0}`.
+    The generator and parity-check matrices satisfy the equations $\mathbf{G}\mathbf{H}^T = \mathbf{0}$.
 
     Arguments:
-        H: The :math:`(n-k, n)` parity-check matrix :math:`\mathbf{G}` in systematic form
-            :math:`\mathbf{H} = [-\mathbf{P}_{k,n-k}^T\ |\ \mathbf{I}_{n-k,n-k}]``.
+        H: The $(n-k, n)$ parity-check matrix $\mathbf{G}$ in systematic form
+            $\mathbf{H} = [-\mathbf{P}_{k,n-k}^T\ |\ \mathbf{I}_{n-k,n-k}]$`.
 
     Returns:
-        The :math:`(k, n)` generator matrix :math:`\mathbf{G} = [\mathbf{I}_{k,k}\ |\ \mathbf{P}_{k,n-k}]`.
+        The $(k, n)$ generator matrix $\mathbf{G} = [\mathbf{I}_{k,k}\ |\ \mathbf{P}_{k,n-k}]$.
 
     Examples:
         .. ipython:: python
diff --git a/src/galois/_codes/_reed_solomon.py b/src/galois/_codes/_reed_solomon.py
index fbc2210d2..25740972d 100644
--- a/src/galois/_codes/_reed_solomon.py
+++ b/src/galois/_codes/_reed_solomon.py
@@ -20,29 +20,27 @@
 @export
 class ReedSolomon(_CyclicCode):
     r"""
-    A general :math:`\textrm{RS}(n, k)` code over :math:`\mathrm{GF}(q)`.
+    A general $\textrm{RS}(n, k)$ code over $\mathrm{GF}(q)$.
 
-    A :math:`\textrm{RS}(n, k)` code is a :math:`[n, k, n - k + 1]_q` linear block code with codeword size :math:`n`,
-    message size :math:`k`, minimum distance :math:`d = n - k + 1`, and symbols taken from an alphabet of size
-    :math:`q`.
+    A $\textrm{RS}(n, k)$ code is a $[n, k, n - k + 1]_q$ linear block code with codeword size $n$,
+    message size $k$, minimum distance $d = n - k + 1$, and symbols taken from an alphabet of size
+    $q$.
 
     .. info::
         :title: Shortened codes
 
-        To create the shortened :math:`\textrm{RS}(n-s, k-s)` code, construct the full-sized
-        :math:`\textrm{RS}(n, k)` code and then pass :math:`k-s` symbols into :func:`encode` and :math:`n-s` symbols
+        To create the shortened $\textrm{RS}(n-s, k-s)$ code, construct the full-sized
+        $\textrm{RS}(n, k)$ code and then pass $k-s$ symbols into :func:`encode` and $n-s$ symbols
         into :func:`decode()`. Shortened codes are only applicable for systematic codes.
 
-    A Reed-Solomon code is a cyclic code over :math:`\mathrm{GF}(q)` with generator polynomial :math:`g(x)`. The
-    generator polynomial has :math:`d-1` roots :math:`\alpha^c, \dots, \alpha^{c+d-2}`. The element :math:`\alpha` is
-    a primitive :math:`n`-th root of unity in :math:`\mathrm{GF}(q)`.
+    A Reed-Solomon code is a cyclic code over $\mathrm{GF}(q)$ with generator polynomial $g(x)$. The
+    generator polynomial has $d-1$ roots $\alpha^c, \dots, \alpha^{c+d-2}$. The element $\alpha$ is
+    a primitive $n$-th root of unity in $\mathrm{GF}(q)$.
 
-    .. math::
-
-        g(x) = (x - \alpha^c) \dots (x - \alpha^{c+d-2})
+    $$g(x) = (x - \alpha^c) \dots (x - \alpha^{c+d-2})$$
 
     Examples:
-        Construct a :math:`\textrm{RS}(15, 9)` code.
+        Construct a $\textrm{RS}(15, 9)$ code.
 
         .. ipython:: python
 
@@ -88,28 +86,28 @@ def __init__(
         systematic: bool = True,
     ):
         r"""
-        Constructs a general :math:`\textrm{RS}(n, k)` code over :math:`\mathrm{GF}(q)`.
+        Constructs a general $\textrm{RS}(n, k)$ code over $\mathrm{GF}(q)$.
 
         Arguments:
-            n: The codeword size :math:`n`. If :math:`n = q - 1`, the Reed-Solomon code is *primitive*.
-            k: The message size :math:`k`.
+            n: The codeword size $n$. If $n = q - 1$, the Reed-Solomon code is *primitive*.
+            k: The message size $k$.
 
                 .. important::
                     Either `k` or `d` must be provided to define the code. Both may be provided as long as they are
                     consistent.
 
-            d: The design distance :math:`d`. This defines the number of roots :math:`d - 1` in the generator
-                polynomial :math:`g(x)` over :math:`\mathrm{GF}(q)`. Reed-Solomon codes achieve the Singleton bound,
-                so :math:`d = n - k + 1`.
-            field: The Galois field :math:`\mathrm{GF}(q)` that defines the alphabet of the codeword symbols.
-                The default is `None` which corresponds to :math:`\mathrm{GF}(2^m)` where
-                :math:`2^{m - 1} \le n < 2^m`. The default field will use `matlab_primitive_poly(2, m)` for the
+            d: The design distance $d$. This defines the number of roots $d - 1$ in the generator
+                polynomial $g(x)$ over $\mathrm{GF}(q)$. Reed-Solomon codes achieve the Singleton bound,
+                so $d = n - k + 1$.
+            field: The Galois field $\mathrm{GF}(q)$ that defines the alphabet of the codeword symbols.
+                The default is `None` which corresponds to $\mathrm{GF}(2^m)$ where
+                $2^{m - 1} \le n < 2^m$. The default field will use `matlab_primitive_poly(2, m)` for the
                 irreducible polynomial.
-            alpha: A primitive :math:`n`-th root of unity :math:`\alpha` in :math:`\mathrm{GF}(q)` that defines the
-                :math:`\alpha^c, \dots, \alpha^{c+d-2}` roots of the generator polynomial :math:`g(x)`.
-            c: The first consecutive power :math:`c` of :math:`\alpha` that defines the
-                :math:`\alpha^c, \dots, \alpha^{c+d-2}` roots of the generator polynomial :math:`g(x)`.
-                The default is 1. If :math:`c = 1`, the Reed-Solomon code is *narrow-sense*.
+            alpha: A primitive $n$-th root of unity $\alpha$ in $\mathrm{GF}(q)$ that defines the
+                $\alpha^c, \dots, \alpha^{c+d-2}$ roots of the generator polynomial $g(x)$.
+            c: The first consecutive power $c$ of $\alpha$ that defines the
+                $\alpha^c, \dots, \alpha^{c+d-2}$ roots of the generator polynomial $g(x)$.
+                The default is 1. If $c = 1$, the Reed-Solomon code is *narrow-sense*.
             systematic: Indicates if the encoding should be systematic, meaning the codeword is the message with
                 parity appended. The default is `True`.
 
@@ -117,7 +115,7 @@ def __init__(
             matlab_primitive_poly, FieldArray.primitive_root_of_unity
 
         Examples:
-            Construct a primitive, narrow-sense :math:`\textrm{RS}(255, 223)` code over :math:`\mathrm{GF}(2^8)`.
+            Construct a primitive, narrow-sense $\textrm{RS}(255, 223)$ code over $\mathrm{GF}(2^8)$.
 
             .. ipython:: python
 
@@ -125,7 +123,7 @@ def __init__(
                 galois.ReedSolomon(255, d=33)
                 galois.ReedSolomon(255, 223, 33)
 
-            Construct a non-primitive, narrow-sense :math:`\textrm{RS}(85, 65)` code over :math:`\mathrm{GF}(2^8)`.
+            Construct a non-primitive, narrow-sense $\textrm{RS}(85, 65)$ code over $\mathrm{GF}(2^8)$.
 
             .. ipython:: python
 
@@ -191,14 +189,14 @@ def __repr__(self) -> str:
         A terse representation of the Reed-Solomon code.
 
         Examples:
-            Construct a primitive, narrow-sense :math:`\textrm{RS}(255, 223)` code over :math:`\mathrm{GF}(2^8)`.
+            Construct a primitive, narrow-sense $\textrm{RS}(255, 223)$ code over $\mathrm{GF}(2^8)$.
 
             .. ipython:: python
 
                 rs = galois.ReedSolomon(255, 223)
                 rs
 
-            Construct a non-primitive, narrow-sense :math:`\textrm{RS}(85, 65)` code over :math:`\mathrm{GF}(2^8)`.
+            Construct a non-primitive, narrow-sense $\textrm{RS}(85, 65)$ code over $\mathrm{GF}(2^8)$.
 
             .. ipython:: python
 
@@ -212,14 +210,14 @@ def __str__(self) -> str:
         A formatted string with relevant properties of the Reed-Solomon code.
 
         Examples:
-            Construct a primitive, narrow-sense :math:`\textrm{RS}(255, 223)` code over :math:`\mathrm{GF}(2^8)`.
+            Construct a primitive, narrow-sense $\textrm{RS}(255, 223)$ code over $\mathrm{GF}(2^8)$.
 
             .. ipython:: python
 
                 rs = galois.ReedSolomon(255, 223)
                 print(rs)
 
-            Construct a non-primitive, narrow-sense :math:`\textrm{RS}(85, 65)` code over :math:`\mathrm{GF}(2^8)`.
+            Construct a non-primitive, narrow-sense $\textrm{RS}(85, 65)$ code over $\mathrm{GF}(2^8)$.
 
             .. ipython:: python
 
@@ -245,7 +243,7 @@ def __str__(self) -> str:
 
                 .. md-tab-item:: Vector
 
-                    Encode a single message using the :math:`\textrm{RS}(15, 9)` code.
+                    Encode a single message using the $\textrm{RS}(15, 9)$ code.
 
                     .. ipython:: python
 
@@ -262,7 +260,7 @@ def __str__(self) -> str:
 
                 .. md-tab-item:: Vector (shortened)
 
-                    Encode a single message using the shortened :math:`\textrm{RS}(11, 5)` code.
+                    Encode a single message using the shortened $\textrm{RS}(11, 5)$ code.
 
                     .. ipython:: python
 
@@ -279,7 +277,7 @@ def __str__(self) -> str:
 
                 .. md-tab-item:: Matrix
 
-                    Encode a matrix of three messages using the :math:`\textrm{RS}(15, 9)` code.
+                    Encode a matrix of three messages using the $\textrm{RS}(15, 9)$ code.
 
                     .. ipython:: python
 
@@ -296,7 +294,7 @@ def __str__(self) -> str:
 
                 .. md-tab-item:: Matrix (shortened)
 
-                    Encode a matrix of three messages using the shortened :math:`\textrm{RS}(11, 5)` code.
+                    Encode a matrix of three messages using the shortened $\textrm{RS}(11, 5)$ code.
 
                     .. ipython:: python
 
@@ -324,7 +322,7 @@ def encode(self, message: ArrayLike, output: Literal["codeword", "parity"] = "co
 
                 .. md-tab-item:: Vector
 
-                    Encode a single message using the :math:`\textrm{RS}(15, 9)` code.
+                    Encode a single message using the $\textrm{RS}(15, 9)$ code.
 
                     .. ipython:: python
 
@@ -339,7 +337,7 @@ def encode(self, message: ArrayLike, output: Literal["codeword", "parity"] = "co
 
                         rs.detect(c)
 
-                    Detect :math:`d_{min}-1` errors in the codeword.
+                    Detect $d_{min}-1$ errors in the codeword.
 
                     .. ipython:: python
 
@@ -350,7 +348,7 @@ def encode(self, message: ArrayLike, output: Literal["codeword", "parity"] = "co
 
                 .. md-tab-item:: Vector (shortened)
 
-                    Encode a single message using the shortened :math:`\textrm{RS}(11, 5)` code.
+                    Encode a single message using the shortened $\textrm{RS}(11, 5)$ code.
 
                     .. ipython:: python
 
@@ -365,7 +363,7 @@ def encode(self, message: ArrayLike, output: Literal["codeword", "parity"] = "co
 
                         rs.detect(c)
 
-                    Detect :math:`d_{min}-1` errors in the codeword.
+                    Detect $d_{min}-1$ errors in the codeword.
 
                     .. ipython:: python
 
@@ -376,7 +374,7 @@ def encode(self, message: ArrayLike, output: Literal["codeword", "parity"] = "co
 
                 .. md-tab-item:: Matrix
 
-                    Encode a matrix of three messages using the :math:`\textrm{RS}(15, 9)` code.
+                    Encode a matrix of three messages using the $\textrm{RS}(15, 9)$ code.
 
                     .. ipython:: python
 
@@ -391,7 +389,7 @@ def encode(self, message: ArrayLike, output: Literal["codeword", "parity"] = "co
 
                         rs.detect(c)
 
-                    Detect one, two, and :math:`d_{min}-1` errors in the codewords.
+                    Detect one, two, and $d_{min}-1$ errors in the codewords.
 
                     .. ipython:: python
 
@@ -404,7 +402,7 @@ def encode(self, message: ArrayLike, output: Literal["codeword", "parity"] = "co
 
                 .. md-tab-item:: Matrix (shortened)
 
-                    Encode a matrix of three messages using the shortened :math:`\textrm{RS}(11, 5)` code.
+                    Encode a matrix of three messages using the shortened $\textrm{RS}(11, 5)$ code.
 
                     .. ipython:: python
 
@@ -419,7 +417,7 @@ def encode(self, message: ArrayLike, output: Literal["codeword", "parity"] = "co
 
                         rs.detect(c)
 
-                    Detect one, two, and :math:`d_{min}-1` errors in the codewords.
+                    Detect one, two, and $d_{min}-1$ errors in the codewords.
 
                     .. ipython:: python
 
@@ -457,18 +455,16 @@ def decode(
         _CyclicCode.decode,
         {},
         r"""
-        In decoding, the syndrome vector :math:`\mathbf{s}` is computed by evaluating the received codeword
-        :math:`\mathbf{r}` at the roots :math:`\alpha^c, \dots, \alpha^{c+d-2}` of the generator polynomial
-        :math:`g(x)`. The equivalent polynomial operation computes the remainder of :math:`r(x)` by :math:`g(x)`.
+        In decoding, the syndrome vector $\mathbf{s}$ is computed by evaluating the received codeword
+        $\mathbf{r}$ at the roots $\alpha^c, \dots, \alpha^{c+d-2}$ of the generator polynomial
+        $g(x)$. The equivalent polynomial operation computes the remainder of $r(x)$ by $g(x)$.
 
-        .. math::
-            \mathbf{s} = [r(\alpha^c),\ \dots,\ r(\alpha^{c+d-2})] \in \mathrm{GF}(q)^{d-1}
+        $$\mathbf{s} = [r(\alpha^c),\ \dots,\ r(\alpha^{c+d-2})] \in \mathrm{GF}(q)^{d-1}$$
 
-        .. math::
-            s(x) = r(x)\ \textrm{mod}\ g(x) \in \mathrm{GF}(q)[x]
+        $$s(x) = r(x)\ \textrm{mod}\ g(x) \in \mathrm{GF}(q)[x]$$
 
         A syndrome of zeros indicates the received codeword is a valid codeword and there are no errors. If the
-        syndrome is non-zero, the decoder will find an error-locator polynomial :math:`\sigma(x)` and the corresponding
+        syndrome is non-zero, the decoder will find an error-locator polynomial $\sigma(x)$ and the corresponding
         error locations and values.
 
         Examples:
@@ -476,7 +472,7 @@ def decode(
 
                 .. md-tab-item:: Vector
 
-                    Encode a single message using the :math:`\textrm{RS}(15, 9)` code.
+                    Encode a single message using the $\textrm{RS}(15, 9)$ code.
 
                     .. ipython:: python
 
@@ -485,7 +481,7 @@ def decode(
                         m = GF.Random(rs.k); m
                         c = rs.encode(m); c
 
-                    Corrupt :math:`t` symbols of the codeword.
+                    Corrupt $t$ symbols of the codeword.
 
                     .. ipython:: python
 
@@ -508,7 +504,7 @@ def decode(
 
                 .. md-tab-item:: Vector (shortened)
 
-                    Encode a single message using the shortened :math:`\textrm{RS}(11, 5)` code.
+                    Encode a single message using the shortened $\textrm{RS}(11, 5)$ code.
 
                     .. ipython:: python
 
@@ -517,7 +513,7 @@ def decode(
                         m = GF.Random(rs.k - 4); m
                         c = rs.encode(m); c
 
-                    Corrupt :math:`t` symbols of the codeword.
+                    Corrupt $t$ symbols of the codeword.
 
                     .. ipython:: python
 
@@ -540,7 +536,7 @@ def decode(
 
                 .. md-tab-item:: Matrix
 
-                    Encode a matrix of three messages using the :math:`\textrm{RS}(15, 9)` code.
+                    Encode a matrix of three messages using the $\textrm{RS}(15, 9)$ code.
 
                     .. ipython:: python
 
@@ -575,7 +571,7 @@ def decode(
 
                 .. md-tab-item:: Matrix (shortened)
 
-                    Encode a matrix of three messages using the shortened :math:`\textrm{RS}(11, 5)` code.
+                    Encode a matrix of three messages using the shortened $\textrm{RS}(11, 5)$ code.
 
                     .. ipython:: python
 
@@ -624,7 +620,7 @@ def _decode_codeword(self, codeword: FieldArray) -> tuple[FieldArray, np.ndarray
         {},
         r"""
         Examples:
-            Construct a :math:`\textrm{RS}(15, 9)` code over :math:`\mathrm{GF}(2^4)`.
+            Construct a $\textrm{RS}(15, 9)$ code over $\mathrm{GF}(2^4)$.
 
             .. ipython:: python
 
@@ -632,7 +628,7 @@ def _decode_codeword(self, codeword: FieldArray) -> tuple[FieldArray, np.ndarray
                 rs.field
                 print(rs.field.properties)
 
-            Construct a :math:`\textrm{RS}(26, 18)` code over :math:`\mathrm{GF}(3^3)`.
+            Construct a $\textrm{RS}(26, 18)$ code over $\mathrm{GF}(3^3)$.
 
             .. ipython:: python
 
@@ -649,14 +645,14 @@ def field(self) -> Type[FieldArray]:
         {},
         r"""
         Examples:
-            Construct a :math:`\textrm{RS}(15, 9)` code over :math:`\mathrm{GF}(2^4)`.
+            Construct a $\textrm{RS}(15, 9)$ code over $\mathrm{GF}(2^4)$.
 
             .. ipython:: python
 
                 rs = galois.ReedSolomon(15, 9); rs
                 rs.n
 
-            Construct a :math:`\textrm{RS}(26, 18)` code over :math:`\mathrm{GF}(3^3)`.
+            Construct a $\textrm{RS}(26, 18)$ code over $\mathrm{GF}(3^3)$.
 
             .. ipython:: python
 
@@ -673,14 +669,14 @@ def n(self) -> int:
         {},
         r"""
         Examples:
-            Construct a :math:`\textrm{RS}(15, 9)` code over :math:`\mathrm{GF}(2^4)`.
+            Construct a $\textrm{RS}(15, 9)$ code over $\mathrm{GF}(2^4)$.
 
             .. ipython:: python
 
                 rs = galois.ReedSolomon(15, 9); rs
                 rs.k
 
-            Construct a :math:`\textrm{RS}(26, 18)` code over :math:`\mathrm{GF}(3^3)`.
+            Construct a $\textrm{RS}(26, 18)$ code over $\mathrm{GF}(3^3)$.
 
             .. ipython:: python
 
@@ -697,14 +693,14 @@ def k(self) -> int:
         {},
         r"""
         Examples:
-            Construct a :math:`\textrm{RS}(15, 9)` code over :math:`\mathrm{GF}(2^4)`.
+            Construct a $\textrm{RS}(15, 9)$ code over $\mathrm{GF}(2^4)$.
 
             .. ipython:: python
 
                 rs = galois.ReedSolomon(15, 9); rs
                 rs.d
 
-            Construct a :math:`\textrm{RS}(26, 18)` code over :math:`\mathrm{GF}(3^3)`.
+            Construct a $\textrm{RS}(26, 18)$ code over $\mathrm{GF}(3^3)$.
 
             .. ipython:: python
 
@@ -721,14 +717,14 @@ def d(self) -> int:
         {},
         r"""
         Examples:
-            Construct a :math:`\textrm{RS}(15, 9)` code over :math:`\mathrm{GF}(2^4)`.
+            Construct a $\textrm{RS}(15, 9)$ code over $\mathrm{GF}(2^4)$.
 
             .. ipython:: python
 
                 rs = galois.ReedSolomon(15, 9); rs
                 rs.t
 
-            Construct a :math:`\textrm{RS}(26, 18)` code over :math:`\mathrm{GF}(3^3)`.
+            Construct a $\textrm{RS}(26, 18)$ code over $\mathrm{GF}(3^3)$.
 
             .. ipython:: python
 
@@ -745,8 +741,8 @@ def t(self) -> int:
         {},
         r"""
         Examples:
-            Construct a narrow-sense :math:`\textrm{RS}(15, 9)` code over :math:`\mathrm{GF}(2^4)` with first
-            consecutive root :math:`\alpha`.
+            Construct a narrow-sense $\textrm{RS}(15, 9)$ code over $\mathrm{GF}(2^4)$ with first
+            consecutive root $\alpha$.
 
             .. ipython:: python
 
@@ -756,8 +752,8 @@ def t(self) -> int:
                 # Evaluate the generator polynomial at its roots in GF(q)
                 rs.generator_poly(rs.roots)
 
-            Construct a non-narrow-sense :math:`\textrm{RS}(15, 9)` code over :math:`\mathrm{GF}(2^4)` with first
-            consecutive root :math:`\alpha^3`.
+            Construct a non-narrow-sense $\textrm{RS}(15, 9)$ code over $\mathrm{GF}(2^4)$ with first
+            consecutive root $\alpha^3$.
 
             .. ipython:: python
 
@@ -777,7 +773,7 @@ def generator_poly(self) -> Poly:
         {},
         r"""
         Examples:
-            Construct a primitive :math:`\textrm{RS}(15, 9)` code over :math:`\mathrm{GF}(2^4)`.
+            Construct a primitive $\textrm{RS}(15, 9)$ code over $\mathrm{GF}(2^4)$.
 
             .. ipython:: python
 
@@ -785,7 +781,7 @@ def generator_poly(self) -> Poly:
                 rs.parity_check_poly
                 rs.H
 
-            Construct a non-primitive :math:`\textrm{RS}(13, 9)` code over :math:`\mathrm{GF}(3^3)`.
+            Construct a non-primitive $\textrm{RS}(13, 9)$ code over $\mathrm{GF}(3^3)$.
 
             .. ipython:: python
 
@@ -802,11 +798,11 @@ def parity_check_poly(self) -> Poly:
         _CyclicCode.roots,
         {},
         r"""
-        These are consecutive powers of :math:`\alpha^c`, specifically :math:`\alpha^c, \dots, \alpha^{c+d-2}`.
+        These are consecutive powers of $\alpha^c$, specifically $\alpha^c, \dots, \alpha^{c+d-2}$.
 
         Examples:
-            Construct a narrow-sense :math:`\textrm{RS}(15, 9)` code over :math:`\mathrm{GF}(2^4)` with first
-            consecutive root :math:`\alpha`.
+            Construct a narrow-sense $\textrm{RS}(15, 9)$ code over $\mathrm{GF}(2^4)$ with first
+            consecutive root $\alpha$.
 
             .. ipython:: python
 
@@ -816,8 +812,8 @@ def parity_check_poly(self) -> Poly:
                 # Evaluate the generator polynomial at its roots in GF(q)
                 rs.generator_poly(rs.roots)
 
-            Construct a non-narrow-sense :math:`\textrm{RS}(15, 9)` code over :math:`\mathrm{GF}(2^4)` with first
-            consecutive root :math:`\alpha^3`.
+            Construct a non-narrow-sense $\textrm{RS}(15, 9)$ code over $\mathrm{GF}(2^4)$ with first
+            consecutive root $\alpha^3$.
 
             .. ipython:: python
 
@@ -835,11 +831,11 @@ def roots(self) -> FieldArray:
     @property
     def alpha(self) -> FieldArray:
         r"""
-        A primitive :math:`n`-th root of unity :math:`\alpha` in :math:`\mathrm{GF}(q)` whose consecutive powers
-        :math:`\alpha^c, \dots, \alpha^{c+d-2}` are roots of the generator polynomial :math:`g(x)`.
+        A primitive $n$-th root of unity $\alpha$ in $\mathrm{GF}(q)$ whose consecutive powers
+        $\alpha^c, \dots, \alpha^{c+d-2}$ are roots of the generator polynomial $g(x)$.
 
         Examples:
-            Construct a primitive :math:`\textrm{RS}(255, 223)` code over :math:`\mathrm{GF}(2^8)`.
+            Construct a primitive $\textrm{RS}(255, 223)$ code over $\mathrm{GF}(2^8)$.
 
             .. ipython:: python
 
@@ -848,7 +844,7 @@ def alpha(self) -> FieldArray:
                 rs.roots[0] == rs.alpha ** rs.c
                 rs.alpha.multiplicative_order() == rs.n
 
-            Construct a non-primitive :math:`\textrm{RS}(85, 65)` code over :math:`\mathrm{GF}(2^8)`.
+            Construct a non-primitive $\textrm{RS}(85, 65)$ code over $\mathrm{GF}(2^8)$.
 
             .. ipython:: python
 
@@ -868,12 +864,12 @@ def alpha(self) -> FieldArray:
     @property
     def c(self) -> int:
         r"""
-        The first consecutive power :math:`c` of :math:`\alpha` that defines the roots
-        :math:`\alpha^c, \dots, \alpha^{c+d-2}` of the generator polynomial :math:`g(x)`.
+        The first consecutive power $c$ of $\alpha$ that defines the roots
+        $\alpha^c, \dots, \alpha^{c+d-2}$ of the generator polynomial $g(x)$.
 
         Examples:
-            Construct a narrow-sense :math:`\textrm{RS}(15, 9)` code over :math:`\mathrm{GF}(2^4)`
-            with first consecutive root :math:`\alpha`.
+            Construct a narrow-sense $\textrm{RS}(15, 9)$ code over $\mathrm{GF}(2^4)$
+            with first consecutive root $\alpha$.
 
             .. ipython:: python
 
@@ -882,8 +878,8 @@ def c(self) -> int:
                 rs.roots[0] == rs.alpha ** rs.c
                 rs.generator_poly
 
-            Construct a narrow-sense :math:`\textrm{RS}(15, 9)` code over :math:`\mathrm{GF}(2^4)`
-            with first consecutive root :math:`\alpha^3`. Notice the design distance is the same, however
+            Construct a narrow-sense $\textrm{RS}(15, 9)$ code over $\mathrm{GF}(2^4)$
+            with first consecutive root $\alpha^3$. Notice the design distance is the same, however
             the generator polynomial is different.
 
             .. ipython:: python
@@ -900,14 +896,14 @@ def c(self) -> int:
         {},
         r"""
         Examples:
-            Construct a primitive :math:`\textrm{RS}(15, 9)` code over :math:`\mathrm{GF}(2^4)`.
+            Construct a primitive $\textrm{RS}(15, 9)$ code over $\mathrm{GF}(2^4)$.
 
             .. ipython:: python
 
                 rs = galois.ReedSolomon(15, 9); rs
                 rs.G
 
-            Construct a non-primitive :math:`\textrm{RS}(13, 9)` code over :math:`\mathrm{GF}(3^3)`.
+            Construct a non-primitive $\textrm{RS}(13, 9)$ code over $\mathrm{GF}(3^3)$.
 
             .. ipython:: python
 
@@ -930,7 +926,7 @@ def G(self) -> FieldArray:
         {},
         r"""
         Examples:
-            Construct a primitive :math:`\textrm{RS}(15, 9)` code over :math:`\mathrm{GF}(2^4)`.
+            Construct a primitive $\textrm{RS}(15, 9)$ code over $\mathrm{GF}(2^4)$.
 
             .. ipython:: python
 
@@ -938,7 +934,7 @@ def G(self) -> FieldArray:
                 rs.H
                 rs.parity_check_poly
 
-            Construct a non-primitive :math:`\textrm{RS}(13, 9)` code over :math:`\mathrm{GF}(3^3)`.
+            Construct a non-primitive $\textrm{RS}(13, 9)$ code over $\mathrm{GF}(3^3)$.
 
             .. ipython:: python
 
@@ -954,10 +950,10 @@ def H(self) -> FieldArray:
     @property
     def is_primitive(self) -> bool:
         r"""
-        Indicates if the Reed-Solomon code is *primitive*, meaning :math:`n = q - 1`.
+        Indicates if the Reed-Solomon code is *primitive*, meaning $n = q - 1$.
 
         Examples:
-            Construct a primitive :math:`\textrm{RS}(255, 223)` code over :math:`\mathrm{GF}(2^8)`.
+            Construct a primitive $\textrm{RS}(255, 223)$ code over $\mathrm{GF}(2^8)$.
 
             .. ipython:: python
 
@@ -965,7 +961,7 @@ def is_primitive(self) -> bool:
                 rs.is_primitive
                 rs.n == rs.field.order - 1
 
-            Construct a non-primitive :math:`\textrm{RS}(85, 65)` code over :math:`\mathrm{GF}(2^8)`.
+            Construct a non-primitive $\textrm{RS}(85, 65)$ code over $\mathrm{GF}(2^8)$.
 
             .. ipython:: python
 
@@ -979,11 +975,11 @@ def is_primitive(self) -> bool:
     def is_narrow_sense(self) -> bool:
         r"""
         Indicates if the Reed-Solomon code is *narrow-sense*, meaning the roots of the generator polynomial are
-        consecutive powers of :math:`\alpha` starting at 1, that is :math:`\alpha, \dots, \alpha^{d-1}`.
+        consecutive powers of $\alpha$ starting at 1, that is $\alpha, \dots, \alpha^{d-1}$.
 
         Examples:
-            Construct a narrow-sense :math:`\textrm{RS}(15, 9)` code over :math:`\mathrm{GF}(2^4)`
-            with first consecutive root :math:`\alpha`.
+            Construct a narrow-sense $\textrm{RS}(15, 9)$ code over $\mathrm{GF}(2^4)$
+            with first consecutive root $\alpha$.
 
             .. ipython:: python
 
@@ -993,8 +989,8 @@ def is_narrow_sense(self) -> bool:
                 rs.generator_poly
                 rs.roots
 
-            Construct a narrow-sense :math:`\textrm{RS}(15, 9)` code over :math:`\mathrm{GF}(2^4)`
-            with first consecutive root :math:`\alpha^3`. Notice the design distance is the same, however
+            Construct a narrow-sense $\textrm{RS}(15, 9)$ code over $\mathrm{GF}(2^4)$
+            with first consecutive root $\alpha^3$. Notice the design distance is the same, however
             the generator polynomial is different.
 
             .. ipython:: python
@@ -1012,7 +1008,7 @@ def is_narrow_sense(self) -> bool:
         {},
         r"""
         Examples:
-            Construct a non-primitive :math:`\textrm{RS}(13, 9)` systematic code over :math:`\mathrm{GF}(3^3)`.
+            Construct a non-primitive $\textrm{RS}(13, 9)$ systematic code over $\mathrm{GF}(3^3)$.
 
             .. ipython:: python
 
@@ -1020,7 +1016,7 @@ def is_narrow_sense(self) -> bool:
                 rs.is_systematic
                 rs.G
 
-            Construct a non-primitive :math:`\textrm{RS}(13, 9)` non-systematic code over :math:`\mathrm{GF}(3^3)`.
+            Construct a non-primitive $\textrm{RS}(13, 9)$ non-systematic code over $\mathrm{GF}(3^3)$.
 
             .. ipython:: python
 
diff --git a/src/galois/_databases/_interface.py b/src/galois/_databases/_interface.py
index 77df053cf..a9696338c 100644
--- a/src/galois/_databases/_interface.py
+++ b/src/galois/_databases/_interface.py
@@ -3,6 +3,7 @@
 """
 from __future__ import annotations
 
+import sys
 import sqlite3
 from pathlib import Path
 
@@ -43,6 +44,14 @@ def fetch(self, n: int) -> tuple[list[int], list[int], int]:
         Returns:
             A tuple containing the prime factors and multiplicities.
         """
+        # Integer string conversion length limitation since Python 3.11
+        # https://github.com/mhostetter/galois/issues/494
+        if hasattr(sys, "set_int_max_str_digits"):
+            default_limit = sys.get_int_max_str_digits()
+            sys.set_int_max_str_digits(0)
+            n = str(n)
+            sys.set_int_max_str_digits(default_limit)
+
         self.cursor.execute(
             """
             SELECT factors, multiplicities, composite
diff --git a/src/galois/_domains/_array.py b/src/galois/_domains/_array.py
index e9ab989e3..108330272 100644
--- a/src/galois/_domains/_array.py
+++ b/src/galois/_domains/_array.py
@@ -298,10 +298,10 @@ def Random(
     @classmethod
     def Identity(cls, size: int, dtype: DTypeLike | None = None) -> Self:
         r"""
-        Creates an :math:`n \times n` identity matrix.
+        Creates an $n \times n$ identity matrix.
 
         Arguments:
-            size: The size :math:`n` along one dimension of the identity matrix.
+            size: The size $n$ along one dimension of the identity matrix.
             dtype: The :obj:`numpy.dtype` of the array elements. The default is `None` which represents the smallest
                 unsigned data type for this :obj:`~galois.Array` subclass (the first element in
                 :obj:`~galois.Array.dtypes`).
@@ -327,7 +327,7 @@ def compile(cls, mode: Literal["auto", "jit-lookup", "jit-calculate", "python-ca
         Arguments:
             mode: The ufunc calculation mode.
 
-                - `"auto"`: Selects `"jit-lookup"` for fields with order less than :math:`2^{20}`, `"jit-calculate"`
+                - `"auto"`: Selects `"jit-lookup"` for fields with order less than $2^{20}$, `"jit-calculate"`
                   for larger fields, and `"python-calculate"` for fields whose elements cannot be represented with
                   :obj:`numpy.int64`.
                 - `"jit-lookup"`: JIT compiles arithmetic ufuncs to use Zech log, log, and anti-log lookup tables for
diff --git a/src/galois/_domains/_meta.py b/src/galois/_domains/_meta.py
index ab5595876..e8aa737c4 100644
--- a/src/galois/_domains/_meta.py
+++ b/src/galois/_domains/_meta.py
@@ -120,23 +120,23 @@ def name(cls) -> str:
     @property
     def characteristic(cls) -> int:
         r"""
-        The characteristic :math:`p` of the Galois field :math:`\mathrm{GF}(p^m)` or :math:`p^e` of the Galois ring
-        :math:`\mathrm{GR}(p^e, m)`.
+        The characteristic $p$ of the Galois field $\mathrm{GF}(p^m)$ or $p^e$ of the Galois ring
+        $\mathrm{GR}(p^e, m)$.
         """
         return cls._characteristic
 
     @property
     def degree(cls) -> int:
         r"""
-        The degree :math:`m` of the Galois field :math:`\mathrm{GF}(p^m)` or Galois ring :math:`\mathrm{GR}(p^e, m)`.
+        The degree $m$ of the Galois field $\mathrm{GF}(p^m)$ or Galois ring $\mathrm{GR}(p^e, m)$.
         """
         return cls._degree
 
     @property
     def order(cls) -> int:
         r"""
-        The order :math:`p^m` of the Galois field :math:`\mathrm{GF}(p^m)` or :math:`p^{em}` of the Galois ring
-        :math:`\mathrm{GR}(p^e, m)`.
+        The order $p^m$ of the Galois field $\mathrm{GF}(p^m)$ or $p^{em}$ of the Galois ring
+        $\mathrm{GR}(p^e, m)$.
         """
         return cls._order
 
diff --git a/src/galois/_fields/_array.py b/src/galois/_fields/_array.py
index f89edbc40..8b7bf5e5c 100644
--- a/src/galois/_fields/_array.py
+++ b/src/galois/_fields/_array.py
@@ -24,7 +24,7 @@
 @export
 class FieldArray(Array, metaclass=FieldArrayMeta):
     r"""
-    An abstract :obj:`~numpy.ndarray` subclass over :math:`\mathrm{GF}(p^m)`.
+    An abstract :obj:`~numpy.ndarray` subclass over $\mathrm{GF}(p^m)$.
 
     .. abstract::
 
@@ -32,7 +32,7 @@ class FieldArray(Array, metaclass=FieldArrayMeta):
         :obj:`~galois.FieldArray` subclasses are created using the class factory :func:`~galois.GF`.
 
     Examples:
-        Create a :obj:`~galois.FieldArray` subclass over :math:`\mathrm{GF}(3^5)` using the class factory
+        Create a :obj:`~galois.FieldArray` subclass over $\mathrm{GF}(3^5)$ using the class factory
         :func:`~galois.GF`.
 
         .. ipython-with-reprs:: int,poly,power
@@ -78,7 +78,7 @@ def __init__(
         ndmin: int = 0,
     ):
         r"""
-        Creates an array over :math:`\mathrm{GF}(p^m)`.
+        Creates an array over $\mathrm{GF}(p^m)$.
 
         Arguments:
             x: A finite field scalar or array.
@@ -90,7 +90,7 @@ def __init__(
             ndmin: The `ndmin` keyword argument from :func:`numpy.array`. The default is 0.
 
         Examples:
-            Create a :obj:`~galois.FieldArray` subclass for :math:`\mathrm{GF}(3^5)`.
+            Create a :obj:`~galois.FieldArray` subclass for $\mathrm{GF}(3^5)$.
 
             .. ipython-with-reprs:: int,poly,power
 
@@ -332,18 +332,18 @@ def Identity(cls, size: int, dtype: DTypeLike | None = None) -> Self:
     @classmethod
     def Vandermonde(cls, element: ElementLike, rows: int, cols: int, dtype: DTypeLike | None = None) -> Self:
         r"""
-        Creates an :math:`m \times n` Vandermonde matrix of :math:`a \in \mathrm{GF}(q)`.
+        Creates an $m \times n$ Vandermonde matrix of $a \in \mathrm{GF}(q)$.
 
         Arguments:
-            element: An element :math:`a` of :math:`\mathrm{GF}(q)`.
-            rows: The number of rows :math:`m` in the Vandermonde matrix.
-            cols: The number of columns :math:`n` in the Vandermonde matrix.
+            element: An element $a$ of $\mathrm{GF}(q)$.
+            rows: The number of rows $m$ in the Vandermonde matrix.
+            cols: The number of columns $n$ in the Vandermonde matrix.
             dtype: The :obj:`numpy.dtype` of the array elements. The default is `None` which represents the smallest
                 unsigned data type for this :obj:`~galois.FieldArray` subclass (the first element in
                 :obj:`~galois.FieldArray.dtypes`).
 
         Returns:
-            A :math:`m \times n` Vandermonde matrix.
+            A $m \times n$ Vandermonde matrix.
 
         Examples:
             .. ipython-with-reprs:: int,poly,power
@@ -381,19 +381,19 @@ def Vandermonde(cls, element: ElementLike, rows: int, cols: int, dtype: DTypeLik
     @classmethod
     def Vector(cls, array: ArrayLike, dtype: DTypeLike | None = None) -> FieldArray:
         r"""
-        Converts length-:math:`m` vectors over the prime subfield :math:`\mathrm{GF}(p)` to an array
-        over :math:`\mathrm{GF}(p^m)`.
+        Converts length-$m$ vectors over the prime subfield $\mathrm{GF}(p)$ to an array
+        over $\mathrm{GF}(p^m)$.
 
         Arguments:
-            array: An array over :math:`\mathrm{GF}(p)` with last dimension :math:`m`. An array with shape
+            array: An array over $\mathrm{GF}(p)$ with last dimension $m$. An array with shape
                 `(n1, n2, m)` has output shape `(n1, n2)`. By convention, the vectors are ordered from degree
-                :math:`m-1` to degree 0.
+                $m-1$ to degree 0.
             dtype: The :obj:`numpy.dtype` of the array elements. The default is `None` which represents the smallest
                 unsigned data type for this :obj:`~galois.FieldArray` subclass (the first element in
                 :obj:`~galois.FieldArray.dtypes`).
 
         Returns:
-            An array over :math:`\mathrm{GF}(p^m)`.
+            An array over $\mathrm{GF}(p^m)$.
 
         Notes:
             This method is the inverse of the :func:`vector` method.
@@ -434,8 +434,8 @@ def Vector(cls, array: ArrayLike, dtype: DTypeLike | None = None) -> FieldArray:
 
     def vector(self, dtype: DTypeLike | None = None) -> FieldArray:
         r"""
-        Converts an array over :math:`\mathrm{GF}(p^m)` to length-:math:`m` vectors over the prime subfield
-        :math:`\mathrm{GF}(p)`.
+        Converts an array over $\mathrm{GF}(p^m)$ to length-$m$ vectors over the prime subfield
+        $\mathrm{GF}(p)$.
 
         Arguments:
             dtype: The :obj:`numpy.dtype` of the array elements. The default is `None` which represents the smallest
@@ -443,11 +443,11 @@ def vector(self, dtype: DTypeLike | None = None) -> FieldArray:
                 :obj:`~galois.FieldArray.dtypes`).
 
         Returns:
-            An array over :math:`\mathrm{GF}(p)` with last dimension :math:`m`.
+            An array over $\mathrm{GF}(p)$ with last dimension $m$.
 
         Notes:
             This method is the inverse of the :func:`Vector` constructor. For an array with shape `(n1, n2)`,
-            the output shape is `(n1, n2, m)`. By convention, the vectors are ordered from degree :math:`m-1`
+            the output shape is `(n1, n2, m)`. By convention, the vectors are ordered from degree $m-1$
             to degree 0.
 
         Examples:
@@ -502,7 +502,7 @@ def compile(cls, mode: Literal["auto", "jit-lookup", "jit-calculate", "python-ca
         Arguments:
             mode: The ufunc calculation mode.
 
-                - `"auto"`: Selects `"jit-lookup"` for fields with order less than :math:`2^{20}`, `"jit-calculate"`
+                - `"auto"`: Selects `"jit-lookup"` for fields with order less than $2^{20}$, `"jit-calculate"`
                   for larger fields, and `"python-calculate"` for fields whose elements cannot be represented with
                   :obj:`numpy.int64`.
                 - `"jit-lookup"`: JIT compiles arithmetic ufuncs to use Zech log, log, and anti-log lookup tables for
@@ -634,31 +634,31 @@ def repr_table(
             of each field element.
 
         Examples:
-            Create a :obj:`~galois.FieldArray` subclass for :math:`\mathrm{GF}(3^3)`.
+            Create a :obj:`~galois.FieldArray` subclass for $\mathrm{GF}(3^3)$.
 
             .. ipython:: python
 
                 GF = galois.GF(3**3)
                 print(GF.properties)
 
-            Generate a representation table for :math:`\mathrm{GF}(3^3)`. Since :math:`x^3 + 2x + 1` is a primitive
-            polynomial, :math:`x` is a primitive element of the field. Notice, :math:`\textrm{ord}(x) = 26`.
+            Generate a representation table for $\mathrm{GF}(3^3)$. Since $x^3 + 2x + 1$ is a primitive
+            polynomial, $x$ is a primitive element of the field. Notice, $\textrm{ord}(x) = 26$.
 
             .. ipython:: python
 
                 print(GF.repr_table())
                 GF("x").multiplicative_order()
 
-            Generate a representation table for :math:`\mathrm{GF}(3^3)` using a different primitive element
-            :math:`2x^2 + 2x + 2`. Notice, :math:`\textrm{ord}(2x^2 + 2x + 2) = 26`.
+            Generate a representation table for $\mathrm{GF}(3^3)$ using a different primitive element
+            $2x^2 + 2x + 2$. Notice, $\textrm{ord}(2x^2 + 2x + 2) = 26$.
 
             .. ipython:: python
 
                 print(GF.repr_table("2x^2 + 2x + 2"))
                 GF("2x^2 + 2x + 2").multiplicative_order()
 
-            Generate a representation table for :math:`\mathrm{GF}(3^3)` using a non-primitive element :math:`x^2`.
-            Notice, :math:`\textrm{ord}(x^2) = 13 \ne 26`.
+            Generate a representation table for $\mathrm{GF}(3^3)$ using a non-primitive element $x^2$.
+            Notice, $\textrm{ord}(x^2) = 13 \ne 26$.
 
             .. ipython:: python
 
@@ -746,11 +746,11 @@ def arithmetic_table(
 
         Arguments:
             operation: The arithmetic operation.
-            x: Optionally specify the :math:`x` values for the arithmetic table. The default is `None`
-                which represents :math:`\{0, \dots, p^m - 1\}`.
-            y: Optionally specify the :math:`y` values for the arithmetic table. The default is `None`
-                which represents :math:`\{0, \dots, p^m - 1\}` for addition, subtraction, and multiplication and
-                :math:`\{1, \dots, p^m - 1\}` for division.
+            x: Optionally specify the $x$ values for the arithmetic table. The default is `None`
+                which represents $\{0, \dots, p^m - 1\}$.
+            y: Optionally specify the $y$ values for the arithmetic table. The default is `None`
+                which represents $\{0, \dots, p^m - 1\}$ for addition, subtraction, and multiplication and
+                $\{1, \dots, p^m - 1\}$ for division.
 
         Returns:
             A string representation of the arithmetic table.
@@ -763,7 +763,7 @@ def arithmetic_table(
                 GF = galois.GF(3**2)
                 print(GF.arithmetic_table("+"))
 
-            An arithmetic table may also be constructed from arbitrary :math:`x` and :math:`y`.
+            An arithmetic table may also be constructed from arbitrary $x$ and $y$.
 
             .. ipython-with-reprs:: int,poly,power
 
@@ -831,28 +831,28 @@ def arithmetic_table(
     @classmethod
     def primitive_root_of_unity(cls, n: int) -> Self:
         r"""
-        Finds a primitive :math:`n`-th root of unity in the finite field.
+        Finds a primitive $n$-th root of unity in the finite field.
 
         Arguments:
             n: The root of unity.
 
         Returns:
-            The primitive :math:`n`-th root of unity, a 0-D scalar array.
+            The primitive $n$-th root of unity, a 0-D scalar array.
 
         Raises:
-            ValueError: If no primitive :math:`n`-th roots of unity exist. This happens when :math:`n` is not a
-                divisor of :math:`p^m - 1`.
+            ValueError: If no primitive $n$-th roots of unity exist. This happens when $n$ is not a
+                divisor of $p^m - 1$.
 
         Notes:
-            A primitive :math:`n`-th root of unity :math:`\omega_n` is such that :math:`\omega_n^n = 1` and
-            :math:`\omega_n^k \ne 1` for all :math:`1 \le k \lt n`.
+            A primitive $n$-th root of unity $\omega_n$ is such that $\omega_n^n = 1$ and
+            $\omega_n^k \ne 1$ for all $1 \le k \lt n$.
 
-            In :math:`\mathrm{GF}(p^m)`, a primitive :math:`n`-th root of unity exists when :math:`n` divides
-            :math:`p^m - 1`. Then, the primitive root is :math:`\omega_n = \alpha^{(p^m - 1)/n}` where :math:`\alpha`
+            In $\mathrm{GF}(p^m)$, a primitive $n$-th root of unity exists when $n$ divides
+            $p^m - 1$. Then, the primitive root is $\omega_n = \alpha^{(p^m - 1)/n}$ where $\alpha$
             is a primitive element of the field.
 
         Examples:
-            In :math:`\mathrm{GF}(31)`, primitive roots exist for all divisors of 30.
+            In $\mathrm{GF}(31)$, primitive roots exist for all divisors of 30.
 
             .. ipython:: python
 
@@ -861,15 +861,15 @@ def primitive_root_of_unity(cls, n: int) -> Self:
                 GF.primitive_root_of_unity(5)
                 GF.primitive_root_of_unity(15)
 
-            However, they do not exist for :math:`n` that do not divide 30.
+            However, they do not exist for $n$ that do not divide 30.
 
             .. ipython:: python
                 :okexcept:
 
                 GF.primitive_root_of_unity(7)
 
-            For :math:`\omega_5`, one can see that :math:`\omega_5^5 = 1` and :math:`\omega_5^k \ne 1` for
-            :math:`1 \le k \lt 5`.
+            For $\omega_5$, one can see that $\omega_5^5 = 1$ and $\omega_5^k \ne 1$ for
+            $1 \le k \lt 5$.
 
             .. ipython:: python
 
@@ -894,28 +894,28 @@ def primitive_root_of_unity(cls, n: int) -> Self:
     @classmethod
     def primitive_roots_of_unity(cls, n: int) -> Self:
         r"""
-        Finds all primitive :math:`n`-th roots of unity in the finite field.
+        Finds all primitive $n$-th roots of unity in the finite field.
 
         Arguments:
             n: The root of unity.
 
         Returns:
-            All primitive :math:`n`-th roots of unity, a 1-D array. The roots are sorted in lexicographical order.
+            All primitive $n$-th roots of unity, a 1-D array. The roots are sorted in lexicographical order.
 
         Raises:
-            ValueError: If no primitive :math:`n`-th roots of unity exist. This happens when :math:`n` is not a
-                divisor of :math:`p^m - 1`.
+            ValueError: If no primitive $n$-th roots of unity exist. This happens when $n$ is not a
+                divisor of $p^m - 1$.
 
         Notes:
-            A primitive :math:`n`-th root of unity :math:`\omega_n` is such that :math:`\omega_n^n = 1` and
-            :math:`\omega_n^k \ne 1` for all :math:`1 \le k \lt n`.
+            A primitive $n$-th root of unity $\omega_n$ is such that $\omega_n^n = 1$ and
+            $\omega_n^k \ne 1$ for all $1 \le k \lt n$.
 
-            In :math:`\mathrm{GF}(p^m)`, a primitive :math:`n`-th root of unity exists when :math:`n` divides
-            :math:`p^m - 1`. Then, the primitive root is :math:`\omega_n = \alpha^{(p^m - 1)/n}` where :math:`\alpha`
+            In $\mathrm{GF}(p^m)$, a primitive $n$-th root of unity exists when $n$ divides
+            $p^m - 1$. Then, the primitive root is $\omega_n = \alpha^{(p^m - 1)/n}$ where $\alpha$
             is a primitive element of the field.
 
         Examples:
-            In :math:`\mathrm{GF}(31)`, primitive roots exist for all divisors of 30.
+            In $\mathrm{GF}(31)$, primitive roots exist for all divisors of 30.
 
             .. ipython:: python
 
@@ -924,15 +924,15 @@ def primitive_roots_of_unity(cls, n: int) -> Self:
                 GF.primitive_roots_of_unity(5)
                 GF.primitive_roots_of_unity(15)
 
-            However, they do not exist for :math:`n` that do not divide 30.
+            However, they do not exist for $n$ that do not divide 30.
 
             .. ipython:: python
                 :okexcept:
 
                 GF.primitive_roots_of_unity(7)
 
-            For :math:`\omega_5`, one can see that :math:`\omega_5^5 = 1` and :math:`\omega_5^k \ne 1` for
-            :math:`1 \le k \lt 5`.
+            For $\omega_5$, one can see that $\omega_5^5 = 1$ and $\omega_5^k \ne 1$ for
+            $1 \le k \lt 5$.
 
             .. ipython:: python
 
@@ -962,19 +962,19 @@ def primitive_roots_of_unity(cls, n: int) -> Self:
 
     def additive_order(self) -> int | np.ndarray:
         r"""
-        Computes the additive order of each element in :math:`x`.
+        Computes the additive order of each element in $x$.
 
         Returns:
-            An integer array of the additive order of each element in :math:`x`. The return value is a single integer
-            if the input array :math:`x` is a scalar.
+            An integer array of the additive order of each element in $x$. The return value is a single integer
+            if the input array $x$ is a scalar.
 
         Notes:
-            The additive order :math:`a` of :math:`x` in :math:`\mathrm{GF}(p^m)` is the smallest integer :math:`a`
-            such that :math:`x a = 0`. With the exception of 0, the additive order of every element is
+            The additive order $a$ of $x$ in $\mathrm{GF}(p^m)$ is the smallest integer $a$
+            such that $x a = 0$. With the exception of 0, the additive order of every element is
             the finite field's characteristic.
 
         Examples:
-            Compute the additive order of each element of :math:`\mathrm{GF}(3^2)`.
+            Compute the additive order of each element of $\mathrm{GF}(3^2)$.
 
             .. ipython-with-reprs:: int,poly,power
 
@@ -996,27 +996,27 @@ def additive_order(self) -> int | np.ndarray:
 
     def multiplicative_order(self) -> int | np.ndarray:
         r"""
-        Computes the multiplicative order :math:`\textrm{ord}(x)` of each element in :math:`x`.
+        Computes the multiplicative order $\textrm{ord}(x)$ of each element in $x$.
 
         Returns:
-            An integer array of the multiplicative order of each element in :math:`x`. The return value is a single
-            integer if the input array :math:`x` is a scalar.
+            An integer array of the multiplicative order of each element in $x$. The return value is a single
+            integer if the input array $x$ is a scalar.
 
         Raises:
             ArithmeticError: If zero is provided as an input. The multiplicative order of 0 is not defined. There is
                 no power of 0 that ever results in 1.
 
         Notes:
-            The multiplicative order :math:`\textrm{ord}(x) = a` of :math:`x` in :math:`\mathrm{GF}(p^m)` is the
-            smallest power :math:`a` such that :math:`x^a = 1`. If :math:`a = p^m - 1`, :math:`a` is said to be a
-            generator of the multiplicative group :math:`\mathrm{GF}(p^m)^\times`.
+            The multiplicative order $\textrm{ord}(x) = a$ of $x$ in $\mathrm{GF}(p^m)$ is the
+            smallest power $a$ such that $x^a = 1$. If $a = p^m - 1$, $a$ is said to be a
+            generator of the multiplicative group $\mathrm{GF}(p^m)^\times$.
 
             Note, :func:`multiplicative_order` should not be confused with :obj:`order`. The former returns the
             multiplicative order of :obj:`~galois.FieldArray` elements. The latter is a property of the field, namely
             the finite field's order or size.
 
         Examples:
-            Compute the multiplicative order of each non-zero element of :math:`\mathrm{GF}(3^2)`.
+            Compute the multiplicative order of each non-zero element of $\mathrm{GF}(3^2)$.
 
             .. ipython-with-reprs:: int,poly,power
 
@@ -1025,8 +1025,8 @@ def multiplicative_order(self) -> int | np.ndarray:
                 order = x.multiplicative_order(); order
                 x ** order
 
-            The elements with :math:`\textrm{ord}(x) = 8` are multiplicative generators of
-            :math:`\mathrm{GF}(3^2)^\times`, which are also called primitive elements.
+            The elements with $\textrm{ord}(x) = 8$ are multiplicative generators of
+            $\mathrm{GF}(3^2)^\times$, which are also called primitive elements.
 
             .. ipython-with-reprs:: int,poly,power
 
@@ -1058,18 +1058,18 @@ def multiplicative_order(self) -> int | np.ndarray:
 
     def is_square(self) -> bool | np.ndarray:
         r"""
-        Determines if the elements of :math:`x` are squares in the finite field.
+        Determines if the elements of $x$ are squares in the finite field.
 
         Returns:
-            A boolean array indicating if each element in :math:`x` is a square. The return value is a single boolean
-            if the input array :math:`x` is a scalar.
+            A boolean array indicating if each element in $x$ is a square. The return value is a single boolean
+            if the input array $x$ is a scalar.
 
         See Also:
             squares, non_squares
 
         Notes:
-            An element :math:`x` in :math:`\mathrm{GF}(p^m)` is a *square* if there exists a :math:`y` such that
-            :math:`y^2 = x` in the field.
+            An element $x$ in $\mathrm{GF}(p^m)$ is a *square* if there exists a $y$ such that
+            $y^2 = x$ in the field.
 
             In fields with characteristic 2, every element is a square (with two identical square roots). In fields
             with characteristic greater than 2, exactly half of the nonzero elements are squares (with two unique
@@ -1079,7 +1079,7 @@ def is_square(self) -> bool | np.ndarray:
             - Section 3.5.1 from https://cacr.uwaterloo.ca/hac/about/chap3.pdf.
 
         Examples:
-            Since :math:`\mathrm{GF}(2^3)` has characteristic 2, every element has a square root.
+            Since $\mathrm{GF}(2^3)$ has characteristic 2, every element has a square root.
 
             .. ipython-with-reprs:: int,poly,power
 
@@ -1089,7 +1089,7 @@ def is_square(self) -> bool | np.ndarray:
                 @suppress
                 GF.repr()
 
-            In :math:`\mathrm{GF}(11)`, the characteristic is greater than 2 so only half of the elements have square
+            In $\mathrm{GF}(11)$, the characteristic is greater than 2 so only half of the elements have square
             roots.
 
             .. ipython-with-reprs:: int,power
@@ -1121,7 +1121,7 @@ def row_reduce(self, ncols: int | None = None, eye: Literal["left", "right"] = "
         Arguments:
             ncols: The number of columns to perform Gaussian elimination over. The default is `None` which represents
                 the number of columns of the matrix.
-            eye: The location of the identity matrix :math:`\mathbf{I}`, either on the left or the right.
+            eye: The location of the identity matrix $\mathbf{I}$, either on the left or the right.
 
         Returns:
             The reduced row echelon form of the input matrix.
@@ -1134,7 +1134,7 @@ def row_reduce(self, ncols: int | None = None, eye: Literal["left", "right"] = "
             3. Add any row to a scalar multiple of another row.
 
         Examples:
-            Perform Gaussian elimination to get the reduced row echelon form of :math:`\mathbf{A}`.
+            Perform Gaussian elimination to get the reduced row echelon form of $\mathbf{A}$.
 
             .. ipython:: python
 
@@ -1143,7 +1143,7 @@ def row_reduce(self, ncols: int | None = None, eye: Literal["left", "right"] = "
                 A.row_reduce()
                 np.linalg.matrix_rank(A)
 
-            Perform Gaussian elimination to get an :math:`\mathbf{I}` on the right side of :math:`\mathbf{A}`.
+            Perform Gaussian elimination to get an $\mathbf{I}$ on the right side of $\mathbf{A}$.
 
             .. ipython:: python
 
@@ -1182,7 +1182,7 @@ def lu_decompose(self) -> tuple[Self, Self]:
             - The upper triangular matrix.
 
         Notes:
-            The LU decomposition of :math:`\mathbf{A}` is defined as :math:`\mathbf{A} = \mathbf{L} \mathbf{U}`.
+            The LU decomposition of $\mathbf{A}$ is defined as $\mathbf{A} = \mathbf{L} \mathbf{U}$.
 
         Examples:
             .. ipython:: python
@@ -1216,9 +1216,9 @@ def plu_decompose(self) -> tuple[Self, Self, Self]:
             - The upper triangular matrix.
 
         Notes:
-            The PLU decomposition of :math:`\mathbf{A}` is defined as
-            :math:`\mathbf{A} = \mathbf{P} \mathbf{L} \mathbf{U}`. This is equivalent to
-            :math:`\mathbf{P}^T \mathbf{A} = \mathbf{L} \mathbf{U}`.
+            The PLU decomposition of $\mathbf{A}$ is defined as
+            $\mathbf{A} = \mathbf{P} \mathbf{L} \mathbf{U}$. This is equivalent to
+            $\mathbf{P}^T \mathbf{A} = \mathbf{L} \mathbf{U}$.
 
         Examples:
             .. ipython:: python
@@ -1245,24 +1245,24 @@ def plu_decompose(self) -> tuple[Self, Self, Self]:
 
     def row_space(self) -> Self:
         r"""
-        Computes the row space of the matrix :math:`\mathbf{A}`.
+        Computes the row space of the matrix $\mathbf{A}$.
 
         Returns:
             The row space basis matrix. The rows of the basis matrix are the basis vectors that span the row space.
             The number of rows of the basis matrix is the dimension of the row space.
 
         Notes:
-            Given an :math:`m \times n` matrix :math:`\mathbf{A}` over :math:`\mathrm{GF}(q)`, the *row space* of
-            :math:`\mathbf{A}` is the vector space :math:`\{\mathbf{x} \in \mathrm{GF}(q)^n\}` defined by all linear
-            combinations of the rows of :math:`\mathbf{A}`. The row space has at most dimension
-            :math:`\textrm{min}(m, n)`.
+            Given an $m \times n$ matrix $\mathbf{A}$ over $\mathrm{GF}(q)$, the *row space* of
+            $\mathbf{A}$ is the vector space $\{\mathbf{x} \in \mathrm{GF}(q)^n\}$ defined by all linear
+            combinations of the rows of $\mathbf{A}$. The row space has at most dimension
+            $\textrm{min}(m, n)$.
 
-            The row space has properties :math:`\mathcal{R}(\mathbf{A}) = \mathcal{C}(\mathbf{A}^T)` and
-            :math:`\textrm{dim}(\mathcal{R}(\mathbf{A})) + \textrm{dim}(\mathcal{LN}(\mathbf{A})) = m`.
+            The row space has properties $\mathcal{R}(\mathbf{A}) = \mathcal{C}(\mathbf{A}^T)$ and
+            $\textrm{dim}(\mathcal{R}(\mathbf{A})) + \textrm{dim}(\mathcal{LN}(\mathbf{A})) = m$.
 
         Examples:
             The :func:`row_space` method defines basis vectors (its rows) that span the row space of
-            :math:`\mathbf{A}`.
+            $\mathbf{A}$.
 
             .. ipython:: python
 
@@ -1271,7 +1271,7 @@ def row_space(self) -> Self:
                 A = GF.Random((m, n)); A
                 R = A.row_space(); R
 
-            The dimension of the row space and left null space sum to :math:`m`.
+            The dimension of the row space and left null space sum to $m$.
 
             .. ipython:: python
 
@@ -1296,24 +1296,24 @@ def row_space(self) -> Self:
 
     def column_space(self) -> Self:
         r"""
-        Computes the column space of the matrix :math:`\mathbf{A}`.
+        Computes the column space of the matrix $\mathbf{A}$.
 
         Returns:
             The column space basis matrix. The rows of the basis matrix are the basis vectors that span the column
             space. The number of rows of the basis matrix is the dimension of the column space.
 
         Notes:
-            Given an :math:`m \times n` matrix :math:`\mathbf{A}` over :math:`\mathrm{GF}(q)`, the *column space* of
-            :math:`\mathbf{A}` is the vector space :math:`\{\mathbf{x} \in \mathrm{GF}(q)^m\}` defined by all linear
-            combinations of the columns of :math:`\mathbf{A}`. The column space has at most dimension
-            :math:`\textrm{min}(m, n)`.
+            Given an $m \times n$ matrix $\mathbf{A}$ over $\mathrm{GF}(q)$, the *column space* of
+            $\mathbf{A}$ is the vector space $\{\mathbf{x} \in \mathrm{GF}(q)^m\}$ defined by all linear
+            combinations of the columns of $\mathbf{A}$. The column space has at most dimension
+            $\textrm{min}(m, n)$.
 
-            The column space has properties :math:`\mathcal{C}(\mathbf{A}) = \mathcal{R}(\mathbf{A}^T)`  and
-            :math:`\textrm{dim}(\mathcal{C}(\mathbf{A})) + \textrm{dim}(\mathcal{N}(\mathbf{A})) = n`.
+            The column space has properties $\mathcal{C}(\mathbf{A}) = \mathcal{R}(\mathbf{A}^T)$  and
+            $\textrm{dim}(\mathcal{C}(\mathbf{A})) + \textrm{dim}(\mathcal{N}(\mathbf{A})) = n$.
 
         Examples:
             The :func:`column_space` method defines basis vectors (its rows) that span the column space of
-            :math:`\mathbf{A}`.
+            $\mathbf{A}$.
 
             .. ipython:: python
 
@@ -1322,7 +1322,7 @@ def column_space(self) -> Self:
                 A = GF.Random((m, n)); A
                 C = A.column_space(); C
 
-            The dimension of the column space and null space sum to :math:`n`.
+            The dimension of the column space and null space sum to $n$.
 
             .. ipython:: python
 
@@ -1343,23 +1343,23 @@ def column_space(self) -> Self:
 
     def left_null_space(self) -> Self:
         r"""
-        Computes the left null space of the matrix :math:`\mathbf{A}`.
+        Computes the left null space of the matrix $\mathbf{A}$.
 
         Returns:
             The left null space basis matrix. The rows of the basis matrix are the basis vectors that span the
             left null space. The number of rows of the basis matrix is the dimension of the left null space.
 
         Notes:
-            Given an :math:`m \times n` matrix :math:`\mathbf{A}` over :math:`\mathrm{GF}(q)`, the *left null space*
-            of :math:`\mathbf{A}` is the vector space :math:`\{\mathbf{x} \in \mathrm{GF}(q)^m\}` that annihilates the
-            rows of :math:`\mathbf{A}`, i.e. :math:`\mathbf{x}\mathbf{A} = \mathbf{0}`.
+            Given an $m \times n$ matrix $\mathbf{A}$ over $\mathrm{GF}(q)$, the *left null space*
+            of $\mathbf{A}$ is the vector space $\{\mathbf{x} \in \mathrm{GF}(q)^m\}$ that annihilates the
+            rows of $\mathbf{A}$, i.e. $\mathbf{x}\mathbf{A} = \mathbf{0}$.
 
-            The left null space has properties :math:`\mathcal{LN}(\mathbf{A}) = \mathcal{N}(\mathbf{A}^T)` and
-            :math:`\textrm{dim}(\mathcal{R}(\mathbf{A})) + \textrm{dim}(\mathcal{LN}(\mathbf{A})) = m`.
+            The left null space has properties $\mathcal{LN}(\mathbf{A}) = \mathcal{N}(\mathbf{A}^T)$ and
+            $\textrm{dim}(\mathcal{R}(\mathbf{A})) + \textrm{dim}(\mathcal{LN}(\mathbf{A})) = m$.
 
         Examples:
             The :func:`left_null_space` method defines basis vectors (its rows) that span the left null space of
-            :math:`\mathbf{A}`.
+            $\mathbf{A}$.
 
             .. ipython:: python
 
@@ -1374,7 +1374,7 @@ def left_null_space(self) -> Self:
 
                 LN @ A
 
-            The dimension of the row space and left null space sum to :math:`m`.
+            The dimension of the row space and left null space sum to $m$.
 
             .. ipython:: python
 
@@ -1409,23 +1409,23 @@ def left_null_space(self) -> Self:
 
     def null_space(self) -> Self:
         r"""
-        Computes the null space of the matrix :math:`\mathbf{A}`.
+        Computes the null space of the matrix $\mathbf{A}$.
 
         Returns:
             The null space basis matrix. The rows of the basis matrix are the basis vectors that span the null space.
             The number of rows of the basis matrix is the dimension of the null space.
 
         Notes:
-            Given an :math:`m \times n` matrix :math:`\mathbf{A}` over :math:`\mathrm{GF}(q)`, the *null space* of
-            :math:`\mathbf{A}` is the vector space :math:`\{\mathbf{x} \in \mathrm{GF}(q)^n\}` that annihilates the
-            columns of :math:`\mathbf{A}`, i.e. :math:`\mathbf{A}\mathbf{x} = \mathbf{0}`.
+            Given an $m \times n$ matrix $\mathbf{A}$ over $\mathrm{GF}(q)$, the *null space* of
+            $\mathbf{A}$ is the vector space $\{\mathbf{x} \in \mathrm{GF}(q)^n\}$ that annihilates the
+            columns of $\mathbf{A}$, i.e. $\mathbf{A}\mathbf{x} = \mathbf{0}$.
 
-            The null space has properties :math:`\mathcal{N}(\mathbf{A}) = \mathcal{LN}(\mathbf{A}^T)` and
-            :math:`\textrm{dim}(\mathcal{C}(\mathbf{A})) + \textrm{dim}(\mathcal{N}(\mathbf{A})) = n`.
+            The null space has properties $\mathcal{N}(\mathbf{A}) = \mathcal{LN}(\mathbf{A}^T)$ and
+            $\textrm{dim}(\mathcal{C}(\mathbf{A})) + \textrm{dim}(\mathcal{N}(\mathbf{A})) = n$.
 
         Examples:
             The :func:`null_space` method defines basis vectors (its rows) that span the null space of
-            :math:`\mathbf{A}`.
+            $\mathbf{A}$.
 
             .. ipython:: python
 
@@ -1440,7 +1440,7 @@ def null_space(self) -> Self:
 
                 A @ N.T
 
-            The dimension of the column space and null space sum to :math:`n`.
+            The dimension of the column space and null space sum to $n$.
 
             .. ipython:: python
 
@@ -1461,27 +1461,26 @@ def null_space(self) -> Self:
 
     def field_trace(self) -> FieldArray:
         r"""
-        Computes the field trace :math:`\mathrm{Tr}_{L / K}(x)` of the elements of :math:`x`.
+        Computes the field trace $\mathrm{Tr}_{L / K}(x)$ of the elements of $x$.
 
         Returns:
-            The field trace of :math:`x` in the prime subfield :math:`\mathrm{GF}(p)`.
+            The field trace of $x$ in the prime subfield $\mathrm{GF}(p)$.
 
         Notes:
-            The `self` array :math:`x` is over the extension field :math:`L = \mathrm{GF}(p^m)`. The field trace of
-            :math:`x` is over the subfield :math:`K = \mathrm{GF}(p)`. In other words,
-            :math:`\mathrm{Tr}_{L / K}(x) : L \rightarrow K`.
+            The `self` array $x$ is over the extension field $L = \mathrm{GF}(p^m)$. The field trace of
+            $x$ is over the subfield $K = \mathrm{GF}(p)$. In other words,
+            $\mathrm{Tr}_{L / K}(x) : L \rightarrow K$.
 
-            For finite fields, since :math:`L` is a Galois extension of :math:`K`, the field trace of :math:`x` is
-            defined as a sum of the Galois conjugates of :math:`x`.
+            For finite fields, since $L$ is a Galois extension of $K$, the field trace of $x$ is
+            defined as a sum of the Galois conjugates of $x$.
 
-            .. math::
-                \mathrm{Tr}_{L / K}(x) = \sum_{i=0}^{m-1} x^{p^i}
+            $$\mathrm{Tr}_{L / K}(x) = \sum_{i=0}^{m-1} x^{p^i}$$
 
         References:
             - https://en.wikipedia.org/wiki/Field_trace
 
         Examples:
-            Compute the field trace of the elements of :math:`\mathrm{GF}(3^2)`.
+            Compute the field trace of the elements of $\mathrm{GF}(3^2)$.
 
             .. ipython-with-reprs:: int,poly,power
 
@@ -1507,27 +1506,26 @@ def field_trace(self) -> FieldArray:
 
     def field_norm(self) -> FieldArray:
         r"""
-        Computes the field norm :math:`\mathrm{N}_{L / K}(x)` of the elements of :math:`x`.
+        Computes the field norm $\mathrm{N}_{L / K}(x)$ of the elements of $x$.
 
         Returns:
-            The field norm of :math:`x` in the prime subfield :math:`\mathrm{GF}(p)`.
+            The field norm of $x$ in the prime subfield $\mathrm{GF}(p)$.
 
         Notes:
-            The `self` array :math:`x` is over the extension field :math:`L = \mathrm{GF}(p^m)`. The field norm of
-            :math:`x` is over the subfield :math:`K = \mathrm{GF}(p)`. In other words,
-            :math:`\mathrm{N}_{L / K}(x) : L \rightarrow K`.
+            The `self` array $x$ is over the extension field $L = \mathrm{GF}(p^m)$. The field norm of
+            $x$ is over the subfield $K = \mathrm{GF}(p)$. In other words,
+            $\mathrm{N}_{L / K}(x) : L \rightarrow K$.
 
-            For finite fields, since :math:`L` is a Galois extension of :math:`K`, the field norm of :math:`x` is
-            defined as a product of the Galois conjugates of :math:`x`.
+            For finite fields, since $L$ is a Galois extension of $K$, the field norm of $x$ is
+            defined as a product of the Galois conjugates of $x$.
 
-            .. math::
-                \mathrm{N}_{L / K}(x) = \prod_{i=0}^{m-1} x^{p^i} = x^{(p^m - 1) / (p - 1)}
+            $$\mathrm{N}_{L / K}(x) = \prod_{i=0}^{m-1} x^{p^i} = x^{(p^m - 1) / (p - 1)}$$
 
         References:
             - https://en.wikipedia.org/wiki/Field_norm
 
         Examples:
-            Compute the field norm of the elements of :math:`\mathrm{GF}(3^2)`.
+            Compute the field norm of the elements of $\mathrm{GF}(3^2)$.
 
             .. ipython-with-reprs:: int,poly,power
 
@@ -1551,35 +1549,35 @@ def field_norm(self) -> FieldArray:
 
     def characteristic_poly(self) -> Poly:
         r"""
-        Computes the characteristic polynomial of a finite field element :math:`a` or a square matrix
-        :math:`\mathbf{A}`.
+        Computes the characteristic polynomial of a finite field element $a$ or a square matrix
+        $\mathbf{A}$.
 
         Returns:
-            For scalar inputs, the degree-:math:`m` characteristic polynomial :math:`c_a(x)` of :math:`a` over
-            :math:`\mathrm{GF}(p)`. For square :math:`n \times n` matrix inputs, the degree-:math:`n` characteristic
-            polynomial :math:`c_A(x)` of :math:`\mathbf{A}` over :math:`\mathrm{GF}(p^m)`.
+            For scalar inputs, the degree-$m$ characteristic polynomial $c_a(x)$ of $a$ over
+            $\mathrm{GF}(p)$. For square $n \times n$ matrix inputs, the degree-$n$ characteristic
+            polynomial $c_A(x)$ of $\mathbf{A}$ over $\mathrm{GF}(p^m)$.
 
         Raises:
             ValueError: If the array is not a single finite field element (scalar 0-D array) or a square
-                :math:`n \times n` matrix (2-D array).
+                $n \times n$ matrix (2-D array).
 
         Notes:
-            An element :math:`a` of :math:`\mathrm{GF}(p^m)` has characteristic polynomial :math:`c_a(x)` over
-            :math:`\mathrm{GF}(p)`. The characteristic polynomial when evaluated in :math:`\mathrm{GF}(p^m)`
-            annihilates :math:`a`, that is :math:`c_a(a) = 0`. In prime fields :math:`\mathrm{GF}(p)`, the
-            characteristic polynomial of :math:`a` is simply :math:`c_a(x) = x - a`.
+            An element $a$ of $\mathrm{GF}(p^m)$ has characteristic polynomial $c_a(x)$ over
+            $\mathrm{GF}(p)$. The characteristic polynomial when evaluated in $\mathrm{GF}(p^m)$
+            annihilates $a$, that is $c_a(a) = 0$. In prime fields $\mathrm{GF}(p)$, the
+            characteristic polynomial of $a$ is simply $c_a(x) = x - a$.
 
-            An :math:`n \times n` matrix :math:`\mathbf{A}` has characteristic polynomial
-            :math:`c_A(x) = \textrm{det}(x\mathbf{I} - \mathbf{A})` over :math:`\mathrm{GF}(p^m)`. The constant
-            coefficient of the characteristic polynomial is :math:`\textrm{det}(-\mathbf{A})`. The :math:`x^{n-1}`
-            coefficient of the characteristic polynomial is :math:`-\textrm{Tr}(\mathbf{A})`. The characteristic
-            polynomial annihilates :math:`\mathbf{A}`, that is :math:`c_A(\mathbf{A}) = \mathbf{0}`.
+            An $n \times n$ matrix $\mathbf{A}$ has characteristic polynomial
+            $c_A(x) = \textrm{det}(x\mathbf{I} - \mathbf{A})$ over $\mathrm{GF}(p^m)$. The constant
+            coefficient of the characteristic polynomial is $\textrm{det}(-\mathbf{A})$. The $x^{n-1}$
+            coefficient of the characteristic polynomial is $-\textrm{Tr}(\mathbf{A})$. The characteristic
+            polynomial annihilates $\mathbf{A}$, that is $c_A(\mathbf{A}) = \mathbf{0}$.
 
         References:
             - https://en.wikipedia.org/wiki/Characteristic_polynomial
 
         Examples:
-            The characteristic polynomial of the element :math:`a`.
+            The characteristic polynomial of the element $a$.
 
             .. ipython-with-reprs:: int,poly,power
 
@@ -1589,7 +1587,7 @@ def characteristic_poly(self) -> Poly:
                 # The characteristic polynomial annihilates a
                 poly(a, field=GF)
 
-            The characteristic polynomial of the square matrix :math:`\mathbf{A}`.
+            The characteristic polynomial of the square matrix $\mathbf{A}$.
 
             .. ipython-with-reprs:: int,poly,power
 
@@ -1614,28 +1612,28 @@ def characteristic_poly(self) -> Poly:
 
     def minimal_poly(self) -> Poly:
         r"""
-        Computes the minimal polynomial of a finite field element :math:`a`.
+        Computes the minimal polynomial of a finite field element $a$.
 
         Returns:
-            For scalar inputs, the minimal polynomial :math:`m_a(x)` of :math:`a` over :math:`\mathrm{GF}(p)`.
+            For scalar inputs, the minimal polynomial $m_a(x)$ of $a$ over $\mathrm{GF}(p)$.
 
         Raises:
-            NotImplementedError: If the array is a a square :math:`n \times n` matrix (2-D array).
+            NotImplementedError: If the array is a a square $n \times n$ matrix (2-D array).
             ValueError: If the array is not a single finite field element (scalar 0-D array).
 
         Notes:
-            An element :math:`a` of :math:`\mathrm{GF}(p^m)` has minimal polynomial :math:`m_a(x)` over
-            :math:`\mathrm{GF}(p)`. The minimal polynomial when evaluated in :math:`\mathrm{GF}(p^m)` annihilates
-            :math:`a`, that is :math:`m_a(a) = 0`. The minimal polynomial always divides the characteristic polynomial.
-            In prime fields :math:`\mathrm{GF}(p)`, the minimal polynomial of :math:`a` is simply
-            :math:`m_a(x) = x - a`.
+            An element $a$ of $\mathrm{GF}(p^m)$ has minimal polynomial $m_a(x)$ over
+            $\mathrm{GF}(p)$. The minimal polynomial when evaluated in $\mathrm{GF}(p^m)$ annihilates
+            $a$, that is $m_a(a) = 0$. The minimal polynomial always divides the characteristic polynomial.
+            In prime fields $\mathrm{GF}(p)$, the minimal polynomial of $a$ is simply
+            $m_a(x) = x - a$.
 
         References:
             - https://en.wikipedia.org/wiki/Minimal_polynomial_(field_theory)
             - https://en.wikipedia.org/wiki/Minimal_polynomial_(linear_algebra)
 
         Examples:
-            The minimal polynomial of the element :math:`a`.
+            The minimal polynomial of the element $a$.
 
             .. ipython-with-reprs:: int,poly,power
 
@@ -1659,10 +1657,10 @@ def minimal_poly(self) -> Poly:
 
     def log(self, base: ElementLike | ArrayLike | None = None) -> int | np.ndarray:
         r"""
-        Computes the discrete logarithm of the array :math:`x` base :math:`\beta`.
+        Computes the discrete logarithm of the array $x$ base $\beta$.
 
         Arguments:
-            base: A primitive element or elements :math:`\beta` of the finite field that is the base of the logarithm.
+            base: A primitive element or elements $\beta$ of the finite field that is the base of the logarithm.
                 The default is `None` which uses :obj:`~FieldArray.primitive_element`.
 
                 .. slow-performance::
@@ -1672,11 +1670,11 @@ def log(self, base: ElementLike | ArrayLike | None = None) -> int | np.ndarray:
                     explicit calculation will be used (which is slower than using lookup tables).
 
         Returns:
-            An integer array :math:`i` of powers of :math:`\beta` such that :math:`\beta^i = x`. The return array
+            An integer array $i$ of powers of $\beta$ such that $\beta^i = x$. The return array
             shape obeys NumPy broadcasting rules.
 
         Examples:
-            Compute the logarithm of :math:`x` with default base :math:`\alpha`, which is the specified primitive
+            Compute the logarithm of $x$ with default base $\alpha$, which is the specified primitive
             element of the field.
 
             .. ipython-with-reprs:: int,poly,power
@@ -1693,7 +1691,7 @@ def log(self, base: ElementLike | ArrayLike | None = None) -> int | np.ndarray:
 
                 np.array_equal(np.log(x), x.log())
 
-            Compute the logarithm of :math:`x` with a different base :math:`\beta`, which is another primitive element
+            Compute the logarithm of $x$ with a different base $\beta$, which is another primitive element
             of the field.
 
             .. ipython-with-reprs:: int,poly,power
diff --git a/src/galois/_fields/_factory.py b/src/galois/_fields/_factory.py
index d52a6890e..dab519816 100644
--- a/src/galois/_fields/_factory.py
+++ b/src/galois/_fields/_factory.py
@@ -59,25 +59,25 @@ def GF(
     repr=None,
 ):
     r"""
-    Creates a :obj:`~galois.FieldArray` subclass for :math:`\mathrm{GF}(p^m)`.
+    Creates a :obj:`~galois.FieldArray` subclass for $\mathrm{GF}(p^m)$.
 
     Arguments:
-        order: The order :math:`p^m` of the field :math:`\mathrm{GF}(p^m)`. The order must be a prime power.
-        characteristic: The characteristic :math:`p` of the field :math:`\mathrm{GF}(p^m)`. The characteristic must
+        order: The order $p^m$ of the field $\mathrm{GF}(p^m)$. The order must be a prime power.
+        characteristic: The characteristic $p$ of the field $\mathrm{GF}(p^m)$. The characteristic must
             be prime.
-        degree: The degree :math:`m` of the field :math:`\mathrm{GF}(p^m)`. The degree must be a positive integer.
-        irreducible_poly: Optionally specify an irreducible polynomial of degree :math:`m` over :math:`\mathrm{GF}(p)`
+        degree: The degree $m$ of the field $\mathrm{GF}(p^m)$. The degree must be a positive integer.
+        irreducible_poly: Optionally specify an irreducible polynomial of degree $m$ over $\mathrm{GF}(p)$
             that defines the finite field arithmetic. The default is `None` which uses the Conway polynomial
-            :math:`C_{p,m}`, see :func:`~galois.conway_poly`.
+            $C_{p,m}$, see :func:`~galois.conway_poly`.
         primitive_element: Optionally specify a primitive element of the field. This value is used when building the
             exponential and logarithm lookup tables and as the base of :obj:`numpy.log`. A primitive element is a
             generator of the multiplicative group of the field.
 
-            For prime fields :math:`\mathrm{GF}(p)`, the primitive element must be an integer and is a primitive root
-            modulo :math:`p`. The default is `None` which uses :func:`~galois.primitive_root`.
+            For prime fields $\mathrm{GF}(p)$, the primitive element must be an integer and is a primitive root
+            modulo $p$. The default is `None` which uses :func:`~galois.primitive_root`.
 
-            For extension fields :math:`\mathrm{GF}(p^m)`, the primitive element is a polynomial of degree less than
-            :math:`m` over :math:`\mathrm{GF}(p)`. The default is `None` which uses :func:`~galois.primitive_element`.
+            For extension fields $\mathrm{GF}(p^m)$, the primitive element is a polynomial of degree less than
+            $m$ over $\mathrm{GF}(p)$. The default is `None` which uses :func:`~galois.primitive_element`.
         verify: Indicates whether to verify that the user-provided irreducible polynomial is in fact irreducible and
             that the user-provided primitive element is in fact a generator of the multiplicative group.
             The default is `True`.
@@ -94,7 +94,7 @@ def GF(
             - `None` (default): For a newly-created :obj:`~galois.FieldArray` subclass, `None` corresponds to
               `"auto"`. If the :obj:`~galois.FieldArray` subclass already exists, `None` does not modify its current
               compilation mode.
-            - `"auto"`: Selects `"jit-lookup"` for fields with order less than :math:`2^{20}`, `"jit-calculate"` for
+            - `"auto"`: Selects `"jit-lookup"` for fields with order less than $2^{20}$, `"jit-calculate"` for
               larger fields, and `"python-calculate"` for fields whose elements cannot be represented with
               :obj:`numpy.int64`.
             - `"jit-lookup"`: JIT compiles arithmetic ufuncs to use Zech log, log, and anti-log lookup tables for
@@ -119,7 +119,7 @@ def GF(
             - `"power"`: Sets the element representation to the :ref:`power representation <power-repr>`.
 
     Returns:
-        A :obj:`~galois.FieldArray` subclass for :math:`\mathrm{GF}(p^m)`.
+        A :obj:`~galois.FieldArray` subclass for $\mathrm{GF}(p^m)$.
 
     Notes:
         :obj:`~galois.FieldArray` subclasses of the same type (order, irreducible polynomial, and primitive element)
@@ -152,7 +152,7 @@ def GF(
             .. md-tab-item:: GF(2^m)
 
                 Construct a binary extension field. Notice the default irreducible polynomial is primitive and
-                :math:`x` is a primitive element.
+                $x$ is a primitive element.
 
                 .. ipython:: python
 
@@ -161,7 +161,7 @@ def GF(
 
             .. md-tab-item:: GF(p^m)
 
-                Construct a prime extension field. Notice the default irreducible polynomial is primitive and :math:`x`
+                Construct a prime extension field. Notice the default irreducible polynomial is primitive and $x$
                 is a primitive element.
 
                 .. ipython:: python
@@ -175,8 +175,8 @@ def GF(
 
             .. md-tab-item:: GF(2^m)
 
-                Construct the :math:`\mathrm{GF}(2^8)` field that is used in AES. Notice the irreducible polynomial
-                is not primitive and :math:`x` is not a primitive element.
+                Construct the $\mathrm{GF}(2^8)$ field that is used in AES. Notice the irreducible polynomial
+                is not primitive and $x$ is not a primitive element.
 
                 .. ipython:: python
 
@@ -185,8 +185,8 @@ def GF(
 
             .. md-tab-item:: GF(p^m)
 
-                Construct :math:`\mathrm{GF}(3^5)` with an irreducible, but not primitive, polynomial. Notice that
-                :math:`x` is not a primitive element.
+                Construct $\mathrm{GF}(3^5)$ with an irreducible, but not primitive, polynomial. Notice that
+                $x$ is not a primitive element.
 
                 .. ipython:: python
 
@@ -215,8 +215,8 @@ def GF(
                     GF = galois.GF(2**100)
                     print(GF.properties)
 
-                The construction of large fields can be sped up by explicitly specifying :math:`p` and :math:`m`.
-                This avoids the need to factor the order :math:`p^m`.
+                The construction of large fields can be sped up by explicitly specifying $p$ and $m$.
+                This avoids the need to factor the order $p^m$.
 
                 .. ipython:: python
 
@@ -232,8 +232,8 @@ def GF(
                     GF = galois.GF(109987**4)
                     print(GF.properties)
 
-                The construction of large fields can be sped up by explicitly specifying :math:`p` and :math:`m`.
-                This avoids the need to factor the order :math:`p^m`.
+                The construction of large fields can be sped up by explicitly specifying $p$ and $m$.
+                This avoids the need to factor the order $p^m$.
 
                 .. ipython:: python
 
diff --git a/src/galois/_fields/_gf2.py b/src/galois/_fields/_gf2.py
index 9efa7ba7f..431a41c4c 100644
--- a/src/galois/_fields/_gf2.py
+++ b/src/galois/_fields/_gf2.py
@@ -118,7 +118,7 @@ class GF2(
     primitive_element=1,
 ):
     r"""
-    A :obj:`~galois.FieldArray` subclass over :math:`\mathrm{GF}(2)`.
+    A :obj:`~galois.FieldArray` subclass over $\mathrm{GF}(2)$.
 
     .. info::
 
diff --git a/src/galois/_fields/_meta.py b/src/galois/_fields/_meta.py
index ad07ca261..53a04bf91 100644
--- a/src/galois/_fields/_meta.py
+++ b/src/galois/_fields/_meta.py
@@ -100,10 +100,10 @@ def name(cls) -> str:
     @property
     def characteristic(cls) -> int:
         r"""
-        The prime characteristic :math:`p` of the Galois field :math:`\mathrm{GF}(p^m)`.
+        The prime characteristic $p$ of the Galois field $\mathrm{GF}(p^m)$.
 
         Notes:
-            Adding :math:`p` copies of any element will always result in 0.
+            Adding $p$ copies of any element will always result in 0.
 
         Examples:
             .. ipython:: python
@@ -118,7 +118,7 @@ def characteristic(cls) -> int:
     @property
     def degree(cls) -> int:
         r"""
-        The extension degree :math:`m` of the Galois field :math:`\mathrm{GF}(p^m)`.
+        The extension degree $m$ of the Galois field $\mathrm{GF}(p^m)$.
 
         Notes:
             The degree is a positive integer. For prime fields, the degree is 1.
@@ -136,7 +136,7 @@ def degree(cls) -> int:
     @property
     def order(cls) -> int:
         r"""
-        The order :math:`p^m` of the Galois field :math:`\mathrm{GF}(p^m)`.
+        The order $p^m$ of the Galois field $\mathrm{GF}(p^m)$.
 
         Notes:
             The order of the field is equal to the field's size.
@@ -154,10 +154,10 @@ def order(cls) -> int:
     @property
     def irreducible_poly(cls) -> Poly:
         r"""
-        The irreducible polynomial :math:`f(x)` of the Galois field :math:`\mathrm{GF}(p^m)`.
+        The irreducible polynomial $f(x)$ of the Galois field $\mathrm{GF}(p^m)$.
 
         Notes:
-            The irreducible polynomial is of degree :math:`m` over :math:`\mathrm{GF}(p)`.
+            The irreducible polynomial is of degree $m$ over $\mathrm{GF}(p)$.
 
         Examples:
             .. ipython:: python
@@ -175,10 +175,10 @@ def is_primitive_poly(cls) -> bool:
         Indicates whether the :obj:`~galois.FieldArray.irreducible_poly` is a primitive polynomial.
 
         Notes:
-            If the irreducible polynomial is primitive, then :math:`x` is a primitive element of the finite field.
+            If the irreducible polynomial is primitive, then $x$ is a primitive element of the finite field.
 
         Examples:
-            The default :math:`\mathrm{GF}(2^8)` field uses a primitive polynomial.
+            The default $\mathrm{GF}(2^8)$ field uses a primitive polynomial.
 
             .. ipython:: python
 
@@ -186,7 +186,7 @@ def is_primitive_poly(cls) -> bool:
                 print(GF.properties)
                 GF.is_primitive_poly
 
-            The :math:`\mathrm{GF}(2^8)` field from AES uses a non-primitive polynomial.
+            The $\mathrm{GF}(2^8)$ field from AES uses a non-primitive polynomial.
 
             .. ipython:: python
 
@@ -199,17 +199,17 @@ def is_primitive_poly(cls) -> bool:
     @property
     def elements(cls) -> FieldArray:
         r"""
-        All of the finite field's elements :math:`\{0, \dots, p^m-1\}`.
+        All of the finite field's elements $\{0, \dots, p^m-1\}$.
 
         Examples:
-            All elements of the prime field :math:`\mathrm{GF}(31)` in increasing order.
+            All elements of the prime field $\mathrm{GF}(31)$ in increasing order.
 
             .. ipython-with-reprs:: int,power
 
                 GF = galois.GF(31)
                 GF.elements
 
-            All elements of the extension field :math:`\mathrm{GF}(5^2)` in lexicographical order.
+            All elements of the extension field $\mathrm{GF}(5^2)$ in lexicographical order.
 
             .. ipython-with-reprs:: int,poly,power
 
@@ -227,20 +227,20 @@ def elements(cls) -> FieldArray:
     @property
     def units(cls) -> FieldArray:
         r"""
-        All of the finite field's units :math:`\{1, \dots, p^m-1\}`.
+        All of the finite field's units $\{1, \dots, p^m-1\}$.
 
         Notes:
             A unit is an element with a multiplicative inverse.
 
         Examples:
-            All units of the prime field :math:`\mathrm{GF}(31)` in increasing order.
+            All units of the prime field $\mathrm{GF}(31)$ in increasing order.
 
             .. ipython-with-reprs:: int,power
 
                 GF = galois.GF(31)
                 GF.units
 
-            All units of the extension field :math:`\mathrm{GF}(5^2)` in lexicographical order.
+            All units of the extension field $\mathrm{GF}(5^2)$ in lexicographical order.
 
             .. ipython-with-reprs:: int,poly,power
 
@@ -258,23 +258,23 @@ def units(cls) -> FieldArray:
     @property
     def primitive_element(cls) -> FieldArray:
         r"""
-        A primitive element :math:`\alpha` of the Galois field :math:`\mathrm{GF}(p^m)`.
+        A primitive element $\alpha$ of the Galois field $\mathrm{GF}(p^m)$.
 
         Notes:
             A primitive element is a multiplicative generator of the field, such that
-            :math:`\mathrm{GF}(p^m) = \{0, 1, \alpha, \alpha^2, \dots, \alpha^{p^m - 2}\}`. A primitive element is a
-            root of the primitive polynomial :math:`f(x)`, such that :math:`f(\alpha) = 0` over
-            :math:`\mathrm{GF}(p^m)`.
+            $\mathrm{GF}(p^m) = \{0, 1, \alpha, \alpha^2, \dots, \alpha^{p^m - 2}\}$. A primitive element is a
+            root of the primitive polynomial $f(x)$, such that $f(\alpha) = 0$ over
+            $\mathrm{GF}(p^m)$.
 
         Examples:
-            The smallest primitive element of the prime field :math:`\mathrm{GF}(31)`.
+            The smallest primitive element of the prime field $\mathrm{GF}(31)$.
 
             .. ipython-with-reprs:: int,power
 
                 GF = galois.GF(31)
                 GF.elements
 
-            The smallest primitive element of the extension field :math:`\mathrm{GF}(5^2)`.
+            The smallest primitive element of the extension field $\mathrm{GF}(5^2)$.
 
             .. ipython-with-reprs:: int,poly,power
 
@@ -292,23 +292,23 @@ def primitive_element(cls) -> FieldArray:
     @property
     def primitive_elements(cls) -> FieldArray:
         r"""
-        All primitive elements :math:`\alpha` of the Galois field :math:`\mathrm{GF}(p^m)`.
+        All primitive elements $\alpha$ of the Galois field $\mathrm{GF}(p^m)$.
 
         Notes:
             A primitive element is a multiplicative generator of the field, such that
-            :math:`\mathrm{GF}(p^m) = \{0, 1, \alpha, \alpha^2, \dots, \alpha^{p^m - 2}\}`. A primitive element is a
-            root of the primitive polynomial :math:`f(x)`, such that :math:`f(\alpha) = 0` over
-            :math:`\mathrm{GF}(p^m)`.
+            $\mathrm{GF}(p^m) = \{0, 1, \alpha, \alpha^2, \dots, \alpha^{p^m - 2}\}$. A primitive element is a
+            root of the primitive polynomial $f(x)$, such that $f(\alpha) = 0$ over
+            $\mathrm{GF}(p^m)$.
 
         Examples:
-            All primitive elements of the prime field :math:`\mathrm{GF}(31)` in increasing order.
+            All primitive elements of the prime field $\mathrm{GF}(31)$ in increasing order.
 
             .. ipython-with-reprs:: int,power
 
                 GF = galois.GF(31)
                 GF.elements
 
-            All primitive elements of the extension field :math:`\mathrm{GF}(5^2)` in lexicographical order.
+            All primitive elements of the extension field $\mathrm{GF}(5^2)$ in lexicographical order.
 
             .. ipython-with-reprs:: int,poly,power
 
@@ -333,8 +333,8 @@ def squares(cls) -> FieldArray:
         All squares in the finite field.
 
         Notes:
-            An element :math:`x` in :math:`\mathrm{GF}(p^m)` is a *square* if there exists a :math:`y` such that
-            :math:`y^2 = x` in the field.
+            An element $x$ in $\mathrm{GF}(p^m)$ is a *square* if there exists a $y$ such that
+            $y^2 = x$ in the field.
 
         See Also:
             is_square
@@ -379,8 +379,8 @@ def non_squares(cls) -> FieldArray:
         All non-squares in the Galois field.
 
         Notes:
-            An element :math:`x` in :math:`\mathrm{GF}(p^m)` is a *non-square* if there does not exist a :math:`y`
-            such that :math:`y^2 = x` in the field.
+            An element $x$ in $\mathrm{GF}(p^m)$ is a *non-square* if there does not exist a $y$
+            such that $y^2 = x$ in the field.
 
         See Also:
             is_square
@@ -443,10 +443,10 @@ def is_extension_field(cls) -> bool:
     @property
     def prime_subfield(cls) -> Type[FieldArray]:
         r"""
-        The prime subfield :math:`\mathrm{GF}(p)` of the extension field :math:`\mathrm{GF}(p^m)`.
+        The prime subfield $\mathrm{GF}(p)$ of the extension field $\mathrm{GF}(p^m)$.
 
         Notes:
-            For the prime field :math:`\mathrm{GF}(p)`, the prime subfield is itself.
+            For the prime field $\mathrm{GF}(p)$, the prime subfield is itself.
 
         Examples:
             .. ipython:: python
@@ -478,7 +478,7 @@ def dtypes(cls) -> list[np.dtype]:
                 GF = galois.GF(31); GF.dtypes
 
             Some data types are too small for certain finite fields, such as :obj:`numpy.int16` for
-            :math:`\mathrm{GF}(7^5)`.
+            $\mathrm{GF}(7^5)$.
 
             .. ipython:: python
 
@@ -544,14 +544,14 @@ def ufunc_mode(cls) -> Literal["jit-lookup", "jit-calculate", "python-calculate"
             The ufuncs may be recompiled with :func:`~galois.FieldArray.compile`.
 
         Examples:
-            Fields with order less than :math:`2^{20}` are compiled, by default, using lookup tables for speed.
+            Fields with order less than $2^{20}$ are compiled, by default, using lookup tables for speed.
 
             .. ipython:: python
 
                 galois.GF(65537).ufunc_mode
                 galois.GF(2**16).ufunc_mode
 
-            Fields with order greater than :math:`2^{20}` are compiled, by default, using explicit calculation for
+            Fields with order greater than $2^{20}$ are compiled, by default, using explicit calculation for
             memory savings. The field elements and arithmetic must still fit within :obj:`numpy.int64`.
 
             .. ipython:: python
@@ -617,14 +617,14 @@ def default_ufunc_mode(cls) -> Literal["jit-lookup", "jit-calculate", "python-ca
             The ufuncs may be recompiled with :func:`~galois.FieldArray.compile`.
 
         Examples:
-            Fields with order less than :math:`2^{20}` are compiled, by default, using lookup tables for speed.
+            Fields with order less than $2^{20}$ are compiled, by default, using lookup tables for speed.
 
             .. ipython:: python
 
                 galois.GF(65537).default_ufunc_mode
                 galois.GF(2**16).default_ufunc_mode
 
-            Fields with order greater than :math:`2^{20}` are compiled, by default, using explicit calculation for
+            Fields with order greater than $2^{20}$ are compiled, by default, using explicit calculation for
             memory savings. The field elements and arithmetic must still fit within :obj:`numpy.int64`.
 
             .. ipython:: python
diff --git a/src/galois/_fields/_primitive_element.py b/src/galois/_fields/_primitive_element.py
index 25c5425f6..28f724117 100644
--- a/src/galois/_fields/_primitive_element.py
+++ b/src/galois/_fields/_primitive_element.py
@@ -17,24 +17,24 @@
 @export
 def is_primitive_element(element: PolyLike, irreducible_poly: Poly) -> bool:
     r"""
-    Determines if :math:`g` is a primitive element of the Galois field :math:`\mathrm{GF}(q^m)` with
-    degree-:math:`m` irreducible polynomial :math:`f(x)` over :math:`\mathrm{GF}(q)`.
+    Determines if $g$ is a primitive element of the Galois field $\mathrm{GF}(q^m)$ with
+    degree-$m$ irreducible polynomial $f(x)$ over $\mathrm{GF}(q)$.
 
     Arguments:
-        element: An element :math:`g` of :math:`\mathrm{GF}(q^m)` is a polynomial over :math:`\mathrm{GF}(q)` with
-            degree less than :math:`m`.
-        irreducible_poly: The degree-:math:`m` irreducible polynomial :math:`f(x)` over :math:`\mathrm{GF}(q)` that
-            defines the extension field :math:`\mathrm{GF}(q^m)`.
+        element: An element $g$ of $\mathrm{GF}(q^m)$ is a polynomial over $\mathrm{GF}(q)$ with
+            degree less than $m$.
+        irreducible_poly: The degree-$m$ irreducible polynomial $f(x)$ over $\mathrm{GF}(q)$ that
+            defines the extension field $\mathrm{GF}(q^m)$.
 
     Returns:
-        `True` if :math:`g` is a primitive element of :math:`\mathrm{GF}(q^m)`.
+        `True` if $g$ is a primitive element of $\mathrm{GF}(q^m)$.
 
     See Also:
         primitive_element, FieldArray.primitive_element
 
     Examples:
-        In the extension field :math:`\mathrm{GF}(3^4)`, the element :math:`x + 2` is a primitive element whose
-        order is :math:`3^4 - 1 = 80`.
+        In the extension field $\mathrm{GF}(3^4)$, the element $x + 2$ is a primitive element whose
+        order is $3^4 - 1 = 80$.
 
         .. ipython:: python
 
@@ -43,7 +43,7 @@ def is_primitive_element(element: PolyLike, irreducible_poly: Poly) -> bool:
             galois.is_primitive_element("x + 2", f)
             GF("x + 2").multiplicative_order()
 
-        However, the element :math:`x + 1` is not a primitive element, as noted by its order being only 20.
+        However, the element $x + 1$ is not a primitive element, as noted by its order being only 20.
 
         .. ipython:: python
 
@@ -103,23 +103,23 @@ def _is_primitive_element(element: Poly, irreducible_poly: Poly) -> bool:
 @export
 def primitive_element(irreducible_poly: Poly, method: Literal["min", "max", "random"] = "min") -> Poly:
     r"""
-    Finds a primitive element :math:`g` of the Galois field :math:`\mathrm{GF}(q^m)` with degree-:math:`m`
-    irreducible polynomial :math:`f(x)` over :math:`\mathrm{GF}(q)`.
+    Finds a primitive element $g$ of the Galois field $\mathrm{GF}(q^m)$ with degree-$m$
+    irreducible polynomial $f(x)$ over $\mathrm{GF}(q)$.
 
     Arguments:
-        irreducible_poly: The degree-:math:`m` irreducible polynomial :math:`f(x)` over :math:`\mathrm{GF}(q)` that
-            defines the extension field :math:`\mathrm{GF}(q^m)`.
+        irreducible_poly: The degree-$m$ irreducible polynomial $f(x)$ over $\mathrm{GF}(q)$ that
+            defines the extension field $\mathrm{GF}(q^m)$.
         method: The search method for finding the primitive element.
 
     Returns:
-        A primitive element :math:`g` of :math:`\mathrm{GF}(q^m)` with irreducible polynomial :math:`f(x)`.
-        The primitive element :math:`g` is a polynomial over :math:`\mathrm{GF}(q)` with degree less than :math:`m`.
+        A primitive element $g$ of $\mathrm{GF}(q^m)$ with irreducible polynomial $f(x)$.
+        The primitive element $g$ is a polynomial over $\mathrm{GF}(q)$ with degree less than $m$.
 
     See Also:
         is_primitive_element, FieldArray.primitive_element
 
     Examples:
-        Construct the extension field :math:`\mathrm{GF}(7^5)`.
+        Construct the extension field $\mathrm{GF}(7^5)$.
 
         .. ipython:: python
 
@@ -127,8 +127,8 @@ def primitive_element(irreducible_poly: Poly, method: Literal["min", "max", "ran
             GF = galois.GF(7**5, irreducible_poly=f, repr="poly")
             print(GF.properties)
 
-        Find the smallest primitive element for the degree-5 extension of :math:`\mathrm{GF}(7)` with irreducible
-        polynomial :math:`f(x)`.
+        Find the smallest primitive element for the degree-5 extension of $\mathrm{GF}(7)$ with irreducible
+        polynomial $f(x)$.
 
         .. ipython:: python
 
@@ -137,8 +137,8 @@ def primitive_element(irreducible_poly: Poly, method: Literal["min", "max", "ran
             g = GF(int(g)); g
             g.multiplicative_order() == GF.order - 1
 
-        Find the largest primitive element for the degree-5 extension of :math:`\mathrm{GF}(7)` with irreducible
-        polynomial :math:`f(x)`.
+        Find the largest primitive element for the degree-5 extension of $\mathrm{GF}(7)$ with irreducible
+        polynomial $f(x)$.
 
         .. ipython:: python
 
@@ -147,8 +147,8 @@ def primitive_element(irreducible_poly: Poly, method: Literal["min", "max", "ran
             g = GF(int(g)); g
             g.multiplicative_order() == GF.order - 1
 
-        Find a random primitive element for the degree-5 extension of :math:`\mathrm{GF}(7)` with irreducible
-        polynomial :math:`f(x)`.
+        Find a random primitive element for the degree-5 extension of $\mathrm{GF}(7)$ with irreducible
+        polynomial $f(x)$.
 
         .. ipython:: python
 
@@ -205,26 +205,26 @@ def primitive_element(irreducible_poly: Poly, method: Literal["min", "max", "ran
 @export
 def primitive_elements(irreducible_poly: Poly) -> list[Poly]:
     r"""
-    Finds all primitive elements :math:`g` of the Galois field :math:`\mathrm{GF}(q^m)` with
-    degree-:math:`m` irreducible polynomial :math:`f(x)` over :math:`\mathrm{GF}(q)`.
+    Finds all primitive elements $g$ of the Galois field $\mathrm{GF}(q^m)$ with
+    degree-$m$ irreducible polynomial $f(x)$ over $\mathrm{GF}(q)$.
 
     Arguments:
-        irreducible_poly: The degree-:math:`m` irreducible polynomial :math:`f(x)` over :math:`\mathrm{GF}(q)` that
-            defines the extension field :math:`\mathrm{GF}(q^m)`.
+        irreducible_poly: The degree-$m$ irreducible polynomial $f(x)$ over $\mathrm{GF}(q)$ that
+            defines the extension field $\mathrm{GF}(q^m)$.
 
     Returns:
-        List of all primitive elements of :math:`\mathrm{GF}(q^m)` with irreducible polynomial :math:`f(x)`.
-        Each primitive element :math:`g` is a polynomial over :math:`\mathrm{GF}(q)` with degree less than :math:`m`.
+        List of all primitive elements of $\mathrm{GF}(q^m)$ with irreducible polynomial $f(x)$.
+        Each primitive element $g$ is a polynomial over $\mathrm{GF}(q)$ with degree less than $m$.
 
     See Also:
         is_primitive_element, FieldArray.primitive_elements
 
     Notes:
-        The number of primitive elements of :math:`\mathrm{GF}(q^m)` is :math:`\phi(q^m - 1)`, where
-        :math:`\phi(n)` is the Euler totient function. See :obj:`~galois.euler_phi`.
+        The number of primitive elements of $\mathrm{GF}(q^m)$ is $\phi(q^m - 1)$, where
+        $\phi(n)$ is the Euler totient function. See :obj:`~galois.euler_phi`.
 
     Examples:
-        Construct the extension field :math:`\mathrm{GF}(3^4)`.
+        Construct the extension field $\mathrm{GF}(3^4)$.
 
         .. ipython:: python
 
@@ -232,20 +232,20 @@ def primitive_elements(irreducible_poly: Poly) -> list[Poly]:
             GF = galois.GF(3**4, irreducible_poly=f, repr="poly")
             print(GF.properties)
 
-        Find all primitive elements for the degree-4 extension of :math:`\mathrm{GF}(3)`.
+        Find all primitive elements for the degree-4 extension of $\mathrm{GF}(3)$.
 
         .. ipython:: python
 
             g = galois.primitive_elements(f); g
 
-        The number of primitive elements is given by :math:`\phi(q^m - 1)`.
+        The number of primitive elements is given by $\phi(q^m - 1)$.
 
         .. ipython:: python
 
             phi = galois.euler_phi(3**4 - 1); phi
             len(g) == phi
 
-        Shows that each primitive element has an order of :math:`q^m - 1`.
+        Shows that each primitive element has an order of $q^m - 1$.
 
         .. ipython:: python
 
diff --git a/src/galois/_lfsr.py b/src/galois/_lfsr.py
index eee610dc9..9709204d2 100644
--- a/src/galois/_lfsr.py
+++ b/src/galois/_lfsr.py
@@ -181,19 +181,16 @@ class FLFSR(_LFSR):
     A Fibonacci linear-feedback shift register (LFSR).
 
     Notes:
-        A Fibonacci LFSR is defined by its feedback polynomial :math:`f(x)`.
+        A Fibonacci LFSR is defined by its feedback polynomial $f(x)$.
 
-        .. math::
-            f(x) = -c_{0}x^{n} - c_{1}x^{n-1} - \dots - c_{n-2}x^{2} - c_{n-1}x + 1 = x^n c(x^{-1})
+        $$f(x) = -c_{0}x^{n} - c_{1}x^{n-1} - \dots - c_{n-2}x^{2} - c_{n-1}x + 1 = x^n c(x^{-1})$$
 
-        The feedback polynomial is the reciprocal of the characteristic polynomial :math:`c(x)` of the linear recurrent
-        sequence :math:`y` produced by the Fibonacci LFSR.
+        The feedback polynomial is the reciprocal of the characteristic polynomial $c(x)$ of the linear recurrent
+        sequence $y$ produced by the Fibonacci LFSR.
 
-        .. math::
-            c(x) = x^{n} - c_{n-1}x^{n-1} - c_{n-2}x^{n-2} - \dots - c_{1}x - c_{0}
+        $$c(x) = x^{n} - c_{n-1}x^{n-1} - c_{n-2}x^{n-2} - \dots - c_{1}x - c_{0}$$
 
-        .. math::
-            y_t = c_{n-1}y_{t-1} + c_{n-2}y_{t-2} + \dots + c_{1}y_{t-n+2} + c_{0}y_{t-n+1}
+        $$y_t = c_{n-1}y_{t-1} + c_{n-2}y_{t-2} + \dots + c_{1}y_{t-n+2} + c_{0}y_{t-n+1}$$
 
         .. code-block:: text
            :caption: Fibonacci LFSR Configuration
@@ -207,12 +204,12 @@ class FLFSR(_LFSR):
                +--------+     +--------+                    +--------+
                 y[t+n-1]       y[t+n-2]                       y[t+1]
 
-        The shift register taps :math:`T` are defined left-to-right as :math:`T = [T_0, T_1, \dots, T_{n-2}, T_{n-1}]`.
-        The state vector :math:`S` is also defined left-to-right as :math:`S = [S_0, S_1, \dots, S_{n-2}, S_{n-1}]`.
+        The shift register taps $T$ are defined left-to-right as $T = [T_0, T_1, \dots, T_{n-2}, T_{n-1}]$.
+        The state vector $S$ is also defined left-to-right as $S = [S_0, S_1, \dots, S_{n-2}, S_{n-1}]$.
 
-        In the Fibonacci configuration, the shift register taps are :math:`T = [c_{n-1}, c_{n-2}, \dots, c_1, c_0]`.
-        Additionally, the state vector is equal to the next :math:`n` outputs in reversed order, namely
-        :math:`S = [y_{t+n-1}, y_{t+n-2}, \dots, y_{t+2}, y_{t+1}]`.
+        In the Fibonacci configuration, the shift register taps are $T = [c_{n-1}, c_{n-2}, \dots, c_1, c_0]$.
+        Additionally, the state vector is equal to the next $n$ outputs in reversed order, namely
+        $S = [y_{t+n-1}, y_{t+n-2}, \dots, y_{t+2}, y_{t+1}]$.
 
     References:
         - Gardner, D. 2019. “Applications of the Galois Model LFSR in Cryptography”. figshare.
@@ -227,7 +224,7 @@ class FLFSR(_LFSR):
             .. md-tab-item:: GF(2)
 
                 Create a Fibonacci LFSR from a degree-4 primitive characteristic polynomial over
-                :math:`\mathrm{GF}(2)`.
+                $\mathrm{GF}(2)$.
 
                 .. ipython:: python
 
@@ -246,7 +243,7 @@ class FLFSR(_LFSR):
             .. md-tab-item:: GF(p)
 
                 Create a Fibonacci LFSR from a degree-4 primitive characteristic polynomial over
-                :math:`\mathrm{GF}(7)`.
+                $\mathrm{GF}(7)$.
 
                 .. ipython:: python
 
@@ -265,7 +262,7 @@ class FLFSR(_LFSR):
             .. md-tab-item:: GF(2^m)
 
                 Create a Fibonacci LFSR from a degree-4 primitive characteristic polynomial over
-                :math:`\mathrm{GF}(2^3)`.
+                $\mathrm{GF}(2^3)$.
 
                 .. ipython:: python
 
@@ -284,7 +281,7 @@ class FLFSR(_LFSR):
             .. md-tab-item:: GF(p^m)
 
                 Create a Fibonacci LFSR from a degree-4 primitive characteristic polynomial over
-                :math:`\mathrm{GF}(3^3)`.
+                $\mathrm{GF}(3^3)$.
 
                 .. ipython:: python
 
@@ -312,35 +309,35 @@ def __init__(
         state: ArrayLike | None = None,
     ):
         r"""
-        Constructs a Fibonacci LFSR from its feedback polynomial :math:`f(x)`.
+        Constructs a Fibonacci LFSR from its feedback polynomial $f(x)$.
 
         Arguments:
             feedback_poly: The feedback polynomial
-                :math:`f(x) = -c_{0}x^{n} - c_{1}x^{n-1} - \dots - c_{n-2}x^{2} - c_{n-1}x + 1`.
-            state: The initial state vector :math:`S = [S_0, S_1, \dots, S_{n-2}, S_{n-1}]`. The default is `None`
+                $f(x) = -c_{0}x^{n} - c_{1}x^{n-1} - \dots - c_{n-2}x^{2} - c_{n-1}x + 1$.
+            state: The initial state vector $S = [S_0, S_1, \dots, S_{n-2}, S_{n-1}]$. The default is `None`
                 which corresponds to all ones.
 
         See Also:
             irreducible_poly, primitive_poly
 
         Notes:
-            A Fibonacci LFSR may be constructed from its characteristic polynomial :math:`c(x)` by passing in its
-            reciprocal as the feedback polynomial. This is because :math:`f(x) = x^n c(x^{-1})`.
+            A Fibonacci LFSR may be constructed from its characteristic polynomial $c(x)$ by passing in its
+            reciprocal as the feedback polynomial. This is because $f(x) = x^n c(x^{-1})$.
         """
         super().__init__(feedback_poly, state=state)
 
     @classmethod
     def Taps(cls, taps: FieldArray, state: ArrayLike | None = None) -> Self:
         r"""
-        Constructs a Fibonacci LFSR from its taps :math:`T = [c_{n-1}, c_{n-2}, \dots, c_1, c_0]`.
+        Constructs a Fibonacci LFSR from its taps $T = [c_{n-1}, c_{n-2}, \dots, c_1, c_0]$.
 
         Arguments:
-            taps: The shift register taps :math:`T = [c_{n-1}, c_{n-2}, \dots, c_1, c_0]`.
-            state: The initial state vector :math:`S = [S_0, S_1, \dots, S_{n-2}, S_{n-1}]`. The default is `None`
+            taps: The shift register taps $T = [c_{n-1}, c_{n-2}, \dots, c_1, c_0]$.
+            state: The initial state vector $S = [S_0, S_1, \dots, S_{n-2}, S_{n-1}]$. The default is `None`
                 which corresponds to all ones.
 
         Returns:
-            A Fibonacci LFSR with taps :math:`T = [c_{n-1}, c_{n-2}, \dots, c_1, c_0]`.
+            A Fibonacci LFSR with taps $T = [c_{n-1}, c_{n-2}, \dots, c_1, c_0]$.
 
         Examples:
             .. ipython:: python
@@ -392,7 +389,7 @@ def reset(self, state: ArrayLike | None = None):
         Resets the Fibonacci LFSR state to the specified state.
 
         Arguments:
-            state: The state vector :math:`S = [S_0, S_1, \dots, S_{n-2}, S_{n-1}]`. The default is `None` which
+            state: The state vector $S = [S_0, S_1, \dots, S_{n-2}, S_{n-1}]$. The default is `None` which
                 corresponds to the initial state.
 
         Examples:
@@ -443,7 +440,7 @@ def step(self, steps: int = 1) -> FieldArray:
             An array of output symbols of type :obj:`field` with size `abs(steps)`.
 
         Examples:
-            Step the Fibonacci LFSR one output at a time. Notice the first :math:`n` outputs of a Fibonacci LFSR are
+            Step the Fibonacci LFSR one output at a time. Notice the first $n$ outputs of a Fibonacci LFSR are
             its state reversed.
 
             .. ipython:: python
@@ -547,11 +544,11 @@ def field(self) -> Type[FieldArray]:
     @property
     def feedback_poly(self) -> Poly:
         r"""
-        The feedback polynomial :math:`f(x) = -c_{0}x^{n} - c_{1}x^{n-1} - \dots - c_{n-2}x^{2} - c_{n-1}x + 1`
+        The feedback polynomial $f(x) = -c_{0}x^{n} - c_{1}x^{n-1} - \dots - c_{n-2}x^{2} - c_{n-1}x + 1$
         that defines the feedback arithmetic.
 
         Notes:
-            The feedback polynomial is the reciprocal of the characteristic polynomial :math:`f(x) = x^n c(x^{-1})`.
+            The feedback polynomial is the reciprocal of the characteristic polynomial $f(x) = x^n c(x^{-1})$.
 
         Examples:
             .. ipython:: python
@@ -572,11 +569,11 @@ def feedback_poly(self) -> Poly:
     @property
     def characteristic_poly(self) -> Poly:
         r"""
-        The characteristic polynomial :math:`c(x) = x^{n} - c_{n-1}x^{n-1} - c_{n-2}x^{n-2} - \dots - c_{1}x - c_{0}`
+        The characteristic polynomial $c(x) = x^{n} - c_{n-1}x^{n-1} - c_{n-2}x^{n-2} - \dots - c_{1}x - c_{0}$
         that defines the linear recurrent sequence.
 
         Notes:
-            The characteristic polynomial is the reciprocal of the feedback polynomial :math:`c(x) = x^n f(x^{-1})`.
+            The characteristic polynomial is the reciprocal of the feedback polynomial $c(x) = x^n f(x^{-1})$.
 
         Examples:
             .. ipython:: python
@@ -597,7 +594,7 @@ def characteristic_poly(self) -> Poly:
     @property
     def taps(self) -> FieldArray:
         r"""
-        The shift register taps :math:`T = [c_{n-1}, c_{n-2}, \dots, c_1, c_0]`. The taps of the shift register define
+        The shift register taps $T = [c_{n-1}, c_{n-2}, \dots, c_1, c_0]$. The taps of the shift register define
         the linear recurrence relation.
 
         Examples:
@@ -621,7 +618,7 @@ def order(self) -> int:
     @property
     def initial_state(self) -> FieldArray:
         r"""
-        The initial state vector :math:`S = [S_0, S_1, \dots, S_{n-2}, S_{n-1}]`.
+        The initial state vector $S = [S_0, S_1, \dots, S_{n-2}, S_{n-1}]$.
 
         Examples:
             .. ipython:: python
@@ -648,7 +645,7 @@ def initial_state(self) -> FieldArray:
     @property
     def state(self) -> FieldArray:
         r"""
-        The current state vector :math:`S = [S_0, S_1, \dots, S_{n-2}, S_{n-1}]`.
+        The current state vector $S = [S_0, S_1, \dots, S_{n-2}, S_{n-1}]$.
 
         Examples:
             .. ipython:: python
@@ -814,19 +811,16 @@ class GLFSR(_LFSR):
     A Galois linear-feedback shift register (LFSR).
 
     Notes:
-        A Galois LFSR is defined by its feedback polynomial :math:`f(x)`.
+        A Galois LFSR is defined by its feedback polynomial $f(x)$.
 
-        .. math::
-            f(x) = -c_{0}x^{n} - c_{1}x^{n-1} - \dots - c_{n-2}x^{2} - c_{n-1}x + 1 = x^n c(x^{-1})
+        $$f(x) = -c_{0}x^{n} - c_{1}x^{n-1} - \dots - c_{n-2}x^{2} - c_{n-1}x + 1 = x^n c(x^{-1})$$
 
-        The feedback polynomial is the reciprocal of the characteristic polynomial :math:`c(x)` of the linear recurrent
-        sequence :math:`y` produced by the Galois LFSR.
+        The feedback polynomial is the reciprocal of the characteristic polynomial $c(x)$ of the linear recurrent
+        sequence $y$ produced by the Galois LFSR.
 
-        .. math::
-            c(x) = x^{n} - c_{n-1}x^{n-1} - c_{n-2}x^{n-2} - \dots - c_{1}x - c_{0}
+        $$c(x) = x^{n} - c_{n-1}x^{n-1} - c_{n-2}x^{n-2} - \dots - c_{1}x - c_{0}$$
 
-        .. math::
-            y_t = c_{n-1}y_{t-1} + c_{n-2}y_{t-2} + \dots + c_{1}y_{t-n+2} + c_{0}y_{t-n+1}
+        $$y_t = c_{n-1}y_{t-1} + c_{n-2}y_{t-2} + \dots + c_{1}y_{t-n+2} + c_{0}y_{t-n+1}$$
 
         .. code-block:: text
            :caption: Galois LFSR Configuration
@@ -840,10 +834,10 @@ class GLFSR(_LFSR):
                +--------+     +--------+                    +--------+
                                                               y[t+1]
 
-        The shift register taps :math:`T` are defined left-to-right as :math:`T = [T_0, T_1, \dots, T_{n-2}, T_{n-1}]`.
-        The state vector :math:`S` is also defined left-to-right as :math:`S = [S_0, S_1, \dots, S_{n-2}, S_{n-1}]`.
+        The shift register taps $T$ are defined left-to-right as $T = [T_0, T_1, \dots, T_{n-2}, T_{n-1}]$.
+        The state vector $S$ is also defined left-to-right as $S = [S_0, S_1, \dots, S_{n-2}, S_{n-1}]$.
 
-        In the Galois configuration, the shift register taps are :math:`T = [c_0, c_1, \dots, c_{n-2}, c_{n-1}]`.
+        In the Galois configuration, the shift register taps are $T = [c_0, c_1, \dots, c_{n-2}, c_{n-1}]$.
 
     References:
         - Gardner, D. 2019. “Applications of the Galois Model LFSR in Cryptography”. figshare.
@@ -857,7 +851,7 @@ class GLFSR(_LFSR):
 
             .. md-tab-item:: GF(2)
 
-                Create a Galois LFSR from a degree-4 primitive characteristic polynomial over :math:`\mathrm{GF}(2)`.
+                Create a Galois LFSR from a degree-4 primitive characteristic polynomial over $\mathrm{GF}(2)$.
 
                 .. ipython:: python
 
@@ -876,7 +870,7 @@ class GLFSR(_LFSR):
             .. md-tab-item:: GF(p)
 
                 Create a Galois LFSR from a degree-4 primitive characteristic polynomial over
-                :math:`\mathrm{GF}(7)`.
+                $\mathrm{GF}(7)$.
 
                 .. ipython:: python
 
@@ -895,7 +889,7 @@ class GLFSR(_LFSR):
             .. md-tab-item:: GF(2^m)
 
                 Create a Galois LFSR from a degree-4 primitive characteristic polynomial over
-                :math:`\mathrm{GF}(2^3)`.
+                $\mathrm{GF}(2^3)$.
 
                 .. ipython:: python
 
@@ -914,7 +908,7 @@ class GLFSR(_LFSR):
             .. md-tab-item:: GF(p^m)
 
                 Create a Galois LFSR from a degree-4 primitive characteristic polynomial over
-                :math:`\mathrm{GF}(3^3)`.
+                $\mathrm{GF}(3^3)$.
 
                 .. ipython:: python
 
@@ -942,35 +936,35 @@ def __init__(
         state: ArrayLike | None = None,
     ):
         r"""
-        Constructs a Galois LFSR from its feedback polynomial :math:`f(x)`.
+        Constructs a Galois LFSR from its feedback polynomial $f(x)$.
 
         Arguments:
             feedback_poly: The feedback polynomial
-                :math:`f(x) = -c_{0}x^{n} - c_{1}x^{n-1} - \dots - c_{n-2}x^{2} - c_{n-1}x + 1`.
-            state: The initial state vector :math:`S = [S_0, S_1, \dots, S_{n-2}, S_{n-1}]`. The default is `None`
+                $f(x) = -c_{0}x^{n} - c_{1}x^{n-1} - \dots - c_{n-2}x^{2} - c_{n-1}x + 1$.
+            state: The initial state vector $S = [S_0, S_1, \dots, S_{n-2}, S_{n-1}]$. The default is `None`
                 which corresponds to all ones.
 
         See Also:
             irreducible_poly, primitive_poly
 
         Notes:
-            A Galois LFSR may be constructed from its characteristic polynomial :math:`c(x)` by passing in its
-            reciprocal as the feedback polynomial. This is because :math:`f(x) = x^n c(x^{-1})`.
+            A Galois LFSR may be constructed from its characteristic polynomial $c(x)$ by passing in its
+            reciprocal as the feedback polynomial. This is because $f(x) = x^n c(x^{-1})$.
         """
         super().__init__(feedback_poly, state=state)
 
     @classmethod
     def Taps(cls, taps: FieldArray, state: ArrayLike | None = None) -> Self:
         r"""
-        Constructs a Galois LFSR from its taps :math:`T = [c_0, c_1, \dots, c_{n-2}, c_{n-1}]`.
+        Constructs a Galois LFSR from its taps $T = [c_0, c_1, \dots, c_{n-2}, c_{n-1}]$.
 
         Arguments:
-            taps: The shift register taps :math:`T = [c_0, c_1, \dots, c_{n-2}, c_{n-1}]`.
-            state: The initial state vector :math:`S = [S_0, S_1, \dots, S_{n-2}, S_{n-1}]`. The default is `None`
+            taps: The shift register taps $T = [c_0, c_1, \dots, c_{n-2}, c_{n-1}]$.
+            state: The initial state vector $S = [S_0, S_1, \dots, S_{n-2}, S_{n-1}]$. The default is `None`
                 which corresponds to all ones.
 
         Returns:
-            A Galois LFSR with taps :math:`T = [c_0, c_1, \dots, c_{n-2}, c_{n-1}]`.
+            A Galois LFSR with taps $T = [c_0, c_1, \dots, c_{n-2}, c_{n-1}]$.
 
         Examples:
             .. ipython:: python
@@ -1022,7 +1016,7 @@ def reset(self, state: ArrayLike | None = None):
         Resets the Galois LFSR state to the specified state.
 
         Arguments:
-            state: The state vector :math:`S = [S_0, S_1, \dots, S_{n-2}, S_{n-1}]`. The default is `None` which
+            state: The state vector $S = [S_0, S_1, \dots, S_{n-2}, S_{n-1}]$. The default is `None` which
                 corresponds to the initial state.
 
         Examples:
@@ -1163,11 +1157,11 @@ def field(self) -> Type[FieldArray]:
     @property
     def feedback_poly(self) -> Poly:
         r"""
-        The feedback polynomial :math:`f(x) = -c_{0}x^{n} - c_{1}x^{n-1} - \dots - c_{n-2}x^{2} - c_{n-1}x + 1`
+        The feedback polynomial $f(x) = -c_{0}x^{n} - c_{1}x^{n-1} - \dots - c_{n-2}x^{2} - c_{n-1}x + 1$
         that defines the feedback arithmetic.
 
         Notes:
-            The feedback polynomial is the reciprocal of the characteristic polynomial :math:`f(x) = x^n c(x^{-1})`.
+            The feedback polynomial is the reciprocal of the characteristic polynomial $f(x) = x^n c(x^{-1})$.
 
         Examples:
             .. ipython:: python
@@ -1188,11 +1182,11 @@ def feedback_poly(self) -> Poly:
     @property
     def characteristic_poly(self) -> Poly:
         r"""
-        The characteristic polynomial :math:`c(x) = x^{n} - c_{n-1}x^{n-1} - c_{n-2}x^{n-2} - \dots - c_{1}x - c_{0}`
+        The characteristic polynomial $c(x) = x^{n} - c_{n-1}x^{n-1} - c_{n-2}x^{n-2} - \dots - c_{1}x - c_{0}$
         that defines the linear recurrent sequence.
 
         Notes:
-            The characteristic polynomial is the reciprocal of the feedback polynomial :math:`c(x) = x^n f(x^{-1})`.
+            The characteristic polynomial is the reciprocal of the feedback polynomial $c(x) = x^n f(x^{-1})$.
 
         Examples:
             .. ipython:: python
@@ -1213,7 +1207,7 @@ def characteristic_poly(self) -> Poly:
     @property
     def taps(self) -> FieldArray:
         r"""
-        The shift register taps :math:`T = [c_0, c_1, \dots, c_{n-2}, c_{n-1}]`. The taps of the shift register define
+        The shift register taps $T = [c_0, c_1, \dots, c_{n-2}, c_{n-1}]$. The taps of the shift register define
         the linear recurrence relation.
 
         Examples:
@@ -1237,7 +1231,7 @@ def order(self) -> int:
     @property
     def initial_state(self) -> FieldArray:
         r"""
-        The initial state vector :math:`S = [S_0, S_1, \dots, S_{n-2}, S_{n-1}]`.
+        The initial state vector $S = [S_0, S_1, \dots, S_{n-2}, S_{n-1}]$.
 
         Examples:
             .. ipython:: python
@@ -1264,7 +1258,7 @@ def initial_state(self) -> FieldArray:
     @property
     def state(self) -> FieldArray:
         r"""
-        The current state vector :math:`S = [S_0, S_1, \dots, S_{n-2}, S_{n-1}]`.
+        The current state vector $S = [S_0, S_1, \dots, S_{n-2}, S_{n-1}]$.
 
         Examples:
             .. ipython:: python
@@ -1440,33 +1434,31 @@ def berlekamp_massey(sequence: FieldArray, output: Literal["galois"]) -> GLFSR:
 @export
 def berlekamp_massey(sequence, output="minimal"):
     r"""
-    Finds the minimal polynomial :math:`c(x)` that produces the linear recurrent sequence :math:`y`.
+    Finds the minimal polynomial $c(x)$ that produces the linear recurrent sequence $y$.
 
     This function implements the Berlekamp-Massey algorithm.
 
     Arguments:
-        sequence: A linear recurrent sequence :math:`y` in :math:`\mathrm{GF}(p^m)`.
+        sequence: A linear recurrent sequence $y$ in $\mathrm{GF}(p^m)$.
         output: The output object type.
 
             - `"minimal"` (default): Returns the minimal polynomial that generates the linear recurrent sequence.
-              The minimal polynomial is a characteristic polynomial :math:`c(x)` of minimal degree.
-            - `"fibonacci"`: Returns a Fibonacci LFSR that produces :math:`y`.
-            - `"galois"`: Returns a Galois LFSR that produces :math:`y`.
+              The minimal polynomial is a characteristic polynomial $c(x)$ of minimal degree.
+            - `"fibonacci"`: Returns a Fibonacci LFSR that produces $y$.
+            - `"galois"`: Returns a Galois LFSR that produces $y$.
 
     Returns:
-        The minimal polynomial :math:`c(x)`, a Fibonacci LFSR, or a Galois LFSR, depending on the value of `output`.
+        The minimal polynomial $c(x)$, a Fibonacci LFSR, or a Galois LFSR, depending on the value of `output`.
 
     Notes:
-        The minimal polynomial is the characteristic polynomial :math:`c(x)` of minimal degree that produces the
-        linear recurrent sequence :math:`y`.
+        The minimal polynomial is the characteristic polynomial $c(x)$ of minimal degree that produces the
+        linear recurrent sequence $y$.
 
-        .. math::
-            c(x) = x^{n} - c_{n-1}x^{n-1} - c_{n-2}x^{n-2} - \dots - c_{1}x - c_{0}
+        $$c(x) = x^{n} - c_{n-1}x^{n-1} - c_{n-2}x^{n-2} - \dots - c_{1}x - c_{0}$$
 
-        .. math::
-            y_t = c_{n-1}y_{t-1} + c_{n-2}y_{t-2} + \dots + c_{1}y_{t-n+2} + c_{0}y_{t-n+1}
+        $$y_t = c_{n-1}y_{t-1} + c_{n-2}y_{t-2} + \dots + c_{1}y_{t-n+2} + c_{0}y_{t-n+1}$$
 
-        For a linear sequence with order :math:`n`, at least :math:`2n` output symbols are required to determine the
+        For a linear sequence with order $n$, at least $2n$ output symbols are required to determine the
         minimal polynomial.
 
     References:
@@ -1476,14 +1468,14 @@ def berlekamp_massey(sequence, output="minimal"):
         - https://crypto.stanford.edu/~mironov/cs359/massey.pdf
 
     Examples:
-        The sequence below is a degree-4 linear recurrent sequence over :math:`\mathrm{GF}(7)`.
+        The sequence below is a degree-4 linear recurrent sequence over $\mathrm{GF}(7)$.
 
         .. ipython:: python
 
             GF = galois.GF(7)
             y = GF([5, 5, 1, 3, 1, 4, 6, 6, 5, 5])
 
-        The characteristic polynomial is :math:`c(x) = x^4 + x^2 + 3x + 5` over :math:`\mathrm{GF}(7)`.
+        The characteristic polynomial is $c(x) = x^4 + x^2 + 3x + 5$ over $\mathrm{GF}(7)$.
 
         .. ipython:: python
 
diff --git a/src/galois/_math.py b/src/galois/_math.py
index 6113fa9ba..22ce1c18b 100644
--- a/src/galois/_math.py
+++ b/src/galois/_math.py
@@ -76,7 +76,7 @@ def prod(*args: int) -> int:
 @export
 def isqrt(n: int) -> int:
     r"""
-    Computes :math:`x = \lfloor\sqrt{n}\rfloor` such that :math:`x^2 \le n < (x + 1)^2`.
+    Computes $x = \lfloor\sqrt{n}\rfloor$ such that $x^2 \le n < (x + 1)^2$.
 
     .. info::
 
@@ -87,7 +87,7 @@ def isqrt(n: int) -> int:
         n: A non-negative integer.
 
     Returns:
-        The integer square root of :math:`n`.
+        The integer square root of $n$.
 
     See Also:
         iroot, ilog
@@ -124,14 +124,14 @@ def isqrt(n: int) -> int:
 @export
 def iroot(n: int, k: int) -> int:
     r"""
-    Computes :math:`x = \lfloor n^{\frac{1}{k}} \rfloor` such that :math:`x^k \le n < (x + 1)^k`.
+    Computes $x = \lfloor n^{\frac{1}{k}} \rfloor$ such that $x^k \le n < (x + 1)^k$.
 
     Arguments:
         n: A non-negative integer.
-        k: The positive root :math:`k`.
+        k: The positive root $k$.
 
     Returns:
-        The integer :math:`k`-th root of :math:`n`.
+        The integer $k$-th root of $n$.
 
     See Also:
         isqrt, ilog
@@ -173,14 +173,14 @@ def iroot(n: int, k: int) -> int:
 @export
 def ilog(n: int, b: int) -> int:
     r"""
-    Computes :math:`x = \lfloor\textrm{log}_b(n)\rfloor` such that :math:`b^x \le n < b^{x + 1}`.
+    Computes $x = \lfloor\textrm{log}_b(n)\rfloor$ such that $b^x \le n < b^{x + 1}$.
 
     Arguments:
         n: A positive integer.
-        b: The logarithm base :math:`b`, must be at least 2.
+        b: The logarithm base $b$, must be at least 2.
 
     Returns:
-        The integer logarithm base :math:`b` of :math:`n`.
+        The integer logarithm base $b$ of $n$.
 
     See Also:
         iroot, isqrt
diff --git a/src/galois/_modular.py b/src/galois/_modular.py
index ab34df8ad..3f5fea4fa 100644
--- a/src/galois/_modular.py
+++ b/src/galois/_modular.py
@@ -18,33 +18,33 @@
 @export
 def totatives(n: int) -> list[int]:
     r"""
-    Returns the positive integers (totatives) in :math:`[1, n)` that are coprime to :math:`n`.
+    Returns the positive integers (totatives) in $[1, n)$ that are coprime to $n$.
 
     Arguments:
         n: A positive integer.
 
     Returns:
-        The totatives of :math:`n`.
+        The totatives of $n$.
 
     See Also:
         euler_phi, carmichael_lambda, is_cyclic
 
     Notes:
-        The totatives of :math:`n` form the multiplicative group :math:`(\mathbb{Z}/n\mathbb{Z}){^\times}`.
+        The totatives of $n$ form the multiplicative group $(\mathbb{Z}/n\mathbb{Z}){^\times}$.
 
     References:
         - Section 2.4.3 from https://cacr.uwaterloo.ca/hac/about/chap2.pdf
         - https://oeis.org/A000010
 
     Examples:
-        Find the totatives that are coprime with :math:`n = 20`.
+        Find the totatives that are coprime with $n = 20$.
 
         .. ipython:: python
 
             n = 20
             x = galois.totatives(n); x
 
-        The number of totatives of :math:`n` is :math:`\phi(n)`.
+        The number of totatives of $n$ is $\phi(n)$.
 
         .. ipython:: python
 
@@ -67,13 +67,13 @@ def totatives(n: int) -> list[int]:
 @export
 def euler_phi(n: int) -> int:
     r"""
-    Counts the positive integers (totatives) in :math:`[1, n)` that are coprime to :math:`n`.
+    Counts the positive integers (totatives) in $[1, n)$ that are coprime to $n$.
 
     Arguments:
         n: A positive integer.
 
     Returns:
-        The number of totatives that are coprime to :math:`n`.
+        The number of totatives that are coprime to $n$.
 
     See Also:
         carmichael_lambda, totatives, is_cyclic
@@ -81,32 +81,31 @@ def euler_phi(n: int) -> int:
     Notes:
         This function implements the Euler totient function
 
-        .. math::
-            \phi(n) = n \prod_{p\ |\ n} \bigg(1 - \frac{1}{p}\bigg) = \prod_{i=1}^{k} p_i^{e_i-1} \big(p_i - 1\big)
+        $$\phi(n) = n \prod_{p\ |\ n} \bigg(1 - \frac{1}{p}\bigg) = \prod_{i=1}^{k} p_i^{e_i-1} \big(p_i - 1\big)$$
 
-        for prime :math:`p` and the prime factorization :math:`n = p_1^{e_1} \dots p_k^{e_k}`.
+        for prime $p$ and the prime factorization $n = p_1^{e_1} \dots p_k^{e_k}$.
 
     References:
         - Section 2.4.1 from https://cacr.uwaterloo.ca/hac/about/chap2.pdf
         - https://oeis.org/A000010
 
     Examples:
-        Compute :math:`\phi(20)`.
+        Compute $\phi(20)$.
 
         .. ipython:: python
 
             n = 20
             phi = galois.euler_phi(n); phi
 
-        Find the totatives that are coprime with :math:`n = 20`. The number of totatives of :math:`n` is
-        :math:`\phi(n)`.
+        Find the totatives that are coprime with $n = 20$. The number of totatives of $n$ is
+        $\phi(n)$.
 
         .. ipython:: python
 
             x = galois.totatives(n); x
             len(x) == phi
 
-        For prime :math:`n`, :math:`\phi(n) = n - 1`.
+        For prime $n$, $\phi(n) = n - 1$.
 
         .. ipython:: python
 
@@ -141,27 +140,27 @@ def _euler_phi(n: int) -> int:
 @export
 def carmichael_lambda(n: int) -> int:
     r"""
-    Finds the smallest positive integer :math:`m` such that :math:`a^m \equiv 1\ (\textrm{mod}\ n)` for
-    every integer :math:`a` in :math:`[1, n)` that is coprime to :math:`n`.
+    Finds the smallest positive integer $m$ such that $a^m \equiv 1\ (\textrm{mod}\ n)$ for
+    every integer $a$ in $[1, n)$ that is coprime to $n$.
 
     Arguments:
         n: A positive integer.
 
     Returns:
-        The smallest positive integer :math:`m` such that :math:`a^m \equiv 1\ (\textrm{mod}\ n)` for
-        every :math:`a` in :math:`[1, n)` that is coprime to :math:`n`.
+        The smallest positive integer $m$ such that $a^m \equiv 1\ (\textrm{mod}\ n)$ for
+        every $a$ in $[1, n)$ that is coprime to $n$.
 
     See Also:
         euler_phi, totatives, is_cyclic
 
     Notes:
-        This function implements the Carmichael function :math:`\lambda(n)`.
+        This function implements the Carmichael function $\lambda(n)$.
 
     References:
         - https://oeis.org/A002322
 
     Examples:
-        The Carmichael :math:`\lambda(n)` function and Euler :math:`\phi(n)` function are often equal. However,
+        The Carmichael $\lambda(n)$ function and Euler $\phi(n)$ function are often equal. However,
         there are notable exceptions.
 
         .. ipython:: python
@@ -169,8 +168,8 @@ def carmichael_lambda(n: int) -> int:
             [galois.euler_phi(n) for n in range(1, 20)]
             [galois.carmichael_lambda(n) for n in range(1, 20)]
 
-        For prime :math:`n`, :math:`\phi(n) = \lambda(n) = n - 1`. And for most composite :math:`n`,
-        :math:`\phi(n) = \lambda(n) < n - 1`.
+        For prime $n$, $\phi(n) = \lambda(n) = n - 1$. And for most composite $n$,
+        $\phi(n) = \lambda(n) < n - 1$.
 
         .. ipython:: python
 
@@ -185,7 +184,7 @@ def carmichael_lambda(n: int) -> int:
 
             galois.is_cyclic(n)
 
-        When :math:`\phi(n) \ne \lambda(n)`, the multiplicative group :math:`(\mathbb{Z}/n\mathbb{Z}){^\times}` is
+        When $\phi(n) \ne \lambda(n)$, the multiplicative group $(\mathbb{Z}/n\mathbb{Z}){^\times}$ is
         not cyclic. See :func:`~galois.is_cyclic`.
 
         .. ipython:: python
@@ -228,34 +227,34 @@ def carmichael_lambda(n: int) -> int:
 @export
 def is_cyclic(n: int) -> bool:
     r"""
-    Determines whether the multiplicative group :math:`(\mathbb{Z}/n\mathbb{Z}){^\times}` is cyclic.
+    Determines whether the multiplicative group $(\mathbb{Z}/n\mathbb{Z}){^\times}$ is cyclic.
 
     Arguments:
         n: A positive integer.
 
     Returns:
-        `True` if the multiplicative group :math:`(\mathbb{Z}/n\mathbb{Z}){^\times}` is cyclic.
+        `True` if the multiplicative group $(\mathbb{Z}/n\mathbb{Z}){^\times}$ is cyclic.
 
     See Also:
         euler_phi, carmichael_lambda, totatives
 
     Notes:
-        The multiplicative group :math:`(\mathbb{Z}/n\mathbb{Z}){^\times}` is the set of positive integers
-        :math:`1 \le a < n` that are coprime with :math:`n`. :math:`(\mathbb{Z}/n\mathbb{Z}){^\times}` being cyclic
-        means that some primitive root of :math:`n`, or generator, :math:`g` can generate the group
-        :math:`\{1, g, g^2, \dots, g^{\phi(n)-1}\}`, where :math:`\phi(n)` is Euler's totient function and calculates
-        the order of the group. If :math:`(\mathbb{Z}/n\mathbb{Z}){^\times}` is cyclic, the number of primitive roots
-        is found by :math:`\phi(\phi(n))`.
+        The multiplicative group $(\mathbb{Z}/n\mathbb{Z}){^\times}$ is the set of positive integers
+        $1 \le a < n$ that are coprime with $n$. $(\mathbb{Z}/n\mathbb{Z}){^\times}$ being cyclic
+        means that some primitive root of $n$, or generator, $g$ can generate the group
+        $\{1, g, g^2, \dots, g^{\phi(n)-1}\}$, where $\phi(n)$ is Euler's totient function and calculates
+        the order of the group. If $(\mathbb{Z}/n\mathbb{Z}){^\times}$ is cyclic, the number of primitive roots
+        is found by $\phi(\phi(n))$.
 
-        :math:`(\mathbb{Z}/n\mathbb{Z}){^\times}` is *cyclic* if and only if :math:`n` is 2, 4, :math:`p^k`,
-        or :math:`2p^k`, where :math:`p` is an odd prime and :math:`k` is a positive integer.
+        $(\mathbb{Z}/n\mathbb{Z}){^\times}$ is *cyclic* if and only if $n$ is 2, 4, $p^k$,
+        or $2p^k$, where $p$ is an odd prime and $k$ is a positive integer.
 
     Examples:
         .. md-tab-set::
 
             .. md-tab-item:: n = 14
 
-                The elements of :math:`(\mathbb{Z}/14\mathbb{Z}){^\times} = \{1, 3, 5, 9, 11, 13\}` are the totatives
+                The elements of $(\mathbb{Z}/14\mathbb{Z}){^\times} = \{1, 3, 5, 9, 11, 13\}$ are the totatives
                 of 14.
 
                 .. ipython:: python
@@ -263,32 +262,32 @@ def is_cyclic(n: int) -> bool:
                     n = 14
                     Znx = galois.totatives(n); Znx
 
-                The Euler totient :math:`\phi(n)` function counts the totatives of :math:`n`, which is equivalent to
-                the order of :math:`(\mathbb{Z}/n\mathbb{Z}){^\times}`.
+                The Euler totient $\phi(n)$ function counts the totatives of $n$, which is equivalent to
+                the order of $(\mathbb{Z}/n\mathbb{Z}){^\times}$.
 
                 .. ipython:: python
 
                     phi = galois.euler_phi(n); phi
                     len(Znx) == phi
 
-                Since 14 is of the form :math:`2p^k`, the multiplicative group
-                :math:`(\mathbb{Z}/14\mathbb{Z}){^\times}` is cyclic, meaning there exists at least one element that
+                Since 14 is of the form $2p^k$, the multiplicative group
+                $(\mathbb{Z}/14\mathbb{Z}){^\times}$ is cyclic, meaning there exists at least one element that
                 generates the group by its powers.
 
                 .. ipython:: python
 
                     galois.is_cyclic(n)
 
-                Find the smallest primitive root modulo 14. Observe that the powers of :math:`g` uniquely represent
-                each element in :math:`(\mathbb{Z}/14\mathbb{Z}){^\times}`.
+                Find the smallest primitive root modulo 14. Observe that the powers of $g$ uniquely represent
+                each element in $(\mathbb{Z}/14\mathbb{Z}){^\times}$.
 
                 .. ipython:: python
 
                     g = galois.primitive_root(n); g
                     [pow(g, i, n) for i in range(0, phi)]
 
-                Find the largest primitive root modulo 14. Observe that the powers of :math:`g` also uniquely represent
-                each element in :math:`(\mathbb{Z}/14\mathbb{Z}){^\times}`, although in a different order.
+                Find the largest primitive root modulo 14. Observe that the powers of $g$ also uniquely represent
+                each element in $(\mathbb{Z}/14\mathbb{Z}){^\times}$, although in a different order.
 
                 .. ipython:: python
 
@@ -297,7 +296,7 @@ def is_cyclic(n: int) -> bool:
 
             .. md-tab-item:: n = 15
 
-                A non-cyclic group is :math:`(\mathbb{Z}/15\mathbb{Z}){^\times} = \{1, 2, 4, 7, 8, 11, 13, 14\}`.
+                A non-cyclic group is $(\mathbb{Z}/15\mathbb{Z}){^\times} = \{1, 2, 4, 7, 8, 11, 13, 14\}$.
 
                 .. ipython:: python
 
@@ -305,8 +304,8 @@ def is_cyclic(n: int) -> bool:
                     Znx = galois.totatives(n); Znx
                     phi = galois.euler_phi(n); phi
 
-                Since 15 is not of the form 2, 4, :math:`p^k`, or :math:`2p^k`, the multiplicative group
-                :math:`(\mathbb{Z}/15\mathbb{Z}){^\times}` is not cyclic, meaning no elements exist whose powers
+                Since 15 is not of the form 2, 4, $p^k$, or $2p^k$, the multiplicative group
+                $(\mathbb{Z}/15\mathbb{Z}){^\times}$ is not cyclic, meaning no elements exist whose powers
                 generate the group.
 
                 .. ipython:: python
@@ -322,7 +321,7 @@ def is_cyclic(n: int) -> bool:
                         primitive_root = span == set(Znx)
                         print("Element: {:2d}, Span: {:<13}, Primitive root: {}".format(a, str(span), primitive_root))
 
-                The Carmichael :math:`\lambda(n)` function finds the maximum multiplicative order of any element, which
+                The Carmichael $\lambda(n)$ function finds the maximum multiplicative order of any element, which
                 is 4 and not 8.
 
                 .. ipython:: python
@@ -338,8 +337,8 @@ def is_cyclic(n: int) -> bool:
 
             .. md-tab-item:: Prime fields
 
-                For prime :math:`n`, a primitive root modulo :math:`n` is also a primitive element of the Galois field
-                :math:`\mathrm{GF}(n)`.
+                For prime $n$, a primitive root modulo $n$ is also a primitive element of the Galois field
+                $\mathrm{GF}(n)$.
 
                 .. ipython:: python
 
@@ -347,7 +346,7 @@ def is_cyclic(n: int) -> bool:
                     galois.is_cyclic(n)
 
                 A primitive element is a generator of the multiplicative group
-                :math:`\mathrm{GF}(p)^{\times} = \{1, 2, \dots, p-1\} = \{1, g, g^2, \dots, g^{\phi(n)-1}\}`.
+                $\mathrm{GF}(p)^{\times} = \{1, 2, \dots, p-1\} = \{1, g, g^2, \dots, g^{\phi(n)-1}\}$.
 
                 .. ipython:: python
 
@@ -355,7 +354,7 @@ def is_cyclic(n: int) -> bool:
                     galois.primitive_root(n)
                     GF.primitive_element
 
-                The number of primitive roots/elements is :math:`\phi(\phi(n))`.
+                The number of primitive roots/elements is $\phi(\phi(n))$.
 
                 .. ipython:: python
 
@@ -398,17 +397,17 @@ def primitive_root(
     method: Literal["min", "max", "random"] = "min",
 ) -> int:
     r"""
-    Finds a primitive root modulo :math:`n` in the range :math:`[\textrm{start}, \textrm{stop})`.
+    Finds a primitive root modulo $n$ in the range $[\textrm{start}, \textrm{stop})$.
 
     Arguments:
         n: A positive integer.
         start: Starting value (inclusive) in the search for a primitive root.
         stop: Stopping value (exclusive) in the search for a primitive root. The default is `None` which corresponds
-            to :math:`n`.
+            to $n$.
         method: The search method for finding the primitive root.
 
     Returns:
-        A primitive root modulo :math:`n` in the specified range.
+        A primitive root modulo $n$ in the specified range.
 
     Raises:
         RuntimeError: If no primitive roots exist in the specified range.
@@ -417,17 +416,17 @@ def primitive_root(
         primitive_roots, is_primitive_root, is_cyclic, totatives, euler_phi, carmichael_lambda
 
     Notes:
-        The integer :math:`g` is a primitive root modulo :math:`n` if the totatives of :math:`n` can be generated by
-        the powers of :math:`g`. The totatives of :math:`n` are the positive integers in :math:`[1, n)` that are
-        coprime with :math:`n`.
+        The integer $g$ is a primitive root modulo $n$ if the totatives of $n$ can be generated by
+        the powers of $g$. The totatives of $n$ are the positive integers in $[1, n)$ that are
+        coprime with $n$.
 
-        Alternatively said, :math:`g` is a primitive root modulo :math:`n` if and only if :math:`g` is a generator of
-        the multiplicative group of integers modulo :math:`n`
-        :math:`(\mathbb{Z}/n\mathbb{Z}){^\times} = \{1, g, g^2, \dots, g^{\phi(n)-1}\}`, where :math:`\phi(n)` is
+        Alternatively said, $g$ is a primitive root modulo $n$ if and only if $g$ is a generator of
+        the multiplicative group of integers modulo $n$
+        $(\mathbb{Z}/n\mathbb{Z}){^\times} = \{1, g, g^2, \dots, g^{\phi(n)-1}\}$, where $\phi(n)$ is
         the order of the group.
 
-        If :math:`(\mathbb{Z}/n\mathbb{Z}){^\times}` is cyclic, the number of primitive roots modulo :math:`n` is
-        given by :math:`\phi(\phi(n))`.
+        If $(\mathbb{Z}/n\mathbb{Z}){^\times}$ is cyclic, the number of primitive roots modulo $n$ is
+        given by $\phi(\phi(n))$.
 
     References:
         - Shoup, V. Searching for primitive roots in finite fields.
@@ -441,7 +440,7 @@ def primitive_root(
 
             .. md-tab-item:: n = 14
 
-                The elements of :math:`(\mathbb{Z}/14\mathbb{Z}){^\times} = \{1, 3, 5, 9, 11, 13\}` are the totatives
+                The elements of $(\mathbb{Z}/14\mathbb{Z}){^\times} = \{1, 3, 5, 9, 11, 13\}$ are the totatives
                 of 14.
 
                 .. ipython:: python
@@ -449,32 +448,32 @@ def primitive_root(
                     n = 14
                     Znx = galois.totatives(n); Znx
 
-                The Euler totient :math:`\phi(n)` function counts the totatives of :math:`n`, which is equivalent to
-                the order of :math:`(\mathbb{Z}/n\mathbb{Z}){^\times}`.
+                The Euler totient $\phi(n)$ function counts the totatives of $n$, which is equivalent to
+                the order of $(\mathbb{Z}/n\mathbb{Z}){^\times}$.
 
                 .. ipython:: python
 
                     phi = galois.euler_phi(n); phi
                     len(Znx) == phi
 
-                Since 14 is of the form :math:`2p^k`, the multiplicative group
-                :math:`(\mathbb{Z}/14\mathbb{Z}){^\times}` is cyclic, meaning there exists at least one element that
+                Since 14 is of the form $2p^k$, the multiplicative group
+                $(\mathbb{Z}/14\mathbb{Z}){^\times}$ is cyclic, meaning there exists at least one element that
                 generates the group by its powers.
 
                 .. ipython:: python
 
                     galois.is_cyclic(n)
 
-                Find the smallest primitive root modulo 14. Observe that the powers of :math:`g` uniquely represent
-                each element in :math:`(\mathbb{Z}/14\mathbb{Z}){^\times}`.
+                Find the smallest primitive root modulo 14. Observe that the powers of $g$ uniquely represent
+                each element in $(\mathbb{Z}/14\mathbb{Z}){^\times}$.
 
                 .. ipython:: python
 
                     g = galois.primitive_root(n); g
                     [pow(g, i, n) for i in range(0, phi)]
 
-                Find the largest primitive root modulo 14. Observe that the powers of :math:`g` also uniquely represent
-                each element in :math:`(\mathbb{Z}/14\mathbb{Z}){^\times}`, although in a different order.
+                Find the largest primitive root modulo 14. Observe that the powers of $g$ also uniquely represent
+                each element in $(\mathbb{Z}/14\mathbb{Z}){^\times}$, although in a different order.
 
                 .. ipython:: python
 
@@ -483,7 +482,7 @@ def primitive_root(
 
             .. md-tab-item:: n = 15
 
-                A non-cyclic group is :math:`(\mathbb{Z}/15\mathbb{Z}){^\times} = \{1, 2, 4, 7, 8, 11, 13, 14\}`.
+                A non-cyclic group is $(\mathbb{Z}/15\mathbb{Z}){^\times} = \{1, 2, 4, 7, 8, 11, 13, 14\}$.
 
                 .. ipython:: python
 
@@ -491,8 +490,8 @@ def primitive_root(
                     Znx = galois.totatives(n); Znx
                     phi = galois.euler_phi(n); phi
 
-                Since 15 is not of the form 2, 4, :math:`p^k`, or :math:`2p^k`, the multiplicative group
-                :math:`(\mathbb{Z}/15\mathbb{Z}){^\times}` is not cyclic, meaning no elements exist whose powers
+                Since 15 is not of the form 2, 4, $p^k$, or $2p^k$, the multiplicative group
+                $(\mathbb{Z}/15\mathbb{Z}){^\times}$ is not cyclic, meaning no elements exist whose powers
                 generate the group.
 
                 .. ipython:: python
@@ -508,7 +507,7 @@ def primitive_root(
                         primitive_root = span == set(Znx)
                         print("Element: {:2d}, Span: {:<13}, Primitive root: {}".format(a, str(span), primitive_root))
 
-                The Carmichael :math:`\lambda(n)` function finds the maximum multiplicative order of any element,
+                The Carmichael $\lambda(n)$ function finds the maximum multiplicative order of any element,
                 which is 4 and not 8.
 
                 .. ipython:: python
@@ -524,14 +523,14 @@ def primitive_root(
 
             .. md-tab-item:: Very large n
 
-                The algorithm is also efficient for very large :math:`n`.
+                The algorithm is also efficient for very large $n$.
 
                 .. ipython:: python
 
                     n = 1000000000000000035000061
                     phi = galois.euler_phi(n); phi
 
-                Find the smallest, the largest, and a random primitive root modulo :math:`n`.
+                Find the smallest, the largest, and a random primitive root modulo $n$.
 
                 .. ipython:: python
 
@@ -575,33 +574,33 @@ def primitive_roots(
     reverse: bool = False,
 ) -> Iterator[int]:
     r"""
-    Iterates through all primitive roots modulo :math:`n` in the range :math:`[\textrm{start}, \textrm{stop})`.
+    Iterates through all primitive roots modulo $n$ in the range $[\textrm{start}, \textrm{stop})$.
 
     Arguments:
         n: A positive integer.
         start: Starting value (inclusive) in the search for a primitive root. The default is 1.
         stop: Stopping value (exclusive) in the search for a primitive root. The default is `None` which corresponds
-            to :math:`n`.
+            to $n$.
         reverse: Indicates to return the primitive roots from largest to smallest. The default is `False`.
 
     Returns:
-        An iterator over the primitive roots modulo :math:`n` in the specified range.
+        An iterator over the primitive roots modulo $n$ in the specified range.
 
     See Also:
         primitive_root, is_primitive_root, is_cyclic, totatives, euler_phi, carmichael_lambda
 
     Notes:
-        The integer :math:`g` is a primitive root modulo :math:`n` if the totatives of :math:`n` can be generated by
-        the powers of :math:`g`. The totatives of :math:`n` are the positive integers in :math:`[1, n)` that are
-        coprime with :math:`n`.
+        The integer $g$ is a primitive root modulo $n$ if the totatives of $n$ can be generated by
+        the powers of $g$. The totatives of $n$ are the positive integers in $[1, n)$ that are
+        coprime with $n$.
 
-        Alternatively said, :math:`g` is a primitive root modulo :math:`n` if and only if :math:`g` is a generator of
-        the multiplicative group of integers modulo :math:`n`
-        :math:`(\mathbb{Z}/n\mathbb{Z}){^\times} = \{1, g, g^2, \dots, g^{\phi(n)-1}\}`, where :math:`\phi(n)` is
+        Alternatively said, $g$ is a primitive root modulo $n$ if and only if $g$ is a generator of
+        the multiplicative group of integers modulo $n$
+        $(\mathbb{Z}/n\mathbb{Z}){^\times} = \{1, g, g^2, \dots, g^{\phi(n)-1}\}$, where $\phi(n)$ is
         the order of the group.
 
-        If :math:`(\mathbb{Z}/n\mathbb{Z}){^\times}` is cyclic, the number of primitive roots modulo :math:`n` is
-        given by :math:`\phi(\phi(n))`.
+        If $(\mathbb{Z}/n\mathbb{Z}){^\times}$ is cyclic, the number of primitive roots modulo $n$ is
+        given by $\phi(\phi(n))$.
 
     References:
         - Shoup, V. Searching for primitive roots in finite fields.
@@ -624,7 +623,7 @@ def primitive_roots(
             list(galois.primitive_roots(30))
 
         Show the each primitive root modulo 22 generates the multiplicative group
-        :math:`(\mathbb{Z}/22\mathbb{Z}){^\times}`.
+        $(\mathbb{Z}/22\mathbb{Z}){^\times}$.
 
         .. ipython:: python
 
@@ -724,32 +723,31 @@ def _primitive_root_random_search(n, start, stop) -> int:
 @export
 def is_primitive_root(g: int, n: int) -> bool:
     r"""
-    Determines if :math:`g` is a primitive root modulo :math:`n`.
+    Determines if $g$ is a primitive root modulo $n$.
 
     Arguments:
         g: A positive integer.
         n: positive integer.
 
     Returns:
-        `True` if :math:`g` is a primitive root modulo :math:`n`.
+        `True` if $g$ is a primitive root modulo $n$.
 
     See Also:
         primitive_root, primitive_roots, is_cyclic, euler_phi
 
     Notes:
-        The integer :math:`g` is a primitive root modulo :math:`n` if the totatives of :math:`n`, the positive integers
-        :math:`1 \le a < n` that are coprime with :math:`n`, can be generated by powers of :math:`g`.
+        The integer $g$ is a primitive root modulo $n$ if the totatives of $n$, the positive integers
+        $1 \le a < n$ that are coprime with $n$, can be generated by powers of $g$.
 
-        Alternatively said, :math:`g` is a primitive root modulo :math:`n` if and only if :math:`g` is a generator of
-        the multiplicative group of integers modulo :math:`n`,
+        Alternatively said, $g$ is a primitive root modulo $n$ if and only if $g$ is a generator of
+        the multiplicative group of integers modulo $n$,
 
-        .. math::
-            (\mathbb{Z}/n\mathbb{Z}){^\times} = \{1, g, g^2, \dots, g^{\phi(n)-1}\}
+        $$(\mathbb{Z}/n\mathbb{Z}){^\times} = \{1, g, g^2, \dots, g^{\phi(n)-1}\}$$
 
-        where :math:`\phi(n)` is order of the group.
+        where $\phi(n)$ is order of the group.
 
-        If :math:`(\mathbb{Z}/n\mathbb{Z}){^\times}` is cyclic, the number of primitive roots modulo :math:`n` is
-        given by :math:`\phi(\phi(n))`.
+        If $(\mathbb{Z}/n\mathbb{Z}){^\times}$ is cyclic, the number of primitive roots modulo $n$ is
+        given by $\phi(\phi(n))$.
 
     Examples:
         .. ipython:: python
diff --git a/src/galois/_ntt.py b/src/galois/_ntt.py
index fef9ecdd1..03d112e26 100644
--- a/src/galois/_ntt.py
+++ b/src/galois/_ntt.py
@@ -18,20 +18,20 @@ def ntt(
     modulus: int | None = None,
 ) -> FieldArray:
     r"""
-    Computes the Number-Theoretic Transform (NTT) of :math:`x`.
+    Computes the Number-Theoretic Transform (NTT) of $x$.
 
     Arguments:
-        x: The input sequence of integers :math:`x`.
-        size: The size :math:`N` of the NTT transform, must be at least the length of :math:`x`. The default is `None`
-            which corresponds to `len(x)`. If `size` is larger than the length of :math:`x`, :math:`x` is zero-padded.
-        modulus: The prime modulus :math:`p` that defines the field :math:`\mathrm{GF}(p)`. The prime modulus must
-            satisfy :math:`p > \textrm{max}(x)` and :math:`p = mN + 1` (i.e., the size of the transform :math:`N` must
-            divide :math:`p - 1`). The default is `None` which corresponds to the smallest :math:`p` that satisfies
-            the criteria. However, if :math:`x` is a :math:`\mathrm{GF}(p)` array, then `None` corresponds to
-            :math:`p` from the specified field.
+        x: The input sequence of integers $x$.
+        size: The size $N$ of the NTT transform, must be at least the length of $x$. The default is `None`
+            which corresponds to `len(x)`. If `size` is larger than the length of $x$, $x$ is zero-padded.
+        modulus: The prime modulus $p$ that defines the field $\mathrm{GF}(p)$. The prime modulus must
+            satisfy $p > \textrm{max}(x)$ and $p = mN + 1$ (i.e., the size of the transform $N$ must
+            divide $p - 1$). The default is `None` which corresponds to the smallest $p$ that satisfies
+            the criteria. However, if $x$ is a $\mathrm{GF}(p)$ array, then `None` corresponds to
+            $p$ from the specified field.
 
     Returns:
-        The NTT :math:`X` of the input :math:`x`, with length :math:`N`. The output is a :math:`\mathrm{GF}(p)` array.
+        The NTT $X$ of the input $x$, with length $N$. The output is a $\mathrm{GF}(p)$ array.
         It can be viewed as a normal NumPy array with `.view(np.ndarray)` or converted to a Python list with
         `.tolist()`.
 
@@ -40,25 +40,24 @@ def ntt(
 
     Notes:
         The Number-Theoretic Transform (NTT) is a specialized Discrete Fourier Transform (DFT) over a finite field
-        :math:`\mathrm{GF}(p)` instead of over :math:`\mathbb{C}`. The DFT uses the primitive :math:`N`-th root of
-        unity :math:`\omega_N = e^{-i 2 \pi /N}`, but the NTT uses a primitive :math:`N`-th root of unity in
-        :math:`\mathrm{GF}(p)`. These roots are such that :math:`\omega_N^N = 1` and :math:`\omega_N^k \neq 1`
-        for :math:`0 < k < N`.
+        $\mathrm{GF}(p)$ instead of over $\mathbb{C}$. The DFT uses the primitive $N$-th root of
+        unity $\omega_N = e^{-i 2 \pi /N}$, but the NTT uses a primitive $N$-th root of unity in
+        $\mathrm{GF}(p)$. These roots are such that $\omega_N^N = 1$ and $\omega_N^k \neq 1$
+        for $0 < k < N$.
 
-        In :math:`\mathrm{GF}(p)`, where :math:`p` is prime, a primitive :math:`N`-th root of unity exists if
-        :math:`N` divides :math:`p - 1`. If that is true, then :math:`p = mN + 1` for some integer :math:`m`. This
-        function finds :math:`\omega_N` by first finding a primitive :math:`p - 1`-th root of unity
-        :math:`\omega_{p - 1}` in :math:`\mathrm{GF}(p)` using :func:`~galois.primitive_root`. From there
-        :math:`\omega_N` is found from :math:`\omega_N = \omega_{p - 1}^m`.
+        In $\mathrm{GF}(p)$, where $p$ is prime, a primitive $N$-th root of unity exists if
+        $N$ divides $p - 1$. If that is true, then $p = mN + 1$ for some integer $m$. This
+        function finds $\omega_N$ by first finding a primitive $p - 1$-th root of unity
+        $\omega_{p - 1}$ in $\mathrm{GF}(p)$ using :func:`~galois.primitive_root`. From there
+        $\omega_N$ is found from $\omega_N = \omega_{p - 1}^m$.
 
-        The :math:`k`-th value of the :math:`N`-point NTT :math:`X = \mathrm{NTT}(x)` is
+        The $k$-th value of the $N$-point NTT $X = \mathrm{NTT}(x)$ is
 
-        .. math::
-            X_k = \sum_{j=0}^{N-1} x_j \omega_N^{jk} ,
+        $$X_k = \sum_{j=0}^{N-1} x_j \omega_N^{jk} ,$$
 
-        with all arithmetic performed in :math:`\mathrm{GF}(p)`.
+        with all arithmetic performed in $\mathrm{GF}(p)$.
 
-        A radix-2 Cooley-Tukey FFT algorithm is implemented, which achieves :math:`O(N \mathrm{log}(N))`.
+        A radix-2 Cooley-Tukey FFT algorithm is implemented, which achieves $O(N \mathrm{log}(N))$.
 
     References:
         - https://cgyurgyik.github.io/posts/2021/04/brief-introduction-to-ntt/
@@ -66,15 +65,15 @@ def ntt(
         - https://www.geeksforgeeks.org/python-number-theoretic-transformation/
 
     Examples:
-        The default modulus is the smallest :math:`p` such that :math:`p > \textrm{max}(x)` and :math:`p = mN + 1`.
-        With the input :math:`x = [1, 2, 3, 4]` and :math:`N = 4`, the default modulus is :math:`p = 5`.
+        The default modulus is the smallest $p$ such that $p > \textrm{max}(x)$ and $p = mN + 1$.
+        With the input $x = [1, 2, 3, 4]$ and $N = 4$, the default modulus is $p = 5$.
 
         .. ipython:: python
 
             galois.ntt([1, 2, 3, 4])
 
-        However, other moduli satisfy :math:`p > \textrm{max}(x)` and :math:`p = mN + 1`. For instance, :math:`p = 13`
-        and :math:`p = 17`. Notice the NTT outputs are different with different moduli. So it is important to perform
+        However, other moduli satisfy $p > \textrm{max}(x)$ and $p = mN + 1$. For instance, $p = 13$
+        and $p = 17$. Notice the NTT outputs are different with different moduli. So it is important to perform
         forward and reverse NTTs with the same modulus.
 
         .. ipython:: python
@@ -82,8 +81,8 @@ def ntt(
             galois.ntt([1, 2, 3, 4], modulus=13)
             galois.ntt([1, 2, 3, 4], modulus=17)
 
-        Instead of explicitly specifying the prime modulus, a :math:`\mathrm{GF}(p)` array may be explicitly passed in
-        and the modulus is taken as :math:`p`.
+        Instead of explicitly specifying the prime modulus, a $\mathrm{GF}(p)$ array may be explicitly passed in
+        and the modulus is taken as $p$.
 
         .. ipython:: python
 
@@ -97,7 +96,7 @@ def ntt(
             galois.ntt([1, 2, 3, 4, 5, 6], size=8)
             galois.ntt([1, 2, 3, 4, 5, 6, 0, 0])
 
-        The :func:`numpy.fft.fft` function may also be used to compute the NTT over :math:`\mathrm{GF}(p)`.
+        The :func:`numpy.fft.fft` function may also be used to compute the NTT over $\mathrm{GF}(p)$.
 
         .. ipython:: python
 
@@ -126,22 +125,22 @@ def intt(
     scaled: bool = True,
 ) -> FieldArray:
     r"""
-    Computes the Inverse Number-Theoretic Transform (INTT) of :math:`X`.
+    Computes the Inverse Number-Theoretic Transform (INTT) of $X$.
 
     Arguments:
-        X: The input sequence of integers :math:`X`.
-        size: The size :math:`N` of the INTT transform, must be at least the length of :math:`X`. The default is `None`
-            which corresponds to `len(X)`. If `size` is larger than the length of :math:`X`, :math:`X` is zero-padded.
-        modulus: The prime modulus :math:`p` that defines the field :math:`\mathrm{GF}(p)`. The prime modulus must
-            satisfy :math:`p > \textrm{max}(X)` and :math:`p = mN + 1` (i.e., the size of the transform :math:`N`
-            must divide :math:`p - 1`). The default is `None` which corresponds to the smallest :math:`p` that
-            satisfies the criteria. However, if :math:`x` is a :math:`\mathrm{GF}(p)` array, then `None` corresponds
-            to :math:`p` from the specified field.
-        scaled: Indicates to scale the INTT output by :math:`N`. The default is `True`. If `True`,
-            :math:`x = \mathrm{INTT}(\mathrm{NTT}(x))`. If `False`, :math:`Nx = \mathrm{INTT}(\mathrm{NTT}(x))`.
+        X: The input sequence of integers $X$.
+        size: The size $N$ of the INTT transform, must be at least the length of $X$. The default is `None`
+            which corresponds to `len(X)`. If `size` is larger than the length of $X$, $X$ is zero-padded.
+        modulus: The prime modulus $p$ that defines the field $\mathrm{GF}(p)$. The prime modulus must
+            satisfy $p > \textrm{max}(X)$ and $p = mN + 1$ (i.e., the size of the transform $N$
+            must divide $p - 1$). The default is `None` which corresponds to the smallest $p$ that
+            satisfies the criteria. However, if $x$ is a $\mathrm{GF}(p)$ array, then `None` corresponds
+            to $p$ from the specified field.
+        scaled: Indicates to scale the INTT output by $N$. The default is `True`. If `True`,
+            $x = \mathrm{INTT}(\mathrm{NTT}(x))$. If `False`, $Nx = \mathrm{INTT}(\mathrm{NTT}(x))$.
 
     Returns:
-        The INTT :math:`x` of the input :math:`X`, with length :math:`N`. The output is a :math:`\mathrm{GF}(p)` array.
+        The INTT $x$ of the input $X$, with length $N$. The output is a $\mathrm{GF}(p)$ array.
         It can be viewed as a normal NumPy array with `.view(np.ndarray)` or converted to a Python list with
         `.tolist()`.
 
@@ -150,26 +149,25 @@ def intt(
 
     Notes:
         The Number-Theoretic Transform (NTT) is a specialized Discrete Fourier Transform (DFT) over a finite field
-        :math:`\mathrm{GF}(p)` instead of over :math:`\mathbb{C}`. The DFT uses the primitive :math:`N`-th root of
-        unity :math:`\omega_N = e^{-i 2 \pi /N}`, but the NTT uses a primitive :math:`N`-th root of unity in
-        :math:`\mathrm{GF}(p)`. These roots are such that :math:`\omega_N^N = 1` and :math:`\omega_N^k \neq 1` for
-        :math:`0 < k < N`.
+        $\mathrm{GF}(p)$ instead of over $\mathbb{C}$. The DFT uses the primitive $N$-th root of
+        unity $\omega_N = e^{-i 2 \pi /N}$, but the NTT uses a primitive $N$-th root of unity in
+        $\mathrm{GF}(p)$. These roots are such that $\omega_N^N = 1$ and $\omega_N^k \neq 1$ for
+        $0 < k < N$.
 
-        In :math:`\mathrm{GF}(p)`, where :math:`p` is prime, a primitive :math:`N`-th root of unity exists if
-        :math:`N` divides :math:`p - 1`. If that is true, then :math:`p = mN + 1` for some integer :math:`m`. This
-        function finds :math:`\omega_N` by first finding a primitive :math:`p - 1`-th root of unity
-        :math:`\omega_{p - 1}` in :math:`\mathrm{GF}(p)` using :func:`~galois.primitive_root`. From there
-        :math:`\omega_N` is found from :math:`\omega_N = \omega_{p - 1}^m`.
+        In $\mathrm{GF}(p)$, where $p$ is prime, a primitive $N$-th root of unity exists if
+        $N$ divides $p - 1$. If that is true, then $p = mN + 1$ for some integer $m$. This
+        function finds $\omega_N$ by first finding a primitive $p - 1$-th root of unity
+        $\omega_{p - 1}$ in $\mathrm{GF}(p)$ using :func:`~galois.primitive_root`. From there
+        $\omega_N$ is found from $\omega_N = \omega_{p - 1}^m$.
 
-        The :math:`j`-th value of the scaled :math:`N`-point INTT :math:`x = \mathrm{INTT}(X)` is
+        The $j$-th value of the scaled $N$-point INTT $x = \mathrm{INTT}(X)$ is
 
-        .. math::
-            x_j = \frac{1}{N} \sum_{k=0}^{N-1} X_k \omega_N^{-kj} ,
+        $$x_j = \frac{1}{N} \sum_{k=0}^{N-1} X_k \omega_N^{-kj} ,$$
 
-        with all arithmetic performed in :math:`\mathrm{GF}(p)`. The scaled INTT has the property that
-        :math:`x = \mathrm{INTT}(\mathrm{NTT}(x))`.
+        with all arithmetic performed in $\mathrm{GF}(p)$. The scaled INTT has the property that
+        $x = \mathrm{INTT}(\mathrm{NTT}(x))$.
 
-        A radix-2 Cooley-Tukey FFT algorithm is implemented, which achieves :math:`O(N \mathrm{log}(N))`.
+        A radix-2 Cooley-Tukey FFT algorithm is implemented, which achieves $O(N \mathrm{log}(N))$.
 
     References:
         - https://cgyurgyik.github.io/posts/2021/04/brief-introduction-to-ntt/
@@ -177,15 +175,15 @@ def intt(
         - https://www.geeksforgeeks.org/python-number-theoretic-transformation/
 
     Examples:
-        The default modulus is the smallest :math:`p` such that :math:`p > \textrm{max}(X)` and :math:`p = mN + 1`.
-        With the input :math:`X = [0, 4, 3, 2]` and :math:`N = 4`, the default modulus is :math:`p = 5`.
+        The default modulus is the smallest $p$ such that $p > \textrm{max}(X)$ and $p = mN + 1$.
+        With the input $X = [0, 4, 3, 2]$ and $N = 4$, the default modulus is $p = 5$.
 
         .. ipython:: python
 
             galois.intt([0, 4, 3, 2])
 
-        However, other moduli satisfy :math:`p > \textrm{max}(X)` and :math:`p = mN + 1`. For instance, :math:`p = 13`
-        and :math:`p = 17`. Notice the INTT outputs are different with different moduli. So it is important to perform
+        However, other moduli satisfy $p > \textrm{max}(X)$ and $p = mN + 1$. For instance, $p = 13$
+        and $p = 17$. Notice the INTT outputs are different with different moduli. So it is important to perform
         forward and reverse NTTs with the same modulus.
 
         .. ipython:: python
@@ -193,8 +191,8 @@ def intt(
             galois.intt([0, 4, 3, 2], modulus=13)
             galois.intt([0, 4, 3, 2], modulus=17)
 
-        Instead of explicitly specifying the prime modulus, a :math:`\mathrm{GF}(p)` array may be explicitly passed in
-        and the modulus is taken as :math:`p`.
+        Instead of explicitly specifying the prime modulus, a $\mathrm{GF}(p)$ array may be explicitly passed in
+        and the modulus is taken as $p$.
 
         .. ipython:: python
 
@@ -203,7 +201,7 @@ def intt(
             x = galois.intt(X); x
             galois.ntt(x)
 
-        The forward NTT and scaled INTT are the identity transform, i.e. :math:`x = \mathrm{INTT}(\mathrm{NTT}(x))`.
+        The forward NTT and scaled INTT are the identity transform, i.e. $x = \mathrm{INTT}(\mathrm{NTT}(x))$.
 
         .. ipython:: python
 
@@ -211,13 +209,13 @@ def intt(
             x = GF([1, 2, 3, 4]); x
             galois.intt(galois.ntt(x))
 
-        This is also true in the reverse order, i.e. :math:`x = \mathrm{NTT}(\mathrm{INTT}(x))`.
+        This is also true in the reverse order, i.e. $x = \mathrm{NTT}(\mathrm{INTT}(x))$.
 
         .. ipython:: python
 
             galois.ntt(galois.intt(x))
 
-        The :func:`numpy.fft.ifft` function may also be used to compute the inverse NTT over :math:`\mathrm{GF}(p)`.
+        The :func:`numpy.fft.ifft` function may also be used to compute the inverse NTT over $\mathrm{GF}(p)$.
 
         .. ipython:: python
 
diff --git a/src/galois/_polymorphic.py b/src/galois/_polymorphic.py
index f6f662652..041acccb6 100644
--- a/src/galois/_polymorphic.py
+++ b/src/galois/_polymorphic.py
@@ -38,14 +38,14 @@ def gcd(a: Poly, b: Poly) -> Poly:
 @export
 def gcd(a, b):
     r"""
-    Finds the greatest common divisor of :math:`a` and :math:`b`.
+    Finds the greatest common divisor of $a$ and $b$.
 
     Arguments:
         a: The first integer or polynomial argument.
         b: The second integer or polynomial argument.
 
     Returns:
-        Greatest common divisor of :math:`a` and :math:`b`.
+        Greatest common divisor of $a$ and $b$.
 
     See Also:
         egcd, lcm, prod
@@ -70,7 +70,7 @@ def gcd(a, b):
 
             .. md-tab-item:: Polynomials
 
-                Generate irreducible polynomials over :math:`\mathrm{GF}(7)`.
+                Generate irreducible polynomials over $\mathrm{GF}(7)$.
 
                 .. ipython:: python
 
@@ -79,7 +79,7 @@ def gcd(a, b):
                     f2 = galois.irreducible_poly(7, 2); f2
                     f3 = galois.irreducible_poly(7, 3); f3
 
-                Compute the GCD of :math:`f_1(x)^2 f_2(x)` and :math:`f_1(x) f_3(x)`, which is :math:`f_1(x)`.
+                Compute the GCD of $f_1(x)^2 f_2(x)$ and $f_1(x) f_3(x)$, which is $f_1(x)$.
 
                 .. ipython:: python
 
@@ -108,16 +108,16 @@ def egcd(a: Poly, b: Poly) -> tuple[Poly, Poly, Poly]:
 @export
 def egcd(a, b):
     r"""
-    Finds the multiplicands of :math:`a` and :math:`b` such that :math:`a s + b t = \mathrm{gcd}(a, b)`.
+    Finds the multiplicands of $a$ and $b$ such that $a s + b t = \mathrm{gcd}(a, b)$.
 
     Arguments:
         a: The first integer or polynomial argument.
         b: The second integer or polynomial argument.
 
     Returns:
-        - Greatest common divisor of :math:`a` and :math:`b`.
-        - The multiplicand :math:`s` of :math:`a`.
-        - The multiplicand :math:`t` of :math:`b`.
+        - Greatest common divisor of $a$ and $b$.
+        - The multiplicand $s$ of $a$.
+        - The multiplicand $t$ of $b$.
 
     See Also:
         gcd, lcm, prod
@@ -146,7 +146,7 @@ def egcd(a, b):
 
             .. md-tab-item:: Polynomials
 
-                Generate irreducible polynomials over :math:`\mathrm{GF}(7)`.
+                Generate irreducible polynomials over $\mathrm{GF}(7)$.
 
                 .. ipython:: python
 
@@ -155,7 +155,7 @@ def egcd(a, b):
                     f2 = galois.irreducible_poly(7, 2); f2
                     f3 = galois.irreducible_poly(7, 3); f3
 
-                Compute the extended GCD of :math:`f_1(x)^2 f_2(x)` and :math:`f_1(x) f_3(x)`.
+                Compute the extended GCD of $f_1(x)^2 f_2(x)$ and $f_1(x) f_3(x)$.
 
                 .. ipython:: python
 
@@ -212,7 +212,7 @@ def lcm(*values):
 
             .. md-tab-item:: Polynomials
 
-                Generate irreducible polynomials over :math:`\mathrm{GF}(7)`.
+                Generate irreducible polynomials over $\mathrm{GF}(7)$.
 
                 .. ipython:: python
 
@@ -221,8 +221,8 @@ def lcm(*values):
                     f2 = galois.irreducible_poly(7, 2); f2
                     f3 = galois.irreducible_poly(7, 3); f3
 
-                Compute the LCM of three polynomials :math:`f_1(x)^2 f_2(x)`, :math:`f_1(x) f_3(x)`, and
-                :math:`f_2(x) f_3(x)`, which is :math:`f_1(x)^2 f_2(x) f_3(x)`.
+                Compute the LCM of three polynomials $f_1(x)^2 f_2(x)$, $f_1(x) f_3(x)$, and
+                $f_2(x) f_3(x)$, which is $f_1(x)^2 f_2(x) f_3(x)$.
 
                 .. ipython:: python
 
@@ -279,7 +279,7 @@ def prod(*values):
 
             .. md-tab-item:: Polynomials
 
-                Generate random polynomials over :math:`\mathrm{GF}(7)`.
+                Generate random polynomials over $\mathrm{GF}(7)$.
 
                 .. ipython:: python
 
@@ -349,7 +349,7 @@ def are_coprime(*values):
 
             .. md-tab-item:: Polynomials
 
-                Generate irreducible polynomials over :math:`\mathrm{GF}(7)`.
+                Generate irreducible polynomials over $\mathrm{GF}(7)$.
 
                 .. ipython:: python
 
@@ -397,23 +397,24 @@ def crt(remainders: Sequence[Poly], moduli: Sequence[Poly]) -> Poly:
 @export
 def crt(remainders, moduli):
     r"""
-    Solves the simultaneous system of congruences for :math:`x`.
+    Solves the simultaneous system of congruences for $x$.
 
     Arguments:
-        remainders: The integer or polynomial remainders :math:`a_i`.
-        moduli: The integer or polynomial moduli :math:`m_i`.
+        remainders: The integer or polynomial remainders $a_i$.
+        moduli: The integer or polynomial moduli $m_i$.
 
     Returns:
-        The simultaneous solution :math:`x` to the system of congruences.
+        The simultaneous solution $x$ to the system of congruences.
 
     Notes:
         This function implements the Chinese Remainder Theorem.
 
-        .. math::
-            x &\equiv a_1\ (\textrm{mod}\ m_1) \\
-            x &\equiv a_2\ (\textrm{mod}\ m_2) \\
-            x &\equiv \ldots \\
-            x &\equiv a_n\ (\textrm{mod}\ m_n)
+        $$
+        x &\equiv a_1\ (\textrm{mod}\ m_1) \\
+        x &\equiv a_2\ (\textrm{mod}\ m_2) \\
+        x &\equiv \ldots \\
+        x &\equiv a_n\ (\textrm{mod}\ m_n)
+        $$
 
     References:
         - Section 14.5 from https://cacr.uwaterloo.ca/hac/about/chap14.pdf
@@ -446,7 +447,7 @@ def crt(remainders, moduli):
 
             .. md-tab-item:: Polynomials
 
-                Define a system of polynomial congruences over :math:`\mathrm{GF}(7)`.
+                Define a system of polynomial congruences over $\mathrm{GF}(7)$.
 
                 .. ipython:: python
 
@@ -552,39 +553,39 @@ def factors(value):
     Computes the prime factors of a positive integer or the irreducible factors of a non-constant, monic polynomial.
 
     Arguments:
-        value: A positive integer :math:`n` or a non-constant, monic polynomial :math:`f(x)`.
+        value: A positive integer $n$ or a non-constant, monic polynomial $f(x)$.
 
     Returns:
-        - Sorted list of prime factors :math:`\{p_1, p_2, \dots, p_k\}` of :math:`n` with
-          :math:`p_1 < p_2 < \dots < p_k` or irreducible factors :math:`\{g_1(x), g_2(x), \dots, g_k(x)\}` of
-          :math:`f(x)` sorted in lexicographical order.
-        - List of corresponding multiplicities :math:`\{e_1, e_2, \dots, e_k\}`.
+        - Sorted list of prime factors $\{p_1, p_2, \dots, p_k\}$ of $n$ with
+          $p_1 < p_2 < \dots < p_k$ or irreducible factors $\{g_1(x), g_2(x), \dots, g_k(x)\}$ of
+          $f(x)$ sorted in lexicographical order.
+        - List of corresponding multiplicities $\{e_1, e_2, \dots, e_k\}$.
 
     Notes:
         .. md-tab-set::
 
             .. md-tab-item:: Integers
 
-                This function factors a positive integer :math:`n` into its :math:`k` prime factors such that
-                :math:`n = p_1^{e_1} p_2^{e_2} \dots p_k^{e_k}`.
+                This function factors a positive integer $n$ into its $k$ prime factors such that
+                $n = p_1^{e_1} p_2^{e_2} \dots p_k^{e_k}$.
 
                 Steps:
 
-                0. Test if :math:`n` is in the `Cunningham Book's
-                   <https://homes.cerias.purdue.edu/~ssw/cun/third/index.html>`_ database of :math:`n = p^m \pm 1`
+                0. Test if $n$ is in the `Cunningham Book's
+                   <https://homes.cerias.purdue.edu/~ssw/cun/third/index.html>`_ database of $n = p^m \pm 1$
                    factorizations. If so, return the prime factorization.
-                1. Test if :math:`n` is prime. If so, return `[n], [1]`. See :func:`~galois.is_prime`.
-                2. Test if :math:`n` is a perfect power, such that :math:`n = x^k`. If so, prime factor :math:`x` and
-                   multiply the exponents by :math:`k`. See :func:`~galois.perfect_power`.
-                3. Use trial division with a list of primes up to :math:`10^6`. If no residual factors, return the
+                1. Test if $n$ is prime. If so, return `[n], [1]`. See :func:`~galois.is_prime`.
+                2. Test if $n$ is a perfect power, such that $n = x^k$. If so, prime factor $x$ and
+                   multiply the exponents by $k$. See :func:`~galois.perfect_power`.
+                3. Use trial division with a list of primes up to $10^6$. If no residual factors, return the
                    discovered prime factors. See :func:`~galois.trial_division`.
                 4. Use Pollard's Rho algorithm to find a non-trivial factor of the residual. Continue until all are
                    found. See :func:`~galois.pollard_rho`.
 
             .. md-tab-item:: Polynomials
 
-                This function factors a monic polynomial :math:`f(x)` into its :math:`k` irreducible factors such that
-                :math:`f(x) = g_1(x)^{e_1} g_2(x)^{e_2} \dots g_k(x)^{e_k}`.
+                This function factors a monic polynomial $f(x)$ into its $k$ irreducible factors such that
+                $f(x) = g_1(x)^{e_1} g_2(x)^{e_2} \dots g_k(x)^{e_k}$.
 
                 Steps:
 
@@ -620,7 +621,7 @@ def factors(value):
 
             .. md-tab-item:: Polynomials
 
-                Generate irreducible polynomials over :math:`\mathrm{GF}(3)`.
+                Generate irreducible polynomials over $\mathrm{GF}(3)$.
 
                 .. ipython:: python
 
@@ -636,7 +637,7 @@ def factors(value):
                     e1, e2, e3 = 5, 4, 3
                     f = g1**e1 * g2**e2 * g3**e3; f
 
-                Factor the polynomial into its irreducible factors over :math:`\mathrm{GF}(3)`.
+                Factor the polynomial into its irreducible factors over $\mathrm{GF}(3)$.
 
                 .. ipython:: python
 
@@ -668,7 +669,7 @@ def is_square_free(value):
     Determines if an integer or polynomial is square-free.
 
     Arguments:
-        value: An integer :math:`n` or polynomial :math:`f(x)`.
+        value: An integer $n$ or polynomial $f(x)$.
 
     Returns:
         `True` if the integer or polynomial is square-free.
@@ -681,19 +682,17 @@ def is_square_free(value):
 
             .. md-tab-item:: Integers
 
-                A square-free integer :math:`n` is divisible by no perfect squares. As a consequence, the prime
-                factorization of a square-free integer :math:`n` is
+                A square-free integer $n$ is divisible by no perfect squares. As a consequence, the prime
+                factorization of a square-free integer $n$ is
 
-                .. math::
-                    n = \prod_{i=1}^{k} p_i^{e_i} = \prod_{i=1}^{k} p_i .
+                $$n = \prod_{i=1}^{k} p_i^{e_i} = \prod_{i=1}^{k} p_i .$$
 
             .. md-tab-item:: Polynomials
 
-                A square-free polynomial :math:`f(x)` has no irreducible factors with multiplicity greater than one.
+                A square-free polynomial $f(x)$ has no irreducible factors with multiplicity greater than one.
                 Therefore, its canonical factorization is
 
-                .. math::
-                    f(x) = \prod_{i=1}^{k} g_i(x)^{e_i} = \prod_{i=1}^{k} g_i(x) .
+                $$f(x) = \prod_{i=1}^{k} g_i(x)^{e_i} = \prod_{i=1}^{k} g_i(x) .$$
 
                 This test is also available in :func:`Poly.is_square_free`.
 
@@ -711,7 +710,7 @@ def is_square_free(value):
 
             .. md-tab-item:: Polynomials
 
-                Generate irreducible polynomials over :math:`\mathrm{GF}(3)`.
+                Generate irreducible polynomials over $\mathrm{GF}(3)$.
 
                 .. ipython:: python
 
@@ -719,7 +718,7 @@ def is_square_free(value):
                     f1 = galois.irreducible_poly(3, 3); f1
                     f2 = galois.irreducible_poly(3, 4); f2
 
-                Determine if composite polynomials are square-free over :math:`\mathrm{GF}(3)`.
+                Determine if composite polynomials are square-free over $\mathrm{GF}(3)$.
 
                 .. ipython:: python
 
diff --git a/src/galois/_polys/_conway.py b/src/galois/_polys/_conway.py
index 6287938b6..8c13b2763 100644
--- a/src/galois/_polys/_conway.py
+++ b/src/galois/_polys/_conway.py
@@ -17,8 +17,8 @@
 @method_of(Poly)
 def is_conway(f: Poly, search: bool = False) -> bool:
     r"""
-    Checks whether the degree-:math:`m` polynomial :math:`f(x)` over :math:`\mathrm{GF}(p)` is the
-    Conway polynomial :math:`C_{p,m}(x)`.
+    Checks whether the degree-$m$ polynomial $f(x)$ over $\mathrm{GF}(p)$ is the
+    Conway polynomial $C_{p,m}(x)$.
 
     .. question:: Why is this a method and not a property?
         :collapsible:
@@ -34,27 +34,27 @@ def is_conway(f: Poly, search: bool = False) -> bool:
                 Manually searching for a Conway polynomial is *very* computationally expensive.
 
     Returns:
-        `True` if the polynomial :math:`f(x)` is the Conway polynomial :math:`C_{p,m}(x)`.
+        `True` if the polynomial $f(x)$ is the Conway polynomial $C_{p,m}(x)$.
 
     Raises:
-        LookupError: If `search=False` and the Conway polynomial :math:`C_{p,m}` is not found in Frank Luebeck's
+        LookupError: If `search=False` and the Conway polynomial $C_{p,m}$ is not found in Frank Luebeck's
             database.
 
     See Also:
         conway_poly, Poly.is_conway_consistent, Poly.is_primitive
 
     Notes:
-        A degree-:math:`m` polynomial :math:`f(x)` over :math:`\mathrm{GF}(p)` is the *Conway polynomial*
-        :math:`C_{p,m}(x)` if it is monic, primitive, compatible with Conway polynomials :math:`C_{p,n}(x)` for all
-        :math:`n\ |\ m`, and is lexicographically first according to a special ordering.
+        A degree-$m$ polynomial $f(x)$ over $\mathrm{GF}(p)$ is the *Conway polynomial*
+        $C_{p,m}(x)$ if it is monic, primitive, compatible with Conway polynomials $C_{p,n}(x)$ for all
+        $n\ |\ m$, and is lexicographically first according to a special ordering.
 
-        A Conway polynomial :math:`C_{p,m}(x)` is *compatible* with Conway polynomials :math:`C_{p,n}(x)` for
-        :math:`n\ |\ m` if :math:`C_{p,n}(x^r)` divides :math:`C_{p,m}(x)`, where :math:`r = \frac{p^m - 1}{p^n - 1}`.
+        A Conway polynomial $C_{p,m}(x)$ is *compatible* with Conway polynomials $C_{p,n}(x)$ for
+        $n\ |\ m$ if $C_{p,n}(x^r)$ divides $C_{p,m}(x)$, where $r = \frac{p^m - 1}{p^n - 1}$.
 
-        The Conway lexicographic ordering is defined as follows. Given two degree-:math:`m` polynomials
-        :math:`g(x) = \sum_{i=0}^m g_i x^i` and :math:`h(x) = \sum_{i=0}^m h_i x^i`, then :math:`g < h` if and only if
-        there exists :math:`i` such that :math:`g_j = h_j` for all :math:`j > i` and
-        :math:`(-1)^{m-i} g_i < (-1)^{m-i} h_i`.
+        The Conway lexicographic ordering is defined as follows. Given two degree-$m$ polynomials
+        $g(x) = \sum_{i=0}^m g_i x^i$ and $h(x) = \sum_{i=0}^m h_i x^i$, then $g < h$ if and only if
+        there exists $i$ such that $g_j = h_j$ for all $j > i$ and
+        $(-1)^{m-i} g_i < (-1)^{m-i} h_i$.
 
     References:
         - http://www.math.rwth-aachen.de/~Frank.Luebeck/data/ConwayPol/CP7.html
@@ -104,8 +104,8 @@ def is_conway(f: Poly, search: bool = False) -> bool:
 @functools.lru_cache()
 def is_conway_consistent(f: Poly, search: bool = False) -> bool:
     r"""
-    Determines whether the degree-:math:`m` polynomial :math:`f(x)` over :math:`\mathrm{GF}(p)` is consistent
-    with smaller Conway polynomials :math:`C_{p,n}(x)` for all :math:`n\ |\ m`.
+    Determines whether the degree-$m$ polynomial $f(x)$ over $\mathrm{GF}(p)$ is consistent
+    with smaller Conway polynomials $C_{p,n}(x)$ for all $n\ |\ m$.
 
     .. question:: Why is this a method and not a property?
         :collapsible:
@@ -121,20 +121,20 @@ def is_conway_consistent(f: Poly, search: bool = False) -> bool:
                 Manually searching for a Conway polynomial is *very* computationally expensive.
 
     Returns:
-        `True` if the polynomial :math:`f(x)` is primitive and consistent with smaller Conway polynomials
-        :math:`C_{p,n}(x)` for all :math:`n\ |\ m`.
+        `True` if the polynomial $f(x)$ is primitive and consistent with smaller Conway polynomials
+        $C_{p,n}(x)$ for all $n\ |\ m$.
 
     Raises:
-        LookupError: If `search=False` and a smaller Conway polynomial :math:`C_{p,n}` is not found in Frank Luebeck's
+        LookupError: If `search=False` and a smaller Conway polynomial $C_{p,n}$ is not found in Frank Luebeck's
             database.
 
     See Also:
         conway_poly, Poly.is_conway, Poly.is_primitive
 
     Notes:
-        A degree-:math:`m` polynomial :math:`f(x)` over :math:`\mathrm{GF}(p)` is *compatible* with Conway polynomials
-        :math:`C_{p,n}(x)` for :math:`n\ |\ m` if :math:`C_{p,n}(x^r)` divides :math:`f(x)`, where
-        :math:`r = \frac{p^m - 1}{p^n - 1}`.
+        A degree-$m$ polynomial $f(x)$ over $\mathrm{GF}(p)$ is *compatible* with Conway polynomials
+        $C_{p,n}(x)$ for $n\ |\ m$ if $C_{p,n}(x^r)$ divides $f(x)$, where
+        $r = \frac{p^m - 1}{p^n - 1}$.
 
         A Conway-consistent polynomial has all the properties of a Conway polynomial except that it is not
         necessarily lexicographically first (according to a special ordering).
@@ -203,12 +203,12 @@ def is_conway_consistent(f: Poly, search: bool = False) -> bool:
 @export
 def conway_poly(characteristic: int, degree: int, search: bool = False) -> Poly:
     r"""
-    Returns the Conway polynomial :math:`C_{p,m}(x)` over :math:`\mathrm{GF}(p)` with degree :math:`m`.
+    Returns the Conway polynomial $C_{p,m}(x)$ over $\mathrm{GF}(p)$ with degree $m$.
 
     Arguments:
-        characteristic: The prime characteristic :math:`p` of the field :math:`\mathrm{GF}(p)` that the polynomial
+        characteristic: The prime characteristic $p$ of the field $\mathrm{GF}(p)$ that the polynomial
             is over.
-        degree: The degree :math:`m` of the Conway polynomial.
+        degree: The degree $m$ of the Conway polynomial.
         search: Manually search for Conway polynomials if they are not included in `Frank Luebeck's database
             <http://www.math.rwth-aachen.de/~Frank.Luebeck/data/ConwayPol/index.html>`_. The default is `False`.
 
@@ -217,30 +217,30 @@ def conway_poly(characteristic: int, degree: int, search: bool = False) -> Poly:
                 Manually searching for a Conway polynomial is *very* computationally expensive.
 
     Returns:
-        The degree-:math:`m` Conway polynomial :math:`C_{p,m}(x)` over :math:`\mathrm{GF}(p)`.
+        The degree-$m$ Conway polynomial $C_{p,m}(x)$ over $\mathrm{GF}(p)$.
 
     See Also:
         Poly.is_conway, Poly.is_conway_consistent, Poly.is_primitive, primitive_poly
 
     Raises:
-        LookupError: If `search=False` and the Conway polynomial :math:`C_{p,m}` is not found in Frank Luebeck's
+        LookupError: If `search=False` and the Conway polynomial $C_{p,m}$ is not found in Frank Luebeck's
             database.
 
     Notes:
-        A degree-:math:`m` polynomial :math:`f(x)` over :math:`\mathrm{GF}(p)` is the *Conway polynomial*
-        :math:`C_{p,m}(x)` if it is monic, primitive, compatible with Conway polynomials :math:`C_{p,n}(x)` for all
-        :math:`n\ |\ m`, and is lexicographically first according to a special ordering.
+        A degree-$m$ polynomial $f(x)$ over $\mathrm{GF}(p)$ is the *Conway polynomial*
+        $C_{p,m}(x)$ if it is monic, primitive, compatible with Conway polynomials $C_{p,n}(x)$ for all
+        $n\ |\ m$, and is lexicographically first according to a special ordering.
 
-        A Conway polynomial :math:`C_{p,m}(x)` is *compatible* with Conway polynomials :math:`C_{p,n}(x)` for
-        :math:`n\ |\ m` if :math:`C_{p,n}(x^r)` divides :math:`C_{p,m}(x)`, where :math:`r = \frac{p^m - 1}{p^n - 1}`.
+        A Conway polynomial $C_{p,m}(x)$ is *compatible* with Conway polynomials $C_{p,n}(x)$ for
+        $n\ |\ m$ if $C_{p,n}(x^r)$ divides $C_{p,m}(x)$, where $r = \frac{p^m - 1}{p^n - 1}$.
 
-        The Conway lexicographic ordering is defined as follows. Given two degree-:math:`m` polynomials
-        :math:`g(x) = \sum_{i=0}^m g_i x^i` and :math:`h(x) = \sum_{i=0}^m h_i x^i`, then :math:`g < h` if and only if
-        there exists :math:`i` such that :math:`g_j = h_j` for all :math:`j > i` and
-        :math:`(-1)^{m-i} g_i < (-1)^{m-i} h_i`.
+        The Conway lexicographic ordering is defined as follows. Given two degree-$m$ polynomials
+        $g(x) = \sum_{i=0}^m g_i x^i$ and $h(x) = \sum_{i=0}^m h_i x^i$, then $g < h$ if and only if
+        there exists $i$ such that $g_j = h_j$ for all $j > i$ and
+        $(-1)^{m-i} g_i < (-1)^{m-i} h_i$.
 
-        The Conway polynomial :math:`C_{p,m}(x)` provides a standard representation of :math:`\mathrm{GF}(p^m)` as a
-        splitting field of :math:`C_{p,m}(x)`. Conway polynomials provide compatibility between fields and their
+        The Conway polynomial $C_{p,m}(x)$ provides a standard representation of $\mathrm{GF}(p^m)$ as a
+        splitting field of $C_{p,m}(x)$. Conway polynomials provide compatibility between fields and their
         subfields and, hence, are the common way to represent extension fields.
 
     References:
@@ -300,11 +300,11 @@ def conway_poly(characteristic: int, degree: int, search: bool = False) -> Poly:
 
 def _conway_poly_database(characteristic: int, degree: int) -> Poly:
     r"""
-    Returns the Conway polynomial :math:`C_{p,m}(x)` over :math:`\mathrm{GF}(p)` with degree :math:`m`
+    Returns the Conway polynomial $C_{p,m}(x)$ over $\mathrm{GF}(p)$ with degree $m$
     from Frank Luebeck's database.
 
     Raises:
-        LookupError: If the Conway polynomial :math:`C_{p,m}(x)` is not found in Frank Luebeck's database.
+        LookupError: If the Conway polynomial $C_{p,m}(x)$ is not found in Frank Luebeck's database.
     """
     db = ConwayPolyDatabase()
     degrees, coeffs = db.fetch(characteristic, degree)
@@ -316,7 +316,7 @@ def _conway_poly_database(characteristic: int, degree: int) -> Poly:
 @functools.lru_cache()
 def _conway_poly_search(characteristic: int, degree: int) -> Poly:
     r"""
-    Manually searches for the Conway polynomial :math:`C_{p,m}(x)` over :math:`\mathrm{GF}(p)` with degree :math:`m`.
+    Manually searches for the Conway polynomial $C_{p,m}(x)$ over $\mathrm{GF}(p)$ with degree $m$.
     """
     for poly in _conway_lexicographic_order(characteristic, degree):
         if is_conway_consistent(poly):
@@ -333,7 +333,7 @@ def _conway_lexicographic_order(
     degree: int,
 ) -> Iterator[Poly]:
     r"""
-    Yields all monic polynomials of degree :math:`m` over :math:`\mathrm{GF}(p)` in the lexicographic order
+    Yields all monic polynomials of degree $m$ over $\mathrm{GF}(p)$ in the lexicographic order
     defined for Conway polynomials.
     """
     field = _factory.FIELD_FACTORY(characteristic)
diff --git a/src/galois/_polys/_dense.py b/src/galois/_polys/_dense.py
index 225573fa7..40f79df36 100644
--- a/src/galois/_polys/_dense.py
+++ b/src/galois/_polys/_dense.py
@@ -344,7 +344,7 @@ def __call__(self, a: Array, b: int, c: Array | None = None) -> Array:
         # Convert the integer b into a vector of uint64 [MSWord, ..., LSWord] so arbitrarily-large exponents may be
         # passed into the JIT-compiled version
         b_vec = []  # Pop on LSWord -> MSWord
-        while b > 2**64:
+        while b >= 2**64:
             q, r = divmod(b, 2**64)
             b_vec.append(r)
             b = q
diff --git a/src/galois/_polys/_factor.py b/src/galois/_polys/_factor.py
index 428672e4d..27da3c5fd 100644
--- a/src/galois/_polys/_factor.py
+++ b/src/galois/_polys/_factor.py
@@ -13,7 +13,7 @@
 @method_of(Poly)
 def is_square_free(f) -> bool:
     r"""
-    Determines whether the polynomial :math:`f(x)` over :math:`\mathrm{GF}(q)` is square-free.
+    Determines whether the polynomial $f(x)$ over $\mathrm{GF}(q)$ is square-free.
 
     .. question:: Why is this a method and not a property?
         :collapsible:
@@ -24,14 +24,13 @@ def is_square_free(f) -> bool:
         `True` if the polynomial is square-free.
 
     Notes:
-        A square-free polynomial :math:`f(x)` has no irreducible factors with multiplicity greater than one.
+        A square-free polynomial $f(x)$ has no irreducible factors with multiplicity greater than one.
         Therefore, its canonical factorization is
 
-        .. math::
-            f(x) = \prod_{i=1}^{k} g_i(x)^{e_i} = \prod_{i=1}^{k} g_i(x) .
+        $$f(x) = \prod_{i=1}^{k} g_i(x)^{e_i} = \prod_{i=1}^{k} g_i(x) .$$
 
     Examples:
-        Generate irreducible polynomials over :math:`\mathrm{GF}(3)`.
+        Generate irreducible polynomials over $\mathrm{GF}(3)$.
 
         .. ipython:: python
 
@@ -39,7 +38,7 @@ def is_square_free(f) -> bool:
             f1 = galois.irreducible_poly(3, 3); f1
             f2 = galois.irreducible_poly(3, 4); f2
 
-        Determine if composite polynomials are square-free over :math:`\mathrm{GF}(3)`.
+        Determine if composite polynomials are square-free over $\mathrm{GF}(3)$.
 
         .. ipython:: python
 
@@ -61,23 +60,22 @@ def is_square_free(f) -> bool:
 @method_of(Poly)
 def square_free_factors(f: Poly) -> tuple[list[Poly], list[int]]:
     r"""
-    Factors the monic polynomial :math:`f(x)` into a product of square-free polynomials.
+    Factors the monic polynomial $f(x)$ into a product of square-free polynomials.
 
     Returns:
-        - The list of non-constant, square-free polynomials :math:`h_j(x)` in the factorization.
-        - The list of corresponding multiplicities :math:`j`.
+        - The list of non-constant, square-free polynomials $h_j(x)$ in the factorization.
+        - The list of corresponding multiplicities $j$.
 
     Raises:
-        ValueError: If :math:`f(x)` is not monic or has degree 0.
+        ValueError: If $f(x)$ is not monic or has degree 0.
 
     Notes:
-        The Square-Free Factorization algorithm factors :math:`f(x)` into a product of :math:`m` square-free
-        polynomials :math:`h_j(x)` with multiplicity :math:`j`.
+        The Square-Free Factorization algorithm factors $f(x)$ into a product of $m$ square-free
+        polynomials $h_j(x)$ with multiplicity $j$.
 
-        .. math::
-            f(x) = \prod_{j=1}^{m} h_j(x)^j
+        $$f(x) = \prod_{j=1}^{m} h_j(x)^j$$
 
-        Some :math:`h_j(x) = 1`, but those polynomials are not returned by this function.
+        Some $h_j(x) = 1$, but those polynomials are not returned by this function.
 
         A complete polynomial factorization is implemented in :func:`~Poly.factors`.
 
@@ -86,8 +84,8 @@ def square_free_factors(f: Poly) -> tuple[list[Poly], list[int]]:
         - Section 2.1 from https://people.csail.mit.edu/dmoshkov/courses/codes/poly-factorization.pdf
 
     Examples:
-        Suppose :math:`f(x) = x(x^3 + 2x + 4)(x^2 + 4x + 1)^3` over :math:`\mathrm{GF}(5)`. Each polynomial
-        :math:`x`, :math:`x^3 + 2x + 4`, and :math:`x^2 + 4x + 1` are all irreducible over :math:`\mathrm{GF}(5)`.
+        Suppose $f(x) = x(x^3 + 2x + 4)(x^2 + 4x + 1)^3$ over $\mathrm{GF}(5)$. Each polynomial
+        $x$, $x^3 + 2x + 4$, and $x^2 + 4x + 1$ are all irreducible over $\mathrm{GF}(5)$.
 
         .. ipython:: python
 
@@ -97,8 +95,8 @@ def square_free_factors(f: Poly) -> tuple[list[Poly], list[int]]:
             c = galois.Poly([1,4,1], field=GF); c, c.is_irreducible()
             f = a * b * c**3; f
 
-        The square-free factorization is :math:`\{x(x^3 + 2x + 4), x^2 + 4x + 1\}` with multiplicities
-        :math:`\{1, 3\}`.
+        The square-free factorization is $\{x(x^3 + 2x + 4), x^2 + 4x + 1\}$ with multiplicities
+        $\{1, 3\}$.
 
         .. ipython:: python
 
@@ -160,35 +158,35 @@ def square_free_factors(f: Poly) -> tuple[list[Poly], list[int]]:
 @method_of(Poly)
 def distinct_degree_factors(f: Poly) -> tuple[list[Poly], list[int]]:
     r"""
-    Factors the monic, square-free polynomial :math:`f(x)` into a product of polynomials whose irreducible factors
+    Factors the monic, square-free polynomial $f(x)$ into a product of polynomials whose irreducible factors
     all have the same degree.
 
     Returns:
-        - The list of polynomials :math:`f_i(x)` whose irreducible factors all have degree :math:`i`.
-        - The list of corresponding distinct degrees :math:`i`.
+        - The list of polynomials $f_i(x)$ whose irreducible factors all have degree $i$.
+        - The list of corresponding distinct degrees $i$.
 
     Raises:
-        ValueError: If :math:`f(x)` is not monic, has degree 0, or is not square-free.
+        ValueError: If $f(x)$ is not monic, has degree 0, or is not square-free.
 
     Notes:
-        The Distinct-Degree Factorization algorithm factors a square-free polynomial :math:`f(x)` with degree
-        :math:`d` into a product of :math:`d` polynomials :math:`f_i(x)`, where :math:`f_i(x)` is the product of
-        all irreducible factors of :math:`f(x)` with degree :math:`i`.
+        The Distinct-Degree Factorization algorithm factors a square-free polynomial $f(x)$ with degree
+        $d$ into a product of $d$ polynomials $f_i(x)$, where $f_i(x)$ is the product of
+        all irreducible factors of $f(x)$ with degree $i$.
 
-        .. math::
-            f(x) = \prod_{i=1}^{d} f_i(x)
+        $$f(x) = \prod_{i=1}^{d} f_i(x)$$
 
-        For example, suppose :math:`f(x) = x(x + 1)(x^2 + x + 1)(x^3 + x + 1)(x^3 + x^2 + 1)` over
-        :math:`\mathrm{GF}(2)`, then the distinct-degree factorization is
+        For example, suppose $f(x) = x(x + 1)(x^2 + x + 1)(x^3 + x + 1)(x^3 + x^2 + 1)$ over
+        $\mathrm{GF}(2)$, then the distinct-degree factorization is
 
-        .. math::
-            f_1(x) &= x(x + 1) = x^2 + x \\
-            f_2(x) &= x^2 + x + 1 \\
-            f_3(x) &= (x^3 + x + 1)(x^3 + x^2 + 1) = x^6 + x^5 + x^4 + x^3 + x^2 + x + 1 \\
-            f_i(x) &= 1\ \textrm{for}\ i = 4, \dots, 10.
+        $$
+        f_1(x) &= x(x + 1) = x^2 + x \\
+        f_2(x) &= x^2 + x + 1 \\
+        f_3(x) &= (x^3 + x + 1)(x^3 + x^2 + 1) = x^6 + x^5 + x^4 + x^3 + x^2 + x + 1 \\
+        f_i(x) &= 1\ \textrm{for}\ i = 4, \dots, 10.
+        $$
 
-        Some :math:`f_i(x) = 1`, but those polynomials are not returned by this function. In this example,
-        the function returns :math:`\{f_1(x), f_2(x), f_3(x)\}` and :math:`\{1, 2, 3\}`.
+        Some $f_i(x) = 1$, but those polynomials are not returned by this function. In this example,
+        the function returns $\{f_1(x), f_2(x), f_3(x)\}$ and $\{1, 2, 3\}$.
 
         The Distinct-Degree Factorization algorithm is often applied after the Square-Free Factorization algorithm,
         see :func:`~Poly.square_free_factors`. A complete polynomial factorization is implemented in
@@ -199,8 +197,8 @@ def distinct_degree_factors(f: Poly) -> tuple[list[Poly], list[int]]:
         - Section 2.2 from https://people.csail.mit.edu/dmoshkov/courses/codes/poly-factorization.pdf
 
     Examples:
-        From the example in the notes, suppose :math:`f(x) = x(x + 1)(x^2 + x + 1)(x^3 + x + 1)(x^3 + x^2 + 1)`
-        over :math:`\mathrm{GF}(2)`.
+        From the example in the notes, suppose $f(x) = x(x + 1)(x^2 + x + 1)(x^3 + x + 1)(x^3 + x^2 + 1)$
+        over $\mathrm{GF}(2)$.
 
         .. ipython:: python
 
@@ -211,8 +209,8 @@ def distinct_degree_factors(f: Poly) -> tuple[list[Poly], list[int]]:
             e = galois.Poly([1, 1, 0, 1]); e, e.is_irreducible()
             f = a * b * c * d * e; f
 
-        The distinct-degree factorization is :math:`\{x(x + 1), x^2 + x + 1, (x^3 + x + 1)(x^3 + x^2 + 1)\}`
-        whose irreducible factors have degrees :math:`\{1, 2, 3\}`.
+        The distinct-degree factorization is $\{x(x + 1), x^2 + x + 1, (x^3 + x + 1)(x^3 + x^2 + 1)\}$
+        whose irreducible factors have degrees $\{1, 2, 3\}$.
 
         .. ipython:: python
 
@@ -265,21 +263,21 @@ def distinct_degree_factors(f: Poly) -> tuple[list[Poly], list[int]]:
 @method_of(Poly)
 def equal_degree_factors(f: Poly, degree: int) -> list[Poly]:
     r"""
-    Factors the monic, square-free polynomial :math:`f(x)` of degree :math:`rd` into a product of :math:`r`
-    irreducible factors with degree :math:`d`.
+    Factors the monic, square-free polynomial $f(x)$ of degree $rd$ into a product of $r$
+    irreducible factors with degree $d$.
 
     Arguments:
-        degree: The degree :math:`d` of each irreducible factor of :math:`f(x)`.
+        degree: The degree $d$ of each irreducible factor of $f(x)$.
 
     Returns:
-        The list of :math:`r` irreducible factors :math:`\{g_1(x), \dots, g_r(x)\}` in lexicographical order.
+        The list of $r$ irreducible factors $\{g_1(x), \dots, g_r(x)\}$ in lexicographical order.
 
     Raises:
-        ValueError: If :math:`f(x)` is not monic, has degree 0, or is not square-free.
+        ValueError: If $f(x)$ is not monic, has degree 0, or is not square-free.
 
     Notes:
-        The Equal-Degree Factorization algorithm factors a square-free polynomial :math:`f(x)` with degree
-        :math:`rd` into a product of :math:`r` irreducible polynomials each with degree :math:`d`. This function
+        The Equal-Degree Factorization algorithm factors a square-free polynomial $f(x)$ with degree
+        $rd$ into a product of $r$ irreducible polynomials each with degree $d$. This function
         implements the Cantor-Zassenhaus algorithm, which is probabilistic.
 
         The Equal-Degree Factorization algorithm is often applied after the Distinct-Degree Factorization algorithm,
@@ -291,7 +289,7 @@ def equal_degree_factors(f: Poly, degree: int) -> list[Poly]:
         - Section 1 from https://www.csa.iisc.ac.in/~chandan/courses/CNT/notes/lec8.pdf
 
     Examples:
-        Factor a product of degree-1 irreducible polynomials over :math:`\mathrm{GF}(2)`.
+        Factor a product of degree-1 irreducible polynomials over $\mathrm{GF}(2)$.
 
         .. ipython:: python
 
@@ -300,7 +298,7 @@ def equal_degree_factors(f: Poly, degree: int) -> list[Poly]:
             f = a * b; f
             f.equal_degree_factors(1)
 
-        Factor a product of degree-3 irreducible polynomials over :math:`\mathrm{GF}(5)`.
+        Factor a product of degree-3 irreducible polynomials over $\mathrm{GF}(5)$.
 
         .. ipython:: python
 
@@ -359,19 +357,19 @@ def equal_degree_factors(f: Poly, degree: int) -> list[Poly]:
 @method_of(Poly)
 def factors(f) -> tuple[list[Poly], list[int]]:
     r"""
-    Computes the irreducible factors of the non-constant, monic polynomial :math:`f(x)`.
+    Computes the irreducible factors of the non-constant, monic polynomial $f(x)$.
 
     Returns:
-        - Sorted list of irreducible factors :math:`\{g_1(x), g_2(x), \dots, g_k(x)\}` of :math:`f(x)` sorted in
+        - Sorted list of irreducible factors $\{g_1(x), g_2(x), \dots, g_k(x)\}$ of $f(x)$ sorted in
           lexicographical order.
-        - List of corresponding multiplicities :math:`\{e_1, e_2, \dots, e_k\}`.
+        - List of corresponding multiplicities $\{e_1, e_2, \dots, e_k\}$.
 
     Raises:
-        ValueError: If :math:`f(x)` is not monic or has degree 0.
+        ValueError: If $f(x)$ is not monic or has degree 0.
 
     Notes:
-        This function factors a monic polynomial :math:`f(x)` into its :math:`k` irreducible factors such that
-        :math:`f(x) = g_1(x)^{e_1} g_2(x)^{e_2} \dots g_k(x)^{e_k}`.
+        This function factors a monic polynomial $f(x)$ into its $k$ irreducible factors such that
+        $f(x) = g_1(x)^{e_1} g_2(x)^{e_2} \dots g_k(x)^{e_k}$.
 
         Steps:
 
@@ -387,7 +385,7 @@ def factors(f) -> tuple[list[Poly], list[int]]:
         - Section 2.1 from https://people.csail.mit.edu/dmoshkov/courses/codes/poly-factorization.pdf
 
     Examples:
-        Generate irreducible polynomials over :math:`\mathrm{GF}(3)`.
+        Generate irreducible polynomials over $\mathrm{GF}(3)$.
 
         .. ipython:: python
 
@@ -403,7 +401,7 @@ def factors(f) -> tuple[list[Poly], list[int]]:
             e1, e2, e3 = 5, 4, 3
             f = g1**e1 * g2**e2 * g3**e3; f
 
-        Factor the polynomial into its irreducible factors over :math:`\mathrm{GF}(3)`.
+        Factor the polynomial into its irreducible factors over $\mathrm{GF}(3)$.
 
         .. ipython:: python
 
diff --git a/src/galois/_polys/_irreducible.py b/src/galois/_polys/_irreducible.py
index 3dad2ac6e..5cb377c54 100644
--- a/src/galois/_polys/_irreducible.py
+++ b/src/galois/_polys/_irreducible.py
@@ -27,7 +27,7 @@
 @functools.lru_cache(maxsize=8192)
 def is_irreducible(f: Poly) -> bool:
     r"""
-    Determines whether the polynomial :math:`f(x)` over :math:`\mathrm{GF}(p^m)` is irreducible.
+    Determines whether the polynomial $f(x)$ over $\mathrm{GF}(p^m)$ is irreducible.
 
     .. question:: Why is this a method and not a property?
         :collapsible:
@@ -41,15 +41,15 @@ def is_irreducible(f: Poly) -> bool:
         irreducible_poly, irreducible_polys
 
     Notes:
-        A polynomial :math:`f(x) \in \mathrm{GF}(p^m)[x]` is *reducible* over :math:`\mathrm{GF}(p^m)` if it can
-        be represented as :math:`f(x) = g(x) h(x)` for some :math:`g(x), h(x) \in \mathrm{GF}(p^m)[x]` of strictly
-        lower degree. If :math:`f(x)` is not reducible, it is said to be *irreducible*. Since Galois fields are not
+        A polynomial $f(x) \in \mathrm{GF}(p^m)[x]$ is *reducible* over $\mathrm{GF}(p^m)$ if it can
+        be represented as $f(x) = g(x) h(x)$ for some $g(x), h(x) \in \mathrm{GF}(p^m)[x]$ of strictly
+        lower degree. If $f(x)$ is not reducible, it is said to be *irreducible*. Since Galois fields are not
         algebraically closed, such irreducible polynomials exist.
 
-        This function implements Rabin's irreducibility test. It says a degree-:math:`m` polynomial :math:`f(x)`
-        over :math:`\mathrm{GF}(q)` for prime power :math:`q` is irreducible if and only if
-        :math:`f(x)\ |\ (x^{q^m} - x)` and :math:`\textrm{gcd}(f(x),\ x^{q^{m_i}} - x) = 1` for
-        :math:`1 \le i \le k`, where :math:`m_i = m/p_i` for the :math:`k` prime divisors :math:`p_i` of :math:`m`.
+        This function implements Rabin's irreducibility test. It says a degree-$m$ polynomial $f(x)$
+        over $\mathrm{GF}(q)$ for prime power $q$ is irreducible if and only if
+        $f(x)\ |\ (x^{q^m} - x)$ and $\textrm{gcd}(f(x),\ x^{q^{m_i}} - x) = 1$ for
+        $1 \le i \le k$, where $m_i = m/p_i$ for the $k$ prime divisors $p_i$ of $m$.
 
     References:
         - Rabin, M. Probabilistic algorithms in finite fields. SIAM Journal on Computing (1980), 273-280.
@@ -132,12 +132,12 @@ def irreducible_poly(
     method: Literal["min", "max", "random"] = "min",
 ) -> Poly:
     r"""
-    Returns a monic irreducible polynomial :math:`f(x)` over :math:`\mathrm{GF}(q)` with degree :math:`m`.
+    Returns a monic irreducible polynomial $f(x)$ over $\mathrm{GF}(q)$ with degree $m$.
 
     Arguments:
-        order: The prime power order :math:`q` of the field :math:`\mathrm{GF}(q)` that the polynomial is over.
-        degree: The degree :math:`m` of the desired irreducible polynomial.
-        terms: The desired number of non-zero terms :math:`t` in the polynomial.
+        order: The prime power order $q$ of the field $\mathrm{GF}(q)$ that the polynomial is over.
+        degree: The degree $m$ of the desired irreducible polynomial.
+        terms: The desired number of non-zero terms $t$ in the polynomial.
 
             - `None` (default): Disregards the number of terms while searching for the polynomial.
             - `int`: The exact number of non-zero terms in the polynomial.
@@ -154,26 +154,26 @@ def irreducible_poly(
         Depending on the type of polynomial requested, this function may use a database of precomputed polynomials.
         Under these conditions, this function returns very quickly.
 
-        - For :math:`q = 2`, :math:`2 \le m \le 10000`, `terms="min"`, and `method="min"`, the
+        - For $q = 2$, $2 \le m \le 10000$, `terms="min"`, and `method="min"`, the
           `HP Table of Low-Weight Binary Irreducible Polynomials
           <https://www.hpl.hp.com/techreports/98/HPL-98-135.pdf>`_ is used.
 
     Returns:
-        The degree-:math:`m` monic irreducible polynomial over :math:`\mathrm{GF}(q)`.
+        The degree-$m$ monic irreducible polynomial over $\mathrm{GF}(q)$.
 
     Raises:
-        RuntimeError: If no monic irreducible polynomial of degree :math:`m` over :math:`\mathrm{GF}(q)` with
-            :math:`t` terms exists. If `terms` is `None` or `"min"`, this should never be raised.
+        RuntimeError: If no monic irreducible polynomial of degree $m$ over $\mathrm{GF}(q)$ with
+            $t$ terms exists. If `terms` is `None` or `"min"`, this should never be raised.
 
     See Also:
         Poly.is_irreducible, primitive_poly, conway_poly
 
     Notes:
-        If :math:`f(x)` is an irreducible polynomial over :math:`\mathrm{GF}(q)` and
-        :math:`a \in \mathrm{GF}(q) \backslash \{0\}`, then :math:`a \cdot f(x)` is also irreducible.
+        If $f(x)$ is an irreducible polynomial over $\mathrm{GF}(q)$ and
+        $a \in \mathrm{GF}(q) \backslash \{0\}$, then $a \cdot f(x)$ is also irreducible.
 
-        In addition to other applications, :math:`f(x)` produces the field extension :math:`\mathrm{GF}(q^m)` of
-        :math:`\mathrm{GF}(q)`.
+        In addition to other applications, $f(x)$ produces the field extension $\mathrm{GF}(q^m)$ of
+        $\mathrm{GF}(q)$.
 
     Examples:
         Find the lexicographically-first, lexicographically-last, and a random monic irreducible polynomial.
@@ -264,12 +264,12 @@ def irreducible_polys(
     reverse: bool = False,
 ) -> Iterator[Poly]:
     r"""
-    Iterates through all monic irreducible polynomials :math:`f(x)` over :math:`\mathrm{GF}(q)` with degree :math:`m`.
+    Iterates through all monic irreducible polynomials $f(x)$ over $\mathrm{GF}(q)$ with degree $m$.
 
     Arguments:
-        order: The prime power order :math:`q` of the field :math:`\mathrm{GF}(q)` that the polynomial is over.
-        degree: The degree :math:`m` of the desired irreducible polynomial.
-        terms: The desired number of non-zero terms :math:`t` in the polynomial.
+        order: The prime power order $q$ of the field $\mathrm{GF}(q)$ that the polynomial is over.
+        degree: The degree $m$ of the desired irreducible polynomial.
+        terms: The desired number of non-zero terms $t$ in the polynomial.
 
             - `None` (default): Disregards the number of terms while searching for the polynomial.
             - `int`: The exact number of non-zero terms in the polynomial.
@@ -279,20 +279,20 @@ def irreducible_polys(
             The default is `False`.
 
     Returns:
-        An iterator over all degree-:math:`m` monic irreducible polynomials over :math:`\mathrm{GF}(q)`.
+        An iterator over all degree-$m$ monic irreducible polynomials over $\mathrm{GF}(q)$.
 
     See Also:
         Poly.is_irreducible, primitive_polys
 
     Notes:
-        If :math:`f(x)` is an irreducible polynomial over :math:`\mathrm{GF}(q)` and
-        :math:`a \in \mathrm{GF}(q) \backslash \{0\}`, then :math:`a \cdot f(x)` is also irreducible.
+        If $f(x)$ is an irreducible polynomial over $\mathrm{GF}(q)$ and
+        $a \in \mathrm{GF}(q) \backslash \{0\}$, then $a \cdot f(x)$ is also irreducible.
 
-        In addition to other applications, :math:`f(x)` produces the field extension :math:`\mathrm{GF}(q^m)` of
-        :math:`\mathrm{GF}(q)`.
+        In addition to other applications, $f(x)$ produces the field extension $\mathrm{GF}(q^m)$ of
+        $\mathrm{GF}(q)$.
 
     Examples:
-        Find all monic irreducible polynomials over :math:`\mathrm{GF}(3)` with degree 4. You may also use `tuple()` on
+        Find all monic irreducible polynomials over $\mathrm{GF}(3)$ with degree 4. You may also use `tuple()` on
         the returned generator.
 
         .. ipython:: python
diff --git a/src/galois/_polys/_lagrange.py b/src/galois/_polys/_lagrange.py
index 8dc15e5e9..0e4695134 100644
--- a/src/galois/_polys/_lagrange.py
+++ b/src/galois/_polys/_lagrange.py
@@ -17,33 +17,31 @@
 @export
 def lagrange_poly(x: Array, y: Array) -> Poly:
     r"""
-    Computes the Lagrange interpolating polynomial :math:`L(x)` such that :math:`L(x_i) = y_i`.
+    Computes the Lagrange interpolating polynomial $L(x)$ such that $L(x_i) = y_i$.
 
     Arguments:
-        x: An array of :math:`x_i` values for the coordinates :math:`(x_i, y_i)`. Must be 1-D. Must have no
+        x: An array of $x_i$ values for the coordinates $(x_i, y_i)$. Must be 1-D. Must have no
             duplicate entries.
-        y: An array of :math:`y_i` values for the coordinates :math:`(x_i, y_i)`. Must be 1-D. Must be the same
-            size as :math:`x`.
+        y: An array of $y_i$ values for the coordinates $(x_i, y_i)$. Must be 1-D. Must be the same
+            size as $x$.
 
     Returns:
-        The Lagrange polynomial :math:`L(x)`.
+        The Lagrange polynomial $L(x)$.
 
     Notes:
         The Lagrange interpolating polynomial is defined as
 
-        .. math::
-            L(x) = \sum_{j=0}^{k-1} y_j \ell_j(x)
+        $$L(x) = \sum_{j=0}^{k-1} y_j \ell_j(x)$$
 
-        .. math::
-            \ell_j(x) = \prod_{\substack{0 \le m < k \\ m \ne j}} \frac{x - x_m}{x_j - x_m} .
+        $$\ell_j(x) = \prod_{\substack{0 \le m < k \\ m \ne j}} \frac{x - x_m}{x_j - x_m} .$$
 
-        It is the polynomial of minimal degree that satisfies :math:`L(x_i) = y_i`.
+        It is the polynomial of minimal degree that satisfies $L(x_i) = y_i$.
 
     References:
         - https://en.wikipedia.org/wiki/Lagrange_polynomial
 
     Examples:
-        Create random :math:`(x, y)` pairs in :math:`\mathrm{GF}(3^2)`.
+        Create random $(x, y)$ pairs in $\mathrm{GF}(3^2)$.
 
         .. ipython:: python
 
@@ -57,7 +55,7 @@ def lagrange_poly(x: Array, y: Array) -> Poly:
 
             L = galois.lagrange_poly(x, y); L
 
-        Show that the polynomial evaluated at :math:`x` is :math:`y`.
+        Show that the polynomial evaluated at $x$ is $y$.
 
         .. ipython:: python
 
diff --git a/src/galois/_polys/_poly.py b/src/galois/_polys/_poly.py
index 53bdc6990..833a6b11a 100644
--- a/src/galois/_polys/_poly.py
+++ b/src/galois/_polys/_poly.py
@@ -30,16 +30,16 @@
 @export
 class Poly:
     r"""
-    A univariate polynomial :math:`f(x)` over :math:`\mathrm{GF}(p^m)`.
+    A univariate polynomial $f(x)$ over $\mathrm{GF}(p^m)$.
 
     Examples:
-        Create a polynomial over :math:`\mathrm{GF}(2)`.
+        Create a polynomial over $\mathrm{GF}(2)$.
 
         .. ipython:: python
 
             galois.Poly([1, 0, 1, 1])
 
-        Create a polynomial over :math:`\mathrm{GF}(3^5)`.
+        Create a polynomial over $\mathrm{GF}(3^5)$.
 
         .. ipython:: python
 
@@ -77,27 +77,27 @@ def __init__(
         order: Literal["desc", "asc"] = "desc",
     ):
         r"""
-        Creates a polynomial :math:`f(x)` over :math:`\mathrm{GF}(p^m)`.
+        Creates a polynomial $f(x)$ over $\mathrm{GF}(p^m)$.
 
-        The polynomial :math:`f(x) = a_d x^d + a_{d-1} x^{d-1} + \dots + a_1 x + a_0` with degree :math:`d` has
-        coefficients :math:`\{a_{d}, a_{d-1}, \dots, a_1, a_0\}` in :math:`\mathrm{GF}(p^m)`.
+        The polynomial $f(x) = a_d x^d + a_{d-1} x^{d-1} + \dots + a_1 x + a_0$ with degree $d$ has
+        coefficients $\{a_{d}, a_{d-1}, \dots, a_1, a_0\}$ in $\mathrm{GF}(p^m)$.
 
         Arguments:
-            coeffs: The polynomial coefficients :math:`\{a_d, a_{d-1}, \dots, a_1, a_0\}`.
-            field: The Galois field :math:`\mathrm{GF}(p^m)` the polynomial is over.
+            coeffs: The polynomial coefficients $\{a_d, a_{d-1}, \dots, a_1, a_0\}$.
+            field: The Galois field $\mathrm{GF}(p^m)$ the polynomial is over.
 
                 - :obj:`None` (default): If the coefficients are an :obj:`~galois.Array`, they won't be modified.
                   If the coefficients are not explicitly in a Galois field, they are assumed to be from
-                  :math:`\mathrm{GF}(2)` and are converted using `galois.GF2(coeffs)`.
+                  $\mathrm{GF}(2)$ and are converted using `galois.GF2(coeffs)`.
                 - :obj:`~galois.Array` subclass: The coefficients are explicitly converted to this Galois field
                   using `field(coeffs)`.
 
             order: The interpretation of the coefficient degrees.
 
                 - `"desc"` (default): The first element of `coeffs` is the highest degree coefficient,
-                  i.e. :math:`\{a_d, a_{d-1}, \dots, a_1, a_0\}`.
+                  i.e. $\{a_d, a_{d-1}, \dots, a_1, a_0\}$.
                 - `"asc"`: The first element of `coeffs` is the lowest degree coefficient,
-                  i.e. :math:`\{a_0, a_1, \dots,  a_{d-1}, a_d\}`.
+                  i.e. $\{a_0, a_1, \dots,  a_{d-1}, a_d\}$.
         """
         verify_isinstance(coeffs, (list, tuple, np.ndarray, Array))
         verify_issubclass(field, Array, optional=True)
@@ -154,23 +154,23 @@ def _PolyLike(cls, poly_like: PolyLike, field: Type[Array] | None = None) -> Sel
     @classmethod
     def Zero(cls, field: Type[Array] | None = None) -> Self:
         r"""
-        Constructs the polynomial :math:`f(x) = 0` over :math:`\mathrm{GF}(p^m)`.
+        Constructs the polynomial $f(x) = 0$ over $\mathrm{GF}(p^m)$.
 
         Arguments:
-            field: The Galois field :math:`\mathrm{GF}(p^m)` the polynomial is over. The default is `None`
+            field: The Galois field $\mathrm{GF}(p^m)$ the polynomial is over. The default is `None`
                 which corresponds to :obj:`~galois.GF2`.
 
         Returns:
-            The polynomial :math:`f(x) = 0`.
+            The polynomial $f(x) = 0$.
 
         Examples:
-            Construct the zero polynomial over :math:`\mathrm{GF}(2)`.
+            Construct the zero polynomial over $\mathrm{GF}(2)$.
 
             .. ipython:: python
 
                 galois.Poly.Zero()
 
-            Construct the zero polynomial over :math:`\mathrm{GF}(3^5)`.
+            Construct the zero polynomial over $\mathrm{GF}(3^5)$.
 
             .. ipython:: python
 
@@ -182,23 +182,23 @@ def Zero(cls, field: Type[Array] | None = None) -> Self:
     @classmethod
     def One(cls, field: Type[Array] | None = None) -> Self:
         r"""
-        Constructs the polynomial :math:`f(x) = 1` over :math:`\mathrm{GF}(p^m)`.
+        Constructs the polynomial $f(x) = 1$ over $\mathrm{GF}(p^m)$.
 
         Arguments:
-            field: The Galois field :math:`\mathrm{GF}(p^m)` the polynomial is over. The default is `None` which
+            field: The Galois field $\mathrm{GF}(p^m)$ the polynomial is over. The default is `None` which
                 corresponds to :obj:`~galois.GF2`.
 
         Returns:
-            The polynomial :math:`f(x) = 1`.
+            The polynomial $f(x) = 1$.
 
         Examples:
-            Construct the one polynomial over :math:`\mathrm{GF}(2)`.
+            Construct the one polynomial over $\mathrm{GF}(2)$.
 
             .. ipython:: python
 
                 galois.Poly.One()
 
-            Construct the one polynomial over :math:`\mathrm{GF}(3^5)`.
+            Construct the one polynomial over $\mathrm{GF}(3^5)$.
 
             .. ipython:: python
 
@@ -210,23 +210,23 @@ def One(cls, field: Type[Array] | None = None) -> Self:
     @classmethod
     def Identity(cls, field: Type[Array] | None = None) -> Self:
         r"""
-        Constructs the polynomial :math:`f(x) = x` over :math:`\mathrm{GF}(p^m)`.
+        Constructs the polynomial $f(x) = x$ over $\mathrm{GF}(p^m)$.
 
         Arguments:
-            field: The Galois field :math:`\mathrm{GF}(p^m)` the polynomial is over. The default is `None` which
+            field: The Galois field $\mathrm{GF}(p^m)$ the polynomial is over. The default is `None` which
                 corresponds to :obj:`~galois.GF2`.
 
         Returns:
-            The polynomial :math:`f(x) = x`.
+            The polynomial $f(x) = x$.
 
         Examples:
-            Construct the identity polynomial over :math:`\mathrm{GF}(2)`.
+            Construct the identity polynomial over $\mathrm{GF}(2)$.
 
             .. ipython:: python
 
                 galois.Poly.Identity()
 
-            Construct the identity polynomial over :math:`\mathrm{GF}(3^5)`.
+            Construct the identity polynomial over $\mathrm{GF}(3^5)$.
 
             .. ipython:: python
 
@@ -243,27 +243,27 @@ def Random(
         field: Type[Array] | None = None,
     ) -> Self:
         r"""
-        Constructs a random polynomial over :math:`\mathrm{GF}(p^m)` with degree :math:`d`.
+        Constructs a random polynomial over $\mathrm{GF}(p^m)$ with degree $d$.
 
         Arguments:
             degree: The degree of the polynomial.
             seed: Non-negative integer used to initialize the PRNG. The default is `None` which means that
                 unpredictable entropy will be pulled from the OS to be used as the seed.
                 A :obj:`numpy.random.Generator` can also be passed.
-            field: The Galois field :math:`\mathrm{GF}(p^m)` the polynomial is over. The default is `None` which
+            field: The Galois field $\mathrm{GF}(p^m)$ the polynomial is over. The default is `None` which
                 corresponds to :obj:`~galois.GF2`.
 
         Returns:
-            The polynomial :math:`f(x)`.
+            The polynomial $f(x)$.
 
         Examples:
-            Construct a random degree-5 polynomial over :math:`\mathrm{GF}(2)`.
+            Construct a random degree-5 polynomial over $\mathrm{GF}(2)$.
 
             .. ipython:: python
 
                 galois.Poly.Random(5)
 
-            Construct a random degree-5 polynomial over :math:`\mathrm{GF}(3^5)` with a given seed. This produces
+            Construct a random degree-5 polynomial over $\mathrm{GF}(3^5)$ with a given seed. This produces
             repeatable results.
 
             .. ipython:: python
@@ -303,15 +303,15 @@ def Random(
     @classmethod
     def Str(cls, string: str, field: Type[Array] | None = None) -> Self:
         r"""
-        Constructs a polynomial over :math:`\mathrm{GF}(p^m)` from its string representation.
+        Constructs a polynomial over $\mathrm{GF}(p^m)$ from its string representation.
 
         Arguments:
-            string: The string representation of the polynomial :math:`f(x)`.
-            field: The Galois field :math:`\mathrm{GF}(p^m)` the polynomial is over. The default is `None` which
+            string: The string representation of the polynomial $f(x)$.
+            field: The Galois field $\mathrm{GF}(p^m)$ the polynomial is over. The default is `None` which
                 corresponds to :obj:`~galois.GF2`.
 
         Returns:
-            The polynomial :math:`f(x)`.
+            The polynomial $f(x)$.
 
         Notes:
             :func:`~galois.Poly.Str` and :func:`~galois.Poly.__str__` are inverse operations.
@@ -329,14 +329,14 @@ def Str(cls, string: str, field: Type[Array] | None = None) -> Self:
             - Any combination of the above rules is acceptable.
 
         Examples:
-            Construct a polynomial over :math:`\mathrm{GF}(2)` from its string representation.
+            Construct a polynomial over $\mathrm{GF}(2)$ from its string representation.
 
             .. ipython:: python
 
                 f = galois.Poly.Str("x^2 + 1"); f
                 str(f)
 
-            Construct a polynomial over :math:`\mathrm{GF}(3^5)` from its string representation.
+            Construct a polynomial over $\mathrm{GF}(3^5)$ from its string representation.
 
             .. ipython:: python
 
@@ -353,28 +353,28 @@ def Str(cls, string: str, field: Type[Array] | None = None) -> Self:
     @classmethod
     def Int(cls, integer: int, field: Type[Array] | None = None) -> Self:
         r"""
-        Constructs a polynomial over :math:`\mathrm{GF}(p^m)` from its integer representation.
+        Constructs a polynomial over $\mathrm{GF}(p^m)$ from its integer representation.
 
         Arguments:
-            integer: The integer representation of the polynomial :math:`f(x)`.
-            field: The Galois field :math:`\mathrm{GF}(p^m)` the polynomial is over. The default is `None` which
+            integer: The integer representation of the polynomial $f(x)$.
+            field: The Galois field $\mathrm{GF}(p^m)$ the polynomial is over. The default is `None` which
                 corresponds to :obj:`~galois.GF2`.
 
         Returns:
-            The polynomial :math:`f(x)`.
+            The polynomial $f(x)$.
 
         Notes:
             :func:`~galois.Poly.Int` and :func:`~galois.Poly.__int__` are inverse operations.
 
         Examples:
-            Construct a polynomial over :math:`\mathrm{GF}(2)` from its integer representation.
+            Construct a polynomial over $\mathrm{GF}(2)$ from its integer representation.
 
             .. ipython:: python
 
                 f = galois.Poly.Int(5); f
                 int(f)
 
-            Construct a polynomial over :math:`\mathrm{GF}(3^5)` from its integer representation.
+            Construct a polynomial over $\mathrm{GF}(3^5)$ from its integer representation.
 
             .. ipython:: python
 
@@ -384,14 +384,14 @@ def Int(cls, integer: int, field: Type[Array] | None = None) -> Self:
                 # The polynomial/integer equivalence
                 int(f) == 13*GF.order**3 + 117
 
-            Construct a polynomial over :math:`\mathrm{GF}(2)` from its binary string.
+            Construct a polynomial over $\mathrm{GF}(2)$ from its binary string.
 
             .. ipython:: python
 
                 f = galois.Poly.Int(int("0b1011", 2)); f
                 bin(f)
 
-            Construct a polynomial over :math:`\mathrm{GF}(2^3)` from its octal string.
+            Construct a polynomial over $\mathrm{GF}(2^3)$ from its octal string.
 
             .. ipython:: python
 
@@ -399,7 +399,7 @@ def Int(cls, integer: int, field: Type[Array] | None = None) -> Self:
                 f = galois.Poly.Int(int("0o5034", 8), field=GF); f
                 oct(f)
 
-            Construct a polynomial over :math:`\mathrm{GF}(2^8)` from its hexadecimal string.
+            Construct a polynomial over $\mathrm{GF}(2^8)$ from its hexadecimal string.
 
             .. ipython:: python
 
@@ -433,31 +433,31 @@ def Degrees(
         field: Type[Array] | None = None,
     ) -> Self:
         r"""
-        Constructs a polynomial over :math:`\mathrm{GF}(p^m)` from its non-zero degrees.
+        Constructs a polynomial over $\mathrm{GF}(p^m)$ from its non-zero degrees.
 
         Arguments:
             degrees: The polynomial degrees with non-zero coefficients.
             coeffs: The corresponding non-zero polynomial coefficients. The default is `None` which corresponds to
                 all ones.
-            field: The Galois field :math:`\mathrm{GF}(p^m)` the polynomial is over.
+            field: The Galois field $\mathrm{GF}(p^m)$ the polynomial is over.
 
                 - :obj:`None` (default): If the coefficients are an :obj:`~galois.Array`, they won't be modified.
                   If the coefficients are not explicitly in a Galois field, they are assumed to be from
-                  :math:`\mathrm{GF}(2)` and are converted using `galois.GF2(coeffs)`.
+                  $\mathrm{GF}(2)$ and are converted using `galois.GF2(coeffs)`.
                 - :obj:`~galois.Array` subclass: The coefficients are explicitly converted to this Galois field
                   using `field(coeffs)`.
 
         Returns:
-            The polynomial :math:`f(x)`.
+            The polynomial $f(x)$.
 
         Examples:
-            Construct a polynomial over :math:`\mathrm{GF}(2)` by specifying the degrees with non-zero coefficients.
+            Construct a polynomial over $\mathrm{GF}(2)$ by specifying the degrees with non-zero coefficients.
 
             .. ipython:: python
 
                 galois.Poly.Degrees([3, 1, 0])
 
-            Construct a polynomial over :math:`\mathrm{GF}(3^5)` by specifying the degrees with non-zero coefficients
+            Construct a polynomial over $\mathrm{GF}(3^5)$ by specifying the degrees with non-zero coefficients
             and their coefficient values.
 
             .. ipython:: python
@@ -522,35 +522,37 @@ def Roots(
         field: Type[Array] | None = None,
     ) -> Self:
         r"""
-        Constructs a monic polynomial over :math:`\mathrm{GF}(p^m)` from its roots.
+        Constructs a monic polynomial over $\mathrm{GF}(p^m)$ from its roots.
 
         Arguments:
             roots: The roots of the desired polynomial.
             multiplicities: The corresponding root multiplicities. The default is `None` which corresponds to
                 all ones.
-            field: The Galois field :math:`\mathrm{GF}(p^m)` the polynomial is over.
+            field: The Galois field $\mathrm{GF}(p^m)$ the polynomial is over.
 
                 - :obj:`None` (default): If the roots are an :obj:`~galois.Array`, they won't be modified. If the
-                  roots are not explicitly in a Galois field, they are assumed to be from :math:`\mathrm{GF}(2)` and
+                  roots are not explicitly in a Galois field, they are assumed to be from $\mathrm{GF}(2)$ and
                   are converted using `galois.GF2(roots)`.
                 - :obj:`~galois.Array` subclass: The roots are explicitly converted to this Galois field using
                   `field(roots)`.
 
         Returns:
-            The polynomial :math:`f(x)`.
+            The polynomial $f(x)$.
 
         Notes:
-            The polynomial :math:`f(x)` with :math:`k` roots :math:`\{r_1, r_2, \dots, r_k\}` with multiplicities
-            :math:`\{m_1, m_2, \dots, m_k\}` is
+            The polynomial $f(x)$ with $k$ roots $\{r_1, r_2, \dots, r_k\}$ with multiplicities
+            $\{m_1, m_2, \dots, m_k\}$ is
 
-            .. math::
-                f(x) &= (x - r_1)^{m_1} (x - r_2)^{m_2} \dots (x - r_k)^{m_k} \\
-                     &= a_d x^d + a_{d-1} x^{d-1} + \dots + a_1 x + a_0
+            $$
+            f(x)
+            &= (x - r_1)^{m_1} (x - r_2)^{m_2} \dots (x - r_k)^{m_k} \\
+            &= a_d x^d + a_{d-1} x^{d-1} + \dots + a_1 x + a_0
+            $$
 
-            with degree :math:`d = \sum_{i=1}^{k} m_i`.
+            with degree $d = \sum_{i=1}^{k} m_i$.
 
         Examples:
-            Construct a polynomial over :math:`\mathrm{GF}(2)` from a list of its roots.
+            Construct a polynomial over $\mathrm{GF}(2)$ from a list of its roots.
 
             .. ipython:: python
 
@@ -559,7 +561,7 @@ def Roots(
                 # Evaluate the polynomial at its roots
                 f(roots)
 
-            Construct a polynomial over :math:`\mathrm{GF}(3^5)` from a list of its roots with specific multiplicities.
+            Construct a polynomial over $\mathrm{GF}(3^5)$ from a list of its roots with specific multiplicities.
 
             .. ipython:: python
 
@@ -659,18 +661,17 @@ def coefficients(
 
     def reverse(self) -> Poly:
         r"""
-        Returns the :math:`d`-th reversal :math:`x^d f(\frac{1}{x})` of the polynomial :math:`f(x)` with
-        degree :math:`d`.
+        Returns the $d$-th reversal $x^d f(\frac{1}{x})$ of the polynomial $f(x)$ with
+        degree $d$.
 
         Returns:
-            The :math:`n`-th reversal :math:`x^n f(\frac{1}{x})`.
+            The $n$-th reversal $x^n f(\frac{1}{x})$.
 
         Notes:
-            For a polynomial :math:`f(x) = a_d x^d + a_{d-1} x^{d-1} + \dots + a_1 x + a_0` with degree :math:`d`,
-            the :math:`d`-th reversal is equivalent to reversing the coefficients.
+            For a polynomial $f(x) = a_d x^d + a_{d-1} x^{d-1} + \dots + a_1 x + a_0$ with degree $d$,
+            the $d$-th reversal is equivalent to reversing the coefficients.
 
-            .. math::
-                \textrm{rev}_d f(x) = x^d f(x^{-1}) = a_0 x^d + a_{1} x^{d-1} + \dots + a_{d-1} x + a_d
+            $$\textrm{rev}_d f(x) = x^d f(x^{-1}) = a_0 x^d + a_{1} x^{d-1} + \dots + a_{d-1} x + a_d$$
 
         Examples:
             .. ipython:: python
@@ -693,54 +694,56 @@ def roots(self, multiplicity: Literal[True]) -> tuple[Array, np.ndarray]:
 
     def roots(self, multiplicity=False):
         r"""
-        Calculates the roots :math:`r` of the polynomial :math:`f(x)`, such that :math:`f(r) = 0`.
+        Calculates the roots $r$ of the polynomial $f(x)$, such that $f(r) = 0$.
 
         Arguments:
             multiplicity: Optionally return the multiplicity of each root. The default is `False` which only returns
                 the unique roots.
 
         Returns:
-            - An array of roots of :math:`f(x)`. The roots are ordered in increasing order.
+            - An array of roots of $f(x)$. The roots are ordered in increasing order.
             - The multiplicity of each root. This is only returned if `multiplicity=True`.
 
         Notes:
-            This implementation uses Chien's search to find the roots :math:`\{r_1, r_2, \dots, r_k\}` of the
-            degree-:math:`d` polynomial
+            This implementation uses Chien's search to find the roots $\{r_1, r_2, \dots, r_k\}$ of the
+            degree-$d$ polynomial
 
-            .. math::
-                f(x) = a_{d}x^{d} + a_{d-1}x^{d-1} + \dots + a_1x + a_0,
+            $$f(x) = a_{d}x^{d} + a_{d-1}x^{d-1} + \dots + a_1x + a_0,$$
 
-            where :math:`k \le d`. Then, :math:`f(x)` can be factored as
+            where $k \le d$. Then, $f(x)$ can be factored as
 
-            .. math::
-                f(x) = (x - r_1)^{m_1} (x - r_2)^{m_2} \dots (x - r_k)^{m_k},
+            $$f(x) = (x - r_1)^{m_1} (x - r_2)^{m_2} \dots (x - r_k)^{m_k},$$
 
-            where :math:`m_i` is the multiplicity of root :math:`r_i` and :math:`d = \sum_{i=1}^{k} m_i`.
+            where $m_i$ is the multiplicity of root $r_i$ and $d = \sum_{i=1}^{k} m_i$.
 
             The Galois field elements can be represented as
-            :math:`\mathrm{GF}(p^m) = \{0, 1, \alpha, \alpha^2, \dots, \alpha^{p^m-2}\}`, where :math:`\alpha` is a
-            primitive element of :math:`\mathrm{GF}(p^m)`.
-
-            0 is a root of :math:`f(x)` if :math:`a_0 = 0`. 1 is a root of :math:`f(x)` if
-            :math:`\sum_{j=0}^{d} a_j = 0`. The remaining elements of :math:`\mathrm{GF}(p^m)` are powers of
-            :math:`\alpha`. The following equations calculate :math:`f(\alpha^i)`, where :math:`\alpha^i` is a
-            root of :math:`f(x)` if :math:`f(\alpha^i) = 0`.
-
-            .. math::
-                f(\alpha^i) &= a_{d}(\alpha^i)^{d} + \dots + a_1(\alpha^i) + a_0 \\
-                            &\overset{\Delta}{=} \lambda_{i,d} + \dots + \lambda_{i,1} + \lambda_{i,0} \\
-                            &= \sum_{j=0}^{d} \lambda_{i,j}
-
-            The next power of :math:`\alpha` can be easily calculated from the previous calculation.
-
-            .. math::
-                f(\alpha^{i+1}) &= a_{d}(\alpha^{i+1})^{d} + \dots + a_1(\alpha^{i+1}) + a_0 \\
-                                &= a_{d}(\alpha^i)^{d}\alpha^d + \dots + a_1(\alpha^i)\alpha + a_0 \\
-                                &= \lambda_{i,d}\alpha^d + \dots + \lambda_{i,1}\alpha + \lambda_{i,0} \\
-                                &= \sum_{j=0}^{d} \lambda_{i,j}\alpha^j
+            $\mathrm{GF}(p^m) = \{0, 1, \alpha, \alpha^2, \dots, \alpha^{p^m-2}\}$, where $\alpha$ is a
+            primitive element of $\mathrm{GF}(p^m)$.
+
+            0 is a root of $f(x)$ if $a_0 = 0$. 1 is a root of $f(x)$ if
+            $\sum_{j=0}^{d} a_j = 0$. The remaining elements of $\mathrm{GF}(p^m)$ are powers of
+            $\alpha$. The following equations calculate $f(\alpha^i)$, where $\alpha^i$ is a
+            root of $f(x)$ if $f(\alpha^i) = 0$.
+
+            $$
+            f(\alpha^i)
+            &= a_{d}(\alpha^i)^{d} + \dots + a_1(\alpha^i) + a_0 \\
+            &\overset{\Delta}{=} \lambda_{i,d} + \dots + \lambda_{i,1} + \lambda_{i,0} \\
+            &= \sum_{j=0}^{d} \lambda_{i,j}
+            $$
+
+            The next power of $\alpha$ can be easily calculated from the previous calculation.
+
+            $$
+            f(\alpha^{i+1})
+            &= a_{d}(\alpha^{i+1})^{d} + \dots + a_1(\alpha^{i+1}) + a_0 \\
+            &= a_{d}(\alpha^i)^{d}\alpha^d + \dots + a_1(\alpha^i)\alpha + a_0 \\
+            &= \lambda_{i,d}\alpha^d + \dots + \lambda_{i,1}\alpha + \lambda_{i,0} \\
+            &= \sum_{j=0}^{d} \lambda_{i,j}\alpha^j
+            $$
 
         Examples:
-            Find the roots of a polynomial over :math:`\mathrm{GF}(2)`.
+            Find the roots of a polynomial over $\mathrm{GF}(2)$.
 
             .. ipython:: python
 
@@ -748,7 +751,7 @@ def roots(self, multiplicity=False):
                 f.roots()
                 f.roots(multiplicity=True)
 
-            Find the roots of a polynomial over :math:`\mathrm{GF}(3^5)`.
+            Find the roots of a polynomial over $\mathrm{GF}(3^5)$.
 
             .. ipython:: python
 
@@ -769,14 +772,14 @@ def roots(self, multiplicity=False):
 
     def square_free_factors(self) -> tuple[list[Poly], list[int]]:
         r"""
-        Factors the monic polynomial :math:`f(x)` into a product of square-free polynomials.
+        Factors the monic polynomial $f(x)$ into a product of square-free polynomials.
         """
         # Will be monkey-patched in `_factor.py`
         raise NotImplementedError
 
     def distinct_degree_factors(self) -> tuple[list[Poly], list[int]]:
         r"""
-        Factors the monic, square-free polynomial :math:`f(x)` into a product of polynomials whose irreducible factors
+        Factors the monic, square-free polynomial $f(x)$ into a product of polynomials whose irreducible factors
         all have the same degree.
         """
         # Will be monkey-patched in `_factor.py`
@@ -784,50 +787,48 @@ def distinct_degree_factors(self) -> tuple[list[Poly], list[int]]:
 
     def equal_degree_factors(self, degree: int) -> list[Poly]:
         r"""
-        Factors the monic, square-free polynomial :math:`f(x)` of degree :math:`rd` into a product of :math:`r`
-        irreducible factors with degree :math:`d`.
+        Factors the monic, square-free polynomial $f(x)$ of degree $rd$ into a product of $r$
+        irreducible factors with degree $d$.
         """
         # Will be monkey-patched in `_factor.py`
         raise NotImplementedError
 
     def factors(self) -> tuple[list[Poly], list[int]]:
         r"""
-        Computes the irreducible factors of the non-constant, monic polynomial :math:`f(x)`.
+        Computes the irreducible factors of the non-constant, monic polynomial $f(x)$.
         """
         # Will be monkey-patched in `_factor.py`
         raise NotImplementedError
 
     def derivative(self, k: int = 1) -> Poly:
         r"""
-        Computes the :math:`k`-th formal derivative :math:`\frac{d^k}{dx^k} f(x)` of the polynomial :math:`f(x)`.
+        Computes the $k$-th formal derivative $\frac{d^k}{dx^k} f(x)$ of the polynomial $f(x)$.
 
         Arguments:
-            k: The number of derivatives to compute. 1 corresponds to :math:`p'(x)`, 2 corresponds to
-                :math:`p''(x)`, etc. The default is 1.
+            k: The number of derivatives to compute. 1 corresponds to $p'(x)$, 2 corresponds to
+                $p''(x)$, etc. The default is 1.
 
         Returns:
-            The :math:`k`-th formal derivative of the polynomial :math:`f(x)`.
+            The $k$-th formal derivative of the polynomial $f(x)$.
 
         Notes:
             For the polynomial
 
-            .. math::
-                f(x) = a_d x^d + a_{d-1} x^{d-1} + \dots + a_1 x + a_0
+            $$f(x) = a_d x^d + a_{d-1} x^{d-1} + \dots + a_1 x + a_0$$
 
             the first formal derivative is defined as
 
-            .. math::
-                f'(x) = (d) \cdot a_{d} x^{d-1} + (d-1) \cdot a_{d-1} x^{d-2} + \dots + (2) \cdot a_{2} x + a_1
+            $$f'(x) = (d) \cdot a_{d} x^{d-1} + (d-1) \cdot a_{d-1} x^{d-2} + \dots + (2) \cdot a_{2} x + a_1$$
 
-            where :math:`\cdot` represents scalar multiplication (repeated addition), not finite field multiplication.
+            where $\cdot$ represents scalar multiplication (repeated addition), not finite field multiplication.
             The exponent that is "brought down" and multiplied by the coefficient is an integer, not a finite field
-            element. For example, :math:`3 \cdot a = a + a + a`.
+            element. For example, $3 \cdot a = a + a + a$.
 
         References:
             - https://en.wikipedia.org/wiki/Formal_derivative
 
         Examples:
-            Compute the derivatives of a polynomial over :math:`\mathrm{GF}(2)`.
+            Compute the derivatives of a polynomial over $\mathrm{GF}(2)$.
 
             .. ipython:: python
 
@@ -836,7 +837,7 @@ def derivative(self, k: int = 1) -> Poly:
                 # p derivatives of a polynomial, where p is the field's characteristic, will always result in 0
                 f.derivative(GF.characteristic)
 
-            Compute the derivatives of a polynomial over :math:`\mathrm{GF}(7)`.
+            Compute the derivatives of a polynomial over $\mathrm{GF}(7)$.
 
             .. ipython:: python
 
@@ -848,7 +849,7 @@ def derivative(self, k: int = 1) -> Poly:
                 # p derivatives of a polynomial, where p is the field's characteristic, will always result in 0
                 f.derivative(GF.characteristic)
 
-            Compute the derivatives of a polynomial over :math:`\mathrm{GF}(3^5)`.
+            Compute the derivatives of a polynomial over $\mathrm{GF}(3^5)$.
 
             .. ipython:: python
 
@@ -881,37 +882,37 @@ def derivative(self, k: int = 1) -> Poly:
 
     def is_irreducible(self) -> bool:
         r"""
-        Determines whether the polynomial :math:`f(x)` over :math:`\mathrm{GF}(p^m)` is irreducible.
+        Determines whether the polynomial $f(x)$ over $\mathrm{GF}(p^m)$ is irreducible.
         """
         # Will be monkey-patched in `_irreducible.py`
         raise NotImplementedError
 
     def is_primitive(self) -> bool:
         r"""
-        Determines whether the polynomial :math:`f(x)` over :math:`\mathrm{GF}(q)` is primitive.
+        Determines whether the polynomial $f(x)$ over $\mathrm{GF}(q)$ is primitive.
         """
         # Will be monkey-patched in `_primitive.py`
         raise NotImplementedError
 
     def is_conway(self) -> bool:
         r"""
-        Checks whether the degree-:math:`m` polynomial :math:`f(x)` over :math:`\mathrm{GF}(p)` is the
-        Conway polynomial :math:`C_{p,m}(x)`.
+        Checks whether the degree-$m$ polynomial $f(x)$ over $\mathrm{GF}(p)$ is the
+        Conway polynomial $C_{p,m}(x)$.
         """
         # Will be monkey-patched in `_conway.py`
         raise NotImplementedError
 
     def is_conway_consistent(self) -> bool:
         r"""
-        Determines whether the degree-:math:`m` polynomial :math:`f(x)` over :math:`\mathrm{GF}(p)` is consistent
-        with smaller Conway polynomials :math:`C_{p,n}(x)` for all :math:`n\ |\ m`.
+        Determines whether the degree-$m$ polynomial $f(x)$ over $\mathrm{GF}(p)$ is consistent
+        with smaller Conway polynomials $C_{p,n}(x)$ for all $n\ |\ m$.
         """
         # Will be monkey-patched in `_conway.py`
         raise NotImplementedError
 
     def is_square_free(self) -> bool:
         r"""
-        Determines whether the polynomial :math:`f(x)` over :math:`\mathrm{GF}(q)` is square-free.
+        Determines whether the polynomial $f(x)$ over $\mathrm{GF}(q)$ is square-free.
         """
         # Will be monkey-patched in `_factor.py`
         raise NotImplementedError
@@ -977,13 +978,13 @@ def __int__(self) -> int:
         Notes:
             :func:`~galois.Poly.Int` and :func:`~galois.Poly.__int__` are inverse operations.
 
-            For the polynomial :math:`f(x) =  a_d x^d + a_{d-1} x^{d-1} + \dots + a_1 x + a_0` over the field
-            :math:`\mathrm{GF}(p^m)`, the integer representation is
-            :math:`i = a_d (p^m)^{d} + a_{d-1} (p^m)^{d-1} + \dots + a_1 (p^m) + a_0` using integer arithmetic,
+            For the polynomial $f(x) =  a_d x^d + a_{d-1} x^{d-1} + \dots + a_1 x + a_0$ over the field
+            $\mathrm{GF}(p^m)$, the integer representation is
+            $i = a_d (p^m)^{d} + a_{d-1} (p^m)^{d-1} + \dots + a_1 (p^m) + a_0$ using integer arithmetic,
             not finite field arithmetic.
 
-            Said differently, the polynomial coefficients :math:`\{a_d, a_{d-1}, \dots, a_1, a_0\}` are considered
-            as the :math:`d` "digits" of a radix-:math:`p^m` value. The polynomial's integer representation is that
+            Said differently, the polynomial coefficients $\{a_d, a_{d-1}, \dots, a_1, a_0\}$ are considered
+            as the $d$ "digits" of a radix-$p^m$ value. The polynomial's integer representation is that
             value in decimal (radix-10).
 
         Examples:
@@ -1012,30 +1013,30 @@ def __call__(self, at: Poly) -> Poly:
 
     def __call__(self, at, field=None, elementwise=True):
         r"""
-        Evaluates the polynomial :math:`f(x)` at :math:`x_0` or the polynomial composition :math:`f(g(x))`.
+        Evaluates the polynomial $f(x)$ at $x_0$ or the polynomial composition $f(g(x))$.
 
         Arguments:
-            at: A finite field scalar or array :math:`x_0` to evaluate the polynomial at or the polynomial
-                :math:`g(x)` to evaluate the polynomial composition :math:`f(g(x))`.
+            at: A finite field scalar or array $x_0$ to evaluate the polynomial at or the polynomial
+                $g(x)$ to evaluate the polynomial composition $f(g(x))$.
             field: The Galois field to evaluate the polynomial over. The default is `None` which represents
                 the polynomial's current field, i.e. :obj:`field`.
-            elementwise: Indicates whether to evaluate :math:`x_0` element-wise. The default is `True`. If `False`
-                (only valid for square matrices), the polynomial indeterminate :math:`x` is exponentiated using matrix
+            elementwise: Indicates whether to evaluate $x_0$ element-wise. The default is `True`. If `False`
+                (only valid for square matrices), the polynomial indeterminate $x$ is exponentiated using matrix
                 powers (repeated matrix multiplication).
 
         Returns:
-            The result of the polynomial evaluation :math:`f(x_0)`. The resulting array has the same shape as
-            :math:`x_0`. Or the polynomial composition :math:`f(g(x))`.
+            The result of the polynomial evaluation $f(x_0)$. The resulting array has the same shape as
+            $x_0$. Or the polynomial composition $f(g(x))$.
 
         Examples:
-            Create a polynomial over :math:`\mathrm{GF}(3^5)`.
+            Create a polynomial over $\mathrm{GF}(3^5)$.
 
             .. ipython:: python
 
                 GF = galois.GF(3**5)
                 f = galois.Poly([37, 123, 0, 201], field=GF); f
 
-            Evaluate the polynomial element-wise at :math:`x_0`.
+            Evaluate the polynomial element-wise at $x_0$.
 
             .. ipython:: python
 
@@ -1044,7 +1045,7 @@ def __call__(self, at, field=None, elementwise=True):
                 # The equivalent calculation
                 GF(37)*x0**3 + GF(123)*x0**2 + GF(201)
 
-            Evaluate the polynomial at the square matrix :math:`X_0`.
+            Evaluate the polynomial at the square matrix $X_0$.
 
             .. ipython:: python
 
@@ -1055,7 +1056,7 @@ def __call__(self, at, field=None, elementwise=True):
                 # The equivalent calculation
                 GF(37)*np.linalg.matrix_power(X0, 3) + GF(123)*np.linalg.matrix_power(X0, 2) + GF(201)*GF.Identity(2)
 
-            Evaluate the polynomial :math:`f(x)` at the polynomial :math:`g(x)`.
+            Evaluate the polynomial $f(x)$ at the polynomial $g(x)$.
 
             .. ipython:: python
 
@@ -1613,7 +1614,7 @@ def is_monic(self) -> bool:
         Returns whether the polynomial is monic, meaning its highest-degree coefficient is one.
 
         Examples:
-            A monic polynomial over :math:`\mathrm{GF}(7)`.
+            A monic polynomial over $\mathrm{GF}(7)$.
 
             .. ipython:: python
 
@@ -1621,7 +1622,7 @@ def is_monic(self) -> bool:
                 p = galois.Poly([1, 0, 4, 5], field=GF); p
                 p.is_monic
 
-            A non-monic polynomial over :math:`\mathrm{GF}(7)`.
+            A non-monic polynomial over $\mathrm{GF}(7)$.
 
             .. ipython:: python
 
diff --git a/src/galois/_polys/_primitive.py b/src/galois/_polys/_primitive.py
index 4452d6bae..a0a23686b 100644
--- a/src/galois/_polys/_primitive.py
+++ b/src/galois/_polys/_primitive.py
@@ -26,7 +26,7 @@
 @functools.lru_cache(maxsize=8192)
 def is_primitive(f: Poly) -> bool:
     r"""
-    Determines whether the polynomial :math:`f(x)` over :math:`\mathrm{GF}(q)` is primitive.
+    Determines whether the polynomial $f(x)$ over $\mathrm{GF}(q)$ is primitive.
 
     .. question:: Why is this a method and not a property?
         :collapsible:
@@ -40,9 +40,9 @@ def is_primitive(f: Poly) -> bool:
         primitive_poly, primitive_polys, conway_poly, matlab_primitive_poly
 
     Notes:
-        A degree-:math:`m` polynomial :math:`f(x)` over :math:`\mathrm{GF}(q)` is *primitive* if it is
-        irreducible and :math:`f(x)\ |\ (x^k - 1)` for :math:`k = q^m - 1` and no :math:`k` less than
-        :math:`q^m - 1`.
+        A degree-$m$ polynomial $f(x)$ over $\mathrm{GF}(q)$ is *primitive* if it is
+        irreducible and $f(x)\ |\ (x^k - 1)$ for $k = q^m - 1$ and no $k$ less than
+        $q^m - 1$.
 
     References:
         - Algorithm 4.77 from https://cacr.uwaterloo.ca/hac/about/chap4.pdf
@@ -58,7 +58,7 @@ def is_primitive(f: Poly) -> bool:
             f = galois.conway_poly(3, 5); f
             f.is_primitive()
 
-        The irreducible polynomial of :math:`\mathrm{GF}(2^8)` for AES is not primitive.
+        The irreducible polynomial of $\mathrm{GF}(2^8)$ for AES is not primitive.
 
         .. ipython:: python
 
@@ -111,12 +111,12 @@ def primitive_poly(
     method: Literal["min", "max", "random"] = "min",
 ) -> Poly:
     r"""
-    Returns a monic primitive polynomial :math:`f(x)` over :math:`\mathrm{GF}(q)` with degree :math:`m`.
+    Returns a monic primitive polynomial $f(x)$ over $\mathrm{GF}(q)$ with degree $m$.
 
     Arguments:
-        order: The prime power order :math:`q` of the field :math:`\mathrm{GF}(q)` that the polynomial is over.
-        degree: The degree :math:`m` of the desired primitive polynomial.
-        terms: The desired number of non-zero terms :math:`t` in the polynomial.
+        order: The prime power order $q$ of the field $\mathrm{GF}(q)$ that the polynomial is over.
+        degree: The degree $m$ of the desired primitive polynomial.
+        terms: The desired number of non-zero terms $t$ in the polynomial.
 
             - `None` (default): Disregards the number of terms while searching for the polynomial.
             - `int`: The exact number of non-zero terms in the polynomial.
@@ -129,23 +129,23 @@ def primitive_poly(
             - `"random"`: Returns a random polynomial.
 
     Returns:
-        The degree-:math:`m` monic primitive polynomial over :math:`\mathrm{GF}(q)`.
+        The degree-$m$ monic primitive polynomial over $\mathrm{GF}(q)$.
 
     Raises:
-        RuntimeError: If no monic primitive polynomial of degree :math:`m` over :math:`\mathrm{GF}(q)` with
-            :math:`t` terms exists. If `terms` is `None` or `"min"`, this should never be raised.
+        RuntimeError: If no monic primitive polynomial of degree $m$ over $\mathrm{GF}(q)$ with
+            $t$ terms exists. If `terms` is `None` or `"min"`, this should never be raised.
 
     See Also:
         Poly.is_primitive, matlab_primitive_poly, conway_poly
 
     Notes:
-        If :math:`f(x)` is a primitive polynomial over :math:`\mathrm{GF}(q)` and
-        :math:`a \in \mathrm{GF}(q) \backslash \{0\}`, then :math:`a \cdot f(x)` is also primitive.
+        If $f(x)$ is a primitive polynomial over $\mathrm{GF}(q)$ and
+        $a \in \mathrm{GF}(q) \backslash \{0\}$, then $a \cdot f(x)$ is also primitive.
 
-        In addition to other applications, :math:`f(x)` produces the field extension :math:`\mathrm{GF}(q^m)`
-        of :math:`\mathrm{GF}(q)`. Since :math:`f(x)` is primitive, :math:`x` is a primitive element :math:`\alpha`
-        of :math:`\mathrm{GF}(q^m)` such that
-        :math:`\mathrm{GF}(q^m) = \{0, 1, \alpha, \alpha^2, \dots, \alpha^{q^m-2}\}`.
+        In addition to other applications, $f(x)$ produces the field extension $\mathrm{GF}(q^m)$
+        of $\mathrm{GF}(q)$. Since $f(x)$ is primitive, $x$ is a primitive element $\alpha$
+        of $\mathrm{GF}(q^m)$ such that
+        $\mathrm{GF}(q^m) = \{0, 1, \alpha, \alpha^2, \dots, \alpha^{q^m-2}\}$.
 
     Examples:
         Find the lexicographically-first, lexicographically-last, and a random monic primitive polynomial.
@@ -242,12 +242,12 @@ def primitive_polys(
     reverse: bool = False,
 ) -> Iterator[Poly]:
     r"""
-    Iterates through all monic primitive polynomials :math:`f(x)` over :math:`\mathrm{GF}(q)` with degree :math:`m`.
+    Iterates through all monic primitive polynomials $f(x)$ over $\mathrm{GF}(q)$ with degree $m$.
 
     Arguments:
-        order: The prime power order :math:`q` of the field :math:`\mathrm{GF}(q)` that the polynomial is over.
-        degree: The degree :math:`m` of the desired primitive polynomial.
-        terms: The desired number of non-zero terms :math:`t` in the polynomial.
+        order: The prime power order $q$ of the field $\mathrm{GF}(q)$ that the polynomial is over.
+        degree: The degree $m$ of the desired primitive polynomial.
+        terms: The desired number of non-zero terms $t$ in the polynomial.
 
             - `None` (default): Disregards the number of terms while searching for the polynomial.
             - `int`: The exact number of non-zero terms in the polynomial.
@@ -257,22 +257,22 @@ def primitive_polys(
             The default is `False`.
 
     Returns:
-        An iterator over all degree-:math:`m` monic primitive polynomials over :math:`\mathrm{GF}(q)`.
+        An iterator over all degree-$m$ monic primitive polynomials over $\mathrm{GF}(q)$.
 
     See Also:
         Poly.is_primitive, irreducible_polys
 
     Notes:
-        If :math:`f(x)` is a primitive polynomial over :math:`\mathrm{GF}(q)` and
-        :math:`a \in \mathrm{GF}(q) \backslash \{0\}`, then :math:`a \cdot f(x)` is also primitive.
+        If $f(x)$ is a primitive polynomial over $\mathrm{GF}(q)$ and
+        $a \in \mathrm{GF}(q) \backslash \{0\}$, then $a \cdot f(x)$ is also primitive.
 
-        In addition to other applications, :math:`f(x)` produces the field extension :math:`\mathrm{GF}(q^m)`
-        of :math:`\mathrm{GF}(q)`. Since :math:`f(x)` is primitive, :math:`x` is a primitive element :math:`\alpha`
-        of :math:`\mathrm{GF}(q^m)` such that
-        :math:`\mathrm{GF}(q^m) = \{0, 1, \alpha, \alpha^2, \dots, \alpha^{q^m-2}\}`.
+        In addition to other applications, $f(x)$ produces the field extension $\mathrm{GF}(q^m)$
+        of $\mathrm{GF}(q)$. Since $f(x)$ is primitive, $x$ is a primitive element $\alpha$
+        of $\mathrm{GF}(q^m)$ such that
+        $\mathrm{GF}(q^m) = \{0, 1, \alpha, \alpha^2, \dots, \alpha^{q^m-2}\}$.
 
     Examples:
-        Find all monic primitive polynomials over :math:`\mathrm{GF}(3)` with degree 4. You may also use `tuple()` on
+        Find all monic primitive polynomials over $\mathrm{GF}(3)$ with degree 4. You may also use `tuple()` on
         the returned generator.
 
         .. ipython:: python
@@ -356,15 +356,15 @@ def primitive_polys(
 @export
 def matlab_primitive_poly(characteristic: int, degree: int) -> Poly:
     r"""
-    Returns Matlab's default primitive polynomial :math:`f(x)` over :math:`\mathrm{GF}(p)` with degree :math:`m`.
+    Returns Matlab's default primitive polynomial $f(x)$ over $\mathrm{GF}(p)$ with degree $m$.
 
     Arguments:
-        characteristic: The prime characteristic :math:`p` of the field :math:`\mathrm{GF}(p)` that the polynomial
+        characteristic: The prime characteristic $p$ of the field $\mathrm{GF}(p)$ that the polynomial
             is over.
-        degree: The degree :math:`m` of the desired primitive polynomial.
+        degree: The degree $m$ of the desired primitive polynomial.
 
     Returns:
-        Matlab's default degree-:math:`m` primitive polynomial over :math:`\mathrm{GF}(p)`.
+        Matlab's default degree-$m$ primitive polynomial over $\mathrm{GF}(p)$.
 
     See Also:
         Poly.is_primitive, primitive_poly, conway_poly
@@ -374,16 +374,16 @@ def matlab_primitive_poly(characteristic: int, degree: int) -> Poly:
         primitive polynomial with minimum terms, which is equivalent to `galois.primitive_poly(p, m, terms="min")`.
         There are three notable exceptions, however:
 
-        1. In :math:`\mathrm{GF}(2^7)`, Matlab uses :math:`x^7 + x^3 + 1`,
-           not :math:`x^7 + x + 1`.
-        2. In :math:`\mathrm{GF}(2^{14})`, Matlab uses :math:`x^{14} + x^{10} + x^6 + x + 1`,
-           not :math:`x^{14} + x^5 + x^3 + x + 1`.
-        3. In :math:`\mathrm{GF}(2^{16})`, Matlab uses :math:`x^{16} + x^{12} + x^3 + x + 1`,
-           not :math:`x^{16} + x^5 + x^3 + x^2 + 1`.
+        1. In $\mathrm{GF}(2^7)$, Matlab uses $x^7 + x^3 + 1$,
+           not $x^7 + x + 1$.
+        2. In $\mathrm{GF}(2^{14})$, Matlab uses $x^{14} + x^{10} + x^6 + x + 1$,
+           not $x^{14} + x^5 + x^3 + x + 1$.
+        3. In $\mathrm{GF}(2^{16})$, Matlab uses $x^{16} + x^{12} + x^3 + x + 1$,
+           not $x^{16} + x^5 + x^3 + x^2 + 1$.
 
     Warning:
-        This has been tested for all the :math:`\mathrm{GF}(2^m)` fields for :math:`2 \le m \le 16` (Matlab doesn't
-        support larger than 16). And it has been spot-checked for :math:`\mathrm{GF}(p^m)`. There may exist other
+        This has been tested for all the $\mathrm{GF}(2^m)$ fields for $2 \le m \le 16$ (Matlab doesn't
+        support larger than 16). And it has been spot-checked for $\mathrm{GF}(p^m)$. There may exist other
         exceptions. Please submit a `GitHub issue <https://github.com/mhostetter/galois/issues>`_ if you discover one.
 
     References:
diff --git a/src/galois/_prime.py b/src/galois/_prime.py
index 0a13e788f..359aa94f3 100644
--- a/src/galois/_prime.py
+++ b/src/galois/_prime.py
@@ -29,13 +29,13 @@
 @export
 def primes(n: int) -> list[int]:
     r"""
-    Returns all primes :math:`p` for :math:`p \le n`.
+    Returns all primes $p$ for $p \le n$.
 
     Arguments:
         n: An integer.
 
     Returns:
-        All primes up to and including :math:`n`. If :math:`n < 2`, the function returns an empty list.
+        All primes up to and including $n$. If $n < 2$, the function returns an empty list.
 
     See Also:
         kth_prime, prev_prime, next_prime
@@ -106,13 +106,13 @@ def primes(n: int) -> list[int]:
 @export
 def kth_prime(k: int) -> int:
     r"""
-    Returns the :math:`k`-th prime, where :math:`k = \{1,2,3,4,\dots\}` for primes :math:`p = \{2,3,5,7,\dots\}`.
+    Returns the $k$-th prime, where $k = \{1,2,3,4,\dots\}$ for primes $p = \{2,3,5,7,\dots\}$.
 
     Arguments:
         k: The prime index (1-indexed).
 
     Returns:
-        The :math:`k`-th prime.
+        The $k$-th prime.
 
     See Also:
         primes, prev_prime, next_prime
@@ -141,13 +141,13 @@ def kth_prime(k: int) -> int:
 @export
 def prev_prime(n: int) -> int:
     r"""
-    Returns the nearest prime :math:`p`, such that :math:`p \le n`.
+    Returns the nearest prime $p$, such that $p \le n$.
 
     Arguments:
-        n: An integer :math:`n \ge 2`.
+        n: An integer $n \ge 2$.
 
     Returns:
-        The nearest prime :math:`p \le n`.
+        The nearest prime $p \le n$.
 
     See Also:
         primes, kth_prime, next_prime
@@ -183,13 +183,13 @@ def prev_prime(n: int) -> int:
 @export
 def next_prime(n: int) -> int:
     r"""
-    Returns the nearest prime :math:`p`, such that :math:`p > n`.
+    Returns the nearest prime $p$, such that $p > n$.
 
     Arguments:
         n: An integer.
 
     Returns:
-        The nearest prime :math:`p > n`.
+        The nearest prime $p > n$.
 
     See Also:
         primes, kth_prime, prev_prime
@@ -223,22 +223,22 @@ def next_prime(n: int) -> int:
 @export
 def random_prime(bits: int, seed: int | None = None) -> int:
     r"""
-    Returns a random prime :math:`p` with :math:`b` bits, such that :math:`2^b \le p < 2^{b+1}`.
+    Returns a random prime $p$ with $b$ bits, such that $2^b \le p < 2^{b+1}$.
 
     Arguments:
-        bits: The number of bits in the prime :math:`p`.
+        bits: The number of bits in the prime $p$.
         seed: Non-negative integer used to initialize the PRNG. The default is `None` which means that unpredictable
             entropy will be pulled from the OS to be used as the seed.
 
     Returns:
-        A random prime in :math:`2^b \le p < 2^{b+1}`.
+        A random prime in $2^b \le p < 2^{b+1}$.
 
     See Also:
         prev_prime, next_prime
 
     Notes:
-        This function randomly generates integers with :math:`b` bits and uses the primality tests in
-        :func:`~galois.is_prime` to determine if :math:`p` is prime.
+        This function randomly generates integers with $b$ bits and uses the primality tests in
+        :func:`~galois.is_prime` to determine if $p$ is prime.
 
     References:
         - https://en.wikipedia.org/wiki/Prime_number_theorem
@@ -251,7 +251,7 @@ def random_prime(bits: int, seed: int | None = None) -> int:
             p = galois.random_prime(1024, seed=1); p
             galois.is_prime(p)
 
-        Verify that :math:`p` is prime using the OpenSSL library.
+        Verify that $p$ is prime using the OpenSSL library.
 
         .. code-block:: console
 
@@ -335,19 +335,19 @@ def random_prime(bits: int, seed: int | None = None) -> int:
 @export
 def mersenne_exponents(n: int | None = None) -> list[int]:
     r"""
-    Returns all known Mersenne exponents :math:`e` for :math:`e \le n`.
+    Returns all known Mersenne exponents $e$ for $e \le n$.
 
     Arguments:
         n: The max exponent of 2. The default is `None` which returns all known Mersenne exponents.
 
     Returns:
-        The list of Mersenne exponents :math:`e` for :math:`e \le n`.
+        The list of Mersenne exponents $e$ for $e \le n$.
 
     See Also:
         mersenne_primes
 
     Notes:
-        A Mersenne exponent :math:`e` is an exponent of 2 such that :math:`2^e - 1` is prime.
+        A Mersenne exponent $e$ is an exponent of 2 such that $2^e - 1$ is prime.
 
     References:
         - https://oeis.org/A000043
@@ -378,13 +378,13 @@ def mersenne_exponents(n: int | None = None) -> list[int]:
 @export
 def mersenne_primes(n: int | None = None) -> list[int]:
     r"""
-    Returns all known Mersenne primes :math:`p` for :math:`p \le 2^n - 1`.
+    Returns all known Mersenne primes $p$ for $p \le 2^n - 1$.
 
     Arguments:
         n: The max power of 2. The default is `None` which returns all known Mersenne exponents.
 
     Returns:
-        The list of known Mersenne primes :math:`p` for :math:`p \le 2^n - 1`.
+        The list of known Mersenne primes $p$ for $p \le 2^n - 1$.
 
     See Also:
         mersenne_exponents
@@ -416,30 +416,30 @@ def mersenne_primes(n: int | None = None) -> list[int]:
 @export
 def fermat_primality_test(n: int, a: int | None = None, rounds: int = 1) -> bool:
     r"""
-    Determines if :math:`n` is composite using Fermat's primality test.
+    Determines if $n$ is composite using Fermat's primality test.
 
     Arguments:
-        n: An odd integer :math:`n \ge 3`.
-        a: An integer in :math:`2 \le a \le n - 2`. The default is `None` which selects a random :math:`a`.
-        rounds: The number of iterations attempting to detect :math:`n` as composite. Additional rounds will choose
-            a new :math:`a`. The default is 1.
+        n: An odd integer $n \ge 3$.
+        a: An integer in $2 \le a \le n - 2$. The default is `None` which selects a random $a$.
+        rounds: The number of iterations attempting to detect $n$ as composite. Additional rounds will choose
+            a new $a$. The default is 1.
 
     Returns:
-        `False` if :math:`n` is shown to be composite. `True` if :math:`n` is a probable prime.
+        `False` if $n$ is shown to be composite. `True` if $n$ is a probable prime.
 
     See Also:
         is_prime, miller_rabin_primality_test
 
     Notes:
-        Fermat's theorem says that for prime :math:`p` and :math:`1 \le a \le p-1`, the congruence
-        :math:`a^{p-1} \equiv 1\ (\textrm{mod}\ p)` holds. Fermat's primality test of :math:`n` computes
-        :math:`a^{n-1}\ \textrm{mod}\ n` for some :math:`1 \le a \le n-1`. If :math:`a` is such that
-        :math:`a^{p-1} \not\equiv 1\ (\textrm{mod}\ p)`, then :math:`a` is said to be a *Fermat witness* to the
-        compositeness of :math:`n`. If :math:`n` is composite and :math:`a^{p-1} \equiv 1\ (\textrm{mod}\ p)`, then
-        :math:`a` is said to be a *Fermat liar* to the primality of :math:`n`.
+        Fermat's theorem says that for prime $p$ and $1 \le a \le p-1$, the congruence
+        $a^{p-1} \equiv 1\ (\textrm{mod}\ p)$ holds. Fermat's primality test of $n$ computes
+        $a^{n-1}\ \textrm{mod}\ n$ for some $1 \le a \le n-1$. If $a$ is such that
+        $a^{p-1} \not\equiv 1\ (\textrm{mod}\ p)$, then $a$ is said to be a *Fermat witness* to the
+        compositeness of $n$. If $n$ is composite and $a^{p-1} \equiv 1\ (\textrm{mod}\ p)$, then
+        $a$ is said to be a *Fermat liar* to the primality of $n$.
 
-        Since :math:`a = \{1, n-1\}` are Fermat liars for all composite :math:`n`, it is common to reduce the range of
-        possible :math:`a` to :math:`2 \le a \le n - 2`.
+        Since $a = \{1, n-1\}$ are Fermat liars for all composite $n$, it is common to reduce the range of
+        possible $a$ to $2 \le a \le n - 2$.
 
     References:
         - Section 4.2.1 from https://cacr.uwaterloo.ca/hac/about/chap4.pdf
@@ -508,38 +508,38 @@ def fermat_primality_test(n: int, a: int | None = None, rounds: int = 1) -> bool
 @export
 def miller_rabin_primality_test(n: int, a: int = 2, rounds: int = 1) -> bool:
     r"""
-    Determines if :math:`n` is composite using the Miller-Rabin primality test.
+    Determines if $n$ is composite using the Miller-Rabin primality test.
 
     Arguments:
-        n: An odd integer :math:`n \ge 3`.
-        a: An integer in :math:`2 \le a \le n - 2`. The default is 2.
-        rounds: The number of iterations attempting to detect :math:`n` as composite. Additional rounds will choose
-            consecutive primes for :math:`a`. The default is 1.
+        n: An odd integer $n \ge 3$.
+        a: An integer in $2 \le a \le n - 2$. The default is 2.
+        rounds: The number of iterations attempting to detect $n$ as composite. Additional rounds will choose
+            consecutive primes for $a$. The default is 1.
 
     Returns:
-        `False` if :math:`n` is shown to be composite. `True` if :math:`n` is probable prime.
+        `False` if $n$ is shown to be composite. `True` if $n$ is probable prime.
 
     See Also:
         is_prime, fermat_primality_test
 
     Notes:
-        The Miller-Rabin primality test is based on the fact that for odd :math:`n` with factorization
-        :math:`n = 2^s r` for odd :math:`r` and integer :math:`a` such that :math:`\textrm{gcd}(a, n) = 1`, then either
-        :math:`a^r \equiv 1\ (\textrm{mod}\ n)` or :math:`a^{2^j r} \equiv -1\ (\textrm{mod}\ n)` for some :math:`j` in
-        :math:`0 \le j \le s - 1`.
+        The Miller-Rabin primality test is based on the fact that for odd $n$ with factorization
+        $n = 2^s r$ for odd $r$ and integer $a$ such that $\textrm{gcd}(a, n) = 1$, then either
+        $a^r \equiv 1\ (\textrm{mod}\ n)$ or $a^{2^j r} \equiv -1\ (\textrm{mod}\ n)$ for some $j$ in
+        $0 \le j \le s - 1$.
 
-        In the Miller-Rabin primality test, if :math:`a^r \not\equiv 1\ (\textrm{mod}\ n)` and
-        :math:`a^{2^j r} \not\equiv -1\ (\textrm{mod}\ n)` for all :math:`j` in :math:`0 \le j \le s - 1`, then
-        :math:`a` is called a *strong witness* to the compositeness of :math:`n`. If not, namely
-        :math:`a^r \equiv 1\ (\textrm{mod}\ n)` or :math:`a^{2^j r} \equiv -1\ (\textrm{mod}\ n)` for any :math:`j`
-        in :math:`0 \le j \le s - 1`, then :math:`a` is called a *strong liar* to the primality of :math:`n` and
-        :math:`n` is called a *strong pseudoprime to the base a*.
+        In the Miller-Rabin primality test, if $a^r \not\equiv 1\ (\textrm{mod}\ n)$ and
+        $a^{2^j r} \not\equiv -1\ (\textrm{mod}\ n)$ for all $j$ in $0 \le j \le s - 1$, then
+        $a$ is called a *strong witness* to the compositeness of $n$. If not, namely
+        $a^r \equiv 1\ (\textrm{mod}\ n)$ or $a^{2^j r} \equiv -1\ (\textrm{mod}\ n)$ for any $j$
+        in $0 \le j \le s - 1$, then $a$ is called a *strong liar* to the primality of $n$ and
+        $n$ is called a *strong pseudoprime to the base a*.
 
-        Since :math:`a = \{1, n-1\}` are strong liars for all composite :math:`n`, it is common to reduce the range
-        of possible :math:`a` to :math:`2 \le a \le n - 2`.
+        Since $a = \{1, n-1\}$ are strong liars for all composite $n$, it is common to reduce the range
+        of possible $a$ to $2 \le a \le n - 2$.
 
-        For composite odd :math:`n`, the probability that the Miller-Rabin test declares it a probable prime is less
-        than :math:`(\frac{1}{4})^t`, where :math:`t` is the number of rounds, and is often much lower.
+        For composite odd $n$, the probability that the Miller-Rabin test declares it a probable prime is less
+        than $(\frac{1}{4})^t$, where $t$ is the number of rounds, and is often much lower.
 
     References:
         - Section 4.2.3 from https://cacr.uwaterloo.ca/hac/about/chap4.pdf
@@ -554,8 +554,8 @@ def miller_rabin_primality_test(n: int, a: int = 2, rounds: int = 1) -> bool:
             [galois.is_prime(p) for p in primes]
             [galois.miller_rabin_primality_test(p) for p in primes]
 
-        However, a composite :math:`n` may have strong liars. 91 has
-        :math:`\{9,10,12,16,17,22,29,38,53,62,69,74,75,79,81,82\}` as strong liars.
+        However, a composite $n$ may have strong liars. 91 has
+        $\{9,10,12,16,17,22,29,38,53,62,69,74,75,79,81,82\}$ as strong liars.
 
         .. ipython:: python
 
@@ -618,22 +618,22 @@ def miller_rabin_primality_test(n: int, a: int = 2, rounds: int = 1) -> bool:
 @export
 def legendre_symbol(a: int, p: int) -> int:
     r"""
-    Computes the Legendre symbol :math:`(\frac{a}{p})`.
+    Computes the Legendre symbol $(\frac{a}{p})$.
 
     Arguments:
         a: An integer.
-        p: An odd prime :math:`p \ge 3`.
+        p: An odd prime $p \ge 3$.
 
     Returns:
-        The Legendre symbol :math:`(\frac{a}{p})` with value in :math:`\{0, 1, -1\}`.
+        The Legendre symbol $(\frac{a}{p})$ with value in $\{0, 1, -1\}$.
 
     See Also:
         jacobi_symbol, kronecker_symbol
 
     Notes:
-        The Legendre symbol is useful for determining if :math:`a` is a quadratic residue modulo :math:`p`, namely
-        :math:`a \in Q_p`. A quadratic residue :math:`a` modulo :math:`p` satisfies
-        :math:`x^2 \equiv a\ (\textrm{mod}\ p)` for some :math:`x`.
+        The Legendre symbol is useful for determining if $a$ is a quadratic residue modulo $p$, namely
+        $a \in Q_p$. A quadratic residue $a$ modulo $p$ satisfies
+        $x^2 \equiv a\ (\textrm{mod}\ p)$ for some $x$.
 
         .. math::
             \bigg(\frac{a}{p}\bigg) =
@@ -649,8 +649,8 @@ def legendre_symbol(a: int, p: int) -> int:
         - Algorithm 2.149 from https://cacr.uwaterloo.ca/hac/about/chap2.pdf
 
     Examples:
-        The quadratic residues modulo 7 are :math:`Q_7 = \{1, 2, 4\}`. The quadratic non-residues
-        modulo 7 are :math:`\overline{Q}_7 = \{3, 5, 6\}`.
+        The quadratic residues modulo 7 are $Q_7 = \{1, 2, 4\}$. The quadratic non-residues
+        modulo 7 are $\overline{Q}_7 = \{3, 5, 6\}$.
 
         .. ipython:: python
 
@@ -672,31 +672,31 @@ def legendre_symbol(a: int, p: int) -> int:
 @export
 def jacobi_symbol(a: int, n: int) -> int:
     r"""
-    Computes the Jacobi symbol :math:`(\frac{a}{n})`.
+    Computes the Jacobi symbol $(\frac{a}{n})$.
 
     Arguments:
         a: An integer.
-        n: An odd integer :math:`n \ge 3`.
+        n: An odd integer $n \ge 3$.
 
     Returns:
-        The Jacobi symbol :math:`(\frac{a}{n})` with value in :math:`\{0, 1, -1\}`.
+        The Jacobi symbol $(\frac{a}{n})$ with value in $\{0, 1, -1\}$.
 
     See Also:
         legendre_symbol, kronecker_symbol
 
     Notes:
-        The Jacobi symbol extends the Legendre symbol for odd :math:`n \ge 3`. Unlike the Legendre symbol,
-        :math:`(\frac{a}{n}) = 1` does not imply :math:`a` is a quadratic residue modulo :math:`n`. However, all
-        :math:`a \in Q_n` have :math:`(\frac{a}{n}) = 1`.
+        The Jacobi symbol extends the Legendre symbol for odd $n \ge 3$. Unlike the Legendre symbol,
+        $(\frac{a}{n}) = 1$ does not imply $a$ is a quadratic residue modulo $n$. However, all
+        $a \in Q_n$ have $(\frac{a}{n}) = 1$.
 
     References:
         - Algorithm 2.149 from https://cacr.uwaterloo.ca/hac/about/chap2.pdf
 
     Examples:
-        The quadratic residues modulo 9 are :math:`Q_9 = \{1, 4, 7\}` and these all satisfy :math:`(\frac{a}{9}) = 1`.
-        The quadratic non-residues modulo 9 are :math:`\overline{Q}_9 = \{2, 3, 5, 6, 8\}`, but notice
-        :math:`\{2, 5, 8\}` also satisfy :math:`(\frac{a}{9}) = 1`. The set of integers :math:`\{3, 6\}` not coprime
-        to 9 satisfies :math:`(\frac{a}{9}) = 0`.
+        The quadratic residues modulo 9 are $Q_9 = \{1, 4, 7\}$ and these all satisfy $(\frac{a}{9}) = 1$.
+        The quadratic non-residues modulo 9 are $\overline{Q}_9 = \{2, 3, 5, 6, 8\}$, but notice
+        $\{2, 5, 8\}$ also satisfy $(\frac{a}{9}) = 1$. The set of integers $\{3, 6\}$ not coprime
+        to 9 satisfies $(\frac{a}{9}) = 0$.
 
         .. ipython:: python
 
@@ -745,15 +745,15 @@ def jacobi_symbol(a: int, n: int) -> int:
 @export
 def kronecker_symbol(a: int, n: int) -> int:
     r"""
-    Computes the Kronecker symbol :math:`(\frac{a}{n})`. The Kronecker symbol extends the Jacobi symbol for
-    all :math:`n`.
+    Computes the Kronecker symbol $(\frac{a}{n})$. The Kronecker symbol extends the Jacobi symbol for
+    all $n$.
 
     Arguments:
         a: An integer.
         n: An integer.
 
     Returns:
-        The Kronecker symbol :math:`(\frac{a}{n})` with value in :math:`\{0, -1, 1\}`.
+        The Kronecker symbol $(\frac{a}{n})$ with value in $\{0, -1, 1\}$.
 
     See Also:
         legendre_symbol, jacobi_symbol
@@ -876,15 +876,15 @@ def factors(n: int) -> tuple[list[int], list[int]]:
 @export
 def perfect_power(n: int) -> tuple[int, int]:
     r"""
-    Returns the integer base :math:`c` and exponent :math:`e` of :math:`n = c^e`. If :math:`n` is *not* a
-    perfect power, then :math:`c = n` and :math:`e = 1`.
+    Returns the integer base $c$ and exponent $e$ of $n = c^e$. If $n$ is *not* a
+    perfect power, then $c = n$ and $e = 1$.
 
     Arguments:
         n: An integer.
 
     Returns:
-        - The *potentially* composite base :math:`c`.
-        - The exponent :math:`e`.
+        - The *potentially* composite base $c$.
+        - The exponent $e$.
 
     See Also:
         factors, is_perfect_power, is_prime_power
@@ -996,21 +996,21 @@ def _adjust_base_and_exponent(n: int, base: int, exponent: int) -> tuple[int, in
 @export
 def trial_division(n: int, B: int | None = None) -> tuple[list[int], list[int], int]:
     r"""
-    Finds all the prime factors :math:`p_i^{e_i}` of :math:`n` for :math:`p_i \le B`.
+    Finds all the prime factors $p_i^{e_i}$ of $n$ for $p_i \le B$.
 
-    The trial division factorization will find all prime factors :math:`p_i \le B` such that :math:`n` factors
-    as :math:`n = p_1^{e_1} \dots p_k^{e_k} n_r` where :math:`n_r` is a residual factor (which may be composite).
+    The trial division factorization will find all prime factors $p_i \le B$ such that $n$ factors
+    as $n = p_1^{e_1} \dots p_k^{e_k} n_r$ where $n_r$ is a residual factor (which may be composite).
 
     Arguments:
         n: A positive integer.
-        B: The max divisor in the trial division. The default is `None` which corresponds to :math:`B = \sqrt{n}`.
-            If :math:`B > \sqrt{n}`, the algorithm will only search up to :math:`\sqrt{n}`, since a prime factor of
-            :math:`n` cannot be larger than :math:`\sqrt{n}`.
+        B: The max divisor in the trial division. The default is `None` which corresponds to $B = \sqrt{n}$.
+            If $B > \sqrt{n}$, the algorithm will only search up to $\sqrt{n}$, since a prime factor of
+            $n$ cannot be larger than $\sqrt{n}$.
 
     Returns:
-        - The discovered prime factors :math:`\{p_1, \dots, p_k\}`.
-        - The corresponding prime exponents :math:`\{e_1, \dots, e_k\}`.
-        - The residual factor :math:`n_r`.
+        - The discovered prime factors $\{p_1, \dots, p_k\}$.
+        - The corresponding prime exponents $\{e_1, \dots, e_k\}$.
+        - The residual factor $n_r$.
 
     See Also:
         factors
@@ -1056,17 +1056,17 @@ def trial_division(n: int, B: int | None = None) -> tuple[list[int], list[int],
 @export
 def pollard_p1(n: int, B: int, B2: int | None = None) -> int:
     r"""
-    Attempts to find a non-trivial factor of :math:`n` if it has a prime factor :math:`p` such that
-    :math:`p-1` is :math:`B`-smooth.
+    Attempts to find a non-trivial factor of $n$ if it has a prime factor $p$ such that
+    $p-1$ is $B$-smooth.
 
     Arguments:
-        n: An odd composite integer :math:`n > 2` that is not a prime power.
-        B: The smoothness bound :math:`B > 2`.
-        B2: The smoothness bound :math:`B_2` for the optional second step of the algorithm. The default is `None` which
+        n: An odd composite integer $n > 2$ that is not a prime power.
+        B: The smoothness bound $B > 2$.
+        B2: The smoothness bound $B_2$ for the optional second step of the algorithm. The default is `None` which
             will not perform the second step.
 
     Returns:
-        A non-trivial factor of :math:`n`.
+        A non-trivial factor of $n$.
 
     Raises:
         RuntimeError: If a non-trivial factor cannot be found.
@@ -1075,19 +1075,19 @@ def pollard_p1(n: int, B: int, B2: int | None = None) -> int:
         factors, pollard_rho
 
     Notes:
-        For a given odd composite :math:`n` with a prime factor :math:`p`, Pollard's :math:`p-1` algorithm can discover
-        a non-trivial factor of :math:`n` if :math:`p-1` is :math:`B`-smooth. Specifically, the prime factorization
-        must satisfy :math:`p-1 = p_1^{e_1} \dots p_k^{e_k}` with each :math:`p_i \le B`.
+        For a given odd composite $n$ with a prime factor $p$, Pollard's $p-1$ algorithm can discover
+        a non-trivial factor of $n$ if $p-1$ is $B$-smooth. Specifically, the prime factorization
+        must satisfy $p-1 = p_1^{e_1} \dots p_k^{e_k}$ with each $p_i \le B$.
 
-        A extension of Pollard's :math:`p-1` algorithm allows a prime factor :math:`p` to be :math:`B`-smooth with the
-        exception of one prime factor :math:`B < p_{k+1} \le B_2`. In this case, the prime factorization is
-        :math:`p-1 = p_1^{e_1} \dots p_k^{e_k} p_{k+1}`. Often :math:`B_2` is chosen such that :math:`B_2 \gg B`.
+        A extension of Pollard's $p-1$ algorithm allows a prime factor $p$ to be $B$-smooth with the
+        exception of one prime factor $B < p_{k+1} \le B_2$. In this case, the prime factorization is
+        $p-1 = p_1^{e_1} \dots p_k^{e_k} p_{k+1}$. Often $B_2$ is chosen such that $B_2 \gg B$.
 
     References:
         - Section 3.2.3 from https://cacr.uwaterloo.ca/hac/about/chap3.pdf
 
     Examples:
-        Here, :math:`n = pq` where :math:`p-1` is 1039-smooth and :math:`q-1` is 17-smooth.
+        Here, $n = pq$ where $p-1$ is 1039-smooth and $q-1$ is 17-smooth.
 
         .. ipython:: python
 
@@ -1095,27 +1095,27 @@ def pollard_p1(n: int, B: int, B2: int | None = None) -> int:
             galois.factors(p - 1)
             galois.factors(q - 1)
 
-        Searching with :math:`B=15` will not recover a prime factor.
+        Searching with $B=15$ will not recover a prime factor.
 
         .. ipython:: python
             :okexcept:
 
             galois.pollard_p1(p*q, 15)
 
-        Searching with :math:`B=17` will recover the prime factor :math:`q`.
+        Searching with $B=17$ will recover the prime factor $q$.
 
         .. ipython:: python
 
             galois.pollard_p1(p*q, 17)
 
-        Searching :math:`B=15` will not recover a prime factor in the first step, but will find :math:`q` in the second
-        step because :math:`p_{k+1} = 17` satisfies :math:`15 < 17 \le 100`.
+        Searching $B=15$ will not recover a prime factor in the first step, but will find $q$ in the second
+        step because $p_{k+1} = 17$ satisfies $15 < 17 \le 100$.
 
         .. ipython:: python
 
             galois.pollard_p1(p*q, 15, B2=100)
 
-        Pollard's :math:`p-1` algorithm may return a composite factor.
+        Pollard's $p-1$ algorithm may return a composite factor.
 
         .. ipython:: python
 
@@ -1184,15 +1184,15 @@ def pollard_p1(n: int, B: int, B2: int | None = None) -> int:
 # @functools.lru_cache(maxsize=1024)
 def pollard_rho(n: int, c: int = 1) -> int:
     r"""
-    Attempts to find a non-trivial factor of :math:`n` using cycle detection.
+    Attempts to find a non-trivial factor of $n$ using cycle detection.
 
     Arguments:
-        n: An odd composite integer :math:`n > 2` that is not a prime power.
-        c: The constant offset in the function :math:`f(x) = x^2 + c\ \textrm{mod}\ n`. The default is 1. A requirement
-            of the algorithm is that :math:`c \not\in \{0, -2\}`.
+        n: An odd composite integer $n > 2$ that is not a prime power.
+        c: The constant offset in the function $f(x) = x^2 + c\ \textrm{mod}\ n$. The default is 1. A requirement
+            of the algorithm is that $c \not\in \{0, -2\}$.
 
     Returns:
-        A non-trivial factor :math:`m` of :math:`n`.
+        A non-trivial factor $m$ of $n$.
 
     Raises:
         RuntimeError: If a non-trivial factor cannot be found.
@@ -1201,18 +1201,18 @@ def pollard_rho(n: int, c: int = 1) -> int:
         factors, pollard_p1
 
     Notes:
-        Pollard's :math:`\rho` algorithm seeks to find a non-trivial factor of :math:`n` by finding a cycle in a
-        sequence of integers :math:`x_0, x_1, \dots` defined by
-        :math:`x_i = f(x_{i-1}) = x_{i-1}^2 + 1\ \textrm{mod}\ p` where :math:`p` is an unknown small prime factor
-        of :math:`n`. This happens when :math:`x_{m} \equiv x_{2m}\ (\textrm{mod}\ p)`. Because :math:`p` is unknown,
-        this is accomplished by computing the sequence modulo :math:`n` and looking for
-        :math:`\textrm{gcd}(x_m - x_{2m}, n) > 1`.
+        Pollard's $\rho$ algorithm seeks to find a non-trivial factor of $n$ by finding a cycle in a
+        sequence of integers $x_0, x_1, \dots$ defined by
+        $x_i = f(x_{i-1}) = x_{i-1}^2 + 1\ \textrm{mod}\ p$ where $p$ is an unknown small prime factor
+        of $n$. This happens when $x_{m} \equiv x_{2m}\ (\textrm{mod}\ p)$. Because $p$ is unknown,
+        this is accomplished by computing the sequence modulo $n$ and looking for
+        $\textrm{gcd}(x_m - x_{2m}, n) > 1$.
 
     References:
         - Section 3.2.2 from https://cacr.uwaterloo.ca/hac/about/chap3.pdf
 
     Examples:
-        Pollard's :math:`\rho` is especially good at finding small factors.
+        Pollard's $\rho$ is especially good at finding small factors.
 
         .. ipython:: python
 
@@ -1273,20 +1273,20 @@ def f(x):
 @export
 def divisors(n: int) -> list[int]:
     r"""
-    Computes all positive integer divisors :math:`d` of the integer :math:`n` such that :math:`d\ |\ n`.
+    Computes all positive integer divisors $d$ of the integer $n$ such that $d\ |\ n$.
 
     Arguments:
         n: An integer.
 
     Returns:
-        Sorted list of positive integer divisors :math:`d` of :math:`n`.
+        Sorted list of positive integer divisors $d$ of $n$.
 
     See Also:
         factors, divisor_sigma
 
     Notes:
-        The :func:`~galois.divisors` function finds *all* positive integer divisors or factors of :math:`n`, where the
-        :func:`~galois.factors` function only finds the prime factors of :math:`n`.
+        The :func:`~galois.divisors` function finds *all* positive integer divisors or factors of $n$, where the
+        :func:`~galois.factors` function only finds the prime factors of $n$.
 
     Examples:
         .. ipython:: python
@@ -1335,24 +1335,23 @@ def divisors(n: int) -> list[int]:
 @export
 def divisor_sigma(n: int, k: int = 1) -> int:
     r"""
-    Returns the sum of :math:`k`-th powers of the positive divisors of :math:`n`.
+    Returns the sum of $k$-th powers of the positive divisors of $n$.
 
     Arguments:
         n: An integer.
-        k: The degree of the positive divisors. The default is 1 which corresponds to :math:`\sigma_1(n)` which is the
+        k: The degree of the positive divisors. The default is 1 which corresponds to $\sigma_1(n)$ which is the
             sum of positive divisors.
 
     Returns:
-        The sum of divisors function :math:`\sigma_k(n)`.
+        The sum of divisors function $\sigma_k(n)$.
 
     See Also:
         factors, divisors
 
     Notes:
-        This function implements the :math:`\sigma_k(n)` function. It is defined as:
+        This function implements the $\sigma_k(n)$ function. It is defined as:
 
-        .. math::
-            \sigma_k(n) = \sum_{d\ |\ n} d^k
+        $$\sigma_k(n) = \sum_{d\ \mid\ n} d^k$$
 
     Examples:
         .. ipython:: python
@@ -1383,25 +1382,25 @@ def divisor_sigma(n: int, k: int = 1) -> int:
 @export
 def is_prime(n: int) -> bool:
     r"""
-    Determines if :math:`n` is prime.
+    Determines if $n$ is prime.
 
     Arguments:
         n: An integer.
 
     Returns:
-        `True` if the integer :math:`n` is prime.
+        `True` if the integer $n$ is prime.
 
     See Also:
         is_composite, is_prime_power, is_perfect_power
 
     Notes:
-        This algorithm will first run Fermat's primality test to check :math:`n` for compositeness, see
-        :func:`~galois.fermat_primality_test`. If it determines :math:`n` is composite, the function will quickly
+        This algorithm will first run Fermat's primality test to check $n$ for compositeness, see
+        :func:`~galois.fermat_primality_test`. If it determines $n$ is composite, the function will quickly
         return.
 
-        If Fermat's primality test returns `True`, then :math:`n` could be prime or pseudoprime. If so, then the
+        If Fermat's primality test returns `True`, then $n$ could be prime or pseudoprime. If so, then the
         algorithm will run 10 rounds of Miller-Rabin's primality test, see :func:`~galois.miller_rabin_primality_test`.
-        With this many rounds, a result of `True` should have high probability of :math:`n` being a true prime,
+        With this many rounds, a result of `True` should have high probability of $n$ being a true prime,
         not a pseudoprime.
 
     Examples:
@@ -1410,7 +1409,7 @@ def is_prime(n: int) -> bool:
             galois.is_prime(13)
             galois.is_prime(15)
 
-        The algorithm is also efficient on very large :math:`n`.
+        The algorithm is also efficient on very large $n$.
 
         .. ipython:: python
 
@@ -1444,13 +1443,13 @@ def is_prime(n: int) -> bool:
 @export
 def is_composite(n: int) -> bool:
     r"""
-    Determines if :math:`n` is composite.
+    Determines if $n$ is composite.
 
     Arguments:
         n: An integer.
 
     Returns:
-        `True` if the integer :math:`n` is composite.
+        `True` if the integer $n$ is composite.
 
     See Also:
         is_prime, is_square_free, is_perfect_power
@@ -1475,19 +1474,19 @@ def is_composite(n: int) -> bool:
 @export
 def is_prime_power(n: int) -> bool:
     r"""
-    Determines if :math:`n` is a prime power :math:`n = p^k` for prime :math:`p` and :math:`k \ge 1`.
+    Determines if $n$ is a prime power $n = p^k$ for prime $p$ and $k \ge 1$.
 
     Arguments:
         n: An integer.
 
     Returns:
-        `True` if the integer :math:`n` is a prime power.
+        `True` if the integer $n$ is a prime power.
 
     See Also:
         is_perfect_power, is_prime
 
     Notes:
-        There is some controversy over whether 1 is a prime power :math:`p^0`. Since 1 is the 0-th power
+        There is some controversy over whether 1 is a prime power $p^0$. Since 1 is the 0-th power
         of all primes, it is often regarded not as a prime power. This function returns `False` for 1.
 
     Examples:
@@ -1517,13 +1516,13 @@ def is_prime_power(n: int) -> bool:
 @export
 def is_perfect_power(n: int) -> bool:
     r"""
-    Determines if :math:`n` is a perfect power :math:`n = c^e` with :math:`e > 1`.
+    Determines if $n$ is a perfect power $n = c^e$ with $e > 1$.
 
     Arguments:
         n: An integer.
 
     Returns:
-        `True` if the integer :math:`n` is a perfect power.
+        `True` if the integer $n$ is a perfect power.
 
     See Also:
         is_prime_power, is_square_free
@@ -1606,21 +1605,21 @@ def is_square_free(n: int) -> bool:
 @export
 def is_smooth(n: int, B: int) -> bool:
     r"""
-    Determines if the integer :math:`n` is :math:`B`-smooth.
+    Determines if the integer $n$ is $B$-smooth.
 
     Arguments:
         n: An integer.
-        B: The smoothness bound :math:`B \ge 2`.
+        B: The smoothness bound $B \ge 2$.
 
     Returns:
-        `True` if :math:`n` is :math:`B`-smooth.
+        `True` if $n$ is $B$-smooth.
 
     See Also:
         factors, is_powersmooth
 
     Notes:
-        An integer :math:`n` with prime factorization :math:`n = p_1^{e_1} \dots p_k^{e_k}` is :math:`B`-smooth
-        if :math:`p_k \le B`. The 2-smooth numbers are the powers of 2. The 5-smooth numbers
+        An integer $n$ with prime factorization $n = p_1^{e_1} \dots p_k^{e_k}$ is $B$-smooth
+        if $p_k \le B$. The 2-smooth numbers are the powers of 2. The 5-smooth numbers
         are known as *regular numbers*. The 7-smooth numbers are known as *humble numbers* or *highly composite
         numbers*.
 
@@ -1664,25 +1663,25 @@ def is_smooth(n: int, B: int) -> bool:
 @export
 def is_powersmooth(n: int, B: int) -> bool:
     r"""
-    Determines if the integer :math:`n` is :math:`B`-powersmooth.
+    Determines if the integer $n$ is $B$-powersmooth.
 
     Arguments:
         n: An integer.
-        B: The smoothness bound :math:`B \ge 2`.
+        B: The smoothness bound $B \ge 2$.
 
     Returns:
-        `True` if :math:`n` is :math:`B`-powersmooth.
+        `True` if $n$ is $B$-powersmooth.
 
     See Also:
         factors, is_smooth
 
     Notes:
-        An integer :math:`n` with prime factorization :math:`n = p_1^{e_1} \dots p_k^{e_k}` is :math:`B`-powersmooth
-        if :math:`p_i^{e_i} \le B` for :math:`1 \le i \le k`.
+        An integer $n$ with prime factorization $n = p_1^{e_1} \dots p_k^{e_k}$ is $B$-powersmooth
+        if $p_i^{e_i} \le B$ for $1 \le i \le k$.
 
     Examples:
-        Comparison of :math:`B`-smooth and :math:`B`-powersmooth. Necessarily, any :math:`n` that is
-        :math:`B`-powersmooth must be :math:`B`-smooth.
+        Comparison of $B$-smooth and $B$-powersmooth. Necessarily, any $n$ that is
+        $B$-powersmooth must be $B$-smooth.
 
         .. ipython:: python
 


Test patch:
diff --git a/tests/fields/conftest.py b/tests/fields/conftest.py
index 9107f5974..81fce0b7e 100644
--- a/tests/fields/conftest.py
+++ b/tests/fields/conftest.py
@@ -484,6 +484,8 @@ def randint(low, high, shape, dtype):
         iterator = np.nditer(array, flags=["multi_index", "refs_ok"])
         for _ in iterator:
             array[iterator.multi_index] = random.randint(low, high - 1)
+    if isinstance(shape, int) and shape == 1:
+        return array.item()
     return array
 
 
diff --git a/tests/polys/test_arithmetic.py b/tests/polys/test_arithmetic.py
index e529dfea1..6d65abee2 100644
--- a/tests/polys/test_arithmetic.py
+++ b/tests/polys/test_arithmetic.py
@@ -175,6 +175,8 @@ def test_modular_power_large_exponent_jit():
         g_coeffs = [193, 88, 107, 214, 72, 3]
         f = R([F.fetch_int(fi) for fi in f_coeffs[::-1]])
         g = R([F.fetch_int(gi) for gi in g_coeffs[::-1]])
+        print(pow(f, 2**64 + 0, g))
+        print(pow(f, 2**64 + 1234, g))
         print(pow(f, 2**70 + 0, g))
         print(pow(f, 2**70 + 1234, g))
         print(pow(f, 2**70 + 7654, g))
@@ -184,6 +186,9 @@ def test_modular_power_large_exponent_jit():
     f = galois.Poly([255, 228, 34, 121, 243, 189, 6, 131, 102, 168, 82], field=GF)
     g = galois.Poly([193, 88, 107, 214, 72, 3], field=GF)
 
+    assert pow(f, 2**64 + 0, g) == galois.Poly.Str("221*x^4 + 112*x^3 + 124*x^2 + 87*x + 4", field=GF)
+    assert pow(f, 2**64 + 1234, g) == galois.Poly.Str("80*x^4 + 101*x^3 + 17*x^2 + 243*x + 74", field=GF)
+
     assert pow(f, 2**70 + 0, g) == galois.Poly.Str("178*x^4 + 228*x^3 + 198*x^2 + 191*x + 211", field=GF)
     assert pow(f, 2**70 + 1234, g) == galois.Poly.Str("100*x^4 + 242*x^3 + 235*x^2 + 171*x + 43", field=GF)
     assert pow(f, 2**70 + 7654, g) == galois.Poly.Str("203*x^4 + 203*x^3 + 155*x^2 + 221*x + 151", field=GF)
@@ -199,6 +204,8 @@ def test_modular_power_large_exponent_python():
         g_coeffs = [193, 88, 107, 214, 72, 3]
         f = R([F.fetch_int(fi) for fi in f_coeffs[::-1]])
         g = R([F.fetch_int(gi) for gi in g_coeffs[::-1]])
+        print([fi.integer_representation() for fi in pow(f, 2**64 + 0, g).list()[::-1]])
+        print([fi.integer_representation() for fi in pow(f, 2**64 + 1234, g).list()[::-1]])
         print([fi.integer_representation() for fi in pow(f, 2**70 + 0, g).list()[::-1]])
         print([fi.integer_representation() for fi in pow(f, 2**70 + 1234, g).list()[::-1]])
         print([fi.integer_representation() for fi in pow(f, 2**70 + 7654, g).list()[::-1]])
@@ -208,6 +215,27 @@ def test_modular_power_large_exponent_python():
     f = galois.Poly([255, 228, 34, 121, 243, 189, 6, 131, 102, 168, 82], field=GF)
     g = galois.Poly([193, 88, 107, 214, 72, 3], field=GF)
 
+    assert pow(f, 2**64 + 0, g) == galois.Poly(
+        [
+            441863720507004970561004876820,
+            13672604657018030323534797793,
+            667292756625776662634439753318,
+            1088336758052065395414982737453,
+            594859692808903855438703377448,
+        ],
+        field=GF,
+    )
+    assert pow(f, 2**64 + 1234, g) == galois.Poly(
+        [
+            199499378257056846135676738371,
+            461241799178212820931813246616,
+            997379144596106156826875511246,
+            1102132140575827285204706059544,
+            949801160766724443620356316946,
+        ],
+        field=GF,
+    )
+
     assert pow(f, 2**70 + 0, g) == galois.Poly(
         [
             420013998870488935594333531316,