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 os
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
import re
|
||||
|
||||
# Logger
|
||||
@@ -102,6 +101,7 @@ class ToricPolytope(Polytope):
|
||||
if no_triangulation < 0 or no_triangulation >= len(self.triangulations):
|
||||
raise IndexError("Invalid triangulation index {} (available: 0..{}).".format(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]
|
||||
|
||||
@@ -233,7 +233,7 @@ class ToricPolytope(Polytope):
|
||||
def _setup_discriminant_symbols(self):
|
||||
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
|
||||
lambda_vars = var('l', n=len(mori_rays)+1, latex_name='l') # l[0] is superfluous
|
||||
a_row = matrix(a_vars)
|
||||
@@ -275,7 +275,7 @@ class ToricPolytope(Polytope):
|
||||
if polynomials[0] == 0:
|
||||
polynomials = maxima.eliminate(equation_system, lambda_symbols[:-1]).sage()
|
||||
reverse_solve = True
|
||||
except:
|
||||
except Exception:
|
||||
# In one-parameter cases there may be nothing to eliminate.
|
||||
polynomials = equation_system
|
||||
logger.debug("No elimination needed for the equation system: %s", equation_system)
|
||||
@@ -289,7 +289,7 @@ class ToricPolytope(Polytope):
|
||||
else:
|
||||
last_lambda = lambda_symbols[-1]
|
||||
polynomials = [poly / last_lambda ** (poly.degree(last_lambda)) for poly in polynomials]
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
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