5 pipeline with unit tests #10
@@ -0,0 +1,53 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["**"]
|
||||
pull_request:
|
||||
branches: ["**"]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: sagemath/sagemath:10.4
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install ruff
|
||||
run: sage --pip install ruff
|
||||
|
||||
- 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: |
|
||||
set -e
|
||||
for f in $(find . -name "*.sage" -not -path "./playground/*"); do
|
||||
sage --preparse "$f"
|
||||
printf 'from sage.all import * # noqa: F401,F403\n%s\n' "$(cat "${f}.py")" > "${f}.py"
|
||||
done
|
||||
|
||||
- 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.
|
||||
run: sage --python -m ruff check --no-respect-gitignore .
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
needs: lint
|
||||
container:
|
||||
image: sagemath/sagemath:10.4
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install pytest
|
||||
run: sage --pip install pytest
|
||||
|
||||
- name: Run tests
|
||||
run: sage --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,32 @@
|
||||
[tool.ruff]
|
||||
line-length = 120
|
||||
target-version = "py311"
|
||||
|
||||
[tool.ruff.lint]
|
||||
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.
|
||||
"*.sage.py" = ["F403", "F405", "F821", "E741", "E402", "E702", "I001", "W291", "W293"]
|
||||
|
||||
# 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.
|
||||
"tests/test_smoke.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
|
||||
@@ -233,7 +232,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 +274,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 +288,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,32 @@
|
||||
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
|
||||
Reference in New Issue
Block a user