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/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: 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(); } 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: 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)); +} 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/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; 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) 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 ); 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_*),