Help with implementing SinkDSM

While implementing this task this error keeps appearing:

Traceback (most recent call last):

  File ~\anaconda3\envs\oemof_052_2\lib\site-packages\spyder_kernels\py3compat.py:356 in compat_exec
    exec(code, globals, locals)

  File c:\users\arian\desktop\1________________\versuch_12_.py:33
    from oemof.solph.custom import SinkDSM

ModuleNotFoundError: No module named 'oemof.solph.custom'

Can someone tell me why this is? Thank you!
How can I implement SinkDSM into my model?
Kind regards,
Armend

here is my model:

###########################################################################
# imports
###########################################################################
import pandas as pd
import matplotlib.pyplot as plt

import logging
import os
import pprint as pp

from datetime import datetime
from oemof.tools import logger
from oemof.solph import flows

from oemof.solph import EnergySystem
from oemof.solph import Model
from oemof.solph import buses
from oemof.solph import components as cmp

from oemof.solph import helpers
from oemof.solph import processing
from oemof.solph import views

from oemof.solph.custom import SinkDSM

def main():
    # *************************************************************************
    # ********** PART 1 - Define and optimise the energy system ***************
    # *************************************************************************

    # Read data file

    verbrauchsdaten_mit_pv_file = os.path.join(os.getcwd(), 'verbrauchsdaten_mit_pv.csv')
    strom_co2_preise_file = os.path.join(os.getcwd(), 'strom_co2_preise_täglich.csv')                                         
        
    verbrauchsdaten_mit_pv_data = pd.read_csv(verbrauchsdaten_mit_pv_file)
    strom_co2_preise_data = pd.read_csv(strom_co2_preise_file)
   
    solver = "cbc"  # 'glpk', 'gurobi',....
    debug = False  # Set number_of_timesteps to 3 to get a readable lp-file.
    solver_verbose = False  # show/hide solver output

    # initiate the logger (see the API docs for more information)
    logger.define_logging(
        logfile="oemof_example.log",
        screen_level=logging.INFO,
        file_level=logging.INFO,
    )

    date_time_index = pd.date_range('1/1/2024 00:00:00', periods=336, freq='30min')
    
    # create the energysystem and assign the time index
    energysystem = EnergySystem(
        timeindex=date_time_index, infer_last_interval=False 
    )
    
    ##########################################################################
    # Create oemof objects
    ##########################################################################

    logging.info("Create oemof objects")

    # The bus objects were assigned to variables which makes it easier to
    # connect components to these buses (see below).

    # create electricity bus
    bel = buses.Bus(label="electricity")

    # adding the buses to the energy system
    energysystem.add(bel)

    # create excess component for the electricity bus to allow overproduction
    energysystem.add(
        cmp.Sink(label="excess_bel", 
                 inputs={bel: flows.Flow(variable_costs=(strom_co2_preise_data["Strompreis (€/kWh)"]*-1))},     #strom verkauf = *-1
                 
            )
    )
       
    energysystem.add(
        cmp.Source(
            label="supplier",
            outputs={
                bel: flows.Flow(
                    nominal_value=1,  #  1.000 W = hier 1kwh
                    variable_costs=strom_co2_preise_data["Strompreis (€/kWh)"], 
                    custom_attributes={"emission_factor": 0.20}, 
                )
            },
        )
    )
  
    # create fixed source object representing pv power plants
    energysystem.add(
        cmp.Source(
            label="pv",
            outputs={bel: flows.Flow(fix=verbrauchsdaten_mit_pv_data["pv"], variable_costs=0, nominal_value=1)},
        )                                                              # werte in csv sind in kW also x 1000 = W
    )                                                                      

    # create simple sink object representing the electrical demand 1
    energysystem.add(
        cmp.Sink(
            label="consumer1",
            inputs={bel: flows.Flow(fix=verbrauchsdaten_mit_pv_data["consumer1"], nominal_value=0.001, variable_costs=1, custom_attributes={"emission_factor": 0.60},)},
        )                                                                  # beudeutet wert in csv x 1 W      # Beispiel: Variable Kosten von 0,25 €/kWh
    )

    # create simple sink object representing the electrical demand 2
    energysystem.add(
        cmp.Sink(
            label="consumer2",
            inputs={bel: flows.Flow(fix=verbrauchsdaten_mit_pv_data["consumer2"], nominal_value=0.001, variable_costs=1 ,custom_attributes={"emission_factor": 0.30},)} 
        )                                                                    # beudeutet wert in csv x 1        # Beispiel: Variable Kosten von 0,25 €/kWh
    )

    data_dict_dsm = {
        "demand_el": [3] * len(date_time_index),
        "Cap_up": [0.5] * len(date_time_index),
        "Cap_do": [0.5] * len(date_time_index)
    }
    data_dsm = pd.DataFrame.from_dict(data_dict_dsm)

    demand_dsm = custom.SinkDSM(
        label="DSM",
        inputs={bel: flows.Flow()},
        demand=data_dsm['demand_el'],
        capacity_up=data_dsm["Cap_up"],
        capacity_down=data_dsm["Cap_do"],
        delay_time=6,  # Beispielwert
        max_demand=1,  # Beispielwert
        max_capacity_up=1,  # Beispielwert
        max_capacity_down=1,  # Beispielwert
        approach="DIW",  # Beispielwert
        cost_dsm_down=5  # Beispielwert
    )
    energysystem.add(demand_dsm)    

    # create storage object representing a battery
    storage = cmp.GenericStorage(
        nominal_storage_capacity=25,# 25.000 Wh = 25 kWh oder 10,08 MWh
        label="storage",
        inputs={bel: flows.Flow(nominal_value=25 / 6)},# 10,08 Mwh/6 = 1,68 MW
        outputs={
            bel: flows.Flow(nominal_value=25 / 6, variable_costs=0.001) # kosten von 0.001 pro entladen Wh
        },
        loss_rate=0.00,
        initial_storage_level=0,
        balanced=True,
        inflow_conversion_factor=1,
        outflow_conversion_factor=0.8,
    )

    energysystem.add(storage)

    ##########################################################################
    # Optimise the energy system and plot the results
    ##########################################################################

    logging.info("Optimise the energy system")

    # initialise the operational model
    model = Model(energysystem)
    
    # This is for debugging only. It is not(!) necessary to solve the problem
    # and should be set to False to save time and disc space in normal use. For
    # debugging the timesteps should be set to 3, to increase the readability
    # of the lp-file.
    if debug:
        filename = os.path.join(
            helpers.extend_basic_path("lp_files"), "basic_example.lp"
        )
        logging.info("Store lp-file in {0}.".format(filename))
        model.write(filename, io_options={"symbolic_solver_labels": False})

    # if tee_switch is true solver messages will be displayed
    logging.info("Solve the optimization problem")
    model.solve(solver=solver, solve_kwargs={"tee": solver_verbose})

    logging.info("Store the energy system with the results.")

    # The processing module of the outputlib can be used to extract the results
    # from the model transfer them into a homogeneous structured dictionary.
    
    # add results to the energy system to make it possible to store them.
    energysystem.results["main"] = processing.results(model)
    energysystem.results["meta"] = processing.meta_results(model)

    # The default path is the '.oemof' folder in your $HOME directory.
    # The default filename is 'es_dump.oemof'.
    # You can omit the attributes (as None is the default value) for testing
    # cases. You should use unique names/folders for valuable results to avoid
    # overwriting.

    # store energy system with results
    energysystem.dump(dpath=None, filename=None)
    
    # *************************************************************************
    # ********** PART 2 - Processing the results ******************************
    # *************************************************************************

    logging.info("**** The script can be divided into two parts here.")
    logging.info("Restore the energy system and the results.")
    energysystem = EnergySystem()
    energysystem.restore(dpath=None, filename=None)

    # define an alias for shorter calls below (optional)
    results = energysystem.results["main"]
    storage = energysystem.groups["storage"]

    # print a time slice of the state of charge
    print("")
    print("********* State of Charge (slice) *********")
    print(
        results[(storage, None)]["sequences"][
            datetime(2024, 1, 1, 0, 0, 0) : datetime(2024, 1, 8, 0, 0, 0)
        ]
    )
    print("")

    # get all variables of a specific component/bus
    custom_storage = views.node(results, "storage")
    electricity_bus = views.node(results, "electricity")

    # plot the time series (sequences) of a specific component/bus

    fig, ax = plt.subplots(figsize=(10, 5))
    custom_storage["sequences"].plot(
        ax=ax, kind="line", drawstyle="steps-post"
    )
    plt.legend(
        loc="upper center",
        prop={"size": 8},
        bbox_to_anchor=(0.5, 1.25),
        ncol=2,
    )
    fig.subplots_adjust(top=0.8)
    plt.show()

    fig, ax = plt.subplots(figsize=(10, 5))
    electricity_bus["sequences"].plot(
        ax=ax, kind="line", drawstyle="steps-post"
    )
    plt.legend(
        loc="upper center", prop={"size": 8}, bbox_to_anchor=(0.5, 1.3), ncol=2
    )
    fig.subplots_adjust(top=0.8)
    plt.show()

    # electricity_bus_flows
    
    # Convert the flow results to a DataFrame
    df_electricity_bus = pd.DataFrame(electricity_bus["sequences"])
    
    # Reset the index and rename the column
    df_electricity_bus.reset_index(inplace=True)
    df_electricity_bus.rename(columns={"index": "Timestep"}, inplace=True)
    
    # Save the DataFrame to a CSV file
    df_electricity_bus.to_csv("electricity_bus_flows.csv", index=False)

    # custom_storage_flows
    
    # Convert the flow results to a DataFrame
    df_custom_storage = pd.DataFrame(custom_storage["sequences"])
    
    # Save the DataFrame to a CSV file with the first column named "Timestep"
    df_custom_storage.to_csv("custom_storage_flows.csv", index_label="Timestep")

    # print the solver results
    print("********* Meta results *********")
    pp.pprint(energysystem.results["meta"])
    print("")

    # print the sums of the flows around the electricity bus
    print("********* Main results *********")
    print(electricity_bus["sequences"].sum(axis=0))

    # Ergebnisse in csv-Speichern
    
    # Metaergebnisse in DataFrame konvertieren
    meta_results = energysystem.results["meta"]
    df_meta = pd.DataFrame(meta_results.items(), columns=["Parameter", "Value"])
    
    # CSV-Datei für Metaergebnisse speichern
    df_meta.to_csv("meta_results.csv", index=False)
    
    # Hauptergebnisse in DataFrame konvertieren
    main_results = electricity_bus["sequences"].sum(axis=0)
    df_main = pd.DataFrame({"Time": main_results.index, "Sum of Flows": main_results.values})
    
    # CSV-Datei für Hauptergebnisse speichern
    df_main.to_csv("main_results.csv", index=False)

