From 3aa3c2b3ac470078933fa51d450e8d0d5d47f791 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 26 Aug 2026 13:06:34 +0100 Subject: [PATCH] Convert Python objects into strings Makes it much more pleasant to construct preconditioners. --- petsctools/options.py | 6 +++++- tests/docs/test_appctx_docs.py | 2 +- tests/test_options.py | 34 ++++++++++++++++++++++++++++++++-- 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/petsctools/options.py b/petsctools/options.py index 8d49215..5e382eb 100644 --- a/petsctools/options.py +++ b/petsctools/options.py @@ -434,7 +434,11 @@ def __init__(self, parameters: dict[str, Any], # Replace any Python objects in the parameters dict with appctx entries appmngr = AppContextManager() for key, value in parameters.items(): - if not isinstance(value, _native_petsc_option_types): + # Convert Python objects into their string representation for + # things like 'pc_python_type' and 'snes_python_type' + if key.endswith("python_type") and isinstance(value, type): + parameters[key] = f"{value.__module__}.{value.__name__}" + elif not isinstance(value, _native_petsc_option_types): parameters[key] = appmngr.add(value) self.appmngr = appmngr diff --git a/tests/docs/test_appctx_docs.py b/tests/docs/test_appctx_docs.py index 52914ad..b4d270c 100644 --- a/tests/docs/test_appctx_docs.py +++ b/tests/docs/test_appctx_docs.py @@ -104,7 +104,7 @@ def test_appctx_docs(): 'ksp_converged_reason': None, 'ksp_type': 'richardson', 'pc_type': 'python', - 'pc_python_type': f'{__name__}.DiffusionJacobiPC', + 'pc_python_type': DiffusionJacobiPC, 'djacobi_scale': 0.9, 'djacobi_sigma': sigma_p, }, diff --git a/tests/test_options.py b/tests/test_options.py index d21c7a3..1fa3b0a 100644 --- a/tests/test_options.py +++ b/tests/test_options.py @@ -247,7 +247,8 @@ def apply(self, pc, x, y): @pytest.mark.skipnopetsc4py @pytest.mark.parametrize("use_prefix", ["with_prefix", "without_prefix"]) -def test_python_options_ksp(use_prefix): +@pytest.mark.parametrize("use_pc_class", [False, True]) +def test_python_options_ksp(use_prefix, use_pc_class): PETSc = petsctools.init() n = 4 sizes = (n, n) @@ -264,10 +265,14 @@ def test_python_options_ksp(use_prefix): parameters = { 'ksp_type': 'preonly', 'pc_type': 'python', - 'pc_python_type': f'{__name__}.JacobiTestPC', 'jacobi_use_prefixed_options': use_prefix == "with_prefix", 'jacobi_scale': diag, } + if use_pc_class: + parameters['pc_python_type'] = JacobiTestPC + else: + parameters['pc_python_type'] = f'{__name__}.JacobiTestPC' + petsctools.set_from_options( ksp, parameters=parameters, options_prefix="myksp" ) @@ -284,6 +289,31 @@ def test_python_options_ksp(use_prefix): assert (x - xcheck).norm() < 1e-14 +class MyPythonSNES: + pass + + +@pytest.mark.skipnopetsc4py +@pytest.mark.parametrize("use_prefix", + [True, False], + ids=["with_prefix", "without_prefix"]) +def test_python_type_option(use_prefix): + from petsc4py import PETSc + + options = petsctools.OptionsManager( + parameters={ + "snes_type": "python", + "snes_python_type": MyPythonSNES, + }, + options_prefix="prefix_" if use_prefix else None + ) + + with options.inserted_options(): + opts = PETSc.Options(options.options_prefix) + assert opts["snes_python_type"] == f"{__name__}.MyPythonSNES", \ + "Python type name was not inserted into the options dictionary" + + @pytest.mark.skipnopetsc4py @pytest.mark.parametrize("options_prefix", (None, "", "custom_")) def test_commandline_options(caplog, options_prefix):