Compare commits
4
Commits
3cc7bc0dda
...
d7ed05771b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7ed05771b | ||
|
|
aadc1f37ea | ||
|
|
d17bdc8949 | ||
|
|
bb830640fc |
@@ -1,26 +1,16 @@
|
||||
# Custom CI image for the arm64 (Raspberry Pi) Gitea Actions runner.
|
||||
# Custom CI image for the arm64 (Raspberry Pi) Gitea Actions runner,
|
||||
# since the official sagemath/sagemath image is amd64-only.
|
||||
#
|
||||
# The official sagemath/sagemath image is amd64-only; conda-forge's `sage`
|
||||
# package is the one place SageMath is actually published for linux-aarch64,
|
||||
# so this bakes it (plus the lint/test tools) into a single image, built and
|
||||
# pushed once rather than reinstalled on every CI run.
|
||||
#
|
||||
# Build & push (run directly on the Pi, or any arm64 machine with Docker):
|
||||
# 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
|
||||
#
|
||||
# Rebuild and re-push whenever this Dockerfile changes (e.g. bumping the sage
|
||||
# version) or the pinned tool versions need updating.
|
||||
|
||||
FROM condaforge/miniforge3:latest
|
||||
|
||||
RUN mamba install -y -c conda-forge sage ruff pytest \
|
||||
&& mamba clean -afy
|
||||
|
||||
# actions/checkout@v4 (and other JS-based actions) run via `node` inside this
|
||||
# container -- act_runner doesn't inject a runtime of its own when a custom
|
||||
# `container:` image is set, so one has to be present here.
|
||||
RUN mamba install -y -c conda-forge nodejs \
|
||||
&& mamba clean -afy
|
||||
|
||||
|
||||
+3
-14
@@ -1,18 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# Reproduces the CI "Preparse .sage files" step locally, so `ruff check` sees
|
||||
# the same generated *.sage.py files that the pipeline lints. Run this before
|
||||
# `ruff check --no-respect-gitignore .` to catch issues that only show up in
|
||||
# the generated output.
|
||||
#
|
||||
# Uses the sage.repl.preparse.preparse_file() Python API directly rather than
|
||||
# the `sage --preparse` CLI flag: the conda-forge `sage` binary used in CI is
|
||||
# a cut-down entry point that doesn't support `--preparse` (or `--python`) at
|
||||
# all, unlike the official sagemath CLI. Whichever `python` on PATH can
|
||||
# actually `import sage.repl` is used to run it -- in CI that's the conda
|
||||
# env's own `python`; locally (official sage install) it's `sage --python`.
|
||||
#
|
||||
# The generated files are gitignored; clean them up afterwards with:
|
||||
# git clean -x sage/ playground/
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
+1
-12
@@ -9,9 +9,7 @@ on:
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
# Custom image (see .gitea/ci-image/Dockerfile): the official
|
||||
# sagemath/sagemath image is amd64-only and this runner is arm64, and
|
||||
# ruff/pytest are baked in here so jobs don't reinstall them every run.
|
||||
# Custom image (see .gitea/ci-image/Dockerfile)
|
||||
container:
|
||||
image: gitea.piribauer.ch/julian/sage-ci:latest
|
||||
|
||||
@@ -20,18 +18,9 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Preparse .sage files
|
||||
# Turns each *.sage file into real Python (*.sage.py) so ruff can
|
||||
# parse it, and prepends the `from sage.all import *` that `sage`
|
||||
# normally injects at runtime, so Sage's globals (ZZ, var, matrix, ...)
|
||||
# resolve instead of looking like undefined names.
|
||||
run: .gitea/preparse.sh
|
||||
|
||||
- name: ruff check
|
||||
# --no-respect-gitignore: *.sage.py is gitignored (it's generated, see
|
||||
# the previous step) but that's exactly what we need to lint here.
|
||||
# `python` here is the conda env's own interpreter -- it already has
|
||||
# sage/ruff/pytest importable, `sage --python` isn't a real flag on
|
||||
# the conda-forge sage CLI.
|
||||
run: python -m ruff check --no-respect-gitignore .
|
||||
|
||||
test:
|
||||
|
||||
+3
-21
@@ -3,33 +3,15 @@ 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 generated by `sage --preparse` from *.sage sources.
|
||||
# - F403/F405/F821/E741: Sage's runtime injects hundreds of globals (ZZ, var,
|
||||
# matrix, LatticePolytope, ...) via `from sage.all import *` before
|
||||
# executing these files, so plain static analysis can't see where names
|
||||
# come from.
|
||||
# - E402/E702/I001: Sage's preparser itself emits an import-then-semicolon-
|
||||
# joined-constants preamble (e.g. `_sage_const_1 = Integer(1); ...`) ahead
|
||||
# of the file's own imports; that's Sage's boilerplate, not this project's
|
||||
# code style.
|
||||
# - W291/W293: the preparser replaces every integer literal with
|
||||
# `_sage_const_N ` (trailing space included) to preserve token boundaries,
|
||||
# so trailing-whitespace warnings fire mechanically on almost every line
|
||||
# with a number in it -- not something `ruff format` could fix anyway,
|
||||
# since it wouldn't change the .sage source that generated it.
|
||||
# - W292: whether the reconstructed file ends in a real trailing newline
|
||||
# depends on how many blank lines the preparser appends, which varies by
|
||||
# Sage version -- not something worth pinning tool versions over.
|
||||
# *.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"]
|
||||
|
||||
# tests/test_smoke.py does `from sage.all import *` and `load(...)` a .sage
|
||||
# file to get at its classes -- the same dynamic-namespace situation as
|
||||
# *.sage.py above, just in a hand-written file: ruff can't see that
|
||||
# Polytope/ToricPolytope/etc. come from the loaded file.
|
||||
# test_smoke.py star-imports sage.all and loads a .sage file, so ruff can't see where its names come from.
|
||||
"tests/test_smoke.py" = ["F403", "F405"]
|
||||
|
||||
+32
-1
@@ -738,4 +738,35 @@ class ToricPolytopeCICY(ToricPolytope):
|
||||
nef_partition=nef_partition,
|
||||
lvec=lvec
|
||||
)
|
||||
|
||||
|
||||
class ToricPolytopeProjectiveSpace(ToricPolytope):
|
||||
"""
|
||||
Computes topological data for hypersurfaces in toric ambient spaces.
|
||||
"""
|
||||
def _compute_points_from_weights(self, weights):
|
||||
# Convert the weights of the projective space to the corresponding points
|
||||
dimension = len(weights) - 1
|
||||
points = identity_matrix(dimension)
|
||||
try:
|
||||
weights.remove(1)
|
||||
except ValueError:
|
||||
raise ValueError("The weights must include a 1 for the projective space.")
|
||||
|
||||
points = points.insert_row(0, [-w for w in weights])
|
||||
return list(points)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
weights,
|
||||
no_triangulation = 0,
|
||||
model_name = None,
|
||||
nef_partition = None,
|
||||
lvec = None, # Note that the inner point is the last entry
|
||||
):
|
||||
super().__init__(
|
||||
self._compute_points_from_weights(weights),
|
||||
no_triangulation=no_triangulation,
|
||||
model_name=model_name,
|
||||
nef_partition=nef_partition,
|
||||
lvec=lvec
|
||||
)
|
||||
|
||||
@@ -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().
|
||||
|
||||
PLAYGROUND_MODEL_FACTORIES = [
|
||||
(
|
||||
"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},
|
||||
),
|
||||
]
|
||||
@@ -1,32 +0,0 @@
|
||||
import os
|
||||
|
||||
import pytest
|
||||
from sage.all import * # noqa: F401,F403
|
||||
|
||||
load(os.path.join(os.path.dirname(__file__), "..", "sage", "toric_topdata.sage"))
|
||||
|
||||
|
||||
def test_polytope_rejects_too_few_points():
|
||||
with pytest.raises(ValueError):
|
||||
Polytope([[0, 0], [1, 0]]) # 2 points in dimension 2: not enough
|
||||
|
||||
|
||||
def test_mirror_quintic_topdata():
|
||||
# Vertices e_1, ..., e_4, -e_1-...-e_4 -- the worked example from
|
||||
# ToricPolytope's own docstring.
|
||||
points = [
|
||||
[1, 0, 0, 0],
|
||||
[0, 1, 0, 0],
|
||||
[0, 0, 1, 0],
|
||||
[0, 0, 0, 1],
|
||||
[-1, -1, -1, -1],
|
||||
]
|
||||
|
||||
polytope = ToricPolytope(points, model_name="ci_smoke_test_quintic")
|
||||
assert polytope.dimension == 4
|
||||
assert len(polytope.triangulations) >= 1
|
||||
|
||||
polytope.topdata()
|
||||
assert polytope.cy_dimension == 3
|
||||
assert polytope.no_divs == 1 # P^4 has Picard rank 1
|
||||
assert polytope.intersection_numbers_CY == {(0, 0, 0): 5} # classical quintic self-intersection
|
||||
@@ -0,0 +1,34 @@
|
||||
import os
|
||||
|
||||
import pytest
|
||||
from sage.all import * # noqa: F401,F403
|
||||
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__), "playground_models.sage"))
|
||||
|
||||
set_palp_dimension(11)
|
||||
|
||||
PLAYGROUND_MODELS = [pytest.param(*row[1:], id=row[0]) for row in PLAYGROUND_MODEL_FACTORIES] # noqa: F821
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"make_model, expected_cy_dimension, expected_no_divs, expected_disc, expected_intersection_numbers_CY",
|
||||
PLAYGROUND_MODELS,
|
||||
)
|
||||
def test_playground_models_disc_and_topdata(
|
||||
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