Giter Club home page Giter Club logo

Comments (4)

FabianHofmann avatar FabianHofmann commented on July 1, 2024 2

Oh, I like this idea! Some thoughts (hopefully later more):

  • I thought about having a Solver class as a skeleton which is then inherited by the individual solvers, like
class Solver:
    def __init__(self, **kwargs):
        ....

    def solve(self):
        raise NotImplementedError

class SolverA(Solver):
    ....
    def solve(self):
        # Implement the solve method for SolverA
        pass

class SolverB(Solver):
    ....
    def solve(self):
        # Implement the solve method for SolverB
        pass
  • I would be in favor of moving the assign_solution to the Model class, following a composition approach (ie the model pull from the solution)

from linopy.

FabianHofmann avatar FabianHofmann commented on July 1, 2024

Hey @dannyopts, you are totally right. I find this is indeed a missing feature in linopy. It would not require too much work, but I am a little bit constrained atm. The solve function would be needed to be subdivided, ideally converted into classes "Solver" which has underlying functions like, "get_solution", "assign_parameters", etc. If you want to propose something, you are more than welcome, otherwise this will lay on my todo list for some time still.

from linopy.

dannyopts avatar dannyopts commented on July 1, 2024

I will take a look and see how I get on

from linopy.

dannyopts avatar dannyopts commented on July 1, 2024

@FabianHofmann I am thinking something like:

We replace the f"run_{solver_name}" function with an abstract Solver class which is implemented for each solver.

Roughly I see this looking something like this:

class Solver:
    """A persistent instance of a solver model which can be updated and resolved
    
    Concrete versions of this class replace f"run_{solver_name}"
    """
    def __init__(self, model, sanitize_zeros=True, problem_fn=None, log_fn=None, **solver_options):
        self.sanitize_zeros = sanitize_zeros
        self.problem_fn = problem_fn
        self.log_fn = log_fn
        self.solver_model = self.create_solver_model(model)
        self.solver_options = solver_options
        
    @staticmethod
    def get_solver(solver_name, **kwargs):
        if solver_name == "highs":
            return HighsSolver(**kwargs)
        if solver_name == "gurobi":
            return GurobiSolver(**kwargs)
        ...
    
    def run(self) -> SolverStatus:
        """Solver the solver_model"""
        raise NotImplementedError()
    
    def compute_IIS(self):
        raise NotImplementedError()
    
    def update_cost_coeffs(self, c) -> None:
        """Set the cost vector on the solver_model"""
        raise NotImplementedError()
    
    def update_constraint(self, constraint_name: str, values: xr.DataArray) -> None:
        """Set the cost vector on the solver_model"""
        pass
    
    def update_rhs(self, b) -> None:
        """Set the b vector"""
        pass
    
    def get_result(self) -> Result: 
        pass

    def close(self) -> None:
        """Free any resources (eg release the license if using gurobi)""""
        pass

(possibly making it a context manager would be a nice touch?)

In the result class we would then add the responsibility of writing the result back to the model:

@dataclass
class Result:
    ...
       
    def update_model(self, model):
        model.objective._value = self.solution.objective
        model.status = self.status.status.value
        model.termination_condition = self.status.termination_condition.value
        model.solver_model = self.solver_model

        if not self.status.is_ok:
            return
        self.assign_solution(self, model)
        self.assign_dual(self, model)

    def assign_solution(self, model):
        # map solution and dual to original shape which includes missing values
        sol = self.solution.primal.copy()
        sol.loc[-1] = nan

        for name, var in model.variables.items():
            idx = np.ravel(var.labels)
            try:
                vals = sol[idx].values.reshape(var.labels.shape)
            except KeyError:
                vals = sol.reindex(idx).values.reshape(var.labels.shape)
            var.solution = xr.DataArray(vals, var.coords)

    def assign_dual(self, model):
        # map solution and dual to original shape which includes missing values
        if not self.solution.dual.empty:
            dual = self.solution.dual.copy()
            dual.loc[-1] = nan

            for name, con in model.constraints.items():
                idx = np.ravel(con.labels)
                try:
                    vals = dual[idx].values.reshape(con.labels.shape)
                except KeyError:
                    vals = dual.reindex(idx).values.reshape(con.labels.shape)
                con.dual = xr.DataArray(vals, con.labels.coords)

And in the Model class solve would become something like:

def solve(
        self,
        ...
    ):
        if remote:
            ...
            return self.status, self.termination_condition

        solver = Solver.get_solver(
            solver_name=solver_name, 
            io_api=io_api, 
            env=env, 
            sanitize_zeros=sanitize_zeros, 
            problem_fn=problem_fn,
            solution_fn=solution_fn,
            log_fn=log_fn,
            basis_fn=basis_fn,
            warmstart_fn=warmstart_fn,
            **solver_options
        )
        with solver:
          result = solver.run()
          result.info()
          
          result.update_model(self)
          
        return result

But a user could also create a Solver instance outside of solve and have a persistent handle to the same underlying model instance and update at their leisure.

Note: this would also resolve the issue which was mentioned in #192 about using a remote gurobi server and wanting to compute an IIS.

Does this sound like a reasonable path forward @FabianHofmann? Any feedback?

from linopy.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.