Skip to content

Add ResidueClass(k, n), an element of ℤ/nℤ, and list ℤ/nℤ through it - #411

Closed
enumeratio wants to merge 1 commit into
cortex-js:mainfrom
enumeratio:residue-class
Closed

enumeratio wants to merge 1 commit into
cortex-js:mainfrom
enumeratio:residue-class

Conversation

@enumeratio

Copy link
Copy Markdown
Contributor

Fix #399

The built-in head you invited on #407, as ResidueClass(k, n). Mod stays the remainder. setResidueClasses is not here: the classes are a built-in value, so there is no host hook.

  • Canonical form. k is reduced into 0…n−1: ResidueClass(7, 5) is ResidueClass(2, 5), ResidueClass(-1, 7) is ResidueClass(6, 7). n must be an integer literal ≥ 1 (n = 1 is the zero ring, one class). A rational k with a unit denominator reads as u·v⁻¹ (ResidueClass(1/3, 7) is ResidueClass(5, 7)). A symbolic or non-positive n, a non-integer n, or a symbolic or float k stays inert, as QuotientRing does. k and n are bigint, so a modulus past 2^53 is exact.
  • Equality. ResidueClass(7, 5) == ResidueClass(2, 5) is True, and two classes of one modulus with different residues are False. Classes of different moduli, or a class and an integer, are not compared (no answer). Classes are not ordered: Less and the others stay unevaluated.
  • Arithmetic. +, −, ×, negation and integer powers are classes, under evaluate(), .N() (a class is exact, so .N() leaves it) and simplify(): ResidueClass(5, 7) + ResidueClass(4, 7) is ResidueClass(2, 7). The inverse, a division and a negative power need gcd(k, n) = 1: 1/ResidueClass(3, 7) is ResidueClass(5, 7), while 1/ResidueClass(2, 4) and ResidueClass(2, 4)^-1 stay unevaluated.
  • Integers and classes. An integer, or a rational with a unit denominator, is read in the class's ring: ResidueClass(5, 7) + 3 is ResidueClass(1, 7). Classes of two moduli meet in ℤ/gcd(m, n), the largest ring both reduce onto, so ResidueClass(2, 4) + ResidueClass(1, 6) is ResidueClass(1, 2). We took this over leaving mixed moduli unevaluated because it is the usual coercion; if you would rather it not answer, it is one line (lift in residue-class.ts).
  • Type. A class has type value, and so does a sum, product, quotient or power with a class operand. The element type of QuotientRing(Integers, n) is value (it was unknown), and stays unknown for another base.
  • ℤ/nℤ listing and membership. QuotientRing(Integers, n) is enumerable: it lists ResidueClass(0, n) … ResidueClass(n−1, n) lazily (a modulus of 10^9 is counted without building anything), so ListFrom, Union, SetMinus, … walk it. Element(ResidueClass(7, 5), ℤ/5ℤ) is True, and a class of another modulus is False. Element(7, ℤ/5ℤ) is still not decided, as on main: 7 is a representative, not a class. Count and finiteness are unchanged.
  • LaTeX. \overline{k}_{n} is ResidueClass(k, n) when k and n are integer literals and n ≥ 1; it serializes back the same way. Before, it parsed to Subscript(Conjugate(k), n). A bare \overline{7}, \overline{z}_1, \overline{7}_{n}, \overline{-3}_{5} and repeating decimals (0.\overline{3}) are unchanged.

Epsil spells it residueClass(k, n). The compiled lane has no lowering for a class, so it falls back (checked); nothing guesses.

Tests: a new item-399-residue-class.test.ts. The QuotientRing tests that pinned "classes are not listed" and set<unknown> now pin the new behavior, and finite-non-enumerable-collections.test.ts keeps its coverage on Linspace(a, 1, 3), the one remaining finite collection without computable elements (two held-set Count cases only ℤ/5ℤ reached are dropped). Your CHANGELOG entries for QuotientRing and for non-computable collections are edited so they no longer say the classes aren't listed.

A bare integer still isn't a class, so Element(7, ℤ/5ℤ) stays undecided as on main, and Intersection({1}, ℤ/5ℤ) is empty.

… equality, exact bigint arithmetic with inverses only for units, integer coercion, \overline{k}_{n} LaTeX; list and decide membership of QuotientRing(Integers, n) through it, with element type value; add tests. Fix cortex-js#399
@arnog

arnog commented Oct 4, 2026

Copy link
Copy Markdown
Member

Thanks, this is very close to what we had in mind. Because the remaining fixes are in core arithmetic (canonical folds, simplify, the .N() path), we'll take it from here and land it with those fixes, with you as co-author. Two decisions changed: arithmetic on classes of two different moduli stays unevaluated (so x + y − y = x always holds), and an integer is not an element of ℤ/nℤ (Element(7, ℤ/5ℤ) is False, consistent with Intersection and Union). We'll close this PR when the commit lands.

@arnog

arnog commented Oct 4, 2026 •

Copy link
Copy Markdown
Member

Landed in a5e3170, with you as co-author. Thanks! Changes from the PR: classes of two moduli stay unevaluated; an integer is not an element of ℤ/nℤ; no number rule (canonical folds, simplify, Expand, Factor, the arithmetic methods) applies to an expression that holds a class; Solve, D and Integrate stay unevaluated for one.

@arnog arnog closed this Oct 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

QuotientRing(Integers, n): a finite collection with a residue-class element type

2 participants