From 91b17db2b3adb0ca1f1ee1f41490eb6406b338c5 Mon Sep 17 00:00:00 2001 From: UriKH Date: Tue, 3 Feb 2026 23:40:03 +0200 Subject: [PATCH 1/8] make determinant a CMF functionality --- ramanujantools/cmf/cmf.py | 7 +++++++ ramanujantools/cmf/pfq.py | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ramanujantools/cmf/cmf.py b/ramanujantools/cmf/cmf.py index 967ba10..455b546 100644 --- a/ramanujantools/cmf/cmf.py +++ b/ramanujantools/cmf/cmf.py @@ -641,3 +641,10 @@ def delta_sequence( trajectory, depths, start, initial_values, final_projection ) return [approximant.delta(limit) for approximant in approximants] + + def determinant(self, axis: sp.Symbol) -> sp.Expr: + """ + Computes the determinant of the relevant CMF matrix matching 'axis' + """ + # Note: this is a default behavior - it should be overridden in the subclasses + return sp.det(self.matrices[axis]) diff --git a/ramanujantools/cmf/pfq.py b/ramanujantools/cmf/pfq.py index d423b69..8e2cd01 100644 --- a/ramanujantools/cmf/pfq.py +++ b/ramanujantools/cmf/pfq.py @@ -171,8 +171,7 @@ def evaluate( m = pFq.contiguous_relation((a_values, b_values), (a_anchor, b_anchor), z_eval) return (vector * m)[0] - @staticmethod - def determinant(p: int, q: int, z: sp.Expr, axis: sp.Symbol): + def determinant(self, axis: sp.Symbol): """ Returns the determinant of an axis (basis) matrix in factored form via a hardcoded formula, for quick performance. @@ -231,6 +230,7 @@ def determinant(p: int, q: int, z: sp.Expr, axis: sp.Symbol): $$(-1)^{q+1} \\frac{-(y_r)^{q+1}+(y_r)^{q-1}((\\sum_{i=0}^{q-1}y_i + \\sum x_i ) +y_r-q+1)} {\\prod_{i=0}^{p-1}(-y_r+x_i)}$$ """ + p, q, z = self.p, self.q, self.z is_y = axis.name.startswith("y") x_axes = pFq.x_axes(p) y_axes = pFq.y_axes(q) From ee801bc6c54f8dc4c863fb57d061f58d69ed7211 Mon Sep 17 00:00:00 2001 From: UriKH Date: Tue, 3 Feb 2026 23:50:05 +0200 Subject: [PATCH 2/8] fix tests --- ramanujantools/cmf/pfq_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ramanujantools/cmf/pfq_test.py b/ramanujantools/cmf/pfq_test.py index f4c3a26..0b0ebbc 100644 --- a/ramanujantools/cmf/pfq_test.py +++ b/ramanujantools/cmf/pfq_test.py @@ -245,5 +245,5 @@ def test_determinant_against_char_poly(p, q, z, axis): def test_hardcoded_determinant_formula(p, q, z, axis): """Tests the high-performance pFq.determinant against the char_poly method.""" det = pFq_determinant_from_char_poly(p, q, z, axis) - calc = pFq.determinant(p, q, z, axis) + calc = pFq(p, q, z).determinant(axis) assert calc == det From 7d4c9d6957bc7c96f2be7aea0da98be9d282e7b8 Mon Sep 17 00:00:00 2001 From: UriKH Date: Sun, 22 Mar 2026 23:33:20 +0200 Subject: [PATCH 3/8] fix get-set states for pickling --- ramanujantools/cmf/meijer_g.py | 15 +++++++++++++++ ramanujantools/cmf/meijer_g_test.py | 16 ++++++++++++++++ ramanujantools/cmf/pfq.py | 13 +++++++++++++ ramanujantools/cmf/pfq_test.py | 16 ++++++++++++++++ 4 files changed, 60 insertions(+) diff --git a/ramanujantools/cmf/meijer_g.py b/ramanujantools/cmf/meijer_g.py index 485ca93..159cf52 100644 --- a/ramanujantools/cmf/meijer_g.py +++ b/ramanujantools/cmf/meijer_g.py @@ -29,6 +29,21 @@ def __init__(self, m: int, n: int, p: int, q: int, z: sp.Expr = z): def __repr__(self) -> str: return f"MeijerG({self.m, self.n, self.p, self.q, self.z})" + def __getstate__(self): + state = super().__getstate__().copy() + state.update({ + 'm': self.m, + 'n': self.n, + 'p': self.p, + 'q': self.q, + 'z': self.z + }) + return state + + def __setstate__(self, state): + self.__dict__.update(state) + super().__setstate__(state) + @staticmethod def a_axes(p) -> list[sp.Symbol]: return sp.symbols(f"a:{p}") diff --git a/ramanujantools/cmf/meijer_g_test.py b/ramanujantools/cmf/meijer_g_test.py index c2f7562..2cf9b1d 100644 --- a/ramanujantools/cmf/meijer_g_test.py +++ b/ramanujantools/cmf/meijer_g_test.py @@ -1,4 +1,5 @@ from pytest import approx +import pickle import sympy as sp from sympy.abc import n, z @@ -28,3 +29,18 @@ def test_gamma(): limit.initial_values = Matrix([[1, 1, 0], [1, 1, 1]]) assert limit.as_float() == approx(limit.mp.euler) + + +def test_serialization(): + original_pfq = MeijerG(p=3, n=2, q=1, m=1, z=-1) + + serialized_data = pickle.dumps(original_pfq) + unpickled_pfq = pickle.loads(serialized_data) + + assert isinstance(unpickled_pfq, MeijerG), "Object type mismatch after unpickling." + assert hasattr(unpickled_pfq, 'p'), 'expected to have attribute p' + assert hasattr(unpickled_pfq, 'q'), 'expected to have attribute q' + assert hasattr(unpickled_pfq, 'z'), 'expected to have attribute z' + assert unpickled_pfq.p == original_pfq.p, f"p mismatch: {unpickled_pfq.p} != {original_pfq.p}" + assert unpickled_pfq.q == original_pfq.q, f"q mismatch: {unpickled_pfq.q} != {original_pfq.q}" + assert unpickled_pfq.z == original_pfq.z, f"z mismatch: {unpickled_pfq.z} != {original_pfq.z}" diff --git a/ramanujantools/cmf/pfq.py b/ramanujantools/cmf/pfq.py index 8e2cd01..d841d24 100644 --- a/ramanujantools/cmf/pfq.py +++ b/ramanujantools/cmf/pfq.py @@ -35,6 +35,19 @@ def __init__( def __repr__(self) -> str: return f"pFq({self.p, self.q, self.z})" + def __getstate__(self): + state = super().__getstate__().copy() + state.update({ + 'p': self.p, + 'q': self.q, + 'z': self.z + }) + return state + + def __setstate__(self, state): + self.__dict__.update(state) + super().__setstate__(state) + @staticmethod def x_axes(p: int) -> list[sp.Symbol]: return sp.symbols(f"x:{p}") diff --git a/ramanujantools/cmf/pfq_test.py b/ramanujantools/cmf/pfq_test.py index 0b0ebbc..9534331 100644 --- a/ramanujantools/cmf/pfq_test.py +++ b/ramanujantools/cmf/pfq_test.py @@ -1,4 +1,5 @@ import pytest +import pickle import sympy as sp from sympy.abc import n, z @@ -247,3 +248,18 @@ def test_hardcoded_determinant_formula(p, q, z, axis): det = pFq_determinant_from_char_poly(p, q, z, axis) calc = pFq(p, q, z).determinant(axis) assert calc == det + + +def test_pfq_serialization(): + original_pfq = pFq(p=2, q=1, z=-1) + + serialized_data = pickle.dumps(original_pfq) + unpickled_pfq = pickle.loads(serialized_data) + + assert isinstance(unpickled_pfq, pFq), "Object type mismatch after unpickling." + assert hasattr(unpickled_pfq, 'p'), 'expected to have attribute p' + assert hasattr(unpickled_pfq, 'q'), 'expected to have attribute q' + assert hasattr(unpickled_pfq, 'z'), 'expected to have attribute z' + assert unpickled_pfq.p == original_pfq.p, f"p mismatch: {unpickled_pfq.p} != {original_pfq.p}" + assert unpickled_pfq.q == original_pfq.q, f"q mismatch: {unpickled_pfq.q} != {original_pfq.q}" + assert unpickled_pfq.z == original_pfq.z, f"z mismatch: {unpickled_pfq.z} != {original_pfq.z}" From 3bc8fa66549f6b9d443c875721fd10af4cc149eb Mon Sep 17 00:00:00 2001 From: UriKH Date: Sun, 22 Mar 2026 23:38:38 +0200 Subject: [PATCH 4/8] add missing tests for MeijerG --- ramanujantools/cmf/meijer_g_test.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ramanujantools/cmf/meijer_g_test.py b/ramanujantools/cmf/meijer_g_test.py index 2cf9b1d..2467381 100644 --- a/ramanujantools/cmf/meijer_g_test.py +++ b/ramanujantools/cmf/meijer_g_test.py @@ -40,7 +40,11 @@ def test_serialization(): assert isinstance(unpickled_pfq, MeijerG), "Object type mismatch after unpickling." assert hasattr(unpickled_pfq, 'p'), 'expected to have attribute p' assert hasattr(unpickled_pfq, 'q'), 'expected to have attribute q' + assert hasattr(unpickled_pfq, 'm'), 'expected to have attribute m' + assert hasattr(unpickled_pfq, 'n'), 'expected to have attribute n' assert hasattr(unpickled_pfq, 'z'), 'expected to have attribute z' assert unpickled_pfq.p == original_pfq.p, f"p mismatch: {unpickled_pfq.p} != {original_pfq.p}" assert unpickled_pfq.q == original_pfq.q, f"q mismatch: {unpickled_pfq.q} != {original_pfq.q}" + assert unpickled_pfq.m == original_pfq.m, f"p mismatch: {unpickled_pfq.m} != {original_pfq.m}" + assert unpickled_pfq.n == original_pfq.n, f"p mismatch: {unpickled_pfq.n} != {original_pfq.n}" assert unpickled_pfq.z == original_pfq.z, f"z mismatch: {unpickled_pfq.z} != {original_pfq.z}" From 0a1da56d50111451f3820f1c0ce5f6d6e32f59af Mon Sep 17 00:00:00 2001 From: UriKH Date: Wed, 25 Mar 2026 12:47:18 +0200 Subject: [PATCH 5/8] Fix PR comments --- ramanujantools/cmf/cmf.py | 7 ------- ramanujantools/cmf/meijer_g.py | 11 ++++++++++- ramanujantools/cmf/meijer_g_test.py | 14 +++++++------- ramanujantools/cmf/pfq.py | 11 ++++++++--- 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/ramanujantools/cmf/cmf.py b/ramanujantools/cmf/cmf.py index 455b546..967ba10 100644 --- a/ramanujantools/cmf/cmf.py +++ b/ramanujantools/cmf/cmf.py @@ -641,10 +641,3 @@ def delta_sequence( trajectory, depths, start, initial_values, final_projection ) return [approximant.delta(limit) for approximant in approximants] - - def determinant(self, axis: sp.Symbol) -> sp.Expr: - """ - Computes the determinant of the relevant CMF matrix matching 'axis' - """ - # Note: this is a default behavior - it should be overridden in the subclasses - return sp.det(self.matrices[axis]) diff --git a/ramanujantools/cmf/meijer_g.py b/ramanujantools/cmf/meijer_g.py index 159cf52..d54c5c4 100644 --- a/ramanujantools/cmf/meijer_g.py +++ b/ramanujantools/cmf/meijer_g.py @@ -41,7 +41,16 @@ def __getstate__(self): return state def __setstate__(self, state): - self.__dict__.update(state) + self.m = state['m'] + self.n = state['n'] + self.p = state['p'] + self.q = state['q'] + self.z = state['z'] + state.remove('m') + state.remove('n') + state.remove('p') + state.remove('q') + state.remove('z') super().__setstate__(state) @staticmethod diff --git a/ramanujantools/cmf/meijer_g_test.py b/ramanujantools/cmf/meijer_g_test.py index 2467381..9f58a1b 100644 --- a/ramanujantools/cmf/meijer_g_test.py +++ b/ramanujantools/cmf/meijer_g_test.py @@ -32,9 +32,9 @@ def test_gamma(): def test_serialization(): - original_pfq = MeijerG(p=3, n=2, q=1, m=1, z=-1) + original_meijer_g = MeijerG(p=3, n=2, q=1, m=1, z=-1) - serialized_data = pickle.dumps(original_pfq) + serialized_data = pickle.dumps(original_meijer_g) unpickled_pfq = pickle.loads(serialized_data) assert isinstance(unpickled_pfq, MeijerG), "Object type mismatch after unpickling." @@ -43,8 +43,8 @@ def test_serialization(): assert hasattr(unpickled_pfq, 'm'), 'expected to have attribute m' assert hasattr(unpickled_pfq, 'n'), 'expected to have attribute n' assert hasattr(unpickled_pfq, 'z'), 'expected to have attribute z' - assert unpickled_pfq.p == original_pfq.p, f"p mismatch: {unpickled_pfq.p} != {original_pfq.p}" - assert unpickled_pfq.q == original_pfq.q, f"q mismatch: {unpickled_pfq.q} != {original_pfq.q}" - assert unpickled_pfq.m == original_pfq.m, f"p mismatch: {unpickled_pfq.m} != {original_pfq.m}" - assert unpickled_pfq.n == original_pfq.n, f"p mismatch: {unpickled_pfq.n} != {original_pfq.n}" - assert unpickled_pfq.z == original_pfq.z, f"z mismatch: {unpickled_pfq.z} != {original_pfq.z}" + assert unpickled_pfq.p == original_meijer_g.p, f"p mismatch: {unpickled_pfq.p} != {original_meijer_g.p}" + assert unpickled_pfq.q == original_meijer_g.q, f"q mismatch: {unpickled_pfq.q} != {original_meijer_g.q}" + assert unpickled_pfq.m == original_meijer_g.m, f"m mismatch: {unpickled_pfq.m} != {original_meijer_g.m}" + assert unpickled_pfq.n == original_meijer_g.n, f"n mismatch: {unpickled_pfq.n} != {original_meijer_g.n}" + assert unpickled_pfq.z == original_meijer_g.z, f"z mismatch: {unpickled_pfq.z} != {original_meijer_g.z}" diff --git a/ramanujantools/cmf/pfq.py b/ramanujantools/cmf/pfq.py index d841d24..93c7ebf 100644 --- a/ramanujantools/cmf/pfq.py +++ b/ramanujantools/cmf/pfq.py @@ -45,7 +45,12 @@ def __getstate__(self): return state def __setstate__(self, state): - self.__dict__.update(state) + self.p = state['p'] + self.q = state['q'] + self.z = state['z'] + state.remove('p') + state.remove('q') + state.remove('z') super().__setstate__(state) @staticmethod @@ -184,7 +189,8 @@ def evaluate( m = pFq.contiguous_relation((a_values, b_values), (a_anchor, b_anchor), z_eval) return (vector * m)[0] - def determinant(self, axis: sp.Symbol): + @staticmethod + def determinant(p: int, q: int, z: sp.Expr, axis: sp.Symbol): """ Returns the determinant of an axis (basis) matrix in factored form via a hardcoded formula, for quick performance. @@ -243,7 +249,6 @@ def determinant(self, axis: sp.Symbol): $$(-1)^{q+1} \\frac{-(y_r)^{q+1}+(y_r)^{q-1}((\\sum_{i=0}^{q-1}y_i + \\sum x_i ) +y_r-q+1)} {\\prod_{i=0}^{p-1}(-y_r+x_i)}$$ """ - p, q, z = self.p, self.q, self.z is_y = axis.name.startswith("y") x_axes = pFq.x_axes(p) y_axes = pFq.y_axes(q) From 1c81af96bb880dcbda0f9cf8fa4c737b49dab5b0 Mon Sep 17 00:00:00 2001 From: UriKH Date: Wed, 25 Mar 2026 12:48:31 +0200 Subject: [PATCH 6/8] fix pFq test according to PR notes --- ramanujantools/cmf/pfq_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ramanujantools/cmf/pfq_test.py b/ramanujantools/cmf/pfq_test.py index 9534331..512ad2a 100644 --- a/ramanujantools/cmf/pfq_test.py +++ b/ramanujantools/cmf/pfq_test.py @@ -246,7 +246,7 @@ def test_determinant_against_char_poly(p, q, z, axis): def test_hardcoded_determinant_formula(p, q, z, axis): """Tests the high-performance pFq.determinant against the char_poly method.""" det = pFq_determinant_from_char_poly(p, q, z, axis) - calc = pFq(p, q, z).determinant(axis) + calc = pFq.determinant(p, q, z, axis) assert calc == det From d7777ff78f98d84f161674bf2434f5ab458e425a Mon Sep 17 00:00:00 2001 From: UriKH Date: Wed, 25 Mar 2026 12:54:30 +0200 Subject: [PATCH 7/8] correct state management pFq and MeijerG --- ramanujantools/cmf/meijer_g.py | 10 +++++----- ramanujantools/cmf/pfq.py | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ramanujantools/cmf/meijer_g.py b/ramanujantools/cmf/meijer_g.py index d54c5c4..e009c3b 100644 --- a/ramanujantools/cmf/meijer_g.py +++ b/ramanujantools/cmf/meijer_g.py @@ -46,11 +46,11 @@ def __setstate__(self, state): self.p = state['p'] self.q = state['q'] self.z = state['z'] - state.remove('m') - state.remove('n') - state.remove('p') - state.remove('q') - state.remove('z') + del state['m'] + del state['n'] + del state['p'] + del state['q'] + del state['z'] super().__setstate__(state) @staticmethod diff --git a/ramanujantools/cmf/pfq.py b/ramanujantools/cmf/pfq.py index 93c7ebf..fc608ee 100644 --- a/ramanujantools/cmf/pfq.py +++ b/ramanujantools/cmf/pfq.py @@ -48,9 +48,9 @@ def __setstate__(self, state): self.p = state['p'] self.q = state['q'] self.z = state['z'] - state.remove('p') - state.remove('q') - state.remove('z') + del state['p'] + del state['q'] + del state['z'] super().__setstate__(state) @staticmethod From adec26ea509175bbce4eab2086b192096098ce97 Mon Sep 17 00:00:00 2001 From: UriKH Date: Wed, 25 Mar 2026 12:59:27 +0200 Subject: [PATCH 8/8] renaming meijerG tests --- ramanujantools/cmf/meijer_g_test.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/ramanujantools/cmf/meijer_g_test.py b/ramanujantools/cmf/meijer_g_test.py index 9f58a1b..f1339ab 100644 --- a/ramanujantools/cmf/meijer_g_test.py +++ b/ramanujantools/cmf/meijer_g_test.py @@ -35,16 +35,16 @@ def test_serialization(): original_meijer_g = MeijerG(p=3, n=2, q=1, m=1, z=-1) serialized_data = pickle.dumps(original_meijer_g) - unpickled_pfq = pickle.loads(serialized_data) - - assert isinstance(unpickled_pfq, MeijerG), "Object type mismatch after unpickling." - assert hasattr(unpickled_pfq, 'p'), 'expected to have attribute p' - assert hasattr(unpickled_pfq, 'q'), 'expected to have attribute q' - assert hasattr(unpickled_pfq, 'm'), 'expected to have attribute m' - assert hasattr(unpickled_pfq, 'n'), 'expected to have attribute n' - assert hasattr(unpickled_pfq, 'z'), 'expected to have attribute z' - assert unpickled_pfq.p == original_meijer_g.p, f"p mismatch: {unpickled_pfq.p} != {original_meijer_g.p}" - assert unpickled_pfq.q == original_meijer_g.q, f"q mismatch: {unpickled_pfq.q} != {original_meijer_g.q}" - assert unpickled_pfq.m == original_meijer_g.m, f"m mismatch: {unpickled_pfq.m} != {original_meijer_g.m}" - assert unpickled_pfq.n == original_meijer_g.n, f"n mismatch: {unpickled_pfq.n} != {original_meijer_g.n}" - assert unpickled_pfq.z == original_meijer_g.z, f"z mismatch: {unpickled_pfq.z} != {original_meijer_g.z}" + unpickled_meijer_g = pickle.loads(serialized_data) + + assert isinstance(unpickled_meijer_g, MeijerG), "Object type mismatch after unpickling." + assert hasattr(unpickled_meijer_g, 'p'), 'expected to have attribute p' + assert hasattr(unpickled_meijer_g, 'q'), 'expected to have attribute q' + assert hasattr(unpickled_meijer_g, 'm'), 'expected to have attribute m' + assert hasattr(unpickled_meijer_g, 'n'), 'expected to have attribute n' + assert hasattr(unpickled_meijer_g, 'z'), 'expected to have attribute z' + assert unpickled_meijer_g.p == original_meijer_g.p, f"p mismatch: {unpickled_meijer_g.p} != {original_meijer_g.p}" + assert unpickled_meijer_g.q == original_meijer_g.q, f"q mismatch: {unpickled_meijer_g.q} != {original_meijer_g.q}" + assert unpickled_meijer_g.m == original_meijer_g.m, f"m mismatch: {unpickled_meijer_g.m} != {original_meijer_g.m}" + assert unpickled_meijer_g.n == original_meijer_g.n, f"n mismatch: {unpickled_meijer_g.n} != {original_meijer_g.n}" + assert unpickled_meijer_g.z == original_meijer_g.z, f"z mismatch: {unpickled_meijer_g.z} != {original_meijer_g.z}"