Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/cross-package-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
julia-version: [1]
julia-version: ['1']
os: [ubuntu-latest]
package_name: [PowerSystemCaseBuilder]
continue-on-error: true
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
with:
# `[sources]` is honored from 1.11 on; on 1.10 the docs env silently ignored the
# OpenAPI pins and fell through to the registry.
version: '1.12'
version: '1'
- name: Install dependencies
run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()'
- name: Build and deploy
Expand Down
9 changes: 2 additions & 7 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,7 @@ PowerOpenAPIModels = "0730f07c-cff6-4c3b-a9df-c546153be50a"
PowerOperationsOpenAPIModels = "a372b6d7-45a2-44c2-8199-6a724b72e8ff"
Unicode = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
[sources]
# LOCAL PATH CO-DEV PINS (temporary): PowerOpenAPIModels split InfrastructureCore/
# InfrastructureTimeSeries out on local disk only; committed git pins below still point at
# `jd/schemas-0.1.0`, which predates the split. Revert to git-url pins (with subdirs matching
# the new package names) once the split is pushed. InfrastructureSystems must also point at
# the local IS4 checkout (not the remote branch) because that checkout already carries this
# same split (uncommitted) and the remote IS4 branch does not yet.
# Branch pins until the OpenAPI packages are registered and the psy6-line branches merge.
InfrastructureSystems = {url = "https://github.com/Sienna-Platform/InfrastructureSystems.jl.git", rev = "IS4"}
InfrastructureCoreOpenAPIModels = {url = "https://github.com/Sienna-Platform/PowerOpenAPIModels.git", rev = "main", subdir = "InfrastructureCoreOpenAPIModels.jl"}
InfrastructureTimeSeriesOpenAPIModels = {url = "https://github.com/Sienna-Platform/PowerOpenAPIModels.git", rev = "main", subdir = "InfrastructureTimeSeriesOpenAPIModels.jl"}
Expand All @@ -42,7 +37,7 @@ InfrastructureSystems = "^3.2"
InfrastructureTimeSeriesOpenAPIModels = "0.1"
JSON = "1"
LinearAlgebra = "1"
OpenAPI = "0.2"
OpenAPI = "1"
PowerCoreOpenAPIModels = "0.1"
PowerDynamicsOpenAPIModels = "0.1"
PowerInvestmentsOpenAPIModels = "0.1"
Expand Down
2 changes: 2 additions & 0 deletions src/PowerFlowFileParser.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ const PC = PowerCoreOpenAPIModels
const PD = PowerOpenAPIModels
const PO = PowerOperationsOpenAPIModels

import OpenAPI.Runtime: Absent, ABSENT

import InfrastructureSystems
const IS = InfrastructureSystems

