Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions .github/scripts/voronoi-julia.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Run with the ArithmeticVoronoi environment pinned in ../workflows/CI.yml:
# julia --startup-file=no --project=/path/to/arithmetic_voronoi \
# .github/scripts/voronoi-julia.jl
println("Loading the Julia provider and its dependencies...")
flush(stdout)
package_load_started = time_ns()
using ArithmeticVoronoi, Oscar, Test
import Oscar: GAP
println("Package loading: ", round((time_ns() - package_load_started) / 1e9; digits=2), " s")
flush(stdout)

# Test this HAP checkout, including its startup registration. Check before
# enabling the provider so the adapter's compatibility wrapper cannot mask
# missing entry points in HAP itself.
hap_root = realpath(joinpath(@__DIR__, "..", ".."))
@assert GAP.Packages.load(hap_root)
@assert realpath(GAP.Packages.locate_package("hap")) == hap_root
@assert GAP.Globals.IsBoundGlobal(GAP.GapObj("RegisterVoronoiBackend"))
@assert GAP.Globals.IsBoundGlobal(GAP.GapObj("VoronoiComplex"))
dispatcher = GAP.Globals.VoronoiComplex
println("HAP checkout: ", hap_root)
println("Julia ", VERSION, "; OSCAR ", pkgversion(Oscar), "; GAP ", GAP.GAP_VERSION)

@testset "Julia provider: GL2 over O_(Q(sqrt(-7)))" begin
enable_hap_julia_backend(; progress=true, resolution_length=3)
@test GAP.Globals.IsIdenticalObj(dispatcher, GAP.Globals.VoronoiComplex)

request = Dict(
"group" => "GL", "rank" => 2,
"field" => Dict("kind" => "quadratic", "discriminant" => -7),
"order" => Dict("kind" => "maximal"),
"lattice" => Dict("kind" => "free"),
)
println("Request: ", request)
flush(stdout)
construct = GAP.evalstr("request -> VoronoiComplex(request : backend := \"julia\")")
# A fresh native enumeration, without Magma, stored complexes or a C++ build.
K = construct(GAP.GapObj(request; recursive=true))
@test GAP.Globals.IsHapNonFreeResolution(K)
cells = Int.(GAP.gap_to_julia(
GAP.evalstr("K -> List([0..2], n -> K!.dimension(n))")(K)))
@test cells == [1, 2, 1]
println("Cell orbits in dimensions 0, 1, 2: ", cells)

R = GAP.Globals.FreeGResolution(K, 3)
C = GAP.Globals.TensorWithIntegers(R)
# These invariants were independently checked with Magma/VMH;
# neither the example nor CI needs Magma to compute them.
for (degree, expected) in ((1, [2, 2, 2]), (2, [2, 12]))
invariants = sort(Int.(GAP.gap_to_julia(GAP.Globals.Homology(C, degree))))
println("H", degree, " invariant factors: ", invariants)
@test invariants == expected
end
end
35 changes: 35 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,38 @@ jobs:
- uses: codecov/codecov-action@v6
with:
token: ${{ secrets.CODECOV_TOKEN }}

julia-voronoi:
name: Julia Voronoi example (GL2, D=-7)
runs-on: ubuntu-latest
permissions:
contents: read
env:
JULIA_NUM_THREADS: '1'
OPENBLAS_NUM_THREADS: '1'
steps:
- uses: actions/checkout@v7
- name: Check out the Julia provider and its pinned environment
uses: actions/checkout@v7
with:
repository: dimpase/arithmetic_voronoi
ref: 007df75debde04c7469124891ac78d555d5de20b
path: arithmetic-voronoi
- uses: julia-actions/setup-julia@v3
with:
version: '1.13.2'
- uses: julia-actions/cache@v3
with:
delete-old-caches: 'false'
- name: Install Julia dependencies
run: >-
julia --startup-file=no --project=arithmetic-voronoi
-e 'using Pkg; Pkg.instantiate(;allow_autoprecomp=false)'
- name: Precompile Julia provider
run: >-
julia --startup-file=no --project=arithmetic-voronoi
-e 'using Pkg; Pkg.precompile("ArithmeticVoronoi"; strict=true, timing=true)'
- name: Construct a fresh complex and check integral H1 and H2
run: >-
julia --startup-file=no --project=arithmetic-voronoi
.github/scripts/voronoi-julia.jl
49 changes: 47 additions & 2 deletions lib/Voronoi/seb.gi
Original file line number Diff line number Diff line change
@@ -1,10 +1,50 @@
HAP_KOMPLEKS123:=fail;
DIMS:=0;; #THIS IS NOT ALLOWED! CHANGE IT!!

