Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
08a515d
Fix bash syntax for if statement
oscarbenjamin Feb 13, 2026
a56eaea
Fix outstanding mypy errors in test_all.py
oscarbenjamin Feb 13, 2026
fd77981
arf: add mixed-mode arithmetic and type stubs
oscarbenjamin Feb 14, 2026
b8c7567
arb: add typing stubs and full test coverage
oscarbenjamin Feb 14, 2026
58e2bb1
ctx: add type stubs for FlintContext
oscarbenjamin Feb 14, 2026
ee120ed
acb: add tests and type annotations
oscarbenjamin Feb 14, 2026
b53c72f
arb_poly: add tests and typing
oscarbenjamin Feb 14, 2026
7a0f983
acb: full test coverage and typing
oscarbenjamin Feb 14, 2026
e19a176
arb_series: tests and typing
oscarbenjamin Feb 14, 2026
8e19d46
acb_series: tests and typing
oscarbenjamin Feb 14, 2026
198f6a1
coverage_plugin: honour pragma: no cover
oscarbenjamin Feb 14, 2026
0c33f73
fmpz_vec: typing and tests
oscarbenjamin Feb 14, 2026
9604c20
fmpq_vec: typing and tests
oscarbenjamin Feb 14, 2026
ce20933
fmpz_mat: add type stubs
oscarbenjamin Feb 14, 2026
f82dde8
fmpz_mat: add type stubs
oscarbenjamin Feb 14, 2026
db112c5
nmod_mat.pyx: add type annotations
oscarbenjamin Feb 15, 2026
ee382ab
fmpz_mod_mat: add type stubs
oscarbenjamin Feb 15, 2026
e86c35d
arb_mat: add typing and tests
oscarbenjamin Feb 15, 2026
e8a296a
acb_mat: add type stubs
oscarbenjamin Feb 15, 2026
b8e95bb
dirichlet: add type stubs and tests
oscarbenjamin Feb 15, 2026
dfa3264
acb_theta: add type stubs
oscarbenjamin Feb 15, 2026
8b4c20d
Fix lint issue
oscarbenjamin Feb 15, 2026
cf2d330
Add _has_acb_theta function
oscarbenjamin Feb 15, 2026
ed0a59c
Relax tolerance in acb_poly roots test
oscarbenjamin Feb 15, 2026
5bad07d
Fix is_close tolerance in tests
oscarbenjamin Feb 15, 2026
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
19 changes: 10 additions & 9 deletions bin/build_dependencies_unix.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ SKIP_MPFR=no
USE_GMP=gmp
PATCH_GMP_ARM64=no
BUILD_ARB=no
USE_GMP_GITHUB_MIRROR=no