Expand Down
9 changes: 5 additions & 4 deletions src/openapi/attributes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ function _impedance_correction_curves(data::Dict)
continue
end
curve = IC.PiecewiseLinearData(;
function_type = "PIECEWISE_LINEAR",
points = [IC.XYCoords(; x = x[i], y = y[i]) for i in eachindex(x)],
)
control_mode =
Expand Down Expand Up @@ -67,7 +68,7 @@ function _new_impedance_correction_attribute!(
transformer_id::Int,
)
curve, control_mode = curves[table_number]
attribute = PO.ImpedanceCorrectionData()
attribute = stage(PO.ImpedanceCorrectionData)
set_value!(attribute, :id, next_id!(get_registry(sys)))
set_value!(attribute, :table_number, table_number)
set_value!(attribute, :impedance_correction_curve, curve)
Expand All @@ -89,7 +90,7 @@ shared attribute and multiple associations — see the file header.
"""
function _attach_impedance_correction!(
sys::OpenAPISystem,
cache::Dict{Tuple{Int, String}, PO.ImpedanceCorrectionData},
cache::Dict{Tuple{Int, String}, Staged{PO.ImpedanceCorrectionData}},
curves::Dict{Int, Tuple{IC.PiecewiseLinearData, String}},
d::Dict,
table_key::AbstractString,
Expand Down Expand Up @@ -139,7 +140,7 @@ function read_substations!(sys::OpenAPISystem, data::Dict; kwargs...)
bus_ids = unique(get_bus_id(reg, Int(node["bus"])) for node in nodes)
name = string(d["name"])

attribute = PO.Substation()
attribute = stage(PO.Substation)
set_value!(attribute, :id, register!(reg, "Substation", name))
set_value!(attribute, :name, name)
set_value!(attribute, :number, number)
Expand Down Expand Up @@ -170,7 +171,7 @@ function read_impedance_corrections!(sys::OpenAPISystem, data::Dict; kwargs...)
bus_lookup = _pm_bus_lookup(sys)
_get_branch_name = get(kwargs, :branch_name_formatter, _get_pm_branch_name)
_get_3w_name = get(kwargs, :xfrm_3w_name_formatter, _get_pm_3w_name)
cache = Dict{Tuple{Int, String}, PO.ImpedanceCorrectionData}()
cache = Dict{Tuple{Int, String}, Staged{PO.ImpedanceCorrectionData}}()

for (_, d) in _sorted_pm_entries(get(data, "branch", Dict{String, Any}()))
if !haskey(d, "correction_table")
Expand Down
10 changes: 5 additions & 5 deletions src/openapi/branch.jl
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ function _make_transformer_circuit!(
reactive_power_flow::Real,
)
arc_id = add_arc!(sys, from_id, to_id)
circuit = PO.TransformerCircuit()
circuit = stage(PO.TransformerCircuit)
set_value!(circuit, :id, next_id!(reg))
set_value!(circuit, :available, available)
set_value!(circuit, :arc, arc_id)
Expand Down Expand Up @@ -293,7 +293,7 @@ function make_line!(
available = _branch_available(d["br_status"] == 1, from_isolated, to_isolated)
arc_id = add_arc!(sys, from_id, to_id)

line = PO.Line()
line = stage(PO.Line)
set_value!(line, :id, register!(reg, "Line", name))
set_value!(line, :name, name)
set_value!(line, :available, available)
Expand Down Expand Up @@ -339,7 +339,7 @@ function make_switch_from_zero_impedance_branch!(
end
@warn "Branch $name has zero impedance and available = $available; converting to a DiscreteControlledACBranch of type SWITCH with available = $available and branch_status = $status"

component = PO.DiscreteControlledACBranch()
component = stage(PO.DiscreteControlledACBranch)
set_value!(component, :id, register!(reg, "DiscreteControlledACBranch", name))
set_value!(component, :name, name)
set_value!(component, :available, available)
Expand Down Expand Up @@ -423,7 +423,7 @@ function make_transformer_2w!(
reactive_power_flow = get(d, "qf", 0.0) * base_power,
)

component = PO.TwoWindingTransformer()
component = stage(PO.TwoWindingTransformer)
set_value!(component, :id, register!(reg, "TwoWindingTransformer", name))
set_value!(component, :name, name)
set_value!(component, :circuit, circuit_id)
Expand Down Expand Up @@ -491,7 +491,7 @@ function make_3w_transformer!(
reactive_power_flow = d["reactive_power_flow_tertiary"],
)

component = PO.ThreeWindingTransformer()
component = stage(PO.ThreeWindingTransformer)
set_value!(component, :id, register!(reg, "ThreeWindingTransformer", name))
set_value!(component, :name, name)
set_value!(component, :primary_circuit, primary_circuit)
Expand Down
18 changes: 9 additions & 9 deletions src/openapi/container.jl
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,16 @@ why each component states the convention it was written in.
uses_per_unit(sys::OpenAPISystem) = sys.power_units == "COMPONENT_BASE"

"""
Add `component` to the document, first stamping this run's `power_units` onto it when
Materialize `staged` into the document, first stamping this run's `power_units` onto it when
its PO type declares the field — the per-component wire-contract requirement every
power-bearing type carries (a component with none, e.g. a pure topology row, is
untouched).
"""
function add_component!(sys::OpenAPISystem, component::T) where {T <: OpenAPI.APIModel}
function add_component!(sys::OpenAPISystem, staged::Staged{T}) where {T <: IC.APIModel}
if hasfield(T, :power_units)
setproperty!(component, :power_units, sys.power_units)
set_value!(staged, :power_units, sys.power_units)
end
PD.add_component!(get_document(sys), component)
PD.add_component!(get_document(sys), materialize(staged))
return
end

Expand All @@ -116,10 +116,10 @@ caller need not know it.
"""
function add_supplemental_attribute!(
sys::OpenAPISystem,
attribute::OpenAPI.APIModel,
attribute::Staged,
component_id::Int,
)
PD.add_supplemental_attribute!(get_document(sys), attribute, component_id)
PD.add_supplemental_attribute!(get_document(sys), materialize(attribute), component_id)
return
end

