From 0175d04e880f4eedeb73471833fc9d05f6bbb2ce Mon Sep 17 00:00:00 2001 From: Doug Torrance Date: Mon, 22 Jun 2026 16:28:31 -0400 Subject: [PATCH 1/8] Add ARingGFM2::dimension() accessor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ARingGFFlint and ARingGFFlintBig already expose dimension() directly; ARingGFM2 delegates to its internal GF object. Needed for the canonical-map predicate that checks GF(p^m) → GF(p^n) divisibility. Co-Authored-By: Claude Sonnet 4.6 --- M2/Macaulay2/e/basic-rings/aring-m2-GF.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/M2/Macaulay2/e/basic-rings/aring-m2-GF.hpp b/M2/Macaulay2/e/basic-rings/aring-m2-GF.hpp index f5a1726e4ea..e3760a88240 100644 --- a/M2/Macaulay2/e/basic-rings/aring-m2-GF.hpp +++ b/M2/Macaulay2/e/basic-rings/aring-m2-GF.hpp @@ -94,6 +94,7 @@ class ARingGFM2 : public SimpleARing ARingGFM2(const PolynomialRing &R, const ring_elem a); GFElement characteristic() const { return mGF.characteristic(); } + GFElement dimension() const { return mGF.dimension(); } void text_out(buffer &o) const; const PolynomialRing &originalRing() const { return mGF.ring(); } From 10b58677779884f2f80df701b4bf33f65f87e8c8 Mon Sep 17 00:00:00 2001 From: Doug Torrance Date: Mon, 22 Jun 2026 16:28:46 -0400 Subject: [PATCH 2/8] Add RingStructure partial order and hasCanonicalMap predicate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Encodes the canonical ring-map DAG (ZZ→QQ→RR→CC, ZZ→Fp→GF) as an enum and BFS-based reflexive-transitive closure. hasCanonicalMap combines structural precedence with characteristic matching and GF(p^m)→GF(p^n) dimension divisibility into a single authoritative predicate. gfDimension() accesses the concrete ARing type via static_cast keyed on ringID(), avoiding a new virtual function on Ring (which would change the vtable layout for all Ring subclasses). Co-Authored-By: Claude Sonnet 4.6 --- M2/Macaulay2/e/CMakeLists.txt | 1 + M2/Macaulay2/e/Makefile.files.in | 1 + M2/Macaulay2/e/basic-rings/ring-structure.cpp | 116 ++++++++++++++++++ M2/Macaulay2/e/basic-rings/ring-structure.hpp | 46 +++++++ 4 files changed, 164 insertions(+) create mode 100644 M2/Macaulay2/e/basic-rings/ring-structure.cpp create mode 100644 M2/Macaulay2/e/basic-rings/ring-structure.hpp diff --git a/M2/Macaulay2/e/CMakeLists.txt b/M2/Macaulay2/e/CMakeLists.txt index b58d5c1f87f..ca45e374b63 100644 --- a/M2/Macaulay2/e/CMakeLists.txt +++ b/M2/Macaulay2/e/CMakeLists.txt @@ -315,6 +315,7 @@ set(SRCLIST basic-rings/aring-ZZp-flint basic-rings/aring-ZZp basic-rings/aring + basic-rings/ring-structure rings/frac rings/GF rings/localring diff --git a/M2/Macaulay2/e/Makefile.files.in b/M2/Macaulay2/e/Makefile.files.in index 67651618e38..c78b3cd6648 100644 --- a/M2/Macaulay2/e/Makefile.files.in +++ b/M2/Macaulay2/e/Makefile.files.in @@ -37,6 +37,7 @@ INTERFACE = \ basic-mutable-matrices/lapack \ basic-mutable-matrices/mat \ basic-rings/aring \ + basic-rings/ring-structure \ basic-rings/aring-CC \ basic-rings/aring-CCC \ basic-rings/aring-CCi \ diff --git a/M2/Macaulay2/e/basic-rings/ring-structure.cpp b/M2/Macaulay2/e/basic-rings/ring-structure.cpp new file mode 100644 index 00000000000..f6351a1974d --- /dev/null +++ b/M2/Macaulay2/e/basic-rings/ring-structure.cpp @@ -0,0 +1,116 @@ +#include "basic-rings/ring-structure.hpp" +#include "rings/ring.hpp" +#include "basic-rings/aring-glue.hpp" + +namespace M2 { + +namespace { + +/// Direct edges in the canonical-map DAG. +/// directEdge(a, b) iff there is an atomic canonical map a → b. +/// Edges: ZZ→QQ, ZZ→PrimeField, QQ→RealField, RealField→ComplexField, +/// RealField→RealInterval, ComplexField→ComplexInterval, +/// RealInterval→ComplexInterval, PrimeField→FiniteField. +bool directEdge(RingStructure a, RingStructure b) +{ + using S = RingStructure; + return (a == S::ZZ && (b == S::QQ || b == S::PrimeField)) || + (a == S::QQ && b == S::RealField) || + (a == S::RealField && (b == S::ComplexField || b == S::RealInterval)) || + (a == S::ComplexField && b == S::ComplexInterval) || + (a == S::RealInterval && b == S::ComplexInterval) || + (a == S::PrimeField && b == S::FiniteField); +} + +constexpr int numRingStructures = 8; // excludes Unknown +constexpr RingStructure allStructures[numRingStructures] = { + RingStructure::ZZ, RingStructure::QQ, + RingStructure::RealField, RingStructure::ComplexField, + RingStructure::RealInterval, RingStructure::ComplexInterval, + RingStructure::PrimeField, RingStructure::FiniteField +}; + +// Returns n for GF(p^n) — the dimension as a vector space over GF(p). +// Only meaningful when structureOf(R->ringID()) == FiniteField. +long gfDimension(const Ring* R) +{ + switch (R->ringID()) { + case ring_GFFlintZech: + return static_cast*>(R)->ring().dimension(); + case ring_GFFlintBig: + return static_cast*>(R)->ring().dimension(); + case ring_GFM2: + return static_cast( + static_cast*>(R)->ring().dimension()); + default: + return 0; + } +} + +} // namespace + +RingStructure structureOf(RingID id) +{ + switch (id) { + case ring_ZZ: case ring_ZZFlint: return RingStructure::ZZ; + case ring_QQ: case ring_QQFlint: return RingStructure::QQ; + case ring_RR: case ring_RRR: return RingStructure::RealField; + case ring_CC: case ring_CCC: return RingStructure::ComplexField; + case ring_RRi: return RingStructure::RealInterval; + case ring_CCi: return RingStructure::ComplexInterval; + case ring_ZZp: case ring_ZZpFfpack: + case ring_ZZpFlint: case ring_tower_ZZp: return RingStructure::PrimeField; + case ring_GFM2: case ring_GFFlintBig: + case ring_GFFlintZech: return RingStructure::FiniteField; + default: return RingStructure::Unknown; + } +} + +bool structurePrecedes(RingStructure a, RingStructure b) +{ + if (a == RingStructure::Unknown || b == RingStructure::Unknown) return false; + if (a == b) return true; + // BFS over the 8-node DAG. + bool reachable[numRingStructures] = {}; + for (int i = 0; i < numRingStructures; ++i) + if (directEdge(a, allStructures[i])) reachable[i] = true; + bool changed = true; + while (changed) { + changed = false; + for (int i = 0; i < numRingStructures; ++i) { + if (!reachable[i]) continue; + for (int j = 0; j < numRingStructures; ++j) { + if (!reachable[j] && directEdge(allStructures[i], allStructures[j])) { + reachable[j] = true; + changed = true; + } + } + } + } + for (int i = 0; i < numRingStructures; ++i) + if (allStructures[i] == b && reachable[i]) return true; + return false; +} + +bool hasCanonicalMap(const Ring* R, const Ring* S) +{ + auto a = structureOf(R->ringID()); + auto b = structureOf(S->ringID()); + if (!structurePrecedes(a, b)) return false; + // Finite-field families require matching characteristic. + if (a == RingStructure::PrimeField || a == RingStructure::FiniteField) + if (R->characteristic() != S->characteristic()) return false; + // GF(p^m) → GF(p^n) requires m | n. + if (a == RingStructure::FiniteField && b == RingStructure::FiniteField) { + long dimR = gfDimension(R), dimS = gfDimension(S); + if (dimR <= 0 || dimS % dimR != 0) return false; + } + return true; +} + +} // namespace M2 + +// Local Variables: +// compile-command: "make -C $M2BUILDDIR/Macaulay2/e " +// indent-tabs-mode: nil +// End: diff --git a/M2/Macaulay2/e/basic-rings/ring-structure.hpp b/M2/Macaulay2/e/basic-rings/ring-structure.hpp new file mode 100644 index 00000000000..33db8ca2cdd --- /dev/null +++ b/M2/Macaulay2/e/basic-rings/ring-structure.hpp @@ -0,0 +1,46 @@ +#ifndef M2_BASIC_RINGS_RING_STRUCTURE_HPP_ +#define M2_BASIC_RINGS_RING_STRUCTURE_HPP_ + +#include "basic-rings/aring.hpp" // for M2::RingID + +class Ring; + +namespace M2 { + +/// Mathematical structure of a ring, collapsing precision and backend. +/// RR and RRR both collapse to RealField; all ZZp backends collapse to +/// PrimeField; etc. Unknown covers ring_old (polynomial rings, fraction +/// fields, etc.) for which no structural order is defined. +enum class RingStructure : int { + ZZ = 0, + QQ = 1, + RealField = 2, + ComplexField = 3, + RealInterval = 4, + ComplexInterval = 5, + PrimeField = 6, + FiniteField = 7, + Unknown = 8 +}; + +/// Maps a concrete RingID to its mathematical structure. +RingStructure structureOf(RingID id); + +/// True iff there is a canonical ring map from structure a to structure b +/// (reflexive-transitive closure of the canonical-map DAG). +/// Returns false when either argument is Unknown. +bool structurePrecedes(RingStructure a, RingStructure b); + +/// True iff there exists a canonical ring map R → S. +/// Combines the structural partial order with characteristic matching (for +/// finite-field families) and dimension divisibility (for GF → GF). +bool hasCanonicalMap(const Ring* R, const Ring* S); + +} // namespace M2 + +#endif + +// Local Variables: +// compile-command: "make -C $M2BUILDDIR/Macaulay2/e " +// indent-tabs-mode: nil +// End: From 84f10a5e790e0012d89f40593bd2822b9e37db11 Mon Sep 17 00:00:00 2001 From: Doug Torrance Date: Mon, 22 Jun 2026 16:28:54 -0400 Subject: [PATCH 3/8] Guard ConcreteRing promote/lift with hasCanonicalMap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rejects structurally invalid conversions (e.g. QQ→ZZ, QQ→Fp) early via the new hasCanonicalMap predicate, before reaching the ring-pair switch. lift shares the same guard because it is the partial inverse of the same canonical map. Co-Authored-By: Claude Sonnet 4.6 --- M2/Macaulay2/e/basic-rings/aring-glue.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/M2/Macaulay2/e/basic-rings/aring-glue.hpp b/M2/Macaulay2/e/basic-rings/aring-glue.hpp index 363763f52d9..4add920f5f8 100644 --- a/M2/Macaulay2/e/basic-rings/aring-glue.hpp +++ b/M2/Macaulay2/e/basic-rings/aring-glue.hpp @@ -5,6 +5,7 @@ #include "basic-rings/aring.hpp" #include "basic-rings/aring-translate.hpp" +#include "basic-rings/ring-structure.hpp" #include "rings/ring.hpp" #include "mutable-matrices/mutablemat.hpp" @@ -620,6 +621,7 @@ bool ConcreteRing::promote(const Ring *R, resultS = copy(fR); return true; } + if (!M2::hasCanonicalMap(R, S)) return false; switch (R->ringID()) { case M2::ring_ZZp: @@ -771,6 +773,7 @@ bool ConcreteRing::lift(const Ring *R, // MES:TODO!! WRITE ME return false; } + if (!M2::hasCanonicalMap(R, S)) return false; switch (R->ringID()) { case M2::ring_ZZp: From 8abb6682ce77656f3cfb5d50e250fced144e2f65 Mon Sep 17 00:00:00 2001 From: Doug Torrance Date: Mon, 22 Jun 2026 16:29:01 -0400 Subject: [PATCH 4/8] Add unit tests for ring structure predicate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tests structureOf (RingID→RingStructure mapping), structurePrecedes reflexivity, valid promote paths through the char-0 tower and finite-field family, and invalid maps (QQ↛ZZ, QQ↛Fp, RR↛QQ, etc.). Co-Authored-By: Claude Sonnet 4.6 --- M2/Macaulay2/e/unit-tests/Makefile.files | 3 +- .../e/unit-tests/RingStructureTest.cpp | 122 ++++++++++++++++++ 2 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 M2/Macaulay2/e/unit-tests/RingStructureTest.cpp diff --git a/M2/Macaulay2/e/unit-tests/Makefile.files b/M2/Macaulay2/e/unit-tests/Makefile.files index fc35ba137f8..8c9bf51df8c 100644 --- a/M2/Macaulay2/e/unit-tests/Makefile.files +++ b/M2/Macaulay2/e/unit-tests/Makefile.files @@ -37,7 +37,8 @@ UNITTEST_CCFILES := \ OverflowTest \ BRP-test \ RingCCCTest \ - LatticePointsTest + LatticePointsTest \ + RingStructureTest # ARingGFTest \ diff --git a/M2/Macaulay2/e/unit-tests/RingStructureTest.cpp b/M2/Macaulay2/e/unit-tests/RingStructureTest.cpp new file mode 100644 index 00000000000..74036b2cbce --- /dev/null +++ b/M2/Macaulay2/e/unit-tests/RingStructureTest.cpp @@ -0,0 +1,122 @@ +#include +#include "basic-rings/ring-structure.hpp" + +using M2::RingStructure; +using M2::structurePrecedes; +using M2::structureOf; +using M2::RingID; + +// structureOf: verify RingID → RingStructure mapping + +TEST(StructureOf, CharZeroTower) +{ + EXPECT_EQ(structureOf(M2::ring_ZZ), RingStructure::ZZ); + EXPECT_EQ(structureOf(M2::ring_ZZFlint), RingStructure::ZZ); + EXPECT_EQ(structureOf(M2::ring_QQ), RingStructure::QQ); + EXPECT_EQ(structureOf(M2::ring_QQFlint), RingStructure::QQ); + EXPECT_EQ(structureOf(M2::ring_RR), RingStructure::RealField); + EXPECT_EQ(structureOf(M2::ring_RRR), RingStructure::RealField); + EXPECT_EQ(structureOf(M2::ring_CC), RingStructure::ComplexField); + EXPECT_EQ(structureOf(M2::ring_CCC), RingStructure::ComplexField); + EXPECT_EQ(structureOf(M2::ring_RRi), RingStructure::RealInterval); + EXPECT_EQ(structureOf(M2::ring_CCi), RingStructure::ComplexInterval); +} + +TEST(StructureOf, FiniteFields) +{ + EXPECT_EQ(structureOf(M2::ring_ZZp), RingStructure::PrimeField); + EXPECT_EQ(structureOf(M2::ring_ZZpFfpack), RingStructure::PrimeField); + EXPECT_EQ(structureOf(M2::ring_ZZpFlint), RingStructure::PrimeField); + EXPECT_EQ(structureOf(M2::ring_tower_ZZp), RingStructure::PrimeField); + EXPECT_EQ(structureOf(M2::ring_GFM2), RingStructure::FiniteField); + EXPECT_EQ(structureOf(M2::ring_GFFlintBig), RingStructure::FiniteField); + EXPECT_EQ(structureOf(M2::ring_GFFlintZech), RingStructure::FiniteField); +} + +TEST(StructureOf, Unknown) +{ + EXPECT_EQ(structureOf(M2::ring_old), RingStructure::Unknown); +} + +// structurePrecedes: reflexivity + +TEST(StructurePrecedes, Reflexive) +{ + EXPECT_TRUE(structurePrecedes(RingStructure::ZZ, RingStructure::ZZ)); + EXPECT_TRUE(structurePrecedes(RingStructure::QQ, RingStructure::QQ)); + EXPECT_TRUE(structurePrecedes(RingStructure::RealField, RingStructure::RealField)); + EXPECT_TRUE(structurePrecedes(RingStructure::ComplexField, RingStructure::ComplexField)); + EXPECT_TRUE(structurePrecedes(RingStructure::RealInterval, RingStructure::RealInterval)); + EXPECT_TRUE(structurePrecedes(RingStructure::ComplexInterval,RingStructure::ComplexInterval)); + EXPECT_TRUE(structurePrecedes(RingStructure::PrimeField, RingStructure::PrimeField)); + EXPECT_TRUE(structurePrecedes(RingStructure::FiniteField, RingStructure::FiniteField)); +} + +// structurePrecedes: valid canonical maps (promote direction) + +TEST(StructurePrecedes, CharZeroTowerPromote) +{ + using S = RingStructure; + // ZZ maps to everything + EXPECT_TRUE(structurePrecedes(S::ZZ, S::QQ)); + EXPECT_TRUE(structurePrecedes(S::ZZ, S::RealField)); + EXPECT_TRUE(structurePrecedes(S::ZZ, S::ComplexField)); + EXPECT_TRUE(structurePrecedes(S::ZZ, S::RealInterval)); + EXPECT_TRUE(structurePrecedes(S::ZZ, S::ComplexInterval)); + EXPECT_TRUE(structurePrecedes(S::ZZ, S::PrimeField)); + EXPECT_TRUE(structurePrecedes(S::ZZ, S::FiniteField)); + + // QQ chain + EXPECT_TRUE(structurePrecedes(S::QQ, S::RealField)); + EXPECT_TRUE(structurePrecedes(S::QQ, S::ComplexField)); + EXPECT_TRUE(structurePrecedes(S::QQ, S::RealInterval)); + EXPECT_TRUE(structurePrecedes(S::QQ, S::ComplexInterval)); + + // RealField chain + EXPECT_TRUE(structurePrecedes(S::RealField, S::ComplexField)); + EXPECT_TRUE(structurePrecedes(S::RealField, S::RealInterval)); + EXPECT_TRUE(structurePrecedes(S::RealField, S::ComplexInterval)); + + // ComplexField → ComplexInterval + EXPECT_TRUE(structurePrecedes(S::ComplexField, S::ComplexInterval)); + + // RealInterval → ComplexInterval + EXPECT_TRUE(structurePrecedes(S::RealInterval, S::ComplexInterval)); + + // PrimeField → FiniteField + EXPECT_TRUE(structurePrecedes(S::PrimeField, S::FiniteField)); +} + +// structurePrecedes: invalid maps — the "nonsense" cases + +TEST(StructurePrecedes, InvalidMaps) +{ + using S = RingStructure; + // QQ does not map to ZZ (no ring hom QQ → ZZ) + EXPECT_FALSE(structurePrecedes(S::QQ, S::ZZ)); + + // QQ does not map to finite-characteristic rings + EXPECT_FALSE(structurePrecedes(S::QQ, S::PrimeField)); + EXPECT_FALSE(structurePrecedes(S::QQ, S::FiniteField)); + + // RealField does not map down to QQ or ZZ + EXPECT_FALSE(structurePrecedes(S::RealField, S::QQ)); + EXPECT_FALSE(structurePrecedes(S::RealField, S::ZZ)); + + // ComplexField does not map to RealField (non-trivial hom impossible) + EXPECT_FALSE(structurePrecedes(S::ComplexField, S::RealField)); + + // Interval types do not map to non-interval real/complex + EXPECT_FALSE(structurePrecedes(S::RealInterval, S::RealField)); + EXPECT_FALSE(structurePrecedes(S::ComplexInterval, S::ComplexField)); + + // FiniteField does not map to char-0 rings + EXPECT_FALSE(structurePrecedes(S::FiniteField, S::ZZ)); + EXPECT_FALSE(structurePrecedes(S::FiniteField, S::QQ)); + EXPECT_FALSE(structurePrecedes(S::FiniteField, S::RealField)); + + // Unknown blocks everything + EXPECT_FALSE(structurePrecedes(S::Unknown, S::ZZ)); + EXPECT_FALSE(structurePrecedes(S::ZZ, S::Unknown)); + EXPECT_FALSE(structurePrecedes(S::Unknown, S::Unknown)); +} From e9f191c95da8500aa394eff633519a868148fe33 Mon Sep 17 00:00:00 2001 From: Doug Torrance Date: Mon, 22 Jun 2026 16:43:36 -0400 Subject: [PATCH 5/8] Reject ring maps with no canonical coefficient-ring homomorphism In map(Ring, Ring, Matrix), when the while loop reaches a basic coefficient ring A (numgens A == 0), check isPromotable(A, R) before proceeding. This catches cases like map(ZZ, QQ) at construction time rather than silently producing an invalid map. Co-Authored-By: Claude Sonnet 4.6 --- M2/Macaulay2/m2/ringmap.m2 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/M2/Macaulay2/m2/ringmap.m2 b/M2/Macaulay2/m2/ringmap.m2 index 1a5df5f3721..50b4ac93a59 100644 --- a/M2/Macaulay2/m2/ringmap.m2 +++ b/M2/Macaulay2/m2/ringmap.m2 @@ -142,7 +142,9 @@ map(Ring, Ring, Matrix) := RingMap => opts -> (R, S, m) -> ( m = m | matrix {{(map(R,ambient A,mm)) A.PrimitiveElement}} ) else m = m | mm; - ))); + )) + else if not (A === R or isPromotable(A, R)) + then error ("no canonical map from ", toString A, " to ", toString R)); n = n + numgens A; try A = coefficientRing A else break ); From 02cd3a03891c8874435c40e66bd7d6ca7c676acb Mon Sep 17 00:00:00 2001 From: Doug Torrance Date: Mon, 22 Jun 2026 18:12:47 -0400 Subject: [PATCH 6/8] Remove spurious promote(QQ, RingElement) fallback This method tried to promote rational numbers element-wise (numerator divided by denominator) to arbitrary rings, succeeding whenever the denominator happened to be a unit. This made promote(2/1, ZZ[x]) work even though there is no ring homomorphism QQ -> ZZ[x], and caused isPromotable(QQ, ZZ[x]) to return true. Per-ring initialization in commonEngineRingInitializations already registers promote(QQ, R) for every ring R that QQ genuinely embeds into (QQ[x], RR, CC, etc. via their baseRings). Removing the global fallback makes isPromotable honest. Co-Authored-By: Claude Sonnet 4.6 --- M2/Macaulay2/m2/enginering.m2 | 7 ------- .../packages/Macaulay2Doc/functions/promote-doc.m2 | 1 - 2 files changed, 8 deletions(-) diff --git a/M2/Macaulay2/m2/enginering.m2 b/M2/Macaulay2/m2/enginering.m2 index 3a1ef19a813..41e6925dc76 100644 --- a/M2/Macaulay2/m2/enginering.m2 +++ b/M2/Macaulay2/m2/enginering.m2 @@ -22,13 +22,6 @@ raw EngineRing := R -> R.RawRing raw Ring := R -> if R.?RawRing then R.RawRing else error "no raw engine ring associated with this ring" isField EngineRing := R -> rawIsField raw R hasEngineLinearAlgebra Ring := (R) -> instance(R, InexactField) or R#?"EngineLinearAlgebra" -- used to decide which algorithm to use ------------------------------------------------------------------------------ --- rational promotion to any engine ring -promote(QQ,RingElement) := (r,S) -> ( - a := promote(numerator r,S); - b := promote(denominator r,S); - if a % b == 0 then a // b - else error ("promotion of this rational number to the ring ", toString S, " not possible")) --- new lift and promote, version 3 basicLift = opts -> (r,Brawring,Bclass) -> ( diff --git a/M2/Macaulay2/packages/Macaulay2Doc/functions/promote-doc.m2 b/M2/Macaulay2/packages/Macaulay2Doc/functions/promote-doc.m2 index 66d4ba2ed09..7c6db1e97fb 100644 --- a/M2/Macaulay2/packages/Macaulay2Doc/functions/promote-doc.m2 +++ b/M2/Macaulay2/packages/Macaulay2Doc/functions/promote-doc.m2 @@ -60,7 +60,6 @@ undocumented {(promote,CC,CC_*), (promote,QQ,CC_*), (promote,QQ,CCi_*), (promote,QQ,QQ), - (promote, QQ, RingElement), (promote,QQ,RR_*), (promote,RR,CC_*), (promote,RR,CCi_*), From f784c275c65e5f2193bf587ff21da8ba14ba03f0 Mon Sep 17 00:00:00 2001 From: Doug Torrance Date: Tue, 23 Jun 2026 09:44:32 -0400 Subject: [PATCH 7/8] Fix isPromotable for InexactField rings promote(RR, R[x]) is installed using class(0_(RR_53)) = RR as the key, so isPromotable(RR_53, R[x]) must look up by the ring family, not the specific precision ring. Fixes map(R[x], R) for RR, CC, RRi, CCi and their variants. Co-Authored-By: Claude Sonnet 4.6 --- M2/Macaulay2/m2/reals.m2 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/M2/Macaulay2/m2/reals.m2 b/M2/Macaulay2/m2/reals.m2 index 62374250545..1d808559b9f 100644 --- a/M2/Macaulay2/m2/reals.m2 +++ b/M2/Macaulay2/m2/reals.m2 @@ -145,6 +145,11 @@ promote(RR,CCi') := (i,K) -> toCCi(toRRi(precision i,i,i),toRRi(precision i, 0,0 promote(RRi,CCi') := (i,K) -> toCCi(i, interval 0) promote(CC,CCi') := (i,K) -> toCCi(toRRi(precision i,realPart i,realPart i),toRRi(precision i, imaginaryPart i, imaginaryPart i)) promote(CCi,CCi') := (i,K) -> toCCi(realPart i, imaginaryPart i) -- this should be fixed + +-- e.g., isPromotable(RR_53, ...) = isPromotable(RR, ...) +-- promote is set up in commonEngineRingInitializations in enginering.m2 +isPromotable(InexactField, Ring) := (R, S) -> isPromotable(class 0_R, S) + lift(RingElement, InexactNumber) := lift(Number, InexactNumber) := opts -> (x, K) -> lift(x, default K, opts) From 998291ff4d843ed7c7ed8cc954f2df5cc595a105 Mon Sep 17 00:00:00 2001 From: Doug Torrance Date: Tue, 23 Jun 2026 13:30:53 -0400 Subject: [PATCH 8/8] Fix promote and isPromotable for cross-backend ZZp rings Install promote(S, RingElement) := basicPromote inside the ZZp constructor so that element-level dispatch finds it (elements dispatch on class(x) = the ring, while Ring-typed methods dispatch on class(ring) = QuotientRing). Add isPromotable(QuotientRing, Ring) outside the constructor: for finite prime fields it checks char equality; for other QuotientRings it falls back to lookup(promote, R, S). This fixes the dispatch mismatch that caused isPromotable(ZZp 101, ZZp(101, Strategy => "Aring")) to return false. Co-Authored-By: Claude Sonnet 4.6 --- M2/Macaulay2/m2/quotring.m2 | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/M2/Macaulay2/m2/quotring.m2 b/M2/Macaulay2/m2/quotring.m2 index d78480a4e2b..a0c34c6a615 100644 --- a/M2/Macaulay2/m2/quotring.m2 +++ b/M2/Macaulay2/m2/quotring.m2 @@ -104,10 +104,18 @@ ZZp Ideal := opts -> (I) -> ( fraction(S,S) := S / S := (x,y) -> if y === 0_S then error "division by zero" else x//y; S.frac = S; -- ZZ/n with n PRIME! sqrt S := x -> promote(tonelliShanks(lift(x, ZZ), n), S); + promote(S, RingElement) := basicPromote; + promote(Matrix, S, RingElement) := (m, R, T) -> basicPromoteMatrix(m, T, identity); + promote(Module, S, RingElement) := (M, R, T) -> basicPromoteModule(M, T, identity); + promote(MutableMatrix, S, RingElement) := (m, R, T) -> basicPromoteMutableMatrix(m, T); savedQuotients#(typ, n) = S; lift(S,QQ) := opts -> liftZZmodQQ; S)) +isPromotable(QuotientRing, Ring) := (R, S) -> ( + if isFinitePrimeField R then char R == char S + else lookup(promote, R, S) =!= null) + initializeEngineLinearAlgebra = method() initializeEngineLinearAlgebra Ring := (R) -> ( R#"EngineLinearAlgebra" = true;