# Optional providers implement function(request), returning a HAP non-free
# resolution or fail for unsupported inputs. HAP passes the request record
# unchanged; providers interpret and validate its field, order, lattice, group
# and other data. No extra dependency is loaded by HAP.
BindGlobal("HAP_VORONOI_BACKENDS", rec());
BindGlobal("RegisterVoronoiBackend", function(name, provider)
if not IsString(name) or Length(name)=0 or name="magma"
or not IsFunction(provider) then
Error("expected a nonempty backend name other than magma and a function");
fi;
HAP_VORONOI_BACKENDS.(name) := provider;
end);
BindGlobal("CallVoronoiBackend", function(name, request)
if not IsRecord(request) then
Error("a Voronoi request must be a record");
fi;
if not IsString(name) or not IsBound(HAP_VORONOI_BACKENDS.(name)) then
Error("unknown Voronoi backend; register it with RegisterVoronoiBackend");
fi;
return HAP_VORONOI_BACKENDS.(name)(request);
end);
BindGlobal("VoronoiComplex", function(request)
local backend;
backend := ValueOption("backend");
if backend=fail then
Error("specify a registered Voronoi backend with : backend := name");
fi;
return CallVoronoiBackend(backend,request);
end);
BindGlobal("HAPQuadraticVoronoiRequest", function(kind,n,d)
return rec(group:=kind,rank:=n,
field:=rec(kind:="quadratic",radicand:=d),
order:=rec(kind:="maximal"),lattice:=rec(kind:="free"));
end);

############################################################
############################################################
VoronoiComplexGL:=function(n,d)
local file,file2,m,k,G;
local file,file2,m,k,G,backend;

backend:=ValueOption("backend");
if backend<>fail and backend<>"magma" then
return CallVoronoiBackend(backend,HAPQuadraticVoronoiRequest("GL",n,d));
fi;

if d<0 then
file:="~/pkg/Voronoi/VMH-ImaginaryQuadraticNumberFields/Magma/BasicData.m";
Expand Down Expand Up @@ -43,7 +83,12 @@ end;
############################################################
############################################################
VoronoiComplexSL:=function(n,d)
local file,file2,m,k,G;
local file,file2,m,k,G,backend;

backend:=ValueOption("backend");
if backend<>fail and backend<>"magma" then
return CallVoronoiBackend(backend,HAPQuadraticVoronoiRequest("SL",n,d));
fi;

if d<0 then
file:="~/pkg/Voronoi/VMH-ImaginaryQuadraticNumberFields/Magma/BasicData.m";
Expand Down
4 changes: 4 additions & 0 deletions read.g
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ fi;

ReadPackageHap( "lib/TitlePage/makeHapMan.gi");

# Load the Voronoi entry points and optional provider interface. This defines
# functions only; Magma is invoked only when its backend is requested.
ReadPackageHap( "lib/Voronoi/seb.gi");

