3
\$\begingroup\$

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:

  1. In \$ \mathbb{Z}/m\mathbb{Z} \$ , you can do addition, subtraction, multiplication and raising elements to a nonnegative integral power.
  2. 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.

asked May 7, 2020 at 14:32
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

An interesting use of Enums!

My only concern with using Enum would be performance -- as you note, all possible values are created when the class itself is created, so if you use a big number then you could also be using a lot of memory.

Otherwise, your __dunder__ (aka magic) methods look good, you don't need the reflected methods (e.g. __radd__) since only the exact same types are used in the operations, and I can see nothing wrong.

answered May 7, 2020 at 21:17
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.