5 pipeline with unit tests #10
@@ -0,0 +1,17 @@
|
|||||||
|
# Custom CI image for the arm64 (Raspberry Pi) Gitea Actions runner,
|
||||||
|
# since the official sagemath/sagemath image is amd64-only.
|
||||||
|
#
|
||||||
|
# Build & push commands:
|
||||||
|
# docker build -t gitea.piribauer.ch/julian/sage-ci:latest -f .gitea/ci-image/Dockerfile .
|
||||||
|
# docker login gitea.piribauer.ch -u julian
|
||||||
|
# docker push gitea.piribauer.ch/julian/sage-ci:latest
|
||||||
|
|
||||||
|
FROM condaforge/miniforge3:latest
|
||||||
|
|
||||||
|
RUN mamba install -y -c conda-forge sage ruff pytest \
|
||||||
|
&& mamba clean -afy
|
||||||
|
|
||||||
|
RUN mamba install -y -c conda-forge nodejs \
|
||||||
|
&& mamba clean -afy
|
||||||
|
|
||||||
|
WORKDIR /workspace
|
||||||
Executable
+26
@@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# This script is run in the CI container to preparse all .sage files into .sage.py files
|
||||||
|
# and can also be run locally for local linting/testing.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if command -v python >/dev/null 2>&1 && python -c "import sage.repl" >/dev/null 2>&1; then
|
||||||
|
run_python() { python "$@"; }
|
||||||
|
else
|
||||||
|
run_python() { sage --python "$@"; }
|
||||||
|
fi
|
||||||
|
|
||||||
|
for f in $(find . -name "*.sage" -not -path "./playground/*"); do
|
||||||
|
run_python -c "
|
||||||
|
import sys
|
||||||
|
from sage.repl.preparse import preparse_file
|
||||||
|
|
||||||
|
path = sys.argv[1]
|
||||||
|
with open(path) as fh:
|
||||||
|
src = fh.read()
|
||||||
|
with open(path + '.py', 'w') as fh:
|
||||||
|
fh.write('from sage.all import * # noqa: F401,F403\n')
|
||||||
|
fh.write(preparse_file(src))
|
||||||
|
" "$f"
|
||||||
|
done
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["**"]
|
||||||
|
pull_request:
|
||||||
|
branches: ["**"]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# Custom image (see .gitea/ci-image/Dockerfile)
|
||||||
|
container:
|
||||||
|
image: gitea.piribauer.ch/julian/sage-ci:latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Preparse .sage files
|
||||||
|
run: .gitea/preparse.sh
|
||||||
|
|
||||||
|
- name: ruff check
|
||||||
|
run: python -m ruff check --no-respect-gitignore .
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: lint
|
||||||
|
container:
|
||||||
|
image: gitea.piribauer.ch/julian/sage-ci:latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: python -m pytest tests/
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
# Sage preparser output (regenerated from .sage sources, not hand-maintained)
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Python / tooling caches
|
||||||
|
__pycache__/
|
||||||
|
.mypy_cache/
|
||||||
|
.ruff_cache/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
.venv/
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
[tool.ruff]
|
||||||
|
line-length = 120
|
||||||
|
target-version = "py311"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
# E: pycodestyle errors, F: pyflakes (undefined/unused names), I: isort (import order), W: pycodestyle warnings
|
||||||
|
select = ["E", "F", "I", "W"]
|
||||||
|
ignore = [
|
||||||
|
"E501", # symbolic-math expressions routinely exceed a "normal" line length
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff.lint.per-file-ignores]
|
||||||
|
# *.sage.py is preparser output: star-import globals, semicolon preamble, and literal-substitution artifacts trip static analysis.
|
||||||
|
"*.sage.py" = ["F403", "F405", "F821", "E741", "E402", "E702", "I001", "W291", "W293", "W292"]
|
||||||
|
|
||||||
|
# test_smoke.py star-imports sage.all and loads a .sage file, so ruff can't see where its names come from.
|
||||||
|
"tests/test_topdata_and_disc.py" = ["F403", "F405"]
|
||||||
@@ -2,7 +2,6 @@ import numpy as np
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
from datetime import datetime, timezone
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
# Logger
|
# Logger
|
||||||
@@ -102,7 +101,8 @@ class ToricPolytope(Polytope):
|
|||||||
if no_triangulation < 0 or no_triangulation >= len(self.triangulations):
|
if no_triangulation < 0 or no_triangulation >= len(self.triangulations):
|
||||||
raise IndexError("Invalid triangulation index {} (available: 0..{}).".format(no_triangulation, len(self.triangulations) - 1))
|
raise IndexError("Invalid triangulation index {} (available: 0..{}).".format(no_triangulation, len(self.triangulations) - 1))
|
||||||
|
|
||||||
logger.info("Using triangulation index %d of [0..%d].", no_triangulation, len(self.triangulations) - 1)
|
if len(self.triangulations) > 1:
|
||||||
|
logger.info("Using triangulation index %d of [0..%d].", no_triangulation, len(self.triangulations) - 1)
|
||||||
self.triangulation = self.triangulations[no_triangulation]
|
self.triangulation = self.triangulations[no_triangulation]
|
||||||
|
|
||||||
def set_nef_partition(self, nef_partition):
|
def set_nef_partition(self, nef_partition):
|
||||||
@@ -233,7 +233,7 @@ class ToricPolytope(Polytope):
|
|||||||
def _setup_discriminant_symbols(self):
|
def _setup_discriminant_symbols(self):
|
||||||
mori_rays = self.Mori_cone.rays()
|
mori_rays = self.Mori_cone.rays()
|
||||||
|
|
||||||
a_vars = [var("a_{}".format(u), latex_name="a_{{}}".format(u)) for u in (1..len(mori_rays))]
|
a_vars = [var("a_{}".format(u), latex_name="a_{{{}}}".format(u)) for u in (1..len(mori_rays))]
|
||||||
z_vars = var('z', n=len(mori_rays)+1, latex_name='z') # z[0] is superfluous
|
z_vars = var('z', n=len(mori_rays)+1, latex_name='z') # z[0] is superfluous
|
||||||
lambda_vars = var('l', n=len(mori_rays)+1, latex_name='l') # l[0] is superfluous
|
lambda_vars = var('l', n=len(mori_rays)+1, latex_name='l') # l[0] is superfluous
|
||||||
a_row = matrix(a_vars)
|
a_row = matrix(a_vars)
|
||||||
@@ -275,7 +275,7 @@ class ToricPolytope(Polytope):
|
|||||||
if polynomials[0] == 0:
|
if polynomials[0] == 0:
|
||||||
polynomials = maxima.eliminate(equation_system, lambda_symbols[:-1]).sage()
|
polynomials = maxima.eliminate(equation_system, lambda_symbols[:-1]).sage()
|
||||||
reverse_solve = True
|
reverse_solve = True
|
||||||
except:
|
except Exception:
|
||||||
# In one-parameter cases there may be nothing to eliminate.
|
# In one-parameter cases there may be nothing to eliminate.
|
||||||
polynomials = equation_system
|
polynomials = equation_system
|
||||||
logger.debug("No elimination needed for the equation system: %s", equation_system)
|
logger.debug("No elimination needed for the equation system: %s", equation_system)
|
||||||
@@ -289,7 +289,7 @@ class ToricPolytope(Polytope):
|
|||||||
else:
|
else:
|
||||||
last_lambda = lambda_symbols[-1]
|
last_lambda = lambda_symbols[-1]
|
||||||
polynomials = [poly / last_lambda ** (poly.degree(last_lambda)) for poly in polynomials]
|
polynomials = [poly / last_lambda ** (poly.degree(last_lambda)) for poly in polynomials]
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return polynomials
|
return polynomials
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
# Entries are (id, factory, expected_cy_dimension, expected_no_divs, expected_disc,
|
||||||
|
# expected_intersection_numbers_CY), where expected_disc is a list of
|
||||||
|
# (str(discriminant_factor), codimension) pairs as returned by ToricPolytope.disc().
|
||||||
|
|
||||||
|
TEST_MODELS = [
|
||||||
|
(
|
||||||
|
"K3_two_parameter_family",
|
||||||
|
lambda: ToricPolytopeProjectiveSpace([1, 1, 2, 4], model_name="K3_two_parameter_family"),
|
||||||
|
2, # cy_dimension
|
||||||
|
2, # no_divs
|
||||||
|
[("z2 - 1/4", 2), ("4096*z1^2*(4*z2 - 1) + 128*z1 - 1", 0)],
|
||||||
|
{(0, 0): 4, (0, 1): 2, (1, 1): 0},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"CY3_two_parameter_family",
|
||||||
|
lambda: ToricPolytopeProjectiveSpace([1, 1, 2, 2, 2], model_name="CY3_two_parameter_family"),
|
||||||
|
3,
|
||||||
|
2,
|
||||||
|
[("z2 - 1/4", 3), ("65536*z1^2*(4*z2 - 1) + 512*z1 - 1", 0)],
|
||||||
|
{(0, 0, 0): 8, (0, 0, 1): 4, (0, 1, 1): 0, (1, 1, 1): 0},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"CY4_two_parameter_family",
|
||||||
|
lambda: ToricPolytopeProjectiveSpace([1, 1, 1, 1, 8, 12], model_name="CY4_two_parameter_family"),
|
||||||
|
4,
|
||||||
|
2,
|
||||||
|
[
|
||||||
|
("z2 - 1/256", 2),
|
||||||
|
("34828517376*z1^4*(256*z2 - 1) + 322486272*z1^3 - 1119744*z1^2 + 1728*z1 - 1", 0),
|
||||||
|
],
|
||||||
|
{(0, 0, 0, 0): 64, (0, 0, 0, 1): 16, (0, 0, 1, 1): 4, (0, 1, 1, 1): 1, (1, 1, 1, 1): 0},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"CICY3_one_parameter",
|
||||||
|
lambda: ToricPolytopeCICY([[3, 3]], model_name="CICY3_one_parameter"),
|
||||||
|
3,
|
||||||
|
1,
|
||||||
|
[("z1 - 1/46656", 0)],
|
||||||
|
{(0, 0, 0): 9},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"CICY3_two_parameter",
|
||||||
|
lambda: ToricPolytopeCICY([[3], [3]], model_name="CICY3_two_parameter"),
|
||||||
|
3,
|
||||||
|
2,
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"-19683*z1^3 - 2187*(27*z1 + 1)*z2^2 - 19683*z2^3 - 2187*z1^2 "
|
||||||
|
"- 81*(729*z1^2 - 189*z1 + 1)*z2 - 81*z1 - 1",
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
{(0, 0, 0): 0, (0, 0, 1): 3, (0, 1, 1): 3, (1, 1, 1): 0},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"CICY5_two_parameter",
|
||||||
|
lambda: ToricPolytopeCICY([[6, 1], [0, 2]], model_name="CICY5_two_parameter"),
|
||||||
|
5,
|
||||||
|
2,
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"16384*z2^7 - 28672*z2^6 + 21504*z2^5 - 448*(1647086*z1 - 5)*z2^3 - 8960*z2^4 "
|
||||||
|
"- 112*(8235430*z1 + 3)*z2^2 - 678223072849*z1^2 - 28*(4941258*z1 - 1)*z2 - 1647086*z1 - 1",
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
{
|
||||||
|
(0, 0, 0, 0, 0): 6,
|
||||||
|
(0, 0, 0, 0, 1): 12,
|
||||||
|
(0, 0, 0, 1, 1): 0,
|
||||||
|
(0, 0, 1, 1, 1): 0,
|
||||||
|
(0, 1, 1, 1, 1): 0,
|
||||||
|
(1, 1, 1, 1, 1): 0,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"CICY3_two_parameter_manual",
|
||||||
|
lambda: ToricPolytope(
|
||||||
|
[
|
||||||
|
[1, 0, 0, 0, 0, 0],
|
||||||
|
[0, 1, 0, 0, 0, 0],
|
||||||
|
[0, 0, 1, 0, 0, 0],
|
||||||
|
[0, 0, 0, 1, 0, 0],
|
||||||
|
[0, 0, 0, 0, 1, 0],
|
||||||
|
[0, 0, 0, 0, 0, 1],
|
||||||
|
[-1, -1, 0, 0, 0, 0],
|
||||||
|
[0, 0, -1, -1, -1, -1],
|
||||||
|
],
|
||||||
|
nef_partition=[[0, 1, 2, 3], [4, 5, 6, 7]],
|
||||||
|
model_name="CICY3_two_parameter_manual",
|
||||||
|
),
|
||||||
|
4,
|
||||||
|
2,
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"-14348907*z1^5 - 2657205*z1^4 - 196830*z1^3 - 29296875*(270*z1 + 1)*z2^2 "
|
||||||
|
"- 30517578125*z2^3 - 7290*z1^2 + 9375*(98415*z1^3 - 32805*z1^2 + 810*z1 - 1)*z2 "
|
||||||
|
"- 135*z1 - 1",
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
{(0, 0, 0, 0): 0, (0, 0, 0, 1): 0, (0, 0, 1, 1): 6, (0, 1, 1, 1): 8, (1, 1, 1, 1): 2},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from sage.all import * # noqa: F401
|
||||||
|
from sage.geometry.lattice_polytope import set_palp_dimension
|
||||||
|
|
||||||
|
load(os.path.join(os.path.dirname(__file__), "..", "sage", "toric_topdata.sage"))
|
||||||
|
load(os.path.join(os.path.dirname(__file__), "test_models.sage"))
|
||||||
|
|
||||||
|
set_palp_dimension(11)
|
||||||
|
|
||||||
|
TEST_MODELS_PARAMETRISED = [pytest.param(*row[1:], id=row[0]) for row in TEST_MODELS]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"make_model, expected_cy_dimension, expected_no_divs, expected_disc, expected_intersection_numbers_CY",
|
||||||
|
TEST_MODELS_PARAMETRISED,
|
||||||
|
)
|
||||||
|
def test_topdata_and_disc(
|
||||||
|
make_model,
|
||||||
|
expected_cy_dimension,
|
||||||
|
expected_no_divs,
|
||||||
|
expected_disc,
|
||||||
|
expected_intersection_numbers_CY,
|
||||||
|
):
|
||||||
|
polytope = make_model()
|
||||||
|
|
||||||
|
discriminants = polytope.disc()
|
||||||
|
assert [(str(factor), codim) for factor, codim in discriminants] == expected_disc
|
||||||
|
|
||||||
|
polytope.topdata()
|
||||||
|
assert polytope.cy_dimension == expected_cy_dimension
|
||||||
|
assert polytope.no_divs == expected_no_divs
|
||||||
|
assert polytope.intersection_numbers_CY == expected_intersection_numbers_CY
|
||||||
Reference in New Issue
Block a user