Developer Documentation

Data Processing Functions

The PowerWaterModels data format allows the user to specify power network data, water network data, and data related to the interdependencies between power and water systems. PowerWaterModels relies on the automated data processing routines of PowerModelsDistribution and WaterModels, which include capabilities for status propagation, nondimensionalization, topology correction, etc. However, these capabilities are typically used on independent infrastructure data, whereas PowerWaterModels must join these data. Thus, in preprocessing routines, it is recommended that capabilities be invoked explictly so that external dependencies are accounted for. For example, the core data parsing function parse_files performs the following operations:

function parse_files(power_path::String, water_path::String, link_path::String)
    joint_network_data = parse_link_file(link_path)
    _IM.update_data!(joint_network_data, parse_power_file(power_path))
    _IM.update_data!(joint_network_data, parse_water_file(water_path))
    correct_network_data!(joint_network_data)

    # Store whether or not each network uses per-unit data.
    p_per_unit = get(joint_network_data["it"][_PMD.pmd_it_name], "per_unit", false)
    w_per_unit = get(joint_network_data["it"][_WM.wm_it_name], "per_unit", false)

    # Make the power and water data sets multinetwork.
    joint_network_data_mn = make_multinetwork(joint_network_data)

    # Prepare and correct pump load linking data.
    assign_pump_loads!(joint_network_data_mn)

    # Modify variable load properties in the power network.
    _modify_loads!(joint_network_data_mn)

    # Return the network dictionary.
    return joint_network_data_mn
end

Here, the parse_power_file and parse_water_file use custom routines to parse and transform the input data, i.e.,

function parse_power_file(file_path::String)
    if split(file_path, ".")[end] == "m" # If reading a MATPOWER file.
        data = _PM.parse_file(file_path)
        _scale_loads!(data, 1.0 / 3.0)
        _PMD.make_multiconductor!(data, 3)
    else
        data = _PMD.parse_file(file_path)
    end

    return _IM.ismultiinfrastructure(data) ? data :
          Dict("multiinfrastructure" => true, "it" => Dict(_PMD.pmd_it_name => data))
end


function parse_water_file(file_path::String; skip_correct::Bool = true)
    data = _WM.parse_file(file_path; skip_correct = skip_correct)
    return _IM.ismultiinfrastructure(data) ? data :
           Dict("multiinfrastructure" => true, "it" => Dict(_WM.wm_it_name => data))
end

After these routines are called, correct_network_data! executes various data and topology correction routines on power, water, and linking data. Then, make_multinetwork ensures that the temporal dimension of each infrastructure and interdependency subdictionary match. Finally, interdependency data are corrected and modified via assign_pump_loads! and _modify_loads! to ensure linking constraints will be modeled appropriately.

Compositional Problems

A best practice is to adopt a compositional approach for building problems in PowerWaterModels, leveraging problem definitions of PowerModelsDistribution and WaterModels. This helps lessen the impact of breaking changes across independent infrastructure packages. For example, the joint optimal power-water flow problem invokes similar problems of PowerModelsDistribution and WaterModels directly with routines like

# Power-only related variables and constraints.
pmd = _get_powermodel_from_powerwatermodel(pwm)
_PMD.build_mn_mc_mld_simple(pmd)

# Water-only related variables and constraints.
wm = _get_watermodel_from_powerwatermodel(pwm)
_WM.build_mn_owf(wm)

# Power-water linking constraints.
build_linking(pwm)

# Add the objective that minimizes power generation costs.
_PMD.objective_mc_min_fuel_cost(pmd)

Compared to the PowerModelsDistribution (_PMD) and WaterModels (_WM) routines, the PowerWaterModels routines only specify interdependency constraints.