simpeg / pymatsolver

Solve matrix equations in python.
https://pymatsolver.readthedocs.io/en/latest/
MIT License
33 stars 16 forks source link

instantiate solvers without setting a system matrix? #22

Open lheagy opened 6 years ago

lheagy commented 6 years ago

Motivation

In developing a Simulation class in SimPEG (simpeg/simpeg#672), I want to be able to readily serialize and deserialize an instance of a Simulation, so we are injecting properties throughout, which gives us serialization, deserialization and validation (thanks @fwkoch!!). Right now, the solvers are attached as the simulation as a class, and the solver_opts are attached to the simulation as a dict. We then instantiate a solver as needed in SimPEG.

State the problem

There are a couple potential snags with this approach

Another approach?

We could instantiate the solver with its options and then call it to create Ainv. This would solve the serialization problem and also allow immediate validation of the solver options on its creation.

import pymatsolver
solver = pymatsolver.Pardiso(**opts)
Ainv = solver(A)

so the base class might look like:

class Base(properties.HasProperties):

    check_accuracy = properties.Bool(
        "check the accuracy of the solve?",
        default = False
    )

    accuracy_tol = properties.Float(
        "tolerance on the accuracy of the solver",
        default=1e-6
    )

    def __init__(self, **kwargs):
        super(Base, self).__init__(**kwargs)

    def __call__(self, A):
        self.A = A.tocsr()

There are a couple impacts of this within SimPEG. The one that comes to mind is:

rowanc1 commented 6 years ago

I think the generic serialization of a class is going to be tough. But inside of pymatsolver, this should be easy.

This snippet might help on the SimPEG side of things?

import properties
import pymatsolver
import inspect

class SolverClass(properties.Property):

    def validate(self, instance, value):
        if not inspect.isclass(value):
            self.error(
                instance, value,
                extra="The Solver must be a class."
            )
        if not hasattr(value, "__mul__"):
            self.error(
                instance, value,
                extra="The Solver must have a `__mul__` method."
            )
        return value

    def to_json(self, value, **kwargs):
        cls_name = value.__name__
        if hasattr(pymatsolver, cls_name):
            return {
                "module": "pymatsolver",
                "version": pymatsolver.__version__,
                "class": cls_name
            }
        return value

    def from_json(self, value, **kwargs):
        assert isinstance(value, dict)
        assert "module" in value and value["module"] == "pymatsolver"
        assert "version" in value and value["version"] == pymatsolver.__version__
        if hasattr(pymatsolver, value["class"]):
            return getattr(pymatsolver, value["class"])
        raise ValueError('pymatsolver has no class "{}"'.format(value))

class Simulation(properties.HasProperties):
    Solver = SolverClass("pymatsolver Solver Class")
test = pymatsolver.Pardiso
sim = Simulation(Solver=test)
data = sim.serialize()
print(data)
assert Simulation.deserialize(data).Solver is test
{
    "Solver": {
        "version": "0.1.2",
        "module": "pymatsolver",
        "class": "Pardiso"
    },
    "__class__": "Simulation"
}