Expand All @@ -139,17 +139,17 @@ disagree.
"""
function add_supplemental_attribute_association!(
sys::OpenAPISystem,
attribute::OpenAPI.APIModel,
attribute::Staged{T},
component_id::Int,
component_type::AbstractString,
)
) where {T}
push!(
get_document(sys).supplemental_attribute_associations,
IC.SupplementalAttributeAssociation(;
component_id = component_id,
component_type = String(component_type),
attribute_id = get_value(attribute, :id),
attribute_type = string(nameof(typeof(attribute))),
attribute_type = string(nameof(T)),
),
)
return
Expand Down
66 changes: 52 additions & 14 deletions src/openapi/cost.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,31 @@
# models already describe a $/hr curve directly (MATPOWER Table B-4), so unlike PTDP's
# table-driven cost.jl there is no fuel-price/heat-rate separation here: a MATPOWER-shaped
# generator cost becomes a `CostCurve`, never a `FuelCurve`.
#
# The schema also gained a level of oneOf wrapping since this file was last written:
# `ThermalGenerationCost`/`HydroGenerationCost.variable_operation_cost` is now
# `ProductionVariableCostCurve` (`Union{CostCurve, FuelCurve}`), and
# `ThermalGenerationCost`/`StorageCost.start_up` is now `ThermalGenerationCostStartUp`/
# `StorageCostStartUp` (`Union{Float64, StartUpStages}`/`Union{Float64,
# StorageCostStartUp2}`) — both plain one-field oneOf wrappers with an auto-generated
# single-arg constructor, so a bare `CostCurve`/`Float64` needs an explicit `T(value)`
# wrap here (this file builds these with a literal kwarg call, not through
# `units.jl`'s `set_value!`, whose `_coerce` would otherwise do this automatically).
# `RenewableGenerationCost`/`LoadCost.variable_operation_cost` stayed plain `CostCurve` —
# not every owner picked up the wrapper, so check each type rather than assuming.
# `CostCurve` itself also gained a `variable_cost_type` discriminator ("COST"), the
# `ProductionVariableCostCurve` counterpart to `FuelCurve`'s own "FUEL".

"""The schema's declared `vom_cost` default for `CostCurve`/`FuelCurve`: a zero
linear input-output curve (Core/common.json defs.CostCurve.properties.vom_cost.default).
`vom_cost` is schema-`required`, so every emitted curve must carry it explicitly —
the generated `CostCurve`/`FuelCurve` constructors default it to `nothing`."""
function _zero_vom_cost()
return PC.InputOutputCurve(;
curve_type = "INPUT_OUTPUT",
function_data = PC.InputOutputCurveFunctionData(
IC.LinearFunctionData(;
function_type = "LINEAR",
proportional_term = 0.0,
constant_term = 0.0,
),
Expand All @@ -23,11 +39,14 @@ end
has no cost data to read from a PowerModels dict."""
function _zero_cost_curve()
return PC.CostCurve(;
power_units = "NATURAL_UNITS",
power_units = IC.UnitSystem("NATURAL_UNITS"),
variable_cost_type = "COST",
value_curve = PC.ValueCurve(
PC.InputOutputCurve(;
curve_type = "INPUT_OUTPUT",
function_data = PC.InputOutputCurveFunctionData(
IC.LinearFunctionData(;
function_type = "LINEAR",
proportional_term = 0.0,
constant_term = 0.0,
),
Expand All @@ -54,7 +73,8 @@ function _piecewise_linear_cost(cost_component::Vector{Float64})
first_slope = (second_y - first_y) / (second_x - first_x)
fixed = max(0.0, first_y - first_slope * first_x)
shifted = [IC.XYCoords(; x = x, y = y - fixed) for (x, y) in points]
return IC.PiecewiseLinearData(; points = shifted), fixed
return IC.PiecewiseLinearData(; function_type = "PIECEWISE_LINEAR", points = shifted),
fixed
end

"""
Expand Down Expand Up @@ -83,6 +103,7 @@ function _polynomial_cost(gen_name::AbstractString, cost_component::Vector{Float
quadratic_term, proportional_term, constant_term =
(get(coeffs, deg, 0.0) for deg in quadratic_degrees)
return IC.QuadraticFunctionData(;
function_type = "QUADRATIC",
quadratic_term = quadratic_term,
proportional_term = proportional_term,
constant_term = constant_term,
Expand All @@ -101,9 +122,10 @@ function make_thermal_cost(gen_name::AbstractString, pm_gen::Dict, sys_mbase::Fl
if !haskey(pm_gen, "model")
@warn "Generator cost data not included for Generator: $gen_name"
return PC.ThermalGenerationCost(;
variable_operation_cost = _zero_cost_curve(),
cost_type = "THERMAL",
variable_operation_cost = PC.ProductionVariableCostCurve(_zero_cost_curve()),
fixed = 0.0,
start_up = 0.0,
start_up = PC.ThermalGenerationCostStartUp(0.0),
shut_down = 0.0,
)
end
Expand All @@ -118,29 +140,45 @@ function make_thermal_cost(gen_name::AbstractString, pm_gen::Dict, sys_mbase::Fl
throw(IS.DataFormatError("$gen_name: unsupported generator cost model=$model"))
end
return PC.ThermalGenerationCost(;
variable_operation_cost = PC.CostCurve(;
power_units = "COMPONENT_BASE",
value_curve = PC.ValueCurve(
PC.InputOutputCurve(;
function_data = PC.InputOutputCurveFunctionData(function_data),
cost_type = "THERMAL",
variable_operation_cost = PC.ProductionVariableCostCurve(
PC.CostCurve(;
power_units = IC.UnitSystem("COMPONENT_BASE"),
variable_cost_type = "COST",
value_curve = PC.ValueCurve(
PC.InputOutputCurve(;
curve_type = "INPUT_OUTPUT",
function_data = PC.InputOutputCurveFunctionData(function_data),
),
),
vom_cost = _zero_vom_cost(),
),
vom_cost = _zero_vom_cost(),
),
fixed = fixed,
start_up = pm_gen["startup"],
start_up = PC.ThermalGenerationCostStartUp(pm_gen["startup"]),
shut_down = pm_gen["shutdown"],
)
end

"""Curtailment cost for a hydro generator: PSCB never derives one from pm data."""
make_hydro_cost() =
PC.HydroGenerationCost(; variable_operation_cost = _zero_cost_curve(), fixed = 0.0)
PC.HydroGenerationCost(;
cost_type = "HYDRO_GEN",
variable_operation_cost = PC.ProductionVariableCostCurve(_zero_cost_curve()),
fixed = 0.0,
)

"""Operating cost for a renewable generator: PSCB never derives one from pm data."""
make_renewable_cost() =
PC.RenewableGenerationCost(; variable_operation_cost = _zero_cost_curve())
PC.RenewableGenerationCost(;
cost_type = "RENEWABLE",
variable_operation_cost = _zero_cost_curve(),
)

"""Operating cost for an interruptible load: PSCB never derives one from pm data."""
make_load_cost() =
PC.LoadCost(; variable_operation_cost = _zero_cost_curve(), fixed = 0.0)
PC.LoadCost(;
cost_type = "LOAD",
variable_operation_cost = _zero_cost_curve(),
fixed = 0.0,
)
Loading
Loading