################# OBJECTIFICATIONS ###############################
ReadPackageHap( "lib/Objectifications/basicMethods.gi");
################# OBJECTIFICATIONS DONE ##########################
Expand Down
30 changes: 30 additions & 0 deletions tst/testextra/voronoi-backends.tst
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
gap> START_TEST("voronoi-backends.tst");
gap> RegisterVoronoiBackend("hap-test", request -> request);
gap> haprequest := rec(group:="unit",rank:=3,
> field:=rec(polynomial:=[-1,-1,0,1]),order:=rec(basis:=[1,2,3]),
> lattice:=rec(ideals:=["a","b","c"]),extra:=rec(example:=true));;
gap> Print(IsIdenticalObj(haprequest,VoronoiComplex(haprequest : backend := "hap-test")),"\n");
true
gap> haprequest := VoronoiComplexGL(2,-7 : backend := "hap-test");;
gap> Print([haprequest.group,haprequest.rank,haprequest.field.radicand],"\n");
[ "GL", 2, -7 ]
gap> Print([haprequest.field.kind,haprequest.order.kind,haprequest.lattice.kind],"\n");
[ "quadratic", "maximal", "free" ]
gap> haprequest := VoronoiComplexSL(2,-1 : backend := "hap-test");;
gap> Print([haprequest.group,haprequest.rank,haprequest.field.radicand],"\n");
[ "SL", 2, -1 ]
gap> haprequest := VoronoiComplexGL(3,5 : backend := "hap-test");;
gap> Print([haprequest.group,haprequest.rank,haprequest.field.radicand],"\n");
[ "GL", 3, 5 ]
gap> RegisterVoronoiBackend("hap-test-fail", request -> fail);
gap> Print(VoronoiComplex(rec() : backend := "hap-test-fail"),"\n");
fail
gap> Print(VoronoiComplexSL(2,5 : backend := "hap-test-fail"),"\n");
fail
gap> RegisterVoronoiBackend("hap-test", request -> request.rank);
gap> Print(VoronoiComplexGL(4,5 : backend := "hap-test"),"\n");
4
gap> Unbind(haprequest);
gap> Unbind(HAP_VORONOI_BACKENDS.("hap-test"));
gap> Unbind(HAP_VORONOI_BACKENDS.("hap-test-fail"));
gap> STOP_TEST("voronoi-backends.tst", 1000);
46 changes: 46 additions & 0 deletions tutorial/tutorialBianchi.xml
Original file line number Diff line number Diff line change
Expand Up @@ -407,5 +407,51 @@ The signs in the boundary maps of the free resolution are delicate. Another easy
<#Include SYSTEM "tutex/bianchi.17.txt">
</Example>

</Section>

<Section><Heading>Optional Voronoi backends</Heading>
An external package can register a provider using
<C>RegisterVoronoiBackend(name, provider)</C>, where <C>provider</C> is a
function of one argument. The name must be a nonempty string other than
<C>"magma"</C>. Registering an existing name replaces its provider.
<P/>
The general entry point is <C>VoronoiComplex(request : backend := name)</C>.
It requires a record and an explicitly selected registered provider. HAP
passes the record unchanged; the provider interprets and validates its
contents. The interface imposes no particular field degree, rank or group
family. For example, a provider may accept the following description:
<Log><![CDATA[
request := rec(
group := "GL", rank := 2,
field := rec(kind := "quadratic", discriminant := -7),
order := rec(kind := "maximal"),
lattice := rec(kind := "free"));
K := VoronoiComplex(request : backend := "julia");
]]></Log>
Field polynomials, order bases, lattice ideals or subgroup data can be
included when a provider supports them. This does not assert that any
particular provider implements those cases. A provider returns a HAP
non-free resolution or <C>fail</C> for an unsupported request; it may also
raise an error explaining the limitation. Failure never falls back to Magma.
<P/>
The existing functions <C>VoronoiComplexGL(n,d)</C> and
<C>VoronoiComplexSL(n,d)</C> also accept the option <C>backend</C>.
When selecting a registered provider, these construct a record with
<C>group</C> equal to <C>"GL"</C> or <C>"SL"</C>, <C>rank := n</C>,
<C>field := rec(kind := "quadratic", radicand := d)</C>,
<C>order := rec(kind := "maximal")</C> and
<C>lattice := rec(kind := "free")</C>. Here <C>d</C> is a squarefree
radicand, not a field discriminant. Without the backend option, or with
<C>backend := "magma"</C>, these two convenience functions retain the
Magma implementation described above.
<P/>
A Julia package running with GAP.jl can register an in-process callback
under the name <C>"julia"</C>. HAP itself does not install or load Julia.
An unregistered backend name is an error. Providers must supply complete stabilizers, the orientation action
and equivariant boundaries; perfect forms or a group presentation alone
do not suffice for higher homology. Standard HAP operations such as
<C>FreeGResolution</C> and <C>TensorWithIntegers</C> then apply to the
returned object. A contracting homotopy is a separate capability and must
not be inferred merely from the existence of a resolution.
</Section>
</Chapter>
Loading