One Hat Cyber Team
Your IP :
216.73.216.216
Server IP :
194.44.31.54
Server :
Linux zen.imath.kiev.ua 4.18.0-553.77.1.el8_10.x86_64 #1 SMP Fri Oct 3 14:30:23 UTC 2025 x86_64
Server Software :
Apache/2.4.37 (Rocky Linux) OpenSSL/1.1.1k
PHP Version :
5.6.40
Buat File
|
Buat Folder
Eksekusi
Dir :
~
/
usr
/
share
/
Macaulay2
/
Edit File:
Seminormalization.m2
newPackage( "Seminormalization", Version => "0.22", Date => "June 14, 2024", Authors => { {Name => "Karl Schwede", Email => "schwede@math.utah.edu", HomePage => "http://math.utah.edu/~schwede/" }, {Name => "Bernard Serbinowski", Email => "bserbinowski@gmail.com" } }, Headline => "seminormalization of rings", Keywords => {"Commutative Algebra"}, PackageImports => {"IntegralClosure"}, PackageExports => {"Pullback", "PushForward"}, Certification => { "journal name" => "The Journal of Software for Algebra and Geometry", "journal URI" => "https://msp.org/jsag/", "article title" => "Seminormalization package for Macaulay2", "acceptance date" => "5 December 2019", "published article URI" => "https://msp.org/jsag/2020/10-1/p01.xhtml", "published article DOI" => "10.2140/jsag.2020.10.1", "published code URI" => "https://msp.org/jsag/2020/10-1/jsag-v10-n1-x01-Seminormalization.m2", "release at publication" => "7a7e6f96b0122482f5d60306f34d06fd8ae1e885", -- git commit number in hex "version at publication" => "0.21", "volume number" => "10", "volume URI" => "https://msp.org/jsag/2020/10-1/" } ) export{ "seminormalize", "isSeminormal", --"renameVariablesOfTarget", "ringProduct", "betterNormalizationMap", "conductorOfRingMap", --potentially the following functions should not be exported "flattenVarDegrees", "ringToAlgebraMap", --"listOfVariableCreator", --"seminormalizationRecursive", --"intersectSeminormalizationAndExtension", "findElementMappingToTarget" --essentially an internal function, but possibly useful to others }; exportMutable{ "Yy" } --************************************************* isSeminormal = method() isSeminormal(Ring) := R1 -> ( R1toR1SN := (seminormalize R1)#1; pushList := pushFwd(R1toR1SN); myMap := (pushList#2)(sub(1, target R1toR1SN)); isSurjective myMap ); seminormalize = method( Options => { Variable => symbol Yy, Strategy => {} } ) seminormalize(Ring) := o->R1 -> ( seminormalizedList := internalSeminormalize(R1, Strategy=>o.Strategy); mapR1toR2 := seminormalizedList#1; renamedVarsList := renameVariablesOfTarget(o.Variable, mapR1toR2); --this returned the {original map fixed up, map from old ring to new ring, map from new ring to old ring} {target renamedVarsList#0, renamedVarsList#0, (seminormalizedList#2)*(renamedVarsList#2)} ); internalSeminormalize = method( Options => { Variable => symbol Yy, Strategy => AllCodimensions }) internalSeminormalize(Ring) := o -> R1 -> ( seminormalizationRecursive(R1, 1, Strategy=>o.Strategy) ); --internal functions --***************************************** --this takes the input ring (which is assumed to be reduced) --and an integer used to be seminormalizationRecursive = method(Options => {Variable => symbol Yy, Verbose => false, Strategy => {}}) seminormalizationRecursive(Ring, ZZ):=o->(inputRing, inputInteger)->( -* STRATEGY The objective is to create the following map diagram (R^N/rad(IR^N))^N <----(psi)-----Semi(R/rad(I)) ^ ^ | | | | (phi) | | | | | R^N <--------------------------- Semi(R) Here ^N means normalization of, Semi means seminormalization I stands for the conductor ideal and rad means radical of phi and psi are used to label the maps. From this diagram, we will pullback to recover Semi(R). While the construction of Phi is clearly doable, the construction of psi requires us to have the seminormalization. Therefore the process is recursive. FUNCTION INFORMATION Return value 1: The seminormalized ringnml;/ Return value 2: The map from the input ring to the seminormalized ring Return value 3: The map from the seminormalization to the normalization Input value 1: An input ring to be seminormalized Input value 2: An input integer. For internal use. Is provided by the driver method. Options: Verbose. Allows for disabling print statements. Strategy. Allows for controlling integralClosure strategy. Should be called through internalSeminormalization *- --first we grab the ring, flattened inputRing=(flattenRing(inputRing))#0; if dim(inputRing)<=0 then( if o.Verbose then print "seminormalizationRecursive: Returning because input ring has dimension 0."; return {inputRing, map(inputRing, inputRing), map(inputRing, inputRing)}; ); normalizationMapOfInputRing := betterNormalizationMap(inputRing, Strategy=>o.Strategy); normalizedInputRing:= target normalizationMapOfInputRing; --this will become the source of map Phi conductorIdeal:=conductorOfRingMap(normalizationMapOfInputRing);--this is I in the earlier diagram. if(conductorIdeal===ideal(1_inputRing)) then ( if o.Verbose then print "seminormalizationRecursive: Returning because input ring is normal."; return {normalizedInputRing, normalizationMapOfInputRing, map(normalizedInputRing, normalizedInputRing)}; ); if o.Verbose then print "The input ring (or recursive input ring) was not already seminormalized"; radicalOfConductor:=radical(conductorIdeal); --we need to be careful, radical does not always work, see docs. --this is rad(I) in the earlier diagram. if o.Verbose then print "checkpoint 1"; radicalOfConductorR:=radical((normalizationMapOfInputRing(conductorIdeal))*normalizedInputRing); --again, radical not the best inputRingModRadical:=(flattenRing(inputRing/radicalOfConductor))#0; --mod out by the radical and flatten --this would be R/rad(I). we will recursively pass this to be seminormalized. if o.Verbose then print "checkpoint 2"; normalizedInputRingModRadicalR:=(flattenRing(normalizedInputRing/radicalOfConductorR))#0; --this would correspond to (R^N)/rad(IR^N). Flattended to remove stuff and allow us to interact with it better mapPhi:=map(normalizedInputRingModRadicalR, normalizedInputRing); -- The map from the normalized Ring to the normalized ring mod out by the radical. --and that's map phi. if o.Verbose then << "checkpoint 3" << endl << "input to the recursion is " << inputRingModRadical << endl; recursionOutput := seminormalizationRecursive(inputRingModRadical, inputInteger+2, o);--recurse. inputInteger goes up by 2. inputInteger+1 is used below. Should allow the user to track where stuff is renamed (at least a little) --recursionOutput now has Semi(R/rad(I)) and a map from inputRingModRadical to it. So R/rad(I) -> Semi(R/rad(I)) mapPsiV1 := map( normalizedInputRingModRadicalR, inputRingModRadical, sub(matrix normalizationMapOfInputRing, normalizedInputRingModRadicalR) );--have to tell Macaulay2 where to send stuff. Fortunately, as we made the map and know both sets of variables, this can be done. --this is a map R/rad(I) -> R^N/rad(IR^N). It's temporary, and included because it's hard to keep things straight when composing maps. if o.Verbose then print "checkpoint 4"; mapPsi1 := (intersectSeminormalizationAndExtension(recursionOutput#1, mapPsiV1, inputInteger+1))#1; --both recursionOutput#1 and mapPsiV1 are maps from R/rad(I). Therefore what we are doing here is finding a ring in which both R^N/rad(IR^N) and Semi(R/rad(I)) live in. mapPsi:=mapPsi1*((ringRenamerAndMap(source(mapPsi1), inputInteger))#2); --finally compose the above with a renamed version of itself. This is just to rename variables so that we avoid collisions. --to clarify, this is a map from the overring to R^N/rad(IR^N) sorPsi:=source mapPsi; sorPsi1:=prune(sorPsi); mapPsi=mapPsi*(sorPsi.minimalPresentationMapInv); --sources and such because it's easier when they're extracted. normalizedInputRingModRadicalRRenamed:=((ringRenamerAndMap(target(mapPhi), inputInteger))#1); --renaming R^N/rad(IR^N) and providing a map to go with it, but actually only taking the map normalizedInputRingRenamed := ringRenamerAndMap(source(mapPhi), inputInteger); --renaming R^N and providing a map to go with it. mapPhi=normalizedInputRingModRadicalRRenamed*mapPhi*(normalizedInputRingRenamed#2); --chaining maps together. this is only so that we avoid naming collisions. normalizationMapOfInputRing = (normalizedInputRingRenamed#1)*normalizationMapOfInputRing; --chaining maps together. this is only so that we avoid naming collisions. mapPsi=normalizedInputRingModRadicalRRenamed*mapPsi*((ringRenamerAndMap(source(mapPsi), inputInteger))#2); --chaining maps together. this is only so that we avoid naming collisions. mapPsi=map(target mapPhi, flattenVarDegrees(source mapPsi), matrix mapPsi); --flatten the source of mapPsi and then remap it to the target. target is given by mapPhi, because we already sent that one to the right place and they go to the same place. if o.Verbose then << "checkpoint 5" << endl << "We are about to do a pullback. Normalization to conductor is" << mapPhi << endl << "gluing map is" << mapPsi; temp := pullback(mapPhi, mapPsi, Verbose=>false); if o.Verbose then << "Here is the pullback output:" << temp; --now we need to reconstruct the map from inputRing to RSN = temp#0 RSNtoRN := temp#1;--the map from Semi(R) to R^N RVarsInRN := apply(gens inputRing, xy -> normalizationMapOfInputRing(xy)); RVarsInRSN := apply(RVarsInRN, yz -> findElementMappingToTarget(RSNtoRN, yz)); SNmap := map(temp#0, inputRing, RVarsInRSN); { temp#0, -- the seminormalization SNmap, -- the seminormalization map, (normalizedInputRingRenamed#2)*(RSNtoRN) --the map from the seminormalization to the normalization } ) --***************************************** --RENAMING FUNCTIONS --***************************************** --given a list of variables, this creates a new list which can be used for variables which should help avoid var name collisions listOfVariableCreator=(inputList, inputInteger)->( for count from 0 to (length inputList) -1 list concatenate(replace("[^[:alnum:]]+", toString count, toString inputList#count), "RE", toString inputInteger) ) --this renames the variables of a ring (to avoid conflicts) ringRenamerAndMap = method() ringRenamerAndMap(Ring, ZZ):=(inputRing, inputInteger)->( currentList:=gens ambient inputRing; nextList:=listOfVariableCreator(currentList, inputInteger); outputRing:=newRing(inputRing, Variables=>nextList); additionalList:=gens ambient outputRing; {outputRing, map(outputRing, inputRing, additionalList), map(inputRing, outputRing, currentList)} ); ringRenamer=(inputRing, inputInteger)->( currentList:=gens ambient inputRing; nextList:=listOfVariableCreator(currentList, inputInteger); outputRing:=newRing(inputRing, Variables=>nextList); outputRing ) --this is an internal function which renames the variables of the seminormalized ring based on the --symbol the user choose (default Y) renameVariablesOfTarget = method() --returns the {original map, map from old ring to new ring, map from new ring to old ring} renameVariablesOfTarget(Symbol, RingMap) := (varName, mapR1toR2) -> ( R2 := target mapR1toR2; newVarList := apply(#(gens R2), tt -> varName_tt); newSeminormalization := newRing(R2, Variables=>newVarList); gMap := map(newSeminormalization, R2, gens newSeminormalization); hMap := map(R2, newSeminormalization, gens R2); (gMap*mapR1toR2, gMap, hMap) ); --one more way to do this, this renames the variables, then returns the maps both ways varRenamer = method() varRenamer(String, Ring) := (varName, R1) -> ( newVarList := apply(#(gens R1), tt -> (varName | toString(tt)) ); R1A := newRing(R1, Variables=>newVarList); mapTo := map(R1A, R1, gens R1A); mapFrom := map(R1, R1A, gens R1); {R1A, mapTo, mapFrom} ); --********************************* --Helper functions --********************************* --the following function computes the conductor of a map of rings conductorOfRingMap = method() conductorOfRingMap(RingMap) :=RingMapInput ->( mapFromSorToTar:=mapModules(RingMapInput); ann(coker(mapFromSorToTar)) ) mapModules = method() mapModules(RingMap) := RingMapInput ->( --Creates a map from the Source viewed as a Source-module to the target viewed as a Source-module. --note needs loadPackage "PushForward"; actualMod:= pushFwd(RingMapInput); tempVal:=target RingMapInput; (actualMod#2)(sub(1, tempVal)) ) findElementMappingToTarget = method() --given phi : R -> S, and an element g in S, this finds f in R such that phi(f) = g (if possible). --if it fails it throws an error? (or maybe we should avoid this check...) --If I recall correctly, I believe the technique used was suggested to us by Adam Boocher findElementMappingToTarget(RingMap, RingElement) := (phi, gg) -> ( --STRATEGY --We use an elimination order to accomplish this R1 := source(phi); S1 := target(phi); newVar := local newVar; R2 := coefficientRing(R1)[{newVar} | (gens ambient R1), MonomialOrder=>Eliminate 1]; phi2 := map(S1, R2, {gg} | apply(gens R1, xx -> phi(xx))); K2 := ker phi2; answer := sub(sub(newVar, R2)%K2, R1); if (phi(answer) == gg) then return answer else error "findElementMappingToTarget: No element mapping to target element was found."; ); --this was formerly findOverringV2 findOverring = method() -- given f : A -> B, g : A -> C, both integral (typically f : A->A^SN), this finds an integral -- extension D in which B and C live -- so that A is in the intersection of B and C. -- Furthermore, D should have the same number of minimal primes -- as C (and they should line up in terms of pullback by containment). --Finally, the D that is constructed has the nice property that its variable names are lined up with the variable names of the new B and C findOverring(RingMap, RingMap) := (f,g) -> ( --STRATEGY --We tensor the two rings together. Then line up minimal primes (if necessary) assert(source f === source g); --first do checking to see if these rings map to the same place A := source f; --define the name of the source rB := ringToAlgebraMap(f, 0); --turn B into an A algebra newB := rB#0; --grab the actual ring flatB := flattenVarDegrees((flattenRing(newB))#0); --remove extraneous variable degrees newfAlg := (rB#1)*f; --grab the map to the new f, newf := map(flatB, A, sub(matrix newfAlg, flatB)); --but we need to actually map to the one without funny degrees rC := ringToAlgebraMap(g, 1); --turn C into an A algebra newC := rC#0; --grab the ring flatC := flattenVarDegrees((flattenRing(newC))#0); --remove extraneous variable degrees newgAlg := (rC#1)*g; --get the map to the new ring newg := map(flatC, A, sub(matrix newgAlg, flatC)); --but we need to actually map to the one without funny degrees BigBC := flattenVarDegrees((flattenRing(newB ** newC))#0); --this is the first attempt at a big ring, --it is just the tensor over A, the problem is it might have too many minimal primes BtoBigBC := map(BigBC, flatB); --the map from B to BigBC CtoBigBC := map(BigBC, flatC); --the map from B to BigBC ambB := ambient flatB; --get the ambient ring of our real flatB minPrimeListBigBC := apply(minimalPrimes(ideal BigBC), q -> sub(q, BigBC)); --thus we grab the minimal primes of BigBC rawMinPrimeListC := minimalPrimes(ideal flatC); minPrimeListC := new MutableHashTable from apply(#(rawMinPrimeListC), i->{i, sub(rawMinPrimeListC#i, flatC) } ); --and the minimal primes of C newPrimeList := new MutableHashTable; invPrime := null; i := 0; flag := false; --the following loop goes through the minimal prime list of BigBC. It removes a minimal prime from the minimal --prime list of C each time it finds a match. When C runs out of primes, the loop should exit. --newPrimeList should store the list of the minimal primes while ((#minPrimeListC > 0) and (#minPrimeListBigBC > 0)) do ( invPrime = preimage(CtoBigBC, minPrimeListBigBC#0); --take the first prime of BigBC and pull it back to B i = 0; flag = true; while( flag and (i < #minPrimeListC) ) do( if (invPrime == minPrimeListC#((keys minPrimeListC)#i) ) then ( --check if that pulled back prime is in minPrimeList for B flag = false; newPrimeList#(#newPrimeList) = minPrimeListBigBC#0; --add it to the newPrimeList remove(minPrimeListC, ((keys minPrimeListC)#i)); ); i = i+1 ); if flag then error "findOverring: The tensor product has too many minimal primes. One of the maps is not integral."; minPrimeListBigBC = drop(minPrimeListBigBC, {0,0});--drop the prime we just compared on ); --do a check to make sure every prime got covered. --once we have a list of minimal primes lying over primes of B, we just intersect them. if (#minPrimeListC > 0) then error "findOverring: Not every prime in target g is covered by a minimal prime, are you sure that f is integral?"; newZero := intersect(values newPrimeList); --this is the new zero ideal of our big ring. {(flattenRing(BigBC/newZero))#0, --return the overring, newf, --the map A->newlabeledB, newg, --the map A->newlabeledC, map(flatB, target f, sub(matrix rB#1, flatB)), --the map oldB -> newlabeledB (an iso) map(flatC, target g, sub(matrix rC#1, flatC)) --the map oldC -> newlabeledC (an iso) } ); --this was formerly ringToAlgebraMapV2 --given a map of rings A->B this writes B = A[...]/?. Useful for tensoring rings together. ringToAlgebraMap = method() --the first input is the map, the second is the integer on which the map is based --it returns the target of the map and then the map from the old ring to the new one ringToAlgebraMap(RingMap) := List => (ff) -> ( ringToAlgebraMap(ff, 1) ) ringToAlgebraMap(RingMap, ZZ) := List => (ff, nn) -> ( --STRATEGY --0. Write A = QQ[x1, ..., xd]/J --1. let [YY1, ..., YYn] correspond to variables of B --2. form A[YY1, ..., YYn], --3. Map to B, sending YY's to the corresponding generators of B. -- A[YY1, ..., YYn] -> B, where the xi go to wherever they originally go, and the YYs go to the generators. --4. Let I be the kernel, then QQ[x1, ..., xd, YY1, ..., YY_n]/I is isomorphic to B. And we have a canonical map -- A to A[YY1, ..., YY_n]/I, variables go to variables. A:=source ff; B:=target ff; currentListB:=gens ambient B; --get the variables of B nextListB:=listOfVariableCreator(currentListB, nn); --rename the variables of B (using the nn given to avoid collisions) currentListA:=gens ambient A; --get the variables of A (we leave these alone) newBamb := (coefficientRing A)[currentListA | nextListB]; --this is QQ[x1, ..., xd, YY1, ..., YYn] varTargets := apply(currentListA, tt -> ff(sub(tt, A))) | apply(currentListB, tt -> sub(tt, B)); --these are where the --variables of newBamb get mapped to in the old ring B. mapToB := map(B, newBamb/sub(ideal A, newBamb), varTargets); --now we map our new ring to the old one I := ker mapToB; --compute the kernel (this is bad, we should be doing this manually) --TODO MAKE THE PREVIOUS LINE FASTER TO EXECUTE I1 := sub(I, newBamb) + sub(ideal A, newBamb); --create the formal ideal realBamb := A[nextListB]; --now adjoin the variables to A really newB := realBamb/sub(I, realBamb); --mod out by the formal ideal BtoNewB := map(newB, B, first entries sub(vars realBamb, newB)); --this is the final and real map {newB, BtoNewB} --return the A algebra A[...]/? and the map B->A[...]/? (a ring isomorphism) ); --given a multigraded ring, this flattens the multigrading into a single grading --it just sums the terms of the multigrading flattenVarDegrees = method() flattenVarDegrees(Ring) := (R1) -> ( KK := coefficientRing R1; varList := gens ambient R1; degListOld := degrees ambient R1; I1 := ideal R1; degListNew := apply(degListOld, zL -> {sum zL} ); S2 := KK[varList, Degrees=>degListNew]; (S2 / sub(I1, S2)) ); --this was formerly called shrunkOverring --given f : A -> A^{SN} and A -> B (both finite injective), this should find A^{SN} \cap B --it REQUIRES that B is seminormal (in practice, B is actually normal) intersectSeminormalizationAndExtension = method() --we pass it ff : A -> A^{SN}, gg : A -> B, and an integer used to avoid variable collisions intersectSeminormalizationAndExtension(RingMap, RingMap, ZZ) := (ff,gg, varInteger) -> ( --STRATEGY --We form ASN \oplus B \to D (where D is an appropriate overring whose primes line up with those of B) --We then compute the kernel of that map, --and finally view that map as a ring. fullOver := findOverring(ff,gg); --now we have A^{SN} -> D and B -> D (variables are setup so these maps should be canonical) D := fullOver#0; f := fullOver#1; --A to ASN (relabeled) g := fullOver#2; --A to B (relabeled) ASN := target f; B := target g; A := source f; assert(A === source g); ASNtoD := map(D, ASN); BtoD := map(D, B); AtoD := ASNtoD * f; --we write down all the maps in sight pushASNtoD := pushFwd(ASNtoD); --now we view D as ASN module pushBtoD := pushFwd(BtoD); --and D as a B module pushf := pushFwd(f); --likewise ASN pushg := pushFwd(g); --and B as A modules pushAtoD := pushFwd(AtoD); --finally D as an A module ASNoverA := pushf#0; --we grab those modules BoverA := pushg#0; DoverA := pushAtoD#0; moduleMapAtoASN := (pushf#2)(sub(1, ASN)); --we have a map A->ASN as A modules moduleMapAtoB := (pushg#2)(sub(1, B)); --and a map A->B as A modules listGensASNinD := apply(first entries (pushf#1), tt -> (pushAtoD#2)(ASNtoD(tt))); --we take the generators of --ASN as an A module, viewed as elements of the ring ASN, and look at their images in D, as a D module! moduleMapASNtoD := map(DoverA, ASNoverA, matrix fold( (a,b)->(a|b), listGensASNinD)); --that list we just created --is used to create a map of A-modules, ASN->D --now we do the same thing with B->D listGensBinD := apply(first entries (pushg#1), tt -> (pushAtoD#2)(BtoD(tt))); moduleMapBtoD := map(DoverA, BoverA, matrix fold( (a,b)->(a|b), listGensBinD )); --this creates an A-module map --B->D moduleMapASNsumBtoDoverA := moduleMapBtoD|((-1)*moduleMapASNtoD);--we direct sum these two modules and create their common map to D K := ker moduleMapASNsumBtoDoverA; --we found our goal, unfortunately its a module... --at this point we have found the intersection. However, we need to rewrite it as a ring. ASNsumBtoB := (source moduleMapASNsumBtoDoverA)^[0]; --projection onto the first coordinate KtoBoverA := ASNsumBtoB*(inducedMap(source moduleMapASNsumBtoDoverA, K));--now we concatenate with A->B targetSubmodule := trim image KtoBoverA; --we just want to make the ring generated by this submodule of B newVarsTargets := first entries ((pushg#1)*(g(gens targetSubmodule))); --so we grab these module generators newVariableNameList := apply(#newVarsTargets, i -> "YY" | toString(i) | "Y" | toString(varInteger)); AwithBonusVars := (flattenRing (A[newVariableNameList]))#0; --we create a new ring with some extra vars AvarsInB := first entries g(vars A); --we find where the regular variables of A go. h := map(B, AwithBonusVars, newVarsTargets | AvarsInB ); --finally, we make the ring map from A[..] -> B unprunedAnswer := (flattenRing(AwithBonusVars / (ker h)))#0; prunedAnswer := prune unprunedAnswer; --there will definitely be some extra unwanted variables, so remove them AtoUnprunedAnswer := map(unprunedAnswer, A); --next is the map from A to the intersection- answerPart1 := (unprunedAnswer.minimalPresentationMap)*AtoUnprunedAnswer; --this is A -> prunedAnswer unprunedAnswerToB := map(B, unprunedAnswer, first entries (h(vars AwithBonusVars))); answerPart2 := (inverse(fullOver#4))*unprunedAnswerToB*(unprunedAnswer.minimalPresentationMapInv); { answerPart1, --return the map from source ring to the intersection, and answerPart2 --the map from the intersection to B (= target of gg) } ); --************************************* ----NORMALIZATION REDUX---- --************************************* --the following function should normalize a general ring betterNormalizationMap=method(Options => { Variable => Yy, Strategy => {} }); betterNormalizationMap(Ring):=o->(R1) -> ( ---STRATEGY --we take the quotient by each minimal primes --normalize each one, produce the map from the original ring to the product of these normalizations --the tricky part is we need to construct a map R -> \prod_i (R/q_i)^N --we do this by keeping track of idempotents S1 := ambient R1; minPrimeList := minimalPrimes ideal R1; --grab the minimal primes of our ring --if there is only one ring, we just return that if (#minPrimeList == 1) then ( integralClosure(R1, Variable=>o.Variable, Strategy=>o.Strategy); return icMap(R1); ); quotientDomainList := apply(minPrimeList, qq -> (flattenRing(S1/qq))#0); --form the quotients by each mapR1ToQuotientList := apply(quotientDomainList, R1q -> map(R1q, R1) ); --record the map from our ring to each quotient apply(quotientDomainList, R1q -> integralClosure(R1q, Variable=>o.Variable, Strategy=>o.Strategy)); normalizationMapList := apply(quotientDomainList, R1q -> icMap(R1q)); --normalize each quotient (recording the map) normalizedRingList := apply(normalizationMapList, fi -> target fi); --list those normalized rings normalizedRing := ringProduct(normalizedRingList); --take the product of said normalizations normalizationMapFromR1List := apply(#quotientDomainList, i -> (normalizationMapList#i)*(mapR1ToQuotientList#i) ); --form the maps from R1 to each normalized quotient. --now comes the tricky part --first, the following creates a list of maps --(R/q_j)^N -> \prod_i (R/q_i)^N --which would be the right (non-ring) map except for the fact that we aren't multiplying by the appropriate idempotent -- (we'll multiply by said idempotent later). fakeMapList := apply(#normalizedRingList, jj -> map(normalizedRing#0, normalizedRingList#jj, (normalizedRing#2)#jj) );--these are not real ring maps --now we make some function closures --these take elements (perhaps of R or R/q_j) --sub them into (R/q_j) --map them to (R/q_j)^N --apply the maps from fakeMapList to them to get elements --multiply them by appropriate idempotents --in other words, this gives us a function which tells us where elements of R go in \prod_i (R/q_i)^N --(well, rather the sum of the images in the list should do that) R1qToNormalizedRingList := apply(#quotientDomainList, jj -> (tt -> ((normalizedRing#1)#jj)*( (fakeMapList#jj)((normalizationMapList#jj)(sub(tt, source (normalizationMapList#jj)) )) ) ) ); --not a real map either, this is the "map" from each R1q to the normalizedRing, but it should work for the variables -- --now we construct the image of each variable of R1 inside the quotients --the following is a list of lists, each entry is the image of the variables of R1 in R1/q_i varImagesInQuotients := apply(#quotientDomainList, jj -> apply(gens R1, vv -> (mapR1ToQuotientList#jj)(vv) ) ); varImagesInNormalizedRing := apply(#quotientDomainList, jj -> apply(varImagesInQuotients#jj, zz -> (R1qToNormalizedRingList#jj)(zz))); --finally, we sum the images of the variables of R in \prod_i (R/q_i)^N summedVarImagesInNormalizedRing := apply(entries transpose matrix varImagesInNormalizedRing, ll -> sum(ll)); --thus we construct the map R -> \prod_i (R/q_i)^N R1ToRealNormalization := map(normalizedRing#0, R1, summedVarImagesInNormalizedRing); --we prune and trim the big normalization prunedNormalization := prune( normalizedRing#0); trimmedNormalization := trim prunedNormalization; --finally, we build the map R1 -> (pruned trimmed normalization), nearlyFinalMap := map(trimmedNormalization, R1, sub(matrix(((normalizedRing#0).minimalPresentationMap)*R1ToRealNormalization), trimmedNormalization)); myVar := o.Variable; if (o.Variable === null) then (myVar = "Ww") else (myVar = toString(myVar)); renamedNorm := varRenamer(myVar, trimmedNormalization); (renamedNorm#1)*(nearlyFinalMap) ); --*********************************** --**The following function determines if a ring is normal, --**it works on non-domains as well -* betterIsNormal = method(Options => {Strategy => {}}) betterIsNormal(Ring) := o -> (R1) -> ( myNorm := betterNormalizationMap(R1, Strategy=> o.Strategy); if not isInjective myNorm then return false; if conductorOfRingMap(myNorm) != ideal(1_R1) then return false; true ) *- --********************************** --the following function takes a list of rings and returns their product. --It returns three items -- a) the product of the rings -- b) the idempotent elements defining each part of the ring (subbed into the product) -- c) a list of lists showing where each each variable of the original rings goes in the product ringProduct = method(Options => { Variable => null }) ringProduct(List):=o->(listRings) ->( --STRATEGY --given a list of rings R_i = A[x_{i,1}, ..., x_{i, n_i}] (with the same coefficient ring A) --we form A[x_{1,1}, ..., x_{t, n_t}, e_1, ..., e_t] --modulo some relations -- a) definitely the relations of the original rings R_i -- b) e_i^2 = e_i (idempotents) -- c) e_i*x_{i, j} = x_{i,j} -- d) e_i*e_{i'} = 0 -- e') (note this implies e_i*x_{i', j} = 0) if (#listRings < 1) then error "ringProduct: Expected more than zero rings."; if (#listRings == 1) then return {listRings#0, sub(1, listRings#0), gens (listRings#0)}; myCoeffRing := coefficientRing(listRings#0); if (any(listRings, RR1 -> not (coefficientRing RR1 === myCoeffRing) ) ) then error "ringProd: Expected all rings to have the same coefficient ring."; numRings:=#listRings; newListRings := apply(numRings, count-> ringRenamer(ringRenamer(listRings#count, count), count) ); --first we make a new list of rings, with variables renamed to avoid collisions listVars := apply(numRings, count->gens ambient (newListRings#count) ); --make a list of all the variables (well, a list of lists) idems := apply(numRings, count->"e"|count); --make a list of idempotents moreCompleteList := flatten flatten apply(numRings, count->{listVars#count, idems#count} ); unQuotiented := myCoeffRing[moreCompleteList]; --make the ring out of all these variables totalVars := gens unQuotiented; --get the variable names of our new ring --now that the ring has actually been created, we need to get the corresponding variables modVarList:=totalVars; listOfListsOfVars := new MutableList from 0..(numRings-1); listOfIdems := new MutableList from 0..(numRings-1); tempList:=null; --next we break up the variables into the appropriate sublists --and put them into the listofListsOfVars and listOfIdems we just made for count from 0 to numRings-1 do ( tempList=take(modVarList, #(listVars#count)); modVarList=take(modVarList, -(#modVarList-#tempList)); listOfIdems#count = modVarList#0; modVarList=take(modVarList, -(#modVarList-1)); listOfListsOfVars#count = tempList; ); --next we break up the variables into the appropriate sublists --and put them into the listofListsOfVars and listOfIdems we just made --next let's build the relations --first idempotents relations:={}; megaIdeal:=sub(ideal(), unQuotiented); for count from 0 to numRings-1 do ( for count1 from count to numRings-1 do ( --we run two loops if(count1==count) then( --if i == j... megaIdeal=megaIdeal+ideal( flatten{(listOfIdems#count)^2-(listOfIdems#count)}); --e^2 - e (do the idempotent relation) megaIdeal=megaIdeal+ideal( flatten{apply(listOfListsOfVars#count, zz -> (listOfIdems#count)*zz-zz)}); --e*x - x (and the idempotent does nothing to x) ) else ( --if i \neq j megaIdeal=megaIdeal+ideal( flatten{(listOfIdems#count)*(listOfIdems#count1)}); --ei*ej = 0 if i \neq j ); ); ); megaIdeal = megaIdeal + ideal( sum(toList listOfIdems) - 1); --add these idempotent relations to our big ideal --next we grab the old relations listOfIdeals:={}; for count from 0 to numRings-1 do ( tempMap:=map(unQuotiented, ambient (newListRings#count), listOfListsOfVars#count); --create a map to our new ring, from each old ring. megaIdeal=megaIdeal+(ideal(listOfIdems#count))*tempMap(ideal(newListRings#count)); --map the old relations to our new ring ); outputRing:=unQuotiented/(trim(megaIdeal)); --and we are DONE(ish) subbedIdempotents :=apply(listOfIdems, tt -> sub(tt, outputRing)); --we get the list of idempotents, in the final quotiented ring subbedRingVarTargets := apply(#listOfListsOfVars, jj -> apply(listOfListsOfVars#jj, tt->((sub(tt, outputRing))) )); --sub the variables into this ring as well renamedProd := null; hMap := null; if (o.Variable === null) then ( return {outputRing, subbedIdempotents, subbedRingVarTargets}; ) else if (class o.Variable === String) then ( renamedProd = varRenamer(o.Variable, outputRing); hMap = renamedProd#1; return { renamedProd#0, --the product of the rings apply(subbedIdempotents, ei -> hMap(ei)), --the idempotent elements defining each part of the ring (subbed into the product) apply(subbedRingVarTargets, li -> apply(li, ti -> hMap(ti))) --a list of lists showing where each each variable of the original rings goes in the product }; ) else if (class o.Variable === Symbol) then ( renamedProd = varRenamer(toString(o.Variable), outputRing); hMap = renamedProd#1; return { renamedProd#0,--the product of the rings apply(subbedIdempotents, ei -> hMap(ei)), --the idempotent elements defining each part of the ring (subbed into the product) apply(subbedRingVarTargets, li -> apply(li, ti -> hMap(ti))) --a list of lists showing where each each variable of the original rings goes in the product }; ); ) beginDocumentation() document { Key => Seminormalization, Headline => "a package used to seminormalize rings", EM "Seminormalization", " is a package which can be used to seminormalize rings or more generally check if a ring is seminormal. Roughly speaking a ring is non-normal if in the Spec, points are identified and tangent spaces killed. Seminormal rings are those where only points are identified. See the following for more discussion: ", HREF("https://mathoverflow.net/questions/109395/is-there-a-geometric-intuition-underlying-the-notion-of-normal-varieties/", "MathOverflow question"), BR{}, BR{}, BOLD "Core functions", UL { {TO "seminormalize", " computes the seminormalization of a ring"}, {TO "isSeminormal", " checks if a ring is seminormal"}, }, "To accomplish this, this we first normalize the ring via ", TO "integralClosure", " and then glue points together via the ", TO "Pullback", " package. There are some other functions exported which people may also find useful.", BR{}, BR{}, BOLD "Other useful functions", UL { {TO "ringProduct", " finds a ring equal to a product of rings"}, {TO "betterNormalizationMap", " computes the integral closure of reduced rings (that are not necessarily domains)"}, }, } doc /// Key seminormalize (seminormalize, Ring) Headline seminormalize a reduced ring Usage seminormalize(S) Inputs S:Ring Outputs :List the first entry of which is the seminormalization of the ring, the second and thirds are maps between the ring, its seminormalization and its normalization Description Text This seminormalizes a reduced ring and outputs a list, the first entry of which is the seminormalized ring, the second is the map from the ring to its seminormalization, and finally the map from the seminormalization to its normalization. In our first example, the cusp, the seminormalization and normalization are isomorphic. Example R = QQ[x,y]/ideal(x^3 - y^2); L = seminormalize(R) L#0 target(L#2) Text The previous example seminormalized a non-seminormal ring. Let's try a seminormal ring (the pinch point). Example R = QQ[x,y,z]/ideal(x^2*y-z^2); L = seminormalize(R) L#0 target(L#2) Text We conclude with an example of a ring where the seminormalization, the normalization and the ring itself are all are distinct, the tacnode. Example R = QQ[x,y]/ideal(y*(y-x^2)); L = seminormalize(R) L#0 target(L#2) /// doc /// Key [betterNormalizationMap, Strategy] [seminormalize, Strategy] Headline controls what strategy is used in calls to integralClosure Description Text These options are passed along whenever the functions call integralClosure. SeeAlso [integralClosure, Strategy] /// doc /// Key [seminormalize, Variable] [betterNormalizationMap, Variable] [ringProduct, Variable] Headline set the name for new variables created by the function Description Text This option sets the default variable for new variables created by the above functions. You must pass it a symbol. We first give an example of this in the context of seminormalization. Example A = QQ[a,b]/ideal(a^2-b^5); seminormalize(A, Variable=>X) Text Here is an example where we normalize a non-domain. Example B = QQ[u,v]/ideal(u*v); betterNormalizationMap(B, Variable=>Y) Text We conclude with an example of taking the product of two rings. Example C = QQ[x]; D = QQ[y]; ringProduct({C,D}, Variable=>z) /// doc /// Key findElementMappingToTarget (findElementMappingToTarget, RingMap, RingElement) Headline given a ring map and an element of the target, this find an element from the source whose image is the given one Usage x = findElementMappingToTarget(phi, y) Inputs phi: RingMap y: RingElement Outputs x: RingElement a ring element mapping to y by phi Description Text Given a ring map $\phi : R \to S$ and $y \in S$, this finds $x \in R$ such that $\phi(x) = y$ (if it exists). If no such element exists, it throws an error. Example R = QQ[u,v, w]; S = QQ[a,b]; phi = map(S, R, {a, a*b, 0}); findElementMappingToTarget(phi, a) findElementMappingToTarget(phi, sub(0, S)) /// doc /// Key conductorOfRingMap (conductorOfRingMap, RingMap) Headline given a ring map this finds the conductor of the map Usage J = conductorOfRingMap phi Inputs phi: RingMap Outputs J: Ideal the conductor Description Text Given a ring map $\phi : R \to S$, this finds the conductor, the annihilator of $S/R$ as an $R$ module. Example R = QQ[a,b]/ideal(a^3-b^2); S = QQ[c]; phi = map(S, R, {c^2,c^3}); conductorOfRingMap phi /// doc /// Key ringProduct (ringProduct, List) Headline compute the product of a list of rings Usage M = ringProduct(L) Inputs L: List Outputs M: List a list with a product of the rings input, and information about its structure Description Text Given a list of rings, of finite type over the same coefficient ring, this computes a ring isomorphic to a product of the rings. It returns a list with three entries. First is the ring. Second is the list of orthogonal idempotents. Finally, it lists where the variables of each of the rings in the list go in the new ring. Example R = QQ[a]; S = QQ[b]; T = QQ[c]; L = ringProduct({R,S}) ringProduct({R,S,T}) Text The third entry in the list correspond to the elements $(x,0)$ and $(0,y)$ in the product of rings. Example R = QQ[x]; S = QQ[y,z]; L = ringProduct({R,S}); newx = L#2#0#0; newy = L#2#1#0; newz = L#2#1#1; newx*newy==0 newx*newz==0 newy*newz==0 /// doc /// Key betterNormalizationMap (betterNormalizationMap, Ring) Headline normalizes non domains Usage phi = betterNormalizationMap(S) Inputs S: Ring Outputs phi: RingMap the map from the ring to its normalization Description Text Given a reduced ring $S$, this returns the normalization map $S \to S^N$. It works by computing the normalization after modding out by each minimal prime, and then taking the product of the rings. This should be compared to the function icMap built into Macaulay2. Example R = QQ[x,y]/ideal(x*y); icMap(R); h2 = betterNormalizationMap(R); RN = target h2 radical ideal singularLocus RN /// doc /// Key flattenVarDegrees (flattenVarDegrees, Ring) Headline turns a multigraded ring into a singly graded ring Usage S = flattenVarDegrees(R) Inputs R: Ring Outputs S: Ring Description Text This turns a multigraded ring into a singly graded ring, summing the individual degrees. Example R = QQ[x,y, Degrees=>{{1,2},{2,5}}]; S = flattenVarDegrees(R); vars S degrees S /// doc /// Key ringToAlgebraMap (ringToAlgebraMap, RingMap) (ringToAlgebraMap, RingMap, ZZ) Headline presents the target of a map of rings as an algebra over the source Usage L = ringToAlgebraMap(f) L = ringToAlgebraMap(f, n) Inputs f: RingMap n: ZZ Outputs L: List the first entry is the target of f as an algebra over the source Description Text Given a ringmap map $f: A \to B$, this writes $B$ as $A[...]/J$. It returns the ring $A[...]/J$ as well as the isomorphism $B \to A[...]/J$. Consider the first example, a normalization of a cusp. Example A = QQ[a,b]/ideal(a^2-b^3); B = QQ[t]; f = map(B, A, {t^3, t^2}); (ringToAlgebraMap(f))#0 (ringToAlgebraMap(f))#1 Text The second input is used to specify an integer used for new variable enumeration and labeling. Here is another example where we consider the Frobenius map. Example A = ZZ/5[x,y,z]/ideal(x^2-y*z); B = ZZ/5[X,Y,Z]/ideal(X^2-Y*Z); f = map(B, A, {X^5, Y^5, Z^5}); (ringToAlgebraMap(f))#0 (ringToAlgebraMap(f, 5))#0 /// doc /// Key isSeminormal (isSeminormal, Ring) Headline checks if a ring is seminormal Usage b = isSeminormal(R) Inputs R: Ring Outputs b: Boolean true if the ring is seminormal Description Text This returns true if the ring is seminormal, otherwise it returns false. Example R = QQ[x,y]/ideal(y^2 - x^3); isSeminormal(R) S = ZZ/5[a,b]/ideal(a^2-b^2+a^3) isSeminormal(R) /// doc /// Key Yy Headline default symbol for new variables in the seminormalization method Description Text This is the default symbol for new variables created by the functions in this packages, in particular the seminormalize method. SeeAlso [seminormalize, Variable] /// TEST /// --#0 check nodes are seminormal assert isSeminormal(ZZ/5[x,y]/ideal(y^2-x^3-x^2)); assert isSeminormal(QQ[x,y]/ideal(y^2-x^3-x^2)); assert isSeminormal(ZZ/11[x,y]/ideal((x+y)^3 - x*y)); assert isSeminormal(ZZ/101[x,y]/ideal(x*y)); /// TEST /// --#1 check cusps and tacnodes are not seminormal assert(not isSeminormal(ZZ/7[x,y]/ideal(y^2-x^3))); assert(not isSeminormal(QQ[x,y]/ideal(y^2-x^5))); assert(not isSeminormal(ZZ/5[x,y]/ideal(x*(x-y^2)))); assert(not isSeminormal(ZZ/101[x,y]/ideal((y-x^2)*(y+x^4)))); /// TEST /// --#2 check pinch points are seminormal, even in char 2) assert(isSeminormal(ZZ/2[x,y,z]/(y^2-x^2*z))) assert(isSeminormal(QQ[x,y,z]/(y^2-x^2*z))) /// TEST /// --#3 check SNC is seminormal and 3 lines in A^2 and 4 lines in A^3 is not assert(isSeminormal(ZZ/23[x,y,z]/(x*y*z))); assert(isSeminormal(QQ[x,y,z]/(x*y,x*z, y*z))); assert(isSeminormal(ZZ/101[x,y,u,v]/(x*u, x*v, y*u,y*v))); assert(not isSeminormal(QQ[x,y]/ideal(x*y*(x+y)))); assert(not isSeminormal(ZZ/7[x,y,z]/intersect(ideal(x,y), ideal(x,z), ideal(y,z), ideal(x+y, z)))); /// TEST /// --#4 check an example of Greco and Traverso, --in particular a SN scheme such that some irreducible component is not SN B = ZZ/11[x,y,u,v,e,f]; I = intersect(ideal(u,v,e-1,f),ideal(x,y,e,f-1)); A = B/I; E = ZZ/11[z1, z2, z3, z4, z5]; h = map(A, E, {x^3+u, x^2+v, y, u^2-v^3, x*y}); J = ker h; D = E/J; assert(isSeminormal(D)); --this should be seminormal, but JJ = preimage(h, ideal(sub(f,A))); D2 = E/(trim(JJ + J)); assert(not isSeminormal(D2)); /// TEST /// --#5 do a test that makes the recursion algorithm work a bit more A = QQ[x,y]; B = A/ideal(y*x); C = QQ[t2,t3]/ideal(t2^3-t3^2); f = map(B, A); g = map(B, C, {(x+y)^2, (x+y)^3}); R = trim prune ((pullback(f,g))#0); --this ring should not be seminormal... assert(not isSeminormal(R)); /// TEST /// --#6 do a test that makes the recursion of the algorithm work a bit more A = QQ[x,y]; B = A/ideal(y*x*(x-2*y)); C = QQ[t]; f = map(B, A); g = map(B, C, {x+y}); R = trim prune ((pullback(f,g))#0); --this ring should be seminormal... assert(isSeminormal(R)); /// TEST /// --#7 check that the non-semi-normal locus is the right set A = QQ[x,y]; B = A/ideal(y^2*x^2); C = QQ[t]; f = map(B, A); g = map(B, C, {x+y}); R = trim prune ((pullback(f,g))#0); --this ring should not be seminormal... snList = seminormalize(R); RtoRSN = snList#1; pushList = pushFwd(RtoRSN); myMap = (pushList#2)(sub(1, target RtoRSN)); cond = radical ann coker myMap; assert(dim radical cond == 1); assert(#(minimalPrimes cond) == 1); /// TEST /// --#8 check that the non-semi-normal locus is the right set again A = QQ[x,y,z]/ideal(x^2*y-z^2); B = A/( (ideal(x^2,y,z))); C = QQ[]; f = map(B, A); g = map(B, C, {}); R = trim prune ((pullback(f,g))#0); --this ring should not be seminormal snList = seminormalize(R); RtoRSN = snList#1; pushList = pushFwd(RtoRSN); myMap = (pushList#2)(sub(1, target RtoRSN)); cond = radical ann coker myMap; assert(dim radical cond == 0); assert(#(minimalPrimes cond) == 1); /// TEST /// --#9 checking the betterNormalization R = ZZ/11[x,y]/ideal(x*y*(x-1)*(y-2)); phi = betterNormalizationMap R; assert(4 == #(minimalPrimes ideal target phi)); S = QQ[a,b,c]/ideal(a*b,a*c,b*c); psi = betterNormalizationMap S; assert(3 == #(minimalPrimes ideal target psi)); /// TEST /// --#10 checking the example that had failed R = QQ[x,y]/ideal(x*y*(x-1)); snmap = (seminormalize R)#1; assert(isInjective snmap); pfwd = pushFwd(snmap); assert( isFreeModule (pfwd#0) and (1 == rank(pfwd#0))) /// TEST /// --#11 checking productOfRings in the case that had failed R = QQ[x]; S = QQ[x,y]/ideal(y-2); T = (ringProduct({R,S}))#0 assert(1 == dim T) assert(isNormal T) assert(2 == #minimalPrimes ideal T) /// end
Simpan