if __name__ == "__main__":
    main()

Hi @Armend,

thanks for reaching out. In solph v0.5, we replaced the previous keyword “custom” by the keyword “experimental”, which we consider a lot more speaking. (See Changelog of oemof.solph 0.5.0 (oemof-solph.readthedocs.io) for reference.)

Cheers,
Patrik

1 Like

Thank you for the prompt response.

Despite making the change and using “experimental” instead of “custom” as you suggested, I’m still encountering the error. I’ve already made the corresponding adjustment in accordance with the update in solph v0.5, but it seems the issue persists.

Do you have any further suggestions on what I could do to resolve the problem?

runcell(0, 'C:/Users/arian/Desktop/1________________/untitled0.py')
Traceback (most recent call last):

  File ~\anaconda3\envs\oemof_052_2\lib\site-packages\spyder_kernels\py3compat.py:356 in compat_exec
    exec(code, globals, locals)

  File c:\users\arian\desktop\1________________\untitled0.py:31
    from oemof.solph.experimental import SinkDSM  # Corrected import statement

ModuleNotFoundError: No module named 'oemof.solph.experimental'

code:
###########################################################################
# imports
###########################################################################
import pandas as pd
import matplotlib.pyplot as plt

import logging
import os
import pprint as pp

from datetime import datetime
from oemof.tools import logger
from oemof.solph import flows

