-
-
Notifications
You must be signed in to change notification settings - Fork 598
Add SageMath-FLINT bridge for number type conversion #39898
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
.. nodoctest | ||
FLINT Bridge Interface | ||
====================== | ||
|
||
This module provides functions to convert from SageMath's numerical types | ||
to Python-FLINT's arbitrary precision types. It solves the issue of direct | ||
conversion failing with code like: | ||
|
||
.. code-block:: python | ||
|
||
flint.arb(RR(1)) | ||
flint.acb(CC(1+I)) | ||
|
||
The bridge uses string-based conversion through mpmath to preserve precision, | ||
with fallback to direct conversion when necessary. | ||
|
||
Real Number Conversion | ||
--------------------- | ||
|
||
.. autofunction:: sage.interfaces.flint.bridge.sage_to_flint_arb | ||
|
||
Complex Number Conversion | ||
------------------------ | ||
|
||
.. autofunction:: sage.interfaces.flint.bridge.sage_to_flint_acb | ||
|
||
Matrix Conversion | ||
--------------- | ||
|
||
.. autofunction:: sage.interfaces.flint.bridge.sage_matrix_to_flint | ||
|
||
Module Reference | ||
-------------- | ||
|
||
.. automodule:: sage.interfaces.flint.bridge | ||
:members: | ||
:undoc-members: | ||
:show-inheritance: |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
""" | ||
SageMath-FLINT bridge interface. | ||
|
||
This module provides conversion functions from SageMath's numerical types | ||
to Python-FLINT's arbitrary precision types. | ||
""" | ||
|
||
from .bridge import sage_to_flint_arb, sage_to_flint_acb, sage_matrix_to_flint |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
""" | ||
flint_sage_bridge.py - Bridge between SageMath and Python-FLINT | ||
Solves GitHub issue #39836 | ||
This module provides one-way conversion from SageMath numerical types | ||
to Python-FLINT arbitrary precision types. | ||
""" | ||
import flint | ||
import mpmath | ||
from sage.all import RR, CC | ||
|
||
def sage_to_flint_arb(sage_real): | ||
""" | ||
Convert SageMath RealNumber to FLINT arb type. | ||
|
||
This preserves more precision than a simple float conversion by using | ||
mpmath as an intermediary. | ||
|
||
Parameters: | ||
----------- | ||
sage_real : sage.rings.real_mpfr.RealNumber | ||
A SageMath real number | ||
|
||
Returns: | ||
-------- | ||
flint.arb | ||
The corresponding FLINT arb number | ||
|
||
Examples: | ||
--------- | ||
>>> from sage.all import RR, pi | ||
>>> sage_to_flint_arb(RR(pi)) | ||
[3.1415926535897932384626433832795028842 +/- ...] | ||
""" | ||
try: | ||
# Get the precision from the SageMath object if available | ||
prec = sage_real.prec() if hasattr(sage_real, 'prec') else 53 | ||
|
||
# Get exact string representation from SageMath | ||
if hasattr(sage_real, 'str'): | ||
# Use the exact string representation if available | ||
str_val = sage_real.str(no_sci=True) | ||
else: | ||
# Fall back to regular string representation | ||
str_val = str(sage_real).replace('[', '').replace(']', '') | ||
|
||
# Convert using mpmath for precision preservation | ||
with mpmath.workdps(prec): | ||
mp_val = mpmath.mpf(str_val) | ||
result = flint.arb(mp_val, prec=prec) | ||
|
||
return result | ||
except (TypeError, ValueError): | ||
# Fallback method | ||
return flint.arb(float(sage_real)) | ||
|
||
def sage_to_flint_acb(sage_complex): | ||
""" | ||
Convert SageMath ComplexNumber to FLINT acb type. | ||
|
||
This preserves more precision than a simple complex conversion by using | ||
mpmath as an intermediary. | ||
|
||
Parameters: | ||
----------- | ||
sage_complex : sage.rings.complex_mpfr.ComplexNumber | ||
A SageMath complex number | ||
|
||
Returns: | ||
-------- | ||
flint.acb | ||
The corresponding FLINT acb complex number | ||
|
||
Examples: | ||
--------- | ||
>>> from sage.all import CC, pi, I | ||
>>> sage_to_flint_acb(CC(pi + I*pi)) | ||
([3.1415926535897932384626433832795028842 +/- ...] + [3.1415926535897932384626433832795028842 +/- ...]*I) | ||
""" | ||
try: | ||
# Get the precision from the SageMath object if available | ||
prec = sage_complex.prec() if hasattr(sage_complex, 'prec') else 53 | ||
|
||
# Get string representations of real and imaginary parts | ||
real_part = sage_complex.real() | ||
imag_part = sage_complex.imag() | ||
|
||
if hasattr(real_part, 'str'): | ||
real_str = real_part.str(no_sci=True) | ||
imag_str = imag_part.str(no_sci=True) | ||
else: | ||
real_str = str(real_part).replace('[', '').replace(']', '') | ||
imag_str = str(imag_part).replace('[', '').replace(']', '') | ||
|
||
# Convert using mpmath with appropriate precision | ||
with mpmath.workdps(prec): | ||
mp_val = mpmath.mpc(real_str, imag_str) | ||
result = flint.acb(mp_val, prec=prec) | ||
|
||
return result | ||
except (TypeError, ValueError): | ||
# Fallback method | ||
return flint.acb(complex(sage_complex)) | ||
|
||
def sage_matrix_to_flint(sage_matrix): | ||
""" | ||
Convert a SageMath matrix with RR/CC entries to FLINT matrix. | ||
|
||
Parameters: | ||
----------- | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We use (Some AI not familiar with our code base may use whatever convention it learns. Not a problem per se, I'm happy if some AI solves my problem as long as the code is high quality, but not disclosing AI usage is dishonest.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're correct that I used AI assistance when drafting portions of this PR, particularly for structuring the documentation format. I should have disclosed this upfront, and I apologize for the oversight. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll soon update the code to use SageMath's standard "INPUT:" convention instead of "Parameters:" |
||
sage_matrix : sage.matrix | ||
A SageMath matrix with real or complex entries | ||
|
||
Returns: | ||
-------- | ||
flint.arb_mat or flint.acb_mat | ||
A FLINT matrix with corresponding entries | ||
|
||
Examples: | ||
--------- | ||
>>> from sage.all import matrix, RR | ||
>>> M = matrix(RR, 2, 2, [1.1, 2.2, 3.3, 4.4]) | ||
>>> sage_matrix_to_flint(M) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Conversion must work out of the box with existing interface, unless there's very strong evidence it is impossible. (For Also need a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Initially, I attempted to create a monkey-patching solution that would modify Python-FLINT's initialization methods to directly handle SageMath types. However, I encountered a limitation: the Due to this constraint, I pivoted to creating explicit conversion functions instead. This approach works reliably but requires users to call the conversion functions rather than working automatically. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I explained in the original issue
This should work at least for ℝ and ℂ, as you can figure by reading the source code. Unless I misread, then explain why. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did attempt implementing mpf and mpc as you suggested in the original issue, but ran into difficulties with the Cython compilation of these methods in SageMath's core files. |
||
[1.10000000000000 2.20000000000000] | ||
[3.30000000000000 4.40000000000000] | ||
""" | ||
rows, cols = sage_matrix.nrows(), sage_matrix.ncols() | ||
|
||
# Detect if we're working with real or complex entries | ||
is_complex = False | ||
for i in range(rows): | ||
for j in range(cols): | ||
if sage_matrix[i,j].parent() == CC or (hasattr(sage_matrix[i,j], 'imag') and not sage_matrix[i,j].imag().is_zero()): | ||
is_complex = True | ||
break | ||
if is_complex: | ||
break | ||
|
||
# Create the appropriate matrix type | ||
if is_complex: | ||
result = flint.acb_mat(rows, cols) | ||
for i in range(rows): | ||
for j in range(cols): | ||
result[i,j] = sage_to_flint_acb(sage_matrix[i,j]) | ||
else: | ||
result = flint.arb_mat(rows, cols) | ||
for i in range(rows): | ||
for j in range(cols): | ||
result[i,j] = sage_to_flint_arb(sage_matrix[i,j]) | ||
|
||
return result |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
""" | ||
Test module for SageMath-FLINT bridge (one-way conversion) | ||
""" | ||
import unittest | ||
import flint | ||
import mpmath | ||
from sage.all import RR, CC, matrix, pi, I, RealField, ComplexField | ||
from sage.interfaces.flint.bridge import ( | ||
sage_to_flint_arb, | ||
sage_to_flint_acb, | ||
sage_matrix_to_flint | ||
) | ||
|
||
class TestFlintSageBridge(unittest.TestCase): | ||
""" | ||
Test suite for the SageMath-to-FLINT bridge functions. | ||
""" | ||
|
||
def setUp(self): | ||
"""Set up test cases.""" | ||
# Real number examples at different precisions | ||
self.real_std = RR(1.234) | ||
self.real_high = RealField(200)(pi) | ||
self.real_exact = RR(1) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We use doctest almost everywhere here instead of pytest. Do read the existing code yourself and try to fit your contribution in before writing code. |
||
|
||
# Complex number examples | ||
self.complex_std = CC(1.234 + 5.678*I) | ||
self.complex_high = ComplexField(200)(pi + I*pi) | ||
self.complex_exact = CC(1 + I) | ||
|
||
# Matrix examples | ||
self.matrix_real = matrix(RR, 2, 2, [1.1, 2.2, 3.3, 4.4]) | ||
self.matrix_complex = matrix(CC, 2, 2, [1+I, 2-I, 3+2*I, 4]) | ||
|
||
def test_real_conversion_basic(self): | ||
"""Test basic conversion of SageMath RealNumber to FLINT arb.""" | ||
arb_num = sage_to_flint_arb(self.real_std) | ||
self.assertIsInstance(arb_num, flint.arb) | ||
self.assertAlmostEqual(float(arb_num), float(self.real_std)) | ||
|
||
def test_real_conversion_exact(self): | ||
"""Test exact number conversion of SageMath RealNumber to FLINT arb.""" | ||
arb_num = sage_to_flint_arb(self.real_exact) | ||
self.assertIsInstance(arb_num, flint.arb) | ||
self.assertEqual(float(arb_num), float(self.real_exact)) | ||
|
||
def test_real_conversion_high_precision(self): | ||
"""Test high precision conversion of SageMath RealNumber to FLINT arb.""" | ||
arb_num = sage_to_flint_arb(self.real_high) | ||
self.assertEqual(str(arb_num).replace('[', '').replace(']', '')[:15], str(self.real_high)[:15]) | ||
|
||
def test_complex_conversion_basic(self): | ||
"""Test basic conversion of SageMath ComplexNumber to FLINT acb.""" | ||
acb_num = sage_to_flint_acb(self.complex_std) | ||
self.assertIsInstance(acb_num, flint.acb) | ||
self.assertAlmostEqual(complex(acb_num), complex(self.complex_std)) | ||
|
||
def test_complex_conversion_high_precision(self): | ||
"""Test high precision conversion of SageMath ComplexNumber to FLINT acb.""" | ||
acb_num = sage_to_flint_acb(self.complex_high) | ||
|
||
flint_real_str = str(acb_num.real).replace('[', '').replace(']', '') | ||
flint_imag_str = str(acb_num.imag).replace('[', '').replace(']', '') | ||
|
||
self.assertEqual(flint_real_str[:15], str(self.complex_high.real())[:15]) | ||
self.assertEqual(flint_imag_str[:15], str(self.complex_high.imag())[:15]) | ||
|
||
def test_complex_conversion_exact(self): | ||
"""Test exact number conversion of SageMath ComplexNumber to FLINT acb.""" | ||
acb_num = sage_to_flint_acb(self.complex_exact) | ||
self.assertIsInstance(acb_num, flint.acb) | ||
self.assertEqual(complex(acb_num), complex(self.complex_exact)) | ||
|
||
def test_matrix_conversion_real(self): | ||
"""Test conversion of SageMath real matrix to FLINT arb_mat.""" | ||
arb_mat = sage_matrix_to_flint(self.matrix_real) | ||
self.assertIsInstance(arb_mat, flint.arb_mat) | ||
self.assertEqual(arb_mat.nrows(), self.matrix_real.nrows()) | ||
self.assertEqual(arb_mat.ncols(), self.matrix_real.ncols()) | ||
# Check elements | ||
for i in range(self.matrix_real.nrows()): | ||
for j in range(self.matrix_real.ncols()): | ||
self.assertAlmostEqual(float(arb_mat[i,j]), float(self.matrix_real[i,j])) | ||
|
||
def test_matrix_conversion_complex(self): | ||
"""Test conversion of SageMath complex matrix to FLINT acb_mat.""" | ||
acb_mat = sage_matrix_to_flint(self.matrix_complex) | ||
self.assertIsInstance(acb_mat, flint.acb_mat) | ||
self.assertEqual(acb_mat.nrows(), self.matrix_complex.nrows()) | ||
self.assertEqual(acb_mat.ncols(), self.matrix_complex.ncols()) | ||
# Check elements | ||
for i in range(self.matrix_complex.nrows()): | ||
for j in range(self.matrix_complex.ncols()): | ||
self.assertAlmostEqual( | ||
complex(acb_mat[i,j]), | ||
complex(self.matrix_complex[i,j]) | ||
) | ||
|
||
def test_error_handling(self): | ||
"""Test error handling for invalid input types.""" | ||
# Test with invalid input for sage_to_flint_arb | ||
with self.assertRaises(Exception): | ||
sage_to_flint_arb("not a number") | ||
|
||
# Test with invalid input for sage_to_flint_acb | ||
with self.assertRaises(Exception): | ||
sage_to_flint_acb("not a number") | ||
|
||
if __name__ == "__main__": | ||
unittest.main() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why nodoctest?