Usage Guide
Basic API
The main function is solve_ots(opt_parameters) which takes a single dictionary and returns a results dictionary.
using PowerGridPlanning
results = solve_ots(opt_parameters)Required Parameters
opt_parameters = Dict(
:network => "RTS", # Network name (see Available Data)
:model => "DCOTS", # "DCOTS", "LACOTS", "DCOPF", or "LACOPF"
# DCOTS/LACOTS: wildfire-aware optimal transmission switching
# DCOPF/LACOPF: pure OPF (no wildfire switching);
# investments still apply; objective restricted to "loadshed"/"cost"
:objective => "tradeoff", # "loadshed", "wildfire", "cost", "tradeoff" (DCOPF/LACOPF: "loadshed"/"cost" only)
:times => [(2021, 7, 15)] # Time specification (see below)
)Optional Parameters
# Solution method
:switching_method => "optimal" # "optimal" (MIP) or "thresholded" (heuristic)
# Wildfire data
:risk_per_line => nothing # Custom risk data: Dict{Int => Dict{Int => Float64}}
# day => (line_id => risk_value). Auto-loaded if not provided
:risk_metric => "cum_wfpi" # For CATS: "max_wfpi", "mean_wfpi", "cum_wfpi"
# Temporal parameters
:T => 24 # Hours per day (default: 24)
# Objective-specific parameters
:tradeoff_weight => 0.5 # For "tradeoff": 0=loadshed only, 1=wildfire only
:voll => 10000.0 # Value of Lost Load ($/MWh) for "cost" objective
# Threshold parameters
# - Required for switching_method="thresholded" (determines which lines are pre-de-energized)
# - Optional for switching_method="optimal": adds a risk constraint (energized risk ≤ threshold × total_risk)
# Hardening is credited toward the constraint (hardened lines reduce active risk)
:threshold => nothing # Absolute risk threshold (in risk units)
:threshold_pct => nothing # Percentage threshold (0.8 = keep 80% of risk active, remove 20%)
# Linear-AC warm start (LACOTS / LACOPF)
:warm_start => nothing # Dict from DCOTS/DCOPF results, or "auto" to run the DC counterpart first
:non_linear => false # Use non-linear apparent power constraints
# Solver parameters
:time_limit => 86400.0 # Solver time limit (seconds, default: 24 hours)
:mip_gap => 0.01 # MIP optimality gap (default: 1%)
# Output parameters
:output_format => "dict" # "dict", "jld2", or "txt"
:output_path => nothing # File path (required for jld2/txt formats)
:lp_str => "" # If provided, save model to LP file at this path
:log_str => "" # If provided, save Gurobi log to file at this path
# Auto-plotting (triggered at end of solve_ots)
:plots => false, # false/"none" = no plots; "all" = network_overview + timeseries;
# "inv_only" = network_overview only; "timeseries_only" = timeseries only
:plot_dir => "" # Directory to save plots (default: current directory)
# Created automatically if it does not exist
# Hardening parameters (models vegetation management, covered conductors, or undergrounding)
:hardening_enabled => false # Enable line hardening optimization
:hardening_effectiveness => 1.0 # Risk reduction factor, 0-1 (1.0 = full mitigation)
:hardening_cost_per_mile => 7e6 # Cost per mile in USD (default: $7M)
:hardening_enforce_energization => true # If hardened, must remain energized
:hardening_candidate_lines => nothing # Vector{Int}: specific lines to consider (default: all risky lines)
# Battery energy storage system (BESS) parameters
:battery_enabled => false # Enable battery installation optimization
:battery_cost_per_pu => 1e8 # Cost per p.u. (100MWh) of battery capacity ($)
:battery_charge_efficiency => 0.95 # Charging efficiency (0-1)
:battery_discharge_efficiency => 0.95 # Discharging efficiency (0-1)
:battery_soc_carryover => 0.999958 # SOC decay between hours (~1%/week)
:battery_charge_rate => 1.0 # Max charge rate as fraction of capacity (p.u./hour)
:battery_discharge_rate => 1.0 # Max discharge rate as fraction of capacity (p.u./hour)
:battery_max_network => nothing # Network-wide capacity limit (p.u., default: unlimited)
:battery_max_per_node => nothing # Per-node capacity limit (p.u., default: 10000)
:battery_exclusive_operation => false # Limit simultaneous charge/discharge
:battery_candidate_buses => nothing # Vector{Int}, "load buses", or nothing (all buses)
:linearized_battery_power => true # For LACOTS: linear (true) or nonlinear (false) reactive power
# Solar PV installation parameters
:solar_enabled => false # Enable solar installation optimization
:solar_cost_per_pu => 1e8 # Cost per p.u. (100MW) of solar capacity ($)
:solar_data_path => nothing # Path to CSV with hourly capacity factors
:solar_capacity_factor_default => 0.3 # Default CF if no data provided (0-1)
:solar_max_network => nothing # Network-wide capacity limit (p.u., default: unlimited)
:solar_max_per_node => nothing # Per-node capacity limit (p.u., default: 10000)
:solar_candidate_buses => nothing # Vector{Int} or nothing (all buses)
:linearized_solar_power => true # For LACOTS: linear (true) or nonlinear (false) inverter capability
# Shared infrastructure budget (batteries + solar + hardening)
:infrastructure_budget => nothing # Budget in USD (default: $1B for non-cost objectives, unlimited for cost)AC Verification API
Use verify_ac after a planning solve when you want to check the fixed planning decisions in a nonlinear AC model. Baseline runs are also supported by omitting the planning results argument.
using PowerGridPlanning
# Baseline AC redispatch with no wildfire switching or new infrastructure
baseline_ac = verify_ac(Dict(
:network => "RTS",
:mode => "ACOPF", # "ACPF" or "ACOPF"
:times => [(2020, 6, 15)],
:T => 1,
:data_dir => "test_data",
))
# Verify an existing DCOTS/LACOTS planning result under nonlinear AC equations
planning_results = solve_ots(Dict(
:network => "RTS",
:model => "DCOTS",
:objective => "loadshed",
:times => [(2020, 6, 15)],
:data_dir => "test_data",
:switching_method => "thresholded",
:threshold_pct => 0.75,
))
ac_check = verify_ac(Dict(
:network => "RTS",
:mode => "ACOPF",
:times => [(2020, 6, 15)],
:T => 1,
:data_dir => "test_data",
), planning_results)
println("AC feasible all hours: $(ac_check[:feasible_all])")
println("Failed hours: $(ac_check[:failed_hours])")
println("AC recovery load shed: $(ac_check[:total_p_load_shed])")Required ac_parameters keys:
:network- Network name:times- Same time formats accepted bysolve_ots:mode-"ACPF"for strict replay feasibility or"ACOPF"for AC redispatch/recovery
Optional AC parameters:
:T => 24:data_dir => "data":optimizer => Ipopt.Optimizer:output_format => "dict"("jld2"and"txt"are also supported):output_path => nothing(required for"jld2"or"txt"):load_shed_penalty => 1e6:silent => true
Time Specification Formats
# Single day
:times => [(2021, 7, 15)]
# Multiple specific days
:times => [(2021, 7, 15), (2021, 7, 16), (2021, 7, 17)]
# Full year (all 365/366 days)
:times => "2020"
# Full month
:times => "June 2021"
:times => "Jun 2021"The number of days D is automatically calculated, resulting in D × T time periods.