from oemof.solph import EnergySystem
from oemof.solph import Model
from oemof.solph import buses
from oemof.solph import components as cmp

from oemof.solph import helpers
from oemof.solph import processing
from oemof.solph import views

from oemof.solph.experimental import SinkDSM  # Corrected import statement

def main():
    # *************************************************************************
    # ********** PART 1 - Define and optimise the energy system ***************
    # *************************************************************************

    # Read data file

    verbrauchsdaten_mit_pv_file = os.path.join(os.getcwd(), 'verbrauchsdaten_mit_pv.csv')
    strom_co2_preise_file = os.path.join(os.getcwd(), 'strom_co2_preise_täglich.csv')

    verbrauchsdaten_mit_pv_data = pd.read_csv(verbrauchsdaten_mit_pv_file)
    strom_co2_preise_data = pd.read_csv(strom_co2_preise_file)

    solver = "cbc"  # 'glpk', 'gurobi',....
    debug = False  # Set number_of_timesteps to 3 to get a readable lp-file.
    solver_verbose = False  # show/hide solver output

    # initiate the logger (see the API docs for more information)
    logger.define_logging(
        logfile="oemof_example.log",
        screen_level=logging.INFO,
        file_level=logging.INFO,
    )

    date_time_index = pd.date_range('1/1/2024 00:00:00', periods=336, freq='30min')

    # create the energysystem and assign the time index
    energysystem = EnergySystem(
        timeindex=date_time_index, infer_last_interval=False
    )

    ##########################################################################
    # Create oemof objects
    ##########################################################################

    logging.info("Create oemof objects")

    # The bus objects were assigned to variables which makes it easier to
    # connect components to these buses (see below).

    # create electricity bus
    bel = buses.Bus(label="electricity")

    # adding the buses to the energy system
    energysystem.add(bel)

    # create excess component for the electricity bus to allow overproduction
    energysystem.add(
        cmp.Sink(label="excess_bel",
                 inputs={bel: flows.Flow(variable_costs=(strom_co2_preise_data["Strompreis (€/kWh)"]*-1))},     #strom verkauf = *-1

            )
    )

    energysystem.add(
        cmp.Source(
            label="supplier",
            outputs={
                bel: flows.Flow(
                    nominal_value=1,  #  1.000 W = hier 1kwh
                    variable_costs=strom_co2_preise_data["Strompreis (€/kWh)"],
                    custom_attributes={"emission_factor": 0.20},
                )
            },
        )
    )

    # create fixed source object representing pv power plants
    energysystem.add(
        cmp.Source(
            label="pv",
            outputs={bel: flows.Flow(fix=verbrauchsdaten_mit_pv_data["pv"], variable_costs=0, nominal_value=1)},
        )                                                              # werte in csv sind in kW also x 1000 = W
    )

    # create simple sink object representing the electrical demand 1
    energysystem.add(
        cmp.Sink(
            label="consumer1",
            inputs={bel: flows.Flow(fix=verbrauchsdaten_mit_pv_data["consumer1"], nominal_value=0.001, variable_costs=1, custom_attributes={"emission_factor": 0.60},)},
        )                                                                  # beudeutet wert in csv x 1 W      # Beispiel: Variable Kosten von 0,25 €/kWh
    )

    # create simple sink object representing the electrical demand 2
    energysystem.add(
        cmp.Sink(
            label="consumer2",
            inputs={bel: flows.Flow(fix=verbrauchsdaten_mit_pv_data["consumer2"], nominal_value=0.001, variable_costs=1 ,custom_attributes={"emission_factor": 0.30},)}
        )                                                                    # beudeutet wert in csv x 1        # Beispiel: Variable Kosten von 0,25 €/kWh
    )

    data_dict_dsm = {
        "demand_el": [3] * len(date_time_index),
        "Cap_up": [0.5] * len(date_time_index),
        "Cap_do": [0.5] * len(date_time_index)
    }
    data_dsm = pd.DataFrame.from_dict(data_dict_dsm)

    demand_dsm = SinkDSM(  # Corrected instantiation of SinkDSM
        label="DSM",
        inputs={bel: flows.Flow()},
        demand=data_dsm['demand_el'],
        capacity_up=data_dsm["Cap_up"],
        capacity_down=data_dsm["Cap_do"],
        delay_time=6,  # Beispielwert
        max_demand=1,  # Beispielwert
        max_capacity_up=1,  # Beispielwert
        max_capacity_down=1,  # Beispielwert
        approach="DIW",  # Beispielwert
        cost_dsm_down=5  # Beispielwert
    )
    energysystem.add(demand_dsm)

    # create storage object representing a battery
    storage = cmp.GenericStorage(
        nominal_storage_capacity=25,# 25.000 Wh = 25 kWh oder 10,08 MWh
        label="storage",
        inputs={bel: flows.Flow(nominal_value=25 / 6)},# 10,08 Mwh/6 = 1,68 MW
        outputs={
            bel: flows.Flow(nominal_value=25 / 6, variable_costs=0.001) # kosten von 0.001 pro entladen Wh
        },
        loss_rate=0.00,
        initial_storage_level=0,
        balanced=True,
        inflow_conversion_factor=1,
        outflow_conversion_factor=0.8,
    )

    energysystem.add(storage)

    ##########################################################################
    # Optimise the energy system and plot the results
    ##########################################################################

    logging.info("Optimise the energy system")

    # initialise the operational model
    model = Model(energysystem)

    # This is for debugging only. It is not(!) necessary to solve the problem
    # and should be set to False to save time and disc space in normal use. For
    # debugging the timesteps should be set to 3, to increase the readability
    # of the lp-file.
    if debug:
        filename = os.path.join(
            helpers.extend_basic_path("lp_files"), "basic_example.lp"
        )
        logging.info("Store lp-file in {0}.".format(filename))
        model.write(filename, io_options={"symbolic_solver_labels": False})

    # if tee_switch is true solver messages will be displayed
    logging.info("Solve the optimization problem")
    model.solve(solver=solver, solve_kwargs={"tee": solver_verbose})

    logging.info("Store the energy system with the results.")

    # The processing module of the outputlib can be used to extract the results
    # from the model transfer them into a homogeneous structured dictionary.

    # add results to the energy system to make it possible to store them.
    energysystem.results["main"] = processing.results(model)
    energysystem.results["meta"] = processing.meta_results(model)

    # The default path is the '.oemof' folder in your $HOME directory.
    # The default filename is 'es_dump.oemof'.
    # You can omit the attributes (as None is the default value) for testing
    # cases. You should use unique names/folders for valuable results to avoid
    # overwriting.

    # store energy system with results
    energysystem.dump(dpath=None, filename=None)

    # *************************************************************************
    # ********** PART 2 - Processing the results ******************************
    # *************************************************************************

    logging.info("**** The script can be divided into two parts here.")
    logging.info("Restore the energy system and the results.")
    energysystem = EnergySystem()
    energysystem.restore(dpath=None, filename=None)

    # define an alias for shorter calls below (optional)
    results = energysystem.results["main"]
    storage = energysystem.groups["storage"]

    # print a time slice of the state of charge
    print("")
    print("********* State of Charge (slice) *********")
    print(
        results[(storage, None)]["sequences"][
            datetime(2024, 1, 1, 0, 0, 0) : datetime(2024, 1, 8, 0, 0, 0)
        ]
    )
    print("")

    # get all variables of a specific component/bus
    custom_storage = views.node(results, "storage")
    electricity_bus = views.node(results, "electricity")

    # plot the time series (sequences) of a specific component/bus

    fig, ax = plt.subplots(figsize=(10, 5))
    custom_storage["sequences"].plot(
        ax=ax, kind="line", drawstyle="steps-post"
    )
    plt.legend(
        loc="upper center",
        prop={"size": 8},
        bbox_to_anchor=(0.5, 1.25),
        ncol=2,
    )
    fig.subplots_adjust(top=0.8)
    plt.show()

    fig, ax = plt.subplots(figsize=(10, 5))
    electricity_bus["sequences"].plot(
        ax=ax, kind="line", drawstyle="steps-post"
    )
    plt.legend(
        loc="upper center", prop={"size": 8}, bbox_to_anchor=(0.5, 1.3), ncol=2
    )
    fig.subplots_adjust(top=0.8)
    plt.show()

    # electricity_bus_flows

    # Convert the flow results to a DataFrame
    df_electricity_bus = pd.DataFrame(electricity_bus["sequences"])

    # Reset the index and rename the column
    df_electricity_bus.reset_index(inplace=True)
    df_electricity_bus.rename(columns={"index": "Timestep"}, inplace=True)

    # Save the DataFrame to a CSV file
    df_electricity_bus.to_csv("electricity_bus_flows.csv", index=False)

    # custom_storage_flows

    # Convert the flow results to a DataFrame
    df_custom_storage = pd.DataFrame(custom_storage["sequences"])

    # Save the DataFrame to a CSV file with the first column named "Timestep"
    df_custom_storage.to_csv("custom_storage_flows.csv", index_label="Timestep")

    # print the solver results
    print("********* Meta results *********")
    pp.pprint(energysystem.results["meta"])
    print("")

    # print the sums of the flows around the electricity bus
    print("********* Main results *********")
    print(electricity_bus["sequences"].sum(axis=0))

    # Ergebnisse in csv-Speichern

    # Metaergebnisse in DataFrame konvertieren
    meta_results = energysystem.results["meta"]
    df_meta = pd.DataFrame(meta_results.items(), columns=["Parameter", "Value"])

    # CSV-Datei für Metaergebnisse speichern
    df_meta.to_csv("meta_results.csv", index=False)

    # Hauptergebnisse in DataFrame konvertieren
    main_results = electricity_bus["sequences"].sum(axis=0)
    df_main = pd.DataFrame({"Time": main_results.index, "Sum of Flows": main_results.values})

    # CSV-Datei für Hauptergebnisse speichern
    df_main.to_csv("main_results.csv", index=False)

if __name__ == "__main__":
    main()

I should have read more carefully, it’s oemof.solph.components.experimental.SinkDSM. The submodule (components) needs to be named.