Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Defining custom components compatible with the Linopy optimization implementation #856

Open
shjam opened this issue Mar 29, 2024 · 7 comments

Comments

@shjam
Copy link

shjam commented Mar 29, 2024

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)

@FabianHofmann
Copy link
Collaborator

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

@loongmxbt
Copy link
Contributor

loongmxbt commented Apr 3, 2024

+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".

@shjam
Copy link
Author

shjam commented Apr 8, 2024

@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!

@loongmxbt
Copy link
Contributor

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

@FabianHofmann
Copy link
Collaborator

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

@loongmxbt
Copy link
Contributor

loongmxbt commented May 27, 2024

@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.

@FabianHofmann
Copy link
Collaborator

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)


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants