PyPSA icon indicating copy to clipboard operation
PyPSA copied to clipboard

Defining custom components compatible with the Linopy optimization implementation

Open shjam opened this issue 1 year ago • 7 comments

A template for defining new components inside PyPSA

In old versions of PyPSA (before v0.25), there was an example on defining custom components (that was a combined heat and power system link). However, by introduction of the new optimization tool using Linopy, this example became outdated and then removed after v0.25 (although its traces are still out there in the documentation custom-components). Because of PyPSA's broad range of users, it would be beneficial to define a template or a guideline on how to define a custom component which is compatible with the new optimization implementation in PyPSA (optimize instead of lopf)

shjam avatar Mar 29 '24 07:03 shjam

@shjam thanks for pointing out. We should likely re-add the example in a light-weight form as a jupyter notebook.

FabianHofmann avatar Apr 02 '24 05:04 FabianHofmann

+1, Hope to add optimize() custom component docs. I'm currently building a "TwoPortStore" which has an input port and an output port, which can "charge" and "discharge" at same time, still try to figure out how to define in "optimize".

loongmxbt avatar Apr 03 '24 02:04 loongmxbt

@shjam thanks for pointing out. We should likely re-add the example in a light-weight form as a jupyter notebook.

Thank you @FabianHofmann ! Adding the example in Jupyter Notebook would be great. Also, I'm currently exploring solutions. I proposed a potential approach involving the introduction of a new method named add_elements with the Network class (see here). However, I am unsure if it aligns with PyPSA's design principles. I would greatly value any feedback on this idea!

Additionally, built on @loongmxbt 's comment, creating detailed documentation for the OptimizeAccessor class would be valuable. Such documentation could serve as a guideline for expanding PyPSA functionalities!

shjam avatar Apr 08 '24 07:04 shjam

Hi, nice to have the brand new v0.28.0, is there any doc updates on custom components. A small example would be great!

loongmxbt avatar May 24 '24 02:05 loongmxbt

@loongmxbt I agree, I already have a draft, this will be push soon :)

FabianHofmann avatar May 24 '24 05:05 FabianHofmann

@FabianHofmann Hope to give my use case an example :-)

I'm currently building a "TwoPortStore" which has an input port and an output port, which can "charge" and "discharge" at same time.

loongmxbt avatar May 27 '24 11:05 loongmxbt

Hey, this could be a structure on how to implement custom optimization functions (sorry @loongmxbt don't have too much time to look into your specific case).

The following creates a new network class which inherits from pypsa.Network. It allows defining a user_functionality which is automatically considered in the optimization.


def patch_extra_functionality(kwargs: dict) -> dict:
    """
    Patch the extra_functionality argument in kwargs to combine it with the
    existing functionality optionally provided by the user.

    Args:
        kwargs (dict): The keyword arguments dictionary.

    Returns:
        dict: The updated keyword arguments dictionary.
    """

    user_functionality = kwargs.get("extra_functionality")

    def combined_extra_functionalitity(n, sns):
        extra_functionality(n, sns)
        if user_functionality is not None:
            user_functionality(n, sns)

    kwargs["extra_functionality"] = combined_extra_functionalitity
    return kwargs


class OptimizationAccessor(pypsa.optimization.optimize.OptimizationAccessor):

    def __call__(self, *args, **kwargs) -> List[str]:
        kwargs = patch_extra_functionality(kwargs)
        kwargs.setdefault("solver_name", "highs")
        return super().__call__(*args, **kwargs)

    def solve_model(self, *args, **kwargs) -> List[str]:
        kwargs.setdefault("solver_name", "highs")
        return super().solve_model(*args, **kwargs)


class Network(pypsa.Network):

    def __init__(self, *args, **kwargs) -> None:
        override_component_attrs = {
            **get_overrides(),
            **kwargs.get("override_component_attrs", {}),
        }
        kwargs["override_component_attrs"] = override_component_attrs
        super().__init__(*args, **kwargs)

        self.optimize = OptimizationAccessor(self)




FabianHofmann avatar May 27 '24 11:05 FabianHofmann