while [[ $# -gt 0 ]]
do
Expand Down Expand Up @@ -124,15 +125,15 @@ cd src
# #
# ------------------------------------------------------------------------- #

if [ $USE_GMP = "gmp" ]; then
if [ "$USE_GMP" = "gmp" ]; then

# ----------------------------------------------------------------------- #
# #
# GMP #
# #
# ----------------------------------------------------------------------- #

if [ $SKIP_GMP = "yes" ]; then
if [ "$SKIP_GMP" = "yes" ]; then
echo
echo --------------------------------------------
echo " skipping GMP"
Expand All @@ -145,7 +146,7 @@ if [ $USE_GMP = "gmp" ]; then
echo --------------------------------------------
echo

if [ $USE_GMP_GITHUB_MIRROR = "yes" ]; then
if [ "$USE_GMP_GITHUB_MIRROR" = "yes" ]; then
# Needed in GitHub Actions because it is blocked from gmplib.org
git clone https://github.com/oscarbenjamin/gmp_mirror.git
cp gmp_mirror/gmp-$GMPVER.tar.xz .
Expand All @@ -163,7 +164,7 @@ if [ $USE_GMP = "gmp" ]; then
# from the GMP repo but was applied after the release of GMP 6.2.1.
# This patch is no longer needed for GMP 6.3.0.
#
if [ $PATCH_GMP_ARM64 = "yes" ]; then
if [ "$PATCH_GMP_ARM64" = "yes" ]; then
echo
echo --------------------------------------------
echo " patching GMP"
Expand Down Expand Up @@ -260,7 +261,7 @@ fi
# #
# ------------------------------------------------------------------------- #

if [ $SKIP_MPFR = "yes" ]; then
if [ "$SKIP_MPFR" = "yes" ]; then
echo
echo --------------------------------------------
echo " skipping MPFR"
Expand Down Expand Up @@ -326,7 +327,7 @@ cd ..
# #
# ------------------------------------------------------------------------- #

if [ $BUILD_ARB = "yes" ]; then
if [ "$BUILD_ARB" = "yes" ]; then

echo
echo --------------------------------------------
Expand Down Expand Up @@ -369,7 +370,7 @@ echo $PREFIX
echo
echo Versions:

if [ $SKIP_GMP = "yes" ]; then
if [ "$SKIP_GMP" = "yes" ]; then
echo GMP: skipped
else
if [[ $USE_GMP = "gmp" ]]; then
Expand All @@ -379,15 +380,15 @@ else
fi
fi

if [ $SKIP_MPFR = "yes" ]; then
if [ "$SKIP_MPFR" = "yes" ]; then
echo MPFR: skipped
else
echo MPFR: $MPFRVER
fi

echo Flint: $FLINTVER

if [ $BUILD_ARB = "yes" ]; then
if [ "$BUILD_ARB" = "yes" ]; then
echo Arb: $ARBVER
fi
echo
Expand Down
55 changes: 47 additions & 8 deletions coverage_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def get_cython_build_rules():


@cache
def parse_all_cfile_lines():
def parse_all_cfile_lines(excluded_line_patterns=()):
"""Parse all generated C files from the build directory."""
#
# Each .c file can include code generated from multiple Cython files (e.g.
Expand All @@ -77,6 +77,14 @@ def parse_all_cfile_lines():
# expensive.
#
all_code_lines = {}
all_excluded_lines = defaultdict(set)
if excluded_line_patterns:
pattern = re.compile("|".join([f"(?:{regex})" for regex in excluded_line_patterns]))
line_is_excluded = pattern.search
else:
line_is_excluded = lambda line: False

source_cache = {}

for c_file, _ in get_cython_build_rules():

Expand All @@ -85,13 +93,33 @@ def parse_all_cfile_lines():
for cython_file, line_map in cfile_lines.items():
if cython_file == '(tree fragment)':
continue
elif cython_file in all_code_lines:
src_lines = source_cache.get(cython_file)
if src_lines is None:
src_path = src_dir / cython_file
if src_path.exists():
with open(src_path, encoding="utf8") as src:
src_lines = src.read().splitlines()
else:
src_lines = []
source_cache[cython_file] = src_lines

excluded = set()
filtered_line_map = {}
for lineno, line in line_map.items():
source_line = src_lines[lineno - 1] if 0 < lineno <= len(src_lines) else ""
if line_is_excluded(source_line) or line_is_excluded(line):
excluded.add(lineno)
else:
filtered_line_map[lineno] = line
if cython_file in all_code_lines:
# Possibly need to merge the lines?
assert all_code_lines[cython_file] == line_map
assert all_code_lines[cython_file] == filtered_line_map
all_excluded_lines[cython_file].update(excluded)
else:
all_code_lines[cython_file] = line_map
all_code_lines[cython_file] = filtered_line_map
all_excluded_lines[cython_file] = excluded

return all_code_lines
return all_code_lines, all_excluded_lines


def parse_cfile_lines(c_file):
Expand All @@ -102,6 +130,11 @@ def parse_cfile_lines(c_file):

class Plugin(CoveragePlugin):
"""A coverage plugin for a spin/meson project with Cython code."""
_excluded_line_patterns = ()

def configure(self, config):
# Match Cython's plugin behavior and respect coverage's exclusion regexes.
self._excluded_line_patterns = tuple(config.get_option("report:exclude_lines"))

def file_tracer(self, filename):
"""Find a tracer for filename to handle trace events."""
Expand All @@ -121,7 +154,7 @@ def file_tracer(self, filename):
def file_reporter(self, filename):
"""Return a file reporter for filename."""
srcfile = Path(filename).relative_to(src_dir)
return CyFileReporter(srcfile)
return CyFileReporter(srcfile, self._excluded_line_patterns)


class CyFileTracer(FileTracer):
Expand Down Expand Up @@ -157,14 +190,15 @@ def get_source_filename(filename):
class CyFileReporter(FileReporter):
"""File reporter for Cython or Python files (.pyx,.pxd,.py)."""

def __init__(self, srcpath):
def __init__(self, srcpath, excluded_line_patterns):
abspath = (src_dir / srcpath)
assert abspath.exists()

# filepath here needs to match dynamic_source_filename
super().__init__(str(abspath))

self.srcpath = srcpath
self.excluded_line_patterns = excluded_line_patterns

def relative_filename(self):
"""Path displayed in the coverage reports."""
Expand All @@ -173,10 +207,15 @@ def relative_filename(self):
def lines(self):
"""Set of line numbers for possibly traceable lines."""
srcpath = str(self.srcpath)
all_line_maps = parse_all_cfile_lines()
all_line_maps, _ = parse_all_cfile_lines(self.excluded_line_patterns)
line_map = all_line_maps[srcpath]
return set(line_map)

def excluded_lines(self):
srcpath = str(self.srcpath)
_, all_excluded_lines = parse_all_cfile_lines(self.excluded_line_patterns)
return set(all_excluded_lines.get(srcpath, ()))


def coverage_init(reg, options):
plugin = Plugin()
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ package = "flint"
"spin.cmds.meson.run",
]

[tool.mypy]
files = ["src"]

[tool.pytest.ini_options]
addopts = "--import-mode=importlib"

Expand Down
17 changes: 17 additions & 0 deletions src/flint/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,23 @@

__version__ = "0.8.0"


def _flint_version_at_least(major: int, minor: int) -> bool:
version_parts = __FLINT_VERSION__.split(".")
if len(version_parts) < 2:
return False
try:
current_major = int(version_parts[0])
current_minor = int(version_parts[1])
except ValueError:
return False
return (current_major, current_minor) >= (major, minor)


def _has_acb_theta() -> bool:
return _flint_version_at_least(3, 1)


__all__ = [
"ctx",
"fmpz",
Expand Down
10 changes: 10 additions & 0 deletions src/flint/flint_base/flint_base.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ class flint_poly(flint_elem, Generic[Telem]):
def complex_roots(self) -> list[Any]: ...
def derivative(self) -> Self: ...

class flint_mat(flint_elem, Generic[Telem]):
def nrows(self) -> int: ...
def ncols(self) -> int: ...
def __getitem__(self, index: tuple[int, int], /) -> Telem: ...
def __setitem__(self, index: tuple[int, int], value: Telem | int, /) -> None: ...
def entries(self) -> list[Telem]: ...
def table(self) -> list[list[Telem]]: ...
def tolist(self) -> list[list[Telem]]: ...
def __iter__(self) -> Iterator[Telem]: ...

class Ordering(enum.Enum):
lex = "lex"
deglex = "deglex"
Expand Down
53 changes: 53 additions & 0 deletions src/flint/flint_base/flint_context.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from __future__ import annotations

from typing import Callable, ParamSpec, TypeVar


_P = ParamSpec("_P")
_R = TypeVar("_R")


class FlintContext:
pretty: bool
unicode: bool

def __init__(self) -> None: ...
def default(self) -> None: ...

@property
def prec(self) -> int: ...
@prec.setter
def prec(self, prec: int) -> None: ...

@property
def dps(self) -> int: ...
@dps.setter
def dps(self, prec: int) -> None: ...

@property
def cap(self) -> int: ...
@cap.setter
def cap(self, cap: int) -> None: ...

@property
def threads(self) -> int: ...
@threads.setter
def threads(self, num: int) -> None: ...

def extraprec(self, n: int) -> PrecisionManager: ...
def extradps(self, n: int) -> PrecisionManager: ...
def workprec(self, n: int) -> PrecisionManager: ...
def workdps(self, n: int) -> PrecisionManager: ...

def __repr__(self) -> str: ...
def cleanup(self) -> None: ...


class PrecisionManager:
def __init__(self, ctx: FlintContext, eprec: int = -1, edps: int = -1) -> None: ...
def __call__(self, func: Callable[_P, _R]) -> Callable[_P, _R]: ...
def __enter__(self) -> None: ...
def __exit__(self, exc_type: object, value: object, traceback: object) -> None: ...


thectx: FlintContext
1 change: 1 addition & 0 deletions src/flint/flint_base/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pkgdir = 'flint/flint_base'
pyfiles = [
'__init__.py',
'flint_base.pyi',
'flint_context.pyi',
]

exts = [
Expand Down
1 change: 1 addition & 0 deletions src/flint/flintlib/functions/arf.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ from flint.flintlib.types.fmpq cimport fmpq_t
# .. macro:: ARF_PREC_EXACT

cdef extern from "flint/arf.h":
cdef const slong ARF_PREC_EXACT
void arf_init(arf_t x)
void arf_clear(arf_t x)
slong arf_allocated_bytes(const arf_t x)
Expand Down
1 change: 1 addition & 0 deletions src/flint/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pyfiles = [
'__init__.py',
'typing.py',
'py.typed',
'pyflint.pyi',
]

exts = [
Expand Down
4 changes: 4 additions & 0 deletions src/flint/pyflint.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .flint_base.flint_context import FlintContext


ctx: FlintContext
29 changes: 28 additions & 1 deletion src/flint/test/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,39 @@
import doctest
import traceback
import argparse
import importlib
import inspect
import pkgutil
from typing import Callable

import flint
from flint.test.test_all import all_tests
from flint.test.test_docstrings import find_doctests


def collect_all_tests() -> list[Callable[[], object]]:
tests: list[Callable[[], object]] = []
import flint.test as test_pkg

for mod in pkgutil.iter_modules(test_pkg.__path__, test_pkg.__name__ + "."):
mod_name = mod.name.rsplit(".", 1)[-1]
if not mod_name.startswith("test_"):
continue
module = importlib.import_module(mod.name)
for name, obj in vars(module).items():
if (
name.startswith("test_")
and inspect.isfunction(obj)
and obj.__module__ == module.__name__
and len(inspect.signature(obj).parameters) == 0
):
tests.append(obj)

return tests


all_tests = collect_all_tests()


def run_tests(verbose=None):
"""Run all tests

Expand Down
Loading
Loading