From 2c12f33afab1e399e04eb9798f703608b3ce7845 Mon Sep 17 00:00:00 2001 From: Tom Hubrecht Date: Mon, 27 Jan 2025 13:14:17 +0100 Subject: [PATCH 1/4] Add PrecisionManager to flint context This allows temporarily changing the working precision using python context managers. Related: https://github.com/flintlib/python-flint/issues/5 --- doc/source/general.rst | 22 ++++++++++++++++ src/flint/flint_base/flint_context.pyx | 36 ++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/doc/source/general.rst b/doc/source/general.rst index fb2ec9ac..797bb67d 100644 --- a/doc/source/general.rst +++ b/doc/source/general.rst @@ -55,6 +55,28 @@ The special method ``ctx.cleanup()`` frees up internal caches used by MPFR, FLINT and Arb. The user does normally not have to worry about this. +The context object ``flint.ctx`` can be controlled locally to increase the +working precision using python context managers:: + + >>> arb(2).sqrt() + [1.41421356237309 +/- 5.15e-15] + >>> with ctx.extraprec(15): + ... arb(2).sqrt() + ... + [1.414213562373095049 +/- 2.10e-19] + +In the same manner, it is possible to exactly set the working precision, +or to update it in terms of digits:: + + >>> with ctx.extradps(15): + ... arb(2).sqrt() + ... + [1.41421356237309504880168872421 +/- 6.27e-31] + >>> with ctx.workprec(15): + ... arb(2).sqrt() + ... + [1.414 +/- 2.46e-4] + Types and methods ----------------- diff --git a/src/flint/flint_base/flint_context.pyx b/src/flint/flint_base/flint_context.pyx index c4c02df6..4afb511f 100644 --- a/src/flint/flint_base/flint_context.pyx +++ b/src/flint/flint_base/flint_context.pyx @@ -57,6 +57,18 @@ cdef class FlintContext: assert num >= 1 and num <= 64 flint_set_num_threads(num) + def extraprec(self, n): + return self.workprec(n + self.prec) + + def extradps(self, n): + return self.workdps(n + self.dps) + + def workprec(self, n): + return PrecisionManager(self, eprec=n) + + def workdps(self, n): + return PrecisionManager(self, edps=n) + def __repr__(self): return "pretty = %-8s # pretty-print repr() output\n" \ "unicode = %-8s # use unicode characters in output\n" \ @@ -69,4 +81,28 @@ cdef class FlintContext: def cleanup(self): flint_cleanup() + +class PrecisionManager: + def __init__(self, ctx, eprec=None, edps=None): + if eprec is not None and edps is not None: + raise ValueError("two different precisions requested") + + self.ctx = ctx + + self.eprec = eprec + self.edps = edps + + def __enter__(self): + self._oldprec = self.ctx.prec + + if self.eprec is not None: + self.ctx.prec = self.eprec + + if self.edps is not None: + self.ctx.dps = self.edps + + def __exit__(self, type, value, traceback): + self.ctx.prec = self._oldprec + + cdef FlintContext thectx = FlintContext() From 81c5b64daf2c2d8f2fb05b9a4ea78b8b6d17a148 Mon Sep 17 00:00:00 2001 From: Tom Hubrecht Date: Mon, 27 Jan 2025 13:46:03 +0100 Subject: [PATCH 2/4] Add a decorator for precision contexts This allows wrapping functions to set internal precisions using context managers --- src/flint/flint_base/flint_context.pyx | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/flint/flint_base/flint_context.pyx b/src/flint/flint_base/flint_context.pyx index 4afb511f..781bca70 100644 --- a/src/flint/flint_base/flint_context.pyx +++ b/src/flint/flint_base/flint_context.pyx @@ -6,6 +6,8 @@ from flint.flintlib.types.flint cimport ( ) from flint.utils.conversion cimport prec_to_dps, dps_to_prec +from functools import wraps + cdef class FlintContext: def __init__(self): self.default() @@ -92,6 +94,24 @@ class PrecisionManager: self.eprec = eprec self.edps = edps + def __call__(self, func): + @wraps(func) + def wrapped(*args, **kwargs): + _oldprec = self.ctx.prec + + try: + if self.eprec is not None: + self.ctx.prec = self.eprec + + if self.edps is not None: + self.ctx.dps = self.edps + + return func(*args, **kwargs) + finally: + self.ctx.prec = _oldprec + + return wrapped + def __enter__(self): self._oldprec = self.ctx.prec From 02cde3e9b0161503692196f57b1697d18176752e Mon Sep 17 00:00:00 2001 From: Tom Hubrecht Date: Mon, 27 Jan 2025 16:34:05 +0100 Subject: [PATCH 3/4] Add doctests for flint_context --- src/flint/flint_base/flint_context.pyx | 70 ++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/src/flint/flint_base/flint_context.pyx b/src/flint/flint_base/flint_context.pyx index 781bca70..482ebc1d 100644 --- a/src/flint/flint_base/flint_context.pyx +++ b/src/flint/flint_base/flint_context.pyx @@ -60,15 +60,85 @@ cdef class FlintContext: flint_set_num_threads(num) def extraprec(self, n): + """ + Adds n bits of precision to the current flint context. + + >>> from flint import arb, ctx + >>> with ctx.extraprec(5): x = arb(2).sqrt().str() + >>> x + '[1.414213562373095 +/- 5.53e-17]' + + This function also works as a wrapper: + + >>> from flint import arb, ctx + >>> @ctx.extraprec(10) + ... def f(x): + ... return x.sqrt().str() + >>> f(arb(2)) + '[1.41421356237309505 +/- 1.46e-18]' + """ return self.workprec(n + self.prec) def extradps(self, n): + """ + Adds n digits of precision to the current flint context. + + >>> from flint import arb, ctx + >>> with ctx.extradps(5): x = arb(2).sqrt().str() + >>> x + '[1.4142135623730950488 +/- 2.76e-21]' + + This function also works as a wrapper: + + >>> from flint import arb, ctx + >>> @ctx.extradps(10) + ... def f(x): + ... return x.sqrt().str() + >>> f(arb(2)) + '[1.414213562373095048801689 +/- 3.13e-25]' + """ return self.workdps(n + self.dps) def workprec(self, n): + """ + Sets the working precision for the current flint context, + using a python context manager. + + >>> from flint import arb, ctx + >>> with ctx.workprec(5): x = arb(2).sqrt().str() + >>> x + '[1e+0 +/- 0.438]' + + This function also works as a wrapper: + + >>> from flint import arb, ctx + >>> @ctx.workprec(24) + ... def f(x): + ... return x.sqrt().str() + >>> f(arb(2)) + '[1.41421 +/- 3.66e-6]' + """ return PrecisionManager(self, eprec=n) def workdps(self, n): + """ + Sets the working precision in digits for the current + flint context, using a python context manager. + + >>> from flint import arb, ctx + >>> with ctx.workdps(5): x = arb(2).sqrt().str() + >>> x + '[1.4142 +/- 1.51e-5]' + + This function also works as a wrapper: + + >>> from flint import arb, ctx + >>> @ctx.workdps(10) + ... def f(x): + ... return x.sqrt().str() + >>> f(arb(2)) + '[1.414213562 +/- 3.85e-10]' + """ return PrecisionManager(self, edps=n) def __repr__(self): From d5f1a183164a75fbcdab0de6a26c9b069cff3f1c Mon Sep 17 00:00:00 2001 From: Tom Hubrecht Date: Tue, 28 Jan 2025 15:46:39 +0100 Subject: [PATCH 4/4] Switch to cdef for Precision manager Also use int for eprec and edps, -1 is a special value replacing None --- src/flint/flint_base/flint_context.pyx | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/flint/flint_base/flint_context.pyx b/src/flint/flint_base/flint_context.pyx index 482ebc1d..324510cb 100644 --- a/src/flint/flint_base/flint_context.pyx +++ b/src/flint/flint_base/flint_context.pyx @@ -154,9 +154,14 @@ cdef class FlintContext: flint_cleanup() -class PrecisionManager: - def __init__(self, ctx, eprec=None, edps=None): - if eprec is not None and edps is not None: +cdef class PrecisionManager: + cdef FlintContext ctx + cdef int eprec + cdef int edps + cdef int _oldprec + + def __init__(self, ctx, eprec=-1, edps=-1): + if eprec != -1 and edps != -1: raise ValueError("two different precisions requested") self.ctx = ctx @@ -170,10 +175,10 @@ class PrecisionManager: _oldprec = self.ctx.prec try: - if self.eprec is not None: + if self.eprec != -1: self.ctx.prec = self.eprec - if self.edps is not None: + if self.edps != -1: self.ctx.dps = self.edps return func(*args, **kwargs) @@ -185,10 +190,10 @@ class PrecisionManager: def __enter__(self): self._oldprec = self.ctx.prec - if self.eprec is not None: + if self.eprec != -1: self.ctx.prec = self.eprec - if self.edps is not None: + if self.edps != -1: self.ctx.dps = self.edps def __exit__(self, type, value, traceback):