I have implemented the integer residue ring \$ \mathbb{Z}/m\mathbb{Z} \$ and the integer multiplicative residue group \$ (\mathbb{Z}/m\mathbb{Z})^* \$. Functionalities include:
- In \$ \mathbb{Z}/m\mathbb{Z} \$ , you can do addition, subtraction, multiplication and raising elements to a nonnegative integral power.
- In \$ (\mathbb{Z}/m\mathbb{Z})^* \$, you can do multiplication, division, raising elements to any integral power and finding the multiplicative orders of elements.
I don't want users to mess around with classes. So the public interface has only two functions, residue_ring_modulo(m) and residue_group_modulo(m), which create and return \$ \mathbb{Z}/m\mathbb{Z} \$ and \$ (\mathbb{Z}/m\mathbb{Z})^* \$ respectively as subclasses of Enum. All other classes are pseudo-private. I choose Enum because all class elements were fixed upon class creation.
Here is the code:
from enum import Enum from math import gcd class _ResidueMonoid(Enum): """Abstract base class to represent an integer multiplicative residue monoid. Examples include Z/mZ (without addition) and (Z/mZ)*. """ @classmethod def _validate_type_and_return_val(cls, other): # Ensure the operands are of the same type before any binary operation if not isinstance(other, cls): raise TypeError("Operands' types not matched") return other.value def __mul__(self, other): other_val = self._validate_type_and_return_val(other) result_val = (self.value * other_val) % self.modulus return self.__class__(result_val) def __str__(self): return f'({self.value} % {self.modulus})' class _ResidueRing(_ResidueMonoid): """Abstract base class to represent an integer residue ring""" def __neg__(self): result_val = (-self.value) % self.modulus return self.__class__(result_val) def __add__(self, other): other_val = self._validate_type_and_return_val(other) result_val = (self.value + other_val) % self.modulus return self.__class__(result_val) def __sub__(self, other): other_val = self._validate_type_and_return_val(other) result_val = (self.value - other_val) % self.modulus return self.__class__(result_val) def __pow__(self, other): # A ring element can only be raised to a nonnegative integral power if not isinstance(other, int): raise TypeError("exponent must be integer") if other < 0: raise ValueError("exponent must be nonnegative") result_val = pow(self.value, other, self.modulus) return self.__class__(result_val) class _ResidueGroup(_ResidueMonoid): """Abstract base class to represent an integer multiplicative residue group""" @staticmethod def _solve_linear_congruence(a, b, m): # solve (ax = b mod m) by recursive Euclidean algorithm if a == 1: return b x = _ResidueGroup._solve_linear_congruence(m % a, (-b) % a, a) return (m * x + b) // a def __truediv__(self, other): other_val = self._validate_type_and_return_val(other) result_val = _ResidueGroup._solve_linear_congruence(other_val, self.value, self.modulus) return self.__class__(result_val) def __pow__(self, other): if not isinstance(other, int): raise TypeError("exponent must be integer") # if the exponent is negative, first find the modular inverse if other < 0: self = self.__class__(1) / self other = -other result_val = pow(self.value, other, self.modulus) return self.__class__(result_val) @property def ord(self): exponent = 1 val = self.value while val != 1: exponent += 1 val = (val * self.value) % self.modulus return exponent def residue_ring_modulo(m): """Create the integer residue ring Z/mZ as a concrete class""" ring_name = f'Z/{m}Z' members = [str(i) for i in range(m)] ring = Enum(ring_name, members, type=_ResidueRing, start=0) ring.modulus = m return ring def residue_group_modulo(m): """Create the integer multiplicative residue group (Z/mZ)* as a concrete class""" group_name = f'(Z/{m}Z)*' members = {str(i) : i for i in range(m) if gcd(i, m) == 1} group = Enum(group_name, members, type=_ResidueGroup) group.modulus = m return group Test output:
>>> Zmod9 = residue_ring_modulo(9) >>> Zmod9(7) + Zmod9(8) <Z/9Z.6: 6> >>> Zmod9(3) * Zmod9(6) <Z/9Z.0: 0> >>> Zmod9(4) ** 2 <Z/9Z.7: 7> >>> >>> Zmod9_star = residue_group_modulo(9) >>> for x in Zmod9_star: ... print(x) (1 % 9) (2 % 9) (4 % 9) (5 % 9) (7 % 9) (8 % 9) >>> >>> Zmod9_star(2) / Zmod9_star(8) <(Z/9Z)*.7: 7> >>> Zmod9_star(4) ** (-3) <(Z/9Z)*.1: 1> >>> Zmod9_star(5).ord 6 >>> I would like to get advice and feedback to improve my code. Thank you.