74529048de
I did this to try out Macaulay2's "triangularize" function, but that turns out to use Maple for rings with more than three variables.
227 lines
6.5 KiB
Julia
227 lines
6.5 KiB
Julia
module Algebraic
|
|
|
|
export
|
|
codimension, dimension,
|
|
Construction, realize,
|
|
Element, Point, Sphere,
|
|
Relation, LiesOn, AlignsWithBy, mprod
|
|
|
|
import Subscripts
|
|
using LinearAlgebra
|
|
using AbstractAlgebra
|
|
using Groebner
|
|
using ...HittingSet
|
|
|
|
# --- commutative algebra ---
|
|
|
|
# as of version 0.36.6, AbstractAlgebra only supports ideals in multivariate
|
|
# polynomial rings when the coefficients are integers. we use Groebner to extend
|
|
# support to rationals and to finite fields of prime order
|
|
Generic.reduce_gens(I::Generic.Ideal{U}) where {T <: FieldElement, U <: MPolyRingElem{T}} =
|
|
Generic.Ideal{U}(base_ring(I), groebner(gens(I)))
|
|
|
|
function codimension(I::Generic.Ideal{U}, maxdepth = Inf) where {T <: RingElement, U <: MPolyRingElem{T}}
|
|
leading = [exponent_vector(f, 1) for f in gens(I)]
|
|
targets = [Set(findall(.!iszero.(exp_vec))) for exp_vec in leading]
|
|
length(HittingSet.solve(HittingSetProblem(targets), maxdepth))
|
|
end
|
|
|
|
dimension(I::Generic.Ideal{U}, maxdepth = Inf) where {T <: RingElement, U <: MPolyRingElem{T}} =
|
|
length(gens(base_ring(I))) - codimension(I, maxdepth)
|
|
|
|
m2_ordering(R::MPolyRing) = Dict(
|
|
:lex => :Lex,
|
|
:deglex => :GLex,
|
|
:degrevlex => :GRevLex
|
|
)[ordering(R)]
|
|
|
|
string_m2(ring::MPolyRing) =
|
|
"QQ[$(join(symbols(ring), ", ")), MonomialOrder => $(m2_ordering(ring))]"
|
|
|
|
string_m2(f::MPolyRingElem) =
|
|
replace(string(f), "//" => "/")
|
|
|
|
# --- primitve elements ---
|
|
|
|
abstract type Element{T} end
|
|
|
|
mutable struct Point{T} <: Element{T}
|
|
coords::Vector{MPolyRingElem{T}}
|
|
vec::Union{Vector{MPolyRingElem{T}}, Nothing}
|
|
rel::Nothing
|
|
|
|
## [to do] constructor argument never needed?
|
|
Point{T}(
|
|
coords::Vector{MPolyRingElem{T}} = MPolyRingElem{T}[],
|
|
vec::Union{Vector{MPolyRingElem{T}}, Nothing} = nothing
|
|
) where T = new(coords, vec, nothing)
|
|
end
|
|
|
|
function buildvec!(pt::Point)
|
|
coordring = parent(pt.coords[1])
|
|
pt.vec = [one(coordring), dot(pt.coords, pt.coords), pt.coords...]
|
|
end
|
|
|
|
mutable struct Sphere{T} <: Element{T}
|
|
coords::Vector{MPolyRingElem{T}}
|
|
vec::Union{Vector{MPolyRingElem{T}}, Nothing}
|
|
rel::Union{MPolyRingElem{T}, Nothing}
|
|
|
|
## [to do] constructor argument never needed?
|
|
Sphere{T}(
|
|
coords::Vector{MPolyRingElem{T}} = MPolyRingElem{T}[],
|
|
vec::Union{Vector{MPolyRingElem{T}}, Nothing} = nothing,
|
|
rel::Union{MPolyRingElem{T}, Nothing} = nothing
|
|
) where T = new(coords, vec, rel)
|
|
end
|
|
|
|
function buildvec!(sph::Sphere)
|
|
coordring = parent(sph.coords[1])
|
|
sph.vec = sph.coords
|
|
sph.rel = mprod(sph.coords, sph.coords) + one(coordring)
|
|
end
|
|
|
|
const coordnames = IdDict{Symbol, Vector{Union{Symbol, Nothing}}}(
|
|
nameof(Point) => [nothing, nothing, :xₚ, :yₚ, :zₚ],
|
|
nameof(Sphere) => [:rₛ, :sₛ, :xₛ, :yₛ, :zₛ]
|
|
)
|
|
|
|
coordname(elt::Element, index) = coordnames[nameof(typeof(elt))][index]
|
|
|
|
function pushcoordname!(coordnamelist, indexed_elt::Tuple{Any, Element}, coordindex)
|
|
eltindex, elt = indexed_elt
|
|
name = coordname(elt, coordindex)
|
|
if !isnothing(name)
|
|
subscript = Subscripts.sub(string(eltindex))
|
|
push!(coordnamelist, Symbol(name, subscript))
|
|
end
|
|
end
|
|
|
|
function takecoord!(coordlist, indexed_elt::Tuple{Any, Element}, coordindex)
|
|
elt = indexed_elt[2]
|
|
if !isnothing(coordname(elt, coordindex))
|
|
push!(elt.coords, popfirst!(coordlist))
|
|
end
|
|
end
|
|
|
|
# --- primitive relations ---
|
|
|
|
abstract type Relation{T} end
|
|
|
|
mprod(v, w) = (v[1]*w[2] + w[1]*v[2]) / 2 - dot(v[3:end], w[3:end])
|
|
|
|
# elements: point, sphere
|
|
struct LiesOn{T} <: Relation{T}
|
|
elements::Vector{Element{T}}
|
|
|
|
LiesOn{T}(pt::Point{T}, sph::Sphere{T}) where T = new{T}([pt, sph])
|
|
end
|
|
|
|
equation(rel::LiesOn) = mprod(rel.elements[1].vec, rel.elements[2].vec)
|
|
|
|
# elements: sphere, sphere
|
|
struct AlignsWithBy{T} <: Relation{T}
|
|
elements::Vector{Element{T}}
|
|
cos_angle::T
|
|
|
|
AlignsWithBy{T}(sph1::Sphere{T}, sph2::Sphere{T}, cos_angle::T) where T = new{T}([sph1, sph2], cos_angle)
|
|
end
|
|
|
|
equation(rel::AlignsWithBy) = mprod(rel.elements[1].vec, rel.elements[2].vec) - rel.cos_angle
|
|
|
|
# --- constructions ---
|
|
|
|
mutable struct Construction{T}
|
|
points::Vector{Point{T}}
|
|
spheres::Vector{Sphere{T}}
|
|
relations::Vector{Relation{T}}
|
|
|
|
function Construction{T}(; elements = Vector{Element{T}}(), relations = Vector{Relation{T}}()) where T
|
|
allelements = union(elements, (rel.elements for rel in relations)...)
|
|
new{T}(
|
|
filter(elt -> isa(elt, Point), allelements),
|
|
filter(elt -> isa(elt, Sphere), allelements),
|
|
relations
|
|
)
|
|
end
|
|
end
|
|
|
|
function Base.push!(ctx::Construction{T}, elt::Point{T}) where T
|
|
push!(ctx.points, elt)
|
|
end
|
|
|
|
function Base.push!(ctx::Construction{T}, elt::Sphere{T}) where T
|
|
push!(ctx.spheres, elt)
|
|
end
|
|
|
|
function Base.push!(ctx::Construction{T}, rel::Relation{T}) where T
|
|
push!(ctx.relations, rel)
|
|
for elt in rel.elements
|
|
push!(ctx, elt)
|
|
end
|
|
end
|
|
|
|
# output options:
|
|
# nothing - find a Gröbner basis
|
|
# :m2 - write a system of polynomials to a Macaulay2 file
|
|
function realize(ctx::Construction{T}; output = nothing) where T
|
|
# collect coordinate names
|
|
coordnamelist = Symbol[]
|
|
eltenum = enumerate(Iterators.flatten((ctx.spheres, ctx.points)))
|
|
for coordindex in 1:5
|
|
for indexed_elt in eltenum
|
|
pushcoordname!(coordnamelist, indexed_elt, coordindex)
|
|
end
|
|
end
|
|
|
|
# construct coordinate ring
|
|
coordring, coordqueue = polynomial_ring(parent_type(T)(), coordnamelist, ordering = :degrevlex)
|
|
|
|
# retrieve coordinates
|
|
for (_, elt) in eltenum
|
|
empty!(elt.coords)
|
|
end
|
|
for coordindex in 1:5
|
|
for indexed_elt in eltenum
|
|
takecoord!(coordqueue, indexed_elt, coordindex)
|
|
end
|
|
end
|
|
|
|
# construct coordinate vectors
|
|
for (_, elt) in eltenum
|
|
buildvec!(elt)
|
|
end
|
|
|
|
# turn relations into equations
|
|
eqns = vcat(
|
|
equation.(ctx.relations),
|
|
[elt.rel for (_, elt) in eltenum if !isnothing(elt.rel)]
|
|
)
|
|
|
|
# add relations to center, orient, and scale the construction
|
|
# [to do] the scaling constraint, as written, can be impossible to satisfy
|
|
# when all of the spheres have to go through the origin
|
|
if !isempty(ctx.points)
|
|
append!(eqns, [sum(pt.coords[k] for pt in ctx.points) for k in 1:3])
|
|
end
|
|
if !isempty(ctx.spheres)
|
|
append!(eqns, [sum(sph.coords[k] for sph in ctx.spheres) for k in 3:4])
|
|
end
|
|
n_elts = length(ctx.points) + length(ctx.spheres)
|
|
if n_elts > 0
|
|
push!(eqns, sum(elt.vec[2] for elt in Iterators.flatten((ctx.points, ctx.spheres))) - n_elts)
|
|
end
|
|
|
|
if output == :m2
|
|
file = open("macaulay2/construction.m2", "w")
|
|
write(file, string(
|
|
"coordring = $(string_m2(coordring))\n",
|
|
"eqns = {\n $(join(string_m2.(eqns), ",\n "))\n}"
|
|
))
|
|
close(file)
|
|
else
|
|
return (Generic.Ideal(coordring, eqns), eqns)
|
|
end
|
|
end
|
|
|
|
end |