diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index a691cba5..df96ac07 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -13,12 +13,8 @@ jobs: steps: - uses: actions/checkout@v2 - uses: julia-actions/julia-buildpkg@latest - - uses: actions/setup-node@v1 - with: - node-version: '16.x' - - name: Install @adobe/jsonschema2md - run: npm install -g @adobe/jsonschema2md@7.0.0 - uses: julia-actions/julia-docdeploy@latest env: + PYTHON: "" GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ba616c6..b5bad368 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,74 @@ - none +## v3.0.0 + +- Added documentation for GraphML export +- Updated process flow diagram for ONM +- Added helper functions to set options and get options from the different data structures used by ONM +- Explicitly exported a number of AbstractUnbalancedPowerModels from PowerModelsDistribution, for better user experience +- Switched to `import LongName as LN` pattern +- Updated `"iterative"` to `"rolling-horizon"` and `"global"` to `"full-lookahead"` (**breaking**) +- Deprecated many runtime arguments in favor of settings schema +- Updated default logger settings +- Added `build_settings_new` functions to match updated schema +- Refactored to use schemas directly to build Julia data structures, to make API maintanence easier +- Added `prepare_data!` function to quickly build the multinetwork `network` data from `network`, `settings` and `events` files +- Refactored settings functions to apply settings to base_network and then rebuild the multinetwork structure (**breaking**) +- Refactored settings schemas to allow for more options for user control of different parts of the entrypoint function (**breaking**) +- Added more documentation for new users to the examples folder, including use cases, basic usage of the Block-MLD problem, and how to build a JuMP model by hand +- Added support for exporting network data as a graph in the GraphML format +- Added EzXML as a dependency to support GraphML export +- Removed ProgressMeter dependency +- Added support for JuMP v1 +- Added `transform_data_model` specific to ONM +- Added `instantiate_onm_model`, an ONM-specific version of `instantiate_mc_model` from PowerModelsDistribution +- Added `dss` settings schema for easier adding of inverter property by source id, e.g., "vsource.source", etc. +- Added `constraint_disable_networking` based on coloring model to enabled microgrids to expand but not network +- Updated events schema to allow for typical string values for certain switch fields +- Added disable-networking option to CLI for future implementation of feature +- Changed objective function term balances to ensure that restoring load is always the most critical term +- Fixed bug in `_prepare_fault_study_multinetwork_data` where `va` was not being used +- Added option to `get_timestep_fault_currents` to filter out switches from outputs that have no associated protection devices (i.e., relay, recloser, fuse) +- Changed instances of `Int64` to `Int` +- Fixed issue with transformer control constraints in dispatch optimization +- Added support for [JuMP](https://jump.dev) v0.23 +- Changed built-in mip solver from [Cbc](https://github.com/jump-dev/Cbc.jl) to [HiGHS](https://github.com/jump-dev/HiGHS.jl) +- Added user option to disable presolvers in the built-in solvers `disable_presolver` +- Changed `@warn` to `@info` in `_find_switch_id_from_source_id` +- Updated radial topology constraint in `constraint_radial_topology` to be switch-direction-agnostic (previously required a strongly connected directed graph) +- Added constraint for reference buses that uses `inverter` state to set theta constraints, `constraint_mc_inverter_theta_ref` +- Added phase unbalance constraint for grid-following storage `constraint_mc_storage_phase_unbalance_grid_following` +- Added inverters to `_prepare_fault_study_multinetwork_data` and `_prepare_dispatch_data` +- Added user option to disable inverter constraint: `disable_inverter_constraint` +- Added constraint for identifying a single grid-forming inverter per connected component `constraint_grid_forming_inverter_per_cc_{block|traditional}` +- Added `get_timestep_inverter_states!` which adds inverter states to the `"Powerflow output"` +- Added `solution_inverter!`, which converts `inverter` variable value to `Inverter` enum +- Added `Inverter`, with `GRID_FOLLOWING` and `GRID_FORMING` enums to indicate what generation object is acting as grid-forming or following +- Fixed `constraint_mc_power_balance_shed_block`, wrong call to `PMD.diag`, should have been `LinearAlgebra.diag` +- Added `cost_pg_parameters` and `cost_pg_model` to settings schemas for generators, voltage sources, and storage and solar devices +- Added `opt-switch-problem` flag to runtime input to enable section of `block` or `traditional` optimal switching problems +- Removed `SwitchModel` types to realign software design with InfrastructureModels (**breaking**) +- Refactored problems to better delineate mld code from PMD (**breaking**) +- Added `traditional` mld problem +- Renamed problems, objective functions, and constraint functions to be more simple for users (**breaking**) +- Added solution processor function `solution_statuses!` to assist in converting solution statuses to `Status` enums +- Fixed `_prepare_dispatch_data` to account for new `traditional` mld problem type +- Disabled _indicator_ constraints (**breaking**) +- Fixed bug in `get_timestep_microgrid_networks` +- Introduced `block` and `traditional` versions of constraints to account for different `z` indicator variables (**breaking**) +- Renamed `constraint_switch_state_max_actions` to `constraint_switch_close_action_limit` to better reflect the nature of the constraint (**breaking**) +- Renamed `variable_mc_block_indicator` to `variable_block_indicator`, since it was not a multiconductor variable (**breaking**) +- Renamed `variable_mc_switch_state` to `variable_switch_state`, since it was not a multiconductor variable (**breaking**) +- Refactored `variable_mc_switch_fixed` to be called from inside `variable_switch_state` directly (**breaking**) +- Added `variable_mc_storage_power_mi_on_off`, which will not attempt to make its own `z_storage` indicator variable as in PowerModelsDistribution +- Added "traditional" indicator variable functions, `variable_bus_voltage_indicator`, `variable_generator_indicator`, `variable_storage_indicator`, and `variable_load_indicator` +- Fixed bug in `solution_reference_buses!` +- Added `solve_onm_model` +- Updated README +- Updated documentation +- Updated examples + ## v2.1.2 - Fixed documentation build process @@ -88,7 +156,7 @@ - Added `mn_opf_oltc_capc` problem for dispatch step - Added `variable_mc_storage_indicator` due to overlap of `z_storage` with `z_block` - Added LinearAlgebra (stdlib) dependency -- Fixed order of parsing in `entrypoint` (events should go *after* settings) +- Fixed order of parsing in `entrypoint` (events should go _after_ settings) - Add support for `null` values in settings schema, and new objects / fields - Updated power variables to be `bounded=false` in the switching problem, and use ampacity constraints only instead - Updated `sbase_default` to `1e3` to avoid convergence issues diff --git a/Makefile b/Makefile deleted file mode 100644 index c2b386f6..00000000 --- a/Makefile +++ /dev/null @@ -1,26 +0,0 @@ -.PHONY: test build build-docker test-docker - -TAG = latest - -# build docs -docs: - julia --project=docs -e 'using Pkg; Pkg.develop(path=".")' && julia --project=docs make.jl && julia --project=docs -e 'using Pkg; Pkg.rm("PowerModelsONM")' - -# build docs without building the Pluto notebooks -docs-fast: - julia --project=docs -e 'using Pkg; Pkg.develop(path=".")' && julia --project=docs make.jl --fast && julia --project=docs -e 'using Pkg; Pkg.rm("PowerModelsONM")' - -# build docker container -build-container: - docker build -f Dockerfile -t powermodelsonm:${TAG} ${CURDIR} - -# build binary -build-binary: - julia -q --project=. -e 'using PackageCompiler; create_app(".", "build"; force=true);' - -# test docker container -test-docker: - docker run PowerModelsONM:latest --verbose -n "test/data/ieee13_feeder.dss" -e "test/data/ieee13_events.json" -o "test_output_docker.json" - -test: - julia --project=. -e 'using Pkg; Pkg.test()' diff --git a/Project.toml b/Project.toml index 6aa568a3..ac43b076 100644 --- a/Project.toml +++ b/Project.toml @@ -1,14 +1,15 @@ name = "PowerModelsONM" uuid = "25264005-a304-4053-a338-565045d392ac" authors = ["David M Fobes "] -version = "2.1.2" +version = "3.0.0" [deps] ArgParse = "c7e460c6-2fb9-53a9-8c5b-16f535851c63" -Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" +EzXML = "8f5d6c58-4d21-5cfd-889c-e3ad7ee6a615" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" +HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" Hwloc = "0e44f5e4-bd66-52a0-8798-143a42290a1d" InfrastructureModels = "2030c09a-7f63-5d83-885d-db604e0e9cc0" Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9" @@ -19,41 +20,41 @@ Juniper = "2ddba703-00a4-53a7-87a5-e8b9971dde84" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" LoggingExtras = "e6f89c97-d47a-5376-807f-9c37f3926c36" -PackageCompiler = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" PolyhedralRelaxations = "2e741578-48fa-11ea-2d62-b52c946f73a0" PowerModelsDistribution = "d7431456-977f-11e9-2de3-97ff7677985e" PowerModelsProtection = "719c1aef-945b-435a-a240-4c2992e5e0df" PowerModelsStability = "f9e4c324-c3b6-4bca-9c3d-419775f0bd17" -ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca" Requires = "ae029012-a4dd-5104-9daa-d747884805df" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" [compat] -ArgParse = "~1.1" -Cbc = "~0.9" -Graphs = "~1.4.1" -Hwloc = "~2.0" -InfrastructureModels = "~0.7" -Ipopt = "~0.9" -JSON = "~0.21" -JSONSchema = "~0.3.3" -JuMP = "~0.22" -Juniper = "~0.8" -LoggingExtras = "~0.4.7" -PackageCompiler = "~1.2.6" -PolyhedralRelaxations = "~0.3.2" -PowerModelsDistribution = "~0.14.1" -PowerModelsProtection = "~0.5" -PowerModelsStability = "~0.3.0" -ProgressMeter = "~1.7.1" -Requires = "~1.1.3" -julia = "^1.6" +ArgParse = "1.1" +EzXML = "1.1.0" +Graphs = "1.4.1, 1.6, 1.7" +HiGHS = "1.1.3" +Hwloc = "2" +InfrastructureModels = "0.7" +Ipopt = "0.9, 1.0.2" +JSON = "0.21" +JSONSchema = "0.3.3, 1" +JuMP = "0.22, 0.23, 1" +Juniper = "0.8, 0.9" +LoggingExtras = "0.4.7, 0.4.9" +PolyhedralRelaxations = "0.3.3, 0.3.4" +PowerModelsDistribution = "0.14.4" +PowerModelsProtection = "0.5.2" +PowerModelsStability = "0.3.2" +Requires = "1.1.3, 1.3" +julia = "1.6" [extras] +HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" +Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +Juniper = "2ddba703-00a4-53a7-87a5-e8b9971dde84" PowerModelsDistribution = "d7431456-977f-11e9-2de3-97ff7677985e" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Test", "JSON", "PowerModelsDistribution"] +test = ["Test", "HiGHS", "Ipopt", "JSON", "Juniper", "PowerModelsDistribution"] diff --git a/README.md b/README.md index a9df1be3..6a594cb9 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # PowerModelsONM -_An Optmization library for the operation and restoration of electric power distribution feeders featuring networked microgrids_ +An Optmization library for the operation and restoration of electric power distribution feeders featuring networked microgrids -| __Documentation__ | __Build Status__ | -| :-----------------------------------------------------------------------: | :---------------------------------------------------------------------------: | -| [![][docs-stable-img]][docs-stable-url] [![][docs-dev-img]][docs-dev-url] | [![][github-actions-img]][github-actions-url] [![][codecov-img]][codecov-url] | +| **Documentation** | **Build Status** | +| :------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------: | +| [![docs-stable][docs-stable-img]][docs-stable-url] [![docs-dev][docs-dev-img]][docs-dev-url] | [![github-actions][github-actions-img]][github-actions-url] [![codecov][codecov-img]][codecov-url] | This package combines various packages in the [InfrastructureModels.jl](https://github.com/lanl-ansi/InfrastructureModels.jl) optimization library ecosystem, particularly those related to electric power distribution. @@ -14,7 +14,7 @@ PowerModelsONM focuses on optimizing the operations and restoration of phase unb To install PowerModelsONM, use the built-in Julia package manager -``` +```julia pkg> add PowerModelsONM ``` @@ -44,16 +44,12 @@ This code is provided under a BSD license as part of the Multi-Infrastructure Co [docs-dev-img]: https://github.com/lanl-ansi/PowerModelsONM.jl/workflows/Documentation/badge.svg [docs-dev-url]: https://lanl-ansi.github.io/PowerModelsONM.jl/dev - [docs-stable-img]: https://github.com/lanl-ansi/PowerModelsONM.jl/workflows/Documentation/badge.svg [docs-stable-url]: https://lanl-ansi.github.io/PowerModelsONM.jl/stable - [github-actions-img]: https://github.com/lanl-ansi/PowerModelsONM.jl/workflows/CI/badge.svg [github-actions-url]: https://github.com/lanl-ansi/PowerModelsONM.jl/actions/workflows/ci.yml - [codecov-img]: https://codecov.io/gh/lanl-ansi/PowerModelsONM.jl/branch/main/graph/badge.svg [codecov-url]: https://codecov.io/gh/lanl-ansi/PowerModelsONM.jl - [contrib-url]: https://lanl-ansi.github.io/PowerModelsONM.jl/stable/developer/contributing.html [discussions-url]: https://github.com/lanl-ansi/PowerModelsONM.jl/discussions [issues-url]: https://github.com/lanl-ansi/PowerModelsONM.jl/issues diff --git a/docs/.gitignore b/docs/.gitignore index b298683a..a303fff2 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1,3 +1,2 @@ build/ site/ -src/schemas \ No newline at end of file diff --git a/docs/Project.toml b/docs/Project.toml index e7bf5b47..24d74354 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,11 +1,13 @@ [deps] +Conda = "8f4d0f93-b110-5947-807f-2305c1781a2d" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" Gumbo = "708ec375-b3d6-5a57-a7ce-8257bf98657a" -NodeJS = "2bd173c7-0d6d-553b-b6af-13a54713934c" Pluto = "c3e4b0f8-55cb-11ea-2926-15256bba5781" +PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0" [compat] +Conda = "1.7" Documenter = "0.27" Gumbo = "0.8" -NodeJS = "1.3" Pluto = "0.19" +PyCall = "1.93.1" diff --git a/docs/make.jl b/docs/make.jl index bfb8c744..f2f47f62 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,9 +1,6 @@ using Documenter using PowerModelsONM -# imports to build schema documentation -import NodeJS - # imports to build pluto notebooks import Pluto import Gumbo @@ -25,21 +22,35 @@ else end # Pages of the documentation + +schema_pages = [ + "output.schema" => "schemas/output.schema.md", + "settings.schema" => "schemas/input-settings.schema.md", + "events.schema" => "schemas/input-events.schema.md", + "faults.schema" => "schemas/input-faults.schema.md", + "runtime-arguments.schema" => "schemas/input-runtime_arguments.schema.md", +] + pages = [ "Introduction" => "index.md", "installation.md", "Manual" => [ "Getting Started" => "manual/quickguide.md", "The ONM Workflow" => "manual/onm_workflow.md", - "Optimal Switch / Load shed Mathematical Model" => "manual/osw_mld_model.md", + "Optimal Switch / Load shed Mathematical Model" => "manual/mld_model.md", "Optimal Dispatch Mathematical Model" => "manual/opf_model.md", + "Exporting with GraphML" => "manual/graphml_export.md", ], "Tutorials" => [ "Beginners Guide" => "tutorials/Beginners Guide.md", + "Block MLD Basic Example" => "tutorials/Block MLD Basic Example.md", + "JuMP Model by Hand - MLD-Block Example" => "tutorials/JuMP Model by Hand - MLD-Block.md", + "Use Case Examples" => "tutorials/Use Case Examples.md", ], "API Reference" => [ "Base functions" => "reference/base.md", "Data Handling" => "reference/data.md", + "GraphML Functions" => "reference/graphml.md", "Main Entrypoint" => "reference/entrypoint.md", "Internal Functions" => "reference/internal.md", "IO Functions" => "reference/io.md", @@ -49,6 +60,7 @@ pages = [ "Variables and Constraints" => "reference/variable_constraint.md", "Types" => "reference/types.md", ], + "Schemas" => schema_pages, "Developer Docs" => [ "Contributing Guide" => "developer/contributing.md", "Style Guide" => "developer/style.md", @@ -56,62 +68,63 @@ pages = [ ], ] +# build documents +makedocs( + format = format, + strict=false, + sitename = "PowerModelsONM", + authors = "David M Fobes and contributors", + pages = pages +) + + # Build schema documentation try - path_of_jsonschema2md = "jsonschema2md" - try - jsonschema2md_version = chomp(read(`$(path_of_jsonschema2md) --version`, String)) - @assert "7.0.0" == jsonschema2md_version - catch - install_jsonschema2md_status = chomp(read(`$(NodeJS.npm_cmd()) install -g @adobe/jsonschema2md@7.0.0`, String)) - path_of_jsonschema2md = split(split(install_jsonschema2md_status, "\n")[1], " -> ")[1] - end + # imports to build schema documentation + import PyCall + import Conda + + Conda.pip_interop(true) + Conda.pip("install", "json-schema-for-humans") + jsfhgc = PyCall.pyimport("json_schema_for_humans.generation_configuration") + jsfhg = PyCall.pyimport("json_schema_for_humans.generate") schemas_in_dir = joinpath(dirname(pathof(PowerModelsONM)), "..", "schemas") - schemas_out_dir = joinpath(dirname(pathof(PowerModelsONM)), "..", "docs", "src", "schemas") + schemas_out_dir = joinpath(dirname(pathof(PowerModelsONM)), "..", "docs", "build", "schemas") mkpath(schemas_out_dir) - run_jsonschema2md_status = chomp(read(`$(path_of_jsonschema2md) -d $(schemas_in_dir) -o $(schemas_out_dir) -x - -n`, String)) - - schema_basenames = [split(file, ".")[1] for file in readdir(schemas_in_dir) if endswith(file, "schema.json")] - schema_files = collect(readdir(schemas_out_dir)) + schema_files = replace.(basename.([x.second for x in schema_pages]), ".md"=>".json") for file in schema_files - doc = open(joinpath(schemas_out_dir, file), "r") do io - replace( - replace( - replace( - read(io,String), - "../../../schemas/" => "https://raw.githubusercontent.com/lanl-ansi/PowerModelsONM.jl/main/schemas/" - ), - r"(\[.+\])?\((.+)?\s\".+\"\)" => s"\1(\2)", - ), - "patternproperties-\\" => "patternproperties-" - ) + jsfhg.generate_from_filename(joinpath(schemas_in_dir, file), joinpath(schemas_out_dir, replace(file, ".json"=>".iframe.html"))) + + doc = open(joinpath(schemas_out_dir, replace(file, ".json"=>".html")), "r") do io + Gumbo.parsehtml(read(io, String)) end - open(joinpath(schemas_out_dir, file), "w") do io - write(io, doc) + # add style for full height iframe + style = Gumbo.HTMLElement(:style) + style.children = Gumbo.HTMLNode[Gumbo.HTMLText("iframe { height: 100vh; width: 100%; }")] + push!(doc.root[1], style) + + # create iframe containing Pluto.jl rendered HTML + iframe = Gumbo.HTMLElement(:iframe) + iframe.attributes = Dict{AbstractString,AbstractString}( + "src" => "$(replace(file, ".json"=>".iframe.html"))", + ) + + # edit existing html to replace :article with :iframe + doc.root[2][1][2][2] = iframe + + # Overwrite HTML + open(joinpath(schemas_out_dir, replace(file, ".json"=>".html")), "w") do io + Gumbo.prettyprint(io, doc) end end - - schema_docs = "Schema Documentation" => [ - string(bn) => "schemas/$(bn).md" for bn in schema_basenames - ] - push!(pages, schema_docs) catch e @warn "json schema documentation build failed, skipping: $e" end -# build documents -makedocs( - # modules = [PowerModelsONM, PowerModelsONM.PowerModelsDistribution, PowerModelsONM.InfrastructureModels], - format = format, - strict=false, - sitename = "PowerModelsONM", - authors = "David M Fobes and contributors", - pages = pages -) # Insert HTML rendered from Pluto.jl into tutorial stubs as iframes if !_FAST @@ -121,6 +134,7 @@ if !_FAST ss.connected_clients[client.id] = client for file in readdir("examples", join=true) if endswith(file, ".jl") + @info "rendering '$(file)' with pluto" nb = Pluto.load_notebook_nobackup(file); client.connected_notebook = nb; Pluto.update_run!(ss, nb, nb.cells); diff --git a/docs/src/assets/infrastructuremodels_ecosystem.png b/docs/src/assets/infrastructuremodels_ecosystem.png new file mode 100644 index 00000000..77f199b3 Binary files /dev/null and b/docs/src/assets/infrastructuremodels_ecosystem.png differ diff --git a/docs/src/assets/nested_ieee13.svg b/docs/src/assets/nested_ieee13.svg new file mode 100644 index 00000000..94010183 --- /dev/null +++ b/docs/src/assets/nested_ieee13.svg @@ -0,0 +1,385 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bus.703 + + + bus.702 + + + load.702 + + + load.703 + + + pvsystem.pv_mg1a + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bus.692 + + + bus.675aux + + + bus.675 + + + load.692_3 + + + load.675b + + + load.675a + + + load.692_1 + + + load.675c + + + capacitor.cap1 + + + generator.675 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bus.700 + + + bus.701 + + + load.701 + + + load.700 + + + pvsystem.pv_mg1b + + + storage.battery_mg1a + + + storage.battery_mg1b + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bus.671 + + + bus.680 + + + bus.652 + + + bus.634 + + + bus.650 + + + bus.rg60 + + + bus.611 + + + bus.645 + + + bus.632 + + + bus.633 + + + bus.684 + + + bus.sourcebus + + + bus.670 + + + bus.646 + + + load.671_1 + + + load.634a + + + load.652 + + + load.671_3 + + + load.646_3 + + + load.670c + + + load.611 + + + load.645 + + + load.634c + + + load.671_2 + + + load.670b + + + load.634b + + + load.670a + + + load.646_2 + + + capacitor.cap2 + + + vsource.source + + + + + + + + + + + + + + + + + + + + + bus.800 + + + bus.800aux + + + + + + + + + + + + + + + + + + + + + + + + + bus.801 + + + load.801 + + + storage.battery_mg1c + + + diff --git a/docs/src/assets/onm_process_flow_v4.png b/docs/src/assets/onm_process_flow_v4.png new file mode 100644 index 00000000..f700c67a Binary files /dev/null and b/docs/src/assets/onm_process_flow_v4.png differ diff --git a/docs/src/assets/unnested_ieee13.svg b/docs/src/assets/unnested_ieee13.svg new file mode 100644 index 00000000..a5ccba9f --- /dev/null +++ b/docs/src/assets/unnested_ieee13.svg @@ -0,0 +1,307 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bus.800aux + + + bus.671 + + + bus.800 + + + bus.680 + + + bus.634 + + + bus.652 + + + bus.701 + + + bus.675 + + + bus.702 + + + bus.650 + + + bus.700 + + + bus.801 + + + bus.rg60 + + + bus.611 + + + bus.645 + + + bus.632 + + + bus.675aux + + + bus.703 + + + bus.633 + + + bus.684 + + + bus.sourcebus + + + bus.692 + + + bus.670 + + + bus.646 + + + load.671_1 + + + load.634a + + + load.692_3 + + + load.675b + + + load.675a + + + load.652 + + + load.692_1 + + + load.701 + + + load.671_3 + + + load.702 + + + load.646_3 + + + load.700 + + + load.801 + + + load.670c + + + load.611 + + + load.645 + + + load.634c + + + load.671_2 + + + load.703 + + + load.670b + + + load.634b + + + load.675c + + + load.670a + + + load.646_2 + + + capacitor.cap1 + + + capacitor.cap2 + + + generator.675 + + + pvsystem.pv_mg1b + + + pvsystem.pv_mg1a + + + storage.battery_mg1a + + + storage.battery_mg1c + + + storage.battery_mg1b + + + vsource.source + + + diff --git a/docs/src/developer/contributing.md b/docs/src/developer/contributing.md index 56a5708f..a37956a8 100644 --- a/docs/src/developer/contributing.md +++ b/docs/src/developer/contributing.md @@ -30,13 +30,13 @@ Every PR to PowerModelsONM should strive to meet the following guidelines. - Unit tests should be added. In the case where existing unit tests were altered, an explanation for the change must be included - Code should be rebased to the latest version of whatever branch the PR is aimed at (no merge conflicts!) -# Versions +## Versions PowerModelsONM follows the Semantic Versioning ([SemVer](https://semver.org/)) convention of `Major.minor.patch`, where `Major` indicates breaking changes, `minor` indicates non-breaking feature additions, and `patch` indicates non-breaking bugfixes. Currently, because `Major==0`, `minor` indicates breaking changes and `patch` indicates any non-breaking change, including both feature additions and bugfixes. Once PowerModelsONM reaches `v1.0.0`, we will adhere strictly to the SemVer convention. -# Branch Management +## Branch Management The main branch is a [protected](https://help.github.com/en/github/administering-a-repository/about-protected-branches) branch, meaning that its history will always be contiguous and can never be overwritten. diff --git a/docs/src/developer/roadmap.md b/docs/src/developer/roadmap.md index ec62e3d8..6b72d488 100644 --- a/docs/src/developer/roadmap.md +++ b/docs/src/developer/roadmap.md @@ -4,11 +4,10 @@ This is a working document that contains an ongoing list of upcoming features an ## Desired Features -This section contains a - -- "fault" events -- microgrid tagging for improved statistics -- unit tests including storage -- SOC relaxation for OSW/MLD -- Improved binary builds -- Improved docker builds +This section contains a list of desired upcoming features, in no particular order: + +- SOC relaxation for MLD +- User-friendly settings solve fidelity settings +- MINLP MLD examples +- Integration of protection optimizer +- Individually controllable / continuously sheddable loads diff --git a/docs/src/developer/style.md b/docs/src/developer/style.md index ce4a17df..b39eb565 100644 --- a/docs/src/developer/style.md +++ b/docs/src/developer/style.md @@ -25,7 +25,7 @@ When specifying types, _i.e._ when specifying the type of a function argument, o - Prefer to use `Vector{T}` instead of `Array{T,1}` - Prefer to use `Matrix{T}` instead of `Array{T,2}` -- Enums should __only__ be used in the `ENGINEERING` data model, never the `MATHEMATICAL` data model +- Enums should **only** be used in the `ENGINEERING` data model, never the `MATHEMATICAL` data model - Enums must be added to the JSON parser when introduced ## Constants @@ -53,7 +53,7 @@ Currently, all phase-aware functions use `mc`, but this is subject to change in ## Formulation Styles -- All new formulations should have __clear__ error messages when they do not support existing components. For example, if a formulation addition which is intended to work with OPF does not support delta-wye transformers, the `constraint_mc_transformer_power_dy` +- All new formulations should have **clear** error messages when they do not support existing components. For example, if a formulation addition which is intended to work with OPF does not support delta-wye transformers, the `constraint_mc_transformer_power_dy` - Formulation `abstract type` and `mutable struct` must be specified in [CapitalizedWords](https://legacy.python.org/dev/peps/pep-0008/#descriptive-naming-styles), which is a subtype of [camelCase](https://en.wikipedia.org/wiki/Camel_case) with the first word also capitalized. ## Problem Specification Styles @@ -69,7 +69,7 @@ In general, it is better to avoid metaprogramming patterns, like creating functi Markdown files should be properly formatted, particularly when including tables. Developers are encouraged to use [markdownlint](https://github.com/markdownlint/markdownlint) and a markdown formatter (such as in VSCode). -# File Structure +## File Structure It is important that new functions, variables, constraints, etc. all go into appropriate places in the code base so that future maintenance and debugging is easier. Pay attention to the current file structure and attempt to conform as best as possible to it. In general @@ -85,10 +85,10 @@ It is important that new functions, variables, constraints, etc. all go into app - `examples` contains Pluto.jl notebooks with walkthroughs of PowerModelsONM for new users - `schemas` contains JSON Schemas for supported I/O file formats -# Dependencies (Project.toml) +## Dependencies (Project.toml) All new dependencies should be carefully considered before being added. It is important to keep the number of external dependencies low to avoid reliance on features that may not be maintained in the future. If possible, Julia Standard Library should be used, particularly in the case where reproducing the desired feature is trivial. There will be cases where it is not simple to duplicate a feature and subsequently maintain it within the package, so adding a dependency would be appropriate in such cases. All new dependencies are are ultimately approved should also include an entry under `[compat]` indicating the acceptable versions (Julia automerge requirement). This includes test-only dependencies that appear under `[extras]` -The `Manifest.toml` __should not__ be included in the repo. +The `Manifest.toml` **should not** be included in the repo. diff --git a/docs/src/index.md b/docs/src/index.md index 9f2ce857..fd61df6f 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -20,15 +20,17 @@ Read the introductory tutorial [Introduction to PowerModelsONM](@ref Introductio The following is a high-level overview of how our documetation is structured. There are three primary sections: -- The __Manual__ contains detailed documentation for certain aspects of PowerModelsONM, such as +- The **Manual** contains detailed documentation for certain aspects of PowerModelsONM, such as -- __Tutorials__ contains working examples of how to use PowerModelsONM. Start here if you are new to PowerModelsONM. +- **Tutorials** contains working examples of how to use PowerModelsONM. Start here if you are new to PowerModelsONM. -- The __API Reference__ contains a complete list of the functions you can use in PowerModelsONM. Look here if you want to know how to use a particular function. +- The **API Reference** contains a complete list of the functions you can use in PowerModelsONM. Look here if you want to know how to use a particular function. ## PowerModelsONM Analyses Packages -PowerModelsONM depends on several other PowerModels(...) packages from the InfrastructureModels ecosystem. +PowerModelsONM depends on several other PowerModels(...) packages from the InfrastructureModels ecosystem. The packages in blue below are created and maintained by the core InfrastructureModels developer team, and the other packages are those that are built as extensions or rely on one of the core InfrastructureModels packages in some way. + +![InfrastructureModels Ecosystem](assets/infrastructuremodels_ecosystem.png) ### PowerModelsDistribution diff --git a/docs/src/installation.md b/docs/src/installation.md index 16f06353..b08be4a6 100644 --- a/docs/src/installation.md +++ b/docs/src/installation.md @@ -3,7 +3,7 @@ From Julia, PowerModelsONM is installed using the built-in package manager: ```julia -pkg> add PowerModelsONM +]add PowerModelsONM ``` or equivalently, @@ -44,4 +44,4 @@ export GRB_LICENSE_FILE="$HOME/.gurobi/gurobi.lic" export GUROBI_HOME="/Library/gurobi910/mac64" ``` -__BEFORE__ importing PowerModelsONM with `using PowerModelsONM`, you __must__ `import Gurobi`. +**BEFORE** importing PowerModelsONM with `using PowerModelsONM`, you **must** `import Gurobi`. diff --git a/docs/src/manual/graphml_export.md b/docs/src/manual/graphml_export.md new file mode 100644 index 00000000..1638a530 --- /dev/null +++ b/docs/src/manual/graphml_export.md @@ -0,0 +1,35 @@ +# GraphML Export + +In PowerModelsONM we include a capability to export a network data structure as a graph, either nested, _i.e._, consisting of subgraphs of load blocks or unnested in the [GraphML format](http://graphml.graphdrawing.org/), which is an XML format. + +We also include in the `examples/data` folder a suggested ["Configuration"](https://github.com/lanl-ansi/PowerModelsONM.jl/tree/main/examples/data/onm_suggested.cnfx) for use in the [yEd](https://www.yworks.com/products/yed/download) Properties Manager. This configuration was used to construct the examples below. + +## Unnested Graph + +To export an unnested graph + +```julia +import PowerModelsONM as ONM +onm_path = joinpath(dirname(pathof(ONM)), "../examples/data") +eng = ONM.PMD.parse_file(joinpath(onm_path, "network.ieee13.dss")) +save_graphml("unnested_ieee13.graphml", eng; type="unnested") +``` + +Below is what this exported graphml looks like after being loaded in yEd, the ONM recommended properaties applied, and the Orthogonal - Classic layout applied. + +![Unnested IEEE13 Graph](../assets/unnested_ieee13.svg) + +## Nested Graph + +To export an nested graph + +```julia +import PowerModelsONM as ONM +onm_path = joinpath(dirname(pathof(ONM)), "../examples/data") +eng = ONM.PMD.parse_file(joinpath(onm_path, "network.ieee13.dss")) +save_graphml("nested_ieee13.graphml", eng; type="nested") +``` + +Below is what this exported graphml looks like after being loaded in yEd, the ONM recommended properaties applied, and the Orthogonal - Classic layout applied. + +![Nested IEEE13 Graph](../assets/nested_ieee13.svg) diff --git a/docs/src/manual/osw_mld_model.md b/docs/src/manual/mld_model.md similarity index 95% rename from docs/src/manual/osw_mld_model.md rename to docs/src/manual/mld_model.md index b0022b70..88f945ca 100644 --- a/docs/src/manual/osw_mld_model.md +++ b/docs/src/manual/mld_model.md @@ -1,10 +1,10 @@ -# [Optimal Switching / Load shed Mathematical Model](@id osw-mld-math) +# [Optimal Load Shed Mathematical Model](@id mld-math) The following contains the mathematical model for the optimal switching / load shed problem as implemented in PowerModelsONM. For more information about notation see the [optimal dispatch documentation](@ref opf-math), or [PowerModelsDistribution AC OPF documentation](https://lanl-ansi.github.io/PowerModelsDistribution.jl/stable/manual/math-model.html). -## OSW/MLD Variables +## MLD Variables ```math \begin{align} @@ -18,7 +18,7 @@ For more information about notation see the [optimal dispatch documentation](@re \end{align} ``` -## OSW/MLD Objective +## MLD Objective ```math \begin{align} @@ -43,7 +43,7 @@ where \end{align} ``` -## OSW/MLD Constraints +## MLD Constraints ```math \begin{align} diff --git a/docs/src/manual/onm_workflow.md b/docs/src/manual/onm_workflow.md index 40f578cf..6d189570 100644 --- a/docs/src/manual/onm_workflow.md +++ b/docs/src/manual/onm_workflow.md @@ -1,6 +1,6 @@ # ONM Workflow -![ONM Workflow Diagram](../assets/onm_workflow-white.png) +![ONM Workflow Diagram](../assets/onm_process_flow_v4.png) PowerModelsONM is designed to have a straightforward workflow for optimizing the operation and recovering of distribution feeders under contingencies. @@ -19,11 +19,11 @@ At a minimum, PowerModelsONM requires a network file, in DSS format, with timese There are several other supplementary files that can be included as well, of which the [events data](@ref Events-Schema) is the most important. This file defines the contingency, by either explicitly defining the switching actions, or by applying a fault to a particular asset (_i.e._, a line). -Another useful file is the network [settings data](@ref Settings-Schema), which is used to define extra information about the network, __not__ related to the timeseries, which cannot be expressed in the DSS format, such as bus voltage magnitude bounds, cold load pickup factors, voltage angle difference bounds, microgrid definitions, etc. +Another useful file is the network [settings data](@ref Settings-Schema), which is used to define extra information about the network, **not** related to the timeseries, which cannot be expressed in the DSS format, such as bus voltage magnitude bounds, cold load pickup factors, voltage angle difference bounds, microgrid definitions, etc. Finally, there are the two inputs for Stability Analysis and Fault Analysis, performed post optimization. For stability analysis, [inverters data](@ref Inverters-Schema) is required for the analysis to produce meaningful results (see, [PowerModelsStability documentation](https://github.com/lanl-ansi/PowerModelsStability.jl)), but for fault analysis, although you can specify faults ahead of time via [fault data](@ref Faults-Schema), it is not necessary because if no faults are specified, a set of faults for analysis will be automatically generated. It should be noted however that a large number of faults will be generated automatically, which could incur serious time penalties in the completion of the algorithm. For more information, see [PowerModelsProtection documentation](https://github.com/lanl-ansi/PowerModelsProtection.jl). -## Optimal Switching Problem (OSW/MLD) +## Optimal Switching Problem (MLD) The optimal switching algorithm in ONM is an extension of the single-network MLD problem contained in PowerModelsDistribution, that takes into consideration certain engineering realities of distribution feeders. @@ -35,7 +35,7 @@ Second, the optimal switching problem currently uses the LinDist3Flow model (`Po Finally, the optimial switching problem currently solves sequentially, rather than globally over the entire multinetwork, which means switch configurations and storage energies are manually updated after each timestep is solved. -The mathematical formulation can be found [here](@ref osw-mld-math). +The mathematical formulation can be found [here](@ref mld-math). ## Optimal Dispatch (OPF) @@ -45,7 +45,7 @@ This nonlinear AC OPF problem is a simple extension of the [AC-OPF problem conta ## Statistics -After the optimizations have completed, ONM collects essential statistics for our default [output specification](@ref Outputs-Schema). These include: +After the optimizations have completed, ONM collects essential statistics for our default [output specification](@ref Output-Schema). These include: - a device action timeline, which contains an ordered list of the switch settings and loads shed at each timestep, - a list of switch changes, _i.e._, switches whose state has changed from the previous timestep, @@ -63,3 +63,5 @@ Finally, if fault analysis is performed, an ordered list of fault analysis resul - the fault susceptance / conductance, - both the unbalanced and symmetric (sequence) fault currents at each protection device, and - the voltage magnitude at each protection device. + +Full details of what is included in the output can be found in the schema files. diff --git a/docs/src/manual/quickguide.md b/docs/src/manual/quickguide.md index e89e73f1..7883e2cc 100644 --- a/docs/src/manual/quickguide.md +++ b/docs/src/manual/quickguide.md @@ -42,4 +42,4 @@ Although PowerModelsONM includes some open source solvers by default, namely we recommend using Gurobi to solve the [`optimal switching problem`](@ref optimize_switches!), if it is available to you, as we have found it has far superior performance on the MISOCP problem that it is solving as compared to the open-source solutions. !!! info - To use Gurobi with PowerModelsONM, do `import Gurobi` __BEFORE__ `import PowerModelsONM`. We use [Requires.jl](https://github.com/JuliaPackaging/Requires.jl) to manage the Gurobi Environment `GRB_ENV`, which will check out a license that can be used throughout the optimization solves. +To use Gurobi with PowerModelsONM, do `import Gurobi` **BEFORE** `import PowerModelsONM`. We use [Requires.jl](https://github.com/JuliaPackaging/Requires.jl) to manage the Gurobi Environment `GRB_ENV`, which will check out a license that can be used throughout the optimization solves. diff --git a/docs/src/reference/data.md b/docs/src/reference/data.md index 3bccb644..11c41ddb 100644 --- a/docs/src/reference/data.md +++ b/docs/src/reference/data.md @@ -1,14 +1,5 @@ # [Data](@id DataAPI) -## Transformations - -```@autodocs -Modules = [PowerModelsONM] -Private = false -Order = [:function] -Pages = ["transformations.jl"] -``` - ## Data Handling ```@autodocs diff --git a/docs/src/reference/entrypoint.md b/docs/src/reference/entrypoint.md index ddfbf92f..00985841 100644 --- a/docs/src/reference/entrypoint.md +++ b/docs/src/reference/entrypoint.md @@ -4,10 +4,12 @@ ```@docs entrypoint +``` + +```@docs initialize_output! -parse_network! -parse_events! -parse_settings! +setup_logging! +prepare_data! build_solver_instances! optimize_switches! optimize_dispatch! diff --git a/docs/src/reference/graphml.md b/docs/src/reference/graphml.md new file mode 100644 index 00000000..02a1f80e --- /dev/null +++ b/docs/src/reference/graphml.md @@ -0,0 +1,8 @@ +# [GraphML](@id GraphMLAPI) + +```@autodocs +Modules = [PowerModelsONM] +Private = false +Order = [:function] +Pages = ["graphml.jl"] +``` diff --git a/docs/src/reference/internal.md b/docs/src/reference/internal.md index e75d210a..fac67d3b 100644 --- a/docs/src/reference/internal.md +++ b/docs/src/reference/internal.md @@ -8,8 +8,7 @@ Filter = t -> startswith(string(t), "_") ## Constants ```@docs -PowerModelsONM._ref_extensions -PowerModelsONM._switch_formulations -PowerModelsONM._dispatch_formulations -PowerModelsONM._solution_processors +PowerModelsONM._default_ref_extensions +PowerModelsONM._default_solution_processors +PowerModelsONM._formulation_lookup ``` diff --git a/docs/src/reference/io.md b/docs/src/reference/io.md index 31a31490..4f82bcae 100644 --- a/docs/src/reference/io.md +++ b/docs/src/reference/io.md @@ -3,15 +3,22 @@ ## Parsers ```@docs +parse_file parse_network +parse_network! parse_events +parse_events! parse_settings +parse_settings! parse_faults parse_inverters -get_protection_network_model -get_protection_network_model! -get_timestep_bus_types -get_timestep_bus_types! +``` + +## Builders + +```@docs +build_events_file +build_settings_file ``` ## Applicators @@ -22,25 +29,25 @@ apply_events! apply_settings apply_settings! initialize_output +get_protection_network_model +get_protection_network_model! +get_timestep_bus_types +get_timestep_bus_types! ``` ## Writers ```@docs write_json -build_settings_file -build_events_file ``` -## JSON Schema +## Getters and Setters ```@docs -load_schema -``` - -```@autodocs -Modules = [PowerModelsONM] -Private = false -Order = [:function] -Pages = ["checks.jl"] +get_option +get_setting +set_setting! +set_settings! +set_option! +set_options! ``` diff --git a/docs/src/reference/prob.md b/docs/src/reference/prob.md index 37f263ab..ba50b448 100644 --- a/docs/src/reference/prob.md +++ b/docs/src/reference/prob.md @@ -4,15 +4,17 @@ ```@docs optimize_switches -solve_mc_osw_mld_mi -solve_mn_mc_osw_mld_mi -build_mn_mc_opf_oltc_capc +solve_block_mld +solve_mn_block_mld +solve_traditional_mld +solve_mn_traditional_mld ``` ## Optimal Dispatch ```@docs optimize_dispatch +solve_mn_opf ``` ## Fault stuides diff --git a/docs/src/reference/schema.md b/docs/src/reference/schema.md new file mode 100644 index 00000000..42731005 --- /dev/null +++ b/docs/src/reference/schema.md @@ -0,0 +1,10 @@ +# [Schema](@id SchemaAPI) + +## Validation + +```@autodocs +Modules = [PowerModelsONM] +Private = false +Order = [:function] +Pages = ["checks.jl"] +``` diff --git a/docs/src/reference/stats.md b/docs/src/reference/stats.md index d6d5d1c2..831e7684 100644 --- a/docs/src/reference/stats.md +++ b/docs/src/reference/stats.md @@ -18,6 +18,15 @@ Order = [:function] Pages = ["stats/actions.jl"] ``` +## Analysis + +```@autodocs +Modules = [PowerModelsONM] +Private = false +Order = [:function] +Pages = ["stats/analysis.jl"] +``` + ## Faults ```@autodocs diff --git a/docs/src/reference/types.md b/docs/src/reference/types.md index c52b1f9e..d13b84de 100644 --- a/docs/src/reference/types.md +++ b/docs/src/reference/types.md @@ -1,19 +1,4 @@ # [Data](@id TypesAPI) ```@docs -AbstractUnbalancedNFASwitchModel -LPUBFSwitchModel -SOCUBFSwitchModel -AbstractUnbalancedACPSwitchModel -AbstractUnbalancedACRSwitchModel -NFAUSwitchPowerModel -LPUBFSwitchPowerModel -SOCUBFSwitchPowerModel -ACPUSwitchPowerModel -ACRUSwitchPowerModel -AbstractSwitchModels -AbstractUBFSwitchModels -AbstractNLPSwitchModels -AbstractQPSwitchModels -AbstractLPSwitchModels ``` diff --git a/docs/src/reference/variable_constraint.md b/docs/src/reference/variable_constraint.md index 724cc94a..2b285c98 100644 --- a/docs/src/reference/variable_constraint.md +++ b/docs/src/reference/variable_constraint.md @@ -6,7 +6,7 @@ Modules = [PowerModelsONM] Private = false Order = [:function] -Pages = ["variable.jl", "form/acp.jl", "form/acr.jl", "form/apo.jl", "form/bf_mx_lin.jl", "form/shared.jl"] +Pages = ["variable.jl", "form/acp.jl", "form/acr.jl", "form/apo.jl", "form/lindistflow.jl", "form/shared.jl"] Filter = t -> startswith(string(t), "variable_") ``` @@ -16,7 +16,7 @@ Filter = t -> startswith(string(t), "variable_") Modules = [PowerModelsONM] Private = false Order = [:function] -Pages = ["constraint_template.jl", "constraint.jl", "form/acp.jl", "form/acr.jl", "form/apo.jl", "form/bf_mx_lin.jl", "form/shared.jl"] +Pages = ["constraint_template.jl", "constraint.jl", "form/acp.jl", "form/acr.jl", "form/apo.jl", "form/lindistflow.jl", "form/shared.jl"] Filter = t -> startswith(string(t), "constraint_") ``` diff --git a/docs/src/schemas/input-events.schema.md b/docs/src/schemas/input-events.schema.md new file mode 100644 index 00000000..a3abb613 --- /dev/null +++ b/docs/src/schemas/input-events.schema.md @@ -0,0 +1,3 @@ +# Events Schema + +If this page exists in final documentation, automatic conversion of schema to documentation failed. diff --git a/docs/src/schemas/input-faults.schema.md b/docs/src/schemas/input-faults.schema.md new file mode 100644 index 00000000..fcd87953 --- /dev/null +++ b/docs/src/schemas/input-faults.schema.md @@ -0,0 +1,3 @@ +# Faults Schema + +If this page exists in final documentation, automatic conversion of schema to documentation failed. diff --git a/docs/src/schemas/input-inverters.schema.md b/docs/src/schemas/input-inverters.schema.md new file mode 100644 index 00000000..476e7200 --- /dev/null +++ b/docs/src/schemas/input-inverters.schema.md @@ -0,0 +1,3 @@ +# Inverters Schema + +If this page exists in final documentation, automatic conversion of schema to documentation failed. diff --git a/docs/src/schemas/input-runtime_arguments.schema.md b/docs/src/schemas/input-runtime_arguments.schema.md new file mode 100644 index 00000000..2b6ebe55 --- /dev/null +++ b/docs/src/schemas/input-runtime_arguments.schema.md @@ -0,0 +1,3 @@ +# Runtime Arguments Schema + +If this page exists in final documentation, automatic conversion of schema to documentation failed. diff --git a/docs/src/schemas/input-settings.schema.md b/docs/src/schemas/input-settings.schema.md new file mode 100644 index 00000000..69f5e3a0 --- /dev/null +++ b/docs/src/schemas/input-settings.schema.md @@ -0,0 +1,3 @@ +# Settings Schema + +If this page exists in final documentation, automatic conversion of schema to documentation failed. diff --git a/docs/src/schemas/output.schema.md b/docs/src/schemas/output.schema.md new file mode 100644 index 00000000..311a9e9f --- /dev/null +++ b/docs/src/schemas/output.schema.md @@ -0,0 +1,3 @@ +# Output Schema + +If this page exists in final documentation, automatic conversion of schema to documentation failed. diff --git a/docs/src/tutorials/Beginners Guide.md b/docs/src/tutorials/Beginners Guide.md index d75c138a..b4e3a191 100644 --- a/docs/src/tutorials/Beginners Guide.md +++ b/docs/src/tutorials/Beginners Guide.md @@ -11,8 +11,7 @@ To experience the full interactivity of this notebook please install Pluto.jl, a Run Julia and add the package: ```julia -julia> ] -(v1.6) pkg> add Pluto +]add Pluto ``` _Using the package manager for the first time after installing Julia can take up to 15 minutes - hang in there!_ @@ -20,8 +19,8 @@ _Using the package manager for the first time after installing Julia can take up To run the notebook server: ```julia -julia> import Pluto -julia> Pluto.run() +import Pluto +Pluto.run() ``` Pluto will open in your browser, and you can get started! diff --git a/docs/src/tutorials/Block MLD Basic Example.md b/docs/src/tutorials/Block MLD Basic Example.md new file mode 100644 index 00000000..db449120 --- /dev/null +++ b/docs/src/tutorials/Block MLD Basic Example.md @@ -0,0 +1,26 @@ +# Block MLD Basic Example + +This is a stub for an interactive Pluto.jl tutorial contained in [examples](https://github.com/lanl-ansi/PowerModelsONM.jl/tree/main/examples). + +During documentation build time, this tutorial will be automatically converted to HTML, but will lose its interactivity. + +To experience the full interactivity of this notebook please install Pluto.jl, and open [this file](https://github.com/lanl-ansi/PowerModelsONM.jl/tree/main/examples/Block%20MLD%20Basic%20Example.jl) + +## Installing and running Pluto.jl + +Run Julia and add the package: + +```julia +]add Pluto +``` + +_Using the package manager for the first time after installing Julia can take up to 15 minutes - hang in there!_ + +To run the notebook server: + +```julia +import Pluto +Pluto.run() +``` + +Pluto will open in your browser, and you can get started! diff --git a/docs/src/tutorials/JuMP Model by Hand - MLD-Block.md b/docs/src/tutorials/JuMP Model by Hand - MLD-Block.md new file mode 100644 index 00000000..85ec56ef --- /dev/null +++ b/docs/src/tutorials/JuMP Model by Hand - MLD-Block.md @@ -0,0 +1,26 @@ +# JuMP Model by Hand - MLD-Block + +This is a stub for an interactive Pluto.jl tutorial contained in [examples](https://github.com/lanl-ansi/PowerModelsONM.jl/tree/main/examples). + +During documentation build time, this tutorial will be automatically converted to HTML, but will lose its interactivity. + +To experience the full interactivity of this notebook please install Pluto.jl, and open [this file](https://github.com/lanl-ansi/PowerModelsONM.jl/tree/main/examples/JuMP%20Model%20by%20Hand%20-%20MLD-Block.jl) + +## Installing and running Pluto.jl + +Run Julia and add the package: + +```julia +]add Pluto +``` + +_Using the package manager for the first time after installing Julia can take up to 15 minutes - hang in there!_ + +To run the notebook server: + +```julia +import Pluto +Pluto.run() +``` + +Pluto will open in your browser, and you can get started! diff --git a/docs/src/tutorials/Use Case Examples.md b/docs/src/tutorials/Use Case Examples.md new file mode 100644 index 00000000..8c7ff6c6 --- /dev/null +++ b/docs/src/tutorials/Use Case Examples.md @@ -0,0 +1,26 @@ +# ONM Use Cases + +This is a stub for an interactive Pluto.jl tutorial contained in [examples](https://github.com/lanl-ansi/PowerModelsONM.jl/tree/main/examples). + +During documentation build time, this tutorial will be automatically converted to HTML, but will lose its interactivity. + +To experience the full interactivity of this notebook please install Pluto.jl, and open [this file](https://github.com/lanl-ansi/PowerModelsONM.jl/tree/main/examples/Use%20Case%20Examples.jl) + +## Installing and running Pluto.jl + +Run Julia and add the package: + +```julia +]add Pluto +``` + +_Using the package manager for the first time after installing Julia can take up to 15 minutes - hang in there!_ + +To run the notebook server: + +```julia +import Pluto +Pluto.run() +``` + +Pluto will open in your browser, and you can get started! diff --git a/examples/Beginners Guide.jl b/examples/Beginners Guide.jl index 00a7fa8c..9dbe656b 100644 --- a/examples/Beginners Guide.jl +++ b/examples/Beginners Guide.jl @@ -1,13 +1,20 @@ ### A Pluto.jl notebook ### -# v0.19.0 +# v0.19.9 using Markdown using InteractiveUtils +# ╔═╡ a3dd604d-b63e-4956-a6cb-16749e5ba17b +# ╠═╡ show_logs = false +begin + using Pkg + Pkg.activate(;temp=true) + Pkg.add(Pkg.PackageSpec(; name="PowerModelsONM", rev="v3.0-rc")) +end + # ╔═╡ d8124cac-293a-4a2d-ba70-bc75ec624712 using PowerModelsONM - # ╔═╡ ef2b5e56-d2d1-11eb-0686-51cf4afc8846 md""" # Introduction to PowerModelsONM @@ -16,7 +23,7 @@ This is an introduction to using PowerModelsONM, a Julia/JuMP library for optimi To use PowerModelsONM, you will need to install the package via `Pkg.add()`. -In this Pluto notebook, we will install via the built-in Pluto notebook package manager: +In this Pluto notebook, we would normally install via the built-in Pluto notebook package manager: """ # ╔═╡ ae76a6e5-f114-476f-8c53-36e369586d0c @@ -25,14 +32,27 @@ Throughout this tutorial, we will utilize data that is included in the PowerMode """ # ╔═╡ 0641c9b7-4cb2-48a7-985a-fea34175a635 -data_dir = joinpath(dirname(pathof(PowerModelsONM)), "..", "test", "data") +test_dir = joinpath(dirname(pathof(PowerModelsONM)), "../test/data") +# ╔═╡ d7d6bf22-36ce-4765-b7d9-a4fd7ca41a47 +example_dir = joinpath(dirname(pathof(PowerModelsONM)), "../examples/data") -# ╔═╡ 626cc32c-99b1-4383-a346-16538af31963 -begin +# ╔═╡ f2deeb76-33c5-4d85-a032-60773e0ebf04 +ieee13_network_ex = joinpath(example_dir, "network.ieee13.dss") + +# ╔═╡ fe5b1f7c-f7d9-4451-9942-3e86fea61a35 +ieee13_settings_ex = joinpath(example_dir, "settings.ieee13.json") -setup_logging!(Dict{String,Any}("verbose"=>true)) - +# ╔═╡ 0b2651db-d938-4737-b63a-7679a5750d9c +ieee13_events_ex = joinpath(example_dir, "events.ieee13.json") + +# ╔═╡ d01b5080-eb75-4a7c-b026-fe1d3bfe996c +ieee13_faults_test = joinpath(test_dir, "ieee13_faults.json") + +# ╔═╡ 0ece8e62-7ce7-4a74-b403-0477b23600c6 +ieee13_inverters_test = joinpath(test_dir, "ieee13_inverters.json") + +# ╔═╡ 626cc32c-99b1-4383-a346-16538af31963 md""" ## How to use PowerModelsONM @@ -55,18 +75,25 @@ In particular, the workflow consists of sequential steps of - Powerflow outputs, which contains dispatch setpoints and bus voltage magnitudes - Small signal stability results - Fault analysis results +""" -### Inputs +# ╔═╡ 51e6236d-f6eb-4dcf-9b03-1f9b04848ce7 +begin + open(joinpath(dirname(pathof(PowerModelsONM)), "../docs/src/assets/onm_process_flow_v4.png"), "r") do io + HTML(read(io, String)) + end +end + +# ╔═╡ 583c7e8f-4e2b-4a91-8edf-681e55b55bfd +md"""### Inputs The first and foremost piece of data you will need is the network definition files in OpenDSS format. For more information on what is currently supported see [PowerModelsDistribution](https://github.com/lanl-ansi/PowerModelsDistribution.jl). Using `parse_network` will return a `network`, which is a "multinetwork" representation of the timeseries contained in the DSS specification, and `base_network`, which is the topological reality of the system, without the expansion into a multinetwork. """ -end # ╔═╡ 4891479c-9eb5-494e-ad1f-9bd52a171c57 -base_network, network = parse_network(joinpath(data_dir, "ieee13_feeder.dss")) - +base_network, network = parse_network(ieee13_network_ex) # ╔═╡ e1f1b7cf-d8ad-432d-ac98-95860e1ec65d md""" @@ -80,6 +107,49 @@ For example, the timestep named `"2"`, which corresponds in this case to timeste # ╔═╡ f7636969-d29b-415e-8fe4-d725b6fa97d0 network["nw"]["2"] +# ╔═╡ c6dbd392-2269-4572-8b07-ff7f233b8c89 +md""" +### Settings + +There are many things that cannot be easily represented in the DSS specification, specifically relating to optimization bounds, which is not the use case for OpenDSS. To support specifying these extra types of settings, we have created a settings JSON Schema. + +In this example, we load some settings that have been found to fit the loaded data model well. +""" + +# ╔═╡ e686d58d-50ea-4789-93e7-5f3c41ee53ad +settings = parse_settings(ieee13_settings_ex) + +# ╔═╡ 70b6075c-b548-44d2-b301-9621023e06e0 +md""" +However, using the `build_settings` helper function it is straightforward to generate these settings files with some reasonable defaults, which can be edited further. +""" + +# ╔═╡ cf4b988d-0774-4e5f-8ded-4db1ca869066 +settings_from_build_settings = build_settings( + base_network; + vm_lb_pu=0.8, + vm_ub_pu=1.2, + vad_deg=5.0, + max_switch_actions=1 +) + +# ╔═╡ 27fd182f-3cdc-4568-b6f5-cae1b5e2e1a2 +md""" +Settings can be easily applied via `apply_settings`, which will return a copy of the network data structure. + +It should be noted that settings are applied to the `base_network`, i.e., not the multinetwork data structure. +""" + +# ╔═╡ b4bec7ca-ee1b-42e2-aea2-86e8b5c3dc46 +base_network_settings = apply_settings(base_network, settings) + +# ╔═╡ 01690a4a-33da-4207-86d9-c55d962f07ce +md""" +It is necessary to rebuild the multinetwork data structure after applying settings. +""" + +# ╔═╡ 8abcb814-472a-4bf9-a02c-b04a6c4a1084 +network_settings = make_multinetwork(base_network_settings) # ╔═╡ 1b10ab40-5b24-4370-984d-cce1de0f95f5 md""" @@ -91,8 +161,7 @@ In this example, we load a contingency where in timestep 1 a switching action is """ # ╔═╡ e941118d-5b63-4886-bacd-82291f4c01c4 -raw_events = parse_events(joinpath(data_dir, "ieee13_events.json")) - +raw_events = parse_events(ieee13_events_ex) # ╔═╡ 353e797c-6155-4d7b-bb79-440d7b8f8ae2 md""" @@ -104,7 +173,6 @@ To parse the events into the native format, we need to have the network data # ╔═╡ 05454fe0-2368-4c67-ad82-bec852c56b85 events = parse_events(raw_events, network) - # ╔═╡ 8134f0d4-7719-42e0-8f72-6967115d6bb6 md""" As you can see, this structure now looks much more like the native network definition, allowing you to see how we parse events into actual actions in the timeseries representation. @@ -113,46 +181,15 @@ To apply these events to a network, we can use `apply_events`, which will return """ # ╔═╡ b0f23e3a-3859-467f-a8d1-5bdcf05132f8 -network_events = apply_events(network, events) +network_settings_events = apply_events(network_settings, events) - -# ╔═╡ c6dbd392-2269-4572-8b07-ff7f233b8c89 +# ╔═╡ 72736da6-917e-4cc1-8aff-0923d4c637e9 md""" -### Settings - -There are many things that cannot be easily represented in the DSS specification, specifically relating to optimization bounds, which is not the use case for OpenDSS. To support specifying these extra types of settings, we have created a settings JSON Schema. - -In this example, we load some settings that set the maximum allowed switching actions at each timestep (`"max_switch_actions"`), how much time has elapsed during each timestep (`"time_elapsed"`, in hours), and a cold-load pickup factor (`"clpu_factor"`) for each load. +Like with settings, we can use a useful helper function `build_events` to create a simple events file with some reasonable defults. """ -# ╔═╡ e686d58d-50ea-4789-93e7-5f3c41ee53ad -settings = parse_settings(joinpath(data_dir, "ieee13_settings.json")) - - -# ╔═╡ 27fd182f-3cdc-4568-b6f5-cae1b5e2e1a2 -md""" -Like with events, settings can be easily applied via `apply_settings`, which will return a copy of the network data structure. -""" - -# ╔═╡ 6c421881-9df0-42c3-bf15-a1d4665bcb84 -begin - dep_runtime_args = Dict{String,Any}( - "voltage-lower-bound" => 0.8, - "voltage-upper-bound" => 1.2, - "voltage-angle-difference" => 5, - "max-switch-actions" => 1 - ) - settings_w_dep_args = deepcopy(settings) - PowerModelsONM._convert_depreciated_runtime_args!(dep_runtime_args, settings_w_dep_args, base_network, length(network_events["nw"])) - - network_events_settings = apply_settings(network_events, settings_w_dep_args) -end - - -# ╔═╡ 70b6075c-b548-44d2-b301-9621023e06e0 -md""" -It should be noted that in the above block we did a slight trick using some depreciated runtime arguments to quickly create a better settings. In the future, additional helper functions will be added to assist users in applying some of these common settings -""" +# ╔═╡ d20f4d1c-c8bc-41bd-8f9a-2ee7ee931697 +events_from_build_events = build_events(base_network) # ╔═╡ 438b71e6-aca2-49b4-ab15-e747d335f331 md""" @@ -167,15 +204,30 @@ In order to actually solve any of the optimization problems within PowerModelsON PowerModelsONM has several solvers built-in in case you don't want to create your own, and can be created with `build_solver_instances`, which will ouput a Dictionary with - `"nlp_solver"`: Ipopt -- `"mip_solver"`: Cbc -- `"minlp_solver"`: Alpine with Ipopt and Cbc -- `"misocp_solver"`: Juniper with Ipopt and Cbc +- `"mip_solver"`: HiGHS +- `"lp_solver"`: HiGHS +- `"minlp_solver"`: Juniper +- `"misocp_solver"`: Juniper """ -# ╔═╡ c733df10-79b1-4b72-8c74-fe1fabfead44 -solvers = build_solver_instances() +# ╔═╡ a8ce787f-6a2c-4c97-940c-8331fbda1f3c +md"To create some solvers, it is useful to have some solver settings. The settings structure contains some reasonable defaults." +# ╔═╡ 87959af2-47b2-484c-8396-98f87a4abc2b +settings["solvers"] + +# ╔═╡ 7197dba8-4fb3-460b-8fe3-a0efc59a2d98 +md"In this case, the settings defaulted to using the Gurobi solver, which we should correct for this notebook" + +# ╔═╡ ad417629-caf6-4efd-8861-d7a40c58b53f +settings["solvers"]["useGurobi"] = false + +# ╔═╡ a566fb16-6368-4edb-846c-0dc1917e15da +md"Now, we build some solver instances with these settings" + +# ╔═╡ c733df10-79b1-4b72-8c74-fe1fabfead44 +solvers = build_solver_instances(; solver_options=settings["solvers"]) # ╔═╡ 8ce619a2-fe39-4b77-beda-bde92878cb86 md""" @@ -195,7 +247,7 @@ To run the optimal switching problem, use `optimize_switches` """ # ╔═╡ 484fc544-157e-4fda-a97b-3c791063b1b8 -optimal_switching_results = optimize_switches(network_events_settings, solvers["mip_solver"]; algorithm="iterative") +optimal_switching_results = optimize_switches(network_settings_events, solvers["mip_solver"]; algorithm="rolling-horizon") # ╔═╡ e5a55ef5-e3df-40a3-b0bf-cb6b4a1fec2d @@ -206,7 +258,6 @@ The result is a dictionary, indexed by the subnetwork indexes discussed before, # ╔═╡ 53c406c3-5312-41b6-a774-55d7406ce4d0 optimal_switching_results["1"]["solution"] - # ╔═╡ cfe3ba9f-7bbb-4d1e-a0da-b0e262017779 md""" ### Optimal Dispatch (opf) @@ -217,12 +268,11 @@ First though, we will want to propagate the switch configuration to the multinet """ # ╔═╡ 9733ae2e-5d24-4c7c-ad95-2a7f88fbe249 -network_events_settings_osw = apply_switch_solutions(network_events_settings, optimal_switching_results) - +network_settings_events_osw = apply_switch_solutions(network_settings_events, optimal_switching_results) # ╔═╡ be5a2e83-51a8-4676-a6e0-8aac6640e5a4 begin - for (n,nw) in network_events_settings_osw["nw"] + for (n,nw) in network_settings_events_osw["nw"] for (_,bus) in nw["bus"] delete!(bus, "vm_lb") delete!(bus, "vm_ub") @@ -237,7 +287,6 @@ end # ╔═╡ bda997b3-e790-4af4-94c8-f8ebf3f34140 optimal_dispatch_results = optimize_dispatch(network, PowerModelsONM.PMD.ACRUPowerModel, solvers["nlp_solver"]) - # ╔═╡ 53cf78c6-e5b4-4888-96d1-c14c35e66be8 md""" ### Fault Analysis (fs) @@ -250,8 +299,7 @@ Here we use an example faults file from our unit tests. """ # ╔═╡ 3a3da57c-4783-4e79-b19a-a50633419eb1 -faults = parse_faults(joinpath(data_dir, "ieee13_faults.json")) - +faults = parse_faults(ieee13_faults_test) # ╔═╡ 8ea1a7a5-b515-494b-86b8-584c8243d7f1 md""" @@ -259,8 +307,7 @@ To run a fault study we simply use `run_fault_studies` """ # ╔═╡ 13adb9f5-ded7-4674-b789-60bdca8bccf0 -fault_studies_results = run_fault_studies(network_events_settings_osw, solvers["nlp_solver"]; faults=faults) - +fault_studies_results = run_fault_studies(network_settings_events_osw, solvers["nlp_solver"]; faults=faults) # ╔═╡ 3a8bab18-14e7-4c61-a304-390ae1e5d535 md""" @@ -274,8 +321,7 @@ For stability analysis, we need to define some inverter properties, which we hav """ # ╔═╡ 8fb0fb4d-3b6c-4e76-907f-7d03d7ac0601 -inverters = parse_inverters(joinpath(data_dir, "ieee13_inverters.json")) - +inverters = parse_inverters(ieee13_inverters_test) # ╔═╡ 9bbf0909-218b-4ba8-bd49-93d839fd1c35 md""" @@ -283,8 +329,7 @@ To run a stability analysis we simply use `run_stability_analysis` """ # ╔═╡ b87dbbf3-2326-48fb-8d45-9a407ca2ed82 -stability_results = run_stability_analysis(network_events_settings_osw, inverters, solvers["nlp_solver"]) - +stability_results = run_stability_analysis(network_settings_events_osw, inverters, solvers["nlp_solver"]) # ╔═╡ 74e7866b-fdf5-49af-aeda-e02f67047b74 md""" @@ -296,12 +341,11 @@ The various results dictionaries can all be used in different ways, and you shou ### Action Statistics -First up are statistics about the actions taken during the OSW/MLD optimization. For this we have two primary functions, `get_timestep_device_actions`, which will get a full list of the switch configurations and loads shed at each timestep +First up are statistics about the actions taken during the MLD optimization. For this we have two primary functions, `get_timestep_device_actions`, which will get a full list of the switch configurations and loads shed at each timestep """ # ╔═╡ 35f58253-d264-4da2-aa09-d48f306984b1 -get_timestep_device_actions(network_events_settings_osw, optimal_switching_results) - +get_timestep_device_actions(network_settings_events_osw, optimal_switching_results) # ╔═╡ a9664398-0ca3-40d7-92b7-5b11796a5c1b md""" @@ -309,8 +353,7 @@ and `get_timestep_switch_changes`, which is a list of switches whose `state` has """ # ╔═╡ 21a22427-88c2-49d1-9d9c-a93be0cb1ebf -get_timestep_switch_changes(network_events_settings_osw) - +get_timestep_switch_changes(network_settings_events, optimal_switching_results) # ╔═╡ 9b999688-6f02-49e6-8835-15d97a38095f md""" @@ -322,8 +365,7 @@ The next category of statistics is related to the optimal dispatch problem. Agai """ # ╔═╡ a9e2b5d0-d5a1-47c6-b692-00a468be245d -get_timestep_voltage_statistics(optimal_dispatch_results["solution"], network_events_settings_osw) - +get_timestep_voltage_statistics(optimal_dispatch_results["solution"], network_settings_events_osw) # ╔═╡ da1a0fed-b9f7-47ef-b633-e2b76aa05225 md""" @@ -331,8 +373,7 @@ and `get_timestep_dispatch`, which will collect the dispatch information about t """ # ╔═╡ d73f0289-7c23-4a74-8601-cbd7e61caff7 -get_timestep_dispatch(optimal_dispatch_results["solution"], network_events_settings) - +get_timestep_dispatch(optimal_dispatch_results["solution"], network_settings_events) # ╔═╡ eff4b469-eb16-4126-91cf-a6b4b6c9ed18 md""" @@ -342,8 +383,7 @@ This next category of statistics is related to the microgrids, and has three pri """ # ╔═╡ bc0ab54b-9bfd-46f4-a424-235ff15dbc1f -get_timestep_load_served(optimal_dispatch_results["solution"], network_events_settings_osw) - +get_timestep_load_served(optimal_dispatch_results["solution"], network_settings_events_osw) # ╔═╡ 8c1ce17c-3ff8-4292-929f-12bf80838427 md""" @@ -353,15 +393,13 @@ Next is `get_timestep_generator_profiles`, which collects information about the # ╔═╡ d1eb32f6-e632-4ffa-b71d-7ae600b17a47 get_timestep_generator_profiles(optimal_dispatch_results["solution"]) - # ╔═╡ 3fc33951-1627-44e6-baec-fb04d6b28b24 md""" Finally is `get_timestep_storage_soc`, which returns how much energy storage charge is remaining in the network at each timestep (which is none in this case, because there is no storage in this network). """ # ╔═╡ 7277f686-c0ca-4304-bffc-aee8bef1eba7 -get_timestep_storage_soc(optimal_dispatch_results["solution"], network_events_settings_osw) - +get_timestep_storage_soc(optimal_dispatch_results["solution"], network_settings_events_osw) # ╔═╡ 7cf3e0fd-355a-4b80-b6ab-4903c318a71f md""" @@ -375,8 +413,7 @@ In the fault analysis category, there is only one function for analysis, `get_ti """ # ╔═╡ b1d28290-61eb-482d-9279-6f8f616c3cf5 -get_timestep_fault_currents(fault_studies_results, faults, network_events_settings_osw) - +get_timestep_fault_currents(fault_studies_results, faults, network_settings_events_osw) # ╔═╡ c22200b8-d57e-4960-8714-c51102cbccd7 md""" @@ -388,7 +425,6 @@ Finally is stability statistics, which again only has one function, `get_timeste # ╔═╡ 1d30b557-afbe-4527-84d7-d742f2abdb93 get_timestep_stability(stability_results) - # ╔═╡ a2f8c882-6fed-48f9-a554-c69e354ba0c0 md""" ## Run the entire workflow via `entrypoint` @@ -400,24 +436,45 @@ Even if you use it from the Julia REPL, it can be very beneficial, because it wi # ╔═╡ 753c3b4c-861b-4787-b304-b4d3b1b84be0 args = Dict{String,Any}( - "network" => joinpath(data_dir, "ieee13_feeder.dss"), - "events" => joinpath(data_dir, "ieee13_events.json"), - "settings" => joinpath(data_dir, "ieee13_settings.json"), - "inverters" => joinpath(data_dir, "ieee13_inverters.json"), - "faults" => joinpath(data_dir, "ieee13_faults.json"), - "opt-switch-algorithm" => "iterative", - "opt-switch-solver" => "mip_solver", - "voltage-lower-bound" => 0.8, - "voltage-upper-bound" => 1.2, - "voltage-angle-difference" => 5, - "max-switch-actions" => 1, + "network" => ieee13_network_ex, + "events" => ieee13_events_ex, + "settings" => ieee13_settings_ex, + "inverters" => ieee13_inverters_test, + "faults" => ieee13_faults_test, +) + +# ╔═╡ 5c6e9ace-de55-4b45-84f8-876e7c04f664 +md"Normally, we would simply call `entrypoint(args)` at this point, but to ensure that this problem solves in reasonable time, we will adjust some settings first, which will require some additional steps." + +# ╔═╡ 1a68e9eb-1e37-4049-9480-74cb185c7531 +md"First, we load the data using `prepare_data!`, which will do all the necessary parsing of the network, settings, and events files" + +# ╔═╡ 24ea3ffc-ec31-4b18-a8f6-b5ede42ff33c +prepare_data!(args) + +# ╔═╡ b8a81954-8115-4e88-9d19-4ea3abec15ed +md"Then we can use `set_settings!` to adjust some options in the `args` data structure" + +# ╔═╡ 550ebcd5-9b7c-435e-9450-4eb5a94aac03 +set_settings!( + args, + Dict( + ("solvers","useGurobi") => false, + ("solvers","HiGHS","time_limit") => 300.0, + ("solvers","Ipopt","max_cpu_time") => 300.0, + ("options","problem","operations-algorithm") => "rolling-horizon", + ("options","problem","dispatch-formulation") => "lindistflow", + ("options","constraints","disable-microgrid-networking") => true, + ("options","problem","skip") => ["stability","faults"] + ) ) +# ╔═╡ 8b192733-2e26-4e9c-a4d9-45e8fbf3571d +md"Finally, we can execute the `entrypoint` function, which will perform all parts of the ONM workflow" # ╔═╡ 32073778-e62a-46ee-9797-3988be5c8fba entrypoint_results = entrypoint(args) - # ╔═╡ 6b1d3355-11ff-4aec-b6b2-b0fe0183dca6 md""" As you can see, every step in the workflow is represented in the results, which has the benefit of aiding in debugging. @@ -428,7 +485,6 @@ In particular, all of the statistics and analysis can be found under `"output_da # ╔═╡ d418b2bf-a01d-43bf-8cd0-ca53be431a9f entrypoint_results["output_data"] - # ╔═╡ 5b3c882d-c7c0-4acc-9d05-fafde347e4ff md""" ## Troubleshooting and Debugging @@ -438,769 +494,46 @@ If you are having trouble getting your network to solve, the first place to look In many cases, the default bounds from DSS are either much too restrictive, such as in the case of line limits, or completely non-existant, such as in the case of bus voltage bounds or line angle different bounds. """ -# ╔═╡ 00000000-0000-0000-0000-000000000001 -PLUTO_PROJECT_TOML_CONTENTS = """ -[deps] -PowerModelsONM = "25264005-a304-4053-a338-565045d392ac" - -[compat] -PowerModelsONM = "~2.1.1" -""" - -# ╔═╡ 00000000-0000-0000-0000-000000000002 -PLUTO_MANIFEST_TOML_CONTENTS = """ -# This file is machine-generated - editing it directly is not advised - -julia_version = "1.7.2" -manifest_format = "2.0" - -[[deps.ASL_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "6252039f98492252f9e47c312c8ffda0e3b9e78d" -uuid = "ae81ac8f-d209-56e5-92de-9978fef736f9" -version = "0.1.3+0" - -[[deps.ArgParse]] -deps = ["Logging", "TextWrap"] -git-tree-sha1 = "3102bce13da501c9104df33549f511cd25264d7d" -uuid = "c7e460c6-2fb9-53a9-8c5b-16f535851c63" -version = "1.1.4" - -[[deps.ArgTools]] -uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" - -[[deps.ArnoldiMethod]] -deps = ["LinearAlgebra", "Random", "StaticArrays"] -git-tree-sha1 = "62e51b39331de8911e4a7ff6f5aaf38a5f4cc0ae" -uuid = "ec485272-7323-5ecc-a04f-4719b315124d" -version = "0.2.0" - -[[deps.ArrayInterface]] -deps = ["Compat", "IfElse", "LinearAlgebra", "Requires", "SparseArrays", "Static"] -git-tree-sha1 = "c933ce606f6535a7c7b98e1d86d5d1014f730596" -uuid = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" -version = "5.0.7" - -[[deps.Artifacts]] -uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" - -[[deps.Base64]] -uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" - -[[deps.BenchmarkTools]] -deps = ["JSON", "Logging", "Printf", "Profile", "Statistics", "UUIDs"] -git-tree-sha1 = "4c10eee4af024676200bc7752e536f858c6b8f93" -uuid = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" -version = "1.3.1" - -[[deps.BinaryProvider]] -deps = ["Libdl", "Logging", "SHA"] -git-tree-sha1 = "ecdec412a9abc8db54c0efc5548c64dfce072058" -uuid = "b99e7846-7c00-51b0-8f62-c81ae34c0232" -version = "0.5.10" - -[[deps.Bzip2_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "19a35467a82e236ff51bc17a3a44b69ef35185a2" -uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0" -version = "1.0.8+0" - -[[deps.CEnum]] -git-tree-sha1 = "215a9aa4a1f23fbd05b92769fdd62559488d70e9" -uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82" -version = "0.4.1" - -[[deps.CSV]] -deps = ["CodecZlib", "Dates", "FilePathsBase", "InlineStrings", "Mmap", "Parsers", "PooledArrays", "SentinelArrays", "Tables", "Unicode", "WeakRefStrings"] -git-tree-sha1 = "873fb188a4b9d76549b81465b1f75c82aaf59238" -uuid = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" -version = "0.10.4" - -[[deps.Calculus]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "f641eb0a4f00c343bbc32346e1217b86f3ce9dad" -uuid = "49dc2e85-a5d0-5ad3-a950-438e2897f1b9" -version = "0.5.1" - -[[deps.Cbc]] -deps = ["BinaryProvider", "CEnum", "Cbc_jll", "Libdl", "MathOptInterface", "SparseArrays"] -git-tree-sha1 = "6656166f484075dd146c9f452b1428116eaf76d4" -uuid = "9961bab8-2fa3-5c5a-9d89-47fab24efd76" -version = "0.9.1" - -[[deps.Cbc_jll]] -deps = ["ASL_jll", "Artifacts", "Cgl_jll", "Clp_jll", "CoinUtils_jll", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "OpenBLAS32_jll", "Osi_jll", "Pkg"] -git-tree-sha1 = "a3c5986d7713bce4260d9826deead060a17c8e2d" -uuid = "38041ee0-ae04-5750-a4d2-bb4d0d83d27d" -version = "200.1000.501+0" - -[[deps.Cgl_jll]] -deps = ["Artifacts", "Clp_jll", "CoinUtils_jll", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Osi_jll", "Pkg"] -git-tree-sha1 = "11eb7b7688925e9751b5d7a187aaa4291eae2664" -uuid = "3830e938-1dd0-5f3e-8b8e-b3ee43226782" -version = "0.6000.300+0" - -[[deps.ChainRulesCore]] -deps = ["Compat", "LinearAlgebra", "SparseArrays"] -git-tree-sha1 = "9950387274246d08af38f6eef8cb5480862a435f" -uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" -version = "1.14.0" - -[[deps.ChangesOfVariables]] -deps = ["ChainRulesCore", "LinearAlgebra", "Test"] -git-tree-sha1 = "bf98fa45a0a4cee295de98d4c1462be26345b9a1" -uuid = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0" -version = "0.1.2" - -[[deps.Clp_jll]] -deps = ["Artifacts", "CoinUtils_jll", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "METIS_jll", "MUMPS_seq_jll", "OpenBLAS32_jll", "Osi_jll", "Pkg"] -git-tree-sha1 = "b1031dcfbb44553194c9e650feb5ab65e372504f" -uuid = "06985876-5285-5a41-9fcb-8948a742cc53" -version = "100.1700.601+0" - -[[deps.CodecBzip2]] -deps = ["Bzip2_jll", "Libdl", "TranscodingStreams"] -git-tree-sha1 = "2e62a725210ce3c3c2e1a3080190e7ca491f18d7" -uuid = "523fee87-0ab8-5b00-afb7-3ecf72e48cfd" -version = "0.7.2" - -[[deps.CodecZlib]] -deps = ["TranscodingStreams", "Zlib_jll"] -git-tree-sha1 = "ded953804d019afa9a3f98981d99b33e3db7b6da" -uuid = "944b1d66-785c-5afd-91f1-9de20f533193" -version = "0.7.0" - -[[deps.CoinUtils_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "OpenBLAS32_jll", "Pkg"] -git-tree-sha1 = "44173e61256f32918c6c132fc41f772bab1fb6d1" -uuid = "be027038-0da8-5614-b30d-e42594cb92df" -version = "200.1100.400+0" - -[[deps.CommonSubexpressions]] -deps = ["MacroTools", "Test"] -git-tree-sha1 = "7b8a93dba8af7e3b42fecabf646260105ac373f7" -uuid = "bbf7d656-a473-5ed7-a52c-81e309532950" -version = "0.3.0" - -[[deps.Compat]] -deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"] -git-tree-sha1 = "96b0bc6c52df76506efc8a441c6cf1adcb1babc4" -uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" -version = "3.42.0" - -[[deps.CompilerSupportLibraries_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" - -[[deps.DataAPI]] -git-tree-sha1 = "cc70b17275652eb47bc9e5f81635981f13cea5c8" -uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" -version = "1.9.0" - -[[deps.DataStructures]] -deps = ["Compat", "InteractiveUtils", "OrderedCollections"] -git-tree-sha1 = "3daef5523dd2e769dad2365274f760ff5f282c7d" -uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" -version = "0.18.11" - -[[deps.DataValueInterfaces]] -git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6" -uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464" -version = "1.0.0" - -[[deps.Dates]] -deps = ["Printf"] -uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" - -[[deps.DelimitedFiles]] -deps = ["Mmap"] -uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab" - -[[deps.DiffResults]] -deps = ["StaticArrays"] -git-tree-sha1 = "c18e98cba888c6c25d1c3b048e4b3380ca956805" -uuid = "163ba53b-c6d8-5494-b064-1a9d43ac40c5" -version = "1.0.3" - -[[deps.DiffRules]] -deps = ["IrrationalConstants", "LogExpFunctions", "NaNMath", "Random", "SpecialFunctions"] -git-tree-sha1 = "dd933c4ef7b4c270aacd4eb88fa64c147492acf0" -uuid = "b552c78f-8df3-52c6-915a-8e097449b14b" -version = "1.10.0" - -[[deps.Distances]] -deps = ["LinearAlgebra", "SparseArrays", "Statistics", "StatsAPI"] -git-tree-sha1 = "3258d0659f812acde79e8a74b11f17ac06d0ca04" -uuid = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7" -version = "0.10.7" - -[[deps.Distributed]] -deps = ["Random", "Serialization", "Sockets"] -uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" - -[[deps.DocStringExtensions]] -deps = ["LibGit2"] -git-tree-sha1 = "b19534d1895d702889b219c382a6e18010797f0b" -uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" -version = "0.8.6" - -[[deps.Downloads]] -deps = ["ArgTools", "LibCURL", "NetworkOptions"] -uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" - -[[deps.FilePaths]] -deps = ["FilePathsBase", "MacroTools", "Reexport", "Requires"] -git-tree-sha1 = "919d9412dbf53a2e6fe74af62a73ceed0bce0629" -uuid = "8fc22ac5-c921-52a6-82fd-178b2807b824" -version = "0.8.3" - -[[deps.FilePathsBase]] -deps = ["Compat", "Dates", "Mmap", "Printf", "Test", "UUIDs"] -git-tree-sha1 = "129b104185df66e408edd6625d480b7f9e9823a0" -uuid = "48062228-2e41-5def-b9a4-89aafe57970f" -version = "0.9.18" - -[[deps.FiniteDiff]] -deps = ["ArrayInterface", "LinearAlgebra", "Requires", "SparseArrays", "StaticArrays"] -git-tree-sha1 = "56956d1e4c1221000b7781104c58c34019792951" -uuid = "6a86dc24-6348-571c-b903-95158fe2bd41" -version = "2.11.0" - -[[deps.ForwardDiff]] -deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "LinearAlgebra", "LogExpFunctions", "NaNMath", "Preferences", "Printf", "Random", "SpecialFunctions", "StaticArrays"] -git-tree-sha1 = "1bd6fc0c344fc0cbee1f42f8d2e7ec8253dda2d2" -uuid = "f6369f11-7733-5829-9624-2563aa707210" -version = "0.10.25" - -[[deps.Future]] -deps = ["Random"] -uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820" - -[[deps.Glob]] -git-tree-sha1 = "4df9f7e06108728ebf00a0a11edee4b29a482bb2" -uuid = "c27321d9-0574-5035-807b-f59d2c89b15c" -version = "1.3.0" - -[[deps.Graphs]] -deps = ["ArnoldiMethod", "DataStructures", "Distributed", "Inflate", "LinearAlgebra", "Random", "SharedArrays", "SimpleTraits", "SparseArrays", "Statistics"] -git-tree-sha1 = "92243c07e786ea3458532e199eb3feee0e7e08eb" -uuid = "86223c79-3864-5bf0-83f7-82e725a168b6" -version = "1.4.1" - -[[deps.HTTP]] -deps = ["Base64", "Dates", "IniFile", "Logging", "MbedTLS", "NetworkOptions", "Sockets", "URIs"] -git-tree-sha1 = "0fa77022fe4b511826b39c894c90daf5fce3334a" -uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3" -version = "0.9.17" - -[[deps.Hwloc]] -deps = ["Hwloc_jll"] -git-tree-sha1 = "92d99146066c5c6888d5a3abc871e6a214388b91" -uuid = "0e44f5e4-bd66-52a0-8798-143a42290a1d" -version = "2.0.0" - -[[deps.Hwloc_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "303d70c961317c4c20fafaf5dbe0e6d610c38542" -uuid = "e33a78d0-f292-5ffc-b300-72abe9b543c8" -version = "2.7.1+0" - -[[deps.IfElse]] -git-tree-sha1 = "debdd00ffef04665ccbb3e150747a77560e8fad1" -uuid = "615f187c-cbe4-4ef1-ba3b-2fcf58d6d173" -version = "0.1.1" - -[[deps.Inflate]] -git-tree-sha1 = "f5fc07d4e706b84f72d54eedcc1c13d92fb0871c" -uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9" -version = "0.1.2" - -[[deps.InfrastructureModels]] -deps = ["JuMP", "Memento"] -git-tree-sha1 = "0c9ec48199cb90a6d1b5c6bdc0f9a15ce8a108b2" -uuid = "2030c09a-7f63-5d83-885d-db604e0e9cc0" -version = "0.7.4" - -[[deps.IniFile]] -git-tree-sha1 = "f550e6e32074c939295eb5ea6de31849ac2c9625" -uuid = "83e8ac13-25f8-5344-8a64-a9f2b223428f" -version = "0.5.1" - -[[deps.InlineStrings]] -deps = ["Parsers"] -git-tree-sha1 = "61feba885fac3a407465726d0c330b3055df897f" -uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48" -version = "1.1.2" - -[[deps.InteractiveUtils]] -deps = ["Markdown"] -uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" - -[[deps.InverseFunctions]] -deps = ["Test"] -git-tree-sha1 = "91b5dcf362c5add98049e6c29ee756910b03051d" -uuid = "3587e190-3f89-42d0-90ee-14403ec27112" -version = "0.1.3" - -[[deps.Ipopt]] -deps = ["BinaryProvider", "Ipopt_jll", "Libdl", "MathOptInterface"] -git-tree-sha1 = "68ba332ff458f3c1f40182016ff9b1bda276fa9e" -uuid = "b6b21f68-93f8-5de0-b562-5493be1d77c9" -version = "0.9.1" - -[[deps.Ipopt_jll]] -deps = ["ASL_jll", "Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "MUMPS_seq_jll", "OpenBLAS32_jll", "Pkg"] -git-tree-sha1 = "e3e202237d93f18856b6ff1016166b0f172a49a8" -uuid = "9cc047cb-c261-5740-88fc-0cf96f7bdcc7" -version = "300.1400.400+0" - -[[deps.IrrationalConstants]] -git-tree-sha1 = "7fd44fd4ff43fc60815f8e764c0f352b83c49151" -uuid = "92d709cd-6900-40b7-9082-c6be49f344b6" -version = "0.1.1" - -[[deps.IteratorInterfaceExtensions]] -git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856" -uuid = "82899510-4779-5014-852e-03e436cf321d" -version = "1.0.0" - -[[deps.JLLWrappers]] -deps = ["Preferences"] -git-tree-sha1 = "abc9885a7ca2052a736a600f7fa66209f96506e1" -uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" -version = "1.4.1" - -[[deps.JSON]] -deps = ["Dates", "Mmap", "Parsers", "Unicode"] -git-tree-sha1 = "3c837543ddb02250ef42f4738347454f95079d4e" -uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" -version = "0.21.3" - -[[deps.JSONSchema]] -deps = ["HTTP", "JSON", "URIs"] -git-tree-sha1 = "2f49f7f86762a0fbbeef84912265a1ae61c4ef80" -uuid = "7d188eb4-7ad8-530c-ae41-71a32a6d4692" -version = "0.3.4" - -[[deps.JuMP]] -deps = ["Calculus", "DataStructures", "ForwardDiff", "JSON", "LinearAlgebra", "MathOptInterface", "MutableArithmetics", "NaNMath", "OrderedCollections", "Printf", "Random", "SparseArrays", "SpecialFunctions", "Statistics"] -git-tree-sha1 = "fe0f87cc077fc6a23c21e469318993caf2947d10" -uuid = "4076af6c-e467-56ae-b986-b466b2749572" -version = "0.22.3" - -[[deps.Juniper]] -deps = ["Distributed", "JSON", "LinearAlgebra", "MathOptInterface", "MutableArithmetics", "Random", "Statistics"] -git-tree-sha1 = "6516abbfe736cbe5f4a43c5240c50249e11e4951" -uuid = "2ddba703-00a4-53a7-87a5-e8b9971dde84" -version = "0.8.0" - -[[deps.LibCURL]] -deps = ["LibCURL_jll", "MozillaCACerts_jll"] -uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" - -[[deps.LibCURL_jll]] -deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] -uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" - -[[deps.LibGit2]] -deps = ["Base64", "NetworkOptions", "Printf", "SHA"] -uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" - -[[deps.LibSSH2_jll]] -deps = ["Artifacts", "Libdl", "MbedTLS_jll"] -uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" - -[[deps.Libdl]] -uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" - -[[deps.LineSearches]] -deps = ["LinearAlgebra", "NLSolversBase", "NaNMath", "Parameters", "Printf"] -git-tree-sha1 = "f27132e551e959b3667d8c93eae90973225032dd" -uuid = "d3d80556-e9d4-5f37-9878-2ab0fcc64255" -version = "7.1.1" - -[[deps.LinearAlgebra]] -deps = ["Libdl", "libblastrampoline_jll"] -uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" - -[[deps.LogExpFunctions]] -deps = ["ChainRulesCore", "ChangesOfVariables", "DocStringExtensions", "InverseFunctions", "IrrationalConstants", "LinearAlgebra"] -git-tree-sha1 = "58f25e56b706f95125dcb796f39e1fb01d913a71" -uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688" -version = "0.3.10" - -[[deps.Logging]] -uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" - -[[deps.LoggingExtras]] -deps = ["Dates", "Logging"] -git-tree-sha1 = "dfeda1c1130990428720de0024d4516b1902ce98" -uuid = "e6f89c97-d47a-5376-807f-9c37f3926c36" -version = "0.4.7" - -[[deps.METIS_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "1d31872bb9c5e7ec1f618e8c4a56c8b0d9bddc7e" -uuid = "d00139f3-1899-568f-a2f0-47f597d42d70" -version = "5.1.1+0" - -[[deps.MUMPS_seq_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "METIS_jll", "OpenBLAS32_jll", "Pkg"] -git-tree-sha1 = "29de2841fa5aefe615dea179fcde48bb87b58f57" -uuid = "d7ed1dd3-d0ae-5e8e-bfb4-87a502085b8d" -version = "5.4.1+0" - -[[deps.MacroTools]] -deps = ["Markdown", "Random"] -git-tree-sha1 = "3d3e902b31198a27340d0bf00d6ac452866021cf" -uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" -version = "0.5.9" - -[[deps.Markdown]] -deps = ["Base64"] -uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" - -[[deps.MathOptInterface]] -deps = ["BenchmarkTools", "CodecBzip2", "CodecZlib", "JSON", "LinearAlgebra", "MutableArithmetics", "OrderedCollections", "Printf", "SparseArrays", "Test", "Unicode"] -git-tree-sha1 = "e8c9653877adcf8f3e7382985e535bb37b083598" -uuid = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" -version = "0.10.9" - -[[deps.MbedTLS]] -deps = ["Dates", "MbedTLS_jll", "Random", "Sockets"] -git-tree-sha1 = "1c38e51c3d08ef2278062ebceade0e46cefc96fe" -uuid = "739be429-bea8-5141-9913-cc70e7f3736d" -version = "1.0.3" - -[[deps.MbedTLS_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" - -[[deps.Memento]] -deps = ["Dates", "Distributed", "Requires", "Serialization", "Sockets", "Test", "UUIDs"] -git-tree-sha1 = "9b0b0dbf419fbda7b383dc12d108621d26eeb89f" -uuid = "f28f55f0-a522-5efc-85c2-fe41dfb9b2d9" -version = "1.3.0" - -[[deps.Mmap]] -uuid = "a63ad114-7e13-5084-954f-fe012c677804" - -[[deps.MozillaCACerts_jll]] -uuid = "14a3606d-f60d-562e-9121-12d972cd8159" - -[[deps.MutableArithmetics]] -deps = ["LinearAlgebra", "SparseArrays", "Test"] -git-tree-sha1 = "842b5ccd156e432f369b204bb704fd4020e383ac" -uuid = "d8a4904e-b15c-11e9-3269-09a3773c0cb0" -version = "0.3.3" - -[[deps.NLSolversBase]] -deps = ["DiffResults", "Distributed", "FiniteDiff", "ForwardDiff"] -git-tree-sha1 = "50310f934e55e5ca3912fb941dec199b49ca9b68" -uuid = "d41bc354-129a-5804-8e4c-c37616107c6c" -version = "7.8.2" - -[[deps.NLsolve]] -deps = ["Distances", "LineSearches", "LinearAlgebra", "NLSolversBase", "Printf", "Reexport"] -git-tree-sha1 = "019f12e9a1a7880459d0173c182e6a99365d7ac1" -uuid = "2774e3e8-f4cf-5e23-947b-6d7e65073b56" -version = "4.5.1" - -[[deps.NaNMath]] -git-tree-sha1 = "b086b7ea07f8e38cf122f5016af580881ac914fe" -uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" -version = "0.3.7" - -[[deps.NetworkOptions]] -uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" - -[[deps.OpenBLAS32_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "9c6c2ed4b7acd2137b878eb96c68e63b76199d0f" -uuid = "656ef2d0-ae68-5445-9ca0-591084a874a2" -version = "0.3.17+0" - -[[deps.OpenBLAS_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] -uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" - -[[deps.OpenLibm_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "05823500-19ac-5b8b-9628-191a04bc5112" - -[[deps.OpenSpecFun_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1" -uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e" -version = "0.5.5+0" - -[[deps.OrderedCollections]] -git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c" -uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" -version = "1.4.1" - -[[deps.Osi_jll]] -deps = ["Artifacts", "CoinUtils_jll", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "OpenBLAS32_jll", "Pkg"] -git-tree-sha1 = "28e0ddebd069f605ab1988ab396f239a3ac9b561" -uuid = "7da25872-d9ce-5375-a4d3-7a845f58efdd" -version = "0.10800.600+0" - -[[deps.PackageCompiler]] -deps = ["Libdl", "Pkg", "UUIDs"] -git-tree-sha1 = "85554feaaf12a784873077837397282bb894a625" -uuid = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d" -version = "1.2.8" - -[[deps.Parameters]] -deps = ["OrderedCollections", "UnPack"] -git-tree-sha1 = "34c0e9ad262e5f7fc75b10a9952ca7692cfc5fbe" -uuid = "d96e819e-fc66-5662-9728-84c9c7592b0a" -version = "0.12.3" - -[[deps.Parsers]] -deps = ["Dates"] -git-tree-sha1 = "621f4f3b4977325b9128d5fae7a8b4829a0c2222" -uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "2.2.4" - -[[deps.Pkg]] -deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] -uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" - -[[deps.PolyhedralRelaxations]] -deps = ["DataStructures", "ForwardDiff", "JuMP", "Logging", "LoggingExtras"] -git-tree-sha1 = "fc2d9132d1c7df35ae7ac00afedf6524288fd98d" -uuid = "2e741578-48fa-11ea-2d62-b52c946f73a0" -version = "0.3.4" - -[[deps.PooledArrays]] -deps = ["DataAPI", "Future"] -git-tree-sha1 = "28ef6c7ce353f0b35d0df0d5930e0d072c1f5b9b" -uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720" -version = "1.4.1" - -[[deps.PowerModels]] -deps = ["InfrastructureModels", "JSON", "JuMP", "LinearAlgebra", "Memento", "NLsolve", "SparseArrays"] -git-tree-sha1 = "c680b66275025a7f3265ee356c95a9b644483150" -uuid = "c36e90e8-916a-50a6-bd94-075b64ef4655" -version = "0.19.5" - -[[deps.PowerModelsDistribution]] -deps = ["CSV", "Dates", "FilePaths", "Glob", "InfrastructureModels", "JSON", "JuMP", "LinearAlgebra", "Logging", "LoggingExtras", "PolyhedralRelaxations", "Statistics"] -git-tree-sha1 = "3d895c1ab35dd8f7eebaf4426352071463f23528" -uuid = "d7431456-977f-11e9-2de3-97ff7677985e" -version = "0.14.3" - -[[deps.PowerModelsONM]] -deps = ["ArgParse", "Cbc", "Dates", "Distributed", "Graphs", "Hwloc", "InfrastructureModels", "Ipopt", "JSON", "JSONSchema", "JuMP", "Juniper", "LinearAlgebra", "Logging", "LoggingExtras", "PackageCompiler", "Pkg", "PolyhedralRelaxations", "PowerModelsDistribution", "PowerModelsProtection", "PowerModelsStability", "ProgressMeter", "Requires", "Statistics"] -git-tree-sha1 = "349f5bad362ead58e33402808eac23c822f1b1a9" -uuid = "25264005-a304-4053-a338-565045d392ac" -version = "2.1.1" - -[[deps.PowerModelsProtection]] -deps = ["Graphs", "InfrastructureModels", "JuMP", "LinearAlgebra", "PowerModels", "PowerModelsDistribution", "Printf"] -git-tree-sha1 = "efa7aa12ff0f8cab4cf3ae274430a1c83d977412" -uuid = "719c1aef-945b-435a-a240-4c2992e5e0df" -version = "0.5.1" - -[[deps.PowerModelsStability]] -deps = ["InfrastructureModels", "JSON", "JuMP", "LinearAlgebra", "Memento", "PowerModelsDistribution"] -git-tree-sha1 = "a7fad5a02e1d7fc548f18910f4c7988fb87f17b8" -uuid = "f9e4c324-c3b6-4bca-9c3d-419775f0bd17" -version = "0.3.1" - -[[deps.Preferences]] -deps = ["TOML"] -git-tree-sha1 = "d3538e7f8a790dc8903519090857ef8e1283eecd" -uuid = "21216c6a-2e73-6563-6e65-726566657250" -version = "1.2.5" - -[[deps.Printf]] -deps = ["Unicode"] -uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" - -[[deps.Profile]] -deps = ["Printf"] -uuid = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79" - -[[deps.ProgressMeter]] -deps = ["Distributed", "Printf"] -git-tree-sha1 = "d7a7aef8f8f2d537104f170139553b14dfe39fe9" -uuid = "92933f4c-e287-5a05-a399-4b506db050ca" -version = "1.7.2" - -[[deps.REPL]] -deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] -uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" - -[[deps.Random]] -deps = ["SHA", "Serialization"] -uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" - -[[deps.Reexport]] -git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b" -uuid = "189a3867-3050-52da-a836-e630ba90ab69" -version = "1.2.2" - -[[deps.Requires]] -deps = ["UUIDs"] -git-tree-sha1 = "4036a3bd08ac7e968e27c203d45f5fff15020621" -uuid = "ae029012-a4dd-5104-9daa-d747884805df" -version = "1.1.3" - -[[deps.SHA]] -uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" - -[[deps.SentinelArrays]] -deps = ["Dates", "Random"] -git-tree-sha1 = "6a2f7d70512d205ca8c7ee31bfa9f142fe74310c" -uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c" -version = "1.3.12" - -[[deps.Serialization]] -uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" - -[[deps.SharedArrays]] -deps = ["Distributed", "Mmap", "Random", "Serialization"] -uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" - -[[deps.SimpleTraits]] -deps = ["InteractiveUtils", "MacroTools"] -git-tree-sha1 = "5d7e3f4e11935503d3ecaf7186eac40602e7d231" -uuid = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" -version = "0.9.4" - -[[deps.Sockets]] -uuid = "6462fe0b-24de-5631-8697-dd941f90decc" - -[[deps.SparseArrays]] -deps = ["LinearAlgebra", "Random"] -uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" - -[[deps.SpecialFunctions]] -deps = ["ChainRulesCore", "IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"] -git-tree-sha1 = "5ba658aeecaaf96923dce0da9e703bd1fe7666f9" -uuid = "276daf66-3868-5448-9aa4-cd146d93841b" -version = "2.1.4" - -[[deps.Static]] -deps = ["IfElse"] -git-tree-sha1 = "87e9954dfa33fd145694e42337bdd3d5b07021a6" -uuid = "aedffcd0-7271-4cad-89d0-dc628f76c6d3" -version = "0.6.0" - -[[deps.StaticArrays]] -deps = ["LinearAlgebra", "Random", "Statistics"] -git-tree-sha1 = "4f6ec5d99a28e1a749559ef7dd518663c5eca3d5" -uuid = "90137ffa-7385-5640-81b9-e52037218182" -version = "1.4.3" - -[[deps.Statistics]] -deps = ["LinearAlgebra", "SparseArrays"] -uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" - -[[deps.StatsAPI]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "c3d8ba7f3fa0625b062b82853a7d5229cb728b6b" -uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0" -version = "1.2.1" - -[[deps.TOML]] -deps = ["Dates"] -uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" - -[[deps.TableTraits]] -deps = ["IteratorInterfaceExtensions"] -git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39" -uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c" -version = "1.0.1" - -[[deps.Tables]] -deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "OrderedCollections", "TableTraits", "Test"] -git-tree-sha1 = "5ce79ce186cc678bbb5c5681ca3379d1ddae11a1" -uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" -version = "1.7.0" - -[[deps.Tar]] -deps = ["ArgTools", "SHA"] -uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" - -[[deps.Test]] -deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] -uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" - -[[deps.TextWrap]] -git-tree-sha1 = "9250ef9b01b66667380cf3275b3f7488d0e25faf" -uuid = "b718987f-49a8-5099-9789-dcd902bef87d" -version = "1.0.1" - -[[deps.TranscodingStreams]] -deps = ["Random", "Test"] -git-tree-sha1 = "216b95ea110b5972db65aa90f88d8d89dcb8851c" -uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" -version = "0.9.6" - -[[deps.URIs]] -git-tree-sha1 = "97bbe755a53fe859669cd907f2d96aee8d2c1355" -uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" -version = "1.3.0" - -[[deps.UUIDs]] -deps = ["Random", "SHA"] -uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" - -[[deps.UnPack]] -git-tree-sha1 = "387c1f73762231e86e0c9c5443ce3b4a0a9a0c2b" -uuid = "3a884ed6-31ef-47d7-9d2a-63182c4928ed" -version = "1.0.2" - -[[deps.Unicode]] -uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" - -[[deps.WeakRefStrings]] -deps = ["DataAPI", "InlineStrings", "Parsers"] -git-tree-sha1 = "b1be2855ed9ed8eac54e5caff2afcdb442d52c23" -uuid = "ea10d353-3f73-51f8-a26c-33c1cb351aa5" -version = "1.4.2" - -[[deps.Zlib_jll]] -deps = ["Libdl"] -uuid = "83775a58-1f1d-513f-b197-d71354ab007a" - -[[deps.libblastrampoline_jll]] -deps = ["Artifacts", "Libdl", "OpenBLAS_jll"] -uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" - -[[deps.nghttp2_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" - -[[deps.p7zip_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" -""" - # ╔═╡ Cell order: # ╟─ef2b5e56-d2d1-11eb-0686-51cf4afc8846 +# ╠═a3dd604d-b63e-4956-a6cb-16749e5ba17b # ╠═d8124cac-293a-4a2d-ba70-bc75ec624712 # ╟─ae76a6e5-f114-476f-8c53-36e369586d0c # ╠═0641c9b7-4cb2-48a7-985a-fea34175a635 -# ╟─626cc32c-99b1-4383-a346-16538af31963 +# ╠═d7d6bf22-36ce-4765-b7d9-a4fd7ca41a47 +# ╠═f2deeb76-33c5-4d85-a032-60773e0ebf04 +# ╠═fe5b1f7c-f7d9-4451-9942-3e86fea61a35 +# ╠═0b2651db-d938-4737-b63a-7679a5750d9c +# ╠═d01b5080-eb75-4a7c-b026-fe1d3bfe996c +# ╠═0ece8e62-7ce7-4a74-b403-0477b23600c6 +# ╠═626cc32c-99b1-4383-a346-16538af31963 +# ╠═51e6236d-f6eb-4dcf-9b03-1f9b04848ce7 +# ╠═583c7e8f-4e2b-4a91-8edf-681e55b55bfd # ╠═4891479c-9eb5-494e-ad1f-9bd52a171c57 # ╟─e1f1b7cf-d8ad-432d-ac98-95860e1ec65d # ╠═f7636969-d29b-415e-8fe4-d725b6fa97d0 +# ╟─c6dbd392-2269-4572-8b07-ff7f233b8c89 +# ╠═e686d58d-50ea-4789-93e7-5f3c41ee53ad +# ╟─70b6075c-b548-44d2-b301-9621023e06e0 +# ╠═cf4b988d-0774-4e5f-8ded-4db1ca869066 +# ╟─27fd182f-3cdc-4568-b6f5-cae1b5e2e1a2 +# ╠═b4bec7ca-ee1b-42e2-aea2-86e8b5c3dc46 +# ╟─01690a4a-33da-4207-86d9-c55d962f07ce +# ╠═8abcb814-472a-4bf9-a02c-b04a6c4a1084 # ╟─1b10ab40-5b24-4370-984d-cce1de0f95f5 # ╠═e941118d-5b63-4886-bacd-82291f4c01c4 # ╟─353e797c-6155-4d7b-bb79-440d7b8f8ae2 # ╠═05454fe0-2368-4c67-ad82-bec852c56b85 # ╟─8134f0d4-7719-42e0-8f72-6967115d6bb6 # ╠═b0f23e3a-3859-467f-a8d1-5bdcf05132f8 -# ╟─c6dbd392-2269-4572-8b07-ff7f233b8c89 -# ╠═e686d58d-50ea-4789-93e7-5f3c41ee53ad -# ╟─27fd182f-3cdc-4568-b6f5-cae1b5e2e1a2 -# ╠═6c421881-9df0-42c3-bf15-a1d4665bcb84 -# ╟─70b6075c-b548-44d2-b301-9621023e06e0 +# ╟─72736da6-917e-4cc1-8aff-0923d4c637e9 +# ╠═d20f4d1c-c8bc-41bd-8f9a-2ee7ee931697 # ╟─438b71e6-aca2-49b4-ab15-e747d335f331 +# ╟─a8ce787f-6a2c-4c97-940c-8331fbda1f3c +# ╠═87959af2-47b2-484c-8396-98f87a4abc2b +# ╟─7197dba8-4fb3-460b-8fe3-a0efc59a2d98 +# ╠═ad417629-caf6-4efd-8861-d7a40c58b53f +# ╟─a566fb16-6368-4edb-846c-0dc1917e15da # ╠═c733df10-79b1-4b72-8c74-fe1fabfead44 # ╟─8ce619a2-fe39-4b77-beda-bde92878cb86 # ╠═484fc544-157e-4fda-a97b-3c791063b1b8 @@ -1238,9 +571,13 @@ uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" # ╠═1d30b557-afbe-4527-84d7-d742f2abdb93 # ╟─a2f8c882-6fed-48f9-a554-c69e354ba0c0 # ╠═753c3b4c-861b-4787-b304-b4d3b1b84be0 +# ╟─5c6e9ace-de55-4b45-84f8-876e7c04f664 +# ╟─1a68e9eb-1e37-4049-9480-74cb185c7531 +# ╠═24ea3ffc-ec31-4b18-a8f6-b5ede42ff33c +# ╟─b8a81954-8115-4e88-9d19-4ea3abec15ed +# ╠═550ebcd5-9b7c-435e-9450-4eb5a94aac03 +# ╠═8b192733-2e26-4e9c-a4d9-45e8fbf3571d # ╠═32073778-e62a-46ee-9797-3988be5c8fba # ╟─6b1d3355-11ff-4aec-b6b2-b0fe0183dca6 # ╠═d418b2bf-a01d-43bf-8cd0-ca53be431a9f # ╟─5b3c882d-c7c0-4acc-9d05-fafde347e4ff -# ╟─00000000-0000-0000-0000-000000000001 -# ╟─00000000-0000-0000-0000-000000000002 diff --git a/examples/Block MLD Basic Example.jl b/examples/Block MLD Basic Example.jl new file mode 100644 index 00000000..da465ad1 --- /dev/null +++ b/examples/Block MLD Basic Example.jl @@ -0,0 +1,145 @@ +### A Pluto.jl notebook ### +# v0.19.5 + +using Markdown +using InteractiveUtils + +# ╔═╡ da854ce8-dba5-11ec-125f-8d121de9288f +using Pkg + +# ╔═╡ e8dd4b8e-bc68-408e-a6f6-32fec5f74942 +begin + Pkg.activate(;temp=true) + Pkg.add(Pkg.PackageSpec(;name="PowerModelsONM", rev="v3.0-rc")) + Pkg.add( + [ + "PowerModelsDistribution", + "InfrastructureModels", + "JuMP", + "HiGHS", + ] + ) +end + +# ╔═╡ 9c81254c-38ba-4b67-bf00-f067039d1706 +md"""# Block MLD Basic Example + +This notebook provides a basic example of how to apply only the block-mld problem to a single time step and solve the JuMP model. + +This example "decomposes" the problem, removing a lot of the automatic user-friendly steps so that users may understand how to access the JuMP model directly. + +""" + +# ╔═╡ 605b5a16-8d40-42e0-a9d7-d33640fdee00 +md"""## TEMP: Install necessary packages + +This section can be removed after release +""" + +# ╔═╡ 79be1100-92fa-4701-8617-8e316b4b49d2 +md"## Import necessary packages" + +# ╔═╡ aeac5a27-d7f9-451f-9a76-0486d03da582 +begin + import PowerModelsONM as ONM + import PowerModelsDistribution as PMD + import InfrastructureModels as IM + import JuMP + import HiGHS +end + +# ╔═╡ 9d5af5c9-7a5f-409b-a1fb-3f158af37153 +md"We will need the path to PowerModelsONM so that we can use the included data models" + +# ╔═╡ a5abfeb8-8f11-433b-a0d5-e0f33f0b6cb6 +onm_path = joinpath(dirname(pathof(ONM)), "..") + +# ╔═╡ 4a36eb92-35c7-4d73-9ed0-6a3ed0e756c5 +md"""## Parse data model using ONM functions + +This function auto-creates the multinetwork data structure, but for this example we will ignore it. +""" + +# ╔═╡ ecfdb2aa-98fe-4c13-afd7-7fb4ecd198df +eng, _ = ONM.parse_network("$onm_path/test/data/ieee13_feeder.dss") + +# ╔═╡ 5ead58cb-f5cb-4a7d-91ea-4158e5005cdd +md"At a minimum, the MLD problems require finite voltage bounds, so we apply the basic $$\pm$$0.1 p.u. " + +# ╔═╡ 3aa1c98d-e888-4595-b13f-44a3f01e9ac2 +PMD.apply_voltage_bounds!(eng; vm_lb=0.9, vm_ub=1.1) + +# ╔═╡ 971dd7d9-9789-4d20-86b2-19778cacc0c9 +md"Normally, the functions in PowerModelsONM and PowerModelsDistribution will automatically handle data conversion between the ENGINEERING and MATHEMATICAL data models, but to make it explicit we are converting it here to illustrate the different transformations" + +# ╔═╡ 0c07c4c1-e76c-4dc3-8bb2-1ccad2a092ea +math = ONM.transform_data_model(eng) + +# ╔═╡ 73493d29-f13e-4993-a033-b09cf7ec4040 +md"## Build the JuMP model" + +# ╔═╡ 7b395675-ed36-4587-b314-ce247d706ffe +pm = ONM.instantiate_onm_model(math, PMD.LPUBFDiagPowerModel, ONM.build_block_mld) + +# ╔═╡ ef3f8b62-0e09-45fd-908b-065a4447b67d +md"## Instantiate a solver and attach it" + +# ╔═╡ 7bcc1e19-f982-44cb-bbc0-df72eca756eb +solver = JuMP.optimizer_with_attributes( + HiGHS.Optimizer, + "presolve"=>"off", + "primal_feasibility_tolerance"=>1e-6, + "dual_feasibility_tolerance"=>1e-6, + "mip_feasibility_tolerance"=>1e-4, + "mip_rel_gap"=>1e-4, + "small_matrix_value"=>1e-12, + "allow_unbounded_or_infeasible"=>true +) + +# ╔═╡ 4c16cc15-c71e-46d0-a30e-841cffeee032 +JuMP.set_optimizer(pm.model, solver) + +# ╔═╡ fe6e5d8e-627f-45c5-8667-9870ec330c1d +md"## Optimize JuMP Model" + +# ╔═╡ bef50174-bdf5-4fbd-9ef9-7a581add3ed2 +JuMP.optimize!(pm.model) + +# ╔═╡ e9f3622a-e410-420c-87d7-d17c3146a62f +md"## Extract the result from the JuMP Model" + +# ╔═╡ fbe9f92c-19f0-46a0-ae50-a0e50fc2efd6 +result = IM.build_result(pm, JuMP.solve_time(pm.model); solution_processors=ONM._default_solution_processors) + +# ╔═╡ cd502839-64fd-4ac6-9fd6-c5798fa317d0 +md"## Convert the solution to the ENGINEERING model" + +# ╔═╡ 97346e89-e1c2-4190-82de-57959179f13f +sol_eng = PMD.transform_solution(result["solution"], math) + +# ╔═╡ Cell order: +# ╟─9c81254c-38ba-4b67-bf00-f067039d1706 +# ╟─605b5a16-8d40-42e0-a9d7-d33640fdee00 +# ╠═da854ce8-dba5-11ec-125f-8d121de9288f +# ╠═e8dd4b8e-bc68-408e-a6f6-32fec5f74942 +# ╟─79be1100-92fa-4701-8617-8e316b4b49d2 +# ╠═aeac5a27-d7f9-451f-9a76-0486d03da582 +# ╟─9d5af5c9-7a5f-409b-a1fb-3f158af37153 +# ╠═a5abfeb8-8f11-433b-a0d5-e0f33f0b6cb6 +# ╟─4a36eb92-35c7-4d73-9ed0-6a3ed0e756c5 +# ╠═ecfdb2aa-98fe-4c13-afd7-7fb4ecd198df +# ╟─5ead58cb-f5cb-4a7d-91ea-4158e5005cdd +# ╠═3aa1c98d-e888-4595-b13f-44a3f01e9ac2 +# ╟─971dd7d9-9789-4d20-86b2-19778cacc0c9 +# ╠═0c07c4c1-e76c-4dc3-8bb2-1ccad2a092ea +# ╟─73493d29-f13e-4993-a033-b09cf7ec4040 +# ╠═7b395675-ed36-4587-b314-ce247d706ffe +# ╟─ef3f8b62-0e09-45fd-908b-065a4447b67d +# ╠═7bcc1e19-f982-44cb-bbc0-df72eca756eb +# ╠═4c16cc15-c71e-46d0-a30e-841cffeee032 +# ╟─fe6e5d8e-627f-45c5-8667-9870ec330c1d +# ╠═bef50174-bdf5-4fbd-9ef9-7a581add3ed2 +# ╟─e9f3622a-e410-420c-87d7-d17c3146a62f +# ╠═fbe9f92c-19f0-46a0-ae50-a0e50fc2efd6 +# ╟─cd502839-64fd-4ac6-9fd6-c5798fa317d0 +# ╠═97346e89-e1c2-4190-82de-57959179f13f diff --git a/examples/JuMP Model by Hand - MLD-Block.jl b/examples/JuMP Model by Hand - MLD-Block.jl new file mode 100644 index 00000000..66b10756 --- /dev/null +++ b/examples/JuMP Model by Hand - MLD-Block.jl @@ -0,0 +1,1985 @@ +### A Pluto.jl notebook ### +# v0.19.9 + +using Markdown +using InteractiveUtils + +# ╔═╡ 14e4d41e-e5bd-11ec-3723-9daa31787999 +using Pkg + +# ╔═╡ f00a9624-13f6-4fcd-a673-bcb2eed06340 +# ╠═╡ show_logs = false +begin + Pkg.activate(;temp=true) + Pkg.add(Pkg.PackageSpec(;name="PowerModelsONM", rev="v3.0-rc")) + Pkg.add( + [ + "PowerModelsDistribution", + "InfrastructureModels", + "JuMP", + "HiGHS", + ] + ) +end + + +# ╔═╡ bbe09ba9-63fb-4b33-ae27-eb05cb9fd936 +html""" + +""" + +# ╔═╡ bdfca444-f5f0-413f-8a47-8346de453d12 +md"""# JuMP Model by Hand - MLD-Block + +This notebook is intended to illustrate how one would build the JuMP model for a MLD problem of the "block" type (i.e., `build_block_mld(pm::AbstractUBFModels)`) with the LinDist3Flow (i.e., `LPUBFDiagPowerModel`) formulation. + +## Outline + +### Environment Setup + +This is based on PowerModelsONM.jl#v3.0-rc, which is not yet released, and therefore requires manual setup of the environment using Pkg, overriding the built-in package management in Pluto notebooks. + +### Solver + +This notebook uses the [HiGHS solver](https://github.com/jump-dev/HiGHS.jl). + +### Data Model + +This notebook uses a modified IEEE-13 case that is [included in PowerModelsONM.jl](https://github.com/lanl-ansi/PowerModelsONM.jl/blob/v3.0-rc/test/data/ieee13_feeder.dss). + +What is loaded here is the single-network, **not** multinetwork (i.e., time-series) version of the feeder. + +There are two bounds that need to be included for the problem being defined in this notebook, voltage bounds on buses, which are applied via the function `apply_voltage_bounds!`, and a switch close-action upper bound. + +### JuMP Model + +Next we build two versions of the JuMP model. The first is one built using the included PowerModelsONM.jl functions: specifically, `instantiate_onm_model`. The second JuMP model is the one we build by hand, avoiding multiple dispatch, so as to make it explicit each variable and constraint that is contained in the model. + +### Model comparison + +At the end we do a quick comparison of the two models, and look at their solutions to ensure that they are equivalent. +""" + +# ╔═╡ 3b579de0-8d2a-4e94-8daf-0d3833a90ab4 +md"## Environment Setup" + +# ╔═╡ ad36afcf-6a7e-4913-b15a-5f19ba383b27 +begin + import PowerModelsONM as ONM + import PowerModelsDistribution as PMD + import InfrastructureModels as IM + import JuMP + import HiGHS + import LinearAlgebra +end + +# ╔═╡ 4de82775-e012-44a5-a440-d7f54792d284 +onm_path = joinpath(dirname(pathof(ONM)), "..") + +# ╔═╡ b41082c7-87f1-42e3-8c20-4d6cddc79375 +md"## Solver Instance Setup" + +# ╔═╡ 7b252a89-19e4-43ba-b795-24299074753e +solver = JuMP.optimizer_with_attributes( + HiGHS.Optimizer, + "presolve"=>"off", + "primal_feasibility_tolerance"=>1e-6, + "dual_feasibility_tolerance"=>1e-6, + "mip_feasibility_tolerance"=>1e-4, + "mip_rel_gap"=>1e-4, + "small_matrix_value"=>1e-12, + "allow_unbounded_or_infeasible"=>true +) + +# ╔═╡ d7dbea25-f1b1-4823-982b-0d5aa9d6ea26 +md"""## Data Model Setup + +This notebook uses the modified IEEE13 disitribution feeder case that is included in PowerModelsONM.jl + +This uses the `parse_file` function included in PowerModelsDistribution.jl, which is a dependency of PowerModelsONM.jl. +""" + +# ╔═╡ cc2aba3c-a412-4c20-8635-2cdcf369d2c8 +eng = PMD.parse_file(joinpath(onm_path, "test/data/ieee13_feeder.dss")) + +# ╔═╡ e66a945e-f437-4ed6-9702-1daf3bccc958 +md"""### Set max actions + +In order to run the block-mld problem in ONM, an upper bound for the number of switch closing-actions is required. In this case, because the network data is **not** multinetwork, we will set the upper bound to `Inf`, but typically one would want to apply a per-timestep limit to see a progression of switching actions. +""" + +# ╔═╡ 88beadb3-e87b-46e4-8aec-826324cd6112 +eng["switch_close_actions_ub"] = Inf + +# ╔═╡ 588344bb-6f8b-463e-896d-725ceb167cb4 +md"""### Apply voltage bounds + +For several of the linearizations of constraints, finite voltage bounds are required. Here we can apply voltage bounds using PowerModelsDistribution.jl's `apply_voltage_bounds!` function, which will apply per-unit bounds of `0.9 <= vm <= 1.1` by default, though those can be altered in the function call. +""" + +# ╔═╡ 8f41758a-5523-487d-9a5b-712ffec668ee +PMD.apply_voltage_bounds!(eng) + +# ╔═╡ b9582cb1-0f92-42ef-88b8-fb7e98ff6c3b +md"""### Convert ENGINEERING to MATHEMATICAL Model + +To build a JuMP model, we require the `MATHEMATICAL` representation of the network model, which is normally performed automatically by PowerModelsDistribution, but here we need to do it manually using `transform_data_model`. + +The ONM version of this function used below includes several augmentations required to pass along extra data parameters that are not contained in the base PowerModelsDistribution data models. +""" + +# ╔═╡ 6fa5d4f4-997d-4340-bdc5-1b2801815351 +math = ONM.transform_data_model(eng) + +# ╔═╡ ebe9dc84-f289-4ae4-bd26-6071106d6a28 +md"""### Build ref structure + +When building the JuMP model, we heavily utilitize a `ref` data structure, which creates several helper data structures that make iterating through the data easier. Again, normally this structure is created automatically, but to manually build a JuMP model we need to build it manually. Also, normally there are top-level keys that are not necessary for the building of this non-multi-infrastructure, non-multinetwork data model, so we have abstracted them out. +""" + +# ╔═╡ e096d427-0916-40be-8f05-444e8f37b410 +ref = IM.build_ref( + math, + PMD.ref_add_core!, + union(ONM._default_global_keys, PMD._pmd_math_global_keys), + PMD.pmd_it_name; + ref_extensions=ONM._default_ref_extensions +)[:it][:pmd][:nw][IM.nw_id_default] + + +# ╔═╡ e6496923-ee2b-46a0-9d81-624197d3cb02 +md"""## JuMP Model by Hand + +In this Section, we will actually build the JuMP Model by hand. + +First we need to create an empty JuMP Model. +""" + +# ╔═╡ afc66e0a-8aed-4d1a-9cf9-15f537b57b95 +model = JuMP.Model() + +# ╔═╡ 0590de28-76c6-485a-ae8e-bf76c0c9d924 +md"""### Variables + +This section will add all the variables necessary for the block-mld problem, in the same order that variables are created in `build_block_mld`. +""" + +# ╔═╡ 379efc70-7458-41f5-a8d4-dcdf59fc9a6e +md"""#### Block variables + +These variables are used to represent the "status", i.e., whether they are energized or not, of each of the possible load-blocks. +""" + +# ╔═╡ c19ed861-e91c-44e6-b0be-e4b56629481c +# variable_block_indicator +z_block = JuMP.@variable( + model, + [i in keys(ref[:blocks])], + base_name="0_z_block", + lower_bound=0, + upper_bound=1, + binary=true +) + +# ╔═╡ 4269aa45-2c4c-4be5-8776-d25b39e5fe90 +md"""#### Inverter variables + +These variables are used to represent whether an "inverter" object, i.e., generator, solar PV, energy storage, etc., are Grid Forming (`1`) or Grid Following (`0`). +""" + +# ╔═╡ 962476bf-fa55-484d-b9f6-fc09d1d891ee +# variable_inverter_indicator +z_inverter = Dict( + (t,i) => get(ref[t][i], "inverter", 1) == 1 ? JuMP.@variable( + model, + base_name="0_$(t)_z_inverter_$(i)", + binary=true, + lower_bound=0, + upper_bound=1, + ) : 0 for t in [:storage, :gen] for i in keys(ref[t]) +) + +# ╔═╡ 1480b91d-fcbb-46c1-9a47-c4daa99731a2 +md"""#### Bus voltage variables + +These variables are used to represent the squared voltage magnitudes `w` for each terminal on each bus. + +By default, voltage magnitudes have a lower bound of `0.0` to avoid infeasibilities, since this is an on-off problem. There are constraints applied later that enforce lower-bounds based on `z_block`. +""" + +# ╔═╡ 04eea7b8-ff6c-4650-b01e-31301257ded4 +# variable_mc_bus_voltage_on_off -> variable_mc_bus_voltage_magnitude_sqr_on_off +w = Dict( + i => JuMP.@variable( + model, + [t in bus["terminals"]], + base_name="0_w_$i", + lower_bound=0, + ) for (i,bus) in ref[:bus] +) + +# ╔═╡ 05312fc9-b125-42e8-a9bd-7129f63ddc9a +md"As noted above, we do require finite upper bounds on voltage magnitudes" + +# ╔═╡ 91014d35-e30b-4af7-9324-3cde48242342 +# w bounds +for (i,bus) in ref[:bus] + for (idx,t) in enumerate(bus["terminals"]) + isfinite(bus["vmax"][idx]) && JuMP.set_upper_bound(w[i][t], bus["vmax"][idx]^2) + end +end + +# ╔═╡ 3277219e-589d-47db-9374-e6712a4a40c4 +md"""#### Branch variables + +`variable_mc_branch_power` + +These variables represent the real and reactive powers on the from- and to-sides of each of the branches, for each from- and to-connection on that branch. +""" + +# ╔═╡ ac115a18-ce73-436a-800e-a83b27c6cee7 +branch_connections = Dict((l,i,j) => connections for (bus,entry) in ref[:bus_arcs_conns_branch] for ((l,i,j), connections) in entry) + +# ╔═╡ bca9289f-bf4f-4ec2-af5f-373b70b4e614 +# variable_mc_branch_power_real +p = Dict( + Dict( + (l,i,j) => JuMP.@variable( + model, + [c in ref[:branch][l]["f_connections"]], + base_name="0_p_($l,$i,$j)" + ) for (l,i,j) in ref[:arcs_branch_from] + )..., + Dict( + (l,i,j) => JuMP.@variable( + model, + [c in ref[:branch][l]["t_connections"]], + base_name="0_p_($l,$i,$j)" + ) for (l,i,j) in ref[:arcs_branch_to] + )..., +) + +# ╔═╡ beb258c4-97da-4044-b8d1-abc695e8a910 +# p bounds +for (l,i,j) in ref[:arcs_branch] + smax = PMD._calc_branch_power_max(ref[:branch][l], ref[:bus][i]) + for (idx, c) in enumerate(branch_connections[(l,i,j)]) + PMD.set_upper_bound(p[(l,i,j)][c], smax[idx]) + PMD.set_lower_bound(p[(l,i,j)][c], -smax[idx]) + end +end + +# ╔═╡ e00d2fdc-a416-4259-b29e-b5608897da9b +# variable_mc_branch_power_imaginary +q = Dict( + Dict( + (l,i,j) => JuMP.@variable( + model, + [c in ref[:branch][l]["f_connections"]], + base_name="0_q_($l,$i,$j)" + ) for (l,i,j) in ref[:arcs_branch_from] + )..., + Dict( + (l,i,j) => JuMP.@variable( + model, + [c in ref[:branch][l]["t_connections"]], + base_name="0_q_($l,$i,$j)" + ) for (l,i,j) in ref[:arcs_branch_to] + )..., +) + +# ╔═╡ 855a0057-610a-4274-86bb-95ceef674257 +# q bounds +for (l,i,j) in ref[:arcs_branch] + smax = PMD._calc_branch_power_max(ref[:branch][l], ref[:bus][i]) + for (idx, c) in enumerate(branch_connections[(l,i,j)]) + PMD.set_upper_bound(q[(l,i,j)][c], smax[idx]) + PMD.set_lower_bound(q[(l,i,j)][c], -smax[idx]) + end +end + +# ╔═╡ 080a174a-c63b-4284-a06d-1031fda7e3a9 +md"""#### Switch variables + +`variable_mc_switch_power` + +These variables represent the from- and to-side real and reactive powers on switches for each from- and to-side connection on the switch. + +Because switches are modeled as zero-length objects, the from- and to-side powers are equivalent, and therefore an explicit type erasure is necessary. +""" + +# ╔═╡ 3177e943-c635-493a-9be6-c2ade040c447 +switch_arc_connections = Dict((l,i,j) => connections for (bus,entry) in ref[:bus_arcs_conns_switch] for ((l,i,j), connections) in entry) + +# ╔═╡ 5e0f7d2d-d6f9-40d9-b3a4-4404c2c66950 +# variable_mc_switch_power_real +psw = Dict( + Dict( + (l,i,j) => JuMP.@variable( + model, + [c in switch_arc_connections[(l,i,j)]], + base_name="0_psw_($l,$i,$j)" + ) for (l,i,j) in ref[:arcs_switch_from] + )..., + Dict( + (l,i,j) => JuMP.@variable( + model, + [c in switch_arc_connections[(l,i,j)]], + base_name="0_psw_($l,$i,$j)" + ) for (l,i,j) in ref[:arcs_switch_to] + )..., +) + +# ╔═╡ 6c8163e3-5a18-4561-a9f4-834e42657f7d +# _psw bounds +for (l,i,j) in ref[:arcs_switch] + smax = PMD._calc_branch_power_max(ref[:switch][l], ref[:bus][i]) + for (idx, c) in enumerate(switch_arc_connections[(l,i,j)]) + PMD.set_upper_bound(psw[(l,i,j)][c], smax[idx]) + PMD.set_lower_bound(psw[(l,i,j)][c], -smax[idx]) + end +end + +# ╔═╡ 9f98ca07-532d-4fc6-a1bd-13a182b0db50 +# this explicit type erasure is necessary +begin + psw_expr = Dict( (l,i,j) => psw[(l,i,j)] for (l,i,j) in ref[:arcs_switch_from] ) + psw_expr = merge(psw_expr, Dict( (l,j,i) => -1.0.*psw[(l,i,j)] for (l,i,j) in ref[:arcs_switch_from])) + + # This is needed to get around error: "unexpected affine expression in nlconstraint" + psw_auxes = Dict( + (l,i,j) => JuMP.@variable( + model, [c in switch_arc_connections[(l,i,j)]], + base_name="0_psw_aux_$((l,i,j))" + ) for (l,i,j) in ref[:arcs_switch] + ) + for ((l,i,j), psw_aux) in psw_auxes + for (idx, c) in enumerate(switch_arc_connections[(l,i,j)]) + JuMP.@constraint(model, psw_expr[(l,i,j)][c] == psw_aux[c]) + end + end + + # overwrite psw + for (k,psw_aux) in psw_auxes + psw[k] = psw_aux + end +end + +# ╔═╡ 7f709599-084b-433f-9b6a-6ded827b69f2 +# variable_mc_switch_power_imaginary +qsw = Dict( + Dict( + (l,i,j) => JuMP.@variable( + model, + [c in switch_arc_connections[(l,i,j)]], + base_name="0_qsw_($l,$i,$j)" + ) for (l,i,j) in ref[:arcs_switch_from] + )..., + Dict( + (l,i,j) => JuMP.@variable( + model, + [c in switch_arc_connections[(l,i,j)]], + base_name="0_qsw_($l,$i,$j)" + ) for (l,i,j) in ref[:arcs_switch_to] + )..., +) + +# ╔═╡ b84ba9e4-5ce2-4b10-bb45-eed88c6a4bbe +# qsw bounds +for (l,i,j) in ref[:arcs_switch] + smax = PMD._calc_branch_power_max(ref[:switch][l], ref[:bus][i]) + for (idx, c) in enumerate(switch_arc_connections[(l,i,j)]) + PMD.set_upper_bound(qsw[(l,i,j)][c], smax[idx]) + PMD.set_lower_bound(qsw[(l,i,j)][c], -smax[idx]) + end +end + +# ╔═╡ 5e538b33-20ae-4520-92ec-efc01494ffcc +# this explicit type erasure is necessary +begin + qsw_expr = Dict( (l,i,j) => qsw[(l,i,j)] for (l,i,j) in ref[:arcs_switch_from] ) + qsw_expr = merge(qsw_expr, Dict( (l,j,i) => -1.0.*qsw[(l,i,j)] for (l,i,j) in ref[:arcs_switch_from])) + + # This is needed to get around error: "unexpected affine expression in nlconstraint" + qsw_auxes = Dict( + (l,i,j) => JuMP.@variable( + model, [c in switch_arc_connections[(l,i,j)]], + base_name="0_qsw_aux_$((l,i,j))" + ) for (l,i,j) in ref[:arcs_switch] + ) + for ((l,i,j), qsw_aux) in qsw_auxes + for (idx, c) in enumerate(switch_arc_connections[(l,i,j)]) + JuMP.@constraint(model, qsw_expr[(l,i,j)][c] == qsw_aux[c]) + end + end + + # overwrite psw + for (k,qsw_aux) in qsw_auxes + qsw[k] = qsw_aux + end +end + +# ╔═╡ 44b283ee-e28c-473d-922f-8f1b8f982f10 +# variable_switch_state +z_switch = Dict(i => JuMP.@variable( + model, + base_name="0_switch_state", + binary=true, + lower_bound=0, + upper_bound=1, +) for i in keys(ref[:switch_dispatchable])) + +# ╔═╡ e910ae7a-680e-44a5-a35d-cabe2dfa50d0 +# fixed switches +for i in [i for i in keys(ref[:switch]) if !(i in keys(ref[:switch_dispatchable]))] + z_switch[i] = ref[:switch][i]["state"] +end + +# ╔═╡ 44fe57a1-edce-45c7-9a8b-40857bddc285 +md"""#### Transformer variables + +`variable_mc_transformer_power` + +These variables represent the from- and to-side real and reactive powers for transformers for each from- and to-side connection. +""" + +# ╔═╡ 06523a91-4665-4e31-b6e2-732cbfd0e0e4 +transformer_connections = Dict((l,i,j) => connections for (bus,entry) in ref[:bus_arcs_conns_transformer] for ((l,i,j), connections) in entry) + +# ╔═╡ 867253fa-32ee-4ab4-bc42-3f4c2f0e5fa4 +# variable_mc_transformer_power_real +pt = Dict( + Dict( + (l,i,j) => JuMP.@variable( + model, + [c in transformer_connections[(l,i,j)]], + base_name="0_pt_($l,$i,$j)" + ) for (l,i,j) in ref[:arcs_transformer_from] + )..., + Dict( + (l,i,j) => JuMP.@variable( + model, + [c in transformer_connections[(l,i,j)]], + base_name="0_pt_($l,$i,$j)" + ) for (l,i,j) in ref[:arcs_transformer_to] + )..., +) + +# ╔═╡ 6e61aac8-5a50-47a7-a150-6557a47e2d3b +# pt bounds +for arc in ref[:arcs_transformer_from] + (l,i,j) = arc + rate_a_fr, rate_a_to = PMD._calc_transformer_power_ub_frto(ref[:transformer][l], ref[:bus][i], ref[:bus][j]) + + for (idx, (fc,tc)) in enumerate(zip(transformer_connections[(l,i,j)], transformer_connections[(l,j,i)])) + PMD.set_lower_bound(pt[(l,i,j)][fc], -rate_a_fr[idx]) + PMD.set_upper_bound(pt[(l,i,j)][fc], rate_a_fr[idx]) + PMD.set_lower_bound(pt[(l,j,i)][tc], -rate_a_to[idx]) + PMD.set_upper_bound(pt[(l,j,i)][tc], rate_a_to[idx]) + end +end + +# ╔═╡ a675e62f-c55e-4d70-85d8-83b5845cd063 +# variable_mc_transformer_power_imaginary +qt = Dict( + Dict( + (l,i,j) => JuMP.@variable( + model, + [c in transformer_connections[(l,i,j)]], + base_name="0_qt_($l,$i,$j)" + ) for (l,i,j) in ref[:arcs_transformer_from] + )..., + Dict( + (l,i,j) => JuMP.@variable( + model, + [c in transformer_connections[(l,i,j)]], + base_name="0_qt_($l,$i,$j)" + ) for (l,i,j) in ref[:arcs_transformer_to] + )..., +) + +# ╔═╡ 732df933-40ca-409c-9d88-bb80ea6d21b0 +# qt bounds +for arc in ref[:arcs_transformer_from] + (l,i,j) = arc + rate_a_fr, rate_a_to = PMD._calc_transformer_power_ub_frto(ref[:transformer][l], ref[:bus][i], ref[:bus][j]) + + for (idx, (fc,tc)) in enumerate(zip(transformer_connections[(l,i,j)], transformer_connections[(l,j,i)])) + PMD.set_lower_bound(qt[(l,i,j)][fc], -rate_a_fr[idx]) + PMD.set_upper_bound(qt[(l,i,j)][fc], rate_a_fr[idx]) + PMD.set_lower_bound(qt[(l,j,i)][tc], -rate_a_to[idx]) + PMD.set_upper_bound(qt[(l,j,i)][tc], rate_a_to[idx]) + end +end + +# ╔═╡ 17002ccb-16c2-449c-849a-70f090fea5e6 +md""" +`variable_mc_oltc_tansformer_tap` + +The following variables represent the tap ratio of the transformer for each non-fixed-tap connection on the transformer""" + +# ╔═╡ fdb80bf1-8c88-474e-935c-9e7c230b5b72 +p_oltc_ids = [id for (id,trans) in ref[:transformer] if !all(trans["tm_fix"])] + +# ╔═╡ 9de1c3d1-fb60-42e2-8d53-111842337458 +# variable_mc_oltc_transformer_tap +tap = Dict( + i => JuMP.@variable( + model, + [p in 1:length(ref[:transformer][i]["f_connections"])], + base_name="0_tm_$(i)", + ) for i in filter(x->!all(x.second["tm_fix"]), ref[:transformer]) +) + +# ╔═╡ 7cf6b40c-f89b-44bc-847d-a06a92d86098 +# tap bounds +for tr_id in p_oltc_ids, p in 1:length(ref[:transformer][tr_id]["f_connections"]) + PMD.set_lower_bound(tap[tr_id][p], ref[:transformer][tr_id]["tm_lb"][p]) + PMD.set_upper_bound(tap[tr_id][p], ref[:transformer][tr_id]["tm_ub"][p]) +end + +# ╔═╡ 9d51b315-b501-4140-af02-b645f04ec7a7 +md"""#### Generator variables + +`variable_mc_generator_power_on_off` + +The following variables represent the real and reactive powers for each connection of generator objects. + +Because these are "on-off" variables, the bounds need to at least include `0.0` +""" + +# ╔═╡ dabecbec-8cd0-48f7-8a13-0bdecd45eb85 +# variable_mc_generator_power_real_on_off +pg = Dict( + i => JuMP.@variable( + model, + [c in gen["connections"]], + base_name="0_pg_$(i)", + ) for (i,gen) in ref[:gen] +) + +# ╔═╡ c0764ed0-4b2c-4bf5-98db-9b7349560530 +# pg bounds +for (i,gen) in ref[:gen] + for (idx,c) in enumerate(gen["connections"]) + isfinite(gen["pmin"][idx]) && JuMP.set_lower_bound(pg[i][c], min(0.0, gen["pmin"][idx])) + isfinite(gen["pmax"][idx]) && JuMP.set_upper_bound(pg[i][c], gen["pmax"][idx]) + end +end + +# ╔═╡ 733cb346-2d08-4c35-8596-946b31ecc7e9 +# variable_mc_generator_power_imaginary_on_off +qg = Dict( + i => JuMP.@variable( + model, + [c in gen["connections"]], + base_name="0_qg_$(i)", + ) for (i,gen) in ref[:gen] +) + +# ╔═╡ 466f22aa-52ff-442f-be00-f4f32e24a173 +# qg bounds +for (i,gen) in ref[:gen] + for (idx,c) in enumerate(gen["connections"]) + isfinite(gen["qmin"][idx]) && JuMP.set_lower_bound(qg[i][c], min(0.0, gen["qmin"][idx])) + isfinite(gen["qmax"][idx]) && JuMP.set_upper_bound(qg[i][c], gen["qmax"][idx]) + end +end + +# ╔═╡ e10f9a86-74f1-4dfb-87c9-fcd920e23c27 +md"""#### Storage variables + +`variable_mc_storage_power_mi_on_off` + +These variables represent all of the variables that are required to model storage objects, including: + +- real and reactive power variables +- imaginary power control variables +- stored energy +- charging and discharging variables +- indicator variables for charging and discharging +""" + +# ╔═╡ efc78626-3a50-4c7d-8a7d-ba2b67df57e3 +# variable_mc_storage_power_real_on_off +ps = Dict( + i => JuMP.@variable( + model, + [c in ref[:storage][i]["connections"]], + base_name="0_ps_$(i)", + ) for i in keys(ref[:storage]) +) + +# ╔═╡ e841b4d8-1e8e-4fd9-b805-4ee0c6359df5 +# ps bounds +for (i,strg) in ref[:storage] + flow_lb, flow_ub = PMD.ref_calc_storage_injection_bounds(ref[:storage], ref[:bus]) + for (idx, c) in enumerate(strg["connections"]) + if !isinf(flow_lb[i][idx]) + PMD.set_lower_bound(ps[i][c], flow_lb[i][idx]) + end + if !isinf(flow_ub[i][idx]) + PMD.set_upper_bound(ps[i][c], flow_ub[i][idx]) + end + end +end + +# ╔═╡ de4839e1-5ac0-415d-8928-e4a9a358deae +# variable_mc_storage_power_imaginary_on_off +qs = Dict( + i => JuMP.@variable( + model, + [c in ref[:storage][i]["connections"]], + base_name="0_qs_$(i)", + ) for i in keys(ref[:storage]) +) + +# ╔═╡ 1b858a96-f894-4276-90a2-aa9833d9dd37 +# qs bounds +for (i,strg) in ref[:storage] + flow_lb, flow_ub =PMD.ref_calc_storage_injection_bounds(ref[:storage], ref[:bus]) + for (idx, c) in enumerate(strg["connections"]) + if !isinf(flow_lb[i][idx]) + PMD.set_lower_bound(qs[i][c], flow_lb[i][idx]) + end + if !isinf(flow_ub[i][idx]) + PMD.set_upper_bound(qs[i][c], flow_ub[i][idx]) + end + end +end + +# ╔═╡ 70dec0fa-a87c-4266-819d-a2ad5903d24a +# variable_mc_storage_power_control_imaginary_on_off +qsc = JuMP.@variable( + model, + [i in keys(ref[:storage])], + base_name="0_qsc_$(i)" +) + +# ╔═╡ 463ae91e-5533-46d0-8907-32f9d5ba17cf +# qsc bounds +for (i,storage) in ref[:storage] + inj_lb, inj_ub = PMD.ref_calc_storage_injection_bounds(ref[:storage], ref[:bus]) + if isfinite(sum(inj_lb[i])) || haskey(storage, "qmin") + lb = max(sum(inj_lb[i]), sum(get(storage, "qmin", -Inf))) + JuMP.set_lower_bound(qsc[i], min(lb, 0.0)) + end + if isfinite(sum(inj_ub[i])) || haskey(storage, "qmax") + ub = min(sum(inj_ub[i]), sum(get(storage, "qmax", Inf))) + JuMP.set_upper_bound(qsc[i], max(ub, 0.0)) + end +end + +# ╔═╡ 00aa935b-0f1a-43ae-8437-bde5e34c1fcd +# variable_storage_energy +se = JuMP.@variable(model, + [i in keys(ref[:storage])], + base_name="0_se", + lower_bound = 0.0, +) + +# ╔═╡ cafb8b69-ebc1-49d6-afe5-ff8af54eb222 +# se bounds +for (i, storage) in ref[:storage] + PMD.set_upper_bound(se[i], storage["energy_rating"]) +end + +# ╔═╡ bc2c0bea-621c-45f6-bc72-3d8907a280dc +# variable_storage_charge +sc = JuMP.@variable(model, + [i in keys(ref[:storage])], + base_name="0_sc", + lower_bound = 0.0, +) + +# ╔═╡ 503bdbad-70f8-42d2-977b-af6ba06b2cde +# sc bounds +for (i, storage) in ref[:storage] + PMD.set_upper_bound(sc[i], storage["charge_rating"]) +end + +# ╔═╡ e8dfb521-6750-4df6-b4ff-0cabf5989e8f +# variable_storage_discharge +sd = JuMP.@variable(model, + [i in keys(ref[:storage])], + base_name="0_sd", + lower_bound = 0.0, +) + +# ╔═╡ 70850ada-165a-4e0d-942a-9dc311add0a6 +# sd bounds +for (i, storage) in ref[:storage] + PMD.set_upper_bound(sd[i], storage["discharge_rating"]) +end + +# ╔═╡ d226e83d-b405-4dd3-9697-471bdbff97a2 +# variable_storage_complementary_indicator +sc_on = JuMP.@variable(model, + [i in keys(ref[:storage])], + base_name="0_sc_on", + binary = true, + lower_bound=0, + upper_bound=1 +) + +# ╔═╡ 050f3e9f-62e9-445d-8c95-9f0419c01c0e +# variable_storage_complementary_indicator +sd_on = JuMP.@variable(model, + [i in keys(ref[:storage])], + base_name="0_sd_on", + binary = true, + lower_bound=0, + upper_bound=1 +) + +# ╔═╡ eb1af86d-a40c-411d-a211-d7a43386bf44 +md"""#### Load variables + +`variable_mc_load_power` + +This initializes some power variables for loads. At this point, only variables for certain types of loads are required, and otherwise the load variables are largely created by the load constraints that are applied later on. + +The types of loads that need to be created ahead of time are: + +- Complex power matrix variables for delta loads +- Complex current matrix variables for delta loads +- Real and reactive power variables for wye loads that require cone constraints (e.g., constant current loads) + +We also want to create the empty variable dictionaries so that we can populate them with the constraints later. + +It should be noted that there are two variables for loads, `pd/qd_bus` and `pd/qd`. The `_bus` variables are related to the non-`_bus` variables depending on the type of connection of the load. See [PowerModelsDistribution documentation](https://lanl-ansi.github.io/PowerModelsDistribution.jl/stable/manual/load-model.html) for details. +""" + +# ╔═╡ ace2c946-7984-4c17-bedb-06dccd6e8a36 +load_wye_ids = [id for (id, load) in ref[:load] if load["configuration"]==PMD.WYE] + +# ╔═╡ 8386d993-ffcc-4c6a-a91b-247f8c97a2ff +load_del_ids = [id for (id, load) in ref[:load] if load["configuration"]==PMD.DELTA] + +# ╔═╡ b5408b8a-eff4-4d42-9ba7-707a40d92956 +load_cone_ids = [id for (id, load) in ref[:load] if PMD._check_load_needs_cone(load)] + +# ╔═╡ b7a7e78a-8f0f-4f47-9f37-8ecf3ddc4972 +load_connections = Dict{Int,Vector{Int}}(id => load["connections"] for (id,load) in ref[:load]) + +# ╔═╡ 86d65cab-d073-4e77-bc0f-3d7e135dcbf8 +begin + pd = Dict() + qd = Dict() + pd_bus = Dict() + qd_bus = Dict() + + # variable_mc_load_power_delta_aux + bound = Dict{eltype(load_del_ids), Matrix{Real}}() + for id in load_del_ids + load = ref[:load][id] + bus_id = load["load_bus"] + bus = ref[:bus][bus_id] + cmax = PMD._calc_load_current_max(load, bus) + bound[id] = bus["vmax"][[findfirst(isequal(c), bus["terminals"]) for c in load_connections[id]]]*cmax' + end + (Xdr,Xdi) = PMD.variable_mx_complex(model, load_del_ids, load_connections, load_connections; symm_bound=bound, name="0_Xd") + + # variable_mc_load_current + cmin = Dict{eltype(load_del_ids), Vector{Real}}() + cmax = Dict{eltype(load_del_ids), Vector{Real}}() + for (id, load) in ref[:load] + bus_id = load["load_bus"] + bus = ref[:bus][bus_id] + cmin[id], cmax[id] = PMD._calc_load_current_magnitude_bounds(load, bus) + end + (CCdr, CCdi) = PMD.variable_mx_hermitian(model, load_del_ids, load_connections; sqrt_upper_bound=cmax, sqrt_lower_bound=cmin, name="0_CCd") + + # variable_mc_load_power + for i in intersect(load_wye_ids, load_cone_ids) + pd[i] = JuMP.@variable( + model, + [c in load_connections[i]], + base_name="0_pd_$(i)" + ) + qd[i] = JuMP.@variable( + model, + [c in load_connections[i]], + base_name="0_qd_$(i)" + ) + + load = ref[:load][i] + bus = ref[:bus][load["load_bus"]] + pmin, pmax, qmin, qmax = PMD._calc_load_pq_bounds(load, bus) + for (idx,c) in enumerate(load_connections[i]) + PMD.set_lower_bound(pd[i][c], pmin[idx]) + PMD.set_upper_bound(pd[i][c], pmax[idx]) + PMD.set_lower_bound(qd[i][c], qmin[idx]) + PMD.set_upper_bound(qd[i][c], qmax[idx]) + end + end +end + +# ╔═╡ 80c50ee0-fb55-4c2c-86dd-434524d1a5e7 +md"""#### Capacitor variables + +`variable_mc_capcontrol` + +This model includes the ability to support capacitor controls (i.e., CapControl objects in DSS). + +These variables represent + +- indicator variables for the capacitor (shunt) objects +- reactive power variables for the capacitor (shunt) objects +""" + +# ╔═╡ dc4d7b85-c968-4271-9e44-f80b90e4d6af +# variable_mc_capacitor_switch_state +z_cap = Dict( + i => JuMP.@variable( + model, + [p in cap["connections"]], + base_name="0_cap_sw_$(i)", + binary = true, + ) for (i,cap) in [(id,cap) for (id,cap) in ref[:shunt] if haskey(cap,"controls")] +) + +# ╔═╡ dfafbbcd-9465-4a78-867b-25703b5157ba +# variable_mc_capacitor_reactive_power +qc = Dict( + i => JuMP.@variable( + model, + [p in cap["connections"]], + base_name="0_cap_cur_$(i)", + ) for (i,cap) in [(id,cap) for (id,cap) in ref[:shunt] if haskey(cap,"controls")] +) + +# ╔═╡ cae714ed-ac90-454f-b2ec-e3bb13a71056 +md"""### Constraints + +In this section we add our constraints +""" + +# ╔═╡ 47f8d8f4-c6e3-4f78-93d3-c5bb4938a754 +md"""#### Inverter constraint + +This constraint requires that there be only one Grid Forming inverter (`z_inverter=1`) for any given connected-component. +""" + +# ╔═╡ 378f45ee-2e0e-428b-962f-fd686bc5d063 +# constraint_grid_forming_inverter_per_cc +begin + L = Set{Int}(keys(ref[:blocks])) + + y = Dict() + for k in L + for ab in keys(ref[:switch]) + y[(k,ab)] = JuMP.@variable( + model, + base_name="0_y", + binary=true, + lower_bound=0, + upper_bound=1 + ) + end + end + + z = z_switch + x = z_inverter + + for ((t,j), z_inv) in x + if t == :gen && startswith(ref[t][j]["source_id"], "voltage_source") + JuMP.@constraint(model, z_inv == 1) + end + end + + for ab in keys(ref[:switch]) + JuMP.@constraint(model, sum(y[(k,ab)] for k in L) == 1) + end + + for k in L + Dₖ = ref[:block_inverters][k] + Tₖ = ref[:block_switches][k] + + if !isempty(Dₖ) + JuMP.@constraint(model, sum(x[i] for i in Dₖ) >= sum(1-z[ab] for ab in Tₖ)-length(Tₖ)+1) + JuMP.@constraint(model, sum(x[i] for i in Dₖ) <= 1) + end + + for ab in Tₖ + JuMP.@constraint(model, sum(x[i] for i in Dₖ) >= y[(k, ab)] - (1 - z[ab])) + JuMP.@constraint(model, sum(x[i] for i in Dₖ) <= y[(k, ab)] + (1 - z[ab])) + + for dc in filter(x->x!=ab, Tₖ) + for k′ in L + JuMP.@constraint(model, y[(k′,ab)] >= y[(k′,dc)] - (1 - z[dc]) - (1 - z[ab])) + JuMP.@constraint(model, y[(k′,ab)] <= y[(k′,dc)] + (1 - z[dc]) + (1 - z[ab])) + end + end + end + end +end + +# ╔═╡ 4bfb96ae-2087-41a8-b9b0-3f4b346992a2 +md"""#### Bus constraints + +There are two contraints on buses: + +- a constraint that enforces that a bus connected to a grid-forming inverter is a slack bus +- an "on-off" constraint that enforces that bus voltage is zero if the load block is not energized (`z_block=0`) +""" + +# ╔═╡ b7a30f17-1f3b-497a-ab1c-bc9ce1ac6e56 +# constraint_mc_inverter_theta_ref +for (i,bus) in ref[:bus] + # reference bus "theta" constraint + vmax = min(bus["vmax"]..., 2.0) + if isfinite(vmax) + if length(w[i]) > 1 && !isempty([z_inverter[inv_obj] for inv_obj in ref[:bus_inverters][i]]) + for t in 2:length(w[i]) + JuMP.@constraint(model, w[i][t] - w[i][1] <= vmax^2 * (1 - sum([z_inverter[inv_obj] for inv_obj in ref[:bus_inverters][i]]))) + JuMP.@constraint(model, w[i][t] - w[i][1] >= -vmax^2 * (1 - sum([z_inverter[inv_obj] for inv_obj in ref[:bus_inverters][i]]))) + end + end + end +end + +# ╔═╡ d1136370-9fc2-47c6-a773-d4dc7901db83 +# constraint_mc_bus_voltage_block_on_off +for (i,bus) in ref[:bus] + # bus voltage on off constraint + for (idx,t) in [(idx,t) for (idx,t) in enumerate(bus["terminals"]) if !bus["grounded"][idx]] + isfinite(bus["vmax"][idx]) && JuMP.@constraint(model, w[i][t] <= bus["vmax"][idx]^2*z_block[ref[:bus_block_map][i]]) + isfinite(bus["vmin"][idx]) && JuMP.@constraint(model, w[i][t] >= bus["vmin"][idx]^2*z_block[ref[:bus_block_map][i]]) + end +end + +# ╔═╡ 8e564c5e-8c0e-4001-abaa-bf9575d41089 +md"""#### Generator constraints + +Generators need "on-off" constraints that enforce that a generator is "off" if the load block containing it is not energized (`z_block=0`) +""" + +# ╔═╡ 05b0aad1-a41b-4fe7-8b76-70848f71d9d2 +# constraint_mc_generator_power_block_on_off +for (i,gen) in ref[:gen] + for (idx, c) in enumerate(gen["connections"]) + isfinite(gen["pmin"][idx]) && JuMP.@constraint(model, pg[i][c] >= gen["pmin"][idx]*z_block[ref[:gen_block_map][i]]) + isfinite(gen["qmin"][idx]) && JuMP.@constraint(model, qg[i][c] >= gen["qmin"][idx]*z_block[ref[:gen_block_map][i]]) + + isfinite(gen["pmax"][idx]) && JuMP.@constraint(model, pg[i][c] <= gen["pmax"][idx]*z_block[ref[:gen_block_map][i]]) + isfinite(gen["qmax"][idx]) && JuMP.@constraint(model, qg[i][c] <= gen["qmax"][idx]*z_block[ref[:gen_block_map][i]]) + end +end + +# ╔═╡ 074845c0-f5ae-4a7c-bf94-3fcade5fdab8 +md"""#### Load constraints + +The following creates the load power constraints for the different supported load configurations (wye or delta) and types (constant power, constant impedance, and constant current). +""" + +# ╔═╡ 8be57ed0-0c7e-40d5-b780-28eb9f9c2490 +# constraint_mc_load_power +for (load_id,load) in ref[:load] + pd0 = load["pd"] + qd0 = load["qd"] + bus_id = load["load_bus"] + bus = ref[:bus][bus_id] + terminals = bus["terminals"] + + a, alpha, b, beta = PMD._load_expmodel_params(load, bus) + vmin, vmax = PMD._calc_load_vbounds(load, bus) + wmin = vmin.^2 + wmax = vmax.^2 + pmin, pmax, qmin, qmax = PMD._calc_load_pq_bounds(load, bus) + + if load["configuration"]==PMD.WYE + if load["model"]==PMD.POWER + pd[load_id] = JuMP.Containers.DenseAxisArray(pd0, load["connections"]) + qd[load_id] = JuMP.Containers.DenseAxisArray(qd0, load["connections"]) + elseif load["model"]==PMD.IMPEDANCE + _w = w[bus_id][[c for c in load["connections"]]] + pd[load_id] = a.*_w + qd[load_id] = b.*_w + else + for (idx,c) in enumerate(load["connections"]) + JuMP.@constraint(model, pd[load_id][c]==1/2*a[idx]*(w[bus_id][c]+1)) + JuMP.@constraint(model, qd[load_id][c]==1/2*b[idx]*(w[bus_id][c]+1)) + end + end + + pd_bus[load_id] = pd[load_id] + qd_bus[load_id] = qd[load_id] + + elseif load["configuration"]==PMD.DELTA + Td = [1 -1 0; 0 1 -1; -1 0 1] + + pd_bus[load_id] = LinearAlgebra.diag(Xdr[load_id]*Td) + qd_bus[load_id] = LinearAlgebra.diag(Xdi[load_id]*Td) + pd[load_id] = LinearAlgebra.diag(Td*Xdr[load_id]) + qd[load_id] = LinearAlgebra.diag(Td*Xdi[load_id]) + + for (idx, c) in enumerate(load["connections"]) + if abs(pd0[idx]+im*qd0[idx]) == 0.0 + JuMP.@constraint(model, Xdr[load_id][:,idx] .== 0) + JuMP.@constraint(model, Xdi[load_id][:,idx] .== 0) + end + end + + if load["model"]==PMD.POWER + for (idx, c) in enumerate(load["connections"]) + JuMP.@constraint(model, pd[load_id][idx]==pd0[idx]) + JuMP.@constraint(model, qd[load_id][idx]==qd0[idx]) + end + elseif load["model"]==PMD.IMPEDANCE + for (idx,c) in enumerate(load["connections"]) + JuMP.@constraint(model, pd[load_id][idx]==3*a[idx]*w[bus_id][[c for c in load["connections"]]][idx]) + JuMP.@constraint(model, qd[load_id][idx]==3*b[idx]*w[bus_id][[c for c in load["connections"]]][idx]) + end + else + for (idx,c) in enumerate(load["connections"]) + JuMP.@constraint(model, pd[load_id][idx]==sqrt(3)/2*a[idx]*(w[bus_id][[c for c in load["connections"]]][idx]+1)) + JuMP.@constraint(model, qd[load_id][idx]==sqrt(3)/2*b[idx]*(w[bus_id][[c for c in load["connections"]]][idx]+1)) + end + end + end +end + +# ╔═╡ 068a66eb-35ef-45ff-8448-75fe67eec38f +md"""#### Power balance constraints + +The following models the power balance constraints, i.e., enforces that power-in and power-out of every bus are balanced. + +This constraint can shed load, using the introduction of `z_block` to the power balance equations, and can also control capacitors. +""" + +# ╔═╡ e32ada08-9f79-47b9-bfef-eaf5f8bbc058 +"PMD native version requires too much information, this is a simplified function" +function build_bus_shunt_matrices(ref, terminals, bus_shunts) + ncnds = length(terminals) + Gs = fill(0.0, ncnds, ncnds) + Bs = fill(0.0, ncnds, ncnds) + for (i, connections) in bus_shunts + shunt = ref[:shunt][i] + for (idx,c) in enumerate(connections) + for (jdx,d) in enumerate(connections) + Gs[findfirst(isequal(c), terminals),findfirst(isequal(d), terminals)] += shunt["gs"][idx,jdx] + Bs[findfirst(isequal(c), terminals),findfirst(isequal(d), terminals)] += shunt["bs"][idx,jdx] + end + end + end + + return (Gs, Bs) +end + +# ╔═╡ d6c7baee-8c8e-4cd9-ba35-06edad733e91 +for (i,bus) in ref[:bus] + uncontrolled_shunts = Tuple{Int,Vector{Int}}[] + controlled_shunts = Tuple{Int,Vector{Int}}[] + + if !isempty(ref[:bus_conns_shunt][i]) && any(haskey(ref[:shunt][sh], "controls") for (sh, conns) in ref[:bus_conns_shunt][i]) + for (sh, conns) in ref[:bus_conns_shunt][i] + if haskey(ref[:shunt][sh], "controls") + push!(controlled_shunts, (sh,conns)) + else + push!(uncontrolled_shunts, (sh, conns)) + end + end + else + uncontrolled_shunts = ref[:bus_conns_shunt][i] + end + + Gt, _ = build_bus_shunt_matrices(ref, bus["terminals"], ref[:bus_conns_shunt][i]) + _, Bt = build_bus_shunt_matrices(ref, bus["terminals"], uncontrolled_shunts) + + ungrounded_terminals = [(idx,t) for (idx,t) in enumerate(bus["terminals"]) if !bus["grounded"][idx]] + + pd_zblock = Dict(l => JuMP.@variable(model, [c in conns], base_name="0_pd_zblock_$(l)") for (l,conns) in ref[:bus_conns_load][i]) + qd_zblock = Dict(l => JuMP.@variable(model, [c in conns], base_name="0_qd_zblock_$(l)") for (l,conns) in ref[:bus_conns_load][i]) + + for (l,conns) in ref[:bus_conns_load][i] + for c in conns + IM.relaxation_product(model, pd_bus[l][c], z_block[ref[:load_block_map][l]], pd_zblock[l][c]) + IM.relaxation_product(model, qd_bus[l][c], z_block[ref[:load_block_map][l]], qd_zblock[l][c]) + end + end + + for (idx, t) in ungrounded_terminals + JuMP.@constraint(model, + sum(p[a][t] for (a, conns) in ref[:bus_arcs_conns_branch][i] if t in conns) + + sum(psw[a_sw][t] for (a_sw, conns) in ref[:bus_arcs_conns_switch][i] if t in conns) + + sum(pt[a_trans][t] for (a_trans, conns) in ref[:bus_arcs_conns_transformer][i] if t in conns) + == + sum(pg[g][t] for (g, conns) in ref[:bus_conns_gen][i] if t in conns) + - sum(ps[s][t] for (s, conns) in ref[:bus_conns_storage][i] if t in conns) + - sum(pd_zblock[l][t] for (l, conns) in ref[:bus_conns_load][i] if t in conns) + - sum((w[i][t] * LinearAlgebra.diag(Gt')[idx]) for (sh, conns) in ref[:bus_conns_shunt][i] if t in conns) + ) + + for (sh, sh_conns) in controlled_shunts + if t in sh_conns + bs = LinearAlgebra.diag(ref[:shunt][sh]["bs"])[findfirst(isequal(t), sh_conns)] + w_lb, w_ub = IM.variable_domain(w[i][t]) + + JuMP.@constraint(model, z_cap[sh] <= z_block[ref[:bus_block_map][i]]) + JuMP.@constraint(model, qc[sh] ≥ bs*z_cap[sh]*w_lb) + JuMP.@constraint(model, qc[sh] ≥ bs*w[t] + bs*z_cap[sh]*w_ub - bs*w_ub*z_block[ref[:bus_block_map][i]]) + JuMP.@constraint(model, qc[sh] ≤ bs*z_cap[sh]*w_ub) + JuMP.@constraint(model, qc[sh] ≤ bs*w[t] + bs*z_cap[sh]*w_lb - bs*w_lb*z_block[ref[:bus_block_map][i]]) + end + end + + JuMP.@constraint(model, + sum(q[a][t] for (a, conns) in ref[:bus_arcs_conns_branch][i] if t in conns) + + sum(qsw[a_sw][t] for (a_sw, conns) in ref[:bus_arcs_conns_switch][i] if t in conns) + + sum(qt[a_trans][t] for (a_trans, conns) in ref[:bus_arcs_conns_transformer][i] if t in conns) + == + sum(qg[g][t] for (g, conns) in ref[:bus_conns_gen][i] if t in conns) + - sum(qs[s][t] for (s, conns) in ref[:bus_conns_storage][i] if t in conns) + - sum(qd_zblock[l][t] for (l, conns) in ref[:bus_conns_load][i] if t in conns) + - sum((-w[i][t] * LinearAlgebra.diag(Bt')[idx]) for (sh, conns) in uncontrolled_shunts if t in conns) + - sum(-qc[sh][t] for (sh, conns) in controlled_shunts if t in conns) + ) + end +end + +# ╔═╡ 5e7470f6-2bb5-49fb-93d7-1b8c8e402526 +md"""#### Storage constraints + +The follow models the constraints necessary to model storage, including: + +- the storage "state", i.e., how much energy is remaining in the storage after the time-elapsed +- the "on-off" constraint that controls whether the storage is charging or discharging (it can only be one or another) +- the power "on-off" constraints, that ensure that the storage is off if the load block is not energized (`z_block=0`) +- the storage losses, which connects the powers to the charging/discharging variables +- the thermal limit constraints +- a storage balance constraint, which ensures that the powers outputted from the storage are within some bound of each other, if the storage is in grid following mode +""" + +# ╔═╡ 8c44ebe8-a2e9-4480-b1d8-b3c19350c029 +for (i,strg) in ref[:storage] + # constraint_storage_state + JuMP.@constraint(model, se[i] - strg["energy"] == ref[:time_elapsed]*(strg["charge_efficiency"]*sc[i] - sd[i]/strg["discharge_efficiency"])) + + # constraint_storage_complementarity_mi_block_on_off + JuMP.@constraint(model, sc_on[i] + sd_on[i] == z_block[ref[:storage_block_map][i]]) + JuMP.@constraint(model, sc_on[i]*strg["charge_rating"] >= sc[i]) + JuMP.@constraint(model, sd_on[i]*strg["discharge_rating"] >= sd[i]) + + # constraint_mc_storage_block_on_off + ncnds = length(strg["connections"]) + pmin = zeros(ncnds) + pmax = zeros(ncnds) + qmin = zeros(ncnds) + qmax = zeros(ncnds) + + inj_lb, inj_ub = PMD.ref_calc_storage_injection_bounds(ref[:storage], ref[:bus]) + for (idx,c) in enumerate(strg["connections"]) + pmin[idx] = inj_lb[i][idx] + pmax[idx] = inj_ub[i][idx] + qmin[idx] = max(inj_lb[i][idx], strg["qmin"]) + qmax[idx] = min(inj_ub[i][idx], strg["qmax"]) + end + + pmin = maximum(pmin) + pmax = minimum(pmax) + qmin = maximum(qmin) + qmax = minimum(qmax) + + isfinite(pmin) && JuMP.@constraint(model, sum(ps[i]) >= z_block[ref[:storage_block_map][i]]*pmin) + isfinite(qmin) && JuMP.@constraint(model, sum(qs[i]) >= z_block[ref[:storage_block_map][i]]*qmin) + + isfinite(pmax) && JuMP.@constraint(model, sum(ps[i]) <= z_block[ref[:storage_block_map][i]]*pmax) + isfinite(qmax) && JuMP.@constraint(model, sum(qs[i]) <= z_block[ref[:storage_block_map][i]]*qmax) + + # constraint_mc_storage_losses_block_on_off + if JuMP.has_lower_bound(qsc[i]) && JuMP.has_upper_bound(qsc[i]) + qsc_zblock = JuMP.@variable(model, base_name="0_qd_zblock_$(i)") + + JuMP.@constraint(model, qsc_zblock >= JuMP.lower_bound(qsc[i]) * z_block[ref[:storage_block_map][i]]) + JuMP.@constraint(model, qsc_zblock >= JuMP.upper_bound(qsc[i]) * z_block[ref[:storage_block_map][i]] + qsc[i] - JuMP.upper_bound(qsc[i])) + JuMP.@constraint(model, qsc_zblock <= JuMP.upper_bound(qsc[i]) * z_block[ref[:storage_block_map][i]]) + JuMP.@constraint(model, qsc_zblock <= qsc[i] + JuMP.lower_bound(qsc[i]) * z_block[ref[:storage_block_map][i]] - JuMP.lower_bound(qsc[i])) + + JuMP.@constraint(model, sum(qs[i]) == qsc_zblock + strg["q_loss"] * z_block[ref[:storage_block_map][i]]) + else + # Note that this is not supported in LP solvers when z_block is continuous + JuMP.@constraint(model, sum(qs[i]) == qsc[i] * z_block[ref[:storage_block_map][i]] + strg["q_loss"] * z_block[ref[:storage_block_map][i]]) + end + JuMP.@constraint(model, sum(ps[i]) + (sd[i] - sc[i]) == strg["p_loss"] * z_block[ref[:storage_block_map][i]]) + + # constraint_mc_storage_thermal_limit + _ps = [ps[i][c] for c in strg["connections"]] + _qs = [qs[i][c] for c in strg["connections"]] + + ps_sqr = [JuMP.@variable(model, base_name="0_ps_sqr_$(i)_$(c)") for c in strg["connections"]] + qs_sqr = [JuMP.@variable(model, base_name="0_qs_sqr_$(i)_$(c)") for c in strg["connections"]] + + for (idx,c) in enumerate(strg["connections"]) + ps_lb, ps_ub = IM.variable_domain(_ps[idx]) + PMD.PolyhedralRelaxations.construct_univariate_relaxation!(model, x->x^2, _ps[idx], ps_sqr[idx], [ps_lb, ps_ub], false) + + qs_lb, qs_ub = IM.variable_domain(_qs[idx]) + PMD.PolyhedralRelaxations.construct_univariate_relaxation!(model, x->x^2, _qs[idx], qs_sqr[idx], [qs_lb, qs_ub], false) + end + + JuMP.@constraint(model, sum(ps_sqr .+ qs_sqr) <= strg["thermal_rating"]^2) + + # constraint_mc_storage_phase_unbalance_grid_following + unbalance_factor = get(strg, "phase_unbalance_factor", Inf) + if isfinite(unbalance_factor) + sd_on_ps = JuMP.@variable(model, [c in strg["connections"]], base_name="0_sd_on_ps_$(i)") + sc_on_ps = JuMP.@variable(model, [c in strg["connections"]], base_name="0_sc_on_ps_$(i)") + sd_on_qs = JuMP.@variable(model, [c in strg["connections"]], base_name="0_sd_on_qs_$(i)") + sc_on_qs = JuMP.@variable(model, [c in strg["connections"]], base_name="0_sc_on_qs_$(i)") + for c in strg["connections"] + PMD.PolyhedralRelaxations.construct_bilinear_relaxation!(model, sd_on[i], ps[i][c], sd_on_ps[c], [0,1], [JuMP.lower_bound(ps[i][c]), JuMP.upper_bound(ps[i][c])]) + PMD.PolyhedralRelaxations.construct_bilinear_relaxation!(model, sc_on[i], ps[i][c], sc_on_ps[c], [0,1], [JuMP.lower_bound(ps[i][c]), JuMP.upper_bound(ps[i][c])]) + PMD.PolyhedralRelaxations.construct_bilinear_relaxation!(model, sd_on[i], qs[i][c], sd_on_qs[c], [0,1], [JuMP.lower_bound(qs[i][c]), JuMP.upper_bound(qs[i][c])]) + PMD.PolyhedralRelaxations.construct_bilinear_relaxation!(model, sc_on[i], qs[i][c], sc_on_qs[c], [0,1], [JuMP.lower_bound(qs[i][c]), JuMP.upper_bound(qs[i][c])]) + end + + ps_zinverter = JuMP.@variable(model, [c in strg["connections"]], base_name="0_ps_zinverter_$(i)") + qs_zinverter = JuMP.@variable(model, [c in strg["connections"]], base_name="0_qs_zinverter_$(i)") + for c in strg["connections"] + PMD.PolyhedralRelaxations.construct_bilinear_relaxation!(model, z_inverter[(:storage,i)], ps[i][c], ps_zinverter[c], [0,1], [JuMP.lower_bound(ps[i][c]), JuMP.upper_bound(ps[i][c])]) + PMD.PolyhedralRelaxations.construct_bilinear_relaxation!(model, z_inverter[(:storage,i)], qs[i][c], qs_zinverter[c], [0,1], [JuMP.lower_bound(qs[i][c]), JuMP.upper_bound(qs[i][c])]) + end + + sd_on_ps_zinverter = JuMP.@variable(model, [c in strg["connections"]], base_name="0_sd_on_ps_zinverter_$(i)") + sc_on_ps_zinverter = JuMP.@variable(model, [c in strg["connections"]], base_name="0_sc_on_ps_zinverter_$(i)") + sd_on_qs_zinverter = JuMP.@variable(model, [c in strg["connections"]], base_name="0_sd_on_qs_zinverter_$(i)") + sc_on_qs_zinverter = JuMP.@variable(model, [c in strg["connections"]], base_name="0_sc_on_qs_zinverter_$(i)") + for c in strg["connections"] + PMD.PolyhedralRelaxations.construct_bilinear_relaxation!(model, z_inverter[(:storage,i)], sd_on_ps[c], sd_on_ps_zinverter[c], [0,1], [JuMP.lower_bound(ps[i][c]), JuMP.upper_bound(ps[i][c])]) + PMD.PolyhedralRelaxations.construct_bilinear_relaxation!(model, z_inverter[(:storage,i)], sc_on_ps[c], sc_on_ps_zinverter[c], [0,1], [JuMP.lower_bound(ps[i][c]), JuMP.upper_bound(ps[i][c])]) + PMD.PolyhedralRelaxations.construct_bilinear_relaxation!(model, z_inverter[(:storage,i)], sd_on_qs[c], sd_on_qs_zinverter[c], [0,1], [JuMP.lower_bound(qs[i][c]), JuMP.upper_bound(qs[i][c])]) + PMD.PolyhedralRelaxations.construct_bilinear_relaxation!(model, z_inverter[(:storage,i)], sc_on_qs[c], sc_on_qs_zinverter[c], [0,1], [JuMP.lower_bound(qs[i][c]), JuMP.upper_bound(qs[i][c])]) + end + + for (idx,c) in enumerate(strg["connections"]) + if idx < length(strg["connections"]) + for d in strg["connections"][idx+1:end] + JuMP.@constraint(model, ps[i][c]-ps_zinverter[c] >= ps[i][d] - unbalance_factor*(-1*sd_on_ps[d] + 1*sc_on_ps[d]) - ps_zinverter[d] + unbalance_factor*(-1*sd_on_ps_zinverter[d] + 1*sc_on_ps_zinverter[d])) + JuMP.@constraint(model, ps[i][c]-ps_zinverter[c] <= ps[i][d] + unbalance_factor*(-1*sd_on_ps[d] + 1*sc_on_ps[d]) - ps_zinverter[d] - unbalance_factor*(-1*sd_on_ps_zinverter[d] + 1*sc_on_ps_zinverter[d])) + + JuMP.@constraint(model, qs[i][c]-qs_zinverter[c] >= qs[i][d] - unbalance_factor*(-1*sd_on_qs[d] + 1*sc_on_qs[d]) - qs_zinverter[d] + unbalance_factor*(-1*sd_on_qs_zinverter[d] + 1*sc_on_qs_zinverter[d])) + JuMP.@constraint(model, qs[i][c]-qs_zinverter[c] <= qs[i][d] + unbalance_factor*(-1*sd_on_qs[d] + 1*sc_on_qs[d]) - qs_zinverter[d] - unbalance_factor*(-1*sd_on_qs_zinverter[d] + 1*sc_on_qs_zinverter[d])) + end + end + end + end +end + +# ╔═╡ 20d08693-413b-4a52-9f54-d8f25b492b50 +md"""#### Branch constraints + +The following constraints model the losses, voltage differences, and limits (ampacity) across branches. +""" + +# ╔═╡ f2d2375d-2ca2-4e97-87f2-5adbf250d152 +for (i,branch) in ref[:branch] + f_bus = branch["f_bus"] + t_bus = branch["t_bus"] + f_idx = (i, f_bus, t_bus) + t_idx = (i, t_bus, f_bus) + + r = branch["br_r"] + x = branch["br_x"] + g_sh_fr = branch["g_fr"] + g_sh_to = branch["g_to"] + b_sh_fr = branch["b_fr"] + b_sh_to = branch["b_to"] + + p_fr = p[f_idx] + q_fr = q[f_idx] + + p_to = p[t_idx] + q_to = q[t_idx] + + w_fr = w[f_bus] + w_to = w[t_bus] + + f_connections = branch["f_connections"] + t_connections = branch["t_connections"] + + # constraint_mc_power_losses + for (idx, (fc,tc)) in enumerate(zip(f_connections, t_connections)) + JuMP.@constraint(model, p_fr[fc] + p_to[tc] == g_sh_fr[idx,idx]*w_fr[fc] + g_sh_to[idx,idx]*w_to[tc]) + JuMP.@constraint(model, q_fr[fc] + q_to[tc] == -b_sh_fr[idx,idx]*w_fr[fc] + -b_sh_to[idx,idx]*w_to[tc]) + end + + w_fr = w[f_bus] + w_to = w[t_bus] + + p_fr = p[f_idx] + q_fr = q[f_idx] + + p_s_fr = [p_fr[fc]- LinearAlgebra.diag(g_sh_fr)[idx].*w_fr[fc] for (idx,fc) in enumerate(f_connections)] + q_s_fr = [q_fr[fc]+ LinearAlgebra.diag(b_sh_fr)[idx].*w_fr[fc] for (idx,fc) in enumerate(f_connections)] + + alpha = exp(-im*2*pi/3) + Gamma = [1 alpha^2 alpha; alpha 1 alpha^2; alpha^2 alpha 1][f_connections,t_connections] + + MP = 2*(real(Gamma).*r + imag(Gamma).*x) + MQ = 2*(real(Gamma).*x - imag(Gamma).*r) + + N = length(f_connections) + + # constraint_mc_model_voltage_magnitude_difference + for (idx, (fc, tc)) in enumerate(zip(f_connections, t_connections)) + JuMP.@constraint(model, w_to[tc] == w_fr[fc] - sum(MP[idx,j]*p_s_fr[j] for j in 1:N) - sum(MQ[idx,j]*q_s_fr[j] for j in 1:N)) + end + + # constraint_mc_voltage_angle_difference + for (idx, (fc, tc)) in enumerate(zip(branch["f_connections"], branch["t_connections"])) + g_fr = branch["g_fr"][idx,idx] + g_to = branch["g_to"][idx,idx] + b_fr = branch["b_fr"][idx,idx] + b_to = branch["b_to"][idx,idx] + + r = branch["br_r"][idx,idx] + x = branch["br_x"][idx,idx] + + w_fr = w[f_bus][fc] + p_fr = p[f_idx][fc] + q_fr = q[f_idx][fc] + + angmin = branch["angmin"] + angmax = branch["angmax"] + + JuMP.@constraint(model, + tan(angmin[idx])*((1 + r*g_fr - x*b_fr)*(w_fr) - r*p_fr - x*q_fr) + <= ((-x*g_fr - r*b_fr)*(w_fr) + x*p_fr - r*q_fr) + ) + JuMP.@constraint(model, + tan(angmax[idx])*((1 + r*g_fr - x*b_fr)*(w_fr) - r*p_fr - x*q_fr) + >= ((-x*g_fr - r*b_fr)*(w_fr) + x*p_fr - r*q_fr) + ) + end + + # ampacity constraints + if haskey(branch, "c_rating_a") && any(branch["c_rating_a"] .< Inf) + c_rating = branch["c_rating_a"] + + # constraint_mc_ampacity_from + p_fr = [p[f_idx][c] for c in f_connections] + q_fr = [q[f_idx][c] for c in f_connections] + w_fr = [w[f_idx[2]][c] for c in f_connections] + + p_sqr_fr = [JuMP.@variable(model, base_name="0_p_sqr_$(f_idx)[$(c)]") for c in f_connections] + q_sqr_fr = [JuMP.@variable(model, base_name="0_q_sqr_$(f_idx)[$(c)]") for c in f_connections] + + for (idx,c) in enumerate(f_connections) + if isfinite(c_rating[idx]) + p_lb, p_ub = IM.variable_domain(p_fr[idx]) + q_lb, q_ub = IM.variable_domain(q_fr[idx]) + w_ub = IM.variable_domain(w_fr[idx])[2] + + if (!isfinite(p_lb) || !isfinite(p_ub)) && isfinite(w_ub) + p_ub = sum(c_rating[isfinite.(c_rating)]) * w_ub + p_lb = -p_ub + end + if (!isfinite(q_lb) || !isfinite(q_ub)) && isfinite(w_ub) + q_ub = sum(c_rating[isfinite.(c_rating)]) * w_ub + q_lb = -q_ub + end + + all(isfinite(b) for b in [p_lb, p_ub]) && PMD.PolyhedralRelaxations.construct_univariate_relaxation!(model, x->x^2, p_fr[idx], p_sqr_fr[idx], [p_lb, p_ub], false) + all(isfinite(b) for b in [q_lb, q_ub]) && PMD.PolyhedralRelaxations.construct_univariate_relaxation!(model, x->x^2, q_fr[idx], q_sqr_fr[idx], [q_lb, q_ub], false) + end + end + + # constraint_mc_ampacity_to + p_to = [p[t_idx][c] for c in t_connections] + q_to = [q[t_idx][c] for c in t_connections] + w_to = [w[t_idx[2]][c] for c in t_connections] + + p_sqr_to = [JuMP.@variable(model, base_name="0_p_sqr_$(t_idx)[$(c)]") for c in t_connections] + q_sqr_to = [JuMP.@variable(model, base_name="0_q_sqr_$(t_idx)[$(c)]") for c in t_connections] + + for (idx,c) in enumerate(t_connections) + if isfinite(c_rating[idx]) + p_lb, p_ub = IM.variable_domain(p_to[idx]) + q_lb, q_ub = IM.variable_domain(q_to[idx]) + w_ub = IM.variable_domain(w_to[idx])[2] + + if (!isfinite(p_lb) || !isfinite(p_ub)) && isfinite(w_ub) + p_ub = sum(c_rating[isfinite.(c_rating)]) * w_ub + p_lb = -p_ub + end + if (!isfinite(q_lb) || !isfinite(q_ub)) && isfinite(w_ub) + q_ub = sum(c_rating[isfinite.(c_rating)]) * w_ub + q_lb = -q_ub + end + + all(isfinite(b) for b in [p_lb, p_ub]) && PMD.PolyhedralRelaxations.construct_univariate_relaxation!(model, x->x^2, p_to[idx], p_sqr_to[idx], [p_lb, p_ub], false) + all(isfinite(b) for b in [q_lb, q_ub]) && PMD.PolyhedralRelaxations.construct_univariate_relaxation!(model, x->x^2, q_to[idx], q_sqr_to[idx], [q_lb, q_ub], false) + end + end + end +end + +# ╔═╡ 978048ae-170a-4b83-8dee-1715350e75cc +md"""#### Switch constraints + +The following constraints model general constraints on topology, and the powers and voltages on either side of the switch, dependent on the state of the switch (i.e., open or closed), including: + +- a switch close-action limit, which limits the maximum number of switch closures allowed, but allows for unlimited switch opening actions to allow for load shedding if necessary +- a radiality constraint, which requires that the topology be a spanning forest, i.e., that each connected component have radial topology (no cycles) +- a constraint that "isolates" load blocks, which prevents switches from being closed if one load block is shed but the other is not +- a constraint that enforces zero power flow across a switch if the switch is open, and inside the power limits otherwise +- a constraint that enforaces that voltages be equal on either side of a switch if the switch is closed, and unpinned otherwise +""" + +# ╔═╡ 23d0f743-d7be-400a-9972-4337ae1bffff +# constraint_switch_close_action_limit +begin + switch_close_actions_ub = ref[:switch_close_actions_ub] + + if switch_close_actions_ub < Inf + Δᵞs = Dict(l => JuMP.@variable(model, base_name="0_delta_switch_state_$(l)") for l in keys(ref[:switch_dispatchable])) + for (s, Δᵞ) in Δᵞs + γ = z_switch[s] + γ₀ = JuMP.start_value(γ) + JuMP.@constraint(model, Δᵞ >= γ * (1 - γ₀)) + JuMP.@constraint(model, Δᵞ >= -γ * (1 - γ₀)) + end + + JuMP.@constraint(model, sum(Δᵞ for (l, Δᵞ) in Δᵞs) <= switch_close_actions_ub) + end +end + +# ╔═╡ a63763bf-1f87-400e-b4cd-b112c9a0cd64 +# constraint_radial_topology +begin + f = Dict() + λ = Dict() + β = Dict() + α = Dict() + + _N₀ = collect(keys(ref[:blocks])) + _L₀ = ref[:block_pairs] + + virtual_iᵣ = maximum(_N₀)+1 + _N = [_N₀..., virtual_iᵣ] + iᵣ = [virtual_iᵣ] + + _L = [_L₀..., [(virtual_iᵣ, n) for n in _N₀]...] + _L′ = union(_L, Set([(j,i) for (i,j) in _L])) + + for (i,j) in _L′ + for k in filter(kk->kk∉iᵣ,_N) + f[(k, i, j)] = JuMP.@variable(model, base_name="0_f_$((k,i,j))") + end + λ[(i,j)] = JuMP.@variable(model, base_name="0_lambda_$((i,j))", binary=true, lower_bound=0, upper_bound=1) + + if (i,j) ∈ _L₀ + β[(i,j)] = JuMP.@variable(model, base_name="0_beta_$((i,j))", lower_bound=0, upper_bound=1) + end + end + + for (s,sw) in ref[:switch] + (i,j) = (ref[:bus_block_map][sw["f_bus"]], ref[:bus_block_map][sw["t_bus"]]) + α[(i,j)] = z_switch[s] + end + + for k in filter(kk->kk∉iᵣ,_N) + for _iᵣ in iᵣ + jiᵣ = filter(((j,i),)->i==_iᵣ&&i!=j,_L) + iᵣj = filter(((i,j),)->i==_iᵣ&&i!=j,_L) + if !(isempty(jiᵣ) && isempty(iᵣj)) + JuMP.@constraint( + model, + sum(f[(k,j,i)] for (j,i) in jiᵣ) - + sum(f[(k,i,j)] for (i,j) in iᵣj) + == + -1.0 + ) + end + end + + jk = filter(((j,i),)->i==k&&i!=j,_L′) + kj = filter(((i,j),)->i==k&&i!=j,_L′) + if !(isempty(jk) && isempty(kj)) + JuMP.@constraint( + model, + sum(f[(k,j,k)] for (j,i) in jk) - + sum(f[(k,k,j)] for (i,j) in kj) + == + 1.0 + ) + end + + for i in filter(kk->kk∉iᵣ&&kk!=k,_N) + ji = filter(((j,ii),)->ii==i&&ii!=j,_L′) + ij = filter(((ii,j),)->ii==i&&ii!=j,_L′) + if !(isempty(ji) && isempty(ij)) + JuMP.@constraint( + model, + sum(f[(k,j,i)] for (j,ii) in ji) - + sum(f[(k,i,j)] for (ii,j) in ij) + == + 0.0 + ) + end + end + + for (i,j) in _L + JuMP.@constraint(model, f[(k,i,j)] >= 0) + JuMP.@constraint(model, f[(k,i,j)] <= λ[(i,j)]) + JuMP.@constraint(model, f[(k,j,i)] >= 0) + JuMP.@constraint(model, f[(k,j,i)] <= λ[(j,i)]) + end + end + + JuMP.@constraint(model, sum((λ[(i,j)] + λ[(j,i)]) for (i,j) in _L) == length(_N) - 1) + + for (i,j) in _L₀ + JuMP.@constraint(model, λ[(i,j)] + λ[(j,i)] == β[(i,j)]) + JuMP.@constraint(model, α[(i,j)] <= β[(i,j)]) + end +end + +# ╔═╡ 9d6af6e9-435a-43e6-980a-0658a4b449a1 +# constraint_isolate_block +begin + for (s, switch) in ref[:switch_dispatchable] + z_block_fr = z_block[ref[:bus_block_map][switch["f_bus"]]] + z_block_to = z_block[ref[:bus_block_map][switch["t_bus"]]] + + γ = z_switch[s] + JuMP.@constraint(model, (z_block_fr - z_block_to) <= (1-γ)) + JuMP.@constraint(model, (z_block_fr - z_block_to) >= -(1-γ)) + end + + for b in keys(ref[:blocks]) + n_gen = length(ref[:block_gens][b]) + n_strg = length(ref[:block_storages][b]) + n_neg_loads = length([_b for (_b,ls) in ref[:block_loads] if any(any(ref[:load][l]["pd"] .< 0) for l in ls)]) + + JuMP.@constraint(model, z_block[b] <= n_gen + n_strg + n_neg_loads + sum(z_switch[s] for s in keys(ref[:block_switches]) if s in keys(ref[:switch_dispatchable]))) + end +end + +# ╔═╡ 1e1b3303-1508-4acb-8dd0-3cf0c64d0a78 +for (i,switch) in ref[:switch] + f_bus_id = switch["f_bus"] + t_bus_id = switch["t_bus"] + f_connections = switch["f_connections"] + t_connections = switch["t_connections"] + f_idx = (i, f_bus_id, t_bus_id) + + w_fr = w[f_bus_id] + w_to = w[f_bus_id] + + f_bus = ref[:bus][f_bus_id] + t_bus = ref[:bus][t_bus_id] + + f_vmax = f_bus["vmax"][[findfirst(isequal(c), f_bus["terminals"]) for c in f_connections]] + t_vmax = t_bus["vmax"][[findfirst(isequal(c), t_bus["terminals"]) for c in t_connections]] + + vmax = min.(fill(2.0, length(f_bus["vmax"])), f_vmax, t_vmax) + + # constraint_mc_switch_state_open_close + for (idx, (fc, tc)) in enumerate(zip(f_connections, t_connections)) + JuMP.@constraint(model, w_fr[fc] - w_to[tc] <= vmax[idx].^2 * (1-z_switch[i])) + JuMP.@constraint(model, w_fr[fc] - w_to[tc] >= -vmax[idx].^2 * (1-z_switch[i])) + end + + rating = min.(fill(1.0, length(f_connections)), PMD._calc_branch_power_max_frto(switch, f_bus, t_bus)...) + + for (idx, c) in enumerate(f_connections) + JuMP.@constraint(model, psw[f_idx][c] <= rating[idx] * z_switch[i]) + JuMP.@constraint(model, psw[f_idx][c] >= -rating[idx] * z_switch[i]) + JuMP.@constraint(model, qsw[f_idx][c] <= rating[idx] * z_switch[i]) + JuMP.@constraint(model, qsw[f_idx][c] >= -rating[idx] * z_switch[i]) + end + + # constraint_mc_switch_ampacity + if haskey(switch, "current_rating") && any(switch["current_rating"] .< Inf) + c_rating = switch["current_rating"] + psw_fr = [psw[f_idx][c] for c in f_connections] + qsw_fr = [qsw[f_idx][c] for c in f_connections] + w_fr = [w[f_idx[2]][c] for c in f_connections] + + psw_sqr_fr = [JuMP.@variable(model, base_name="0_psw_sqr_$(f_idx)[$(c)]") for c in f_connections] + qsw_sqr_fr = [JuMP.@variable(model, base_name="0_qsw_sqr_$(f_idx)[$(c)]") for c in f_connections] + + for (idx,c) in enumerate(f_connections) + if isfinite(c_rating[idx]) + p_lb, p_ub = IM.variable_domain(psw_fr[idx]) + q_lb, q_ub = IM.variable_domain(qsw_fr[idx]) + w_ub = IM.variable_domain(w_fr[idx])[2] + + if (!isfinite(p_lb) || !isfinite(p_ub)) && isfinite(w_ub) + p_ub = sum(c_rating[isfinite.(c_rating)]) * w_ub + p_lb = -p_ub + end + if (!isfinite(q_lb) || !isfinite(q_ub)) && isfinite(w_ub) + q_ub = sum(c_rating[isfinite.(c_rating)]) * w_ub + q_lb = -q_ub + end + + all(isfinite(b) for b in [p_lb, p_ub]) && PMD.PolyhedralRelaxations.construct_univariate_relaxation!(model, x->x^2, psw_fr[idx], psw_sqr_fr[idx], [p_lb, p_ub], false) + all(isfinite(b) for b in [q_lb, q_ub]) && PMD.PolyhedralRelaxations.construct_univariate_relaxation!(model, x->x^2, qsw_fr[idx], qsw_sqr_fr[idx], [q_lb, q_ub], false) + end + end + end +end + +# ╔═╡ 9b7446d5-0751-4df6-b716-e8d5f85848a8 +md"""#### Transformer Constraints + +The following constraints model wye and delta connected transformers, including the capability to adjust the tap variables for voltage stability. +""" + +# ╔═╡ 0bad7fc4-0a8d-46e7-b126-91b3542fed42 +for (i,transformer) in ref[:transformer] + f_bus = transformer["f_bus"] + t_bus = transformer["t_bus"] + f_idx = (i, f_bus, t_bus) + t_idx = (i, t_bus, f_bus) + configuration = transformer["configuration"] + f_connections = transformer["f_connections"] + t_connections = transformer["t_connections"] + tm_set = transformer["tm_set"] + tm_fixed = transformer["tm_fix"] + tm_scale = PMD.calculate_tm_scale(transformer, ref[:bus][f_bus], ref[:bus][t_bus]) + pol = transformer["polarity"] + + if configuration == PMD.WYE + tm = [tm_fixed[idx] ? tm_set[idx] : var(pm, nw, :tap, trans_id)[idx] for (idx,(fc,tc)) in enumerate(zip(f_connections,t_connections))] + + p_fr = [pt[f_idx][p] for p in f_connections] + p_to = [pt[t_idx][p] for p in t_connections] + q_fr = [qt[f_idx][p] for p in f_connections] + q_to = [qt[t_idx][p] for p in t_connections] + + w_fr = w[f_bus] + w_to = w[t_bus] + + tmsqr = [ + tm_fixed[i] ? tm[i]^2 : JuMP.@variable( + model, + base_name="0_tmsqr_$(trans_id)_$(f_connections[i])", + start=JuMP.start_value(tm[i])^2, + lower_bound=JuMP.has_lower_bound(tm[i]) ? JuMP.lower_bound(tm[i])^2 : 0.9^2, + upper_bound=JuMP.has_upper_bound(tm[i]) ? JuMP.upper_bound(tm[i])^2 : 1.1^2 + ) for i in 1:length(tm) + ] + + for (idx, (fc, tc)) in enumerate(zip(f_connections, t_connections)) + if tm_fixed[idx] + JuMP.@constraint(model, w_fr[fc] == (pol*tm_scale*tm[idx])^2*w_to[tc]) + else + PMD.PolyhedralRelaxations.construct_univariate_relaxation!( + model, + x->x^2, + tm[idx], + tmsqr[idx], + [ + JuMP.has_lower_bound(tm[idx]) ? JuMP.lower_bound(tm[idx]) : 0.9, + JuMP.has_upper_bound(tm[idx]) ? JuMP.upper_bound(tm[idx]) : 1.1 + ], + false + ) + + tmsqr_w_to = JuMP.@variable(model, base_name="0_tmsqr_w_to_$(trans_id)_$(t_bus)_$(tc)") + PMD.PolyhedralRelaxations.construct_bilinear_relaxation!( + model, + tmsqr[idx], + w_to[tc], + tmsqr_w_to, + [JuMP.lower_bound(tmsqr[idx]), JuMP.upper_bound(tmsqr[idx])], + [ + JuMP.has_lower_bound(w_to[tc]) ? JuMP.lower_bound(w_to[tc]) : 0.0, + JuMP.has_upper_bound(w_to[tc]) ? JuMP.upper_bound(w_to[tc]) : 1.1^2 + ] + ) + + JuMP.@constraint(model, w_fr[fc] == (pol*tm_scale)^2*tmsqr_w_to) + end + end + + JuMP.@constraint(model, p_fr + p_to .== 0) + JuMP.@constraint(model, q_fr + q_to .== 0) + + elseif configuration == PMD.DELTA + tm = [tm_fixed[idx] ? tm_set[idx] : var(pm, nw, :tap, trans_id)[fc] for (idx,(fc,tc)) in enumerate(zip(f_connections,t_connections))] + nph = length(tm_set) + + p_fr = [pt[f_idx][p] for p in f_connections] + p_to = [pt[t_idx][p] for p in t_connections] + q_fr = [qt[f_idx][p] for p in f_connections] + q_to = [qt[t_idx][p] for p in t_connections] + + w_fr = w[f_bus] + w_to = w[t_bus] + + for (idx,(fc, tc)) in enumerate(zip(f_connections,t_connections)) + # rotate by 1 to get 'previous' phase + # e.g., for nph=3: 1->3, 2->1, 3->2 + jdx = (idx-1+1)%nph+1 + fd = f_connections[jdx] + JuMP.@constraint(model, 3.0*(w_fr[fc] + w_fr[fd]) == 2.0*(pol*tm_scale*tm[idx])^2*w_to[tc]) + end + + for (idx,(fc, tc)) in enumerate(zip(f_connections,t_connections)) + # rotate by nph-1 to get 'previous' phase + # e.g., for nph=3: 1->3, 2->1, 3->2 + jdx = (idx-1+nph-1)%nph+1 + fd = f_connections[jdx] + td = t_connections[jdx] + JuMP.@constraint(model, 2*p_fr[fc] == -(p_to[tc]+p_to[td])+(q_to[td]-q_to[tc])/sqrt(3.0)) + JuMP.@constraint(model, 2*q_fr[fc] == (p_to[tc]-p_to[td])/sqrt(3.0)-(q_to[td]+q_to[tc])) + end + end +end + +# ╔═╡ 6df404eb-d816-4ae4-ae3f-a39505f79669 +md"""### Objective + +Below is the objective function used for the block-mld problem, which includes terms for + +- minimizing the amount of load shed +- minimizing the number of switches left open +- minimizing the number of switches changing from one state to another +- maximizing the amount of stored energy at the end of the elapsed time +- minimizing the cost of generation + +""" + +# ╔═╡ 5c04b2c2-e83b-4289-b439-2e016a20678e +begin + delta_sw_state = JuMP.@variable( + model, + [i in keys(ref[:switch_dispatchable])], + base_name="$(i)_delta_sw_state", + ) + + for (s,switch) in ref[:switch_dispatchable] + JuMP.@constraint(model, delta_sw_state[s] >= (switch["state"] - z_switch[s])) + JuMP.@constraint(model, delta_sw_state[s] >= -(switch["state"] - z_switch[s])) + end + + total_energy_ub = sum(strg["energy_rating"] for (i,strg) in ref[:storage]) + total_pmax = sum(Float64[all(.!isfinite.(gen["pmax"])) ? 0.0 : sum(gen["pmax"][isfinite.(gen["pmax"])]) for (i, gen) in ref[:gen]]) + + total_energy_ub = total_energy_ub <= 1.0 ? 1.0 : total_energy_ub + total_pmax = total_pmax <= 1.0 ? 1.0 : total_pmax + + n_dispatchable_switches = length(keys(ref[:switch_dispatchable])) + n_dispatchable_switches = n_dispatchable_switches < 1 ? 1 : n_dispatchable_switches + + block_weights = ref[:block_weights] + + JuMP.@objective(model, Min, + sum( block_weights[i] * (1-z_block[i]) for (i,block) in ref[:blocks]) + + sum( ref[:switch_scores][l]*(1-z_switch[l]) for l in keys(ref[:switch_dispatchable]) ) + + sum( delta_sw_state[l] for l in keys(ref[:switch_dispatchable])) / n_dispatchable_switches + + sum( (strg["energy_rating"] - se[i]) for (i,strg) in ref[:storage]) / total_energy_ub + + sum( sum(get(gen, "cost", [0.0, 0.0])[2] * pg[i][c] + get(gen, "cost", [0.0, 0.0])[1] for c in gen["connections"]) for (i,gen) in ref[:gen]) / total_energy_ub + ) +end + +# ╔═╡ a78fb463-0ffe-41db-a48b-63a4ae9ff3f7 +md"""## Model comparison + +In this section we compare the models and their solutions, to see if they are equivalent. +""" + +# ╔═╡ 52867723-336e-460d-a1a6-a7993778b3e9 +md"""### JuMP Model as built automatically by ONM + +Here, we build the JuMP model using the built-in ONM tools. Specifically, we use the `instantiate_onm_model` function, to build the block-mld problem `build_block_mld`, using the LinDist3Flow formulation `LPUBFDiagPowerModel`. + +We are doing this so that we can compare the automatically built model against the manually built one. +""" + +# ╔═╡ d9cd6181-cd1d-48f9-b610-f3b2dea1c640 +orig_model = ONM.instantiate_onm_model(eng, PMD.LPUBFDiagPowerModel, ONM.build_block_mld).model + +# ╔═╡ bc4e4a20-2584-4706-a4d7-ad0d9de43351 +md"""#### Save automatic model to disk for comparison + +If it is desired to look at the model in a file, to more directly compare it to another model, change `false` to `true`. +""" + +# ╔═╡ 9ffd1f23-f82c-45b5-9a69-9fde7e296cf1 +if false + orig_dest = JuMP.MOI.FileFormats.Model(format = JuMP.MOI.FileFormats.FORMAT_MPS) + JuMP.MOI.copy_to(orig_dest, pm_orig.model) + JuMP.MOI.write_to_file(orig_dest, "orig_model.mof.mps") +end + +# ╔═╡ 61e70040-9fc4-4681-a25f-d144a857aabd +md"""### Manual Model + +Below is a summary of the JuMP model that was built by-hand above. +""" + +# ╔═╡ efcd69c1-6ea2-4524-a053-bfb40fb01dda +model + +# ╔═╡ 4938609f-ce32-4798-bfc8-c6ca205e1209 +md"""#### Save manual model to disk for comparison + +If it is desired to look at the model in a file, to more directly compare it to another model, change `false` to `true`. +""" + +# ╔═╡ ba60b4e3-fcdc-4efc-994b-1872a8f58703 +if false + new_dest = JuMP.MOI.FileFormats.Model(format = JuMP.MOI.FileFormats.FORMAT_MOF) + JuMP.MOI.copy_to(new_dest, model) + JuMP.MOI.write_to_file(new_dest, "new_model.mof.json") +end + +# ╔═╡ 8c545f8e-22b3-4f53-a02d-5473bc9e1a3a +md"""### Solve original model +""" + +# ╔═╡ cfd8e9d3-1bb0-42a2-920d-5e343609c237 +JuMP.set_optimizer(orig_model, solver) + +# ╔═╡ 6dd1b166-1a00-4e89-b46f-9621bf35982f +JuMP.optimize!(orig_model) + +# ╔═╡ 25970a05-5503-455c-9b56-d0147702371c +md"### Solve Manual Model" + +# ╔═╡ b758be56-9ed0-4474-8361-73b3d2de89af +JuMP.set_optimizer(model, solver) + +# ╔═╡ 5084a4ed-1638-4d77-91e4-5d77788ce0fe +JuMP.optimize!(model) + +# ╔═╡ Cell order: +# ╟─bbe09ba9-63fb-4b33-ae27-eb05cb9fd936 +# ╟─bdfca444-f5f0-413f-8a47-8346de453d12 +# ╟─3b579de0-8d2a-4e94-8daf-0d3833a90ab4 +# ╠═14e4d41e-e5bd-11ec-3723-9daa31787999 +# ╠═f00a9624-13f6-4fcd-a673-bcb2eed06340 +# ╠═ad36afcf-6a7e-4913-b15a-5f19ba383b27 +# ╠═4de82775-e012-44a5-a440-d7f54792d284 +# ╟─b41082c7-87f1-42e3-8c20-4d6cddc79375 +# ╠═7b252a89-19e4-43ba-b795-24299074753e +# ╟─d7dbea25-f1b1-4823-982b-0d5aa9d6ea26 +# ╠═cc2aba3c-a412-4c20-8635-2cdcf369d2c8 +# ╟─e66a945e-f437-4ed6-9702-1daf3bccc958 +# ╠═88beadb3-e87b-46e4-8aec-826324cd6112 +# ╟─588344bb-6f8b-463e-896d-725ceb167cb4 +# ╠═8f41758a-5523-487d-9a5b-712ffec668ee +# ╟─b9582cb1-0f92-42ef-88b8-fb7e98ff6c3b +# ╠═6fa5d4f4-997d-4340-bdc5-1b2801815351 +# ╟─ebe9dc84-f289-4ae4-bd26-6071106d6a28 +# ╠═e096d427-0916-40be-8f05-444e8f37b410 +# ╟─e6496923-ee2b-46a0-9d81-624197d3cb02 +# ╠═afc66e0a-8aed-4d1a-9cf9-15f537b57b95 +# ╟─0590de28-76c6-485a-ae8e-bf76c0c9d924 +# ╟─379efc70-7458-41f5-a8d4-dcdf59fc9a6e +# ╠═c19ed861-e91c-44e6-b0be-e4b56629481c +# ╟─4269aa45-2c4c-4be5-8776-d25b39e5fe90 +# ╠═962476bf-fa55-484d-b9f6-fc09d1d891ee +# ╟─1480b91d-fcbb-46c1-9a47-c4daa99731a2 +# ╠═04eea7b8-ff6c-4650-b01e-31301257ded4 +# ╟─05312fc9-b125-42e8-a9bd-7129f63ddc9a +# ╠═91014d35-e30b-4af7-9324-3cde48242342 +# ╟─3277219e-589d-47db-9374-e6712a4a40c4 +# ╠═ac115a18-ce73-436a-800e-a83b27c6cee7 +# ╠═bca9289f-bf4f-4ec2-af5f-373b70b4e614 +# ╠═beb258c4-97da-4044-b8d1-abc695e8a910 +# ╠═e00d2fdc-a416-4259-b29e-b5608897da9b +# ╠═855a0057-610a-4274-86bb-95ceef674257 +# ╟─080a174a-c63b-4284-a06d-1031fda7e3a9 +# ╠═3177e943-c635-493a-9be6-c2ade040c447 +# ╠═5e0f7d2d-d6f9-40d9-b3a4-4404c2c66950 +# ╠═6c8163e3-5a18-4561-a9f4-834e42657f7d +# ╠═9f98ca07-532d-4fc6-a1bd-13a182b0db50 +# ╠═7f709599-084b-433f-9b6a-6ded827b69f2 +# ╠═b84ba9e4-5ce2-4b10-bb45-eed88c6a4bbe +# ╠═5e538b33-20ae-4520-92ec-efc01494ffcc +# ╠═44b283ee-e28c-473d-922f-8f1b8f982f10 +# ╠═e910ae7a-680e-44a5-a35d-cabe2dfa50d0 +# ╟─44fe57a1-edce-45c7-9a8b-40857bddc285 +# ╠═06523a91-4665-4e31-b6e2-732cbfd0e0e4 +# ╠═867253fa-32ee-4ab4-bc42-3f4c2f0e5fa4 +# ╠═6e61aac8-5a50-47a7-a150-6557a47e2d3b +# ╠═a675e62f-c55e-4d70-85d8-83b5845cd063 +# ╠═732df933-40ca-409c-9d88-bb80ea6d21b0 +# ╟─17002ccb-16c2-449c-849a-70f090fea5e6 +# ╠═fdb80bf1-8c88-474e-935c-9e7c230b5b72 +# ╠═9de1c3d1-fb60-42e2-8d53-111842337458 +# ╠═7cf6b40c-f89b-44bc-847d-a06a92d86098 +# ╟─9d51b315-b501-4140-af02-b645f04ec7a7 +# ╠═dabecbec-8cd0-48f7-8a13-0bdecd45eb85 +# ╠═c0764ed0-4b2c-4bf5-98db-9b7349560530 +# ╠═733cb346-2d08-4c35-8596-946b31ecc7e9 +# ╠═466f22aa-52ff-442f-be00-f4f32e24a173 +# ╟─e10f9a86-74f1-4dfb-87c9-fcd920e23c27 +# ╠═efc78626-3a50-4c7d-8a7d-ba2b67df57e3 +# ╠═e841b4d8-1e8e-4fd9-b805-4ee0c6359df5 +# ╠═de4839e1-5ac0-415d-8928-e4a9a358deae +# ╠═1b858a96-f894-4276-90a2-aa9833d9dd37 +# ╠═70dec0fa-a87c-4266-819d-a2ad5903d24a +# ╠═463ae91e-5533-46d0-8907-32f9d5ba17cf +# ╠═00aa935b-0f1a-43ae-8437-bde5e34c1fcd +# ╠═cafb8b69-ebc1-49d6-afe5-ff8af54eb222 +# ╠═bc2c0bea-621c-45f6-bc72-3d8907a280dc +# ╠═503bdbad-70f8-42d2-977b-af6ba06b2cde +# ╠═e8dfb521-6750-4df6-b4ff-0cabf5989e8f +# ╠═70850ada-165a-4e0d-942a-9dc311add0a6 +# ╠═d226e83d-b405-4dd3-9697-471bdbff97a2 +# ╠═050f3e9f-62e9-445d-8c95-9f0419c01c0e +# ╟─eb1af86d-a40c-411d-a211-d7a43386bf44 +# ╠═ace2c946-7984-4c17-bedb-06dccd6e8a36 +# ╠═8386d993-ffcc-4c6a-a91b-247f8c97a2ff +# ╠═b5408b8a-eff4-4d42-9ba7-707a40d92956 +# ╠═b7a7e78a-8f0f-4f47-9f37-8ecf3ddc4972 +# ╠═86d65cab-d073-4e77-bc0f-3d7e135dcbf8 +# ╟─80c50ee0-fb55-4c2c-86dd-434524d1a5e7 +# ╠═dc4d7b85-c968-4271-9e44-f80b90e4d6af +# ╠═dfafbbcd-9465-4a78-867b-25703b5157ba +# ╟─cae714ed-ac90-454f-b2ec-e3bb13a71056 +# ╟─47f8d8f4-c6e3-4f78-93d3-c5bb4938a754 +# ╠═378f45ee-2e0e-428b-962f-fd686bc5d063 +# ╟─4bfb96ae-2087-41a8-b9b0-3f4b346992a2 +# ╠═b7a30f17-1f3b-497a-ab1c-bc9ce1ac6e56 +# ╠═d1136370-9fc2-47c6-a773-d4dc7901db83 +# ╟─8e564c5e-8c0e-4001-abaa-bf9575d41089 +# ╠═05b0aad1-a41b-4fe7-8b76-70848f71d9d2 +# ╟─074845c0-f5ae-4a7c-bf94-3fcade5fdab8 +# ╠═8be57ed0-0c7e-40d5-b780-28eb9f9c2490 +# ╟─068a66eb-35ef-45ff-8448-75fe67eec38f +# ╠═e32ada08-9f79-47b9-bfef-eaf5f8bbc058 +# ╠═d6c7baee-8c8e-4cd9-ba35-06edad733e91 +# ╟─5e7470f6-2bb5-49fb-93d7-1b8c8e402526 +# ╠═8c44ebe8-a2e9-4480-b1d8-b3c19350c029 +# ╟─20d08693-413b-4a52-9f54-d8f25b492b50 +# ╠═f2d2375d-2ca2-4e97-87f2-5adbf250d152 +# ╟─978048ae-170a-4b83-8dee-1715350e75cc +# ╠═23d0f743-d7be-400a-9972-4337ae1bffff +# ╠═a63763bf-1f87-400e-b4cd-b112c9a0cd64 +# ╠═9d6af6e9-435a-43e6-980a-0658a4b449a1 +# ╠═1e1b3303-1508-4acb-8dd0-3cf0c64d0a78 +# ╟─9b7446d5-0751-4df6-b716-e8d5f85848a8 +# ╠═0bad7fc4-0a8d-46e7-b126-91b3542fed42 +# ╟─6df404eb-d816-4ae4-ae3f-a39505f79669 +# ╠═5c04b2c2-e83b-4289-b439-2e016a20678e +# ╟─a78fb463-0ffe-41db-a48b-63a4ae9ff3f7 +# ╟─52867723-336e-460d-a1a6-a7993778b3e9 +# ╠═d9cd6181-cd1d-48f9-b610-f3b2dea1c640 +# ╟─bc4e4a20-2584-4706-a4d7-ad0d9de43351 +# ╠═9ffd1f23-f82c-45b5-9a69-9fde7e296cf1 +# ╟─61e70040-9fc4-4681-a25f-d144a857aabd +# ╠═efcd69c1-6ea2-4524-a053-bfb40fb01dda +# ╟─4938609f-ce32-4798-bfc8-c6ca205e1209 +# ╠═ba60b4e3-fcdc-4efc-994b-1872a8f58703 +# ╟─8c545f8e-22b3-4f53-a02d-5473bc9e1a3a +# ╠═cfd8e9d3-1bb0-42a2-920d-5e343609c237 +# ╠═6dd1b166-1a00-4e89-b46f-9621bf35982f +# ╟─25970a05-5503-455c-9b56-d0147702371c +# ╠═b758be56-9ed0-4474-8361-73b3d2de89af +# ╠═5084a4ed-1638-4d77-91e4-5d77788ce0fe diff --git a/examples/Use Case Examples.jl b/examples/Use Case Examples.jl new file mode 100644 index 00000000..130686f3 --- /dev/null +++ b/examples/Use Case Examples.jl @@ -0,0 +1,780 @@ +### A Pluto.jl notebook ### +# v0.19.9 + +#> [frontmatter] +#> title = "PowerModelsONM Use-Case Examples" + +using Markdown +using InteractiveUtils + +# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). +macro bind(def, element) + quote + local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end + local el = $(esc(element)) + global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) + el + end +end + +# ╔═╡ 701604a7-f004-4a2c-94a0-249993bf0ea0 +# ╠═╡ show_logs = false +begin + using Pkg + Pkg.activate(; temp=true) + Pkg.add([ + Pkg.PackageSpec(; name="PowerModelsONM", rev="v3.0-rc"), + Pkg.PackageSpec(; name="PowerModelsDistribution", version="0.14.4"), + Pkg.PackageSpec(; name="VegaLite", version="2.6.0"), + Pkg.PackageSpec(; name="DataFrames", version="1.3.4"), + Pkg.PackageSpec(; name="CSV", version="0.10.4"), + Pkg.PackageSpec(; name="PlutoUI", version="0.7.39") + ]) + + # Pluto Notebook features + using PlutoUI + + # Plotting + import VegaLite as VL + + # Data Frames, CSV Parsing + import DataFrames as DF + import CSV +end + +# ╔═╡ bc824edf-8b1d-47b3-8183-d2ec7032f500 +html""" + +""" + +# ╔═╡ 861f564c-8408-48fd-8cf2-d6e12915176a +md"""# ONM Use Cases + +In this Pluto notebook, we present several use cases that are used to study the basics of the ONM library. + +We use three different data sets, described in more detail below, consisting of + +- a modified version of the ieee13 disitribution feeder, which is also used in PowerModelsONM's unit tests, +- a modified version of the iowa-240 distribution feeder, and +- a feeder provided by a utility partner, whose raw data is not included here for privacy reasons. +""" + +# ╔═╡ 7a915f37-a7f5-4b85-ba15-42a9712832f7 +md""" +## Environment + +Here we setup the appropriate environment that was originally used to study the use cases in this notebook. +""" + +# ╔═╡ 9fdad25d-fb6c-48b2-9b6a-d2ea11b6c3a0 +md""" +### Use Gurobi? + +Originally, these results were obtained using Gurobi, and are not guaranteed to be exactly reproduced with other solvers, although experimentation with HiGHS suggested that results are very close, it not identical. + +In order to use the Gurobi solver, you must already have Gurobi binaries on your system, a valid license, and the `ENV` variables correctly populated. + +Use Gurobi? $(@bind use_gurobi CheckBox()) +""" + +# ╔═╡ b70631b1-5eb6-42ac-a7ec-358222633b62 +if use_gurobi + Pkg.add(Pkg.PackageSpec(; name="Gurobi", version="0.11.3")) + import Gurobi +end + +# ╔═╡ 62288458-90ff-4fa2-bae6-6f7216304b45 +md"""## Import PowerModelsONM + +In this notebook we import PowerModelsONM as `ONM`, which gives a clearer picture to the user from where each function originates. +""" + +# ╔═╡ 5baf2811-6b63-4edb-9dda-840bff0f1004 +# ╠═╡ show_logs = false +import PowerModelsONM as ONM + +# ╔═╡ 36f20393-54d1-4566-9d07-afaa5f83fc8c +md"The following is the path to the data used in this notebook" + +# ╔═╡ 03c51d46-3314-48bb-8039-f04780773ad3 +onm_path = joinpath(dirname(pathof(ONM)), "..", "examples", "data") + +# ╔═╡ 3ddafef5-26e9-405e-bf3b-81a1f2c5af27 +md""" +## Modified IEEE13 Use Case (ieee13-onm-mod) + +In the following Section, we will utilize the modified IEEE13 distribution feeder case. This case has been modified to include new switches, DER, including solar PV, energy storage, and traditional generation, and some additional loads, to create new load blocks and microgrids. The purpose behind the creation of this modified network was primarily for unit testing of the various features of PowerModelsONM. + +### Single-line diagram of ieee13-onm-mod + +Below is the single-line diagram of the modified IEEE13 distribution feeder. This was created by exporting the network data to the `graphml` format and useing yEd for layout and visualization. + +The legend is as follows: + +- Black dots are buses, +- red star is the substation, +- orange solid lines are transformers, +- black solid lines are lines, +- dashed green lines are normally-closed switches, +- dotted red lines are normally-open tie switches, +- yellow triangles are loads, +- blue circles are generators, +- blue trapezoids are solar PV, and +- blue rectangles are battery storage. + +""" + +# ╔═╡ d5b09fa0-32a1-42b0-824e-a19392ee3d9b +# ╠═╡ show_logs = false +begin + ieee13_svg = open(joinpath(onm_path, "ieee13.svg"), "r") do io + read(io, String) + end + + HTML("""
$(ieee13_svg)
""") +end + +# ╔═╡ 88f865e8-ac92-4ec5-a791-7f7802c1b731 +md"""### Instantiate ieee13-onm-mod data, settings, events + +In PowerModelsONM, there are several standard input files besides the network definition in DSS format. + +In particular, we often define *settings*, which can contain additional parameters, such as solver settings or options used to easily control which default constraints, objective terms, variables, etc. are used in a problem, or overwrites of the DSS parameters, in the syntax of the PowerModelsDistribution ENGINEERING data model. These *settings* apply across all time steps. + +We can also define *events*, which are a time-series of switching events, and apply to the time steps at which they are defined, **and all later timesteps**. + +Below, we load the base data set for ieee13-onm-mod by defining a `Dict` with paths to the relevant files, and parsing it with the `prepare_data!` function. +""" + +# ╔═╡ b11b3d8d-bb1e-433d-a97d-dc1eb0b29fd7 +# ╠═╡ show_logs = false +ieee13_data = ONM.prepare_data!( + Dict{String,Any}( + "network" => joinpath(onm_path, "network.ieee13.dss"), + "settings" => joinpath(onm_path, "settings.ieee13.json"), + "events" => joinpath(onm_path, "events.ieee13.json"), + ) +) + +# ╔═╡ 9174e419-e5d2-462f-9d04-d7d7067fa984 +ieee13_mn = deepcopy(ieee13_data["network"]) + +# ╔═╡ 917f902c-2f59-4a35-97e0-ccd1a6bd384a +md"""### Instantiate solvers for ieee13-onm-mod use cases +""" + +# ╔═╡ b0d134fb-aea8-41aa-a533-f1a45eb86c38 +md"If `use_gurobi`, set appropriate option before instantiating solvers" + +# ╔═╡ 596407b3-e534-47fb-913e-57ac634c7061 +ieee13_data["settings"]["solvers"]["useGurobi"] = use_gurobi + +# ╔═╡ 29872a21-ea5e-4875-8c61-8c1c1f55d34f +md"For the use-case utilizing ieee13-onm-mod data, we will only need a MIP solver" + +# ╔═╡ 1138de8e-6aaa-4fd3-9805-0632e819f74b +ieee13_mip_solver = ONM.build_solver_instances(; solver_options=ieee13_data["settings"]["solvers"])["mip_solver"] + +# ╔═╡ 00b34495-c18d-45f8-aa20-ced51dd91231 +md"""### Use-Case I: Radiality constraint comparison + +In the following use-case, we explore the effect of the radiality constraint on the number of loads shed throughout the time-series. + +The radiality constraint implemented by default in PowerModelsONM is based on a multi-commodity flow formulation as defined in **S. Lei, C. Chen, Y. Song, and Y. Hou, “Radiality constraints for resilient reconfiguration of distribution systems: Formulation and application to microgrid formation,” IEEE Transactions on Smart Grid, vol. 11, no. 5, pp. 3944–3956, 2020.** + + +""" + +# ╔═╡ 48b6664b-3512-4344-9ae3-12c8527f4726 +md"First we obtain the results for the case where radiality is enforced, which is the default state" + +# ╔═╡ 0d23be40-d3cf-4cb3-9976-1a3de0769be7 +# ╠═╡ show_logs = false +result_rad_enabled = ONM.optimize_switches( + ieee13_mn, + ieee13_mip_solver; + formulation=ONM.LPUBFDiagPowerModel, + algorithm="rolling-horizon", + problem="block" +) + +# ╔═╡ f6aba3cc-4cbb-42bb-bfb2-a8bfb89090f2 +md"Next, to obtain the results for the case where radiality is not enforced, we need to set the option 'options/constraints/disable-radiality-constraint' to false, which we can do with the `set_setting!` helper function which will return the multinetwork data structure." + +# ╔═╡ 02098b72-8d5c-46c3-b0b5-cfcbb4bcdead +ieee13_mn_rad_disabled = ONM.set_setting!( + deepcopy(ieee13_data), + ("options","constraints","disable-radiality-constraint"), + true +) + +# ╔═╡ ea73ea65-c4a7-4934-9900-2d815e1075f7 +# ╠═╡ show_logs = false +result_rad_disabled = ONM.optimize_switches( + ieee13_mn_rad_disabled, + ieee13_mip_solver; + formulation=ONM.LPUBFDiagPowerModel, + algorithm="rolling-horizon", + problem="block" +) + +# ╔═╡ 7c974201-7a25-4f08-aed9-6d6e28824e01 +md"Below is a plot of the number of load shed at each timestep for radiality constrained and unconstrained." + +# ╔═╡ 28adbe85-4e82-4fcd-8e8f-28421ad56c36 +begin + rad_enabled_dat = ONM.get_timestep_device_actions(ieee13_mn, result_rad_enabled) + rad_disabled_dat = ONM.get_timestep_device_actions(ieee13_mn, result_rad_disabled) + + ieee13_rad_df = DF.DataFrame( + loads_shed = [ + [length(r["Shedded loads"]) for r in rad_enabled_dat]..., + [length(r["Shedded loads"]) for r in rad_disabled_dat]... + ], + radiality = [ + fill("constrained", length(rad_enabled_dat))..., + fill("unconstrained", length(rad_disabled_dat))... + ], + simulation_time_steps = [ + sort(collect(values(ieee13_data["network"]["mn_lookup"])))..., + sort(collect(values(ieee13_data["network"]["mn_lookup"])))... + ] + ) + + ieee13_rad_df |> VL.@vlplot( + autosize = { + type ="fit", + contains= "padding" + }, + mark = "bar", + encoding = { + x = { + field = ":radiality", + type = "nominal", + axis = { + title = "", + labels = false + } + }, + y = { + field = ":loads_shed", + type = "quantitative", + axis = { + title = "# Loads Shed", + grid = true + } + }, + column = { + field = ":simulation_time_steps", + type = "nominal", + axis = { + title = "Time Step (hr)" + } + }, + color = { + field = ":radiality", + type = "nominal", + axis = { + title = "Radiality" + } + } + }, + config={ + view={stroke="transparent"}, + axis={domainWidth=1, titleFontSize=18, labelFontSize=18}, + legend={titleFontSize=18, labelFontSize=18}, + header={titleFontSize=18, labelFontSize=18}, + mark={titleFontSize=18, labelFontSize=18}, + title={titleFontSize=18, labelFontSize=18}, + } + ) +end + +# ╔═╡ c9b3f06f-e323-4b3e-9c7f-bf2fffc147ba +md""" +## Modified Iowa-240 Use Cases (iowa240-onm-mod) + +The modified Iowa-240 disitribution feeder case was dervied from the [Iowa Distribution Test Systems](http://wzy.ece.iastate.edu/Testsystem.html) developed by F. Bu *et al.*, published originally in **F. Bu, Y. Yuan, Z. Wang, K. Dehghanpour, and A. Kimber, "A Time-series Distribution Test System based on Real Utility Data." 2019 North American Power Symposium (NAPS), Wichita, KS, USA, 2019, pp. 1-6.** + +Again, like with ieee13-onm-mod, the goal with the modification of the Iowa-240 system was to create microgrids. In this case, the primary changes were to add DER, solar PV and energy storage in this case, and to equivalence out the distribution level transformers to simplify the case. + +### Single-line Diagram of iowa240-onm-mod + +Below is the single-line diagram for the modified Iowa-240 feeder, again made by exporting the network data to a `graphml` file and using yEd to perform layout and visualization. + +The legend is identical to the one noted for [Single-line diagram of ieee13-onm-mod](#single-line-diagram-of-ieee13-onm-mod) +""" + +# ╔═╡ 52e269ec-bb85-4bd7-a20b-2d74d8dc45d0 +begin + iowa240_svg = open(joinpath(onm_path, "iowa240.svg"), "r") do io + read(io, String) + end + HTML("""
$(iowa240_svg)
""") +end + +# ╔═╡ 9bdccc20-c648-4eef-b9c6-4b00879175ec +md"""### Load base iowa240-onm-mod data, settings, events +""" + +# ╔═╡ 3da0e6c8-4723-405a-9028-b689af65bbd2 +# ╠═╡ show_logs = false +iowa240_data = ONM.prepare_data!( + Dict{String,Any}( + "network" => joinpath(onm_path, "network.iowa240.dss"), + "settings" => joinpath(onm_path, "settings.iowa240.json"), + "events" => joinpath(onm_path, "events.iowa240.json"), + ) +) + +# ╔═╡ b2c44bdf-7460-4330-a97b-984dbea8e0be +iowa240_mn = iowa240_data["network"] + +# ╔═╡ 46670d95-c4c5-4a3b-adce-f9637ce1daba +md"""### Instantiate iowa240-onm-mod solvers + +If `use_gurobi` is `false`, we need to disable the Gurobi solver before solvers are instantiated +""" + +# ╔═╡ f5dbdad4-f919-4b34-a7b6-5c479148528f +iowa240_data["settings"]["solvers"]["useGurobi"] = use_gurobi + +# ╔═╡ 3d3049c4-9363-4c52-b14b-9f41e14657d2 +iowa240_mip_solver = ONM.build_solver_instances(; solver_options=iowa240_data["settings"]["solvers"])["mip_solver"] + +# ╔═╡ e37c48ba-f0ba-4138-8677-d21ed1be4992 +iowa240_nlp_solver = ONM.build_solver_instances(; solver_options=iowa240_data["settings"]["solvers"])["nlp_solver"] + +# ╔═╡ 60378675-075f-427c-909c-602bdd34e15c +md"""### Use-Case II: Algorithm Comparison between Rolling-Horizon and Full-Lookahead + +In this use-case we utilize the iowa240-onm-mod case to explore the differences when using the two algorithms currently available in PowerModelsONM: `"rolling-horizon"`, which optimizes the feeder topology sequentially one timestep at a time, and `"full-lookahead"`, which optimizes the feeder topology for all timesteps all at once. + +For this use-case we need to run two optimizations, one using `"rolling-horizon"` (`result_iowa240_rolling`), and one using `"full-lookahead"` (`result_iowa240_lookahead`). + +Note that in each case the ACR formulation is utilized after the switch optimization to obtain the optimal dispatch results, which requires some additional time, so these cases will take between 300-500 seconds to run, each. +""" + +# ╔═╡ 2bb9deea-580f-4614-9bc4-c4a804a7a69e +# ╠═╡ show_logs = false +result_iowa240_rolling = ONM.optimize_dispatch( + iowa240_mn, + ONM.ACRUPowerModel, + iowa240_nlp_solver; + switching_solutions=ONM.optimize_switches( + iowa240_mn, + iowa240_mip_solver; + formulation=ONM.LPUBFDiagPowerModel, + algorithm="rolling-horizon", + problem="block" + ) +) + +# ╔═╡ f058abdf-714a-4044-bcb9-85dde7f75190 +# ╠═╡ show_logs = false +result_iowa240_lookahead = ONM.optimize_dispatch( + iowa240_mn, + ONM.ACRUPowerModel, + iowa240_nlp_solver; + switching_solutions=ONM.optimize_switches( + iowa240_mn, + iowa240_mip_solver; + formulation=ONM.LPUBFDiagPowerModel, + algorithm="full-lookahead", + problem="block" + ) +) + +# ╔═╡ 0a311867-ac57-472a-93ec-4de35aff3036 +md""" +Below is a plot of the comparison between the Storage State of Charge (SOC) in percent of total available stored energy, and the Total load served, as a percent of total load in the feeder, as a function of time in hours. +""" + +# ╔═╡ bbb3028f-e812-4caf-b859-a3b9814a34ec +begin + iowa240_rolling_total_served = ONM.get_timestep_load_served( + result_iowa240_rolling["solution"], + iowa240_mn)["Total load (%)"] + iowa240_rolling_soc_stats = ONM.get_timestep_storage_soc( + result_iowa240_rolling["solution"], + iowa240_mn + ) + + iowa240_lookahead_total_served = ONM.get_timestep_load_served( + result_iowa240_lookahead["solution"], + iowa240_mn)["Total load (%)"] + iowa240_lookahead_soc_stats = ONM.get_timestep_storage_soc( + result_iowa240_lookahead["solution"], + iowa240_mn + ) + + iowa240_algorithm_df = DF.DataFrame( + simulation_time_steps = [ + sort(collect(values(iowa240_mn["mn_lookup"])))..., + sort(collect(values(iowa240_mn["mn_lookup"])))..., + sort(collect(values(iowa240_mn["mn_lookup"])))..., + sort(collect(values(iowa240_mn["mn_lookup"])))..., + ].+1.0, + data = [ + iowa240_rolling_soc_stats..., + iowa240_rolling_total_served..., + iowa240_lookahead_soc_stats..., + iowa240_lookahead_total_served..., + ], + data_type = [ + fill("Storage SOC (%)", length(iowa240_rolling_soc_stats))..., + fill("Total Load Served (%)", length(iowa240_rolling_total_served))..., + fill("Storage SOC (%)", length(iowa240_lookahead_soc_stats))..., + fill("Total Load Served (%)", length(iowa240_lookahead_total_served))..., + ], + algorithm = [ + fill("Rolling Horizon", length(iowa240_rolling_soc_stats)*2)..., + fill("Decomposition", length(iowa240_rolling_soc_stats)*2)..., + ] + ) + + iowa240_algorithm_df |> VL.@vlplot( + mark="line", + autosize = { + type ="fit", + contains= "padding" + }, + encoding={ + row={ + field=":data_type", + type="nominal", + axis={ + title="", + labels=false + } + }, + x={ + field=":simulation_time_steps", + type="nominal", + axis={ + title="Time Step (hr)", + grid=true, + values=collect([i for i in iowa240_algorithm_df[!,:simulation_time_steps] if i%2==0]) + } + }, + y={ + field=":data", + type="quantitative", + axis={ + grid=true, + title="" + }, + scale={ + domain=[0,100] + } + }, + color={ + field=":algorithm", + type="nominal", + legend={ + orient="right", + fillColor="white" + }, + axis={ + title="Algorithm" + } + }, + }, + config={ + axis={titleFontSize=14, labelFontSize=14}, + legend={titleFontSize=14, labelFontSize=14}, + header={titleFontSize=14, labelFontSize=14}, + mark={titleFontSize=14, labelFontSize=14}, + title={titleFontSize=14, labelFontSize=14}, + } + ) +end + +# ╔═╡ bba41d8f-5a27-4b82-8613-ab0dc532db76 +md"""### Use-Case III: Formulation Comparison, LinDist3Flow vs Network Flow Approximation + +In this use case we compare the results of the standard LinDist3Flow formulation versus the results from the network flow approximation, otherwise known as the transportation model. +""" + +# ╔═╡ 67a23f8c-862f-4578-957f-4cfd82d20e89 +# ╠═╡ show_logs = false +result_iowa240_ldf = ONM.optimize_dispatch( + iowa240_mn, + ONM.LPUBFDiagPowerModel, + iowa240_nlp_solver; + switching_solutions=ONM.optimize_switches( + iowa240_mn, + iowa240_mip_solver; + formulation=ONM.LPUBFDiagPowerModel, + algorithm="full-lookahead", + problem="block" + ) +) + +# ╔═╡ 93647124-a64e-4ee0-a6ac-0c975b231a3e +# ╠═╡ show_logs = false +result_iowa240_nfa = ONM.optimize_dispatch( + iowa240_mn, + ONM.NFAUPowerModel, + iowa240_nlp_solver; + switching_solutions=ONM.optimize_switches( + iowa240_mn, + iowa240_mip_solver; + formulation=ONM.NFAUPowerModel, + algorithm="full-lookahead", + problem="block" + ) +) + +# ╔═╡ 775a2d77-2c72-4601-8600-6e60c71077fa +md"Below is a plot of the dispatch profiles for the LinDistFlow and Transportation models separated by the source of the dispatch, as a function of time" + +# ╔═╡ 2d5f4134-514f-442a-ae22-ae363e749382 +begin + profile_types = ["Solar DG (kW)", "Diesel DG (kW)", "Grid mix (kW)", "Energy storage (kW)"] + + iowa240_ldf_dispatch_stats = ONM.get_timestep_generator_profiles(result_iowa240_ldf["solution"]) + iowa240_nfa_dispatch_stats = ONM.get_timestep_generator_profiles(result_iowa240_nfa["solution"]) + + iowa240_formulation_df = DF.DataFrame( + simulation_time_steps = [ + [j for n in 1:length(profile_types) for j in sort(collect(values(iowa240_mn["mn_lookup"])))]..., + [j for n in 1:length(profile_types) for j in sort(collect(values(iowa240_mn["mn_lookup"])))]... + ], + data = [ + [j for item in profile_types for j in iowa240_nfa_dispatch_stats[item]]..., + [j for item in profile_types for j in iowa240_ldf_dispatch_stats[item]]... + ], + data_type = [ + [j for item in profile_types for j in fill(item, length(iowa240_mn["nw"]))]..., + [j for item in profile_types for j in fill(item, length(iowa240_mn["nw"]))]... + ], + formulation = [ + fill("Transportation", length(iowa240_mn["nw"])*length(profile_types))..., + fill("LinDistFlow", length(iowa240_mn["nw"])*length(profile_types))... + ] + ) + + iowa240_formulation_df |> VL.@vlplot( + autosize = { + type ="fit", + contains= "padding" + }, + mark="line", + encoding={ + row={ + field=":data_type", + type="nominal", + axis={ + title="", + labels=false + } + }, + x={ + field=":simulation_time_steps", + type="nominal", + axis={ + title="Time Step (hr)", + grid=true, + values=collect([i for i in iowa240_formulation_df[!,:simulation_time_steps] if i%2==0]) + } + }, + y={ + field=":data", + type="quantitative", + axis={ + grid=true, + title="" + } + }, + color={ + field=":formulation", + type="nominal", + legend={ + orient="right", + fillColor="white" + }, + axis={ + title="Formulation" + } + }, + }, + resolve={ + scale={ + y="independent" + } + }, + config={ + axis={titleFontSize=14, labelFontSize=14}, + legend={titleFontSize=14, labelFontSize=14}, + header={titleFontSize=14, labelFontSize=14}, + mark={titleFontSize=14, labelFontSize=14}, + title={titleFontSize=14, labelFontSize=14}, + } + ) +end + +# ╔═╡ 26a7c89c-2fdf-4657-b970-9e036f569504 +md""" +## Practical Use Case + +**Note that the input data for the practical use-case are propriatary, and we therefore only include some general details of the feeder, and some aggregate results from the optimization, with some explanation, to ensure compliance with the non-disclosure aggreement.** + +To demonstrate algorithmic scalability and highlight a practical use case enabled by our approach, we utilize a utility-provided feeder model featuring 374 buses (981 nodes), 40 dispatchable switches, and 25 DER. The DER include a mix of solar PV, battery storage, and traditional generation, split between 5 microgrids. This scenario considers 48 7.5-minute time steps over a 6-hour time horizon during the day, where solar PV irradiance is always non-zero, and the feeder is isolated throughout, and the microgrids are all operating independently in the initial time step. + +### Use-Case IV: Microgrid Networking, Enabled vs Disabled + +We ran two scenarios, "Enabled" one where microgrids were allowed to expand and network with one-another, and "Disabled" another where microgrids were allowed to expand, but not allowed to network with one-another. This is achieved by setting the option "options/constraints/disable-microgrid-networking" to `true`. + +Below is a plot that compares the percent of load served, separated by category, where total load is a percent of all possible load in the system supported, microgrid load is a percent of the total load contained only within microgrids, bonus load via MG is a percent of the total possible load *outside* the microgrids that are supported by microgrid resources, and feeder served is the total load outside the Microgrid that is supported by the feeder (substation). +""" + +# ╔═╡ 4bf3ca39-e55d-4ca8-9bfa-27b143a1c99e +begin + practical_case_stats = CSV.read(joinpath(onm_path, "practical-case-stats.csv"), DF.DataFrame) + + + practical_case_stats |> VL.@vlplot( + autosize = { + type ="fit", + contains= "padding" + }, + facet = {row={field=":type", title="Microgrid Networking"}}, + spec = { + encoding = { + x = { + field = ":simulation_time_steps", + type = "nominal", + axis = { + title = "Time Step (Min)", + values = [0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195], + grid = true + } + }, + }, + layer = [ + { + mark = "line", + encoding = { + y = { + field = ":Total Load Served", + type = "quantitative", + title = "Load Served (%)" + }, + color = { + datum = "Total Load", + legend = { + orient = "right", + fillColor = "white" + } + } + } + }, + { + mark = "line", + encoding = { + y = { + field = ":Microgrid Load Served", + type = "quantitative", + title = "Load Served (%)" + }, + color = { + datum = "Microgrid Load" + } + } + }, + { + mark = "line", + encoding = { + y = { + field = ":Bonus Load Served via Microgrid", + type = "quantitative", + title = "Load Served (%)" + }, + color = { + datum = "Bonus Load via MG" + } + } + }, + { + mark = "line", + encoding = { + y = { + field = ":Feeder Load Served", + type = "quantitative", + title = "Load Served (%)" + }, + color = { + datum = "Feeder Served" + } + } + } + ], + }, + config = { + axis = {titleFontSize = 18, labelFontSize = 18}, + legend = {titleFontSize = 18, labelFontSize = 18}, + header = {titleFontSize = 18, labelFontSize = 18}, + mark = {titleFontSize = 18, labelFontSize = 18}, + title = {titleFontSize = 18, labelFontSize = 18}, + } + ) +end + +# ╔═╡ Cell order: +# ╟─bc824edf-8b1d-47b3-8183-d2ec7032f500 +# ╟─861f564c-8408-48fd-8cf2-d6e12915176a +# ╟─7a915f37-a7f5-4b85-ba15-42a9712832f7 +# ╠═701604a7-f004-4a2c-94a0-249993bf0ea0 +# ╟─9fdad25d-fb6c-48b2-9b6a-d2ea11b6c3a0 +# ╠═b70631b1-5eb6-42ac-a7ec-358222633b62 +# ╟─62288458-90ff-4fa2-bae6-6f7216304b45 +# ╠═5baf2811-6b63-4edb-9dda-840bff0f1004 +# ╟─36f20393-54d1-4566-9d07-afaa5f83fc8c +# ╠═03c51d46-3314-48bb-8039-f04780773ad3 +# ╟─3ddafef5-26e9-405e-bf3b-81a1f2c5af27 +# ╟─d5b09fa0-32a1-42b0-824e-a19392ee3d9b +# ╟─88f865e8-ac92-4ec5-a791-7f7802c1b731 +# ╠═b11b3d8d-bb1e-433d-a97d-dc1eb0b29fd7 +# ╠═9174e419-e5d2-462f-9d04-d7d7067fa984 +# ╟─917f902c-2f59-4a35-97e0-ccd1a6bd384a +# ╟─b0d134fb-aea8-41aa-a533-f1a45eb86c38 +# ╠═596407b3-e534-47fb-913e-57ac634c7061 +# ╟─29872a21-ea5e-4875-8c61-8c1c1f55d34f +# ╠═1138de8e-6aaa-4fd3-9805-0632e819f74b +# ╟─00b34495-c18d-45f8-aa20-ced51dd91231 +# ╟─48b6664b-3512-4344-9ae3-12c8527f4726 +# ╠═0d23be40-d3cf-4cb3-9976-1a3de0769be7 +# ╟─f6aba3cc-4cbb-42bb-bfb2-a8bfb89090f2 +# ╠═02098b72-8d5c-46c3-b0b5-cfcbb4bcdead +# ╠═ea73ea65-c4a7-4934-9900-2d815e1075f7 +# ╟─7c974201-7a25-4f08-aed9-6d6e28824e01 +# ╟─28adbe85-4e82-4fcd-8e8f-28421ad56c36 +# ╟─c9b3f06f-e323-4b3e-9c7f-bf2fffc147ba +# ╟─52e269ec-bb85-4bd7-a20b-2d74d8dc45d0 +# ╟─9bdccc20-c648-4eef-b9c6-4b00879175ec +# ╠═3da0e6c8-4723-405a-9028-b689af65bbd2 +# ╠═b2c44bdf-7460-4330-a97b-984dbea8e0be +# ╟─46670d95-c4c5-4a3b-adce-f9637ce1daba +# ╠═f5dbdad4-f919-4b34-a7b6-5c479148528f +# ╠═3d3049c4-9363-4c52-b14b-9f41e14657d2 +# ╠═e37c48ba-f0ba-4138-8677-d21ed1be4992 +# ╟─60378675-075f-427c-909c-602bdd34e15c +# ╠═2bb9deea-580f-4614-9bc4-c4a804a7a69e +# ╠═f058abdf-714a-4044-bcb9-85dde7f75190 +# ╟─0a311867-ac57-472a-93ec-4de35aff3036 +# ╟─bbb3028f-e812-4caf-b859-a3b9814a34ec +# ╟─bba41d8f-5a27-4b82-8613-ab0dc532db76 +# ╠═67a23f8c-862f-4578-957f-4cfd82d20e89 +# ╠═93647124-a64e-4ee0-a6ac-0c975b231a3e +# ╟─775a2d77-2c72-4601-8600-6e60c71077fa +# ╟─2d5f4134-514f-442a-ae22-ae363e749382 +# ╟─26a7c89c-2fdf-4657-b970-9e036f569504 +# ╟─4bf3ca39-e55d-4ca8-9bfa-27b143a1c99e diff --git a/examples/data/events.ieee13.json b/examples/data/events.ieee13.json new file mode 100644 index 00000000..d5fcb22e --- /dev/null +++ b/examples/data/events.ieee13.json @@ -0,0 +1,111 @@ +[ + { + "affected_asset": "line.671692", + "event_type": "switch", + "timestep": 1, + "event_data": { + "dispatchable": false, + "state": "open", + "status": 1 + } + }, + { + "affected_asset": "line.671700", + "event_type": "switch", + "timestep": 1, + "event_data": { + "dispatchable": "NO", + "state": "OPEN", + "status": "ENABLED" + } + }, + { + "affected_asset": "line.701702", + "event_type": "switch", + "timestep": 1, + "event_data": { + "dispatchable": "NO", + "state": "OPEN", + "status": "ENABLED" + } + }, + { + "affected_asset": "line.703800", + "event_type": "switch", + "timestep": 1, + "event_data": { + "dispatchable": "NO", + "state": "OPEN", + "status": "ENABLED" + } + }, + { + "affected_asset": "line.800801", + "event_type": "switch", + "timestep": 1, + "event_data": { + "dispatchable": "NO", + "state": "OPEN", + "status": "ENABLED" + } + }, + { + "affected_asset": "line.801675", + "event_type": "switch", + "timestep": 1, + "event_data": { + "dispatchable": "NO", + "state": "OPEN", + "status": "ENABLED" + } + }, + + { + "affected_asset": "line.671700", + "event_type": "switch", + "timestep": 2, + "event_data": { + "dispatchable": "YES" + } + }, + { + "affected_asset": "line.701702", + "event_type": "switch", + "timestep": 2, + "event_data": { + "dispatchable": "YES" + } + }, + { + "affected_asset": "line.703800", + "event_type": "switch", + "timestep": 2, + "event_data": { + "dispatchable": "YES" + } + }, + { + "affected_asset": "line.671692", + "event_type": "switch", + "timestep": 2, + "event_data": { + "dispatchable": "YES" + } + }, + { + "affected_asset": "line.800801", + "event_type": "switch", + "timestep": 2, + "event_data": { + "dispatchable": "YES" + } + }, + { + "affected_asset": "line.801675", + "event_type": "switch", + "timestep": 2, + "event_data": { + "dispatchable": "YES" + } + } +] diff --git a/examples/data/events.iowa240.json b/examples/data/events.iowa240.json new file mode 100644 index 00000000..bf3628c4 --- /dev/null +++ b/examples/data/events.iowa240.json @@ -0,0 +1,190 @@ +[ + { + "event_data": { + "status": "ENABLED", + "dispatchable": "NO", + "state": "OPEN" + }, + "timestep": 1, + "affected_asset": "line.cb_101", + "event_type": "switch" + }, + { + "event_data": { + "status": "ENABLED", + "dispatchable": "NO", + "state": "OPEN" + }, + "timestep": 1, + "affected_asset": "line.cb_102", + "event_type": "switch" + }, + { + "event_data": { + "status": "ENABLED", + "dispatchable": "NO", + "state": "OPEN" + }, + "timestep": 1, + "affected_asset": "line.cb_302", + "event_type": "switch" + }, + { + "event_data": { + "status": "ENABLED", + "dispatchable": "NO", + "state": "OPEN" + }, + "timestep": 1, + "affected_asset": "line.cb_303", + "event_type": "switch" + }, + { + "event_data": { + "status": "ENABLED", + "dispatchable": "NO", + "state": "OPEN" + }, + "timestep": 1, + "affected_asset": "line.cb_202", + "event_type": "switch" + }, + { + "event_data": { + "status": "ENABLED", + "dispatchable": "NO", + "state": "OPEN" + }, + "timestep": 1, + "affected_asset": "line.cb_203", + "event_type": "switch" + }, + { + "event_data": { + "status": "ENABLED", + "dispatchable": "NO", + "state": "OPEN" + }, + "timestep": 1, + "affected_asset": "line.cb_201", + "event_type": "switch" + }, + { + "event_data": { + "status": "ENABLED", + "dispatchable": "NO", + "state": "OPEN" + }, + "timestep": 1, + "affected_asset": "line.cb_301", + "event_type": "switch" + }, + { + "event_data": { + "status": "ENABLED", + "dispatchable": "NO", + "state": "OPEN" + }, + "timestep": 1, + "affected_asset": "line.cb_204", + "event_type": "switch" + }, + { + "event_data": { + "status": "ENABLED", + "dispatchable": "NO", + "type": "breaker", + "state": "OPEN" + }, + "timestep": 1, + "affected_asset": "line.cb_101", + "event_type": "switch" + }, + { + "event_data": { + "status": "ENABLED", + "dispatchable": "NO", + "type": "breaker", + "state": "OPEN" + }, + "timestep": 1, + "affected_asset": "line.cb_201", + "event_type": "switch" + }, + { + "event_data": { + "status": "ENABLED", + "dispatchable": "NO", + "type": "breaker", + "state": "OPEN" + }, + "timestep": 1, + "affected_asset": "line.cb_202", + "event_type": "switch" + }, + { + "event_data": { + "status": "ENABLED", + "dispatchable": "NO", + "type": "breaker", + "state": "OPEN" + }, + "timestep": 1, + "affected_asset": "line.cb_301", + "event_type": "switch" + }, + { + "event_data": { "dispatchable": "NO" }, + "timestep": 2, + "affected_asset": "line.cb_102", + "event_type": "switch" + }, + { + "event_data": { "dispatchable": "YES" }, + "timestep": 2, + "affected_asset": "line.cb_202", + "event_type": "switch" + }, + { + "event_data": { "dispatchable": "YES" }, + "timestep": 2, + "affected_asset": "line.cb_203", + "event_type": "switch" + }, + { + "event_data": { "dispatchable": "NO" }, + "timestep": 2, + "affected_asset": "line.cb_204", + "event_type": "switch" + }, + { + "event_data": { "dispatchable": "YES" }, + "timestep": 2, + "affected_asset": "line.cb_302", + "event_type": "switch" + }, + { + "event_data": { "dispatchable": "NO" }, + "timestep": 2, + "affected_asset": "line.cb_303", + "event_type": "switch" + }, + { + "event_data": { "dispatchable": "YES" }, + "timestep": 6, + "affected_asset": "line.cb_101", + "event_type": "switch" + }, + { + "event_data": { "dispatchable": "YES" }, + "timestep": 12, + "affected_asset": "line.cb_201", + "event_type": "switch" + }, + { + "event_data": { "dispatchable": "YES" }, + "timestep": 18, + "affected_asset": "line.cb_301", + "event_type": "switch" + } +] diff --git a/examples/data/ieee13.svg b/examples/data/ieee13.svg new file mode 100644 index 00000000..cd0c8ad5 --- /dev/null +++ b/examples/data/ieee13.svg @@ -0,0 +1,963 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 675 + + + + + + + + + + + 675aux + + + + + + + + + + + 692 + + + + + + + + + + + + + generator.675 + + + + + + + + + + + + + load.675 + + + + + + + + + + + + + load.692 + + + + + + + + + + + 700 + + + + + + + + + + + 701 + + + + + + + + + + + + + load.700 + + + + + + + + + + + + + load.701 + + + + + + + + + + + + + solar.pv_mg1b + + + + + + + + + + + + + storage.battery_mg1a + + + + + + + + + + + + + storage.battery_mg1b + + + + + + + + + + + 801 + + + + + + + + + + + + + load.801 + + + + + + + + + + + + + storage.battery_mg1c + + + + + + + + + + + 702 + + + + + + + + + + + 703 + + + + + + + + + + + + + load.702 + + + + + + + + + + + + + load.703 + + + + + + + + + + + + + solar.pv_mg1a + + + + + + + + + + + 671 + + + + + + + + + + + 680 + + + + + + + + + + + 652 + + + + + + + + + + + 634 + + + + + + + + + + + 650 + + + + + + + + + + + rg60 + + + + + + + + + + + 611 + + + + + + + + + + + 645 + + + + + + + + + + + 632 + + + + + + + + + + + 633 + + + + + + + + + + + 684 + + + + + + + + + + + sourcebus + + + + + + + + + + + 670 + + + + + + + + + + + 646 + + + + + + + + + + + + + voltage_source.source + + + + + + + + + + + + + load.671 + + + + + + + + + + + + + load.634 + + + + + + + + + + + + + load.652 + + + + + + + + + + + + + load.611 + + + + + + + + + + + + + load.645 + + + + + + + + + + + + + load.670 + + + + + + + + + + + + + load.646 + + + + + + + 702703 + + + + + + 692675 + + + + + + 675675aux + + + + + + 700701 + + + + + + 632670 + + + + + + 632645 + + + + + + 684611 + + + + + + 671684 + + + + + + 645646 + + + + + + 650632 + + + + + + 671680 + + + + + + 632633 + + + + + + 684652 + + + + + + 670671 + + + + + + xfm1 + + + + + + reg1 + + + + + + sub + + + + + + 801675 + + + + + + 671692 + + + + + + 671700 + + + + + + 703800 + + + + + + 800801 + + + + + + 701702 + + + + diff --git a/examples/data/iowa240.svg b/examples/data/iowa240.svg new file mode 100644 index 00000000..fe978785 --- /dev/null +++ b/examples/data/iowa240.svg @@ -0,0 +1,10788 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + l_1002_1004 + + + + + + l_1006_1006_l_1 + + + + + + l_1009_1010 + + + + + + l_1015_1015_l + + + + + + l_1014_1014_l + + + + + + l_1005_1005_l + + + + + + l_1017_1017_l + + + + + + l_1014_1015 + + + + + + l_1013_1013_l + + + + + + l_1004_1005 + + + + + + l_1006_1007 + + + + + + l_1013_1016 + + + + + + l_1012_1013 + + + + + + l_1016_1016_l + + + + + + l_1016_1017 + + + + + + l_1011_1011_l + + + + + + l_1009_1009_l + + + + + + l_1005_1006 + + + + + + l_1007_1007_l_2 + + + + + + l_1010_1010_l + + + + + + l_1002_1003 + + + + + + l_1009_1011 + + + + + + l_1003_1003_l + + + + + + l_1013_1014 + + + + + + l_1008_1009 + + + + + + l_1004_1004_l + + + + + + l_1001_1002 + + + + + + l_1008_1008_l + + + + + + l_1011_1012 + + + + + + l_1012_1012_l + + + + + + l_1006_1008 + + + + + + l_3107_3140 + + + + + + l_3088_3089 + + + + + + l_3133_3137 + + + + + + l_3132_3132_l + + + + + + l_3142_3142_l + + + + + + l_3120_3120_l + + + + + + l_3144_3144_l + + + + + + l_3145_3146 + + + + + + l_3154_3154_l + + + + + + l_3118_3119 + + + + + + l_3106_3106_l + + + + + + l_3097_3097_l + + + + + + l_3092_3100 + + + + + + l_3138_3138_l + + + + + + l_3136_3136_l + + + + + + l_3158_3158_l + + + + + + l_3114_3114_l + + + + + + l_3161_3162 + + + + + + l_3153_3154 + + + + + + l_3118_3132 + + + + + + l_3102_3103 + + + + + + l_3103_3103_l + + + + + + l_3126_3126_l + + + + + + l_3153_3153_l + + + + + + l_3142_3143 + + + + + + l_3141_3142 + + + + + + l_3088_3088_l + + + + + + l_3139_3139_l + + + + + + l_3116_3117 + + + + + + l_3130_3131 + + + + + + l_3083_3084 + + + + + + l_3151_3152 + + + + + + l_3144_3145 + + + + + + l_3114_3115 + + + + + + l_3143_3143_l + + + + + + l_3080_3082 + + + + + + l_3124_3124_l + + + + + + l_3094_3095 + + + + + + l_3125_3125_l + + + + + + l_3109_3110 + + + + + + l_3089_3089_l + + + + + + l_3108_3108_l + + + + + + l_3109_3109_l + + + + + + l_3150_3150_l + + + + + + l_3113_3114 + + + + + + l_3140_3156 + + + + + + l_3105_3105_l + + + + + + l_3096_3096_l + + + + + + l_3140_3141 + + + + + + l_3087_3087_l + + + + + + l_3101_3102 + + + + + + l_3081_3081_l + + + + + + l_3152_3153 + + + + + + l_3112_3112_l + + + + + + l_3132_3133 + + + + + + l_3091_3091_l + + + + + + l_3092_3093 + + + + + + l_3141_3141_l + + + + + + l_3079_3080 + + + + + + l_3080_3081 + + + + + + l_3116_3116_l + + + + + + l_3104_3104_l + + + + + + l_3082_3107 + + + + + + l_3128_3129 + + + + + + l_3135_3135_l + + + + + + l_3093_3094 + + + + + + l_3160_3160_l + + + + + + l_3119_3120 + + + + + + l_3110_3111 + + + + + + l_3076_3077 + + + + + + l_3124_3125 + + + + + + l_3115_3115_l + + + + + + l_3083_3083_l + + + + + + l_3122_3122_l + + + + + + l_3099_3099_l + + + + + + l_3101_3101_l + + + + + + l_3086_3086_l + + + + + + l_3156_3157 + + + + + + l_3095_3096 + + + + + + l_3140_3148 + + + + + + l_3137_3137_l + + + + + + l_3111_3112 + + + + + + l_3117_3117_l + + + + + + l_3107_3108 + + + + + + l_3162_3162_l + + + + + + l_3090_3091 + + + + + + l_3150_3151 + + + + + + l_3138_3139 + + + + + + l_3084_3084_l + + + + + + l_3133_3134 + + + + + + l_3134_3135 + + + + + + l_3110_3110_l + + + + + + l_3090_3090_l + + + + + + l_3121_3121_l + + + + + + l_3130_3130_l + + + + + + l_3105_3106 + + + + + + l_3098_3099 + + + + + + l_3160_3161 + + + + + + l_3148_3149 + + + + + + l_3078_3078_l + + + + + + l_3098_3098_l + + + + + + l_3082_3083 + + + + + + l_3107_3118 + + + + + + l_3146_3146_l + + + + + + l_3147_3147_l + + + + + + l_3085_3086 + + + + + + l_3154_3155 + + + + + + l_3146_3147 + + + + + + l_3104_3105 + + + + + + l_3148_3148_l + + + + + + l_3087_3088 + + + + + + l_3120_3121 + + + + + + l_3158_3159 + + + + + + l_3129_3130 + + + + + + l_3127_3128 + + + + + + l_3102_3102_l + + + + + + l_3089_3090 + + + + + + l_3085_3085_l + + + + + + l_3086_3087 + + + + + + l_3152_3152_l + + + + + + l_3137_3138 + + + + + + l_3100_3104 + + + + + + l_3121_3122 + + + + + + l_3157_3158 + + + + + + l_3082_3092 + + + + + + l_3156_3144 + + + + + + l_3161_3161_l + + + + + + l_3093_3093_l + + + + + + l_3123_3124 + + + + + + l_3126_3127 + + + + + + l_3129_3129_l + + + + + + l_3149_3149_l + + + + + + l_3125_3126 + + + + + + l_3100_3101 + + + + + + l_3119_3123 + + + + + + l_3159_3159_l + + + + + + l_3134_3134_l + + + + + + l_3076_3079 + + + + + + l_3128_3128_l + + + + + + l_3077_3077_l + + + + + + l_3092_3098 + + + + + + l_3123_3123_l + + + + + + l_3095_3095_l + + + + + + l_3107_3113 + + + + + + l_3127_3127_l + + + + + + l_3111_3111_l + + + + + + l_3151_3151_l + + + + + + l_3155_3155_l + + + + + + l_3094_3094_l + + + + + + l_3157_3157_l + + + + + + l_3096_3097 + + + + + + l_3135_3136 + + + + + + l_3113_3116 + + + + + + l_3149_3150 + + + + + + l_3145_3145_l + + + + + + l_3077_3078 + + + + + + l_3084_3085 + + + + + + l_3131_3131_l + + + + + + l_3159_3160 + + + + + + l_3108_3109 + + + + + + t_3082 + + + + + + l_2030_2031 + + + + + + l_2028_2028_l + + + + + + l_2030_2030_l + + + + + + l_2043_2043_l + + + + + + l_2034_2034_l + + + + + + l_2053_2053_l + + + + + + l_2033_2035 + + + + + + l_2039_2042 + + + + + + l_2050_2051 + + + + + + l_2048_2048_l + + + + + + l_2027_2028 + + + + + + l_2032_2033 + + + + + + l_2038_2039 + + + + + + l_2058_2058_l + + + + + + l_2037_2037_l + + + + + + l_2051_2051_l + + + + + + l_2035_2036 + + + + + + l_2059_2060 + + + + + + l_2036_2037 + + + + + + l_2044_2049 + + + + + + l_2049_2049_l + + + + + + l_2037_2038 + + + + + + l_2050_2050_l + + + + + + l_2046_2046_l + + + + + + l_2040_2041 + + + + + + l_2043_2044 + + + + + + l_2053_2054 + + + + + + l_2047_2047_l + + + + + + l_2033_2034 + + + + + + l_2049_2050 + + + + + + l_2056_2056_l + + + + + + l_2031_2031_l + + + + + + l_2059_2059_l + + + + + + l_2052_2052_l + + + + + + l_2055_2055_l + + + + + + l_2054_2054_l + + + + + + l_2054_2055 + + + + + + l_2044_2045 + + + + + + l_2040_2040_l + + + + + + l_2039_2040 + + + + + + l_2042_2042_l + + + + + + l_2029_2029_l + + + + + + l_2045_2045_l + + + + + + l_2029_2030 + + + + + + l_2057_2059 + + + + + + l_2026_2027 + + + + + + l_2053_2057 + + + + + + l_2057_2058 + + + + + + l_2045_2047 + + + + + + l_2045_2046 + + + + + + l_2055_2056 + + + + + + l_2060_2060_l + + + + + + l_2047_2048 + + + + + + l_2044_2053 + + + + + + l_2035_2035_l + + + + + + l_2042_2043 + + + + + + l_2032_2032_l + + + + + + l_2028_2029 + + + + + + l_2051_2052 + + + + + + l_2027_2032 + + + + + + l_2041_2041_l + + + + + + reg1 + + + + + + sub_xfmr + + + + + + l_2023_2024 + + + + + + l_2025_2025_l + + + + + + l_2021_2022 + + + + + + l_2024_2024_l + + + + + + l_2014_2015 + + + + + + l_2024_2025 + + + + + + l_2016_2017 + + + + + + l_2022_2023 + + + + + + l_2013_2019 + + + + + + l_2018_2018_l + + + + + + l_2014_2014_l + + + + + + l_2013_2014 + + + + + + l_2014_2016 + + + + + + l_2019_2020 + + + + + + l_2015_2015_l + + + + + + l_2017_2018 + + + + + + l_2020_2020_l + + + + + + l_2019_2021 + + + + + + l_2023_2023_l + + + + + + l_2022_2022_l + + + + + + l_2016_2016_l + + + + + + l_2017_2017_l + + + + + + l_2006_2007 + + + + + + l_2006_2010 + + + + + + l_2003_2004 + + + + + + l_2002_2003 + + + + + + l_2005_2005_l + + + + + + l_2009_2009_l + + + + + + l_2011_2011_l + + + + + + l_2004_2005 + + + + + + l_2002_2002_l + + + + + + l_2010_2010_l + + + + + + l_2007_2009 + + + + + + l_2008_2008_l + + + + + + l_2010_2011 + + + + + + l_2005_2006 + + + + + + l_2007_2008 + + + + + + l_2003_2003_l + + + + + + l_2001_2002 + + + + + + l_2011_2012 + + + + + + cb_101 + + + + + + cb_102 + + + + + + cb_302 + + + + + + cb_303 + + + + + + cb_202 + + + + + + cb_203 + + + + + + cb_201 + + + + + + cb_301 + + + + + + cb_204 + + + + + + l_3071_3071_l + + + + + + l_3071_3072 + + + + + + l_3039_3039_l + + + + + + l_3039_3053 + + + + + + l_3038_3039 + + + + + + l_3038_3038_l + + + + + + l_3005_3006 + + + + + + l_3005_3008 + + + + + + l_3041_3042 + + + + + + l_3041_3041_l + + + + + + l_3017_3017_l + + + + + + l_3013_3014 + + + + + + l_3013_3013_l + + + + + + l_3062_3063 + + + + + + l_3062_3062_l + + + + + + l_3011_3012 + + + + + + l_3011_3011_l + + + + + + l_3006_3006_l + + + + + + l_3006_3007 + + + + + + l_3046_3047 + + + + + + l_3046_3048 + + + + + + l_3031_3031_l + + + + + + l_3031_3032 + + + + + + l_3004_3004_l + + + + + + l_3043_3043_l + + + + + + l_3072_3072_l + + + + + + l_3033_3033_l + + + + + + l_3033_3034 + + + + + + l_3049_3049_l + + + + + + l_3049_3050 + + + + + + l_3002_3002_l + + + + + + l_3067_3067_l + + + + + + l_3027_3028 + + + + + + l_3027_3027_l + + + + + + l_3061_3061_l + + + + + + l_3066_3067 + + + + + + l_3066_3066_l + + + + + + l_3054_3054_l + + + + + + l_3030_3031 + + + + + + l_3030_3040 + + + + + + l_3030_3035 + + + + + + l_3019_3019_l + + + + + + l_3019_3020 + + + + + + l_3018_3018_l + + + + + + l_3018_3019 + + + + + + l_3010_3010_l + + + + + + l_3010_3011 + + + + + + l_3057_3057_l + + + + + + l_3057_3058 + + + + + + l_3064_3064_l + + + + + + l_3064_3065 + + + + + + l_3059_3060 + + + + + + l_3059_3059_l + + + + + + l_3074_3074_l + + + + + + l_3051_3051_l + + + + + + l_3051_3052 + + + + + + l_3007_3007_l + + + + + + l_3065_3066 + + + + + + l_3065_3065_l + + + + + + l_3009_3009_l + + + + + + l_3009_3010 + + + + + + l_3063_3063_l + + + + + + l_3063_3064 + + + + + + l_3024_3024_l + + + + + + l_3024_3025 + + + + + + l_3053_3055 + + + + + + l_3053_3054 + + + + + + l_3068_3075 + + + + + + l_3068_3069 + + + + + + l_3029_3029_l + + + + + + l_3032_3033 + + + + + + l_3032_3032_l + + + + + + l_3070_3070_l + + + + + + l_3070_3071 + + + + + + l_3026_3026_l + + + + + + l_3026_3027 + + + + + + l_3028_3028_l + + + + + + l_3028_3029 + + + + + + l_3025_3025_l + + + + + + l_3047_3047_l + + + + + + l_3044_3044_l + + + + + + l_3044_3045 + + + + + + l_3069_3073 + + + + + + l_3069_3070 + + + + + + l_3060_3060_l + + + + + + l_3060_3061 + + + + + + l_3034_3034_l + + + + + + l_3034_3068 + + + + + + l_3058_3058_l + + + + + + l_3058_3059 + + + + + + l_3045_3045_l + + + + + + l_3014_3014_l + + + + + + l_3003_3005 + + + + + + l_3003_3002 + + + + + + l_3003_3004 + + + + + + l_3016_3016_l + + + + + + l_3016_3017 + + + + + + l_3008_3013 + + + + + + l_3008_3015 + + + + + + l_3008_3009 + + + + + + l_3073_3074 + + + + + + l_3073_3073_l + + + + + + l_3042_3042_l + + + + + + l_3042_3043 + + + + + + l_3036_3036_l + + + + + + l_3036_3037 + + + + + + l_3056_3057 + + + + + + l_3056_3056_l + + + + + + l_3020_3021 + + + + + + l_3020_3020_l + + + + + + l_3055_3056 + + + + + + l_3055_3062 + + + + + + l_3052_3052_l + + + + + + l_3040_3044 + + + + + + l_3040_3041 + + + + + + l_3040_3046 + + + + + + l_3035_3035_l + + + + + + l_3035_3036 + + + + + + l_3012_3012_l + + + + + + l_3015_3016 + + + + + + l_3015_3018 + + + + + + l_3015_3022 + + + + + + l_3021_3021_l + + + + + + l_3048_3048_l + + + + + + l_3048_3049 + + + + + + l_3037_3038 + + + + + + l_3037_3037_l + + + + + + l_3001_3003 + + + + + + l_3023_3024 + + + + + + l_3023_3023_l + + + + + + l_3022_3023 + + + + + + l_3022_3026 + + + + + + l_3022_3030 + + + + + + l_3050_3050_l + + + + + + l_3050_3051 + + + + diff --git a/examples/data/network.ieee13.dss b/examples/data/network.ieee13.dss new file mode 100644 index 00000000..e1aff6b5 --- /dev/null +++ b/examples/data/network.ieee13.dss @@ -0,0 +1,176 @@ +Clear +Set DefaultBaseFrequency=60 + +! +! This script is based on a script developed by Tennessee Tech Univ students +! Tyler Patton, Jon Wood, and David Woods, April 2009 +! + +new circuit.IEEE13Nodeckt +~ basekv=115 pu=1.0001 phases=3 bus1=SourceBus +~ Angle=30 ! advance angle 30 deg so result agree with published angle +~ MVAsc3=20000 MVASC1=21000 ! stiffen the source to approximate inf source +~ baseMVA=1 + + +!SUB TRANSFORMER DEFINITION +! Although this data was given, it does not appear to be used in the test case results +! The published test case starts at 1.0 per unit at Bus 650. To make this happen, we will change the impedance +! on the transformer to something tiny by dividing by 1000 using the DSS in-line RPN math +New Transformer.Sub Phases=3 Windings=2 XHL=(8 1000 /) +~ wdg=1 bus=SourceBus conn=delta kv=115 kva=5000 %r=(.5 1000 /) +~ wdg=2 bus=650 conn=wye kv=4.16 kva=5000 %r=(.5 1000 /) + +! FEEDER 1-PHASE VOLTAGE REGULATORS +! Define low-impedance 2-wdg transformer + +New Transformer.Reg1 phases=1 bank=reg1 XHL=0.01 kVAs=[1666 1666] +~ Buses=[650.1 RG60.1] kVs=[2.4 2.4] %LoadLoss=0.01 +~ %rs=[0 0] ! correct default here + +New Transformer.Reg2 phases=1 bank=reg1 XHL=0.01 kVAs=[1666 1666] +~ Buses=[650.2 RG60.2] kVs=[2.4 2.4] %LoadLoss=0.01 +~ %rs=[0 0] ! correct default here + +New Transformer.Reg3 phases=1 bank=reg1 XHL=0.01 kVAs=[1666 1666] +~ Buses=[650.3 RG60.3] kVs=[2.4 2.4] %LoadLoss=0.01 +~ %rs=[0 0] ! correct default here + + +!TRANSFORMER DEFINITION +New Transformer.XFM1 Phases=3 Windings=2 XHL=2 +~ wdg=1 bus=633 conn=Wye kv=4.16 kva=500 %r=.55 XHT=1 +~ wdg=2 bus=634 conn=Wye kv=0.480 kva=500 %r=.55 XLT=1 + + +!LINE CODES + +// these are local matrix line codes +// corrected 9-14-2011 +New linecode.mtx601 nphases=3 BaseFreq=60 +~ rmatrix = (0.3465 | 0.1560 0.3375 | 0.1580 0.1535 0.3414 ) +~ xmatrix = (1.0179 | 0.5017 1.0478 | 0.4236 0.3849 1.0348 ) +~ units=mi +New linecode.mtx602 nphases=3 BaseFreq=60 +~ rmatrix = (0.7526 | 0.1580 0.7475 | 0.1560 0.1535 0.7436 ) +~ xmatrix = (1.1814 | 0.4236 1.1983 | 0.5017 0.3849 1.2112 ) +~ units=mi +New linecode.mtx603 nphases=2 BaseFreq=60 +~ rmatrix = (1.3238 | 0.2066 1.3294 ) +~ xmatrix = (1.3569 | 0.4591 1.3471 ) +~ units=mi +New linecode.mtx604 nphases=2 BaseFreq=60 +~ rmatrix = (1.3238 | 0.2066 1.3294 ) +~ xmatrix = (1.3569 | 0.4591 1.3471 ) +~ units=mi +New linecode.mtx605 nphases=1 BaseFreq=60 +~ rmatrix = (1.3292 ) +~ xmatrix = (1.3475 ) +~ units=mi +New Linecode.mtx606 nphases=3 Units=mi +~ Rmatrix=[0.791721 |0.318476 0.781649 |0.28345 0.318476 0.791721 ] +~ Xmatrix=[0.438352 |0.0276838 0.396697 |-0.0184204 0.0276838 0.438352 ] +~ Cmatrix=[383.948 |0 383.948 |0 0 383.948 ] +New linecode.mtx607 nphases=1 BaseFreq=60 +~ rmatrix = (1.3425 ) +~ xmatrix = (0.5124 ) +~ cmatrix = [236] +~ units=mi + +!LOADSHAPE DEFINITION +New LoadShape.microgrid1a interval=1 npts=8 useactual=no mult=(0.25, 0.4, 0.8, 1.0, 1.0, 0.8, 0.6, 0.5) +New LoadShape.microgrid1b interval=1 npts=8 useactual=no mult=(1.0, 1.0, 0.8, 0.5, 0.4, 0.4, 0.4, 0.8) +New LoadShape.microgrid1c interval=1 npts=8 useactual=no mult=(0.25, 0.5, 0.75, 0.75, 0.75, 1.0, 4.0, 0.8) +New LoadShape.microgrid1d interval=1 npts=8 useactual=no mult=(0.4, 0.4, 0.4, 1.0, 1.0, 0.8, 0.8, 0.6) +New LoadShape.pvdaily interval=1 npts=8 useactual=no mult=(0, 0, 0.4, 1.0, 0.8, 0.3, 0, 0) + +!LOAD DEFINITIONS +!New Load.671 Bus1=671.1.2.3 Phases=3 Conn=Delta Model=1 kV=4.16 kW=1155 kvar=660 vminpu=0.6 vmaxpu=1.4 +New Load.671_1 Bus1=671.1 Phases=1 Conn=Wye Model=1 kV=4.16 kW=385 kvar=220 vminpu=0.6 vmaxpu=1.4 +New Load.671_2 Bus1=671.2 Phases=1 Conn=Wye Model=1 kV=4.16 kW=385 kvar=220 vminpu=0.6 vmaxpu=1.4 +New Load.671_3 Bus1=671.3 Phases=1 Conn=Wye Model=1 kV=4.16 kW=385 kvar=220 vminpu=0.6 vmaxpu=1.4 +New Load.634a Bus1=634.1 Phases=1 Conn=Wye Model=1 kV=0.277 kW=160 kvar=110 vminpu=0.6 vmaxpu=1.4 +New Load.634b Bus1=634.2 Phases=1 Conn=Wye Model=1 kV=0.277 kW=120 kvar=90 vminpu=0.6 vmaxpu=1.4 +New Load.634c Bus1=634.3 Phases=1 Conn=Wye Model=1 kV=0.277 kW=120 kvar=90 vminpu=0.6 vmaxpu=1.4 +New Load.645 Bus1=645.2 Phases=1 Conn=Wye Model=1 kV=2.4 kW=170 kvar=125 vminpu=0.6 vmaxpu=1.4 +!New Load.646 Bus1=646.2.3 Phases=1 Conn=Delta Model=2 kV=4.16 kW=230 kvar=132 vminpu=0.6 vmaxpu=1.4 +New Load.646_2 Bus1=646.2 Phases=1 Conn=Wye Model=2 kV=4.16 kW=115 kvar=66 vminpu=0.6 vmaxpu=1.4 +New Load.646_3 Bus1=646.3 Phases=1 Conn=Wye Model=2 kV=4.16 kW=115 kvar=66 vminpu=0.6 vmaxpu=1.4 +!New Load.692 Bus1=692.3.1 Phases=1 Conn=Delta Model=5 kV=4.16 kW=170 kvar=151 vminpu=0.6 vmaxpu=1.4 +New Load.692_3 Bus1=692.3 Phases=1 Conn=Wye Model=2 kV=4.16 kW=85 kvar=75.5 vminpu=0.6 vmaxpu=1.4 daily=microgrid1d +New Load.692_1 Bus1=692.1 Phases=1 Conn=Wye Model=2 kV=4.16 kW=85 kvar=75.5 vminpu=0.6 vmaxpu=1.4 daily=microgrid1d +New Load.675a Bus1=675.1 Phases=1 Conn=Wye Model=1 kV=2.4 kW=485 kvar=190 vminpu=0.6 vmaxpu=1.4 daily=microgrid1d +New Load.675b Bus1=675.2 Phases=1 Conn=Wye Model=1 kV=2.4 kW=68 kvar=60 vminpu=0.6 vmaxpu=1.4 daily=microgrid1d +New Load.675c Bus1=675.3 Phases=1 Conn=Wye Model=1 kV=2.4 kW=290 kvar=212 vminpu=0.6 vmaxpu=1.4 daily=microgrid1d +New Load.611 Bus1=611.3 Phases=1 Conn=Wye Model=5 kV=2.4 kW=170 kvar=80 vminpu=0.6 vmaxpu=1.4 +New Load.652 Bus1=652.1 Phases=1 Conn=Wye Model=2 kV=2.4 kW=128 kvar=86 vminpu=0.6 vmaxpu=1.4 +New Load.670a Bus1=670.1 Phases=1 Conn=Wye Model=1 kV=2.4 kW=17 kvar=10 vminpu=0.6 vmaxpu=1.4 +New Load.670b Bus1=670.2 Phases=1 Conn=Wye Model=1 kV=2.4 kW=66 kvar=38 vminpu=0.6 vmaxpu=1.4 +New Load.670c Bus1=670.3 Phases=1 Conn=Wye Model=1 kV=2.4 kW=117 kvar=68 vminpu=0.6 vmaxpu=1.4 + +!CAPACITOR DEFINITIONS +New Capacitor.Cap1 Bus1=675 phases=3 kVAR=600 kV=4.16 +New Capacitor.Cap2 Bus1=611.3 phases=1 kVAR=100 kV=2.4 + +!Bus 670 is the concentrated point load of the distributed load on line 632 to 671 located at 1/3 the distance from node 632 + +!LINE DEFINITIONS +New Line.650632 Phases=3 Bus1=RG60.1.2.3 Bus2=632.1.2.3 LineCode=mtx601 Length=2000 units=ft normamps=800 emergamps=800 +New Line.632670 Phases=3 Bus1=632.1.2.3 Bus2=670.1.2.3 LineCode=mtx601 Length=667 units=ft normamps=800 emergamps=800 +New Line.670671 Phases=3 Bus1=670.1.2.3 Bus2=671.1.2.3 LineCode=mtx601 Length=1333 units=ft normamps=800 emergamps=800 +New Line.671680 Phases=3 Bus1=671.1.2.3 Bus2=680.1.2.3 LineCode=mtx601 Length=1000 units=ft normamps=800 emergamps=800 +New Line.632633 Phases=3 Bus1=632.1.2.3 Bus2=633.1.2.3 LineCode=mtx602 Length=500 units=ft +New Line.632645 Phases=2 Bus1=632.3.2 Bus2=645.3.2 LineCode=mtx603 Length=500 units=ft +New Line.645646 Phases=2 Bus1=645.3.2 Bus2=646.3.2 LineCode=mtx603 Length=300 units=ft +New Line.692675 Phases=3 Bus1=692.1.2.3 Bus2=675.1.2.3 LineCode=mtx606 Length=500 units=ft normamps=800 emergamps=800 +New Line.671684 Phases=2 Bus1=671.1.3 Bus2=684.1.3 LineCode=mtx604 Length=300 units=ft +New Line.684611 Phases=1 Bus1=684.3 Bus2=611.3 LineCode=mtx605 Length=300 units=ft +New Line.684652 Phases=1 Bus1=684.1 Bus2=652.1 LineCode=mtx607 Length=800 units=ft + +!SWITCH DEFINITIONS +New Line.671692 Phases=3 Bus1=671 Bus2=692 Switch=y r1=0 r0=0 x1=0.000 x0=0.000 c1=0.000 c0=0.000 normamps=800 emergamps=1000 + +!MICROGRID DEFINITIONS +New Line.671700 Phases=3 Bus1=670 Bus2=700 Switch=y r1=0 r0=0 x1=0.000 x0=0.000 c1=0.000 c0=0.000 enabled=y +New Line.700701 Phases=3 Bus1=700.1.2.3 Bus2=701.1.2.3 LineCode=mtx601 Length=800 units=ft +New Load.700 Phases=3 Bus1=700.1.2.3 Conn=Wye Model=1 kv=4.16 kw=10 kvar=3 daily=microgrid1b +New Load.701 Phases=3 Bus1=701.1.2.3 Conn=Wye Model=1 kv=4.16 kw=15 kvar=5 daily=microgrid1b + +New PVSystem.PV_mg1b Phases=3 Bus1=700.1.2.3 kv=4.16 conn=wye irradiance=1 Pmpp=25 kva=35 pf=0.74 daily=pvdaily +New Storage.Battery_mg1b Phases=3 Bus1=700 kwhstored=5 kwhrated=50 kwrated=10 %idlingkw=0 %idlingkvar=0 %effcharge=100 %effdischarge=100 %charge=100 %discharge=100 %r=0 %x=0 enabled=y + +New Line.701702 Phases=3 Bus1=701 Bus2=702 Switch=y r1=0 r0=0 x1=0.000 x0=0.000 c1=0.000 c0=0.000 enabled=y +New Line.702703 Phases=3 Bus1=702.1.2.3 Bus2=703.1.2.3 LineCode=mtx601 Length=900 units=ft +New Load.702 Phases=3 Bus1=702.1.2.3 conn=wye model=1 kv=4.16 kw=50 kvar=20 daily=microgrid1a +New Load.703 Phases=3 Bus1=703.1.2.3 conn=wye model=1 kv=4.16 kw=135 kvar=100 daily=microgrid1a + +New PVSystem.PV_mg1a Phases=3 Bus1=703.1.2.3 kv=4.16 conn=wye irradiance=1 Pmpp=25 kva=210 pf=0.74 daily=pvdaily +New Storage.Battery_mg1a Phases=3 Bus1=700 kwhstored=25 kwhrated=200 kwrated=50 %idlingkw=0 %idlingkvar=0 %effcharge=100 %effdischarge=100 %charge=100 %discharge=100 %r=0 %x=0 enabled=y + +New Line.703800 Phases=3 Bus1=703 Bus2=800aux Switch=y r1=0 r0=0 x1=0 x0=0 c1=0 c0=0 enabled=y normamps=50 emergamps=50 +New Line.800800aux Phases=3 Bus1=800aux Bus2=800 LineCode=mtx601 Length=1000 units=ft +New Line.800801 Phases=3 Bus1=800 Bus2=801 Switch=y r1=0 r0=0 x1=0 x0=0 c1=0 c0=0 enabled=y normamps=50 emergamps=50 + +New Load.801 Phases=3 Bus1=801 conn=wye model=1 kv=4.16 kw=200.0 kvar=120.0 daily=microgrid1c + +New Storage.Battery_mg1c Phases=3 Bus1=801 kwhstored=500 kwhrated=1000 kwrated=250 %idlingkw=0 %idlingkvar=0 %effcharge=100 %effdischarge=100 %charge=100 %discharge=100 %r=0 %x=0 enabled=y + +New Line.801675 Phases=3 Bus1=801 Bus2=675aux Switch=y r1=0 r0=0 x1=0 x0=0 c1=0.0 c0=0.0 normamps=50 emergamps=50 enabled=n ! tie switch +New Line.675675aux Phases=3 Bus1=675 Bus2=675aux LineCode=mtx601 Length=15 units=mi normamps=800 emergamps=800 + +New Generator.675 Phases=3 Bus1=675 kv=2.4 kva=500 kw=500 kvar=350 + +New Relay.801675 monitoredobj=line.801675 +New Recloser.671700 monitoredobj=line.671700 phasetrip=140 + +Set Voltagebases=[115, 4.16, .48] +calcv + +Transformer.Reg1.Taps=[1.0 1.0625] +Transformer.Reg2.Taps=[1.0 1.0500] +Transformer.Reg3.Taps=[1.0 1.06875] + +Set Controlmode=OFF +Set tolerance=0.001 + +Solve diff --git a/examples/data/network.iowa240.dss b/examples/data/network.iowa240.dss new file mode 100644 index 00000000..78f50d15 --- /dev/null +++ b/examples/data/network.iowa240.dss @@ -0,0 +1,1501 @@ +Clear +new circuit.240_node_test_system ! Initiate a new circuit called "240_node_test_system" + +Edit "Vsource.source" Bus1= eq_source_bus.1.2.3 Phases=3 Angle=0.00000 Pu=1.00000 BaseKv=69.00000 R1=4.54263687 X1=10.52743053 R0=7.36552668 X0=24.50463867 + +//------------------- substation transformer --------------------// +New Transformer.Sub_Xfmr Phases=3 Windings=2 XHL=6.26591063 +~ wdg=1 bus=eq_source_bus.1.2.3 conn=delta kV=69.000000 kva=10000.00000000 %R=0.62659091 +~ wdg=2 bus=bus_Xfmr.1.2.3.0 conn=wye kV=13.800000 kva=10000.00000000 %R=0.62659091 + +//-------------------- 3 single-phase tap changers -------------// +New Transformer.sub_regulator_A Phases=1 bank=Reg1 Windings=2 XHL=0.0100000 +~ wdg=1 bus=bus_Xfmr.1 conn=wye kV=7.9677 kva=3500 %R=0.00100000 +~ wdg=2 bus=bus1.1 conn=wye kV=7.9677 kva=3500 %R=0.00100000 NumTaps=32 MaxTap=1.1000 MinTap=0.9000 +! Winding number, bus, connection, voltage rating, kVA rating, percent resistance, total number of taps, max per unit tap, min per unit tap + +New Transformer.sub_regulator_B Phases=1 bank=Reg1 Windings=2 XHL=0.0100000 +~ wdg=1 bus=bus_Xfmr.2 conn=wye kV=7.9677 kva=3500 %R=0.00100000 +~ wdg=2 bus=bus1.2 conn=wye kV=7.9677 kva=3500 %R=0.00100000 NumTaps=32 MaxTap=1.1000 MinTap=0.9000 + +New Transformer.sub_regulator_C Phases=1 bank=Reg1 Windings=2 XHL=0.0100000 +~ wdg=1 bus=bus_Xfmr.3 conn=wye kV=7.9677 kva=3500 %R=0.00100000 +~ wdg=2 bus=bus1.3 conn=wye kV=7.9677 kva=3500 %R=0.00100000 NumTaps=32 MaxTap=1.1000 MinTap=0.9000 + +//------------------ tap controls ------------------------------// +New RegControl.Reg_contr_A Transformer=sub_regulator_A bus=bus1.1 Winding=2 vReg=123.00000 R=0.00000 X=0.00000 Band=2 PTratio=66.395279 vLimit=129.00000 +New RegControl.Reg_contr_B Transformer=sub_regulator_B bus=bus1.2 Winding=2 vReg=123.00000 R=0.00000 X=0.00000 Band=2 PTratio=66.395279 vLimit=129.00000 +New RegControl.Reg_contr_C Transformer=sub_regulator_C bus=bus1.3 Winding=2 vReg=123.00000 R=0.00000 X=0.00000 Band=2 PTratio=66.395279 vLimit=129.00000 + +// For the linecode name, OH denotes overhead, UG denotes underground, 1p denotes 1 phase, 3p denotes 3 phases, "type+number" denotes conductor type. +//--------------------------------------------------------------------------------------// +// three-phase overhead line using type 1 overhead conductor +//--------------------------------------------------------------------------------------// +New LineCode.OH_3p_type1 nphases= 3 Units= mi + ~ Rmatrix= (0.615927 | 0.170927 0.615927 | 0.170927 0.170927 0.615927 ) + ~ Xmatrix= (1.209389 | 0.433188 1.209389 | 0.433188 0.433188 1.209389 ) + +//--------------------------------------------------------------------------------------// +// two-phase overhead line using type 2 overhead conductor +//--------------------------------------------------------------------------------------// +New LineCode.OH_2p_type2 nphases= 2 Units= mi + ~ Rmatrix= (0.589255 | 0.169495 0.589255 ) + ~ Xmatrix= (1.074856 | 0.387876 1.074856 ) + +//--------------------------------------------------------------------------------------// +// one-phase overhead line using type 2 overhead conductor +//--------------------------------------------------------------------------------------// +New LineCode.OH_1p_type2 nphases= 1 Units= mi + ~ Rmatrix= (0.592473 ) + ~ Xmatrix= (1.065821 ) + +//--------------------------------------------------------------------------------------// +// three-phase overhead line using type 5 overhead conductor +//--------------------------------------------------------------------------------------// +New LineCode.OH_3p_type5 nphases= 3 Units= mi + ~ Rmatrix= (1.191602 | 0.234849 1.191602 | 0.234849 0.234849 1.191602 ) + ~ Xmatrix= (1.209452 | 0.489263 1.209452 | 0.489263 0.489263 1.209452 ) + +//--------------------------------------------------------------------------------------// +// one-phase overhead line using type 5 overhead conductor +//--------------------------------------------------------------------------------------// +New LineCode.OH_1p_type5 nphases= 1 Units= mi + ~ Rmatrix= (1.194260 ) + ~ Xmatrix= (1.205420 ) + +//--------------------------------------------------------------------------------------// +// three-phase underground cable using type 1 underground conductor +//--------------------------------------------------------------------------------------// +New LineCode.UG_3p_type1 nphases= 3 Units= mi + ~ Rmatrix= (1.009423 | 0.409732 1.009423 | 0.409732 0.409732 1.009423 ) + ~ Xmatrix= (0.493164 | 0.100849 0.493164 | 0.100849 0.100849 0.493164 ) + ~ Cmatrix= (286.101593 | 0.000000 286.101593 | 0.000000 0.000000 286.101593 ) + +//--------------------------------------------------------------------------------------// +// three-phase underground cable using type 2 underground conductor +//--------------------------------------------------------------------------------------// +New LineCode.UG_3p_type2 nphases= 3 Units= mi + ~ Rmatrix= (1.692577 | 0.550087 1.692577 | 0.550087 0.550087 1.692577 ) + ~ Xmatrix= (0.791756 | 0.344931 0.791756 | 0.344931 0.344931 0.791756 ) + ~ Cmatrix= (207.880539 | 0.000000 207.880539 | 0.000000 0.000000 207.880539 ) + +//--------------------------------------------------------------------------------------// +// one-phase underground cable using type 2 underground conductor +//--------------------------------------------------------------------------------------// +New LineCode.UG_1p_type2 nphases= 1 Units= mi + ~ Rmatrix= (1.675338 ) + ~ Xmatrix= (1.210007 ) + ~ Cmatrix= (207.880539 ) + +//--------------------------------------------------------------------------------------// +// line repersenting transformer windings=2 kva=112.5 +//--------------------------------------------------------------------------------------// +New LineCode.T_type1 nphases= 3 Units= Ft + ~ Rmatrix= (41.13504 | 0 41.13504 | 0 0 41.13504 ) + ~ Xmatrix= (65.51136 | 0 65.51136 | 0 0 65.51136 ) + +//--------------------------------------------------------------------------------------// +// line repersenting transformer windings=2 kva=75.0 +//--------------------------------------------------------------------------------------// +New LineCode.T_type2 nphases= 3 Units= Ft + ~ Rmatrix= (57.63984 | 0 57.63984 | 0 0 57.63984 ) + ~ Xmatrix= (48.49872 | 0 48.49872 | 0 0 48.49872 ) + + + +//--------------------------------------------------------------------------------------// +// line repersenting transformer windings=99 kva=75.0 +//--------------------------------------------------------------------------------------// +New LineCode.T_type3 nphases= 1 Units= Ft + ~ Rmatrix= (25.392 ) + ~ Xmatrix= (53.3232) + +//--------------------------------------------------------------------------------------// +// line repersenting transformer windings=99 kva=25.0 +//--------------------------------------------------------------------------------------// +New LineCode.T_type4 nphases= 1 Units= Ft + ~ Rmatrix= (101.31408 ) + ~ Xmatrix= (143.21088) + +//--------------------------------------------------------------------------------------// +// line repersenting transformer windings=2 kva=45.0 +//--------------------------------------------------------------------------------------// +New LineCode.T_type5 nphases= 3 Units= Ft + ~ Rmatrix= (106.6464 | 0 106.6464 | 0 0 106.6464 ) + ~ Xmatrix= (73.2136 | 0 73.2136 | 0 0 73.2136 ) + +//--------------------------------------------------------------------------------------// +// line repersenting transformer windings=3 kva=25.0 +//--------------------------------------------------------------------------------------// +New LineCode.T_type6 nphases= 1 Units= Ft + ~ Rmatrix= (33.773617 ) + ~ Xmatrix= (35.805113) + +//--------------------------------------------------------------------------------------// +// line repersenting transformer windings=3 kva=37.5 +//--------------------------------------------------------------------------------------// +New LineCode.T_type7 nphases= 1 Units= Ft + ~ Rmatrix= (20.145667 ) + ~ Xmatrix= (21.965548) + +//--------------------------------------------------------------------------------------// +// line repersenting transformer windings=3 kva=50.0 +//--------------------------------------------------------------------------------------// +New LineCode.T_type8 nphases= 1 Units= Ft + ~ Rmatrix= (13.966534 ) + ~ Xmatrix= (17.140746) + +//--------------------------------------------------------------------------------------// +// line repersenting transformer windings=2 kva=300.0 +//--------------------------------------------------------------------------------------// +New LineCode.T_type9 nphases= 3 Units= Ft + ~ Rmatrix= (11.4264 | 0 11.4264 | 0 0 11.4264 ) + ~ Xmatrix= (28.566 | 0 28.566 | 0 0 28.566 ) + +//--------------------------------------------------------------------------------------// +// line repersenting transformer windings=3 kva=15.0 +//--------------------------------------------------------------------------------------// +New LineCode.T_type10 nphases= 1 Units= Ft + ~ Rmatrix= (70.255896 ) + ~ Xmatrix= (40.629916) + +//--------------------------------------------------------------------------------------// +// line repersenting transformer windings=2 kva=225.0 +//--------------------------------------------------------------------------------------// +New LineCode.T_type11 nphases= 3 Units= Ft + ~ Rmatrix= (18.6208 | 0 18.6208 | 0 0 18.6208 ) + ~ Xmatrix= (39.3576 | 0 39.3576 | 0 0 39.3576 ) + +//--------------------------------------------------------------------------------------// +// line repersenting transformer windings=3 kva=100.0 +//--------------------------------------------------------------------------------------// +New LineCode.T_type12 nphases= 1 Units= Ft + ~ Rmatrix= (7.110235 ) + ~ Xmatrix= (10.665353) + +//--------------------------------------------------------------------------------------// +// line repersenting transformer windings=2 kva=500.0 +//--------------------------------------------------------------------------------------// +New LineCode.T_type13 nphases= 3 Units= Ft + ~ Rmatrix= (6.09408 | 0 6.09408 | 0 0 6.09408 ) + ~ Xmatrix= (22.47192 | 0 22.47192 | 0 0 22.47192 ) + +//***************************************************************************************// +// Feeder A +//***************************************************************************************// +New Line.L_1001_1002 phases=3 Bus1=bus1001.1.2.3 Bus2=bus1002.1.2.3 length=2967 units=Ft LineCode=UG_3p_type1 +New Line.L_1002_1003 phases=3 Bus1=bus1002.1.2.3 Bus2=bus1003.1.2.3 length=372 units=Ft LineCode=UG_3p_type2 +New Line.L_1002_1004 phases=3 Bus1=bus1002.1.2.3 Bus2=bus1004.1.2.3 length=638 units=Ft LineCode=OH_3p_type1 +New Line.L_1004_1005 phases=3 Bus1=bus1004.1.2.3 Bus2=bus1005.1.2.3 length=394 units=Ft LineCode=OH_3p_type1 +New Line.L_1005_1006 phases=3 Bus1=bus1005.1.2.3 Bus2=bus1006.1.2.3 length=1049 units=Ft LineCode=OH_3p_type1 +New Line.L_1006_1007 phases=2 Bus1=bus1006.1.2 Bus2=bus1007.1.2 length=2000 units=Ft LineCode=OH_2p_type2 +New Line.L_1006_1008 phases=3 Bus1=bus1006.1.2.3 Bus2=bus1008.1.2.3 length=454 units=Ft LineCode=OH_3p_type1 +New Line.L_1008_1009 phases=3 Bus1=bus1008.1.2.3 Bus2=bus1009.1.2.3 length=1082 units=Ft LineCode=OH_3p_type1 +New Line.L_1009_1010 phases=3 Bus1=bus1009.1.2.3 Bus2=bus1010.1.2.3 length=169 units=Ft LineCode=OH_3p_type1 +New Line.L_1009_1011 phases=3 Bus1=bus1009.1.2.3 Bus2=bus1011.1.2.3 length=60 units=Ft LineCode=UG_3p_type2 +New Line.L_1011_1012 phases=3 Bus1=bus1011.1.2.3 Bus2=bus1012.1.2.3 length=60 units=Ft LineCode=UG_3p_type2 +New Line.L_1012_1013 phases=3 Bus1=bus1012.1.2.3 Bus2=bus1013.1.2.3 length=306 units=Ft LineCode=UG_3p_type2 +New Line.L_1013_1014 phases=1 Bus1=bus1013.2.0 Bus2=bus1014.2.0 length=208 units=Ft LineCode=UG_1p_type2 +New Line.L_1014_1015 phases=1 Bus1=bus1014.2.0 Bus2=bus1015.2.0 length=126 units=Ft LineCode=UG_1p_type2 +New Line.L_1013_1016 phases=1 Bus1=bus1013.3.0 Bus2=bus1016.3.0 length=468 units=Ft LineCode=UG_1p_type2 +New Line.L_1016_1017 phases=1 Bus1=bus1016.3.0 Bus2=bus1017.3.0 length=71 units=Ft LineCode=UG_1p_type2 +New Line.L_1003_1003_L phases=3 Bus1=bus1003.1.2.3.0 Bus2=T_bus1003_L.1.2.3.0 length=1 units=Ft LineCode=T_type1 +New Line.L_1004_1004_L phases=3 Bus1=bus1004.1.2.3.0 Bus2=T_bus1004_L.1.2.3.0 length=1 units=Ft LineCode=T_type2 +New Line.L_1005_1005_L phases=3 Bus1=bus1005.1.2.3.0 Bus2=T_bus1005_L.1.2.3.0 length=1 units=Ft LineCode=T_type2 +New Line.L_1006_1006_L_1 phases=1 Bus1=bus1006.1.0 Bus2=T_bus1006_L.1.0 length=1 units=Ft LineCode=T_type3 +New Line.L_1006_1006_L_2 phases=1 Bus1=bus1006.2.0 Bus2=T_bus1006_L.2.0 length=1 units=Ft LineCode=T_type3 +New Line.L_1007_1007_L_1 phases=1 Bus1=bus1007.1.0 Bus2=T_bus1007_L.1.0 length=1 units=Ft LineCode=T_type4 +New Line.L_1007_1007_L_2 phases=1 Bus1=bus1007.2.0 Bus2=T_bus1007_L.2.0 length=1 units=Ft LineCode=T_type4 +New Line.L_1008_1008_L phases=3 Bus1=bus1008.1.2.3.0 Bus2=T_bus1008_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_1009_1009_L phases=3 Bus1=bus1009.1.2.3.0 Bus2=T_bus1009_L.1.2.3.0 length=1 units=Ft LineCode=T_type2 +New Line.L_1010_1010_L phases=3 Bus1=bus1010.1.2.3.0 Bus2=T_bus1010_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_1011_1011_L phases=1 Bus1=bus1011.1.0 Bus2=T_bus1011_L.1.0 length=1 units=Ft LineCode=T_type6 +New Line.L_1012_1012_L phases=1 Bus1=bus1012.2.0 Bus2=T_bus1012_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_1013_1013_L phases=3 Bus1=bus1013.1.2.3.0 Bus2=T_bus1013_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_1014_1014_L phases=1 Bus1=bus1014.2.0 Bus2=T_bus1014_L.2.0 length=1 units=Ft LineCode=T_type6 +New Line.L_1015_1015_L phases=1 Bus1=bus1015.2.0 Bus2=T_bus1015_L.2.0 length=1 units=Ft LineCode=T_type6 +New Line.L_1016_1016_L phases=1 Bus1=bus1016.3.0 Bus2=T_bus1016_L.3.0 length=1 units=Ft LineCode=T_type8 +New Line.L_1017_1017_L phases=1 Bus1=bus1017.3.0 Bus2=T_bus1017_L.3.0 length=1 units=Ft LineCode=T_type7 + +//***************************************************************************************// +// Feeder B +//***************************************************************************************// +New Line.L_2001_2002 phases=3 Bus1=bus2001.1.2.3 Bus2=bus2002.1.2.3 length=1388 units=Ft LineCode=UG_3p_type1 +New Line.L_2002_2003 phases=3 Bus1=bus2002.1.2.3 Bus2=bus2003.1.2.3 length=1281 units=Ft LineCode=OH_3p_type1 +New Line.L_2003_2004 phases=3 Bus1=bus2003.1.2.3 Bus2=bus2004.1.2.3 length=798 units=Ft LineCode=OH_3p_type1 +New Line.L_2004_2005 phases=3 Bus1=bus2004.1.2.3 Bus2=bus2005.1.2.3 length=317 units=Ft LineCode=OH_3p_type1 +New Line.L_2005_2006 phases=3 Bus1=bus2005.1.2.3 Bus2=bus2006.1.2.3 length=145 units=Ft LineCode=OH_3p_type1 +New Line.L_2006_2007 phases=1 Bus1=bus2006.1 Bus2=bus2007.1 length=245 units=Ft LineCode=OH_1p_type5 +New Line.L_2007_2008 phases=1 Bus1=bus2007.1 Bus2=bus2008.1 length=125 units=Ft LineCode=UG_1p_type2 +New Line.L_2007_2009 phases=1 Bus1=bus2007.1 Bus2=bus2009.1 length=161 units=Ft LineCode=OH_1p_type2 +New Line.L_2006_2010 phases=3 Bus1=bus2006.1.2.3 Bus2=bus2010.1.2.3 length=170 units=Ft LineCode=OH_3p_type1 +New Line.L_2010_2011 phases=3 Bus1=bus2010.1.2.3 Bus2=bus2011.1.2.3 length=275 units=Ft LineCode=OH_3p_type1 +New Line.L_2011_2012 phases=3 Bus1=bus2011.1.2.3 Bus2=bus2012.1.2.3 length=503 units=Ft LineCode=OH_3p_type1 +New Line.L_2013_2014 phases=3 Bus1=bus2013.1.2.3 Bus2=bus2014.1.2.3 length=268 units=Ft LineCode=OH_3p_type1 +New Line.L_2014_2015 phases=1 Bus1=bus2014.2 Bus2=bus2015.2 length=120 units=Ft LineCode=UG_1p_type2 +New Line.L_2014_2016 phases=3 Bus1=bus2014.1.2.3 Bus2=bus2016.1.2.3 length=257 units=Ft LineCode=OH_3p_type1 +New Line.L_2016_2017 phases=3 Bus1=bus2016.1.2.3 Bus2=bus2017.1.2.3 length=174 units=Ft LineCode=UG_3p_type2 +New Line.L_2017_2018 phases=3 Bus1=bus2017.1.2.3 Bus2=bus2018.1.2.3 length=447 units=Ft LineCode=UG_3p_type2 +New Line.L_2013_2019 phases=3 Bus1=bus2013.1.2.3 Bus2=bus2019.1.2.3 length=45 units=Ft LineCode=OH_3p_type1 +New Line.L_2019_2020 phases=1 Bus1=bus2019.2 Bus2=bus2020.2 length=642 units=Ft LineCode=UG_1p_type2 +New Line.L_2019_2021 phases=3 Bus1=bus2019.1.2.3 Bus2=bus2021.1.2.3 length=43 units=Ft LineCode=OH_3p_type1 +New Line.L_2021_2022 phases=1 Bus1=bus2021.3 Bus2=bus2022.3 length=205 units=Ft LineCode=UG_1p_type2 +New Line.L_2022_2023 phases=1 Bus1=bus2022.3 Bus2=bus2023.3 length=150 units=Ft LineCode=UG_1p_type2 +New Line.L_2023_2024 phases=1 Bus1=bus2023.3 Bus2=bus2024.3 length=408 units=Ft LineCode=UG_1p_type2 +New Line.L_2024_2025 phases=1 Bus1=bus2024.3 Bus2=bus2025.3 length=331 units=Ft LineCode=UG_1p_type2 +New Line.L_2026_2027 phases=3 Bus1=bus2026.1.2.3 Bus2=bus2027.1.2.3 length=369 units=Ft LineCode=OH_3p_type1 +New Line.L_2027_2028 phases=3 Bus1=bus2027.1.2.3 Bus2=bus2028.1.2.3 length=285 units=Ft LineCode=UG_3p_type2 +New Line.L_2028_2029 phases=3 Bus1=bus2028.1.2.3 Bus2=bus2029.1.2.3 length=131 units=Ft LineCode=UG_3p_type2 +New Line.L_2029_2030 phases=3 Bus1=bus2029.1.2.3 Bus2=bus2030.1.2.3 length=165 units=Ft LineCode=UG_3p_type2 +New Line.L_2030_2031 phases=3 Bus1=bus2030.1.2.3 Bus2=bus2031.1.2.3 length=245 units=Ft LineCode=UG_3p_type2 +New Line.L_2027_2032 phases=3 Bus1=bus2027.1.2.3 Bus2=bus2032.1.2.3 length=149 units=Ft LineCode=OH_3p_type1 +New Line.L_2032_2033 phases=3 Bus1=bus2032.1.2.3 Bus2=bus2033.1.2.3 length=289 units=Ft LineCode=OH_3p_type1 +New Line.L_2033_2034 phases=3 Bus1=bus2033.1.2.3 Bus2=bus2034.1.2.3 length=185 units=Ft LineCode=OH_3p_type5 +New Line.L_2033_2035 phases=3 Bus1=bus2033.1.2.3 Bus2=bus2035.1.2.3 length=160 units=Ft LineCode=OH_3p_type1 +New Line.L_2035_2036 phases=3 Bus1=bus2035.1.2.3 Bus2=bus2036.1.2.3 length=160 units=Ft LineCode=OH_3p_type1 +New Line.L_2036_2037 phases=3 Bus1=bus2036.1.2.3 Bus2=bus2037.1.2.3 length=80 units=Ft LineCode=OH_3p_type1 +New Line.L_2037_2038 phases=3 Bus1=bus2037.1.2.3 Bus2=bus2038.1.2.3 length=192 units=Ft LineCode=OH_3p_type1 +New Line.L_2038_2039 phases=3 Bus1=bus2038.1.2.3 Bus2=bus2039.1.2.3 length=800 units=Ft LineCode=OH_3p_type1 +New Line.L_2039_2040 phases=3 Bus1=bus2039.1.2.3 Bus2=bus2040.1.2.3 length=309 units=Ft LineCode=OH_3p_type1 +New Line.L_2040_2041 phases=3 Bus1=bus2040.1.2.3 Bus2=bus2041.1.2.3 length=383 units=Ft LineCode=OH_3p_type1 +New Line.L_2039_2042 phases=3 Bus1=bus2039.1.2.3 Bus2=bus2042.1.2.3 length=35 units=Ft LineCode=UG_3p_type2 +New Line.L_2042_2043 phases=3 Bus1=bus2042.1.2.3 Bus2=bus2043.1.2.3 length=158 units=Ft LineCode=UG_3p_type2 +New Line.L_2043_2044 phases=3 Bus1=bus2043.1.2.3 Bus2=bus2044.1.2.3 length=342 units=Ft LineCode=UG_3p_type2 +New Line.L_2044_2045 phases=1 Bus1=bus2044.2 Bus2=bus2045.2 length=323 units=Ft LineCode=UG_1p_type2 +New Line.L_2045_2046 phases=1 Bus1=bus2045.2 Bus2=bus2046.2 length=330 units=Ft LineCode=UG_1p_type2 +New Line.L_2045_2047 phases=1 Bus1=bus2045.2 Bus2=bus2047.2 length=189 units=Ft LineCode=UG_1p_type2 +New Line.L_2047_2048 phases=1 Bus1=bus2047.2 Bus2=bus2048.2 length=259 units=Ft LineCode=UG_1p_type2 +New Line.L_2044_2049 phases=3 Bus1=bus2044.1.2.3 Bus2=bus2049.1.2.3 length=130 units=Ft LineCode=UG_3p_type2 +New Line.L_2049_2050 phases=3 Bus1=bus2049.1.2.3 Bus2=bus2050.1.2.3 length=124 units=Ft LineCode=UG_3p_type2 +New Line.L_2050_2051 phases=3 Bus1=bus2050.1.2.3 Bus2=bus2051.1.2.3 length=139 units=Ft LineCode=UG_3p_type2 +New Line.L_2051_2052 phases=3 Bus1=bus2051.1.2.3 Bus2=bus2052.1.2.3 length=134 units=Ft LineCode=UG_3p_type2 +New Line.L_2044_2053 phases=3 Bus1=bus2044.1.2.3 Bus2=bus2053.1.2.3 length=464 units=Ft LineCode=UG_3p_type2 +New Line.L_2053_2054 phases=3 Bus1=bus2053.1.2.3 Bus2=bus2054.1.2.3 length=139 units=Ft LineCode=UG_3p_type2 +New Line.L_2054_2055 phases=1 Bus1=bus2054.1.2.3 Bus2=bus2055.1.2.3 length=156 units=Ft LineCode=UG_3p_type2 +New Line.L_2055_2056 phases=1 Bus1=bus2055.1.2.3 Bus2=bus2056.1.2.3 length=204 units=Ft LineCode=UG_3p_type2 +New Line.L_2053_2057 phases=3 Bus1=bus2053.1.2.3 Bus2=bus2057.1.2.3 length=761 units=Ft LineCode=UG_3p_type2 +New Line.L_2057_2058 phases=3 Bus1=bus2057.1.2.3 Bus2=bus2058.1.2.3 length=407 units=Ft LineCode=UG_3p_type2 +New Line.L_2057_2059 phases=3 Bus1=bus2057.1.2.3 Bus2=bus2059.1.2.3 length=685 units=Ft LineCode=UG_3p_type2 +New Line.L_2059_2060 phases=3 Bus1=bus2059.1.2.3 Bus2=bus2060.1.2.3 length=304 units=Ft LineCode=UG_3p_type2 +New Line.L_2002_2002_L phases=3 Bus1=bus2002.1.2.3 Bus2=T_bus2002_L.1.2.3 length=1 units=Ft LineCode=T_type9 +New Line.L_2003_2003_L phases=3 Bus1=bus2003.1.2.3.0 Bus2=T_bus2003_L.1.2.3.0 length=1 units=Ft LineCode=T_type2 +New Line.L_2005_2005_L phases=3 Bus1=bus2005.1.2.3.0 Bus2=T_bus2005_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_2008_2008_L phases=1 Bus1=bus2008.1.0 Bus2=T_bus2008_L.1.0 length=1 units=Ft LineCode=T_type6 +New Line.L_2009_2009_L phases=1 Bus1=bus2009.1.0 Bus2=T_bus2009_L.1.0 length=1 units=Ft LineCode=T_type6 +New Line.L_2010_2010_L phases=3 Bus1=bus2010.1.2.3.0 Bus2=T_bus2010_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_2011_2011_L phases=3 Bus1=bus2011.1.2.3.0 Bus2=T_bus2011_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_2014_2014_L phases=1 Bus1=bus2014.2.0 Bus2=T_bus2014_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_2015_2015_L phases=1 Bus1=bus2015.2.0 Bus2=T_bus2015_L.2.0 length=1 units=Ft LineCode=T_type6 +New Line.L_2016_2016_L phases=1 Bus1=bus2016.2.0 Bus2=T_bus2016_L.2.0 length=1 units=Ft LineCode=T_type6 +New Line.L_2017_2017_L phases=1 Bus1=bus2017.2.0 Bus2=T_bus2017_L.2.0 length=1 units=Ft LineCode=T_type6 +New Line.L_2018_2018_L phases=1 Bus1=bus2018.2.0 Bus2=T_bus2018_L.2.0 length=1 units=Ft LineCode=T_type6 +New Line.L_2020_2020_L phases=1 Bus1=bus2020.2.0 Bus2=T_bus2020_L.2.0 length=1 units=Ft LineCode=T_type6 +New Line.L_2022_2022_L phases=1 Bus1=bus2022.3.0 Bus2=T_bus2022_L.3.0 length=1 units=Ft LineCode=T_type6 +New Line.L_2023_2023_L phases=1 Bus1=bus2023.3.0 Bus2=T_bus2023_L.3.0 length=1 units=Ft LineCode=T_type10 +New Line.L_2024_2024_L phases=1 Bus1=bus2024.3.0 Bus2=T_bus2024_L.3.0 length=1 units=Ft LineCode=T_type6 +New Line.L_2025_2025_L phases=1 Bus1=bus2025.3.0 Bus2=T_bus2025_L.3.0 length=1 units=Ft LineCode=T_type6 +New Line.L_2028_2028_L phases=1 Bus1=bus2028.1.0 Bus2=T_bus2028_L.1.0 length=1 units=Ft LineCode=T_type6 +New Line.L_2029_2029_L phases=3 Bus1=bus2029.1.2.3.0 Bus2=T_bus2029_L.1.2.3.0 length=1 units=Ft LineCode=T_type2 +New Line.L_2030_2030_L phases=3 Bus1=bus2030.1.2.3.0 Bus2=T_bus2030_L.1.2.3.0 length=1 units=Ft LineCode=T_type2 +New Line.L_2031_2031_L phases=1 Bus1=bus2031.1.0 Bus2=T_bus2031_L.1.0 length=1 units=Ft LineCode=T_type6 +New Line.L_2032_2032_L phases=3 Bus1=bus2032.1.2.3.0 Bus2=T_bus2032_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_2034_2034_L phases=3 Bus1=bus2034.1.2.3.0 Bus2=T_bus2034_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_2035_2035_L phases=3 Bus1=bus2035.1.2.3.0 Bus2=T_bus2035_L.1.2.3.0 length=1 units=Ft LineCode=T_type2 +New Line.L_2037_2037_L phases=3 Bus1=bus2037.1.2.3.0 Bus2=T_bus2037_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_2040_2040_L phases=3 Bus1=bus2040.1.2.3.0 Bus2=T_bus2040_L.1.2.3.0 length=1 units=Ft LineCode=T_type9 +New Line.L_2041_2041_L phases=3 Bus1=bus2041.1.2.3.0 Bus2=T_bus2041_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_2042_2042_L phases=3 Bus1=bus2042.1.2.3.0 Bus2=T_bus2042_L.1.2.3.0 length=1 units=Ft LineCode=T_type2 +New Line.L_2043_2043_L phases=3 Bus1=bus2043.1.2.3.0 Bus2=T_bus2043_L.1.2.3.0 length=1 units=Ft LineCode=T_type2 +New Line.L_2045_2045_L phases=1 Bus1=bus2045.2.0 Bus2=T_bus2045_L.2.0 length=1 units=Ft LineCode=T_type10 +New Line.L_2046_2046_L phases=1 Bus1=bus2046.2.0 Bus2=T_bus2046_L.2.0 length=1 units=Ft LineCode=T_type8 +New Line.L_2047_2047_L phases=1 Bus1=bus2047.2.0 Bus2=T_bus2047_L.2.0 length=1 units=Ft LineCode=T_type8 +New Line.L_2048_2048_L phases=1 Bus1=bus2048.2.0 Bus2=T_bus2048_L.2.0 length=1 units=Ft LineCode=T_type8 +New Line.L_2049_2049_L phases=1 Bus1=bus2049.3.0 Bus2=T_bus2049_L.3.0 length=1 units=Ft LineCode=T_type7 +New Line.L_2050_2050_L phases=1 Bus1=bus2050.3.0 Bus2=T_bus2050_L.3.0 length=1 units=Ft LineCode=T_type7 +New Line.L_2051_2051_L phases=1 Bus1=bus2051.3.0 Bus2=T_bus2051_L.3.0 length=1 units=Ft LineCode=T_type7 +New Line.L_2052_2052_L phases=3 Bus1=bus2052.1.2.3.0 Bus2=T_bus2052_L.1.2.3.0 length=1 units=Ft LineCode=T_type11 +New Line.L_2053_2053_L phases=3 Bus1=bus2053.1.2.3.0 Bus2=T_bus2053_L.1.2.3.0 length=1 units=Ft LineCode=T_type11 +New Line.L_2054_2054_L phases=1 Bus1=bus2054.1.0 Bus2=T_bus2054_L.1.0 length=1 units=Ft LineCode=T_type7 +New Line.L_2055_2055_L phases=1 Bus1=bus2055.1.0 Bus2=T_bus2055_L.1.0 length=1 units=Ft LineCode=T_type8 +New Line.L_2056_2056_L phases=1 Bus1=bus2056.1.0 Bus2=T_bus2056_L.1.0 length=1 units=Ft LineCode=T_type6 +New Line.L_2058_2058_L phases=3 Bus1=bus2058.1.2.3.0 Bus2=T_bus2058_L.1.2.3.0 length=1 units=Ft LineCode=T_type9 +New Line.L_2059_2059_L phases=1 Bus1=bus2059.2.0 Bus2=T_bus2059_L.2.0 length=1 units=Ft LineCode=T_type6 +New Line.L_2060_2060_L phases=1 Bus1=bus2060.2.0 Bus2=T_bus2060_L.2.0 length=1 units=Ft LineCode=T_type6 + +//***************************************************************************************// +// Feeder C +//***************************************************************************************// +New Line.L_3001_3003 phases=3 Bus1=bus3001.1.2.3 Bus2=bus3003.1.2.3 length=2307 units=Ft LineCode=UG_3p_type1 +New Line.L_3003_3002 phases=3 Bus1=bus3003.1.2.3 Bus2=bus3002.1.2.3 length=85 units=Ft LineCode=UG_3p_type2 +New Line.L_3003_3004 phases=3 Bus1=bus3003.1.2.3 Bus2=bus3004.1.2.3 length=81 units=Ft LineCode=UG_3p_type2 +New Line.L_3003_3005 phases=3 Bus1=bus3003.1.2.3 Bus2=bus3005.1.2.3 length=1997 units=Ft LineCode=UG_3p_type1 +New Line.L_3005_3006 phases=3 Bus1=bus3005.1.2.3 Bus2=bus3006.1.2.3 length=311 units=Ft LineCode=OH_3p_type1 +New Line.L_3006_3007 phases=3 Bus1=bus3006.1.2.3 Bus2=bus3007.1.2.3 length=370 units=Ft LineCode=OH_3p_type1 +New Line.L_3005_3008 phases=3 Bus1=bus3005.1.2.3 Bus2=bus3008.1.2.3 length=136 units=Ft LineCode=OH_3p_type1 +New Line.L_3008_3009 phases=1 Bus1=bus3008.1 Bus2=bus3009.1 length=161 units=Ft LineCode=OH_1p_type5 +New Line.L_3009_3010 phases=1 Bus1=bus3009.1 Bus2=bus3010.1 length=347 units=Ft LineCode=OH_1p_type5 +New Line.L_3010_3011 phases=1 Bus1=bus3010.1 Bus2=bus3011.1 length=265 units=Ft LineCode=OH_1p_type5 +New Line.L_3011_3012 phases=1 Bus1=bus3011.1 Bus2=bus3012.1 length=312 units=Ft LineCode=OH_1p_type5 +New Line.L_3008_3013 phases=1 Bus1=bus3008.2 Bus2=bus3013.2 length=170 units=Ft LineCode=OH_1p_type5 +New Line.L_3013_3014 phases=1 Bus1=bus3013.2 Bus2=bus3014.2 length=295 units=Ft LineCode=OH_1p_type5 +New Line.L_3008_3015 phases=3 Bus1=bus3008.1.2.3 Bus2=bus3015.1.2.3 length=322 units=Ft LineCode=OH_3p_type1 +New Line.L_3015_3016 phases=1 Bus1=bus3015.2 Bus2=bus3016.2 length=210 units=Ft LineCode=OH_1p_type5 +New Line.L_3016_3017 phases=1 Bus1=bus3016.2 Bus2=bus3017.2 length=259 units=Ft LineCode=OH_1p_type5 +New Line.L_3015_3018 phases=1 Bus1=bus3015.3 Bus2=bus3018.3 length=181 units=Ft LineCode=OH_1p_type5 +New Line.L_3018_3019 phases=1 Bus1=bus3018.3 Bus2=bus3019.3 length=281 units=Ft LineCode=OH_1p_type5 +New Line.L_3019_3020 phases=1 Bus1=bus3019.3 Bus2=bus3020.3 length=307 units=Ft LineCode=OH_1p_type5 +New Line.L_3020_3021 phases=1 Bus1=bus3020.3 Bus2=bus3021.3 length=303 units=Ft LineCode=OH_1p_type5 +New Line.L_3015_3022 phases=3 Bus1=bus3015.1.2.3 Bus2=bus3022.1.2.3 length=289 units=Ft LineCode=OH_3p_type1 +New Line.L_3022_3023 phases=1 Bus1=bus3022.1 Bus2=bus3023.1 length=181 units=Ft LineCode=OH_1p_type5 +New Line.L_3023_3024 phases=1 Bus1=bus3023.1 Bus2=bus3024.1 length=320 units=Ft LineCode=OH_1p_type5 +New Line.L_3024_3025 phases=1 Bus1=bus3024.1 Bus2=bus3025.1 length=331 units=Ft LineCode=OH_1p_type5 +New Line.L_3022_3026 phases=1 Bus1=bus3022.1 Bus2=bus3026.1 length=164 units=Ft LineCode=OH_1p_type5 +New Line.L_3026_3027 phases=1 Bus1=bus3026.1 Bus2=bus3027.1 length=368 units=Ft LineCode=OH_1p_type5 +New Line.L_3027_3028 phases=1 Bus1=bus3027.1 Bus2=bus3028.1 length=241 units=Ft LineCode=OH_1p_type5 +New Line.L_3028_3029 phases=1 Bus1=bus3028.1 Bus2=bus3029.1 length=317 units=Ft LineCode=OH_1p_type5 +New Line.L_3022_3030 phases=3 Bus1=bus3022.1.2.3 Bus2=bus3030.1.2.3 length=304 units=Ft LineCode=OH_3p_type1 +New Line.L_3030_3035 phases=3 Bus1=bus3030.1.2.3 Bus2=bus3035.1.2.3 length=103 units=Ft LineCode=OH_3p_type1 +New Line.L_3035_3036 phases=3 Bus1=bus3035.1.2.3 Bus2=bus3036.1.2.3 length=271 units=Ft LineCode=OH_3p_type1 +New Line.L_3036_3037 phases=3 Bus1=bus3036.1.2.3 Bus2=bus3037.1.2.3 length=136 units=Ft LineCode=OH_3p_type1 +New Line.L_3037_3038 phases=3 Bus1=bus3037.1.2.3 Bus2=bus3038.1.2.3 length=252 units=Ft LineCode=OH_3p_type1 +New Line.L_3038_3039 phases=3 Bus1=bus3038.1.2.3 Bus2=bus3039.1.2.3 length=235 units=Ft LineCode=OH_3p_type1 +New Line.L_3039_3053 phases=3 Bus1=bus3039.1.2.3 Bus2=bus3053.1.2.3 length=263 units=Ft LineCode=OH_3p_type1 +New Line.L_3053_3054 phases=3 Bus1=bus3053.1.2.3 Bus2=bus3054.1.2.3 length=280 units=Ft LineCode=UG_3p_type2 +New Line.L_3053_3055 phases=1 Bus1=bus3053.2 Bus2=bus3055.2 length=1335 units=Ft LineCode=UG_1p_type2 +New Line.L_3055_3056 phases=1 Bus1=bus3055.2 Bus2=bus3056.2 length=128 units=Ft LineCode=UG_1p_type2 +New Line.L_3056_3057 phases=1 Bus1=bus3056.2 Bus2=bus3057.2 length=178 units=Ft LineCode=UG_1p_type2 +New Line.L_3057_3058 phases=1 Bus1=bus3057.2 Bus2=bus3058.2 length=178 units=Ft LineCode=UG_1p_type2 +New Line.L_3058_3059 phases=1 Bus1=bus3058.2 Bus2=bus3059.2 length=274 units=Ft LineCode=UG_1p_type2 +New Line.L_3059_3060 phases=1 Bus1=bus3059.2 Bus2=bus3060.2 length=77 units=Ft LineCode=UG_1p_type2 +New Line.L_3060_3061 phases=1 Bus1=bus3060.2 Bus2=bus3061.2 length=184 units=Ft LineCode=UG_1p_type2 +New Line.L_3055_3062 phases=1 Bus1=bus3055.2 Bus2=bus3062.2 length=443 units=Ft LineCode=UG_1p_type2 +New Line.L_3062_3063 phases=1 Bus1=bus3062.2 Bus2=bus3063.2 length=198 units=Ft LineCode=UG_1p_type2 +New Line.L_3063_3064 phases=1 Bus1=bus3063.2 Bus2=bus3064.2 length=157 units=Ft LineCode=UG_1p_type2 +New Line.L_3064_3065 phases=1 Bus1=bus3064.2 Bus2=bus3065.2 length=205 units=Ft LineCode=UG_1p_type2 +New Line.L_3065_3066 phases=1 Bus1=bus3065.2 Bus2=bus3066.2 length=123 units=Ft LineCode=UG_1p_type2 +New Line.L_3066_3067 phases=1 Bus1=bus3066.2 Bus2=bus3067.2 length=274 units=Ft LineCode=UG_1p_type2 +New Line.L_3030_3040 phases=3 Bus1=bus3030.1.2.3 Bus2=bus3040.1.2.3 length=332 units=Ft LineCode=OH_3p_type1 +New Line.L_3040_3044 phases=1 Bus1=bus3040.3 Bus2=bus3044.3 length=144 units=Ft LineCode=OH_1p_type5 +New Line.L_3044_3045 phases=1 Bus1=bus3044.3 Bus2=bus3045.3 length=393 units=Ft LineCode=OH_1p_type5 +New Line.L_3040_3041 phases=1 Bus1=bus3040.3 Bus2=bus3041.3 length=121 units=Ft LineCode=OH_1p_type5 +New Line.L_3041_3042 phases=1 Bus1=bus3041.3 Bus2=bus3042.3 length=328 units=Ft LineCode=OH_1p_type5 +New Line.L_3042_3043 phases=1 Bus1=bus3042.3 Bus2=bus3043.3 length=418 units=Ft LineCode=OH_1p_type5 +New Line.L_3040_3046 phases=3 Bus1=bus3040.1.2.3 Bus2=bus3046.1.2.3 length=329 units=Ft LineCode=OH_3p_type1 +New Line.L_3046_3047 phases=3 Bus1=bus3046.1.2.3 Bus2=bus3047.1.2.3 length=219 units=Ft LineCode=OH_3p_type1 +New Line.L_3046_3048 phases=3 Bus1=bus3046.1.2.3 Bus2=bus3048.1.2.3 length=130 units=Ft LineCode=OH_3p_type1 +New Line.L_3048_3049 phases=3 Bus1=bus3048.1.2.3 Bus2=bus3049.1.2.3 length=226 units=Ft LineCode=OH_3p_type1 +New Line.L_3049_3050 phases=3 Bus1=bus3049.1.2.3 Bus2=bus3050.1.2.3 length=167 units=Ft LineCode=OH_3p_type1 +New Line.L_3050_3051 phases=3 Bus1=bus3050.1.2.3 Bus2=bus3051.1.2.3 length=360 units=Ft LineCode=OH_3p_type1 +New Line.L_3051_3052 phases=3 Bus1=bus3051.1.2.3 Bus2=bus3052.1.2.3 length=292 units=Ft LineCode=OH_3p_type1 +New Line.L_3030_3031 phases=3 Bus1=bus3030.1.2.3 Bus2=bus3031.1.2.3 length=243 units=Ft LineCode=OH_3p_type1 +New Line.L_3031_3032 phases=3 Bus1=bus3031.1.2.3 Bus2=bus3032.1.2.3 length=251 units=Ft LineCode=OH_3p_type1 +New Line.L_3032_3033 phases=3 Bus1=bus3032.1.2.3 Bus2=bus3033.1.2.3 length=388 units=Ft LineCode=OH_3p_type1 +New Line.L_3033_3034 phases=3 Bus1=bus3033.1.2.3 Bus2=bus3034.1.2.3 length=257 units=Ft LineCode=OH_3p_type1 +New Line.L_3034_3068 phases=3 Bus1=bus3034.1.2.3 Bus2=bus3068.1.2.3 length=164 units=Ft LineCode=OH_3p_type1 +New Line.L_3068_3069 phases=3 Bus1=bus3068.1.2.3 Bus2=bus3069.1.2.3 length=329 units=Ft LineCode=OH_3p_type1 +New Line.L_3069_3070 phases=3 Bus1=bus3069.1.2.3 Bus2=bus3070.1.2.3 length=107 units=Ft LineCode=UG_3p_type2 +New Line.L_3070_3071 phases=3 Bus1=bus3070.1.2.3 Bus2=bus3071.1.2.3 length=141 units=Ft LineCode=UG_3p_type2 +New Line.L_3071_3072 phases=3 Bus1=bus3071.1.2.3 Bus2=bus3072.1.2.3 length=90 units=Ft LineCode=UG_3p_type2 +New Line.L_3069_3073 phases=3 Bus1=bus3069.1.2.3 Bus2=bus3073.1.2.3 length=20 units=Ft LineCode=OH_3p_type1 +New Line.L_3073_3074 phases=3 Bus1=bus3073.1.2.3 Bus2=bus3074.1.2.3 length=340 units=Ft LineCode=OH_3p_type1 +New Line.L_3068_3075 phases=3 Bus1=bus3068.1.2.3 Bus2=bus3075.1.2.3 length=188 units=Ft LineCode=OH_3p_type1 +New Line.L_3076_3077 phases=3 Bus1=bus3076.1.2.3 Bus2=bus3077.1.2.3 length=877 units=Ft LineCode=OH_3p_type1 +New Line.L_3077_3078 phases=3 Bus1=bus3077.1.2.3 Bus2=bus3078.1.2.3 length=80 units=Ft LineCode=OH_3p_type1 +New Line.L_3076_3079 phases=3 Bus1=bus3076.1.2.3 Bus2=bus3079.1.2.3 length=191 units=Ft LineCode=OH_3p_type1 +New Line.L_3079_3080 phases=3 Bus1=bus3079.1.2.3 Bus2=bus3080.1.2.3 length=604 units=Ft LineCode=OH_3p_type1 +New Line.L_3080_3081 phases=3 Bus1=bus3080.1.2.3 Bus2=bus3081.1.2.3 length=195 units=Ft LineCode=OH_3p_type1 +New Line.L_3080_3082 phases=3 Bus1=bus3080.1.2.3 Bus2=bus3082.1.2.3 length=540 units=Ft LineCode=OH_3p_type1 +New Line.L_3082_3083 phases=1 Bus1=bus3082.1 Bus2=bus3083.1 length=125 units=Ft LineCode=UG_1p_type2 +New Line.L_3083_3084 phases=1 Bus1=bus3083.1 Bus2=bus3084.1 length=196 units=Ft LineCode=UG_1p_type2 +New Line.L_3084_3085 phases=1 Bus1=bus3084.1 Bus2=bus3085.1 length=515 units=Ft LineCode=UG_1p_type2 +New Line.L_3085_3086 phases=1 Bus1=bus3085.1 Bus2=bus3086.1 length=338 units=Ft LineCode=UG_1p_type2 +New Line.L_3086_3087 phases=1 Bus1=bus3086.1 Bus2=bus3087.1 length=239 units=Ft LineCode=UG_1p_type2 +New Line.L_3087_3088 phases=1 Bus1=bus3087.1 Bus2=bus3088.1 length=420 units=Ft LineCode=UG_1p_type2 +New Line.L_3088_3089 phases=1 Bus1=bus3088.1 Bus2=bus3089.1 length=112 units=Ft LineCode=UG_1p_type2 +New Line.L_3089_3090 phases=1 Bus1=bus3089.1 Bus2=bus3090.1 length=565 units=Ft LineCode=UG_1p_type2 +New Line.L_3090_3091 phases=1 Bus1=bus3090.1 Bus2=bus3091.1 length=222 units=Ft LineCode=UG_1p_type2 +New Line.L_3082_3092 phases=3 Bus1=bus3082.1.2.3 Bus2=bus3092.1.2.3 length=185 units=Ft LineCode=UG_3p_type2 +New Line.L_3092_3093 phases=1 Bus1=bus3092.1 Bus2=bus3093.1 length=230 units=Ft LineCode=UG_1p_type2 +New Line.L_3093_3094 phases=1 Bus1=bus3093.1 Bus2=bus3094.1 length=125 units=Ft LineCode=UG_1p_type2 +New Line.L_3094_3095 phases=1 Bus1=bus3094.1 Bus2=bus3095.1 length=65 units=Ft LineCode=UG_1p_type2 +New Line.L_3095_3096 phases=1 Bus1=bus3095.1 Bus2=bus3096.1 length=296 units=Ft LineCode=UG_1p_type2 +New Line.L_3096_3097 phases=1 Bus1=bus3096.1 Bus2=bus3097.1 length=117 units=Ft LineCode=UG_1p_type2 +New Line.L_3092_3098 phases=1 Bus1=bus3092.2 Bus2=bus3098.2 length=284 units=Ft LineCode=UG_1p_type2 +New Line.L_3098_3099 phases=1 Bus1=bus3098.2 Bus2=bus3099.2 length=368 units=Ft LineCode=UG_1p_type2 +New Line.L_3092_3100 phases=3 Bus1=bus3092.1.2.3 Bus2=bus3100.1.2.3 length=91 units=Ft LineCode=UG_3p_type2 +New Line.L_3100_3101 phases=1 Bus1=bus3100.3 Bus2=bus3101.3 length=157 units=Ft LineCode=UG_1p_type2 +New Line.L_3101_3102 phases=1 Bus1=bus3101.3 Bus2=bus3102.3 length=225 units=Ft LineCode=UG_1p_type2 +New Line.L_3102_3103 phases=1 Bus1=bus3102.3 Bus2=bus3103.3 length=134 units=Ft LineCode=UG_1p_type2 +New Line.L_3100_3104 phases=1 Bus1=bus3100.3 Bus2=bus3104.3 length=223 units=Ft LineCode=UG_1p_type2 +New Line.L_3104_3105 phases=1 Bus1=bus3104.3 Bus2=bus3105.3 length=120 units=Ft LineCode=UG_1p_type2 +New Line.L_3105_3106 phases=1 Bus1=bus3105.3 Bus2=bus3106.3 length=181 units=Ft LineCode=UG_1p_type2 +New Line.L_3082_3107 phases=3 Bus1=bus3082.1.2.3 Bus2=bus3107.1.2.3 length=1461 units=Ft LineCode=UG_3p_type1 +New Line.L_3107_3108 phases=1 Bus1=bus3107.1 Bus2=bus3108.1 length=466 units=Ft LineCode=UG_1p_type2 +New Line.L_3108_3109 phases=1 Bus1=bus3108.1 Bus2=bus3109.1 length=136 units=Ft LineCode=UG_1p_type2 +New Line.L_3109_3110 phases=1 Bus1=bus3109.1 Bus2=bus3110.1 length=152 units=Ft LineCode=UG_1p_type2 +New Line.L_3110_3111 phases=1 Bus1=bus3110.1 Bus2=bus3111.1 length=276 units=Ft LineCode=UG_1p_type2 +New Line.L_3111_3112 phases=1 Bus1=bus3111.1 Bus2=bus3112.1 length=328 units=Ft LineCode=UG_1p_type2 +New Line.L_3107_3113 phases=1 Bus1=bus3107.2 Bus2=bus3113.2 length=245 units=Ft LineCode=UG_1p_type2 +New Line.L_3113_3114 phases=1 Bus1=bus3113.2 Bus2=bus3114.2 length=149 units=Ft LineCode=UG_1p_type2 +New Line.L_3114_3115 phases=1 Bus1=bus3114.2 Bus2=bus3115.2 length=331 units=Ft LineCode=UG_1p_type2 +New Line.L_3113_3116 phases=1 Bus1=bus3113.2 Bus2=bus3116.2 length=456 units=Ft LineCode=UG_1p_type2 +New Line.L_3116_3117 phases=1 Bus1=bus3116.2 Bus2=bus3117.2 length=112 units=Ft LineCode=UG_1p_type2 +New Line.L_3107_3118 phases=3 Bus1=bus3107.1.2.3 Bus2=bus3118.1.2.3 length=2267 units=Ft LineCode=UG_3p_type1 +New Line.L_3118_3119 phases=1 Bus1=bus3118.2 Bus2=bus3119.2 length=464 units=Ft LineCode=UG_1p_type2 +New Line.L_3119_3120 phases=1 Bus1=bus3119.2 Bus2=bus3120.2 length=350 units=Ft LineCode=UG_1p_type2 +New Line.L_3120_3121 phases=1 Bus1=bus3120.2 Bus2=bus3121.2 length=206 units=Ft LineCode=UG_1p_type2 +New Line.L_3121_3122 phases=1 Bus1=bus3121.2 Bus2=bus3122.2 length=177 units=Ft LineCode=UG_1p_type2 +New Line.L_3119_3123 phases=1 Bus1=bus3119.2 Bus2=bus3123.2 length=91 units=Ft LineCode=UG_1p_type2 +New Line.L_3123_3124 phases=1 Bus1=bus3123.2 Bus2=bus3124.2 length=67 units=Ft LineCode=UG_1p_type2 +New Line.L_3124_3125 phases=1 Bus1=bus3124.2 Bus2=bus3125.2 length=151 units=Ft LineCode=UG_1p_type2 +New Line.L_3125_3126 phases=1 Bus1=bus3125.2 Bus2=bus3126.2 length=343 units=Ft LineCode=UG_1p_type2 +New Line.L_3126_3127 phases=1 Bus1=bus3126.2 Bus2=bus3127.2 length=234 units=Ft LineCode=UG_1p_type2 +New Line.L_3127_3128 phases=1 Bus1=bus3127.2 Bus2=bus3128.2 length=238 units=Ft LineCode=UG_1p_type2 +New Line.L_3128_3129 phases=1 Bus1=bus3128.2 Bus2=bus3129.2 length=243 units=Ft LineCode=UG_1p_type2 +New Line.L_3129_3130 phases=1 Bus1=bus3129.2 Bus2=bus3130.2 length=230 units=Ft LineCode=UG_1p_type2 +New Line.L_3130_3131 phases=1 Bus1=bus3130.2 Bus2=bus3131.2 length=194 units=Ft LineCode=UG_1p_type2 +New Line.L_3118_3132 phases=1 Bus1=bus3118.1 Bus2=bus3132.1 length=501 units=Ft LineCode=UG_1p_type2 +New Line.L_3132_3133 phases=1 Bus1=bus3132.1 Bus2=bus3133.1 length=145 units=Ft LineCode=UG_1p_type2 +New Line.L_3133_3134 phases=1 Bus1=bus3133.1 Bus2=bus3134.1 length=281 units=Ft LineCode=UG_1p_type2 +New Line.L_3134_3135 phases=1 Bus1=bus3134.1 Bus2=bus3135.1 length=240 units=Ft LineCode=UG_1p_type2 +New Line.L_3135_3136 phases=1 Bus1=bus3135.1 Bus2=bus3136.1 length=367 units=Ft LineCode=UG_1p_type2 +New Line.L_3133_3137 phases=1 Bus1=bus3133.1 Bus2=bus3137.1 length=252 units=Ft LineCode=UG_1p_type2 +New Line.L_3137_3138 phases=1 Bus1=bus3137.1 Bus2=bus3138.1 length=351 units=Ft LineCode=UG_1p_type2 +New Line.L_3138_3139 phases=1 Bus1=bus3138.1 Bus2=bus3139.1 length=289 units=Ft LineCode=UG_1p_type2 +New Line.L_3107_3140 phases=1 Bus1=bus3107.3 Bus2=bus3140.3 length=255 units=Ft LineCode=UG_1p_type2 +New Line.L_3140_3141 phases=1 Bus1=bus3140.3 Bus2=bus3141.3 length=134 units=Ft LineCode=UG_1p_type2 +New Line.L_3141_3142 phases=1 Bus1=bus3141.3 Bus2=bus3142.3 length=193 units=Ft LineCode=UG_1p_type2 +New Line.L_3142_3143 phases=1 Bus1=bus3142.3 Bus2=bus3143.3 length=202 units=Ft LineCode=UG_1p_type2 +New Line.L_3140_3148 phases=1 Bus1=bus3140.3 Bus2=bus3148.3 length=147 units=Ft LineCode=UG_1p_type2 +New Line.L_3148_3149 phases=1 Bus1=bus3148.3 Bus2=bus3149.3 length=253 units=Ft LineCode=UG_1p_type2 +New Line.L_3149_3150 phases=1 Bus1=bus3149.3 Bus2=bus3150.3 length=139 units=Ft LineCode=UG_1p_type2 +New Line.L_3150_3151 phases=1 Bus1=bus3150.3 Bus2=bus3151.3 length=156 units=Ft LineCode=UG_1p_type2 +New Line.L_3151_3152 phases=1 Bus1=bus3151.3 Bus2=bus3152.3 length=260 units=Ft LineCode=UG_1p_type2 +New Line.L_3152_3153 phases=1 Bus1=bus3152.3 Bus2=bus3153.3 length=139 units=Ft LineCode=UG_1p_type2 +New Line.L_3153_3154 phases=1 Bus1=bus3153.3 Bus2=bus3154.3 length=119 units=Ft LineCode=UG_1p_type2 +New Line.L_3154_3155 phases=1 Bus1=bus3154.3 Bus2=bus3155.3 length=505 units=Ft LineCode=UG_1p_type2 +New Line.L_3140_3156 phases=1 Bus1=bus3140.3 Bus2=bus3156.3 length=319 units=Ft LineCode=UG_1p_type2 +New Line.L_3156_3144 phases=1 Bus1=bus3156.3 Bus2=bus3144.3 length=122 units=Ft LineCode=UG_1p_type2 +New Line.L_3144_3145 phases=1 Bus1=bus3144.3 Bus2=bus3145.3 length=178 units=Ft LineCode=UG_1p_type2 +New Line.L_3145_3146 phases=1 Bus1=bus3145.3 Bus2=bus3146.3 length=178 units=Ft LineCode=UG_1p_type2 +New Line.L_3146_3147 phases=1 Bus1=bus3146.3 Bus2=bus3147.3 length=178 units=Ft LineCode=UG_1p_type2 +New Line.L_3156_3157 phases=1 Bus1=bus3156.3 Bus2=bus3157.3 length=153 units=Ft LineCode=UG_1p_type2 +New Line.L_3157_3158 phases=1 Bus1=bus3157.3 Bus2=bus3158.3 length=295 units=Ft LineCode=UG_1p_type2 +New Line.L_3158_3159 phases=1 Bus1=bus3158.3 Bus2=bus3159.3 length=257 units=Ft LineCode=UG_1p_type2 +New Line.L_3159_3160 phases=1 Bus1=bus3159.3 Bus2=bus3160.3 length=243 units=Ft LineCode=UG_1p_type2 +New Line.L_3160_3161 phases=1 Bus1=bus3160.3 Bus2=bus3161.3 length=237 units=Ft LineCode=UG_1p_type2 +New Line.L_3161_3162 phases=1 Bus1=bus3161.3 Bus2=bus3162.3 length=176 units=Ft LineCode=UG_1p_type2 +New Line.L_3002_3002_L phases=3 Bus1=bus3002.1.2.3.0 Bus2=T_bus3002_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_3004_3004_L phases=3 Bus1=bus3004.1.2.3.0 Bus2=T_bus3004_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_3006_3006_L phases=3 Bus1=bus3006.1.2.3.0 Bus2=T_bus3006_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_3007_3007_L phases=3 Bus1=bus3007.1.2.3.0 Bus2=T_bus3007_L.1.2.3.0 length=1 units=Ft LineCode=T_type2 +New Line.L_3009_3009_L phases=1 Bus1=bus3009.1.0 Bus2=T_bus3009_L.1.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3010_3010_L phases=1 Bus1=bus3010.1.0 Bus2=T_bus3010_L.1.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3011_3011_L phases=1 Bus1=bus3011.1.0 Bus2=T_bus3011_L.1.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3012_3012_L phases=1 Bus1=bus3012.1.0 Bus2=T_bus3012_L.1.0 length=1 units=Ft LineCode=T_type8 +New Line.L_3013_3013_L phases=1 Bus1=bus3013.2.0 Bus2=T_bus3013_L.2.0 length=1 units=Ft LineCode=T_type6 +New Line.L_3014_3014_L phases=1 Bus1=bus3014.2.0 Bus2=T_bus3014_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3016_3016_L phases=1 Bus1=bus3016.2.0 Bus2=T_bus3016_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3017_3017_L phases=1 Bus1=bus3017.2.0 Bus2=T_bus3017_L.2.0 length=1 units=Ft LineCode=T_type12 +New Line.L_3018_3018_L phases=1 Bus1=bus3018.3.0 Bus2=T_bus3018_L.3.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3019_3019_L phases=1 Bus1=bus3019.3.0 Bus2=T_bus3019_L.3.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3020_3020_L phases=1 Bus1=bus3020.3.0 Bus2=T_bus3020_L.3.0 length=1 units=Ft LineCode=T_type12 +New Line.L_3021_3021_L phases=1 Bus1=bus3021.3.0 Bus2=T_bus3021_L.3.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3023_3023_L phases=1 Bus1=bus3023.1.0 Bus2=T_bus3023_L.1.0 length=1 units=Ft LineCode=T_type8 +New Line.L_3024_3024_L phases=1 Bus1=bus3024.1.0 Bus2=T_bus3024_L.1.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3025_3025_L phases=1 Bus1=bus3025.1.0 Bus2=T_bus3025_L.1.0 length=1 units=Ft LineCode=T_type8 +New Line.L_3026_3026_L phases=1 Bus1=bus3026.1.0 Bus2=T_bus3026_L.1.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3027_3027_L phases=1 Bus1=bus3027.1.0 Bus2=T_bus3027_L.1.0 length=1 units=Ft LineCode=T_type12 +New Line.L_3028_3028_L phases=1 Bus1=bus3028.1.0 Bus2=T_bus3028_L.1.0 length=1 units=Ft LineCode=T_type12 +New Line.L_3029_3029_L phases=1 Bus1=bus3029.1.0 Bus2=T_bus3029_L.1.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3031_3031_L phases=3 Bus1=bus3031.1.2.3.0 Bus2=T_bus3031_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_3032_3032_L phases=3 Bus1=bus3032.1.2.3.0 Bus2=T_bus3032_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_3033_3033_L phases=3 Bus1=bus3033.1.2.3.0 Bus2=T_bus3033_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_3034_3034_L phases=3 Bus1=bus3034.1.2.3.0 Bus2=T_bus3034_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_3035_3035_L phases=3 Bus1=bus3035.1.2.3.0 Bus2=T_bus3035_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_3036_3036_L phases=3 Bus1=bus3036.1.2.3.0 Bus2=T_bus3036_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_3037_3037_L phases=3 Bus1=bus3037.1.2.3.0 Bus2=T_bus3037_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_3038_3038_L phases=3 Bus1=bus3038.1.2.3.0 Bus2=T_bus3038_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_3039_3039_L phases=3 Bus1=bus3039.1.2.3.0 Bus2=T_bus3039_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_3041_3041_L phases=1 Bus1=bus3041.3.0 Bus2=T_bus3041_L.3.0 length=1 units=Ft LineCode=T_type8 +New Line.L_3042_3042_L phases=1 Bus1=bus3042.3.0 Bus2=T_bus3042_L.3.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3043_3043_L phases=1 Bus1=bus3043.3.0 Bus2=T_bus3043_L.3.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3044_3044_L phases=1 Bus1=bus3044.3.0 Bus2=T_bus3044_L.3.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3045_3045_L phases=1 Bus1=bus3045.3.0 Bus2=T_bus3045_L.3.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3047_3047_L phases=3 Bus1=bus3047.1.2.3.0 Bus2=T_bus3047_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_3048_3048_L phases=3 Bus1=bus3048.1.2.3.0 Bus2=T_bus3048_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_3049_3049_L phases=3 Bus1=bus3049.1.2.3.0 Bus2=T_bus3049_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_3050_3050_L phases=3 Bus1=bus3050.1.2.3.0 Bus2=T_bus3050_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_3051_3051_L phases=3 Bus1=bus3051.1.2.3.0 Bus2=T_bus3051_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_3052_3052_L phases=3 Bus1=bus3052.1.2.3.0 Bus2=T_bus3052_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_3054_3054_L phases=3 Bus1=bus3054.1.2.3.0 Bus2=T_bus3054_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_3056_3056_L phases=1 Bus1=bus3056.2.0 Bus2=T_bus3056_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3057_3057_L phases=1 Bus1=bus3057.2.0 Bus2=T_bus3057_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3058_3058_L phases=1 Bus1=bus3058.2.0 Bus2=T_bus3058_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3059_3059_L phases=1 Bus1=bus3059.2.0 Bus2=T_bus3059_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3060_3060_L phases=1 Bus1=bus3060.2.0 Bus2=T_bus3060_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3061_3061_L phases=1 Bus1=bus3061.2.0 Bus2=T_bus3061_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3062_3062_L phases=1 Bus1=bus3062.2.0 Bus2=T_bus3062_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3063_3063_L phases=1 Bus1=bus3063.2.0 Bus2=T_bus3063_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3064_3064_L phases=1 Bus1=bus3064.2.0 Bus2=T_bus3064_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3065_3065_L phases=1 Bus1=bus3065.2.0 Bus2=T_bus3065_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3066_3066_L phases=1 Bus1=bus3066.2.0 Bus2=T_bus3066_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3067_3067_L phases=1 Bus1=bus3067.2.0 Bus2=T_bus3067_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3070_3070_L phases=3 Bus1=bus3070.1.2.3.0 Bus2=T_bus3070_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_3071_3071_L phases=3 Bus1=bus3071.1.2.3.0 Bus2=T_bus3071_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_3072_3072_L phases=3 Bus1=bus3072.1.2.3.0 Bus2=T_bus3072_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_3073_3073_L phases=3 Bus1=bus3073.1.2.3.0 Bus2=T_bus3073_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_3074_3074_L phases=3 Bus1=bus3074.1.2.3.0 Bus2=T_bus3074_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_3077_3077_L phases=3 Bus1=bus3077.1.2.3.0 Bus2=T_bus3077_L.1.2.3.0 length=1 units=Ft LineCode=T_type13 +New Line.L_3078_3078_L phases=3 Bus1=bus3078.1.2.3.0 Bus2=T_bus3078_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_3081_3081_L phases=3 Bus1=bus3081.1.2.3.0 Bus2=T_bus3081_L.1.2.3.0 length=1 units=Ft LineCode=T_type5 +New Line.L_3083_3083_L phases=1 Bus1=bus3083.1.0 Bus2=T_bus3083_L.1.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3084_3084_L phases=1 Bus1=bus3084.1.0 Bus2=T_bus3084_L.1.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3085_3085_L phases=1 Bus1=bus3085.1.0 Bus2=T_bus3085_L.1.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3086_3086_L phases=1 Bus1=bus3086.1.0 Bus2=T_bus3086_L.1.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3087_3087_L phases=1 Bus1=bus3087.1.0 Bus2=T_bus3087_L.1.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3088_3088_L phases=1 Bus1=bus3088.1.0 Bus2=T_bus3088_L.1.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3089_3089_L phases=1 Bus1=bus3089.1.0 Bus2=T_bus3089_L.1.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3090_3090_L phases=1 Bus1=bus3090.1.0 Bus2=T_bus3090_L.1.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3091_3091_L phases=1 Bus1=bus3091.1.0 Bus2=T_bus3091_L.1.0 length=1 units=Ft LineCode=T_type6 +New Line.L_3093_3093_L phases=1 Bus1=bus3093.1.0 Bus2=T_bus3093_L.1.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3094_3094_L phases=1 Bus1=bus3094.1.0 Bus2=T_bus3094_L.1.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3095_3095_L phases=1 Bus1=bus3095.1.0 Bus2=T_bus3095_L.1.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3096_3096_L phases=1 Bus1=bus3096.1.0 Bus2=T_bus3096_L.1.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3097_3097_L phases=1 Bus1=bus3097.1.0 Bus2=T_bus3097_L.1.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3098_3098_L phases=1 Bus1=bus3098.2.0 Bus2=T_bus3098_L.2.0 length=1 units=Ft LineCode=T_type8 +New Line.L_3099_3099_L phases=1 Bus1=bus3099.2.0 Bus2=T_bus3099_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3101_3101_L phases=1 Bus1=bus3101.3.0 Bus2=T_bus3101_L.3.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3102_3102_L phases=1 Bus1=bus3102.3.0 Bus2=T_bus3102_L.3.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3103_3103_L phases=1 Bus1=bus3103.3.0 Bus2=T_bus3103_L.3.0 length=1 units=Ft LineCode=T_type8 +New Line.L_3104_3104_L phases=1 Bus1=bus3104.3.0 Bus2=T_bus3104_L.3.0 length=1 units=Ft LineCode=T_type12 +New Line.L_3105_3105_L phases=1 Bus1=bus3105.3.0 Bus2=T_bus3105_L.3.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3106_3106_L phases=1 Bus1=bus3106.3.0 Bus2=T_bus3106_L.3.0 length=1 units=Ft LineCode=T_type12 +New Line.L_3108_3108_L phases=1 Bus1=bus3108.1.0 Bus2=T_bus3108_L.1.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3109_3109_L phases=1 Bus1=bus3109.1.0 Bus2=T_bus3109_L.1.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3110_3110_L phases=1 Bus1=bus3110.1.0 Bus2=T_bus3110_L.1.0 length=1 units=Ft LineCode=T_type8 +New Line.L_3111_3111_L phases=1 Bus1=bus3111.1.0 Bus2=T_bus3111_L.1.0 length=1 units=Ft LineCode=T_type8 +New Line.L_3112_3112_L phases=1 Bus1=bus3112.1.0 Bus2=T_bus3112_L.1.0 length=1 units=Ft LineCode=T_type8 +New Line.L_3114_3114_L phases=1 Bus1=bus3114.2.0 Bus2=T_bus3114_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3115_3115_L phases=1 Bus1=bus3115.2.0 Bus2=T_bus3115_L.2.0 length=1 units=Ft LineCode=T_type8 +New Line.L_3116_3116_L phases=1 Bus1=bus3116.2.0 Bus2=T_bus3116_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3117_3117_L phases=1 Bus1=bus3117.2.0 Bus2=T_bus3117_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3120_3120_L phases=1 Bus1=bus3120.2.0 Bus2=T_bus3120_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3121_3121_L phases=1 Bus1=bus3121.2.0 Bus2=T_bus3121_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3122_3122_L phases=1 Bus1=bus3122.2.0 Bus2=T_bus3122_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3123_3123_L phases=1 Bus1=bus3123.2.0 Bus2=T_bus3123_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3124_3124_L phases=1 Bus1=bus3124.2.0 Bus2=T_bus3124_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3125_3125_L phases=1 Bus1=bus3125.2.0 Bus2=T_bus3125_L.2.0 length=1 units=Ft LineCode=T_type10 +New Line.L_3126_3126_L phases=1 Bus1=bus3126.2.0 Bus2=T_bus3126_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3127_3127_L phases=1 Bus1=bus3127.2.0 Bus2=T_bus3127_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3128_3128_L phases=1 Bus1=bus3128.2.0 Bus2=T_bus3128_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3129_3129_L phases=1 Bus1=bus3129.2.0 Bus2=T_bus3129_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3130_3130_L phases=1 Bus1=bus3130.2.0 Bus2=T_bus3130_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3131_3131_L phases=1 Bus1=bus3131.2.0 Bus2=T_bus3131_L.2.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3132_3132_L phases=1 Bus1=bus3132.1.0 Bus2=T_bus3132_L.1.0 length=1 units=Ft LineCode=T_type8 +New Line.L_3134_3134_L phases=1 Bus1=bus3134.1.0 Bus2=T_bus3134_L.1.0 length=1 units=Ft LineCode=T_type8 +New Line.L_3135_3135_L phases=1 Bus1=bus3135.1.0 Bus2=T_bus3135_L.1.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3136_3136_L phases=1 Bus1=bus3136.1.0 Bus2=T_bus3136_L.1.0 length=1 units=Ft LineCode=T_type8 +New Line.L_3137_3137_L phases=1 Bus1=bus3137.1.0 Bus2=T_bus3137_L.1.0 length=1 units=Ft LineCode=T_type8 +New Line.L_3138_3138_L phases=1 Bus1=bus3138.1.0 Bus2=T_bus3138_L.1.0 length=1 units=Ft LineCode=T_type8 +New Line.L_3139_3139_L phases=1 Bus1=bus3139.1.0 Bus2=T_bus3139_L.1.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3141_3141_L phases=1 Bus1=bus3141.3.0 Bus2=T_bus3141_L.3.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3142_3142_L phases=1 Bus1=bus3142.3.0 Bus2=T_bus3142_L.3.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3143_3143_L phases=1 Bus1=bus3143.3.0 Bus2=T_bus3143_L.3.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3144_3144_L phases=1 Bus1=bus3144.3.0 Bus2=T_bus3144_L.3.0 length=1 units=Ft LineCode=T_type10 +New Line.L_3145_3145_L phases=1 Bus1=bus3145.3.0 Bus2=T_bus3145_L.3.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3146_3146_L phases=1 Bus1=bus3146.3.0 Bus2=T_bus3146_L.3.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3147_3147_L phases=1 Bus1=bus3147.3.0 Bus2=T_bus3147_L.3.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3148_3148_L phases=1 Bus1=bus3148.3.0 Bus2=T_bus3148_L.3.0 length=1 units=Ft LineCode=T_type8 +New Line.L_3149_3149_L phases=1 Bus1=bus3149.3.0 Bus2=T_bus3149_L.3.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3150_3150_L phases=1 Bus1=bus3150.3.0 Bus2=T_bus3150_L.3.0 length=1 units=Ft LineCode=T_type6 +New Line.L_3151_3151_L phases=1 Bus1=bus3151.3.0 Bus2=T_bus3151_L.3.0 length=1 units=Ft LineCode=T_type6 +New Line.L_3152_3152_L phases=1 Bus1=bus3152.3.0 Bus2=T_bus3152_L.3.0 length=1 units=Ft LineCode=T_type6 +New Line.L_3153_3153_L phases=1 Bus1=bus3153.3.0 Bus2=T_bus3153_L.3.0 length=1 units=Ft LineCode=T_type7 +New Line.L_3154_3154_L phases=1 Bus1=bus3154.3.0 Bus2=T_bus3154_L.3.0 length=1 units=Ft LineCode=T_type6 +New Line.L_3155_3155_L phases=1 Bus1=bus3155.3.0 Bus2=T_bus3155_L.3.0 length=1 units=Ft LineCode=T_type6 +New Line.L_3157_3157_L phases=1 Bus1=bus3157.3.0 Bus2=T_bus3157_L.3.0 length=1 units=Ft LineCode=T_type8 +New Line.L_3158_3158_L phases=1 Bus1=bus3158.3.0 Bus2=T_bus3158_L.3.0 length=1 units=Ft LineCode=T_type8 +New Line.L_3159_3159_L phases=1 Bus1=bus3159.3.0 Bus2=T_bus3159_L.3.0 length=1 units=Ft LineCode=T_type8 +New Line.L_3160_3160_L phases=1 Bus1=bus3160.3.0 Bus2=T_bus3160_L.3.0 length=1 units=Ft LineCode=T_type6 +New Line.L_3161_3161_L phases=1 Bus1=bus3161.3.0 Bus2=T_bus3161_L.3.0 length=1 units=Ft LineCode=T_type6 +New Line.L_3162_3162_L phases=1 Bus1=bus3162.3.0 Bus2=T_bus3162_L.3.0 length=1 units=Ft LineCode=T_type6 + +//---------------------Circuit Breakers of Feeder A----------------------// +New Line.CB_101 Phases=3 Bus1=bus1.1.2.3 Bus2=bus1001.1.2.3 Switch=y r1=1e-4 r0=1e-4 x1=0 x0=0 c1=0 c0=0 +New Line.CB_102 Phases=3 Bus1=bus1010.1.2.3 Bus2=bus2057.1.2.3 Switch=y r1=1e-4 r0=1e-4 x1=0 x0=0 c1=0 c0=0 enabled=n ! Bus2 is supposed to connect to bus2057.1.2.3 when reconfiguring, normally open +! Circuit breaker name, number of phases, 1st bus, 2nd bus, this line is a switch, positive-sequence resistance per uint length, zero-sequence resistance per uint length, positive-sequence reactance per uint length, zero-sequence reactance per uint length, positive-sequence capacitance per uint length, zero-sequence capacitance per uint length. Note that we use Bus2=bus2057.4.5.6 to achieve a normally open circuit breaker, i.e. connecting the energized nodes (bus1010.1.2.3) to non-energized nodes (bus2057.4.5.6). + +//---------------------Circuit Breakers of Feeder B----------------------// +New Line.CB_201 Phases=3 Bus1=bus1.1.2.3 Bus2=bus2001.1.2.3 Switch=y r1=1e-4 r0=1e-4 x1=0 x0=0 c1=0 c0=0 +New Line.CB_202 Phases=3 Bus1=bus2012.1.2.3 Bus2=bus2013.1.2.3 Switch=y r1=1e-4 r0=1e-4 x1=0 x0=0 c1=0 c0=0 +New Line.CB_203 Phases=3 Bus1=bus2021.1.2.3 Bus2=bus2026.1.2.3 Switch=y r1=1e-4 r0=1e-4 x1=0 x0=0 c1=0 c0=0 +New Line.CB_204 Phases=3 Bus1=bus2013.1.2.3 Bus2=bus3005.1.2.3 Switch=y r1=1e-4 r0=1e-4 x1=0 x0=0 c1=0 c0=0 enabled=n ! Bus2 is supposed to connect to bus3005.1.2.3 when reconfiguring, normally open + +//---------------------Circuit Breakers of Feeder C----------------------// +New Line.CB_301 Phases=3 Bus1=bus1.1.2.3 Bus2=bus3001.1.2.3 Switch=y r1=1e-4 r0=1e-4 x1=0 x0=0 c1=0 c0=0 +New Line.CB_302 Phases=3 Bus1=bus3075.1.2.3 Bus2=bus3076.1.2.3 Switch=y r1=1e-4 r0=1e-4 x1=0 x0=0 c1=0 c0=0 +New Line.CB_303 Phases=3 Bus1=bus3081.1.2.3 Bus2=bus2016.1.2.3 Switch=y r1=1e-4 r0=1e-4 x1=0 x0=0 c1=0 c0=0 enabled=n ! Bus2 is supposed to connect to bus2016.1.2.3 when reconfiguring, normally open + +//***************************************************************************************// +// Feeder A +//***************************************************************************************// +New LoadShape.1003_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(15.434 14.526 15.054 14.667 13.87 13.516 12.884 13.43 11.331 11.749 11.511 11.72 11.063 14.099 12.124 11.395 11.945 12.189 12.612 13.385 13.923 14.158 13.13 14.626) + ~ qmult=(3.8681 4.2367 7.291 6.6825 5.9086 5.3419 5.8701 5.7212 3.7243 5.6903 4.5494 1.67 2.2464 4.1122 4.7917 4.8542 2.4255 5.5535 4.1454 3.3546 5.5027 6.857 5.1893 6.6638) +New LoadShape.1004_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(6.913 6.73 6.833 6.941 6.915 6.691 7.306 11.51 14.474 19.695 19.69 18.02 17.975 12.407 9.221 10.019 8.176 6.436 6.496 6.618 6.564 6.52 6.101 7.343) + ~ qmult=(2.9449 2.6599 3.3094 3.3617 2.2729 3.0485 2.1309 2.3372 7.0101 3.9992 3.9982 2.5677 8.1897 4.5031 3.0308 1.4276 2.0491 0.9171 3.1462 1.6586 1.6451 2.9706 1.5291 2.4135) +New LoadShape.1005_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(5.05 4.978 5.072 5.092 5.583 4.963 5.876 4.692 4.326 5.434 11.284 9.336 9.978 9.331 7.307 7.011 8.049 11.693 6.849 3.564 5.272 3.493 3.516 3.482) + ~ qmult=(1.8329 2.268 1.2712 1.6737 0.7955 1.4475 2.5032 1.3685 0.8784 1.7861 2.2913 3.0686 2.5007 3.975 1.4837 2.0449 3.4289 1.6662 2.7069 1.4086 1.0705 1.3805 1.6019 1.1445) +New Loadshape.1006_1_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.16 2.02 2.34 2.36 2.14 2.28 2.38 1.94 1.88 1.94 1.94 4.52 4.64 4.94 4.58 4.32 4.76 2.32 1.28 1.84 1.58 1.9 1.64 1.58) + ~ qmult=(1.0461 0.4102 0.4752 0.4792 0.5363 1.1043 0.6942 0.3939 0.3818 0.7667 0.2764 2.0594 1.6841 2.1044 1.6623 1.568 1.3883 0.9883 0.3733 0.4611 0.396 0.9202 0.6482 0.7199) +New Loadshape.1006_2_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.16 2.02 2.34 2.36 2.14 2.28 2.38 1.94 1.88 1.94 1.94 4.52 4.64 4.94 4.58 4.32 4.76 2.32 1.28 1.84 1.58 1.9 1.64 1.58) + ~ qmult=(1.0461 0.4102 0.4752 0.4792 0.5363 1.1043 0.6942 0.3939 0.3818 0.7667 0.2764 2.0594 1.6841 2.1044 1.6623 1.568 1.3883 0.9883 0.3733 0.4611 0.396 0.9202 0.6482 0.7199) +New Loadshape.1007_1_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(1.7515 1.667 1.783 1.7275 1.6745 1.522 1.6725 1.4865 1.823 1.71 1.649 1.646 1.235 1.5585 1.5745 1.3375 1.366 1.5355 1.814 2.031 1.9885 2.0965 2.066 1.929) + ~ qmult=(0.6357 0.3385 0.2541 0.5678 0.4197 0.5003 0.2383 0.5395 0.8829 0.4286 0.7025 0.541 0.176 0.2221 0.7174 0.5286 0.6616 0.5047 0.7728 0.5924 0.2833 0.5254 0.7499 0.7624) +New Loadshape.1007_2_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(1.7515 1.667 1.783 1.7275 1.6745 1.522 1.6725 1.4865 1.823 1.71 1.649 1.646 1.235 1.5585 1.5745 1.3375 1.366 1.5355 1.814 2.031 1.9885 2.0965 2.066 1.929) + ~ qmult=(0.6357 0.3385 0.2541 0.5678 0.4197 0.5003 0.2383 0.5395 0.8829 0.4286 0.7025 0.541 0.176 0.2221 0.7174 0.5286 0.6616 0.5047 0.7728 0.5924 0.2833 0.5254 0.7499 0.7624) +New LoadShape.1008_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(7.414 10.375 10.849 10.888 10.972 6.517 5.961 6.422 6.202 5.826 5.318 5.196 4.9 5.031 5.474 9.587 7.917 7.374 5.887 6.998 5.096 4.812 5.246 5.471) + ~ qmult=(1.8581 5.0248 2.203 3.9518 2.7498 2.3654 1.9593 2.3309 1.8089 0.8302 1.5511 1.0551 1.6106 1.9884 2.6512 3.789 1.6076 1.4974 1.4754 1.421 2.1709 1.7465 1.5301 2.1623) +New LoadShape.1009_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(9.511 9.424 7.976 8.809 8.649 7.727 8.443 7.314 7.274 8.197 8.16 8.034 8.275 7.608 7.424 8.185 7.635 7.961 8.53 8.102 6.843 8.927 9.1 8.888) + ~ qmult=(4.3333 4.5643 1.1365 4.2664 2.1676 3.7424 3.3369 2.404 2.6401 3.4919 1.657 2.0135 2.7199 3.6847 3.3825 3.9642 1.9135 1.6165 2.1378 3.924 2.2492 4.0673 1.8478 3.2259) +New LoadShape.1010_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(4.569 5.298 4.967 3.913 4.019 3.212 4.119 4.021 3.513 4.024 6.047 5.522 5.826 6.059 7.094 4.426 7.176 6.127 7.391 6.652 7.048 5.819 10.921 6.803) + ~ qmult=(1.6583 0.7549 1.9631 0.5576 0.8161 0.6522 1.7547 1.832 0.7133 0.5734 2.9287 1.6106 2.6544 2.1991 1.0108 1.4548 1.4571 2.0138 2.6826 3.2217 3.0024 0.8292 4.9758 3.2948) +New LoadShape.1011_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.954 2.76 3.025 2.891 2.842 3.99 2.718 2.389 5.452 3.722 4.3 3.023 2.732 3.269 6.523 5.537 4.506 3.557 3.671 5.234 4.744 3.411 6.21 3.275) + ~ qmult=(1.2584 0.805 1.4651 0.7246 0.7123 1.1638 0.7928 1.0885 1.9788 1.6958 1.8318 0.8817 0.3893 1.0745 2.7788 2.5227 1.1293 0.7223 1.3324 2.3847 1.7218 1.4531 1.8113 0.8208) +New LoadShape.1012_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(3.035 1.38 1.609 1.299 1.789 1.44 1.207 1.209 2.026 3.172 7.231 10.1 5.458 8.783 10.162 4.741 5.273 4.223 5.406 9.523 12.443 8.275 1.668 1.473) + ~ qmult=(1.4699 0.5454 0.7331 0.427 0.588 0.6134 0.3967 0.4778 0.9231 0.6441 2.109 4.8917 1.981 2.8868 4.0163 1.1882 1.3215 1.799 1.9621 2.7775 5.3007 3.0034 0.4865 0.3692) +New LoadShape.1013_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.289 2.302 2.71 2.294 2.308 2.328 2.446 2.665 1.973 2.113 1.972 2.673 2.226 1.648 1.686 1.839 1.723 2.22 5.32 3.268 2.775 2.394 2.411 2.251) + ~ qmult=(0.9047 0.8355 1.3125 0.5749 0.7586 0.3317 0.613 0.9673 0.8989 0.7669 0.9551 0.5428 0.5579 0.7509 0.4918 0.262 0.8345 0.8774 1.0803 1.4889 1.1821 0.3411 0.4896 1.0256) +New LoadShape.1014_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(0.751 0.547 0.484 0.371 0.32 0.423 0.302 0.327 0.859 0.491 0.404 0.971 0.479 0.364 0.408 0.446 0.509 3.195 1.209 2.715 1.44 0.633 0.432 0.36) + ~ qmult=(0.1525 0.2162 0.069 0.0753 0.065 0.139 0.0613 0.1584 0.3395 0.1941 0.1957 0.2434 0.2182 0.1439 0.1341 0.2032 0.2012 1.5474 0.5855 1.3149 0.5691 0.1586 0.126 0.1744) +New LoadShape.1015_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(0.614 0.522 0.437 0.401 0.42 0.417 0.415 0.43 0.469 0.748 0.611 0.761 0.929 0.899 0.924 2.758 5.948 2.36 2.035 4.46 3.457 1.93 0.718 0.697) + ~ qmult=(0.1539 0.0744 0.0623 0.1318 0.138 0.0847 0.104 0.1254 0.0952 0.2715 0.2415 0.222 0.2328 0.2955 0.2695 0.9065 0.8475 0.4792 0.9272 1.9 0.4926 0.8222 0.236 0.1415) +New LoadShape.1016_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(3.035 1.38 1.609 1.299 1.789 1.44 1.207 1.209 2.026 3.172 7.231 10.1 5.458 8.783 10.162 4.741 5.273 4.223 5.406 9.523 12.443 8.275 1.668 1.473) + ~ qmult=(1.1995 0.1966 0.584 0.5918 0.8151 0.6974 0.4381 0.303 0.7353 1.2537 3.2945 2.5313 2.1571 2.8868 4.9217 1.5583 1.9138 1.669 2.1366 1.357 5.3007 3.2705 0.7106 0.2099) +New LoadShape.1017_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.19 2.974 1.703 2.508 4.918 5.823 3.388 3.551 3.073 2.822 5.599 6.715 1.606 4.118 4.7 3.963 3.628 2.472 4.302 6.445 4.099 6.125 3.972 1.983) + ~ qmult=(0.6388 0.8674 0.4967 0.6286 1.785 2.4806 1.6409 1.5127 0.8963 1.2022 2.7117 2.6539 0.2288 1.0321 1.5448 0.5647 0.9093 0.721 1.0782 2.3392 1.8676 1.2437 1.8097 0.4027) + +//***************************************************************************************// +// Feeder B +//***************************************************************************************// +New LoadShape.2002_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(30.42 30.68 31.68 32.44 30.64 31.08 30.66 29.96 28.84 30.8 40.02 40.12 37.72 38.2 39.78 39.44 38.44 43.76 42.02 40.66 42.68 40.8 41.08 41.2) + ~ qmult=(9.9986 12.1255 9.24 8.1302 14.8396 9.065 8.9425 11.8409 9.4792 14.9171 19.3826 10.055 14.9079 16.2731 18.1243 14.3148 13.9519 19.9376 16.6074 17.3211 16.8682 16.1252 13.5023 18.7713) +New LoadShape.2003_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(11.792 12.049 12.591 11.48 10.414 10.625 10.478 11.106 5.995 5.524 7.608 6.459 4.886 8.799 4.529 3.552 4.438 5.294 6.489 9.4 10.233 10.399 10.899 11.508) + ~ qmult=(1.6803 2.4467 2.5567 2.8772 4.1159 4.8409 3.444 5.06 2.5539 1.6112 3.241 1.6188 1.2245 2.8921 1.79 1.1675 1.754 1.5441 2.5646 3.4117 3.7141 1.4818 4.643 4.9024) +New LoadShape.2005_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(6.964 7.121 6.764 6.402 6.464 7.083 6.523 6.485 5.335 5.539 8.839 9.933 10.373 8.631 8.181 7.418 4.55 6.26 6.162 4.745 9.947 7.603 9.27 9.147) + ~ qmult=(0.9923 2.3406 3.0818 2.7272 2.5547 3.2271 2.5781 2.1315 0.7602 2.1892 4.2809 3.6052 4.0997 3.9324 2.9693 2.9318 0.9239 2.6667 2.8075 2.2981 1.4174 1.5439 3.6637 1.8574) +New LoadShape.2008_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.015 1.875 1.722 1.845 1.586 1.727 1.569 1.572 1.816 2.039 1.456 1.227 1.154 1.286 1.326 1.409 1.235 1.243 2.184 2.423 2.656 2.193 1.751 2.008) + ~ qmult=(0.4092 0.7987 0.834 0.8406 0.5213 0.5037 0.3932 0.394 0.2588 0.2905 0.6634 0.5943 0.4561 0.3751 0.3867 0.2008 0.2508 0.1771 1.0578 0.7067 0.7747 0.7208 0.5755 0.7936) +New LoadShape.2009_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(0.674 0.69 0.622 0.582 0.69 0.935 0.942 0.646 0.613 0.697 0.807 0.548 0.538 0.516 0.589 0.536 0.48 0.402 0.851 0.762 0.78 0.716 1.125 0.548) + ~ qmult=(0.2871 0.1729 0.265 0.2479 0.3342 0.3073 0.2747 0.2943 0.0873 0.253 0.2929 0.2654 0.2606 0.1293 0.1476 0.1088 0.2187 0.1589 0.3625 0.2223 0.3323 0.1454 0.4083 0.2654) +New LoadShape.2010_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(1.729 1.289 1.495 1.288 1.198 1.324 1.357 1.451 1.47 1.071 2.254 1.151 0.989 1.19 3.29 3.322 6.231 3.407 1.341 0.992 1.303 2.585 1.424 1.564) + ~ qmult=(0.2464 0.1837 0.213 0.5487 0.4348 0.4805 0.6183 0.3637 0.2095 0.1526 1.027 0.4903 0.2479 0.4319 0.6681 1.2057 2.6544 0.9937 0.1911 0.2893 0.5551 1.0217 0.6488 0.6663) +New LoadShape.2011_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(7.185 5.959 5.84 6.165 5.868 5.937 5.982 6.176 5.983 6.044 6.59 4.745 4.885 5.084 4.463 4.127 6.293 7.177 5.994 5.011 5.999 5.396 5.054 4.825) + ~ qmult=(2.0956 1.21 1.4636 1.7981 2.6735 2.1548 0.8524 2.03 1.2149 2.3887 1.6516 1.7222 1.2243 2.1658 1.4669 1.8803 1.2778 1.7987 2.7309 1.4615 2.371 2.1326 1.8344 0.6875) +New LoadShape.2014_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.191 2.225 2.695 2.251 2.778 3.527 2.65 1.968 1.919 2.316 2.696 1.992 2.043 6.732 2.831 2.968 1.834 2.832 2.207 4.618 5.855 6.553 2.343 2.647) + ~ qmult=(0.7952 0.649 0.9782 0.817 1.0083 1.6069 1.2835 0.2804 0.8743 0.7612 1.3057 0.4045 0.9308 2.8678 1.3711 1.173 0.8356 1.0279 0.6437 2.2366 1.7077 0.9338 1.1348 0.87) +New LoadShape.2015_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(1.588 1.618 1.187 1.425 1.442 1.341 1.4 1.231 1.439 1.231 1.261 1.411 1.254 1.485 1.323 1.922 3.226 3.329 1.71 1.462 1.365 1.79 1.55 1.644) + ~ qmult=(0.7235 0.3285 0.5749 0.6902 0.2928 0.2723 0.4083 0.5244 0.473 0.5609 0.2561 0.2865 0.3143 0.6326 0.5229 0.6317 1.5624 1.0942 0.3472 0.6661 0.3981 0.7075 0.3885 0.412) +New LoadShape.2016_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(1.787 1.707 1.677 1.634 1.688 2.193 3.665 2.425 2.407 2.948 2.05 1.859 2.273 2.293 2.144 2.204 2.216 3.451 2.508 2.034 2.316 2.576 2.732 2.928) + ~ qmult=(0.7063 0.3466 0.7641 0.4766 0.4923 0.8667 1.3302 0.6078 0.4888 0.969 0.8102 0.4659 0.9683 0.9063 0.9133 0.7999 1.0096 0.4917 0.7315 0.5098 0.9866 0.7513 1.3232 1.2473) +New LoadShape.2017_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(6.123 3.861 4.369 3.721 4.445 3.931 3.629 4.274 4.363 3.741 4.214 5.689 7.311 7.18 4.823 8.008 8.954 6.215 5.467 7.184 7.733 3.047 3.282 3.023) + ~ qmult=(2.9655 0.5502 1.2743 1.3505 1.8936 0.5601 1.3172 1.0712 1.0935 1.0911 0.8557 1.6593 2.6535 2.0942 1.9062 1.1411 3.2499 2.0428 1.3702 1.0237 3.5233 1.1059 1.4953 0.9936) +New LoadShape.2018_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(3.065 3.668 3.857 3.434 3.556 3.29 3.914 4.904 7.337 5.92 4.872 3.938 3.198 3.336 3.242 3.217 3.805 4.348 5.426 4.3 3.409 3.183 3.403 3.178) + ~ qmult=(1.4844 1.4497 0.5496 1.1287 1.1688 1.0814 0.9809 1.6119 2.8998 1.9458 0.9893 1.2944 1.5489 1.6157 1.1767 0.9383 1.6209 1.5781 2.1445 1.6995 0.8544 1.1553 0.9925 1.1535) +New LoadShape.2020_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(5.17 4.525 4.506 4.676 4.431 4.975 4.729 4.638 4.22 4.316 3.808 4.237 3.681 4.049 5.919 6.875 6.435 10.226 9.56 9.785 8.512 4.707 3.756 3.7) + ~ qmult=(2.3555 1.9276 2.1824 0.6663 1.2924 1.0102 1.1852 2.1131 0.6013 2.0903 1.3821 1.2358 0.9225 1.0148 1.9455 2.0052 1.8769 4.9527 3.4698 3.2162 2.7978 2.1446 0.5352 1.3429) +New LoadShape.2022_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(1.658 1.453 1.453 1.457 1.418 1.162 1.769 1.343 1.253 2.535 1.842 1.986 3.298 2.912 4.481 1.68 1.71 1.298 1.21 1.505 3.772 3.511 5.697 2.15) + ~ qmult=(0.545 0.619 0.207 0.2076 0.5147 0.5628 0.8568 0.4414 0.3655 1.155 0.8921 0.6528 1.3035 1.3267 0.9099 0.49 0.8282 0.3786 0.5513 0.7289 1.7186 1.3876 2.0677 0.5388) +New LoadShape.2023_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(0.703 0.694 0.661 0.756 0.655 0.71 0.663 0.599 0.32 0.355 0.284 0.336 0.29 0.362 0.293 0.447 0.306 0.312 1.947 2.476 2.736 3.125 2.222 0.683) + ~ qmult=(0.2311 0.2743 0.3012 0.2988 0.1642 0.1779 0.2179 0.1747 0.1458 0.1403 0.1294 0.0842 0.1405 0.119 0.1419 0.1304 0.0767 0.1329 0.488 0.7222 1.2466 0.4453 1.0124 0.2245) +New LoadShape.2024_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.242 2.222 2.098 1.99 2.107 2.123 2.709 3.071 2.278 2.849 1.695 2.133 1.646 1.46 1.63 1.605 1.658 1.669 2.28 2.249 2.338 2.312 2.508 2.6) + ~ qmult=(0.3195 0.8065 0.9559 0.7865 0.3002 0.4311 0.9832 0.7697 0.8268 0.5785 0.6152 0.843 0.5974 0.6652 0.2323 0.5825 0.3367 0.5486 0.463 1.0247 0.6819 1.1198 1.2147 0.7583) +New LoadShape.2025_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(4.584 1.913 2.143 1.89 1.67 1.587 2.09 1.587 1.767 1.856 4.193 2.431 2.249 2.076 2.381 2.575 2.524 1.91 2.47 4.428 4.553 3.362 6.952 3.931) + ~ qmult=(1.337 0.9265 0.7778 0.747 0.5489 0.3977 0.7586 0.7231 0.5808 0.5413 2.0308 0.6093 0.9581 0.4215 0.6945 0.6454 1.0752 0.7549 0.7204 2.1446 0.9245 0.4791 1.4117 1.4268) +New LoadShape.2028_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(0.932 0.983 0.991 1.034 1.003 0.897 1.023 1.042 0.949 0.901 0.909 0.929 1.337 1.46 3.268 4.8 3.638 1.493 1.086 0.977 0.921 0.889 0.962 0.942) + ~ qmult=(0.3383 0.3231 0.48 0.4087 0.4273 0.4087 0.3713 0.1485 0.1927 0.2258 0.2651 0.1324 0.39 0.6652 0.819 0.9747 1.3204 0.7231 0.357 0.3861 0.2686 0.3227 0.4383 0.3723) +New LoadShape.2029_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(9.016 8.392 8.192 8.336 8.803 8.344 9.06 8.449 4.962 4.497 4.316 4.006 3.817 3.61 3.947 4.015 3.942 4.063 4.575 7.339 8.554 8.426 8.724 8.588) + ~ qmult=(3.5634 2.7583 3.2377 3.798 3.1951 1.6943 2.6425 2.4643 1.801 1.3116 2.0903 1.5833 0.9566 1.3103 1.7983 0.8153 1.9092 1.8512 2.2158 1.8393 4.1429 2.1118 2.5445 3.3942) +New LoadShape.2030_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(5.527 5.21 4.925 4.891 5.193 5.048 5.071 4.727 3.721 3.63 8.883 8.299 4.223 3.747 5.233 5.068 3.949 4.565 9.57 9.892 5.277 4.085 4.199 3.977) + ~ qmult=(2.006 1.3057 0.7018 0.9932 0.74 1.4723 0.7226 0.9599 1.8022 1.1931 2.2263 2.4205 1.9241 1.8148 2.0682 2.309 1.4333 0.927 3.7823 2.4792 2.0856 0.8295 1.3801 1.812) +New LoadShape.2031_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(4.09 3.609 4.334 3.519 3.536 3.849 3.454 3.942 4.104 2.956 3.018 3.165 2.806 3.776 3.454 4.706 3.222 2.546 2.944 2.684 3.267 3.065 2.845 2.754) + ~ qmult=(1.6165 0.7328 1.2641 1.7043 1.2834 1.397 0.8657 1.6793 1.9877 0.9716 0.6128 1.0403 1.359 0.7667 1.1353 1.1794 1.468 0.3628 0.7378 1.2999 0.4655 1.3057 0.5777 1.0885) +New LoadShape.2032_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(1.224 1.188 1.094 1.177 1.069 0.952 1.026 0.716 0.586 0.925 0.692 0.963 2.351 2.415 0.78 1.443 0.55 0.455 0.526 1.042 1.416 1.905 1.499 1.068) + ~ qmult=(0.357 0.5754 0.2221 0.3869 0.1523 0.2777 0.3372 0.283 0.2496 0.448 0.2735 0.4664 0.8533 0.9545 0.1111 0.6147 0.2174 0.0924 0.2241 0.4747 0.2018 0.3868 0.683 0.4221) +New LoadShape.2034_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.926 2.07 2.964 1.728 1.8 1.738 1.955 2.083 1.652 1.455 1.866 1.421 1.968 2.935 2.192 1.22 1.28 1.079 1.798 2.349 6.219 5.25 1.79 1.83) + ~ qmult=(1.3331 0.9431 0.6019 0.8369 0.5916 0.7919 0.2786 0.423 0.7037 0.5281 0.7949 0.5158 0.7143 0.596 0.8663 0.3558 0.1824 0.1537 0.5244 1.1377 2.0441 2.2365 0.8155 0.8338) +New LoadShape.2035_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(4.579 4.133 4.238 3.57 4.072 3.806 3.782 3.697 3.133 7.225 15.162 21.6 19.459 12.721 13.654 12.281 11.652 7.733 2.721 3.188 7.187 4.106 2.533 2.773) + ~ qmult=(0.9298 1.7607 1.0621 1.729 1.0205 1.1101 1.8317 0.9266 0.4464 1.8108 5.9924 9.8413 5.6755 3.1882 5.8166 4.0366 3.3985 3.7453 0.8943 1.1571 2.6085 1.1976 0.6348 0.695) +New LoadShape.2037_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(4.25 4.275 4.763 4.247 4.315 4.187 4.172 4.195 4.001 7.896 3.528 2.153 2.228 3.307 4.054 3.044 2.02 2.04 2.051 2.24 2.546 3.189 2.588 2.529) + ~ qmult=(0.6056 1.6896 1.1937 0.8624 1.7054 1.5197 1.3713 1.5226 1.8229 3.8242 1.5029 1.0427 1.0791 0.4712 0.5777 1.1048 0.9783 0.7404 0.7444 0.813 1.2331 1.0482 0.3688 0.9179) +New LoadShape.2040_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(61.52 61.52 60.92 63.72 72.64 76.44 83.64 83.52 70.36 68.6 62.2 61.4 61.32 62.04 62.6 60.6 57.64 67.92 72.96 72.16 68.84 72.84 65.2 63.88) + ~ qmult=(20.2206 22.3288 20.0234 25.1838 30.9445 32.5633 11.9181 35.5794 32.057 33.2245 12.6302 27.9747 29.6986 26.4289 30.3186 17.675 26.2616 24.6517 33.2416 34.9487 29.3257 21.245 13.2394 27.2128) +New LoadShape.2041_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(1.752 1.711 1.796 2.045 1.648 1.683 1.809 1.892 1.672 1.674 1.564 1.742 2.168 1.709 3.006 1.816 1.673 1.55 1.607 1.572 1.656 1.659 2.095 1.942) + ~ qmult=(0.511 0.4288 0.8698 0.7422 0.4807 0.6108 0.7706 0.862 0.3395 0.6616 0.5677 0.6323 0.9878 0.8277 1.3696 0.3688 0.8103 0.3885 0.3263 0.5167 0.802 0.5453 0.6886 0.5664) +New LoadShape.2042_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(4.753 5.298 5.118 5.164 5.082 4.992 4.99 4.946 3.994 4.242 4.024 3.789 3.19 2.851 3.494 2.756 3.476 2.502 2.482 3.472 3.336 3.7 3.712 4.013) + ~ qmult=(2.0248 2.2569 2.0228 1.6973 1.4823 2.2744 1.4554 2.107 1.7014 1.5396 1.4605 1.2454 1.2608 0.8315 0.7095 1.0003 1.0138 1.0658 0.3537 1.6816 1.3185 0.7513 0.9303 0.8149) +New LoadShape.2043_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(6.001 7.342 5.729 5.441 5.446 5.371 5.48 5.319 4.739 6.502 7.176 6.907 6.81 7.468 7.376 7.965 7.358 4.707 5.567 5.298 4.947 5.002 4.934 4.889) + ~ qmult=(1.9724 2.9017 2.2642 2.479 2.6376 1.3461 2.3345 2.1022 2.1592 2.9624 1.0225 1.4025 2.4717 3.1814 2.9152 3.629 3.5636 1.5471 2.6962 0.7549 0.7049 2.4226 1.95 2.0827) +New LoadShape.2045_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.049 2.121 2.213 2.167 2.124 2.452 2.143 2.256 2.543 4.376 2.616 2.133 2.168 2.548 2.181 2.204 2.512 2.204 2.15 2.128 2.216 2.522 2.248 2.204) + ~ qmult=(0.8098 0.9035 0.9427 0.5431 0.6195 0.8059 0.7044 0.8188 1.2316 0.8886 0.763 0.9087 0.7869 1.1609 0.862 0.8711 0.6296 1.0042 0.5388 0.5333 1.0096 0.8289 0.3203 0.5524) +New LoadShape.2046_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(5.341 4.558 4.602 4.417 4.387 4.325 4.81 5.65 6.502 8.958 6.408 7.27 6.045 5.187 4.59 4.147 3.703 3.904 6.096 8.72 8.404 6.576 5.056 4.448) + ~ qmult=(1.5578 1.4981 1.8188 1.2883 0.6251 0.6163 1.4029 2.0507 2.7698 2.9444 2.9196 2.1204 0.8614 1.7049 0.932 0.5909 1.6871 0.7927 2.4093 3.9729 1.7065 2.8014 2.4487 1.2973) +New LoadShape.2047_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(7.303 5.282 5.162 5.148 5.492 5.107 4.839 6.906 7.542 15.827 10.136 12.416 12.382 15.768 13.501 9.214 8.689 9.621 9.39 9.192 11.758 8.832 9.146 10.137) + ~ qmult=(2.13 2.0876 1.5056 2.193 1.6018 1.8536 1.2128 3.1465 1.0747 6.2552 4.6181 3.6213 5.6414 7.1841 4.9002 1.871 3.1537 1.9536 1.9067 1.8665 3.8647 1.7934 1.8572 4.0064) +New LoadShape.2048_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(4.626 3.732 3.277 3.238 3.194 4.379 4.25 3.821 4.301 4.161 4.451 4.301 3.736 4.432 7.144 6.503 5.659 7.671 8.157 8.418 7.325 7.015 6.033 4.275) + ~ qmult=(1.1594 1.475 0.9558 1.3794 0.6486 1.7307 1.2396 1.5102 1.0779 1.8958 1.8961 1.9596 0.7586 0.6315 3.0433 2.7703 2.4107 1.5577 2.0443 2.1097 3.3374 3.3975 1.512 1.4051) +New LoadShape.2049_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(3.561 3.441 3.301 3.449 3.518 3.451 3.242 3.33 4.923 3.622 4.633 4.711 7.066 7.806 3.538 5.533 6.527 3.674 5.562 4.041 4.877 4.336 5.609 3.743) + ~ qmult=(1.6224 0.4903 1.5987 1.1336 1.6028 1.1343 1.1767 0.9712 2.3843 1.4315 2.2439 0.6713 3.0101 3.7806 1.5072 2.0082 1.9037 1.2076 2.5341 1.5971 2.222 0.6178 1.4057 1.8128) +New LoadShape.2050_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(3.361 2.464 2.189 2.465 1.957 2.465 3.084 2.383 2.945 2.542 3.26 2.494 4.274 8.187 5.109 9.108 3.594 3.048 3.299 5.214 3.664 4.702 4.939 3.449) + ~ qmult=(1.1047 0.8099 0.9325 0.8947 0.4905 1.1939 0.7729 1.1541 0.598 0.6371 1.5789 0.7274 1.0712 2.9715 1.6792 3.3058 1.1813 0.7639 1.1974 1.0587 0.744 2.2773 0.7038 0.4915) +New LoadShape.2051_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(6.696 4.355 4.928 4.166 4.888 4.144 3.975 5.499 6.985 6.976 9.857 4.887 5.031 6.984 10.728 5.416 4.401 8.892 9.465 8.534 10.188 8.414 5.838 4.626) + ~ qmult=(0.9541 1.4314 1.2351 1.5121 1.7741 1.2087 1.8111 1.9959 1.7506 1.4165 2.875 1.7737 1.4674 0.9952 4.5701 1.9657 1.4465 4.3066 4.0321 2.805 2.9715 2.7655 2.3073 0.9393) +New LoadShape.2052_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(68.824 70.224 69.281 69.103 70.239 72.921 69.195 65.686 64.872 66.182 65.069 64.628 56.635 61.086 61.671 68.402 68.326 69.977 76.615 76.063 69.559 69.083 68.703 67.405) + ~ qmult=(33.333 23.0815 14.0681 27.3113 23.0864 23.968 33.5127 9.3597 21.3224 28.1934 18.9785 31.3008 18.615 27.8316 29.8686 27.0342 24.799 33.8914 30.2802 15.4453 31.692 31.4751 31.302 32.6457) +New LoadShape.2053_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(5.28 5.28 5.6 5.28 5.68 5.28 5.56 5.52 5.24 5.16 7.84 4.32 4.2 4.84 4.48 4.2 4.24 4.24 5.16 5.36 5.4 5.52 5.48 5.64) + ~ qmult=(1.54 2.5572 1.8406 2.5572 1.6567 1.0721 1.3935 1.8143 2.5378 2.4991 3.7971 1.8403 1.0526 1.213 1.7706 1.6599 1.2367 0.6042 2.351 2.2834 2.1342 1.3834 1.5983 1.645) +New LoadShape.2054_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(4.809 4.314 3.998 4.199 4.238 4.49 4.684 4.214 8.109 4.918 4.638 6.301 8.077 6.923 7.808 5.975 7.745 6.827 9.619 7.518 8.799 6.521 5.392 4.152) + ~ qmult=(0.6852 2.0894 0.8118 0.5983 0.8606 1.6297 1.9954 1.3851 3.6946 2.3819 1.5244 2.071 3.4408 2.2755 2.8339 2.3615 1.5727 0.9728 1.3706 2.7287 2.5664 3.1583 1.957 1.7687) +New LoadShape.2055_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(3.377 3.166 2.99 3.568 2.817 3.104 3.021 3.994 6.471 5.685 5.713 3.968 8.388 8.377 9.447 6.211 5.247 12.519 10.186 9.132 12.675 6.664 3.875 4.998) + ~ qmult=(0.4812 1.3487 0.8721 1.4102 1.3643 1.0202 0.993 1.9344 2.7566 2.5902 1.6663 1.9218 2.757 4.0572 2.7554 0.885 1.0654 1.7839 4.9333 2.6635 5.0095 2.8389 0.5522 1.6428) +New LoadShape.2056_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(4.521 3.585 4.362 3.635 4.152 4.183 3.495 4.859 4.676 8.323 9.67 7.371 7.773 4.346 5.317 4.724 3.805 3.396 4.467 5.047 3.952 3.827 4.089 3.595) + ~ qmult=(0.6442 1.7363 0.8857 1.4366 1.7687 0.8494 1.4889 1.5971 1.8481 3.5456 4.1194 3.5699 3.5415 1.7176 2.265 2.1523 0.9536 1.1162 2.0352 2.2995 1.914 1.7436 0.5827 0.73) +New LoadShape.2058_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(33.52 33.56 31.76 30.48 32.32 31.36 30.76 30.88 27.04 32.4 35.44 42.2 39.64 41.72 39.04 38.16 39.48 37.08 38.56 44.0 42.16 38.52 37.16 33.48) + ~ qmult=(6.8065 11.0306 4.5256 12.9844 9.4267 14.288 12.1571 12.2046 12.3198 14.7619 8.8821 17.9771 13.029 15.1423 18.9079 5.4375 14.3293 14.655 11.2467 15.9699 19.2087 17.5502 13.4873 9.765) +New LoadShape.2059_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(3.149 2.433 2.269 2.241 2.506 2.306 2.359 2.224 1.912 2.776 5.739 5.93 4.246 3.045 3.63 4.364 4.125 4.239 3.951 3.039 3.054 1.993 3.587 2.507) + ~ qmult=(0.4487 0.9616 0.8235 0.7366 0.7309 0.9114 0.688 0.3169 0.2724 0.5637 2.4448 2.7018 1.5411 1.0008 1.0588 1.5839 1.7572 1.5386 0.8023 0.6171 0.8908 0.7877 1.7373 1.2142) +New LoadShape.2060_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(0.53 0.413 0.438 0.5 1.063 0.484 0.431 0.43 0.402 1.321 2.344 4.364 4.213 1.77 1.6 4.533 2.855 2.749 4.655 4.696 0.826 0.335 0.219 0.576) + ~ qmult=(0.1742 0.1759 0.1731 0.1253 0.4843 0.1757 0.1836 0.2083 0.0573 0.4795 0.9985 1.4344 1.7947 0.4436 0.6324 2.1954 1.2162 0.3917 2.2545 2.1396 0.4001 0.0977 0.0866 0.2454) + +//***************************************************************************************// +// Feeder C +//***************************************************************************************// +New LoadShape.3002_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.533 2.697 2.179 2.832 1.871 2.583 5.346 5.946 7.203 8.879 5.712 5.496 3.147 4.089 6.617 5.623 4.45 5.091 4.565 3.598 2.777 4.636 5.402 5.663) + ~ qmult=(0.6348 0.9789 0.5461 1.0279 0.6791 1.1768 1.0856 2.8798 1.8052 4.0454 0.8139 1.3774 1.1422 1.344 0.9429 1.64 0.9036 1.8478 1.1441 1.422 0.81 1.5238 1.0969 2.2382) +New LoadShape.3004_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(4.452 4.878 3.634 2.424 2.795 3.397 2.747 1.906 2.02 4.094 2.717 3.287 3.242 3.98 2.65 2.306 2.395 4.711 4.671 5.088 3.69 6.302 4.857 2.491) + ~ qmult=(1.4633 2.3625 0.9108 1.1044 1.1907 0.484 1.3304 0.812 0.9203 1.8653 1.3159 1.4003 1.3811 1.6955 0.3776 0.7579 1.0912 2.1464 2.1282 1.0332 1.7871 2.6846 2.3524 0.9845) +New LoadShape.3006_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(5.555 5.829 5.516 5.518 5.473 5.533 5.777 5.417 4.961 5.069 6.429 10.767 10.405 7.678 6.22 6.301 7.39 7.588 4.74 6.541 6.534 3.524 3.567 3.58) + ~ qmult=(2.3664 1.7001 2.5132 2.1809 1.9864 2.5209 2.7979 1.7805 1.447 1.0293 1.6113 3.9079 3.7765 3.7186 2.4583 2.287 3.5791 2.999 0.6754 0.932 0.931 0.7156 0.894 1.0442) +New LoadShape.3007_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(10.358 8.431 7.719 7.578 10.663 7.391 8.709 6.033 11.105 7.838 6.719 8.111 10.144 10.102 9.651 9.705 8.467 7.589 7.79 13.135 14.435 10.091 8.799 7.968) + ~ qmult=(4.7192 1.2014 3.5169 3.4526 4.8582 3.1486 2.1827 2.7487 1.5824 1.9644 2.2084 3.9283 4.3213 1.4395 2.4188 4.7003 3.0731 2.7544 3.3185 6.3616 3.6178 3.9882 1.2538 3.1492) +New LoadShape.3009_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.906 2.678 2.508 2.753 2.415 3.888 3.08 4.223 4.939 7.572 7.401 3.992 2.473 6.512 8.371 7.208 9.853 8.651 4.877 6.594 6.818 9.77 6.569 3.615) + ~ qmult=(1.0547 1.297 0.5093 1.0881 0.7938 1.4112 0.8983 1.799 1.4405 2.7483 3.5845 1.5777 0.8128 2.967 3.566 3.491 1.404 1.7567 1.4225 2.1673 3.1064 4.4513 2.3842 0.906) +New LoadShape.3010_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(3.778 3.997 3.853 3.76 4.057 3.666 3.88 5.063 4.429 4.259 4.516 8.516 6.345 3.823 3.461 4.472 4.331 13.578 5.508 6.705 8.788 4.708 8.786 4.274) + ~ qmult=(1.4932 0.5695 1.7555 1.0967 1.4725 1.5617 0.7879 2.1568 1.7505 1.0674 2.0576 3.3657 2.0855 1.3876 0.4932 2.0375 0.8794 5.7842 1.9991 2.65 2.5632 0.956 3.7428 1.5513) +New LoadShape.3011_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(4.592 7.947 4.154 6.896 4.563 6.813 5.005 6.648 3.629 5.765 3.952 13.157 13.636 8.973 7.492 6.029 8.208 4.056 6.159 5.82 5.301 4.494 4.454 3.009) + ~ qmult=(1.5093 3.3854 1.8926 2.5029 1.3309 2.2393 1.2544 3.2198 1.1928 2.0924 1.6835 2.6716 6.2127 3.2568 2.4625 1.511 3.244 0.5779 2.4342 2.1124 1.0764 1.1263 1.464 1.0921) +New LoadShape.3012_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(4.1 4.234 4.142 4.066 3.732 3.796 3.648 3.793 5.67 4.866 5.468 6.285 6.283 4.8 6.502 4.809 5.932 3.271 3.085 5.698 4.715 6.495 5.269 5.094) + ~ qmult=(0.8325 1.8037 1.5033 0.8256 1.2266 1.1072 0.5198 1.1063 2.4154 2.0729 2.3294 1.8331 1.2758 1.8971 1.3203 1.2052 2.527 1.5842 1.1197 1.4281 0.6719 2.7669 1.7318 2.17) +New LoadShape.3013_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.854 1.777 1.468 1.469 1.429 1.28 1.255 1.416 1.535 1.657 2.656 2.031 2.466 1.714 2.176 2.253 2.459 2.554 4.043 4.668 6.08 5.584 8.635 7.205) + ~ qmult=(0.8324 0.2532 0.5802 0.2093 0.5648 0.5453 0.5346 0.6858 0.5571 0.7059 1.2864 0.509 1.1235 0.4296 0.3101 0.4575 0.3504 1.0094 1.0133 1.9886 1.7733 2.2069 1.7534 2.3682) +New LoadShape.3014_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.879 2.719 2.634 2.662 2.643 2.675 3.02 2.914 2.494 3.766 3.316 4.814 4.283 3.158 2.128 2.199 2.314 2.566 2.472 2.942 4.957 2.66 2.897 4.749) + ~ qmult=(1.1379 0.9869 0.5349 0.3793 0.5367 0.7802 0.8808 0.9578 1.0624 1.824 0.4725 0.686 1.0734 1.2481 0.7724 1.0019 0.5799 0.6431 0.502 1.0678 1.7992 0.8743 1.145 1.7237) +New LoadShape.3016_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(6.502 5.736 5.486 6.15 5.883 5.284 5.271 6.248 7.897 7.608 5.334 10.46 7.128 7.539 5.951 5.557 6.547 7.097 8.839 12.49 8.914 7.261 5.782 6.466) + ~ qmult=(2.5698 2.6134 1.114 1.7937 2.3251 1.5412 0.7511 2.6616 1.6036 1.0841 1.5557 2.124 3.4522 2.4779 2.352 2.1963 2.9829 1.7787 3.4934 1.7797 3.7973 1.8198 2.0986 1.6205) +New LoadShape.3017_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(10.486 8.122 5.581 5.279 5.782 5.039 5.752 7.81 12.7 12.081 8.696 12.34 9.745 14.473 14.134 10.159 10.442 13.524 14.652 11.83 13.365 13.033 9.637 5.778) + ~ qmult=(3.8059 1.6492 1.3987 1.323 2.0986 2.2958 0.8196 2.2779 5.4102 5.5043 2.1794 4.8771 4.7197 6.5941 6.0211 4.6286 3.0456 4.9086 4.2735 2.4022 4.8508 6.3122 3.1675 1.8991) +New LoadShape.3018_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(5.185 6.264 3.893 3.831 4.034 3.868 4.652 4.768 7.326 6.236 9.283 12.537 14.142 6.025 8.881 15.582 11.969 11.229 7.668 9.203 8.139 10.672 6.73 5.611) + ~ qmult=(1.2995 1.827 1.8855 1.8554 1.9538 0.9694 1.1659 1.8844 3.3378 2.2634 1.3228 3.1421 2.0151 0.8585 2.919 4.5447 4.7305 3.6908 3.0306 3.0249 1.6527 3.1127 2.4427 2.5564) +New LoadShape.3019_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(4.37 4.971 5.636 4.175 5.818 4.898 5.434 6.448 7.023 8.14 10.702 11.57 9.579 11.501 9.799 8.77 10.908 16.102 12.568 9.436 11.728 12.471 8.392 10.38) + ~ qmult=(0.8874 1.4499 1.6438 1.5153 2.4785 2.2316 2.1477 1.616 3.4014 1.1599 3.5176 5.2714 4.0806 4.1743 3.2208 4.2475 1.5543 2.2944 5.3539 2.7522 3.8548 5.682 1.7041 3.4117) +New LoadShape.3020_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(6.358 3.793 3.752 3.476 4.326 3.682 4.37 4.674 7.086 21.318 16.491 12.914 21.301 22.166 13.636 11.422 15.068 16.618 20.116 19.759 13.694 8.537 9.359 6.672) + ~ qmult=(1.8544 1.6158 1.2332 1.1425 1.4219 1.4552 1.0952 1.6964 1.0097 6.2177 7.987 5.5013 6.2128 4.501 1.943 5.5319 7.2978 3.3744 6.6118 9.5697 6.6323 1.7335 3.0762 1.6722) +New LoadShape.3021_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(9.359 5.182 5.35 5.509 3.709 3.479 3.584 7.0 5.263 7.252 5.616 12.708 8.891 5.979 6.205 11.714 7.356 7.544 14.262 14.683 12.067 11.37 7.549 6.925) + ~ qmult=(2.3456 1.2987 1.3408 1.1187 1.7964 1.5851 1.5268 2.7666 1.0687 2.8662 2.0383 2.5805 3.227 1.9652 2.8271 5.6733 2.6699 2.9816 3.5744 5.3292 5.1405 3.3163 2.9836 2.2761) +New LoadShape.3023_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(5.404 4.84 4.811 5.74 5.023 5.416 6.093 7.438 10.815 6.966 8.994 13.297 11.225 13.087 9.714 11.371 9.249 11.278 12.818 15.239 12.089 14.89 14.205 6.714) + ~ qmult=(2.4621 2.3441 2.3301 2.4452 1.9852 2.4676 2.7761 2.9397 4.6072 0.9926 3.2644 1.8947 2.2793 3.817 4.4258 4.1271 3.04 3.2894 1.8265 4.4447 5.1499 2.1217 6.472 3.2517) +New LoadShape.3024_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(5.2097 4.9583 4.7033 5.1239 4.6916 5.147 5.419 6.8546 7.3001 7.3281 7.3075 7.1265 8.6071 10.2336 7.9699 5.7444 10.3797 12.9372 19.0315 14.6447 12.3972 10.8583 8.5515 6.8569) + ~ qmult=(1.3057 1.4462 1.1788 2.3345 2.2722 0.7334 2.469 3.123 1.0402 2.6597 3.3294 3.4515 2.5104 1.4582 3.1499 1.8881 4.7291 3.2424 4.7697 6.6723 4.8997 2.2049 1.2185 1.9999) +New LoadShape.3025_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(8.539 6.51 4.492 4.62 4.325 4.397 3.982 4.831 5.704 9.406 7.184 7.214 11.387 13.534 13.568 8.059 9.948 6.783 9.058 10.621 13.386 14.788 8.579 5.63) + ~ qmult=(2.4905 1.8987 1.1258 0.6583 1.5698 2.0033 1.3088 2.2011 2.7626 3.0916 2.8393 3.0732 3.3212 2.7482 1.9333 1.6364 2.9015 2.6808 1.2907 3.491 5.7024 2.1072 1.742 0.8022) +New LoadShape.3026_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(5.364 4.066 3.763 4.429 3.558 3.747 6.43 7.294 7.774 7.533 9.169 7.652 9.036 9.825 5.931 10.816 7.227 7.833 8.63 10.824 9.918 7.121 5.509 6.681) + ~ qmult=(1.7631 1.9693 0.9431 0.6311 1.2914 0.5339 3.1142 2.3974 1.1077 2.7341 1.3065 3.4864 2.6355 1.9951 2.1527 3.555 1.8113 2.843 3.9319 3.157 3.2599 2.5846 1.1187 3.2358) +New LoadShape.3027_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(6.082 5.214 5.6 5.465 6.15 6.387 6.192 6.544 7.228 9.474 18.272 13.191 10.066 10.396 8.624 7.6 6.614 10.767 10.479 9.438 11.759 7.118 6.855 9.63) + ~ qmult=(1.7739 1.5208 0.798 1.7963 2.4306 1.6007 2.2474 2.5864 2.1082 2.3744 7.2216 3.306 4.5862 4.4287 1.7512 2.7584 2.4006 1.5342 3.0564 3.7301 3.865 2.0761 0.9768 3.806) +New LoadShape.3028_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(6.972 5.969 5.926 5.431 4.636 6.011 5.132 5.854 8.976 6.813 9.016 5.61 14.944 7.92 9.758 7.938 7.971 6.509 7.851 7.781 16.888 10.602 9.659 6.779) + ~ qmult=(2.5305 2.5428 2.5245 2.6304 1.6826 2.3757 0.7313 1.7074 1.8227 1.3834 2.9634 1.406 6.3661 3.3739 4.4459 3.3816 2.8931 2.7728 3.8024 3.3147 8.1792 4.1902 4.6781 3.2832) +New LoadShape.3029_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.775 2.544 2.343 2.265 2.118 2.851 2.211 4.431 2.503 5.699 6.531 6.783 3.414 4.19 6.052 8.843 6.595 5.566 8.326 5.296 6.577 6.488 7.294 2.725) + ~ qmult=(0.5635 0.6376 1.1348 0.8221 0.6962 1.299 0.8025 0.8998 0.9085 2.0685 3.1631 1.3773 0.9958 0.597 2.9311 2.2163 2.6065 1.1302 1.1864 2.565 3.1854 2.7639 2.1274 1.1608) +New LoadShape.3031_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(3.75 3.617 3.401 3.129 3.119 3.271 4.602 3.995 3.289 3.532 4.047 3.784 5.191 6.013 6.903 7.089 7.58 6.006 8.434 8.136 6.327 7.304 4.684 2.732) + ~ qmult=(1.5975 1.1889 1.3442 0.6354 1.0252 1.0751 1.9604 0.5693 1.5929 1.3959 1.3302 1.2437 2.0516 1.221 2.0134 3.0199 2.9958 1.2196 1.2018 3.9404 1.8454 2.4007 1.1739 0.7968) +New LoadShape.3032_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(4.39 3.602 3.765 4.057 3.817 4.316 4.468 7.115 9.374 9.381 6.521 7.008 11.505 7.188 10.418 7.502 6.777 13.185 12.19 8.666 6.52 6.246 5.655 4.841) + ~ qmult=(0.6255 1.1839 1.3665 0.8238 0.5439 1.7058 1.3032 2.3386 4.2709 4.2741 2.7779 2.044 4.1758 3.0621 4.7466 3.1958 2.887 4.7855 4.4244 1.2348 3.1578 2.4686 2.0525 1.5912) +New LoadShape.3033_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(7.356 6.227 6.458 6.66 9.811 11.733 6.648 8.366 8.973 8.548 9.766 4.068 7.475 7.574 6.525 4.919 4.672 8.952 12.563 10.083 11.014 11.492 7.991 5.023) + ~ qmult=(2.6699 1.8162 3.1278 2.4173 3.5609 3.4221 2.4129 2.0967 3.5464 3.8946 3.8598 0.5797 3.4057 2.2091 1.9031 0.9988 1.9903 2.611 2.551 4.2953 4.6919 2.8802 2.9003 1.02) +New LoadShape.3034_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(7.72 4.706 4.854 4.617 4.966 4.763 4.867 4.786 4.295 5.443 4.661 9.39 10.531 8.359 10.264 9.998 4.944 5.988 10.774 10.882 10.623 14.991 10.406 4.515) + ~ qmult=(1.1 1.708 1.5954 1.3466 1.4484 2.029 1.5997 1.1995 1.5589 1.9755 2.2574 3.0863 4.7981 3.3037 3.7253 2.9161 1.954 1.9682 1.5352 3.5767 2.1571 6.8301 4.7411 1.7844) +New LoadShape.3035_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(6.348 5.972 5.442 5.794 5.71 5.901 6.502 6.278 6.644 10.333 8.596 7.192 11.851 12.049 10.891 8.97 7.088 6.905 11.736 15.406 12.648 7.018 6.876 6.942) + ~ qmult=(0.9045 1.2127 2.3183 1.6899 2.6016 1.4789 2.5698 3.0406 2.6259 4.4018 3.6619 2.3639 1.6887 4.7621 4.3044 3.8212 2.8014 1.7306 3.423 6.0888 4.1572 2.9897 0.9798 2.9573) +New LoadShape.3036_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.66 2.359 3.076 2.73 2.348 2.902 2.372 2.746 2.98 4.158 2.107 4.132 1.7 2.372 2.58 6.864 4.732 3.546 3.161 4.347 2.8 4.975 5.713 3.317) + ~ qmult=(0.8743 0.8562 1.4015 1.2438 0.3346 0.5893 1.0105 0.9026 0.8692 1.2127 0.4278 1.3581 0.3452 0.8609 0.3676 2.4913 1.186 1.4015 1.2493 0.8827 0.7017 1.2469 2.2579 0.6735) +New LoadShape.3037_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(4.47 3.784 3.981 3.65 3.653 3.476 3.719 4.551 4.379 5.449 5.004 9.643 10.664 12.185 10.998 11.179 6.951 8.923 8.727 7.459 8.51 10.925 7.828 4.497) + ~ qmult=(1.1203 1.4955 1.3085 1.3248 1.2007 1.3738 1.5843 0.9241 1.4393 1.9777 1.9777 3.4999 4.8587 2.4743 5.3266 4.0574 0.9905 2.2363 4.2267 2.1755 1.728 2.2184 3.5665 1.1271) +New LoadShape.3038_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(5.453 4.977 4.91 4.571 4.39 3.151 3.65 4.516 5.167 7.814 4.643 4.258 5.269 10.338 7.341 6.287 3.649 3.748 5.601 5.365 9.277 8.755 6.155 5.203) + ~ qmult=(1.7923 1.6359 2.2371 1.1456 1.8701 0.7897 1.1997 2.0576 0.7363 1.9584 1.1636 1.5454 1.5368 5.0069 1.4907 3.0449 0.741 1.4813 1.1373 1.7634 3.952 1.2475 1.7952 2.5199) +New LoadShape.3039_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(8.078 3.4 4.218 2.726 3.294 3.061 2.869 3.439 4.135 6.611 6.71 7.38 7.067 10.188 7.829 9.312 10.445 9.394 7.887 11.663 9.638 8.463 9.028 6.631) + ~ qmult=(2.6551 1.3438 1.2303 1.3203 0.6689 0.6216 0.719 1.5669 1.7615 1.3424 0.9561 2.9168 2.3228 4.6418 3.3351 3.3798 2.1209 1.3386 1.6015 1.6619 3.1679 1.7185 3.5681 0.9449) +New LoadShape.3041_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(5.689 4.792 4.84 4.962 4.594 4.638 5.233 5.663 9.469 8.009 9.119 7.753 12.689 8.829 7.776 6.181 6.968 7.357 7.611 12.272 7.263 7.297 6.465 5.406) + ~ qmult=(1.6593 1.201 2.3441 1.6309 2.0931 1.6834 1.72 2.5801 1.9228 3.649 3.6041 2.5483 5.7813 2.5751 3.0733 0.8807 2.7539 2.1458 3.6862 5.5913 1.4748 2.884 3.1311 1.9621) +New LoadShape.3042_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(5.359 5.537 4.478 6.186 4.375 5.081 5.242 5.151 6.445 10.872 7.79 6.879 5.15 7.129 5.368 6.683 6.132 8.659 6.619 14.595 11.315 11.687 8.219 8.114) + ~ qmult=(1.0882 1.1243 0.9093 0.8815 2.1189 0.724 0.7469 0.734 3.1215 2.7248 2.5604 2.4967 2.4943 2.8176 2.4457 0.9523 2.6122 3.6887 3.2057 3.6579 5.4801 2.929 2.3972 3.6968) +New LoadShape.3043_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(6.051 5.405 4.946 5.678 5.179 5.065 5.306 6.227 6.476 5.59 5.321 7.761 4.841 3.492 6.69 6.13 6.728 7.459 7.924 7.137 8.869 6.863 6.718 6.458) + ~ qmult=(0.8622 2.4626 1.4426 2.4188 1.0516 1.8383 2.4175 1.2644 2.3505 1.1351 0.7582 3.0673 1.9133 1.2674 3.2401 2.4227 3.2585 1.8694 2.3112 1.017 1.8009 2.4909 1.9594 2.1226) +New LoadShape.3044_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(3.866 3.806 2.949 3.322 2.976 3.288 3.432 4.539 4.705 3.408 3.96 5.289 4.352 6.103 8.661 4.203 6.362 5.064 9.921 9.305 8.898 4.78 5.454 3.966) + ~ qmult=(1.6469 0.5423 0.5988 1.5135 0.7459 1.5925 1.5637 2.1983 2.0043 0.692 1.5651 1.9197 1.8539 0.8696 3.6896 2.0356 0.9065 1.477 4.5201 4.2395 1.2679 1.5711 1.5908 1.3036) +New LoadShape.3045_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(4.334 3.84 4.275 3.444 3.431 3.477 3.67 3.693 4.213 5.037 5.622 6.103 7.465 6.138 5.865 4.583 5.328 6.614 6.279 5.703 7.898 5.957 9.306 8.726) + ~ qmult=(2.0991 1.8598 0.8681 0.6993 1.356 1.0141 1.332 1.5732 1.3847 0.7177 1.409 1.5296 1.0637 2.9728 1.7106 0.9306 1.554 1.343 3.0411 2.5984 3.3645 1.493 3.0587 3.7173) +New LoadShape.3047_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(8.965 8.764 8.67 8.707 8.758 9.705 8.681 10.277 10.731 9.291 16.478 17.521 17.454 10.891 10.013 9.762 9.297 9.078 10.064 12.857 9.798 9.773 10.169 9.045) + ~ qmult=(4.3419 3.993 2.1729 3.4412 1.2479 3.1899 1.7628 2.0868 3.8948 4.4998 4.1298 7.4639 5.0907 4.6395 4.8495 3.8582 1.8878 2.9838 2.5223 4.2259 4.4641 3.2122 4.9251 3.8532) +New LoadShape.3048_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.666 2.173 1.61 1.662 1.626 1.723 1.381 1.838 1.975 3.47 6.317 4.37 3.985 6.121 7.39 5.604 3.53 2.867 2.224 2.58 2.886 2.794 4.032 3.695) + ~ qmult=(1.2147 0.7142 0.4035 0.8049 0.7408 0.3499 0.4028 0.8374 0.7806 0.8697 3.0595 1.2746 1.8156 1.7853 3.1481 1.6345 1.3951 1.2213 0.731 1.1755 0.9486 0.3981 1.5935 0.5265) +New LoadShape.3049_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.4628 1.734 2.272 1.817 1.7485 2.095 1.676 2.1605 2.3468 2.5099 3.6044 9.0397 7.7484 6.5772 3.3891 3.0815 3.2523 4.6696 8.5718 9.7029 9.174 10.754 3.315 2.511) + ~ qmult=(0.8939 0.7387 0.898 0.7181 0.6346 0.828 0.6624 0.6301 0.3344 0.992 0.5136 3.281 1.9419 0.9372 1.1139 0.4391 1.3855 1.362 1.2214 2.83 3.0153 4.8997 1.6055 0.8253) +New LoadShape.3050_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.229 2.317 2.571 1.856 1.839 1.879 2.248 2.604 3.209 3.204 2.458 2.046 1.918 2.39 3.374 2.432 2.53 2.82 4.111 2.976 2.838 5.606 2.342 4.092) + ~ qmult=(0.5586 0.9157 0.845 0.61 0.4609 0.6176 1.0242 1.1864 0.8043 1.1629 1.1905 0.4155 0.8739 1.1575 1.2246 0.3465 0.7379 1.1145 1.873 0.9782 0.5763 2.7151 0.85 0.5831) +New LoadShape.3051_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.773 2.329 1.813 2.123 2.026 2.128 2.903 4.034 3.909 3.612 3.669 4.849 3.989 3.297 4.012 3.356 5.09 4.404 4.862 3.536 2.708 1.725 2.141 1.662) + ~ qmult=(0.8088 0.5837 0.826 0.5321 0.4114 1.0306 0.7276 1.7185 1.6652 1.5387 1.3317 1.9164 0.5684 1.3031 1.5856 1.4297 2.1683 0.8943 2.2152 1.2834 0.6787 0.6818 0.5366 0.3375) +New LoadShape.3052_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.227 2.181 1.952 1.875 2.078 2.024 1.892 1.985 2.177 1.83 2.128 1.957 2.173 3.121 3.262 2.163 2.262 3.378 3.62 2.132 10.371 6.137 2.236 2.175) + ~ qmult=(0.8802 0.6361 0.8315 0.7987 0.7542 0.7999 0.4742 0.8456 0.8604 0.6015 0.9695 0.8337 0.7142 0.4447 0.4648 0.5421 0.5669 1.439 1.3139 0.7738 2.1059 2.9723 1.0188 0.8596) +New LoadShape.3054_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(0.672 0.771 0.814 0.49 0.717 1.565 0.527 0.856 0.753 0.579 0.657 0.617 0.612 0.553 0.461 0.64 1.3 0.612 0.561 0.587 0.745 0.583 0.654 0.614) + ~ qmult=(0.3255 0.3513 0.2374 0.1228 0.2834 0.3922 0.107 0.2814 0.3431 0.1176 0.2993 0.2239 0.0872 0.2007 0.21 0.0912 0.4273 0.1785 0.239 0.1192 0.3394 0.1461 0.215 0.2229) +New LoadShape.3056_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(3.229 3.005 3.136 3.104 3.033 2.397 2.508 3.473 3.486 3.456 4.795 2.732 2.628 4.186 2.708 2.515 6.096 7.525 6.735 4.592 4.094 6.079 3.504 5.526) + ~ qmult=(1.4712 1.3691 1.5188 1.4142 0.8846 1.1609 0.9912 0.8704 1.1458 0.7018 0.9737 1.1638 1.1974 1.5193 1.1536 0.9128 1.2378 2.1948 2.6618 1.6667 1.744 0.8662 0.4993 2.3541) +New LoadShape.3057_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.984 2.554 2.247 2.302 2.513 2.205 4.476 7.434 6.979 2.281 2.054 2.698 4.094 5.623 3.725 3.612 2.724 4.259 5.424 6.635 5.48 3.876 2.627 2.622) + ~ qmult=(1.4452 0.3639 1.0238 0.9098 0.9932 0.7247 2.0393 3.6005 1.7491 0.5717 0.9948 0.8868 1.1941 1.1418 0.7564 1.0535 0.6827 1.0674 2.627 3.2135 1.1128 1.1305 0.7662 0.8618) +New LoadShape.3058_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.714 1.88 1.903 2.565 2.052 2.256 2.618 5.621 3.938 3.298 4.824 5.175 5.353 5.888 3.385 4.466 6.148 5.512 5.767 4.423 5.879 5.411 5.096 3.337) + ~ qmult=(1.2365 0.8009 0.6907 0.931 0.811 0.4581 1.1928 2.2216 1.5564 0.9619 1.209 2.2045 2.1156 1.9353 0.4823 1.7651 1.7932 2.0006 1.8955 1.1085 1.4734 2.4653 1.4863 1.0968) +New LoadShape.3059_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.991 3.036 2.916 2.831 2.471 2.564 3.184 3.341 4.019 4.442 3.434 4.433 3.369 3.421 3.571 3.338 2.374 2.241 4.031 3.547 3.235 4.019 2.16 1.736) + ~ qmult=(1.4486 0.9979 1.2422 1.206 0.8122 0.3654 1.3564 1.4233 1.7121 0.902 1.1287 1.609 1.4352 1.4573 1.4113 0.9736 1.0113 1.0854 1.1757 0.5054 0.8108 1.0073 0.71 0.8408) +New LoadShape.3060_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.877 2.151 2.236 2.124 2.041 1.988 1.908 2.624 2.824 2.905 3.286 2.662 2.932 2.457 3.142 2.997 3.129 2.316 2.631 3.287 2.84 2.906 2.632 2.659) + ~ qmult=(0.41 0.98 1.0188 0.9677 0.5953 0.5798 0.8693 0.5328 1.2867 0.5899 1.2987 1.0521 1.249 0.4989 1.1404 1.0878 0.9126 0.7612 0.9549 1.0804 1.3755 0.7283 0.375 1.2878) +New LoadShape.3061_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(0.977 1.125 1.552 1.19 1.277 3.368 4.726 1.457 5.058 1.504 5.018 4.128 3.83 1.673 3.633 1.86 2.251 3.432 2.518 2.169 2.18 2.687 1.707 1.847) + ~ qmult=(0.2449 0.2284 0.4527 0.5763 0.5047 0.4799 0.9597 0.5758 1.8358 0.6852 2.1377 1.0346 1.5137 0.5499 1.6552 0.6114 0.9589 0.6969 1.1472 0.8572 1.0558 0.3829 0.7777 0.8945) +New LoadShape.3062_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(0.866 0.77 0.836 0.816 0.804 0.965 0.914 0.814 1.716 2.154 2.655 2.91 5.387 1.823 1.822 2.212 1.856 1.977 2.643 2.536 2.594 2.579 1.78 1.248) + ~ qmult=(0.2526 0.2795 0.3034 0.238 0.2345 0.4674 0.3612 0.2954 0.8311 0.3069 0.6654 0.5909 2.2949 0.5992 0.7201 0.6452 0.4652 0.2817 1.2801 0.9204 0.6501 1.2491 0.5851 0.5686) +New LoadShape.3063_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(4.921 4.214 3.858 4.503 4.54 4.343 5.161 5.839 6.302 7.191 4.397 3.642 3.213 3.192 3.77 4.256 4.187 4.365 4.754 7.995 6.475 6.532 5.639 7.421) + ~ qmult=(0.7012 1.6655 1.1253 1.4801 1.1378 1.2667 1.8732 1.1857 2.8713 1.0247 1.8731 0.519 0.6524 1.546 1.3683 1.9391 1.9077 0.622 2.166 3.4059 2.9501 1.9052 1.6447 2.6935) +New LoadShape.3064_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(3.396 3.431 2.582 2.828 2.763 2.877 2.881 5.331 6.123 5.67 5.751 5.744 4.766 4.594 4.881 5.882 7.299 8.549 7.883 8.479 11.062 13.941 7.108 3.888) + ~ qmult=(1.4467 1.2453 1.2505 0.9295 1.2589 0.721 0.9469 1.9349 1.7859 1.8636 1.1678 1.6753 0.6791 2.0931 1.2233 1.4742 3.5351 3.6419 1.1233 1.7217 5.04 4.0661 3.4426 1.134) +New LoadShape.3065_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(6.499 3.427 3.08 2.547 2.882 2.906 4.753 5.597 3.645 2.889 5.716 7.331 6.773 7.798 4.694 4.312 4.245 6.472 5.472 9.427 9.742 9.037 4.086 4.085) + ~ qmult=(2.5686 1.5614 1.0123 0.8372 0.4107 1.238 0.9651 2.0314 1.5528 0.5866 2.7684 2.6608 3.0859 1.1112 1.1764 0.6144 0.6049 2.9487 2.4931 4.5657 1.3882 2.2649 0.5822 1.3427) +New LoadShape.3066_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.739 2.417 4.937 1.92 1.824 1.951 1.87 1.94 2.207 3.299 2.911 1.813 1.701 1.438 1.882 4.318 2.102 1.978 3.161 3.751 4.472 3.363 5.503 4.158) + ~ qmult=(0.6865 0.8773 1.44 0.9299 0.662 0.9449 0.6146 0.8264 0.8723 1.1974 0.5911 0.5288 0.4263 0.5219 0.3822 1.4193 0.7629 0.6501 0.7922 1.4825 2.1659 0.8428 2.1749 0.8443) +New LoadShape.3067_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(7.904 9.615 7.432 7.395 6.094 6.205 5.917 7.193 8.332 9.094 10.884 9.216 6.577 13.915 11.932 11.958 8.884 6.161 7.136 7.427 10.058 9.403 12.987 6.986) + ~ qmult=(3.3671 2.4097 1.5091 1.0537 2.7765 0.8842 2.6959 2.098 2.7386 4.4044 4.6366 1.3132 2.9966 5.4996 4.3307 4.3402 4.0477 1.797 3.0399 1.5081 3.9752 4.2841 5.1328 2.0376) +New LoadShape.3070_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(0.696 0.7 0.507 1.327 1.146 0.618 0.559 0.65 0.515 1.396 1.959 1.316 0.643 1.371 0.776 1.037 1.204 1.393 1.639 0.79 2.159 1.36 0.898 1.041) + ~ qmult=(0.0992 0.2541 0.0722 0.5245 0.5221 0.1802 0.1837 0.2569 0.1502 0.4072 0.3978 0.5606 0.1875 0.4976 0.2817 0.3764 0.4759 0.6747 0.6982 0.1126 0.3076 0.6587 0.4091 0.3422) +New LoadShape.3071_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(3.532 2.1925 2.0753 2.1171 2.1581 2.1005 2.5253 2.1791 2.5866 3.5518 4.6199 6.1729 7.403 4.631 5.989 5.23 3.711 4.121 4.946 4.164 4.1 3.754 2.529 3.278) + ~ qmult=(1.6092 0.7206 0.5201 0.9019 1.0452 0.8948 0.3598 0.7162 0.6483 0.8902 1.1579 2.4397 2.6869 2.2429 1.501 1.062 0.5288 0.8368 1.0043 1.2145 1.868 1.5992 1.1522 1.1898) +New LoadShape.3072_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.645 2.531 2.258 1.784 1.937 1.923 1.724 1.917 1.959 3.55 4.504 4.137 4.617 8.685 2.833 2.745 2.053 3.788 8.685 6.841 4.728 4.051 2.549 2.26) + ~ qmult=(1.0454 0.7382 1.0288 0.5203 0.4855 0.3905 0.2457 0.8166 0.8925 1.5123 1.6347 1.0368 1.6757 1.7636 1.0282 1.1694 0.4169 0.5398 3.957 2.7037 2.1541 1.1815 0.5176 0.6592) +New LoadShape.3073_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.27 2.628 2.781 2.556 2.336 3.111 2.365 2.207 2.341 2.374 1.906 1.783 1.779 2.267 2.486 2.01 1.862 1.793 2.927 2.387 2.712 2.442 2.44 2.752) + ~ qmult=(0.4609 0.6586 0.5647 1.0889 0.9951 0.4433 0.8584 0.4482 0.7694 1.1498 0.8684 0.2541 0.8616 0.9657 0.5048 0.7295 0.4667 0.2555 0.4171 1.0875 0.5507 1.0403 0.7117 0.6897) +New LoadShape.3074_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(5.119 4.701 3.984 4.21 3.799 4.162 4.375 4.376 3.749 4.032 4.787 6.306 4.858 3.108 2.909 2.851 3.06 6.712 3.996 4.6 3.063 4.732 4.54 6.321) + ~ qmult=(2.0232 2.1418 1.5746 1.3838 0.5413 1.8963 0.8884 0.8886 0.7613 1.0105 0.972 1.8393 1.92 0.4429 0.5907 1.3808 1.1106 3.0581 1.3134 1.9596 1.4835 2.156 1.6478 1.5842) +New LoadShape.3077_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(36.218 33.626 33.454 32.871 32.658 34.201 34.427 32.614 31.335 31.991 33.803 28.733 28.508 27.119 26.727 26.419 23.358 23.482 24.62 25.04 23.459 24.785 27.565 26.718) + ~ qmult=(17.5412 14.3246 14.2513 6.6747 13.9122 8.5716 8.6282 6.6226 11.3731 11.6112 6.864 4.0942 12.1444 8.9136 12.9445 12.0369 6.8128 9.2807 6.1704 6.2756 9.2716 9.7957 12.559 7.7927) +New LoadShape.3078_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(3.652 3.666 3.691 3.754 4.071 3.75 3.696 3.601 3.745 3.21 3.156 3.168 3.226 3.12 3.562 4.04 3.787 3.234 3.244 3.681 3.899 3.683 4.011 3.542) + ~ qmult=(0.7416 1.3306 1.4588 1.4837 0.5801 0.9398 1.3415 0.7312 0.5336 0.9363 0.791 0.924 0.4597 1.0255 1.4078 1.721 1.7254 1.2782 0.813 1.336 0.7917 1.4556 0.8145 0.5047) +New LoadShape.3081_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(1.923 2.062 1.95 1.808 1.919 1.892 2.064 1.927 1.772 1.693 1.6 1.611 1.838 1.526 1.635 1.512 1.707 1.508 1.897 1.661 1.865 1.68 1.844 1.85) + ~ qmult=(0.6321 0.5168 0.8884 0.5943 0.5597 0.862 0.9404 0.6334 0.8582 0.7714 0.401 0.5847 0.7264 0.6501 0.5934 0.6889 0.7272 0.4957 0.4754 0.2367 0.9033 0.5522 0.3744 0.7881) +New LoadShape.3083_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(3.449 4.094 4.46 3.162 3.57 3.456 3.633 3.719 6.132 4.652 6.568 6.108 6.417 6.464 4.168 4.954 7.222 3.75 5.956 4.294 5.314 6.069 4.602 3.298) + ~ qmult=(1.1336 1.8653 1.3008 1.5314 0.5087 1.008 1.4359 0.7552 2.0155 1.529 2.3839 1.2403 2.9237 2.7537 1.7756 1.7981 2.3738 0.7615 2.7136 1.9564 1.0791 2.3986 1.8188 1.084) +New LoadShape.3084_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.304 1.72 1.543 1.268 1.193 1.215 1.527 1.407 2.358 2.144 2.646 2.119 2.857 2.282 3.202 2.317 2.581 2.171 2.17 2.821 2.956 3.334 2.439 2.232) + ~ qmult=(0.4678 0.6798 0.7473 0.3178 0.433 0.2467 0.6957 0.5107 0.4788 0.7047 1.1272 0.6965 1.3017 1.0397 1.1622 0.987 0.7528 1.0515 0.5439 1.0239 0.8622 0.677 0.6113 1.081) +New LoadShape.3085_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(6.726 7.026 6.976 7.119 6.861 7.024 7.275 7.787 7.978 7.844 9.374 12.154 12.777 12.347 10.338 8.923 9.231 11.37 14.521 12.307 9.765 8.194 7.869 7.285) + ~ qmult=(2.2107 2.5501 2.9718 3.2435 3.3229 2.7761 2.6405 3.7714 2.8956 3.5738 3.4023 5.8865 5.0498 4.4814 4.0858 2.9328 4.4708 2.3088 3.6393 5.9606 3.8594 2.974 1.9722 2.8792) +New LoadShape.3086_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(4.484 1.758 1.707 1.993 1.856 2.208 2.322 5.06 5.201 6.41 7.325 3.78 4.658 3.858 1.757 1.533 2.119 3.583 6.898 5.11 3.796 4.328 2.455 3.216) + ~ qmult=(1.6275 0.2505 0.3466 0.5813 0.7907 1.006 0.7632 1.0275 2.519 2.3265 1.0438 1.494 1.841 1.4003 0.5775 0.4471 0.5311 1.3005 2.9385 2.4749 1.6171 1.2623 0.9703 1.4653) +New LoadShape.3087_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(3.583 4.268 3.057 3.13 3.222 2.985 3.542 3.991 5.564 8.977 7.38 9.94 8.656 6.669 4.205 6.077 3.35 3.416 5.964 12.081 12.306 7.519 3.601 6.409) + ~ qmult=(0.5105 2.0671 1.4806 1.5159 0.9397 0.7481 1.1642 1.0002 2.3703 2.6183 3.5743 3.9285 3.1417 2.6358 0.5992 1.9974 1.6225 1.4552 1.211 3.5236 4.4665 3.2031 0.7312 1.8693) +New LoadShape.3088_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(5.729 4.94 4.16 3.992 4.343 3.91 4.223 8.019 5.776 4.99 9.858 8.34 9.149 7.56 4.731 7.249 6.577 4.737 6.765 10.94 10.212 5.865 5.26 6.6) + ~ qmult=(2.7747 1.4408 2.0148 1.8188 2.1034 1.4191 1.799 2.0098 2.2828 1.4554 2.8752 1.1884 4.1684 1.8947 2.0154 2.865 2.9966 1.3816 2.2235 2.2215 2.9785 1.9277 1.9091 2.1693) +New LoadShape.3089_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(0.817 1.198 0.681 0.722 0.752 0.707 1.178 0.868 1.066 6.85 7.254 2.628 8.189 7.282 3.428 3.144 1.869 1.763 5.132 4.387 3.096 1.898 1.596 1.31) + ~ qmult=(0.348 0.1707 0.2238 0.3076 0.2472 0.1772 0.5705 0.1237 0.4541 2.4862 1.473 0.5336 2.0524 3.1021 0.4885 1.2426 0.6143 0.5795 0.7313 1.9988 0.6287 0.3854 0.2274 0.3283) +New LoadShape.3090_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.599 2.964 3.747 3.144 2.507 2.493 3.478 5.891 4.769 2.961 2.753 2.563 3.988 5.696 11.249 5.868 5.03 5.134 6.604 3.883 4.038 3.13 3.077 2.577) + ~ qmult=(1.0272 0.6019 0.5339 1.0334 0.9099 0.9853 1.0144 1.1962 2.3097 1.4341 0.9049 0.5204 1.3108 0.8116 4.4459 2.6735 2.2917 1.6875 1.9262 1.4093 0.82 1.3334 1.0114 0.5233) +New LoadShape.3091_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.052 2.516 1.983 2.037 2.12 2.14 1.985 1.939 5.013 5.327 7.061 2.674 1.934 4.15 2.395 1.92 2.124 4.153 5.096 5.792 4.074 2.789 3.785 1.577) + ~ qmult=(0.7448 0.7338 0.4027 0.6695 0.9031 0.9116 0.579 0.826 1.4621 1.9334 1.0061 1.2951 0.7644 0.5913 0.4863 0.6311 0.4313 1.6414 0.7261 1.1761 0.5805 1.1881 1.6124 0.5183) +New LoadShape.3093_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.854 3.091 2.841 4.131 4.144 5.005 5.032 5.731 7.918 8.794 14.558 7.017 3.532 5.572 6.492 5.396 6.83 11.207 6.232 12.378 12.282 5.067 6.196 4.932) + ~ qmult=(1.2158 0.9015 0.712 0.8388 1.0386 2.424 1.8264 1.1637 3.1294 1.2531 6.2017 1.7586 1.3959 0.794 2.9578 2.1326 1.7118 4.7742 2.463 3.6103 2.494 0.722 0.8829 2.3887) +New LoadShape.3094_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(3.893 2.803 1.92 1.649 2.719 3.475 5.537 4.25 4.942 3.48 5.185 6.363 15.582 12.289 10.86 6.726 11.283 8.81 4.409 5.144 7.493 6.32 5.03 4.363) + ~ qmult=(1.1355 0.8175 0.7588 0.7025 1.1583 1.683 2.0097 1.5425 2.3935 0.8722 1.7042 1.5947 2.2203 2.4954 1.5475 1.6857 2.8278 3.4819 1.105 2.1913 3.629 1.5839 2.2917 2.1131) +New LoadShape.3095_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(4.144 3.516 3.877 2.834 3.545 4.111 4.952 5.115 6.16 5.571 5.042 6.466 8.528 18.199 9.444 9.509 6.108 7.308 9.942 16.376 15.282 10.962 9.192 6.413) + ~ qmult=(1.7653 1.2761 1.5323 1.2912 1.1652 1.991 2.2562 2.0216 2.9834 2.6982 1.4706 2.1253 1.2152 3.6955 4.0231 1.9309 2.2169 3.5394 4.5297 7.9313 6.9627 3.1972 4.188 3.106) +New LoadShape.3096_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.877 3.37 3.933 3.739 3.945 4.449 4.132 4.957 5.867 4.508 6.807 7.069 14.015 13.352 5.454 5.721 5.339 5.431 5.216 4.548 5.878 5.231 9.006 2.96) + ~ qmult=(1.3934 0.4802 1.7919 1.8109 0.9887 2.027 0.839 1.4458 2.6731 1.9204 3.2968 3.4237 2.8459 3.8943 2.6415 0.8152 1.0841 2.6304 1.7144 1.1398 1.1936 2.0674 3.2687 1.1699) +New LoadShape.3097_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(5.053 5.127 4.75 4.828 4.586 5.006 4.288 5.114 10.417 9.789 3.789 4.39 5.965 8.665 8.364 7.361 8.051 7.657 6.765 11.568 10.916 10.948 5.159 4.896) + ~ qmult=(1.0261 2.0263 1.1905 1.9081 1.3376 2.1325 1.6947 0.7287 4.4376 3.8689 0.7694 2.1262 0.85 4.1967 2.0962 1.4947 3.182 2.5167 3.2764 4.9279 2.2166 4.3269 1.8725 1.428) +New LoadShape.3098_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(8.393 8.921 7.064 7.904 7.883 7.784 7.556 7.733 7.915 9.937 11.307 8.548 5.681 12.247 12.954 12.562 9.058 9.952 13.161 14.962 16.502 15.602 9.319 6.984) + ~ qmult=(2.7586 2.9322 2.7919 2.5979 2.2992 1.1092 2.2038 2.8067 3.8334 2.8983 4.4688 2.4932 2.0619 2.4869 5.5184 1.79 3.2876 3.6121 4.3258 3.0382 7.0298 7.5564 2.3356 2.7603) +New LoadShape.3099_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.779 2.774 2.556 2.939 2.525 2.459 2.541 2.694 2.52 4.656 5.243 3.379 7.072 4.896 2.928 5.009 2.813 2.372 9.427 9.713 9.778 5.042 3.713 3.554) + ~ qmult=(0.5643 0.8091 0.6406 1.0667 0.9979 0.4993 0.516 0.3839 1.1481 1.358 1.5292 0.8469 3.0127 0.9942 0.9624 2.1338 0.9246 1.0105 2.3626 3.5253 4.7357 1.83 1.2204 1.0366) +New LoadShape.3101_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.599 4.535 3.769 1.334 1.38 1.646 1.504 1.44 2.34 1.793 1.719 7.04 4.045 3.79 3.609 6.787 7.614 6.39 5.166 5.901 10.001 7.983 4.517 3.492) + ~ qmult=(1.1841 1.7923 0.7653 0.3891 0.1966 0.5974 0.5459 0.3609 0.9248 0.8684 0.565 2.5552 1.3295 1.1054 1.6443 2.6824 1.5461 2.9114 1.049 1.9396 2.5065 3.8663 1.9242 1.6913) +New LoadShape.3102_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(3.855 3.301 3.225 3.142 3.843 3.083 3.248 3.466 6.143 3.694 9.318 10.914 15.834 13.026 9.046 10.825 11.405 7.975 7.037 8.407 11.62 5.893 5.354 4.214) + ~ qmult=(1.7564 0.4704 1.4694 1.5217 1.1209 1.4047 0.6595 0.4939 1.7917 0.7501 4.5129 2.7353 5.747 1.8561 3.8536 2.713 4.8585 3.3973 3.4082 3.3227 3.8193 1.1966 2.2808 1.5295) +New LoadShape.3103_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(8.311 6.517 5.876 6.206 6.273 5.502 5.859 10.121 8.505 11.626 8.146 8.901 8.047 6.508 6.331 12.347 13.563 17.691 10.503 13.608 13.628 7.093 9.651 13.38) + ~ qmult=(2.0829 1.9008 1.1932 1.8101 1.2738 1.3789 2.4959 1.4422 3.875 2.9138 3.2195 2.9256 2.9207 2.7724 1.5867 4.4814 4.4579 3.5923 1.4966 3.4105 4.4793 3.4353 4.6742 1.9065) +New LoadShape.3104_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(6.27 5.456 6.341 8.829 9.393 9.807 13.163 19.839 41.034 45.508 37.493 38.877 32.718 25.44 13.056 6.039 6.191 4.76 4.138 6.337 5.669 10.078 9.537 6.089) + ~ qmult=(2.671 1.5913 2.7013 2.902 3.0873 3.876 4.3265 7.8409 8.3323 9.2408 14.8182 17.7129 10.7539 11.5908 2.6511 2.1919 2.6374 1.5645 1.8853 1.8483 2.2405 3.6578 3.7693 2.0014) +New LoadShape.3105_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.366 5.37 3.906 2.513 2.114 2.581 3.429 3.728 4.646 5.499 5.162 2.659 8.206 7.767 8.772 5.3 7.983 6.153 3.167 2.932 5.493 3.327 3.838 3.262) + ~ qmult=(1.0079 2.2876 0.9789 0.9121 0.6166 0.5241 1.4607 0.757 1.6863 1.9959 1.0482 0.6664 2.6972 1.9466 3.4669 0.7552 3.4007 2.98 1.5338 1.42 1.6021 0.9704 1.2615 0.6624) +New LoadShape.3106_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(11.687 10.477 9.863 10.764 9.883 10.205 11.304 18.183 27.886 23.676 16.394 26.757 28.892 13.111 8.798 12.318 10.24 8.414 10.188 11.471 10.346 15.197 16.713 12.063) + ~ qmult=(2.3731 4.1408 4.4937 1.5338 3.5871 1.4541 2.833 5.3034 11.0213 6.9055 7.94 11.3984 13.993 5.1818 3.1932 5.9659 3.3657 1.1989 4.6418 5.5557 2.1008 4.4325 3.3937 3.5184) +New LoadShape.3108_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(1.82 2.243 1.499 1.582 1.637 1.645 2.071 3.043 3.802 3.041 2.275 2.549 2.795 6.461 3.105 4.274 2.558 2.351 2.614 2.62 2.406 2.193 3.544 2.049) + ~ qmult=(0.8292 0.5621 0.6386 0.6739 0.7458 0.334 0.4205 0.6179 1.8414 0.7621 0.9691 1.2345 1.1907 1.8845 1.0206 1.9473 1.011 0.8533 1.1136 1.1161 0.7017 0.9992 0.7196 0.5976) +New LoadShape.3109_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(13.575 10.564 11.001 7.403 7.651 7.63 7.258 7.799 8.425 10.354 11.011 7.668 6.298 4.11 3.9 5.052 3.356 8.003 4.341 8.538 5.449 3.197 6.307 5.826) + ~ qmult=(6.5747 4.5002 2.7571 3.3729 3.2593 3.4763 2.8685 1.9546 1.7108 5.0147 2.7596 3.4936 2.4891 1.7509 1.1375 1.9967 0.4782 3.876 1.4268 3.89 1.9777 0.4555 2.2891 2.3026) +New LoadShape.3110_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(12.451 9.719 7.732 6.231 4.94 6.531 6.061 8.126 8.34 9.77 12.224 8.436 14.791 20.798 24.844 15.26 12.329 11.619 13.974 12.13 17.557 10.06 8.496 7.909) + ~ qmult=(4.921 4.4281 1.57 2.2616 0.7039 0.9306 1.519 2.9493 3.2962 3.546 3.5653 2.4605 3.0034 7.5487 8.1658 6.5007 3.596 2.912 4.593 5.8748 5.7707 4.5835 2.7925 3.1258) +New LoadShape.3111_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(8.563 6.936 6.42 7.607 5.709 6.568 9.403 8.69 8.118 10.7 8.579 12.811 17.428 12.971 12.061 11.43 11.91 11.193 15.807 13.463 18.208 19.761 19.226 11.706) + ~ qmult=(1.2202 3.3593 1.609 1.5447 2.432 2.9925 1.3399 1.7646 1.1568 2.1727 2.5022 4.2108 3.5389 2.6339 2.4491 4.5174 4.3228 4.0625 7.2019 5.3209 7.7566 6.4951 7.5986 3.8476) +New LoadShape.3112_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(6.961 6.209 5.565 5.147 5.334 5.492 5.891 5.661 5.216 5.598 5.221 5.291 8.025 5.658 6.386 6.249 4.395 4.969 5.802 12.009 10.449 8.584 7.075 7.692) + ~ qmult=(3.1715 2.645 1.6231 2.4928 0.7601 2.3396 1.9363 1.8607 2.0615 1.84 1.895 2.0911 2.6377 1.6502 0.91 2.8471 1.2819 0.708 2.2931 3.9472 2.6188 1.7431 2.0635 2.5282) +New LoadShape.3114_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(9.259 9.385 5.514 5.143 4.722 5.048 5.38 5.223 5.552 5.593 6.963 7.96 5.91 5.071 10.208 6.163 6.439 6.598 6.466 6.412 6.706 8.714 6.995 6.398) + ~ qmult=(4.4843 3.998 2.349 1.5 2.1514 1.8322 0.7666 0.7442 2.689 1.6313 2.752 3.8552 2.8623 1.479 2.0728 1.5446 2.743 3.1956 1.8859 2.9214 1.9559 2.5416 2.7646 1.2992) +New LoadShape.3115_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(5.495 5.733 4.538 4.586 5.704 5.358 5.41 5.244 4.195 6.714 8.66 7.146 7.955 9.17 7.311 4.943 6.069 8.711 12.864 8.518 11.889 7.843 9.897 9.026) + ~ qmult=(0.783 1.8843 1.6471 0.9312 1.4296 1.5628 2.1382 1.9033 1.3788 0.9567 2.5258 2.0842 3.6244 3.9064 2.8895 1.9536 2.7651 2.5407 5.0842 2.4844 4.6988 3.5734 4.7933 1.8328) +New LoadShape.3116_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(6.008 5.842 5.272 5.608 5.496 6.46 5.497 5.679 6.026 5.334 12.52 5.517 5.334 7.095 7.378 8.713 10.632 8.291 8.05 10.923 9.514 11.238 8.271 6.472) + ~ qmult=(2.5594 1.9202 2.5533 2.389 2.6618 2.5532 2.6623 1.6564 1.5103 2.1081 4.5442 1.8134 1.3368 2.5751 2.1519 3.9698 3.4946 3.7775 3.8988 5.2903 1.3557 1.6013 3.7684 1.8877) +New LoadShape.3117_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(1.928 1.917 2.702 2.426 2.045 2.601 2.35 2.809 2.672 3.663 6.128 8.222 2.388 2.732 4.973 7.342 3.161 1.875 4.355 5.379 3.36 4.078 1.936 2.484) + ~ qmult=(0.6998 0.6301 0.5487 0.608 0.8082 0.8549 1.0707 0.5704 1.056 0.5219 2.0142 1.6695 0.5985 0.7968 1.4505 1.0462 1.5309 0.6163 1.2702 1.0923 1.5309 1.4801 0.9376 1.0582) +New LoadShape.3120_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(1.381 1.486 1.424 1.465 1.153 1.148 1.701 1.76 2.031 1.864 5.181 3.714 3.684 6.296 7.473 7.163 5.877 2.878 2.25 2.317 5.793 5.271 3.936 2.176) + ~ qmult=(0.1968 0.2117 0.5168 0.2088 0.4912 0.523 0.4961 0.6956 0.8027 0.3785 2.2071 0.9308 1.2109 0.8971 1.0648 2.3544 2.5036 1.3939 0.4569 0.7616 2.6394 1.5374 0.9865 0.4419) +New LoadShape.3121_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.487 2.652 2.114 2.152 1.906 2.495 1.868 2.192 6.759 4.95 3.043 2.438 2.727 4.933 2.806 2.77 2.708 4.634 8.629 6.262 7.516 5.044 2.689 2.58) + ~ qmult=(0.6233 1.1297 1.0239 0.3066 0.4777 0.8201 0.7383 0.7205 2.4532 2.3974 1.2027 0.3474 0.3886 1.7904 1.359 0.5625 1.2338 1.6819 1.7522 2.8531 3.2018 2.2981 1.1455 1.0991) +New LoadShape.3122_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.756 1.694 1.451 1.475 1.718 1.435 1.376 1.864 4.342 4.07 2.83 2.691 1.605 2.451 6.295 8.409 6.879 5.871 10.925 9.406 3.49 3.182 2.605 1.557) + ~ qmult=(1.3348 0.344 0.4232 0.6283 0.7827 0.6538 0.4994 0.5437 1.8497 1.8543 0.9302 1.2261 0.3259 0.3492 2.8681 2.1075 2.261 2.3204 5.2912 2.7434 1.0179 0.4534 1.1869 0.6154) +New LoadShape.3123_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(4.267 5.743 4.37 4.815 4.244 5.284 4.538 5.493 5.434 5.687 5.007 10.642 5.216 5.4 5.989 5.617 4.549 5.601 5.651 7.047 6.214 6.072 4.716 4.219) + ~ qmult=(0.608 2.7815 1.8616 1.5826 1.5404 1.073 0.9215 2.34 0.7743 1.4253 2.425 4.206 0.7432 2.4603 0.8534 1.4078 0.9237 1.841 1.4163 2.5577 3.0096 1.233 2.009 0.8567) +New LoadShape.3124_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.014 2.411 2.453 1.827 2.041 2.127 1.851 2.319 2.1 5.17 4.856 2.392 3.042 2.187 1.836 1.897 4.488 2.345 2.162 4.035 2.178 1.83 1.681 1.661) + ~ qmult=(0.858 1.1677 1.188 0.7783 0.8695 0.8406 0.5399 0.3304 1.0171 1.2957 1.217 0.5995 1.4733 0.7188 0.5355 0.6885 0.9113 0.999 0.985 0.8193 0.7905 0.4586 0.6101 0.6565) +New LoadShape.3125_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(0.595 0.543 0.699 0.761 1.066 0.404 0.417 0.411 0.97 0.742 0.995 2.975 0.393 0.208 0.199 0.302 0.339 0.714 3.105 2.892 1.614 1.027 1.099 1.06) + ~ qmult=(0.2711 0.2146 0.2763 0.3467 0.2672 0.0576 0.1648 0.1873 0.197 0.186 0.2494 0.6041 0.0985 0.0684 0.0284 0.0993 0.085 0.3042 1.4147 1.3176 0.7817 0.3728 0.3205 0.4189) +New LoadShape.3126_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.83 2.408 2.311 2.36 2.299 2.163 2.53 2.324 4.419 3.55 5.68 3.998 8.045 5.513 5.563 6.041 9.345 6.19 4.529 13.128 7.622 8.33 3.754 3.14) + ~ qmult=(1.1185 0.874 0.5792 0.6883 0.5762 0.6309 0.3605 1.1256 2.1402 1.2885 1.1534 1.1661 3.1796 2.5118 2.5346 2.5735 3.0716 1.5514 0.9197 2.6658 2.5052 3.7953 1.7104 0.9158) +New LoadShape.3127_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(8.347 6.354 3.722 4.717 3.89 4.189 4.374 5.174 12.139 14.216 8.875 13.141 13.197 16.156 16.761 11.472 9.403 12.417 8.633 11.92 15.361 8.993 10.403 6.572) + ~ qmult=(2.7435 2.895 0.5304 1.8643 1.6571 1.7845 1.7287 2.2041 5.5307 2.0257 4.0436 4.3192 4.3376 4.7122 6.0834 2.3295 3.4128 2.5214 2.8375 4.3264 6.9987 4.3555 5.0384 2.3853) +New LoadShape.3128_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(4.428 4.529 5.481 5.553 5.417 4.424 4.431 4.201 4.219 4.54 4.417 4.3 5.327 4.552 5.53 4.899 5.084 6.85 5.151 7.361 8.001 5.189 3.788 2.807) + ~ qmult=(1.7501 1.321 1.8015 1.6196 2.1409 1.8846 1.4564 0.853 1.2305 1.934 1.7457 1.0777 1.0817 1.9391 1.3859 2.2321 2.4623 2.7073 1.291 1.8448 2.3336 2.3642 1.6137 0.9226) +New LoadShape.3129_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.487 2.96 2.834 1.744 1.088 1.357 0.998 4.009 4.621 4.095 2.692 0.912 1.817 1.724 2.17 2.972 3.803 7.292 5.286 5.802 5.684 4.46 5.127 2.931) + ~ qmult=(0.9829 0.4218 1.2073 0.7946 0.4635 0.2756 0.1422 1.4551 0.6585 1.9833 1.3038 0.3604 0.5972 0.835 0.6329 1.3541 1.25 2.882 1.9186 2.81 2.7529 1.3008 1.2849 1.2486) +New LoadShape.3130_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(1.554 1.887 1.762 1.69 2.38 2.568 2.025 4.224 2.301 1.923 1.827 1.927 2.141 2.093 2.488 3.092 2.317 2.0 5.322 4.68 4.635 3.392 3.485 2.338) + ~ qmult=(0.6142 0.6849 0.8028 0.77 0.9406 0.5215 0.4112 1.0586 0.6711 0.3905 0.5329 0.562 0.4347 0.425 0.8178 0.9018 1.1222 0.4061 2.5776 0.6669 1.3519 1.5454 1.4846 0.6819) +New LoadShape.3131_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(3.234 2.865 2.521 2.748 2.927 2.754 2.759 3.286 5.45 5.747 13.046 10.487 6.5 7.518 5.435 5.211 4.229 9.11 11.892 10.267 15.555 11.482 7.842 3.577) + ~ qmult=(0.6567 0.5818 0.5119 1.3309 1.4176 0.9052 0.9068 1.5915 1.5896 2.4482 1.859 5.0791 1.6291 1.8842 1.9726 1.306 0.8587 1.8499 4.3162 4.6778 6.1477 4.8913 3.7981 0.8965) +New LoadShape.3132_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(4.031 4.309 4.337 5.28 4.557 4.395 4.414 3.601 3.638 4.465 4.454 3.991 5.249 5.902 4.67 9.835 10.661 12.415 11.333 6.145 5.29 5.638 5.81 5.328) + ~ qmult=(1.3249 1.564 0.618 1.0721 1.654 1.2819 1.1063 1.534 1.4378 0.6362 0.9044 1.3118 1.9051 0.841 0.6654 2.4649 3.8694 3.1115 2.3013 1.7923 0.7538 2.5687 2.2963 0.7592) +New LoadShape.3134_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(6.308 5.455 5.681 5.54 4.969 4.899 5.307 5.625 6.636 11.491 11.332 9.504 8.373 7.42 6.278 11.883 10.25 11.92 12.224 10.614 10.33 12.033 12.706 7.896) + ~ qmult=(2.6872 2.3238 1.1536 2.1895 1.009 2.3727 1.9262 1.8488 3.0235 3.3515 4.4787 2.772 3.039 2.6931 1.5734 5.7552 3.369 3.4767 3.5653 1.5124 2.5889 4.7557 5.4127 2.303) +New LoadShape.3135_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(3.571 3.284 3.526 3.055 3.59 3.619 4.504 4.181 7.709 7.368 7.691 9.309 6.821 4.032 8.344 8.173 5.905 8.71 6.497 8.96 11.111 6.747 9.054 5.784) + ~ qmult=(1.2961 1.1919 1.6065 0.6203 1.4189 0.907 2.0521 1.5175 1.0985 1.4961 2.5279 1.8903 2.6958 1.176 1.6943 2.9664 2.1432 4.2184 1.6283 2.945 2.7847 2.4488 2.2691 1.1745) +New LoadShape.3136_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(6.194 5.07 5.147 4.73 4.308 5.47 4.885 4.914 4.585 10.391 14.439 12.316 5.79 9.734 12.985 9.78 9.494 8.844 8.46 12.548 16.478 9.369 8.406 9.65) + ~ qmult=(1.2577 1.0295 0.7334 2.2908 1.2565 2.6492 1.2243 2.38 0.931 2.6042 6.5786 5.6113 2.4665 4.7144 4.268 2.8525 3.7523 4.2833 1.2055 5.3454 7.9807 2.7326 2.1067 4.3967) +New LoadShape.3137_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(14.286 14.022 10.997 9.805 10.452 9.791 10.58 11.353 14.036 15.428 16.933 12.106 9.768 9.672 8.951 8.752 10.059 14.559 11.348 10.361 12.191 12.231 9.735 9.636) + ~ qmult=(4.6956 1.998 2.7561 1.3971 2.1224 2.8557 3.0858 2.3053 3.5178 6.5723 8.201 5.1571 4.7309 3.179 2.9421 2.5527 4.583 3.6488 5.4961 1.4764 5.5544 4.4393 2.4398 3.4974) +New LoadShape.3138_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(6.504 6.946 7.759 8.877 8.265 7.74 7.144 5.958 5.933 6.668 6.766 3.982 3.032 4.556 5.18 5.691 9.658 9.307 7.331 5.046 5.177 3.888 4.125 5.001) + ~ qmult=(1.897 0.9898 1.9446 4.2993 3.2665 3.5264 2.0837 1.4932 2.8735 3.038 2.4557 1.8143 1.1983 2.2066 2.3601 1.6599 4.6776 4.2404 2.4096 0.719 1.51 1.7714 1.0338 0.7126) +New LoadShape.3139_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0) + ~ qmult=(0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0) +New LoadShape.3141_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.296 2.2 2.323 2.255 2.195 1.905 2.069 2.406 3.524 5.129 4.939 4.11 3.321 3.538 9.224 4.842 3.696 4.523 4.915 5.948 7.555 7.206 7.031 7.973) + ~ qmult=(1.112 0.8695 1.0584 0.3213 0.3128 0.8115 0.8814 0.3428 1.0278 1.6858 1.2378 1.7509 1.4147 1.1629 4.4674 0.6899 0.9263 1.7876 1.2318 2.71 1.5341 2.848 1.7621 2.6206) +New LoadShape.3142_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(3.619 3.168 4.713 2.285 3.234 3.753 2.864 4.858 4.477 5.274 6.411 8.523 7.295 8.029 4.708 5.242 6.507 5.625 4.955 3.925 6.806 3.314 4.283 3.128) + ~ qmult=(1.7528 0.6433 2.2826 0.751 0.9433 1.7099 1.3049 0.9865 0.9091 0.7515 0.9135 3.0934 3.5331 2.639 2.145 1.723 0.9272 1.8488 1.0062 0.9837 1.9851 1.3098 2.0744 0.784) +New LoadShape.3143_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(7.378 5.997 8.046 5.645 6.085 5.386 5.377 5.559 4.914 13.989 15.656 6.895 8.71 9.953 10.186 9.765 10.92 23.132 10.225 12.111 22.743 8.841 11.627 9.567) + ~ qmult=(1.0513 2.9045 2.0165 2.5719 0.8671 1.3499 0.7662 1.8272 1.6152 6.3736 2.2309 2.9373 1.7686 2.903 4.9333 3.8594 3.5892 9.8542 4.9522 3.0353 11.0149 4.0281 3.8216 1.9427) +New LoadShape.3144_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(0.687 0.725 0.568 0.811 0.644 0.481 0.57 0.865 1.046 1.57 2.485 2.21 1.277 1.016 0.874 0.987 0.916 0.823 2.017 0.714 1.183 0.64 0.81 0.936) + ~ qmult=(0.1722 0.3303 0.2245 0.1647 0.2743 0.2049 0.2428 0.2843 0.2124 0.3935 1.0586 0.7264 0.4197 0.2063 0.3172 0.4205 0.2296 0.1671 0.7321 0.1017 0.1686 0.2104 0.2662 0.4533) +New LoadShape.3145_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(1.92 1.697 1.613 1.915 1.751 1.92 2.411 2.555 2.857 7.649 13.379 8.524 2.207 3.972 4.142 4.245 6.813 10.829 7.891 5.912 9.174 6.996 4.218 3.242) + ~ qmult=(0.8179 0.7229 0.5854 0.8725 0.4388 0.56 1.1677 0.5188 1.3837 2.231 2.7167 4.1284 1.0689 0.566 1.5033 1.8084 1.7075 2.1989 2.3015 0.8424 2.6757 1.7534 2.0429 1.1767) +New LoadShape.3146_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(1.331 1.994 1.515 1.423 1.298 1.785 1.973 4.629 1.702 1.98 1.399 0.868 0.855 2.126 3.825 2.814 1.629 2.166 2.066 1.349 1.243 1.468 2.127 1.801) + ~ qmult=(0.526 0.7237 0.4419 0.4677 0.4711 0.8133 0.5755 2.109 0.2425 0.6508 0.6374 0.1763 0.3103 0.7716 1.3883 0.5714 0.694 1.049 0.8801 0.6146 0.4511 0.5328 0.8406 0.8723) +New LoadShape.3147_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(8.398 5.466 4.938 4.64 4.914 4.297 5.456 4.583 6.535 5.094 4.956 5.727 5.652 6.774 7.104 6.474 9.815 8.294 12.556 11.77 10.431 10.119 9.894 8.599) + ~ qmult=(1.1967 1.3699 1.9516 2.114 1.2316 1.4124 1.1079 0.9306 2.5828 0.7259 2.1112 2.2635 2.4077 2.6773 1.0123 0.9225 1.993 3.278 3.1468 3.8686 3.4285 2.9514 3.252 3.3985) +New LoadShape.3148_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(5.86 5.893 5.263 4.834 4.774 4.848 5.337 5.985 9.479 12.843 11.607 14.825 6.743 9.386 9.182 8.23 11.335 13.512 9.876 9.436 17.591 15.531 11.655 10.296) + ~ qmult=(1.9261 2.5104 1.7299 1.5889 0.6803 0.9844 2.5848 2.1723 3.1156 6.2201 2.909 4.324 0.9608 2.7376 3.9115 2.9871 1.6152 3.3864 2.8805 2.3649 7.4937 5.1048 5.3102 3.7369) +New LoadShape.3149_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(4.068 2.959 3.335 2.165 2.276 2.194 2.933 3.023 2.988 3.972 10.193 9.214 6.327 3.676 3.469 2.088 2.911 4.275 6.858 3.851 6.323 6.272 7.298 3.083) + ~ qmult=(1.6078 1.3482 1.3181 1.0486 0.3243 0.3126 0.4179 1.3773 0.9821 1.5698 4.9367 3.6416 2.2964 0.7464 1.5805 1.0113 1.0566 1.6896 2.9215 0.782 3.0624 1.5719 2.6488 0.4393) +New LoadShape.3150_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(1.318 1.679 1.081 1.186 1.044 1.106 0.992 1.112 1.702 1.386 1.398 0.784 0.864 1.379 0.939 1.07 1.918 1.445 1.21 3.78 4.086 1.625 1.808 3.868) + ~ qmult=(0.6383 0.5519 0.4925 0.2408 0.212 0.2246 0.2014 0.3655 0.4266 0.1975 0.6369 0.3797 0.1754 0.4022 0.3086 0.5182 0.5594 0.6584 0.4392 1.8307 0.5822 0.4073 0.7146 0.7854) +New LoadShape.3151_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(0.798 0.987 1.442 1.034 0.764 0.893 1.116 2.28 2.645 3.755 1.632 1.385 1.194 0.818 1.078 1.072 1.809 1.657 1.194 1.246 1.447 2.009 1.234 1.174) + ~ qmult=(0.2896 0.3582 0.657 0.3753 0.2773 0.3804 0.4411 1.0388 1.2051 1.4841 0.5923 0.5474 0.4334 0.3727 0.4261 0.3127 0.6566 0.6549 0.3924 0.3634 0.5252 0.6603 0.3599 0.3424) +New LoadShape.3152_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.011 1.479 1.469 1.521 1.475 2.02 1.821 1.128 0.709 0.768 0.989 2.12 2.335 2.416 2.228 2.066 2.62 2.1 2.74 2.514 2.511 2.67 2.669 1.504) + ~ qmult=(0.7948 0.5845 0.5332 0.6479 0.7144 0.5063 0.5985 0.5463 0.2573 0.3272 0.4506 0.7695 0.9229 0.3443 0.5584 0.2944 0.7642 0.9568 0.6867 0.7332 1.144 1.2931 1.0549 0.4387) +New LoadShape.3153_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(4.949 3.997 4.195 3.6325 3.4137 3.7774 4.6052 4.05 7.6595 8.2208 11.9214 9.5547 10.9711 9.9135 15.8725 9.6995 13.5144 12.1158 16.8259 10.7861 8.1664 8.2504 7.0899 5.8046) + ~ qmult=(1.7962 1.7027 1.7871 1.0595 0.8556 1.8295 1.1542 1.8452 1.0914 3.7455 5.7738 4.6275 4.9986 3.2584 6.2732 2.829 5.3412 3.9823 8.1492 3.1459 3.2276 2.0677 1.4397 2.6447) +New LoadShape.3154_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.223 2.415 2.359 1.709 2.654 1.983 1.966 3.279 3.896 3.389 1.974 2.573 2.604 5.123 3.597 4.515 5.793 3.835 6.728 3.284 4.55 5.631 3.086 3.428) + ~ qmult=(0.4514 1.1696 1.0748 0.7786 1.2092 0.9035 0.8957 1.3968 1.6597 0.9885 0.8994 1.0169 0.6526 2.3341 1.1823 1.7844 2.1026 0.7787 1.6862 1.0794 1.7983 2.5656 0.6266 0.8591) +New LoadShape.3155_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.043 2.761 1.566 1.664 1.562 1.638 1.717 1.91 1.967 1.555 1.459 1.663 2.57 1.772 1.878 1.465 1.566 1.626 1.874 6.139 2.084 2.583 1.946 1.681) + ~ qmult=(0.9895 0.692 0.318 0.3379 0.5669 0.2334 0.8316 0.8137 0.493 0.6146 0.5295 0.8054 0.6441 0.4441 0.6816 0.5317 0.7584 0.6927 0.8538 2.797 0.9495 1.251 0.5676 0.6101) +New LoadShape.3157_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(6.224 5.023 5.316 5.375 6.542 5.731 5.024 4.918 9.42 13.35 12.968 13.51 10.804 19.915 9.336 6.469 7.628 8.079 5.685 7.066 8.581 10.129 8.04 8.496) + ~ qmult=(1.8153 1.8231 2.101 0.7659 2.3744 2.0801 1.4653 1.785 3.0962 1.9023 2.6333 2.7433 5.2326 8.4838 2.3398 2.3479 3.2495 2.3564 1.4248 3.0101 3.3914 4.3149 2.015 2.1293) +New LoadShape.3158_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(4.564 5.435 4.232 4.68 3.612 4.529 4.616 13.52 9.931 10.4 5.345 7.727 12.68 9.384 6.524 6.874 6.328 6.401 6.187 11.173 9.626 8.391 6.361 4.711) + ~ qmult=(2.0794 1.3621 0.8593 1.6986 0.9053 1.1351 1.3463 4.4438 3.925 4.1103 2.277 2.8045 4.6022 4.5449 1.6351 1.3958 1.8457 1.2998 2.0336 1.5921 4.3857 3.8231 2.514 1.8619) +New LoadShape.3159_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(7.676 7.681 7.776 7.775 7.358 6.51 8.67 14.43 14.216 10.819 7.302 8.616 6.919 7.804 8.163 9.546 9.52 10.18 7.342 10.449 11.267 7.237 7.098 7.009) + ~ qmult=(3.0337 2.5246 3.3126 1.5788 1.4941 1.3219 1.7605 6.1472 6.8851 4.6089 3.5365 3.1272 2.018 1.9559 1.1632 3.1376 4.3374 1.4506 1.4909 3.4344 2.2879 3.2973 1.7789 3.1934) +New LoadShape.3160_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(5.285 1.657 1.318 1.613 1.604 1.294 1.106 1.871 4.974 6.105 5.54 4.057 1.844 1.73 1.734 1.684 2.122 1.369 2.661 1.662 2.121 2.692 3.968 3.812) + ~ qmult=(2.4079 0.5446 0.5615 0.5854 0.7308 0.5896 0.1576 0.8525 1.9659 2.2158 1.3885 1.4725 0.8931 0.2465 0.5058 0.24 0.9668 0.6237 1.0517 0.2368 0.4307 0.9771 1.5683 1.6239) +New LoadShape.3161_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(2.12 1.843 2.375 1.836 2.125 2.75 3.594 4.009 4.443 4.658 6.28 2.958 5.224 4.211 2.999 3.153 2.49 4.395 5.106 4.659 4.012 2.429 2.337 1.737) + ~ qmult=(0.9659 0.6689 0.3384 0.2616 0.7713 0.5584 0.9007 1.3177 1.8927 1.1674 2.8613 0.6006 1.8961 1.6643 0.8747 0.7902 0.9037 1.8723 2.3264 1.8414 1.1702 0.4932 0.4745 0.3527) +New LoadShape.3162_loadcurve interval=1 npts=24 useactual=Yes + ~ pmult=(3.195 2.908 3.013 2.875 2.556 2.559 2.611 2.864 5.031 5.493 7.95 5.482 7.522 3.885 4.064 3.121 3.019 3.055 3.136 2.932 3.214 3.201 3.251 3.086) + ~ qmult=(1.5474 0.8482 1.3728 1.0435 0.3642 0.7464 1.0319 0.9414 1.6536 1.3767 3.8504 2.1666 3.6431 1.2769 1.1853 0.4447 0.8805 1.3014 0.4469 1.1588 1.5566 1.3636 1.2849 0.9001) + + +// This file is to define loads with a variety of phase configurations. + +//***************************************************************************************// +// Feeder A +//***************************************************************************************// +New Load.Load_1003 phases=3 conn=wye bus1=T_bus1003_L.1.2.3.0 kV=13.8 kW=15.290000000000001 Kvar=3.832035216364534 daily=1003_loadcurve +New Load.Load_1004 phases=3 conn=wye bus1=T_bus1004_L.1.2.3.0 kV=13.8 kW=6.892000000000000 Kvar=3.337947946542479 daily=1004_loadcurve +New Load.Load_1005 phases=3 conn=wye bus1=T_bus1005_L.1.2.3.0 kV=13.8 kW=4.916000000000000 Kvar=1.942927521885772 daily=1005_loadcurve +!New Load.Load_1006_1 phases=1 conn=wye bus1=T_bus1006_L.1.0 kV=7.9677 kW=2.52 Kvar=1.14814591984 daily=1006_1_loadcurve +!New Load.Load_1006_2 phases=1 conn=wye bus1=T_bus1006_L.2.0 kV=7.9677 kW=2.52 Kvar=1.14814591984 daily=1006_2_loadcurve +!New Load.Load_1007_1 phases=1 conn=wye bus1=T_bus1007_L.1.0 kV=7.9677 kW=2.0815 Kvar=1.00811646122 daily=1007_1_loadcurve +!New Load.Load_1007_2 phases=1 conn=wye bus1=T_bus1007_L.2.0 kV=7.9677 kW=2.0815 Kvar=1.00811646122 daily=1007_2_loadcurve +New Load.Load_1008 phases=3 conn=wye bus1=T_bus1008_L.1.2.3.0 kV=13.8 kW=14.096000000000000 Kvar=6.422327335762342 daily=1008_loadcurve +New Load.Load_1009 phases=3 conn=wye bus1=T_bus1009_L.1.2.3.0 kV=13.8 kW=17.081000000000000 Kvar=6.750843165445662 daily=1009_loadcurve +New Load.Load_1010 phases=3 conn=wye bus1=T_bus1010_L.1.2.3.0 kV=13.8 kW=7.136000000000000 Kvar=2.820327663990413 daily=1010_loadcurve +New Load.Load_1011 phases=1 conn=wye bus1=T_bus1011_L.1 kV=7.9677 kW=3.125000000000000 Kvar=1.027137828683947 daily=1011_loadcurve +New Load.Load_1012 phases=1 conn=wye bus1=T_bus1012_L.2 kV=7.9677 kW=3.083000000000000 Kvar=1.118979580058769 daily=1012_loadcurve +New Load.Load_1013 phases=3 conn=wye bus1=T_bus1013_L.1.2.3.0 kV=13.8 kW=1.537000000000 Kvar=0.448291666666667 daily=1013_loadcurve +New Load.Load_1014 phases=1 conn=wye bus1=T_bus1014_L.2 kV=7.9677 kW=0.458000000000000 Kvar=0.195107182990382 daily=1014_loadcurve +New Load.Load_1015 phases=1 conn=wye bus1=T_bus1015_L.2 kV=7.9677 kW=0.793000000000000 Kvar=0.161025517882765 daily=1015_loadcurve +New Load.Load_1016 phases=1 conn=wye bus1=T_bus1016_L.3 kV=7.9677 kW=3.083000000000000 Kvar=1.493165049215099 daily=1016_loadcurve +New Load.Load_1017 phases=1 conn=wye bus1=T_bus1017_L.3 kV=7.9677 kW=2.131000000000000 Kvar=0.621541666666667 daily=1017_loadcurve + +//***************************************************************************************// +// Feeder B +//***************************************************************************************// +New Load.Load_2002 phases=3 conn=wye bus1=T_bus2002_L.1.2.3.0 kV=13.8 kW=27.940000000000000 Kvar=10.140865866637050 daily=2002_loadcurve +New Load.Load_2003 phases=3 conn=wye bus1=T_bus2003_L.1.2.3.0 kV=13.8 kW=14.286000000000001 Kvar=3.580409097513652 daily=2003_loadcurve +New Load.Load_2005 phases=3 conn=wye bus1=T_bus2005_L.1.2.3.0 kV=13.8 kW=8.947000000000000 Kvar=3.536080662797397 daily=2005_loadcurve +New Load.Load_2008 phases=1 conn=wye bus1=T_bus2008_L.1 kV=7.9677 kW=2.067000000000000 Kvar=0.941753022348238 daily=2008_loadcurve +New Load.Load_2009 phases=1 conn=wye bus1=T_bus2009_L.1 kV=7.9677 kW=1.246000000000000 Kvar=0.492450710388461 daily=2009_loadcurve +New Load.Load_2010 phases=3 conn=wye bus1=T_bus2010_L.1.2.3.0 kV=13.8 kW=2.290000000000000 Kvar=0.905065912351184 daily=2010_loadcurve +New Load.Load_2011 phases=3 conn=wye bus1=T_bus2011_L.1.2.3.0 kV=13.8 kW=5.367999999999999 Kvar=1.764376276600136 daily=2011_loadcurve +New Load.Load_2014 phases=1 conn=wye bus1=T_bus2014_L.2 kV=7.9677 kW=2.816000000000000 Kvar=0.821333333333334 daily=2014_loadcurve +New Load.Load_2015 phases=1 conn=wye bus1=T_bus2015_L.2 kV=7.9677 kW=1.490000000000000 Kvar=0.634737342042945 daily=2015_loadcurve +New Load.Load_2016 phases=1 conn=wye bus1=T_bus2016_L.2 kV=7.9677 kW=1.640000000000000 Kvar=0.333016203439767 daily=2016_loadcurve +New Load.Load_2017 phases=1 conn=wye bus1=T_bus2017_L.2 kV=7.9677 kW=5.125000000000000 Kvar=2.482150787293994 daily=2017_loadcurve +New Load.Load_2018 phases=1 conn=wye bus1=T_bus2018_L.2 kV=7.9677 kW=2.809000000000000 Kvar=0.819291666666667 daily=2018_loadcurve +New Load.Load_2020 phases=1 conn=wye bus1=T_bus2020_L.2 kV=7.9677 kW=3.086000000000000 Kvar=1.014319148581971 daily=2020_loadcurve +New Load.Load_2022 phases=1 conn=wye bus1=T_bus2022_L.3 kV=7.9677 kW=2.728000000000000 Kvar=1.242913519577161 daily=2022_loadcurve +New Load.Load_2023 phases=1 conn=wye bus1=T_bus2023_L.3 kV=7.9677 kW=0.728000000000000 Kvar=0.147826704941555 daily=2023_loadcurve +New Load.Load_2024 phases=1 conn=wye bus1=T_bus2024_L.3 kV=7.9677 kW=2.549000000000000 Kvar=0.363212828405741 daily=2024_loadcurve +New Load.Load_2025 phases=1 conn=wye bus1=T_bus2025_L.3 kV=7.9677 kW=2.701000000000000 Kvar=1.067503506227313 daily=2025_loadcurve +New Load.Load_2028 phases=1 conn=wye bus1=T_bus2028_L.1 kV=7.9677 kW=0.000000000000000 Kvar=0.0000000000000000 daily=2028_loadcurve +New Load.Load_2029 phases=3 conn=wye bus1=T_bus2029_L.1.2.3.0 kV=13.8 kW=9.427000000000000 Kvar=1.914233993796757 daily=2029_loadcurve +New Load.Load_2030 phases=3 conn=wye bus1=T_bus2030_L.1.2.3.0 kV=13.8 kW=5.331000000000000 Kvar=2.581921140890592 daily=2030_loadcurve +New Load.Load_2031 phases=1 conn=wye bus1=T_bus2031_L.1 kV=7.9677 kW=3.279000000000000 Kvar=1.588092181763319 daily=2031_loadcurve +New Load.Load_2032 phases=3 conn=wye bus1=T_bus2032_L.1.2.3.0 kV=13.8 kW=2.217000000000000 Kvar=1.010095041386571 daily=2032_loadcurve +New Load.Load_2034 phases=3 conn=wye bus1=T_bus2034_L.1.2.3.0 kV=13.8 kW=3.621000000000000 Kvar=1.753730341617864 daily=2034_loadcurve +New Load.Load_2035 phases=3 conn=wye bus1=T_bus2035_L.1.2.3.0 kV=13.8 kW=3.931000000000000 Kvar=1.426762481093423 daily=2035_loadcurve +New Load.Load_2037 phases=3 conn=wye bus1=T_bus2037_L.1.2.3.0 kV=13.8 kW=3.941000000000000 Kvar=1.295344058509899 daily=2037_loadcurve +New Load.Load_2040 phases=3 conn=wye bus1=T_bus2040_L.1.2.3.0 kV=13.8 kW=81.759999999999990 Kvar=23.846666666666675 daily=2040_loadcurve +New Load.Load_2041 phases=3 conn=wye bus1=T_bus2041_L.1.2.3.0 kV=13.8 kW=1.919000000000000 Kvar=0.389669569756654 daily=2041_loadcurve +New Load.Load_2042 phases=3 conn=wye bus1=T_bus2042_L.1.2.3.0 kV=13.8 kW=4.223000000000000 Kvar=2.045292248730251 daily=2042_loadcurve +New Load.Load_2043 phases=3 conn=wye bus1=T_bus2043_L.1.2.3.0 kV=13.8 kW=6.509000000000000 Kvar=1.631309170916727 daily=2043_loadcurve +New Load.Load_2045 phases=1 conn=wye bus1=T_bus2045_L.2 kV=7.9677 kW=2.175000000000000 Kvar=0.545106382968794 daily=2045_loadcurve +New Load.Load_2046 phases=1 conn=wye bus1=T_bus2046_L.2 kV=7.9677 kW=6.621000000000000 Kvar=2.820534189037812 daily=2046_loadcurve +New Load.Load_2047 phases=1 conn=wye bus1=T_bus2047_L.2 kV=7.9677 kW=5.512000000000001 Kvar=1.381437417436319 daily=2047_loadcurve +New Load.Load_2048 phases=1 conn=wye bus1=T_bus2048_L.2 kV=7.9677 kW=3.764000000000000 Kvar=1.714929064401919 daily=2048_loadcurve +New Load.Load_2049 phases=1 conn=wye bus1=T_bus2049_L.3 kV=7.9677 kW=3.538000000000000 Kvar=1.284122528137505 daily=2049_loadcurve +New Load.Load_2050 phases=1 conn=wye bus1=T_bus2050_L.3 kV=7.9677 kW=3.747000000000000 Kvar=0.533918582987961 daily=2050_loadcurve +New Load.Load_2051 phases=1 conn=wye bus1=T_bus2051_L.3 kV=7.9677 kW=5.778000000000001 Kvar=2.461417692834991 daily=2051_loadcurve +New Load.Load_2052 phases=3 conn=wye bus1=T_bus2052_L.1.2.3.0 kV=13.8 kW=40.029000000000000 Kvar=17.052282593716143 daily=2052_loadcurve +New Load.Load_2053 phases=3 conn=wye bus1=T_bus2053_L.1.2.3.0 kV=13.8 kW=4.280000000000000 Kvar=1.950025609893787 daily=2053_loadcurve +New Load.Load_2054 phases=1 conn=wye bus1=T_bus2054_L.1 kV=7.9677 kW=7.159000000000000 Kvar=3.467261948534187 daily=2054_loadcurve +New Load.Load_2055 phases=1 conn=wye bus1=T_bus2055_L.1 kV=7.9677 kW=4.260000000000000 Kvar=1.242500000000000 daily=2055_loadcurve +New Load.Load_2056 phases=1 conn=wye bus1=T_bus2056_L.1 kV=7.9677 kW=4.723000000000001 Kvar=2.011989574811295 daily=2056_loadcurve +New Load.Load_2058 phases=3 conn=wye bus1=T_bus2058_L.1.2.3.0 kV=13.8 kW=31.480000000000004 Kvar=11.425714297843033 daily=2058_loadcurve +New Load.Load_2059 phases=1 conn=wye bus1=T_bus2059_L.2 kV=7.9677 kW=2.635000000000000 Kvar=1.276188746247742 daily=2059_loadcurve +New Load.Load_2060 phases=1 conn=wye bus1=T_bus2060_L.2 kV=7.9677 kW=1.946000000000000 Kvar=0.639619268678067 daily=2060_loadcurve + +//***************************************************************************************// +// Feeder C +//***************************************************************************************// +New Load.Load_3002 phases=3 conn=wye bus1=T_bus3002_L.1.2.3.0 kV=13.8 kW=19.645 Kvar=7.13018289 daily=3002_loadcurve +New Load.Load_3004 phases=3 conn=wye bus1=T_bus3004_L.1.2.3.0 kV=13.8 kW=3.117 Kvar=1.509632001 daily=3004_loadcurve +New Load.Load_3006 phases=3 conn=wye bus1=T_bus3006_L.1.2.3.0 kV=13.8 kW=4.033 Kvar=1.837489085 daily=3006_loadcurve +New Load.Load_3007 phases=3 conn=wye bus1=T_bus3007_L.1.2.3.0 kV=13.8 kW=9.425 Kvar=4.564735838 daily=3007_loadcurve +New Load.Load_3009 phases=1 conn=wye bus1=T_bus3009_L.1 kV=7.9677 kW=4.064 Kvar=1.606195575 daily=3009_loadcurve +New Load.Load_3010 phases=1 conn=wye bus1=T_bus3010_L.1 kV=7.9677 kW=4.935 Kvar=1.950436802 daily=3010_loadcurve +New Load.Load_3011 phases=1 conn=wye bus1=T_bus3011_L.1 kV=7.9677 kW=7.419 Kvar=2.438507376 daily=3011_loadcurve +New Load.Load_3012 phases=1 conn=wye bus1=T_bus3012_L.1 kV=7.9677 kW=3.763 Kvar=1.365786623 daily=3012_loadcurve +New Load.Load_3013 phases=1 conn=wye bus1=T_bus3013_L.2 kV=7.9677 kW=2.539 Kvar=0.740541667 daily=3013_loadcurve +New Load.Load_3014 phases=1 conn=wye bus1=T_bus3014_L.2 kV=7.9677 kW=3.105 Kvar=1.322724461 daily=3014_loadcurve +New Load.Load_3016 phases=1 conn=wye bus1=T_bus3016_L.2 kV=7.9677 kW=6.246 Kvar=3.025075867 daily=3016_loadcurve +New Load.Load_3017 phases=1 conn=wye bus1=T_bus3017_L.2 kV=7.9677 kW=8.493 Kvar=2.477125 daily=3017_loadcurve +New Load.Load_3018 phases=1 conn=wye bus1=T_bus3018_L.3 kV=7.9677 kW=5.351 Kvar=1.94215366 daily=3018_loadcurve +New Load.Load_3019 phases=1 conn=wye bus1=T_bus3019_L.3 kV=7.9677 kW=5.403 Kvar=1.77588022 daily=3019_loadcurve +New Load.Load_3020 phases=1 conn=wye bus1=T_bus3020_L.3 kV=7.9677 kW=5.984 Kvar=2.726390946 daily=3020_loadcurve +New Load.Load_3021 phases=1 conn=wye bus1=T_bus3021_L.3 kV=7.9677 kW=7.73 Kvar=3.521892048 daily=3021_loadcurve +New Load.Load_3023 phases=1 conn=wye bus1=T_bus3023_L.1 kV=7.9677 kW=4.344 Kvar=0.618986476 daily=3023_loadcurve +New Load.Load_3024 phases=1 conn=wye bus1=T_bus3024_L.1 kV=7.9677 kW=5.02 Kvar=1.984030952 daily=3024_loadcurve +New Load.Load_3025 phases=1 conn=wye bus1=T_bus3025_L.1 kV=7.9677 kW=6.546 Kvar=1.90925 daily=3025_loadcurve +New Load.Load_3026 phases=1 conn=wye bus1=T_bus3026_L.1 kV=7.9677 kW=10.273 Kvar=2.086021621 daily=3026_loadcurve +New Load.Load_3027 phases=1 conn=wye bus1=T_bus3027_L.1 kV=7.9677 kW=7.116 Kvar=1.444965429 daily=3027_loadcurve +New Load.Load_3028 phases=1 conn=wye bus1=T_bus3028_L.1 kV=7.9677 kW=11.008 Kvar=5.33141773 daily=3028_loadcurve +New Load.Load_3029 phases=1 conn=wye bus1=T_bus3029_L.1 kV=7.9677 kW=2.494 Kvar=1.207899329 daily=3029_loadcurve +New Load.Load_3031 phases=3 conn=wye bus1=T_bus3031_L.1.2.3.0 kV=13.8 kW=4.271 Kvar=0.86726354 daily=3031_loadcurve +New Load.Load_3032 phases=3 conn=wye bus1=T_bus3032_L.1.2.3.0 kV=13.8 kW=5.709 Kvar=2.764994897 daily=3032_loadcurve +New Load.Load_3033 phases=3 conn=wye bus1=T_bus3033_L.1.2.3.0 kV=13.8 kW=9.779 Kvar=3.549303053 daily=3033_loadcurve +New Load.Load_3034 phases=3 conn=wye bus1=T_bus3034_L.1.2.3.0 kV=13.8 kW=4.776 Kvar=0.680543142 daily=3034_loadcurve +New Load.Load_3035 phases=3 conn=wye bus1=T_bus3035_L.1.2.3.0 kV=13.8 kW=10.893 Kvar=3.580355958 daily=3035_loadcurve +New Load.Load_3036 phases=3 conn=wye bus1=T_bus3036_L.1.2.3.0 kV=13.8 kW=4.026 Kvar=1.17425 daily=3036_loadcurve +New Load.Load_3037 phases=3 conn=wye bus1=T_bus3037_L.1.2.3.0 kV=13.8 kW=7.303 Kvar=2.886330287 daily=3037_loadcurve +New Load.Load_3038 phases=3 conn=wye bus1=T_bus3038_L.1.2.3.0 kV=13.8 kW=6.13 Kvar=1.787916667 daily=3038_loadcurve +New Load.Load_3039 phases=3 conn=wye bus1=T_bus3039_L.1.2.3.0 kV=13.8 kW=4.966 Kvar=1.008389309 daily=3039_loadcurve +New Load.Load_3041 phases=1 conn=wye bus1=T_bus3041_L.3 kV=7.9677 kW=5.252 Kvar=1.316275275 daily=3041_loadcurve +New Load.Load_3042 phases=1 conn=wye bus1=T_bus3042_L.3 kV=7.9677 kW=7.719 Kvar=1.09989793 daily=3042_loadcurve +New Load.Load_3043 phases=1 conn=wye bus1=T_bus3043_L.3 kV=7.9677 kW=6.856 Kvar=1.718275569 daily=3043_loadcurve +New Load.Load_3044 phases=1 conn=wye bus1=T_bus3044_L.3 kV=7.9677 kW=4.119 Kvar=1.754686652 daily=3044_loadcurve +New Load.Load_3045 phases=1 conn=wye bus1=T_bus3045_L.3 kV=7.9677 kW=4.434 Kvar=1.11126515 daily=3045_loadcurve +New Load.Load_3047 phases=3 conn=wye bus1=T_bus3047_L.1.2.3.0 kV=13.8 kW=10.213 Kvar=3.706824019 daily=3047_loadcurve +New Load.Load_3048 phases=3 conn=wye bus1=T_bus3048_L.1.2.3.0 kV=13.8 kW=2.868 Kvar=0.408667867 daily=3048_loadcurve +New Load.Load_3049 phases=3 conn=wye bus1=T_bus3049_L.1.2.3.0 kV=13.8 kW=1.671 Kvar=0.711843019 daily=3049_loadcurve +New Load.Load_3050 phases=3 conn=wye bus1=T_bus3050_L.1.2.3.0 kV=13.8 kW=2.646 Kvar=1.12719128 daily=3050_loadcurve +New Load.Load_3051 phases=3 conn=wye bus1=T_bus3051_L.1.2.3.0 kV=13.8 kW=2.412 Kvar=1.098939666 daily=3051_loadcurve +New Load.Load_3052 phases=3 conn=wye bus1=T_bus3052_L.1.2.3.0 kV=13.8 kW=6.101 Kvar=2.954849162 daily=3052_loadcurve +New Load.Load_3054 phases=3 conn=wye bus1=T_bus3054_L.1.2.3.0 kV=13.8 kW=2.471 Kvar=1.052641592 daily=3054_loadcurve +New Load.Load_3056 phases=1 conn=wye bus1=T_bus3056_L.2 kV=7.9677 kW=3.796 Kvar=1.377764024 daily=3056_loadcurve +New Load.Load_3057 phases=1 conn=wye bus1=T_bus3057_L.2 kV=7.9677 kW=3.679 Kvar=1.781821024 daily=3057_loadcurve +New Load.Load_3058 phases=1 conn=wye bus1=T_bus3058_L.2 kV=7.9677 kW=3.475 Kvar=1.142177265 daily=3058_loadcurve +New Load.Load_3059 phases=1 conn=wye bus1=T_bus3059_L.2 kV=7.9677 kW=4.366 Kvar=1.989208367 daily=3059_loadcurve +New Load.Load_3060 phases=1 conn=wye bus1=T_bus3060_L.2 kV=7.9677 kW=3.63 Kvar=1.193123302 daily=3060_loadcurve +New Load.Load_3061 phases=1 conn=wye bus1=T_bus3061_L.2 kV=7.9677 kW=1.283 Kvar=0.374208333 daily=3061_loadcurve +New Load.Load_3062 phases=1 conn=wye bus1=T_bus3062_L.2 kV=7.9677 kW=0.929 Kvar=0.423264905 daily=3062_loadcurve +New Load.Load_3063 phases=1 conn=wye bus1=T_bus3063_L.2 kV=7.9677 kW=6.523 Kvar=2.367532858 daily=3063_loadcurve +New Load.Load_3064 phases=1 conn=wye bus1=T_bus3064_L.2 kV=7.9677 kW=7.792 Kvar=2.272666667 daily=3064_loadcurve +New Load.Load_3065 phases=1 conn=wye bus1=T_bus3065_L.2 kV=7.9677 kW=2.813 Kvar=1.020982666 daily=3065_loadcurve +New Load.Load_3066 phases=1 conn=wye bus1=T_bus3066_L.2 kV=7.9677 kW=3.257 Kvar=1.577437095 daily=3066_loadcurve +New Load.Load_3067 phases=1 conn=wye bus1=T_bus3067_L.2 kV=7.9677 kW=6.204 Kvar=2.039156189 daily=3067_loadcurve +New Load.Load_3070 phases=3 conn=wye bus1=T_bus3070_L.1.2.3.0 kV=13.8 kW=0.845 Kvar=0.120405979 daily=3070_loadcurve +New Load.Load_3071 phases=3 conn=wye bus1=T_bus3071_L.1.2.3.0 kV=13.8 kW=2.163 Kvar=0.71094372 daily=3071_loadcurve +New Load.Load_3072 phases=3 conn=wye bus1=T_bus3072_L.1.2.3.0 kV=13.8 kW=1.964 Kvar=0.279854843 daily=3072_loadcurve +New Load.Load_3073 phases=3 conn=wye bus1=T_bus3073_L.1.2.3.0 kV=13.8 kW=2.297 Kvar=1.046544118 daily=3073_loadcurve +New Load.Load_3074 phases=3 conn=wye bus1=T_bus3074_L.1.2.3.0 kV=13.8 kW=4.791 Kvar=2.182844088 daily=3074_loadcurve +New Load.Load_3077 phases=3 conn=wye bus1=T_bus3077_L.1.2.3.0 kV=13.8 kW=41.037 Kvar=16.21886019 daily=3077_loadcurve +New Load.Load_3078 phases=3 conn=wye bus1=T_bus3078_L.1.2.3.0 kV=13.8 kW=4.012 Kvar=1.827921203 daily=3078_loadcurve +New Load.Load_3081 phases=3 conn=wye bus1=T_bus3081_L.1.2.3.0 kV=13.8 kW=1.581 Kvar=0.39623595 daily=3081_loadcurve +New Load.Load_3083 phases=1 conn=wye bus1=T_bus3083_L.1 kV=7.9677 kW=3.831 Kvar=0.777917729 daily=3083_loadcurve +New Load.Load_3084 phases=1 conn=wye bus1=T_bus3084_L.1 kV=7.9677 kW=2.25 Kvar=0.65625 daily=3084_loadcurve +New Load.Load_3085 phases=1 conn=wye bus1=T_bus3085_L.1 kV=7.9677 kW=6.765 Kvar=1.695468819 daily=3085_loadcurve +New Load.Load_3086 phases=1 conn=wye bus1=T_bus3086_L.1 kV=7.9677 kW=2.35 Kvar=0.92877943 daily=3086_loadcurve +New Load.Load_3087 phases=1 conn=wye bus1=T_bus3087_L.1 kV=7.9677 kW=4.101 Kvar=1.747018684 daily=3087_loadcurve +New Load.Load_3088 phases=1 conn=wye bus1=T_bus3088_L.1 kV=7.9677 kW=8.41 Kvar=1.707723336 daily=3088_loadcurve +New Load.Load_3089 phases=1 conn=wye bus1=T_bus3089_L.1 kV=7.9677 kW=0.836 Kvar=0.303427483 daily=3089_loadcurve +New Load.Load_3090 phases=1 conn=wye bus1=T_bus3090_L.1 kV=7.9677 kW=3.236 Kvar=0.461105027 daily=3090_loadcurve +New Load.Load_3091 phases=1 conn=wye bus1=T_bus3091_L.1 kV=7.9677 kW=2.536 Kvar=0.739666667 daily=3091_loadcurve +New Load.Load_3093 phases=1 conn=wye bus1=T_bus3093_L.1 kV=7.9677 kW=4.264 Kvar=1.942735795 daily=3093_loadcurve +New Load.Load_3094 phases=1 conn=wye bus1=T_bus3094_L.1 kV=7.9677 kW=3.12 Kvar=0.444575922 daily=3094_loadcurve +New Load.Load_3095 phases=1 conn=wye bus1=T_bus3095_L.1 kV=7.9677 kW=5.901 Kvar=2.141777004 daily=3095_loadcurve +New Load.Load_3096 phases=1 conn=wye bus1=T_bus3096_L.1 kV=7.9677 kW=2.842 Kvar=0.934120227 daily=3096_loadcurve +New Load.Load_3097 phases=1 conn=wye bus1=T_bus3097_L.1 kV=7.9677 kW=11.1 Kvar=4.02876203 daily=3097_loadcurve +New Load.Load_3098 phases=1 conn=wye bus1=T_bus3098_L.2 kV=7.9677 kW=6.461 Kvar=2.752374474 daily=3098_loadcurve +New Load.Load_3099 phases=1 conn=wye bus1=T_bus3099_L.2 kV=7.9677 kW=4.424 Kvar=0.630385858 daily=3099_loadcurve +New Load.Load_3101 phases=1 conn=wye bus1=T_bus3101_L.3 kV=7.9677 kW=2.387 Kvar=1.156076864 daily=3101_loadcurve +New Load.Load_3102 phases=1 conn=wye bus1=T_bus3102_L.3 kV=7.9677 kW=8.2 Kvar=2.391666667 daily=3102_loadcurve +New Load.Load_3103 phases=1 conn=wye bus1=T_bus3103_L.3 kV=7.9677 kW=6.718 Kvar=2.655123493 daily=3103_loadcurve +New Load.Load_3104 phases=1 conn=wye bus1=T_bus3104_L.3 kV=7.9677 kW=8.73 Kvar=2.869412238 daily=3104_loadcurve +New Load.Load_3105 phases=1 conn=wye bus1=T_bus3105_L.3 kV=7.9677 kW=3.052 Kvar=0.619735032 daily=3105_loadcurve +New Load.Load_3106 phases=1 conn=wye bus1=T_bus3106_L.3 kV=7.9677 kW=9.815 Kvar=3.879136214 daily=3106_loadcurve +New Load.Load_3108 phases=1 conn=wye bus1=T_bus3108_L.1 kV=7.9677 kW=2.175 Kvar=0.634375 daily=3108_loadcurve +New Load.Load_3109 phases=1 conn=wye bus1=T_bus3109_L.1 kV=7.9677 kW=13.807 Kvar=6.687035301 daily=3109_loadcurve +New Load.Load_3110 phases=1 conn=wye bus1=T_bus3110_L.1 kV=7.9677 kW=9.577 Kvar=1.364648591 daily=3110_loadcurve +New Load.Load_3111 phases=1 conn=wye bus1=T_bus3111_L.1 kV=7.9677 kW=9.729 Kvar=2.837625 daily=3111_loadcurve +New Load.Load_3112 phases=1 conn=wye bus1=T_bus3112_L.1 kV=7.9677 kW=9.339 Kvar=1.330735427 daily=3112_loadcurve +New Load.Load_3114 phases=1 conn=wye bus1=T_bus3114_L.2 kV=7.9677 kW=5.162 Kvar=2.351876682 daily=3114_loadcurve +New Load.Load_3115 phases=1 conn=wye bus1=T_bus3115_L.2 kV=7.9677 kW=7.794 Kvar=1.110584851 daily=3115_loadcurve +New Load.Load_3116 phases=1 conn=wye bus1=T_bus3116_L.2 kV=7.9677 kW=7.337 Kvar=2.139958333 daily=3116_loadcurve +New Load.Load_3117 phases=1 conn=wye bus1=T_bus3117_L.2 kV=7.9677 kW=3.688 Kvar=1.786179923 daily=3117_loadcurve +New Load.Load_3120 phases=1 conn=wye bus1=T_bus3120_L.2 kV=7.9677 kW=3.209 Kvar=0.457257735 daily=3120_loadcurve +New Load.Load_3121 phases=1 conn=wye bus1=T_bus3121_L.2 kV=7.9677 kW=2.845 Kvar=0.713024211 daily=3121_loadcurve +New Load.Load_3122 phases=1 conn=wye bus1=T_bus3122_L.2 kV=7.9677 kW=4.209 Kvar=1.917677054 daily=3122_loadcurve +New Load.Load_3123 phases=1 conn=wye bus1=T_bus3123_L.2 kV=7.9677 kW=5.077 Kvar=2.458903326 daily=3123_loadcurve +New Load.Load_3124 phases=1 conn=wye bus1=T_bus3124_L.2 kV=7.9677 kW=3.261 Kvar=1.579374384 daily=3124_loadcurve +New Load.Load_3125 phases=1 conn=wye bus1=T_bus3125_L.2 kV=7.9677 kW=0.76 Kvar=0.3680848 daily=3125_loadcurve +New Load.Load_3126 phases=1 conn=wye bus1=T_bus3126_L.2 kV=7.9677 kW=6.249 Kvar=2.662062853 daily=3126_loadcurve +New Load.Load_3127 phases=1 conn=wye bus1=T_bus3127_L.2 kV=7.9677 kW=6.152 Kvar=1.24921688 daily=3127_loadcurve +New Load.Load_3128 phases=1 conn=wye bus1=T_bus3128_L.2 kV=7.9677 kW=3.688 Kvar=1.21218698 daily=3128_loadcurve +New Load.Load_3129 phases=1 conn=wye bus1=T_bus3129_L.2 kV=7.9677 kW=1.967 Kvar=0.646521635 daily=3129_loadcurve +New Load.Load_3130 phases=1 conn=wye bus1=T_bus3130_L.2 kV=7.9677 kW=3.657 Kvar=0.742585522 daily=3130_loadcurve +New Load.Load_3131 phases=1 conn=wye bus1=T_bus3131_L.2 kV=7.9677 kW=3.556 Kvar=1.620161465 daily=3131_loadcurve +New Load.Load_3132 phases=1 conn=wye bus1=T_bus3132_L.1 kV=7.9677 kW=11.56 Kvar=4.924539379 daily=3132_loadcurve +New Load.Load_3134 phases=1 conn=wye bus1=T_bus3134_L.1 kV=7.9677 kW=5.953 Kvar=0.848256558 daily=3134_loadcurve +New Load.Load_3135 phases=1 conn=wye bus1=T_bus3135_L.1 kV=7.9677 kW=7.005 Kvar=2.302432157 daily=3135_loadcurve +New Load.Load_3136 phases=1 conn=wye bus1=T_bus3136_L.1 kV=7.9677 kW=9.443 Kvar=4.573453636 daily=3136_loadcurve +New Load.Load_3137 phases=1 conn=wye bus1=T_bus3137_L.1 kV=7.9677 kW=13.759 Kvar=2.793884112 daily=3137_loadcurve +New Load.Load_3138 phases=1 conn=wye bus1=T_bus3138_L.1 kV=7.9677 kW=8.522 Kvar=3.630356798 daily=3138_loadcurve +New Load.Load_3139 phases=1 conn=wye bus1=T_bus3139_L.1 kV=7.9677 kW=0 Kvar=0 daily=3139_loadcurve +New Load.Load_3141 phases=1 conn=wye bus1=T_bus3141_L.3 kV=7.9677 kW=4.915 Kvar=0.998033317 daily=3141_loadcurve +New Load.Load_3142 phases=1 conn=wye bus1=T_bus3142_L.3 kV=7.9677 kW=4.017 Kvar=1.006755099 daily=3142_loadcurve +New Load.Load_3143 phases=1 conn=wye bus1=T_bus3143_L.3 kV=7.9677 kW=7.716 Kvar=2.536126556 daily=3143_loadcurve +New Load.Load_3144 phases=1 conn=wye bus1=T_bus3144_L.3 kV=7.9677 kW=0.482 Kvar=0.219605688 daily=3144_loadcurve +New Load.Load_3145 phases=1 conn=wye bus1=T_bus3145_L.3 kV=7.9677 kW=1.782 Kvar=0.863061991 daily=3145_loadcurve +New Load.Load_3146 phases=1 conn=wye bus1=T_bus3146_L.3 kV=7.9677 kW=1.616 Kvar=0.736271352 daily=3146_loadcurve +New Load.Load_3147 phases=1 conn=wye bus1=T_bus3147_L.3 kV=7.9677 kW=8.324 Kvar=4.031497201 daily=3147_loadcurve +New Load.Load_3148 phases=1 conn=wye bus1=T_bus3148_L.3 kV=7.9677 kW=9.987 Kvar=4.550211628 daily=3148_loadcurve +New Load.Load_3149 phases=1 conn=wye bus1=T_bus3149_L.3 kV=7.9677 kW=2.029 Kvar=0.864350381 daily=3149_loadcurve +New Load.Load_3150 phases=1 conn=wye bus1=T_bus3150_L.3 kV=7.9677 kW=2.602 Kvar=0.652122671 daily=3150_loadcurve +New Load.Load_3151 phases=1 conn=wye bus1=T_bus3151_L.3 kV=7.9677 kW=1.686 Kvar=0.554161401 daily=3151_loadcurve +New Load.Load_3152 phases=1 conn=wye bus1=T_bus3152_L.3 kV=7.9677 kW=1.934 Kvar=0.936678951 daily=3152_loadcurve +New Load.Load_3153 phases=1 conn=wye bus1=T_bus3153_L.3 kV=7.9677 kW=5.976 Kvar=2.894308899 daily=3153_loadcurve +New Load.Load_3154 phases=1 conn=wye bus1=T_bus3154_L.3 kV=7.9677 kW=4.836 Kvar=0.689092679 daily=3154_loadcurve +New Load.Load_3155 phases=1 conn=wye bus1=T_bus3155_L.3 kV=7.9677 kW=1.477 Kvar=0.485466423 daily=3155_loadcurve +New Load.Load_3157 phases=1 conn=wye bus1=T_bus3157_L.3 kV=7.9677 kW=9.74 Kvar=4.149222625 daily=3157_loadcurve +New Load.Load_3158 phases=1 conn=wye bus1=T_bus3158_L.3 kV=7.9677 kW=6.517 Kvar=1.63331416 daily=3158_loadcurve +New Load.Load_3159 phases=1 conn=wye bus1=T_bus3159_L.3 kV=7.9677 kW=9.776 Kvar=4.454077187 daily=3159_loadcurve +New Load.Load_3160 phases=1 conn=wye bus1=T_bus3160_L.3 kV=7.9677 kW=4.252 Kvar=1.397564815 daily=3160_loadcurve +New Load.Load_3161 phases=1 conn=wye bus1=T_bus3161_L.3 kV=7.9677 kW=2.744 Kvar=0.390998824 daily=3161_loadcurve +New Load.Load_3162 phases=1 conn=wye bus1=T_bus3162_L.3 kV=7.9677 kW=3.75 Kvar=0.761469977 daily=3162_loadcurve + +New Capacitor.CAP_201 phases=3 bus1=bus2038.1.2.3 kV=13.80000 kvar=50.00000000 enabled=Yes +New Capacitor.CAP_301 phases=3 bus1=bus3079.1.2.3 kV=13.80000 kvar=50.00000000 enabled=Yes + +New LoadShape.PVScaling1 mult=(0 0 0 0 0 0 0 0.084654201 0.4084613 0.676864065 0.859273011 0.976124644 1 0.934483244 0.778657014 0.531155984 0.209112825 0 0 0 0 0 0 0) + +New pvsystem.PV1 phases=3 bus1=bus1009.1.2.3 kv=13.8 conn=wye irradiance=1 pmpp=( 110 2 *) kva=( 100 2 *) pf=1 balanced=true limitcurrent=true vminpu=0.9090909 daily=PVScaling1 +New Storage.Bat1 phases=3 bus1=bus1009.1.2.3 kv=13.8 kwhstored=( 200 2 *) kwhrated=( 200 2 *) kwrated=( 100 2 *) kva=( 100 2 *) kvar=( 100 2 *) +~ %charge=100 %discharge=100 %effcharge=100 %effdischarge=100 %idlingkw=1 %r=0 %x=50 + +New pvsystem.PV2 phases=3 bus1=bus2027.1.2.3 kv=13.8 conn=wye irradiance=1 pmpp=( 110 2 *) kva=( 100 2 *) pf=1 balanced=true limitcurrent=true vminpu=0.9090909 daily=PVScaling1 +New Storage.Bat2 phases=3 bus1=bus2027.1.2.3 kv=13.8 kwhstored=( 800 2 *) kwhrated=( 800 2 *) kwrated=( 400 2 *) kva=( 400 2 *) kvar=( 400 2 *) +~ %charge=100 %discharge=100 %effcharge=100 %effdischarge=100 %idlingkw=1 %r=0 %x=50 + +New pvsystem.PV3 phases=3 bus1=bus3030.1.2.3 kv=13.8 conn=wye irradiance=1 pmpp=( 110 2 *) kva=( 100 2 *) pf=1 balanced=true limitcurrent=true vminpu=0.9090909 daily=PVScaling1 +New Storage.Bat3 phases=3 bus1=bus3030.1.2.3 kv=13.8 kwhstored=( 800 2 *) kwhrated=( 800 2 *) kwrated=( 400 2 *) kva=( 400 2 *) kvar=( 400 2 *) +~ %charge=100 %discharge=100 %effcharge=100 %effdischarge=100 %idlingkw=1 %r=0 %x=50 + +New pvsystem.PV4 phases=3 bus1=bus3082.1.2.3 kv=13.8 conn=wye irradiance=1 pmpp=( 110 2 *) kva=( 100 2 *) pf=1 balanced=true limitcurrent=true vminpu=0.9090909 daily=PVScaling1 + +New Transformer.T_3082 Phases=3 Windings=2 XHL=4.98 +~ wdg=1 bus=bus3082.1.2.3.0 conn=wye kV=13.8 kva=750 %R=0.55 +~ wdg=2 bus=T_bus3082_L.1.2.3 conn=delta kV=0.48 kva=750 %R=0.55 + +New Generator.Gen3 phases=3 bus1=T_bus3082_L.1.2.3 kv=0.48 pf=1 conn=wye kw=( 750 2 *) kvar=( 0 2 *) Xdp=0.15 H=1.5 D=1.0 + +!--------------------------Calculate---------------------! +Set VoltageBases = "69.0, 13.8, 0.208" ! Set base voltage as 69 kV, 13.8 kV, 0.208 kV +CalcVoltageBases ! Estimate the voltage base for each bus +solve ! Solve the circuit + +new energymeter.circuit element=Transformer.Sub_Xfmr + +!-------------Show Snapshot Power Flow Result------------! +!Show Voltage LN Nodes +!Show currents +!Show power kva element +!Show convergence +!Show isolated +!Show kvbasemismatch +!Show losses +!Show overloads +!Show topology + +!----------------- Plotting -----------------------! + +!Set markCapacitors=yes CapMarkersize=3 +!Set markRegulators=yes RegMarkersize=5 +!Interpolate +!Plot Circuit Power Max=500 dots=n labels=n C1=Blue 1ph=3 ! $00FF0000 +!Plot Circuit voltage Max=0 dots=n n C1=Blue C2=$FF00FF 1ph=3 +!plot circuit Losses Max=1 dots=n labels=n subs=y C1=Blue diff --git a/examples/data/onm_suggested.cnfx b/examples/data/onm_suggested.cnfx new file mode 100644 index 00000000..dc9f5b92 --- /dev/null +++ b/examples/data/onm_suggested.cnfx @@ -0,0 +1,672 @@ + + + + + diff --git a/examples/data/practical-case-stats.csv b/examples/data/practical-case-stats.csv new file mode 100644 index 00000000..693e3827 --- /dev/null +++ b/examples/data/practical-case-stats.csv @@ -0,0 +1,55 @@ +Bonus Load Served via Microgrid,Feeder Load Served,Microgrid Load Served,Total Load Served,simulation_time_steps,type +0.7324133477256377,0.0,100.0,5.202851783335356,0.0,Disabled +0.7346044614346641,0.0,100.0,5.218218067280235,7.5,Disabled +3.415447784714513,0.0,100.00000000000003,7.790812396579977,15.0,Disabled +3.425782132207434,0.0,100.00000000000003,7.813440896844118,22.5,Disabled +3.430927614946854,0.0,100.0,7.824705315584208,30.0,Disabled +3.441175501083034,0.0,100.00000000000003,7.847135219713901,37.5,Disabled +3.4513663477266707,0.0,100.00000000000003,7.86943417535107,45.0,Disabled +3.461500629776833,0.0,100.0,7.891603325897748,52.5,Disabled +3.466546706045786,0.0,100.00000000000003,7.902639577940638,60.0,Disabled +3.430927614946854,0.0,100.0,7.824705315584208,67.5,Disabled +3.389355989323918,0.0,100.00000000000003,7.733652870497079,75.0,Disabled +3.3522009612071377,0.0,100.00000000000004,7.652188000977097,82.5,Disabled +3.314294949328208,0.0,100.00000000000003,7.568992865539986,90.0,Disabled +3.275614952719661,0.0,100.00000000000003,7.484011747747851,97.5,Disabled +3.2304307376043506,0.0,100.00000000000003,7.384628862375601,105.0,Disabled +3.1900102713377447,0.0,100.0,7.295621601773204,112.5,Disabled +3.1427697722175427,0.0,100.0,7.191473787366953,120.0,Disabled +3.124759176160249,0.0,100.0,7.151732245987073,127.5,Disabled +3.1004886637513884,0.0,100.00000000000003,7.098147333705336,135.0,Disabled +3.082090544935304,0.0,99.99999999999997,7.057504284803377,142.5,Disabled +3.063522506353826,0.0,100.00000000000003,7.016465459682843,150.0,Disabled +6.975395679979308,0.0,99.99999999999997,10.746304515200025,157.5,Disabled +6.917173153471502,0.0,100.00000000000003,10.660060806826088,165.0,Disabled +6.8730283920768525,0.0,99.99999999999997,10.594631052018066,172.5,Disabled +6.813520528849727,0.0,100.0,10.506377220139374,180.0,Disabled +6.798526357344739,0.0,100.0,10.484130252347043,187.5,Disabled +6.7834848596359105,0.0,100.0,10.46180914677224,195.0,Disabled +5.1195429744880645,0.0,100.0,9.3924104449228,0.0,Enabled +5.135373055262798,0.0,100.0,9.420213001502086,7.5,Enabled +7.829652792670886,0.0,100.00000000000003,12.005050060501024,15.0,Enabled +7.853599322046718,0.0,100.00000000000003,12.040088961733268,22.5,Enabled +7.865459643005887,0.0,100.0,12.057471421527335,30.0,Enabled +21.38524064838071,0.0,100.00000000000003,24.972416287588892,37.5,Enabled +21.387960414806706,0.0,100.00000000000003,24.985249261028507,45.0,Enabled +21.390665085089278,0.0,100.0,24.99800753207818,52.5,Enabled +21.392011798399686,0.0,100.00000000000003,25.004358857747622,60.0,Enabled +35.341586610252456,0.0,100.0,38.28346735834217,67.5,Enabled +35.17206955074363,0.0,100.00000000000003,38.08719116024763,75.0,Enabled +43.75611793233627,0.0,100.00000000000004,46.25848183883592,82.5,Enabled +43.50168733516428,0.0,100.00000000000003,45.98792098302959,90.0,Enabled +43.24206164129926,0.0,100.00000000000003,45.71155189201951,97.5,Enabled +34.52401836319667,0.0,100.00000000000003,37.33482141013003,105.0,Enabled +34.359195398426415,0.0,100.0,37.14295389139258,112.5,Enabled +42.350385705710735,0.0,100.0,44.76018231373061,120.0,Enabled +42.22949599763706,0.0,100.0,44.630937912717116,127.5,Enabled +42.066588831734755,0.0,100.00000000000003,44.45667315974717,135.0,Enabled +41.94309801523395,0.0,99.99999999999997,44.324496944927404,142.5,Enabled +41.81846667285781,0.0,100.00000000000003,44.19103361696288,150.0,Enabled +41.69267893094762,0.0,99.99999999999997,44.05626428328031,157.5,Enabled +41.52313510706803,0.0,100.00000000000003,43.8745069232197,165.0,Enabled +41.394585693292726,0.0,99.99999999999997,43.73661466733761,172.5,Enabled +41.221298972712646,0.0,100.0,43.55062100124944,180.0,Enabled +41.17763598992335,0.0,100.0,43.50373584044733,187.5,Enabled +41.133835193369315,0.0,100.0,43.45669443534771,195.0,Enabled diff --git a/examples/data/settings.ieee13.json b/examples/data/settings.ieee13.json new file mode 100644 index 00000000..b7360e05 --- /dev/null +++ b/examples/data/settings.ieee13.json @@ -0,0 +1,320 @@ +{ + "bus": { + "646": { + "vm_ub": [2.641954831811728, 2.641954831811728], + "vm_lb": [2.161599407845959, 2.161599407845959] + }, + "671": { + "vm_ub": [2.641954831811728, 2.641954831811728, 2.641954831811728], + "vm_lb": [2.161599407845959, 2.161599407845959, 2.161599407845959] + }, + "800": { + "vm_ub": [2.641954831811728, 2.641954831811728, 2.641954831811728], + "vm_lb": [2.161599407845959, 2.161599407845959, 2.161599407845959] + }, + "680": { + "vm_ub": [2.641954831811728, 2.641954831811728, 2.641954831811728], + "vm_lb": [2.161599407845959, 2.161599407845959, 2.161599407845959] + }, + "634": { + "vm_ub": [0.30484094213212237, 0.30484094213212237, 0.30484094213212237], + "vm_lb": [0.24941531628991828, 0.24941531628991828, 0.24941531628991828] + }, + "652": { "vm_ub": [2.641954831811728], "vm_lb": [2.161599407845959] }, + "701": { + "vm_ub": [2.641954831811728, 2.641954831811728, 2.641954831811728], + "vm_lb": [2.161599407845959, 2.161599407845959, 2.161599407845959], + "microgrid_id": "3" + }, + "675": { + "vm_ub": [2.641954831811728, 2.641954831811728, 2.641954831811728], + "vm_lb": [2.161599407845959, 2.161599407845959, 2.161599407845959], + "microgrid_id": "2" + }, + "702": { + "vm_ub": [2.641954831811728, 2.641954831811728, 2.641954831811728], + "vm_lb": [2.161599407845959, 2.161599407845959, 2.161599407845959], + "microgrid_id": "4" + }, + "650": { + "vm_ub": [2.641954831811728, 2.641954831811728, 2.641954831811728], + "vm_lb": [2.161599407845959, 2.161599407845959, 2.161599407845959] + }, + "700": { + "vm_ub": [2.641954831811728, 2.641954831811728, 2.641954831811728], + "vm_lb": [2.161599407845959, 2.161599407845959, 2.161599407845959], + "microgrid_id": "3" + }, + "801": { + "vm_ub": [2.641954831811728, 2.641954831811728, 2.641954831811728], + "vm_lb": [2.161599407845959, 2.161599407845959, 2.161599407845959], + "microgrid_id": "1" + }, + "rg60": { + "vm_ub": [2.641954831811728, 2.641954831811728, 2.641954831811728], + "vm_lb": [2.161599407845959, 2.161599407845959, 2.161599407845959] + }, + "611": { "vm_ub": [2.641954831811728], "vm_lb": [2.161599407845959] }, + "645": { + "vm_ub": [2.641954831811728, 2.641954831811728], + "vm_lb": [2.161599407845959, 2.161599407845959] + }, + "632": { + "vm_ub": [2.641954831811728, 2.641954831811728, 2.641954831811728], + "vm_lb": [2.161599407845959, 2.161599407845959, 2.161599407845959] + }, + "675aux": { + "vm_ub": [2.641954831811728, 2.641954831811728, 2.641954831811728], + "vm_lb": [2.161599407845959, 2.161599407845959, 2.161599407845959], + "microgrid_id": "2" + }, + "703": { + "vm_ub": [2.641954831811728, 2.641954831811728, 2.641954831811728], + "vm_lb": [2.161599407845959, 2.161599407845959, 2.161599407845959], + "microgrid_id": "4" + }, + "633": { + "vm_ub": [2.641954831811728, 2.641954831811728, 2.641954831811728], + "vm_lb": [2.161599407845959, 2.161599407845959, 2.161599407845959] + }, + "684": { + "vm_ub": [2.641954831811728, 2.641954831811728], + "vm_lb": [2.161599407845959, 2.161599407845959] + }, + "692": { + "vm_ub": [2.641954831811728, 2.641954831811728, 2.641954831811728], + "vm_lb": [2.161599407845959, 2.161599407845959, 2.161599407845959], + "microgrid_id": "2" + }, + "670": { + "vm_ub": [2.641954831811728, 2.641954831811728, 2.641954831811728], + "vm_lb": [2.161599407845959, 2.161599407845959, 2.161599407845959] + }, + "800aux": { + "vm_ub": [2.641954831811728, 2.641954831811728, 2.641954831811728], + "vm_lb": [2.161599407845959, 2.161599407845959, 2.161599407845959] + } + }, + "solvers": { + "HiGHS": { + "presolve": "choose", + "small_matrix_value": 1.0e-12, + "mip_feasibility_tolerance": 0.0001, + "primal_feasibility_tolerance": 1.0e-5, + "dual_feasibility_tolerance": 1.0e-5, + "mip_rel_gap": 0.0001, + "allow_unbounded_or_infeasible": true, + "output_flag": false + }, + "useGurobi": true, + "Juniper": { + "branch_strategy": ":MostInfeasible", + "traverse_strategy": ":DFS", + "mip_gap": 0.0001 + }, + "Ipopt": { + "tol": 1.0e-5, + "mu_strategy": "adaptive", + "print_level": 0, + "mumps_mem_percent": 200 + }, + "Gurobi": { + "FeasibilityTol": 1.0e-5, + "NumericFocus": 3, + "MIPGap": 0.0001, + "Quad": 1, + "MIPFocus": 2, + "DualReductions": 0, + "GURO_PAR_DUMP": 0, + "OutputFlag": 0, + "Presolve": -1 + }, + "KNITRO": { + "presolve": 1, + "opttol": 0.0001, + "algorithm": 3, + "feastol": 1.0e-5, + "outlev": 0 + }, + "useKNITRO": false + }, + "settings": { "sbase_default": 1000.0 }, + "storage": { + "battery_mg1a": { "phase_unbalance_factor": 0.0 }, + "battery_mg1c": { "phase_unbalance_factor": 0.0 }, + "battery_mg1b": { "phase_unbalance_factor": 0.0 } + }, + "switch": { + "801675": { "cm_ub": [35.0, 35.0, 35.0] }, + "671692": { "cm_ub": [null, null, null] }, + "671700": { "cm_ub": [null, null, null] }, + "703800": { "cm_ub": [null, null, null] }, + "800801": { "cm_ub": [35.0, 35.0, 35.0] }, + "701702": { "cm_ub": [null, null, null] } + }, + "generator": {}, + "dss": { + "Storage.Battery_mg1b": { "inverter": "gfm" }, + "Storage.Battery_mg1c": { "inverter": "grid_forming" }, + "PVSystem.PV_mg1a": { "inverter": "gfm" }, + "PVSystem.PV_mg1b": { "inverter": "gfm" }, + "Storage.Battery_mg1a": { "inverter": "GRID_FORMING" }, + "Generator.675": { "inverter": "GRID_FORMING" } + }, + "voltage_source": { + "source": { "qg_lb": [0.0, 0.0, 0.0], "pg_lb": [0.0, 0.0, 0.0] } + }, + "line": { + "632670": { + "vad_ub": [5.0, 5.0, 5.0], + "cm_ub": [null, null, null], + "vad_lb": [-5.0, -5.0, -5.0] + }, + "700701": { + "vad_ub": [5.0, 5.0, 5.0], + "cm_ub": [null, null, null], + "vad_lb": [-5.0, -5.0, -5.0] + }, + "632645": { + "vad_ub": [5.0, 5.0], + "cm_ub": [null, null], + "vad_lb": [-5.0, -5.0] + }, + "684611": { "vad_ub": [5.0], "cm_ub": [null], "vad_lb": [-5.0] }, + "692675": { + "vad_ub": [5.0, 5.0, 5.0], + "cm_ub": [null, null, null], + "vad_lb": [-5.0, -5.0, -5.0] + }, + "675675aux": { + "vad_ub": [5.0, 5.0, 5.0], + "cm_ub": [null, null, null], + "vad_lb": [-5.0, -5.0, -5.0] + }, + "671684": { + "vad_ub": [5.0, 5.0], + "cm_ub": [null, null], + "vad_lb": [-5.0, -5.0] + }, + "645646": { + "vad_ub": [5.0, 5.0], + "cm_ub": [null, null], + "vad_lb": [-5.0, -5.0] + }, + "650632": { + "vad_ub": [5.0, 5.0, 5.0], + "cm_ub": [null, null, null], + "vad_lb": [-5.0, -5.0, -5.0] + }, + "800800aux": { + "vad_ub": [5.0, 5.0, 5.0], + "cm_ub": [null, null, null], + "vad_lb": [-5.0, -5.0, -5.0] + }, + "702703": { + "vad_ub": [5.0, 5.0, 5.0], + "cm_ub": [null, null, null], + "vad_lb": [-5.0, -5.0, -5.0] + }, + "671680": { + "vad_ub": [5.0, 5.0, 5.0], + "cm_ub": [null, null, null], + "vad_lb": [-5.0, -5.0, -5.0] + }, + "632633": { + "vad_ub": [5.0, 5.0, 5.0], + "cm_ub": [null, null, null], + "vad_lb": [-5.0, -5.0, -5.0] + }, + "684652": { "vad_ub": [5.0], "cm_ub": [null], "vad_lb": [-5.0] }, + "670671": { + "vad_ub": [5.0, 5.0, 5.0], + "cm_ub": [null, null, null], + "vad_lb": [-5.0, -5.0, -5.0] + } + }, + "options": { + "outputs": { + "sparse-fault-studies": true, + "pretty-print": true, + "debug-output": false + }, + "variables": { + "unbound-transformer-power": false, + "unbound-generation-power": false, + "unbound-storage-power": false, + "unbound-line-power": false, + "unbound-line-current": false, + "unbound-voltage": false, + "relax-integer-variables": false, + "unbound-switch-power": false + }, + "constraints": { + "disable-grid-forming-inverter-constraint": true, + "disable-switch-close-action-limit": false, + "enable-strictly-increasing-restoration-constraint": false, + "disable-thermal-limit-constraints": false, + "disable-storage-unbalance-constraint": false, + "disable-radiality-constraint": false, + "disable-current-limit-constraints": false, + "disable-block-isolation-constraint": false, + "disable-microgrid-networking": false, + "disable-microgrid-expansion": false + }, + "problem": { + "operations-problem-type": "block", + "operations-formulation": "LPUBFDiagPowerModel", + "dispatch-formulation": "LPUBFDiagPowerModel", + "operations-algorithm": "rolling-horizon", + "dispatch-solver": "nlp_solver", + "operations-solver": "mip_solver", + "concurrent-fault-studies": true, + "concurrent-stability-studies": true + }, + "data": { + "fix-small-numbers": false, + "switch-close-actions-ub": [1, 1, 1, 1, 1, 1, 1, 1] + }, + "objective": { + "disable-load-block-shed-cost": false, + "disable-switch-state-change-cost": false, + "disable-generation-dispatch-cost": false, + "enable-switch-state-open-cost": true, + "disable-storage-discharge-cost": false, + "disable-load-block-weight-cost": false + } + }, + "transformer": { + "xfm1": { "sm_ub": 25000.0 }, + "reg1": { "sm_ub": 25000.0 }, + "sub": { "sm_ub": 25000.0 } + }, + "shunt": {}, + "solar": {}, + "load": { + "671_1": { "clpu_factor": 2.0 }, + "634a": { "clpu_factor": 2.0 }, + "692_3": { "clpu_factor": 2.0 }, + "675b": { "clpu_factor": 2.0 }, + "675a": { "clpu_factor": 2.0 }, + "652": { "clpu_factor": 2.0 }, + "692_1": { "clpu_factor": 2.0 }, + "701": { "clpu_factor": 2.0 }, + "671_3": { "clpu_factor": 2.0 }, + "702": { "clpu_factor": 2.0 }, + "646_3": { "clpu_factor": 2.0 }, + "700": { "clpu_factor": 2.0 }, + "801": { "clpu_factor": 2.0 }, + "670c": { "clpu_factor": 2.0 }, + "611": { "clpu_factor": 2.0 }, + "645": { "clpu_factor": 2.0 }, + "634c": { "clpu_factor": 2.0 }, + "671_2": { "clpu_factor": 2.0 }, + "703": { "clpu_factor": 2.0 }, + "670b": { "clpu_factor": 2.0 }, + "634b": { "clpu_factor": 2.0 }, + "675c": { "clpu_factor": 2.0 }, + "670a": { "clpu_factor": 2.0 }, + "646_2": { "clpu_factor": 2.0 } + } +} diff --git a/examples/data/settings.iowa240.json b/examples/data/settings.iowa240.json new file mode 100644 index 00000000..3581ac12 --- /dev/null +++ b/examples/data/settings.iowa240.json @@ -0,0 +1,3309 @@ +{ + "bus": { + "t_bus3143_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3141": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3038_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus2022": { "vm_ub": [8.36580540055768], "vm_lb": [7.569062029075995] }, + "t_bus2048_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "1" + }, + "bus3062": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus1007_l": { + "vm_ub": [8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995], + "microgrid_id": "4" + }, + "t_bus3111_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus1011": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "4" + }, + "t_bus3087_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3154": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus1015_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "4" + }, + "bus3150": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3075": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus2050": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus3031": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus3004": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3024_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus2007": { "vm_ub": [8.36580540055768], "vm_lb": [7.569062029075995] }, + "bus3153": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3033": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3096_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3128": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3027_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus3027": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus2039": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus3125": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3118": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3141_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3030": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus2056_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "1" + }, + "t_bus2045_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "1" + }, + "bus1006": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "4" + }, + "t_bus3149_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3085": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3119": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3094": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3049_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus2053": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus3057": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus3142": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus2042_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus3133": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3078": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "3" + }, + "bus2033": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "t_bus2014_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995] + }, + "t_bus3120_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3144_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3099": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3154_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3146": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3066_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus3093": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus2055_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "1" + }, + "bus2043": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus3158": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3024": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus3063": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus2026": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus2040": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus_xfmr": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995] + }, + "bus2030": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "t_bus2047_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "1" + }, + "t_bus3078_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "3" + }, + "bus3113": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3032": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3002_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus1": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995] + }, + "bus2045": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "1" + }, + "t_bus3161_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3114": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3065_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus3086": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3014": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus1008": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "4" + }, + "bus3073": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3064_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus3036": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus2025_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995] + }, + "bus3139": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus2022_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995] + }, + "bus3100": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "3" + }, + "bus3052": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus2015_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995] + }, + "t_bus3031_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus3079": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "3" + }, + "bus3035": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus2042": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus3110": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3086_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus2017": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995] + }, + "bus3048": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3035_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus1014_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "4" + }, + "bus2020": { "vm_ub": [8.36580540055768], "vm_lb": [7.569062029075995] }, + "bus3084": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3001": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus2020_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995] + }, + "t_bus3135_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3013": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus2043_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "t_bus3052_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3057_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3060_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3125_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3127": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus2028": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "t_bus3019_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus3043": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3147_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3104_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3049": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus3143": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus2058_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus3002": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus3123": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3061": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus2006": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995] + }, + "t_bus1003_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "4" + }, + "bus2011": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995] + }, + "bus2059": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus3147": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus2032": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus3098": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3144": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus2058": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "t_bus3117_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3117": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3102": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3107": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "3" + }, + "bus3064": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus3059": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus2035": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "t_bus3025_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus1002": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "4" + }, + "t_bus3041_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3114_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3140": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus1015": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "4" + }, + "bus2018": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995] + }, + "bus1001": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "4" + }, + "t_bus3028_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus2023": { "vm_ub": [8.36580540055768], "vm_lb": [7.569062029075995] }, + "bus3009": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3142_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3157": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3062_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus2052_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "t_bus2050_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "1" + }, + "bus3026": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus3095": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3153_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3103_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3105": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus2031": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus3161": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus2054": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "t_bus3150_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3092": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "3" + }, + "bus3077": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "3" + }, + "bus3044": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus3121": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3058": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3004_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus2002_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995] + }, + "t_bus3124_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3045": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus2046_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "1" + }, + "t_bus1017_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "4" + }, + "bus3003": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus3155": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3023_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3112_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus2018_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995] + }, + "bus3136": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3132_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus2051_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "1" + }, + "t_bus3074_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3071_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3151_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3089_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus2010": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995] + }, + "bus1016": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "4" + }, + "t_bus3091_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3152": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3152_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3061_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3155_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3014_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3072_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus3037": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus2011_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995] + }, + "bus2019": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995] + }, + "t_bus2003_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995] + }, + "bus3149": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus2037_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus3023": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus2036": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus2041": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus3116": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus1013_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "4" + }, + "t_bus3048_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus3071": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus3039": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3099_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3102_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3009_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus3005": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus2053_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus3041": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus3038": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus3089": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus2057": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "t_bus3088_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus1014": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "4" + }, + "bus3017": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3054_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus3087": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3101": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus2049_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "1" + }, + "t_bus1009_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "4" + }, + "bus2034": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "t_bus3093_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3098_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus2040_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus3072": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus2014": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995] + }, + "t_bus3084_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3042_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus3148": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3083_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3151": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3122_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3108_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus2041_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "t_bus3157_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus1010": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "4" + }, + "bus3067": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3159_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3096": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3051_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3134_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3056_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3109_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3034_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus3018": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus2059_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "1" + }, + "t_bus3007_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus3010": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus2055": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus2021": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995] + }, + "bus3112": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus2029": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "t_bus2030_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus3082": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "3" + }, + "bus3065": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus3134": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3051": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus3160": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3074": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus2049": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "t_bus2005_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995] + }, + "t_bus2009_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995] + }, + "bus3108": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3010_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3017_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus2054_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "1" + }, + "bus2016": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995] + }, + "t_bus1008_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "4" + }, + "t_bus3016_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus3029": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus2017_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995] + }, + "bus3097": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3109": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3028": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus3025": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3070_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus3047": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3105_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3060": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3085_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3069": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus3034": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus1005": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "4" + }, + "t_bus1012_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "4" + }, + "t_bus3162_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3082_l": { + "vm_ub": [0.2909845356715714, 0.2909845356715714, 0.2909845356715714], + "vm_lb": [0.26327172275046934, 0.26327172275046934, 0.26327172275046934], + "microgrid_id": "3" + }, + "bus3088": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus2024": { "vm_ub": [8.36580540055768], "vm_lb": [7.569062029075995] }, + "bus2046": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "1" + }, + "t_bus3021_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus3016": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3011_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3097_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus2029_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus3042": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus2035_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus3056": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3033_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus3162": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3055": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3047_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3090_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3040": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus2008_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995] + }, + "bus3131": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3012": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3058_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus3130": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3136_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3121_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3015": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus3156": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3073_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus3129": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus2005": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995] + }, + "t_bus3020_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3044_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus1006_l": { + "vm_ub": [8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995], + "microgrid_id": "4" + }, + "t_bus3131_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus1005_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "4" + }, + "bus3022": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus2051": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus2056": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus2004": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995] + }, + "bus1003": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "4" + }, + "bus3126": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3130_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3011": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus3145": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus2047": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "1" + }, + "t_bus3127_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3146_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus2010_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995] + }, + "t_bus3115_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3018_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus3120": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3006": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3029_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus2028_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "1" + }, + "bus3046": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3037_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3012_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3081_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "3" + }, + "bus3111": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus2012": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995] + }, + "bus2027": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus3138": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3106_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus1010_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "4" + }, + "t_bus3137_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3123_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3090": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3066": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus3054": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus3091": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3059_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus2052": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "t_bus3006_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus3019": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus1013": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "4" + }, + "bus3076": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "3" + }, + "bus2009": { "vm_ub": [8.36580540055768], "vm_lb": [7.569062029075995] }, + "t_bus3063_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3095_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3138_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus1004": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "4" + }, + "t_bus2024_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995] + }, + "t_bus3026_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus2001": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995] + }, + "t_bus2016_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995] + }, + "bus3135": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3101_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3116_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus1007": { + "vm_ub": [8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995], + "microgrid_id": "4" + }, + "bus3007": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus2032_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus3106": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3080": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3139_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus2013": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995] + }, + "t_bus3148_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus2060_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "1" + }, + "bus3053": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus3103": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3068": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus3081": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "3" + }, + "bus2015": { "vm_ub": [8.36580540055768], "vm_lb": [7.569062029075995] }, + "bus3083": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3094_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3070": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus3122": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus1004_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "4" + }, + "t_bus1016_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "4" + }, + "bus2008": { "vm_ub": [8.36580540055768], "vm_lb": [7.569062029075995] }, + "t_bus3045_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3145_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus2031_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "1" + }, + "bus2060": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus3132": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus2037": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "t_bus3067_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3043_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus1009": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "4" + }, + "bus3008": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus2044": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "t_bus3050_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus3020": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus3115": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3039_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3036_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3128_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3032_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "bus2025": { "vm_ub": [8.36580540055768], "vm_lb": [7.569062029075995] }, + "t_bus2034_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus1017": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "4" + }, + "t_bus1011_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "4" + }, + "bus2038": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "1" + }, + "bus1012": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "4" + }, + "bus3021": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus3159": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3077_l": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3126_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3110_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus2048": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "1" + }, + "bus3124": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus2002": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995] + }, + "t_bus2023_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995] + }, + "t_bus3158_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "t_bus3013_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "2" + }, + "bus2003": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995] + }, + "t_bus3129_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3050": { + "vm_ub": [8.36580540055768, 8.36580540055768, 8.36580540055768], + "vm_lb": [7.569062029075995, 7.569062029075995, 7.569062029075995], + "microgrid_id": "2" + }, + "t_bus3160_l": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3104": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + }, + "bus3137": { + "vm_ub": [8.36580540055768], + "vm_lb": [7.569062029075995], + "microgrid_id": "3" + } + }, + "solvers": { + "HiGHS": { + "presolve": "choose", + "small_matrix_value": 1.0e-12, + "mip_feasibility_tolerance": 0.0001, + "primal_feasibility_tolerance": 1.0e-6, + "dual_feasibility_tolerance": 1.0e-6, + "mip_rel_gap": 0.0001, + "allow_unbounded_or_infeasible": true, + "output_flag": false + }, + "useGurobi": true, + "Juniper": { + "branch_strategy": ":MostInfeasible", + "traverse_strategy": ":DFS", + "mip_gap": 0.0001 + }, + "Ipopt": { + "tol": 1.0e-6, + "mu_strategy": "adaptive", + "print_level": 0, + "mumps_mem_percent": 200 + }, + "Gurobi": { + "FeasibilityTol": 1.0e-6, + "NumericFocus": 3, + "MIPGap": 0.0001, + "Quad": 1, + "MIPFocus": 2, + "DualReductions": 0, + "GURO_PAR_DUMP": 0, + "OutputFlag": 0, + "Presolve": -1 + }, + "KNITRO": { + "presolve": 0, + "opttol": 0.0001, + "algorithm": 3, + "feastol": 1.0e-6, + "outlev": 0 + }, + "useKNITRO": false + }, + "settings": { "sbase_default": 1000.0 }, + "storage": {}, + "switch": { + "cb_101": { "cm_ub": [null, null, null] }, + "cb_102": { + "cm_ub": [null, null, null], + "status": "ENABLED", + "dispatchable": "NO", + "state": "OPEN" + }, + "cb_302": { "cm_ub": [null, null, null] }, + "cb_303": { + "cm_ub": [null, null, null], + "status": "ENABLED", + "dispatchable": "NO", + "state": "OPEN" + }, + "cb_202": { "cm_ub": [null, null, null] }, + "cb_203": { "cm_ub": [null, null, null] }, + "cb_201": { "cm_ub": [null, null, null] }, + "cb_301": { "cm_ub": [null, null, null] }, + "cb_204": { + "cm_ub": [null, null, null], + "status": "ENABLED", + "dispatchable": "NO", + "state": "OPEN" + } + }, + "generator": {}, + "voltage_source": { + "source": { + "cost_pg_parameters": [2.0, 0.0], + "qg_lb": [0.0, 0.0, 0.0], + "pg_lb": [0.0, 0.0, 0.0], + "cost_pg_model": 2 + } + }, + "line": { + "l_3107_3140": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3036_3036_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2006_2007": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3054_3054_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3013_3014": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3088_3089": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3034_3034_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_1002_1004": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3133_3137": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_1006_1006_l_1": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3132_3132_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3142_3142_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3120_3120_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3002_3002_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3144_3144_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3035_3035_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3145_3146": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3072_3072_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3154_3154_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3011_3012": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2023_2024": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2030_2031": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_1009_1010": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3118_3119": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3106_3106_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3097_3097_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3092_3100": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_1015_1015_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3138_3138_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2028_2028_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2006_2010": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3136_3136_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3055_3056": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3158_3158_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3114_3114_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3041_3042": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2025_2025_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3008_3013": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2030_2030_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2043_2043_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2034_2034_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3161_3162": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3153_3154": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2053_2053_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3118_3132": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3050_3050_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2021_2022": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3102_3103": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3103_3103_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2024_2024_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2033_2035": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3126_3126_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2003_2004": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3153_3153_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3046_3047": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3142_3143": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3141_3142": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_1014_1014_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_1005_1005_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3037_3038": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3088_3088_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2039_2042": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3139_3139_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3027_3028": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3047_3047_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3019_3019_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3116_3117": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2014_2015": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2050_2051": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2048_2048_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2027_2028": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3130_3131": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3023_3024": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3069_3073": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3023_3023_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2032_2033": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3083_3084": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2024_2025": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2038_2039": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3151_3152": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2058_2058_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_1017_1017_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3144_3145": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2016_2017": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3114_3115": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3043_3043_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3143_3143_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3080_3082": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3124_3124_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3094_3095": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3059_3060": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3070_3070_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2037_2037_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3125_3125_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3109_3110": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_1014_1015": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3089_3089_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3061_3061_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3015_3016": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3020_3021": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3108_3108_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3109_3109_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3150_3150_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3030_3031": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3021_3021_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3113_3114": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3140_3156": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3105_3105_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3055_3062": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3096_3096_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3026_3026_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3057_3057_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2022_2023": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2002_2003": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2005_2005_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3140_3141": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2051_2051_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2035_2036": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3070_3071": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3009_3009_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2013_2019": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2059_2060": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3087_3087_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3041_3041_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3057_3058": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2036_2037": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3101_3102": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3081_3081_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_1013_1013_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3071_3071_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3048_3048_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3152_3153": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_1004_1005": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3112_3112_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3132_3133": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3039_3039_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3091_3091_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3092_3093": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3141_3141_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_1006_1007": { + "vad_ub": [3.0, 3.0], + "cm_ub": [null, null], + "vad_lb": [-3.0, -3.0] + }, + "l_2018_2018_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2044_2049": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3032_3033": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3034_3068": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3079_3080": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3080_3081": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2049_2049_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3022_3023": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3038_3039": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3005_3006": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3116_3116_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_1013_1016": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3029_3029_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2037_2038": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3104_3104_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3082_3107": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3128_3129": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2009_2009_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3135_3135_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3038_3038_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3093_3094": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3160_3160_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3119_3120": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2050_2050_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3001_3003": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3110_3111": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3011_3011_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3030_3040": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3076_3077": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_1012_1013": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2046_2046_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2014_2014_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3022_3026": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3124_3125": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3042_3042_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3115_3115_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2011_2011_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3071_3072": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3083_3083_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3045_3045_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3122_3122_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2040_2041": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3031_3031_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3099_3099_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2043_2044": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3006_3006_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_1016_1016_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3101_3101_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2053_2054": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_1016_1017": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_1011_1011_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3086_3086_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3064_3064_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3058_3058_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3069_3070": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3156_3157": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_1009_1009_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3095_3096": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2013_2014": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3140_3148": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2004_2005": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2047_2047_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3008_3015": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2033_2034": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3013_3013_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3137_3137_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2049_2050": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2056_2056_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3111_3112": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3117_3117_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3107_3108": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2031_2031_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3019_3020": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3162_3162_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_1005_1006": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3065_3066": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3020_3020_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3010_3010_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3063_3063_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2002_2002_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_1007_1007_l_2": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3090_3091": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2059_2059_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3150_3151": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_1010_1010_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2014_2016": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2052_2052_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3138_3139": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3049_3049_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3084_3084_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2055_2055_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3133_3134": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3134_3135": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3037_3037_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3110_3110_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3050_3051": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3051_3051_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3090_3090_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3121_3121_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3042_3043": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2054_2054_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3003_3005": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3036_3037": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3068_3075": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3016_3016_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3018_3018_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3130_3130_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3105_3106": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_1002_1003": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3024_3024_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2019_2020": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3098_3099": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3160_3161": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3063_3064": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3044_3044_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2054_2055": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3148_3149": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3078_3078_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3066_3067": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3033_3033_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2044_2045": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2010_2010_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3006_3007": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3033_3034": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3059_3059_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3065_3065_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3098_3098_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2007_2009": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2015_2015_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2040_2040_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2017_2018": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3018_3019": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_1009_1011": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2008_2008_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3022_3030": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3030_3035": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3082_3083": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3107_3118": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3146_3146_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2020_2020_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3147_3147_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2010_2011": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3053_3055": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3049_3050": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3085_3086": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3154_3155": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3146_3147": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3104_3105": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3032_3032_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3056_3057": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3073_3074": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2039_2040": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3025_3025_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3008_3009": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3148_3148_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2042_2042_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3087_3088": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3120_3121": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3015_3018": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3074_3074_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3158_3159": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3035_3036": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3048_3049": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2005_2006": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3129_3130": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_1007_1007_l_1": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3127_3128": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3102_3102_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3027_3027_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2029_2029_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3089_3090": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2019_2021": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3085_3085_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3086_3087": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2045_2045_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_1003_1003_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2029_2030": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3014_3014_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2057_2059": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2026_2027": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2053_2057": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3044_3045": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2057_2058": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2023_2023_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3152_3152_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_1013_1014": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3137_3138": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3058_3059": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3051_3052": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3100_3104": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3052_3052_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3121_3122": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3157_3158": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3028_3028_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3026_3027": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2045_2047": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_1008_1009": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3003_3002": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3005_3008": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3082_3092": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3156_3144": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3161_3161_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2022_2022_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3093_3093_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3031_3032": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2045_2046": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2007_2008": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3123_3124": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2055_2056": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3056_3056_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3062_3063": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3126_3127": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_1004_1004_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3129_3129_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3149_3149_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3039_3053": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3125_3126": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3073_3073_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3100_3101": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3004_3004_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3119_3123": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3159_3159_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2060_2060_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_1001_1002": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3053_3054": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2047_2048": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2044_2053": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3134_3134_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3076_3079": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3066_3066_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3128_3128_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3024_3025": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2035_2035_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3040_3044": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3077_3077_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3046_3048": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3092_3098": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3062_3062_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3060_3060_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_1008_1008_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3123_3123_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2016_2016_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2003_2003_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3095_3095_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_1006_1006_l_2": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3003_3004": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3016_3017": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2042_2043": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3107_3113": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3127_3127_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3028_3029": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2032_2032_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_2028_2029": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3007_3007_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3111_3111_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3151_3151_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3009_3010": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3060_3061": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3155_3155_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_1011_1012": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3015_3022": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3094_3094_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3157_3157_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3096_3097": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2001_2002": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3135_3136": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2017_2017_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3017_3017_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3113_3116": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3068_3069": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3149_3150": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2051_2052": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3145_3145_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2027_2032": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_1012_1012_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3077_3078": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3067_3067_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2041_2041_l": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3084_3085": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3064_3065": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3012_3012_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3131_3131_l": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3010_3011": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_2011_2012": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3040_3041": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3159_3160": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_1006_1008": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + }, + "l_3108_3109": { "vad_ub": [3.0], "cm_ub": [null], "vad_lb": [-3.0] }, + "l_3040_3046": { + "vad_ub": [3.0, 3.0, 3.0], + "cm_ub": [null, null, null], + "vad_lb": [-3.0, -3.0, -3.0] + } + }, + "options": { + "outputs": { + "sparse-fault-studies": true, + "pretty-print": true, + "debug-output": false + }, + "variables": { + "unbound-transformer-power": false, + "unbound-generation-power": false, + "unbound-storage-power": false, + "unbound-line-power": false, + "unbound-line-current": false, + "unbound-voltage": false, + "relax-integer-variables": false, + "unbound-switch-power": false + }, + "constraints": { + "disable-grid-forming-inverter-constraint": false, + "disable-switch-close-action-limit": false, + "enable-strictly-increasing-restoration-constraint": false, + "disable-thermal-limit-constraints": false, + "disable-storage-unbalance-constraint": false, + "disable-radiality-constraint": false, + "disable-current-limit-constraints": false, + "disable-block-isolation-constraint": false, + "disable-microgrid-networking": false, + "disable-microgrid-expansion": false + }, + "problem": { + "operations-problem-type": "block", + "operations-formulation": "LPUBFDiagPowerModel", + "dispatch-formulation": "LPUBFDiagPowerModel", + "operations-algorithm": "full-lookahead", + "dispatch-solver": "nlp_solver", + "operations-solver": "mip_solver", + "concurrent-fault-studies": true, + "concurrent-stability-studies": true + }, + "data": { + "fix-small-numbers": true, + "switch-close-actions-ub": [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + }, + "objective": { + "disable-load-block-shed-cost": false, + "disable-switch-state-change-cost": false, + "disable-generation-dispatch-cost": false, + "enable-switch-state-open-cost": true, + "disable-storage-discharge-cost": true, + "disable-load-block-weight-cost": false + } + }, + "transformer": { + "reg1": { "sm_ub": null }, + "sub_xfmr": { "sm_ub": null }, + "t_3082": { "sm_ub": null } + }, + "shunt": {}, + "solar": {}, + "load": {} +} diff --git a/schemas/input-events.schema.json b/schemas/input-events.schema.json index 693122e5..3c9eaee0 100644 --- a/schemas/input-events.schema.json +++ b/schemas/input-events.schema.json @@ -48,6 +48,23 @@ } ] }, + "title": "Events", + "x-tags": ["input", "output"], + "examples": [ + [ + { + "timestep": 1, + "event_type": "switch", + "affected_asset": "line.switch_name", + "event_data": { + "type": "breaker", + "state": "open", + "dispatchable": false, + "status": 1 + } + } + ] + ], "items": { "type": "object", "additionalProperties": false, @@ -88,17 +105,18 @@ "state": { "type": "string", "description": "What is the state of the switch, \"open\" or \"closed\"? Only used if \"event_type\" is \"switch\".", - "enum": ["open", "closed"], + "enum": ["open", "closed", "OPEN", "CLOSED"], "example": "open" }, "dispatchable": { - "type": "boolean", + "type": ["boolean", "string"], "description": "Is the affected object dispatchable? (i.e., can be opened or closed), default is false", - "default": false + "default": false, + "enum": ["YES", "NO", "yes", "no", true, false] }, "status": { - "type": "integer", - "enum": [0, 1], + "type": ["integer", "string"], + "enum": [0, 1, "DISABLED", "ENABLED", "enabled", "disabled"], "example": 1, "description": "Status of the object, if 0, completely outaged from the model. Default 1.", "default": 1 @@ -124,22 +142,5 @@ } }, "required": ["timestep", "event_type", "affected_asset", "event_data"] - }, - "title": "Events", - "x-tags": ["input", "output"], - "examples": [ - [ - { - "timestep": 1, - "event_type": "switch", - "affected_asset": "line.switch_name", - "event_data": { - "type": "breaker", - "state": "open", - "dispatchable": false, - "status": 1 - } - } - ] - ] + } } diff --git a/schemas/input-runtime_arguments.schema.json b/schemas/input-runtime_arguments.schema.json index 7573f8df..89540ce7 100644 --- a/schemas/input-runtime_arguments.schema.json +++ b/schemas/input-runtime_arguments.schema.json @@ -20,7 +20,7 @@ "faults": "../test/data/ieee13_faults.json", "inverters": "../test/data/ieee13_inverters.json", "events": "../test/data/ieee13_events.json", - "quiet": true, + "log-level": "error", "opt-disp-formulation": "acr" } ], @@ -30,204 +30,158 @@ "type": "string", "description": "The network file path (DSS format)" }, - "output": { - "type": "string", - "description": "The path to the output file (JSON format)" - }, - "faults": { + "settings": { "type": "string", - "description": "The path to the faults input file (JSON format)" + "description": "The path to the settings input file (JSON format)" }, "events": { "type": "string", "description": "The path to the events input file (JSON format)" }, + "faults": { + "type": "string", + "description": "The path to the faults input file (JSON format)" + }, "inverters": { "type": "string", "description": "The path to the inverters input file (JSON format)" }, - "settings": { + "output": { "type": "string", - "description": "The path to the settings input file (JSON format)" + "description": "The path to the output file (JSON format)" + }, + "gurobi": { + "type": "boolean", + "deprecated": true, + "description": "deprecated: solvers/useGurobi" + }, + "knitro": { + "type": "boolean", + "description": "deprecated: solvers/useKNITRO", + "deprecated": true + }, + "skip": { + "type": "array", + "uniqueItems": true, + "deprecated": true, + "description": "deprecated: options/problem/skip", + "items": { + "type": "string", + "enum": ["stability", "faults", "dispatch", "switching"] + } + }, + "log-level": { + "type": "string", + "deprecated": true, + "description": "deprecated: options/outputs/log-level", + "enum": ["debug", "info", "warn", "error"] }, "quiet": { "type": "boolean", - "default": false, - "description": "A flag to indicate the log level should be :Error" + "description": "missing", + "deprecated": true }, "verbose": { "type": "boolean", - "default": false, - "description": "A flag to indicate the log level should be :Info" + "description": "missing", + "deprecated": true }, "debug": { "type": "boolean", - "default": false, - "description": "A flag to indicate the log level should be :Debug" + "deprecated": true, + "description": "missing" }, - "gurobi": { - "type": "boolean", - "default": false, - "description": "A flag to signal to use the Gurobi solver" + "nprocs": { + "type": "integer", + "description": "Number of processors to utilize for parallel computations (Fault studies and Stability Analysis)", + "default": 1 }, - "knitro": { + "pretty-print": { "type": "boolean", - "default": false, - "description": "A flag to signal to use the KNITRO solver" + "description": "deprecated: options/output/pretty-print", + "default": false }, "opt-switch-formulation": { "type": "string", "enum": ["lindistflow", "nfa", "fbs", "fot"], - "default": "lindistflow" + "deprecated": true, + "description": "deprecated: options/problem/operations-formulation" }, "opt-switch-algorithm": { "type": "string", - "description": "Flag to use multinetwork problem for optimal switching algorithm", + "description": "deprecated: options/problem/operations-algorithm", "enum": ["iterative", "global"], - "default": "global" + "deprecated": true }, "opt-switch-solver": { "type": "string", "enum": ["misocp_solver", "nlp_solver", "minlp_solver", "mip_solver"], - "default": "misocp_solver" + "deprecated": true, + "description": "deprecated: options/problem/operations-solver" + }, + "opt-switch-problem": { + "type": "string", + "enum": ["traditional", "block"], + "deprecated": true, + "description": "deprecated: options/problem/operations-problem-type" }, "opt-disp-formulation": { "type": "string", "enum": ["acp", "acr", "lindistflow", "nfa", "fbs", "fot"], - "description": "Formulation to use for [`optimize_dispath`](@ref optimize_dispatch), which includes the real power only formulation (`nfa`), the LinDist3Flow formulation (`lindistflow`), the AC-rectangular formulation (`acr`), or the AC-polar formulation (`acp`)" + "description": "deprecated: options/problem/dispatch-formulation", + "deprecated": true }, "opt-disp-algorithm": { "type": "string", "enum": ["opf", "mld", "oltc"], - "default": "opf" + "deprecated": true, + "description": "deprecated: missing" }, "opt-disp-solver": { "type": "string", "enum": ["nlp_solver", "misocp_solver", "minlp_solver", "mip_solver"], - "description": "Which solver (from the solver instances built-in to ONM) to use for the optimal dispatch problem. WARNING: solver capabilities should match the optimal dispatch formulation", - "default": "nlp_solver" + "description": "deprecated: options/problem/dispatch-solver", + "deprecated": true }, "fix-small-numbers": { "type": "boolean", - "description": "Will prune small impedances, admittances, and lengths, for better problem solving stability" + "description": "deprecated: options/data/fix-small-numbers", + "deprecated": true }, - "network-file": { - "type": "string", - "deprecated": true, - "description": "DEPRECIATED: the path to the network file (DSS format)" - }, - "output-file": { - "type": "string", - "deprecated": true, - "description": "DEPRECIATED: the path to the output file (JSON format)" - }, - "problem": { - "type": "string", - "deprecated": true, - "enum": ["opf", "mld"], - "description": "DEPRECIATED: algorithm to use for [`optimize_dispatch`](@ref optimize_dispatch), either the optimal power flow (`opf`) or load shed (`mld`)" - }, - "formulation": { - "type": "string", - "deprecated": true, - "enum": ["acr", "acp", "lindistflow", "nfa"], - "description": "DEPRECIATED: Formulation to use for [`optimize_dispath`](@ref optimize_dispatch), which includes the real power only formulation (`nfa`), the LinDist3Flow formulation (`lindistflow`), the AC-rectangular formulation (`acr`), or the AC-polar formulation (`acp`)" - }, - "protection-settings": { - "type": "string", - "deprecated": true, - "description": "DEPRECIATED: enumerations of the protection settings (XLSX format)" - }, - "debug-export-file": { - "type": "string", - "deprecated": true, - "description": "DEPRECIATED: the path to the debug output (JSON format)" - }, - "use-gurobi": { + "disable-switch-penalty": { "type": "boolean", - "default": false, - "deprecated": true, - "description": "DEPRECIATED: flag to indicate usage of the Gurobi solver" - }, - "solver-tolerance": { - "type": "number", - "default": 0.0001, - "deprecated": true, - "description": "DEPRECIATED: tolerance of the nonlinear optimization solver" - }, - "max-switch-actions": { - "type": "integer", - "default": 0, - "deprecated": true, - "description": "DEPRECIATED: maximum allowed switching actions" - }, - "timestep-hours": { - "type": "number", - "default": 1, - "deprecated": true, - "description": "DEPRECIATED: the timestep delta, in hours" - }, - "voltage-lower-bound": { - "default": 0.8, - "deprecated": true, - "type": "number", - "description": "DEPRECIATED: the voltage magnitude lower bound for buses, in per-unit representation" - }, - "voltage-upper-bound": { - "type": "number", - "default": 1.2, - "deprecated": true, - "description": "DEPRECIATED: the voltage magnitude upper bound for buses in per-unit representation" - }, - "voltage-angle-difference": { - "type": "number", - "default": 5, - "deprecated": true, - "description": "DEPRECIATED: the maximum allowed voltage angle difference across lines, in degrees" + "description": "deprecated: options/objective/disable-switch-state-change-cost", + "deprecated": true }, - "clpu-factor": { - "type": "number", - "description": "DEPRECIATED: the cold load pickup factor" - }, - "skip": { - "type": "array", - "uniqueItems": true, - "description": "Parts of the ONM algorithm to skip, e.g. \"--skip faults\" to skip fault studies, or \"--skip faults,stability\" to skip both fault studies and stability analysis", - "items": { - "type": "string", - "enum": ["stability", "faults", "dispatch", "switching"] - } - }, - "pretty-print": { + "apply-switch-scores": { "type": "boolean", - "description": "Toggle to enable pretty-printed output json", - "default": false + "description": "deprecated: options/objective/enable-switch-state-open-cost", + "deprecated": true }, - "nprocs": { - "type": "integer", - "description": "Number of processors to utilize for parallel computations (Fault studies and Stability Analysis)", - "minimum": 1, - "default": 1 + "disable-radial-constraint": { + "type": "boolean", + "deprecated": true, + "description": "deprecated: options/constraints/disable-radiality-constraint" }, - "disable-switch-penalty": { + "disable-isolation-constraint": { "type": "boolean", - "description": "Toggle to disable applying a penalty to changing switch status in the objective function", - "default": true + "description": "deprecated: options/constraints/disable-block-isolation-constraint", + "deprecated": true }, - "apply-switch-scores": { + "disable-inverter-constraint": { "type": "boolean", - "description": "Toggle to enable adding a weight to switches in the objective function, to promote closing", - "default": false + "description": "deprecated: options/constraints/disable-grid-forming-inverter-constraint", + "deprecated": true }, - "disable-radial-constraint": { + "disable-networking": { "type": "boolean", - "description": "Toggle to disable the radial constraint (allow mesh-networking)", - "default": true + "deprecated": true, + "description": "deprecated: options/constraints/disable-microgrid-networking" }, - "disable-isolation-constraint": { + "disable-presolver": { "type": "boolean", - "description": "Toggle to disable the block isolation constraint (allow power to pass through 'shedded' blocks)", - "default": true + "deprecated": true, + "description": "deprecated: {solvers/HiGHS/presolve,solvers/Gurobi/Presolve,solvers/KNITRO/presolve}" } }, "required": ["network"] diff --git a/schemas/input-settings.schema.json b/schemas/input-settings.schema.json index adede71a..09dc5903 100644 --- a/schemas/input-settings.schema.json +++ b/schemas/input-settings.schema.json @@ -6,17 +6,27 @@ "You may not use this file except in compliance with the License. You may obtain a copy", "of the License at https://github.com/lanl-ansi/PowerModelsONM.jl/blob/main/LICENSE.md" ], - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "schemas/settings", "type": "object", "title": "Settings", "additionalProperties": false, "description": "Network settings to apply to the base network (_i.e._, all multinetwork timesteps, not a specific one), such as bus voltage bounds, load priorities, cold load pickup, and global settings such as maximum switch actions per timestep and time elapsed per time step.\n\n Additional properties on each object are allowed, but the object types are restricted to those in the ONM model.", - "x-tags": ["input"], + "x-tags": [ + "input" + ], "examples": [ { - "max_switch_actions": [1, 1, 1], - "time_elapsed": [0.1667, 0.1667, 0.1667], + "switch_close_actions_ub": [ + 1, + 1, + 1 + ], + "time_elapsed": [ + 0.1667, + 0.1667, + 0.1667 + ], "load": { "671": { "clpu_factor": 2 @@ -35,7 +45,7 @@ "$ref": "./settings/input-settings-solar.schema.json" }, "voltage_source": { - "$ref": "./output-optimization_metadata.schema.json" + "$ref": "./settings/input-settings-voltage_source.schema.json" }, "storage": { "$ref": "./settings/input-settings-storage.schema.json" @@ -58,54 +68,97 @@ "transformer": { "$ref": "./settings/input-settings-transformer.schema.json" }, + "options": { + "$ref": "./settings/input-settings-options.schema.json" + }, + "solvers": { + "$ref": "./settings/input-settings-solvers.schema.json" + }, + "dss": { + "$ref": "./settings/input-settings-dss.schema.json" + }, "max_switch_actions": { - "type": ["array", "number"], - "description": "The maximum allowed switching actions per timestep (or at each timestep, if an array)", + "type": [ + "array", + "integer", + "null" + ], + "description": "deprecated: options/data/switch-close-actions-ub", + "deprecated": true, "items": { - "type": "number" + "type": [ + "integer", + "null" + ] } }, "disable_networking": { - "type": ["boolean"], - "description": "Disables microgrid networking" + "type": "boolean", + "description": "deprecated: options/constraints/disable-microgrid-networking", + "default": false, + "deprecated": true }, "disable_radial_constraint": { - "type": ["boolean"], - "description": "Disables radiality constraints" + "type": "boolean", + "description": "deprecated: options/constraints/disable-radiality-constraint", + "default": false, + "deprecated": true }, "disable_isolation_constraint": { - "type": ["boolean"], - "description": "Disables block isolation constraint" + "type": "boolean", + "description": "deprecated: options/constraints/disable-block-isolation-constraint", + "default": false, + "deprecated": true + }, + "disable_inverter_constraint": { + "type": "boolean", + "default": false, + "deprecated": true, + "description": "deprecated: options/constraints/disable-grid-forming-inverter-constraint" }, "disable_switch_penalty": { - "type": ["boolean"], - "description": "apply penalty to switching in objective" + "type": "boolean", + "description": "deprecated: options/objective/disable-switch-state-change-cost", + "deprecated": true }, "apply_switch_scores": { - "type": ["boolean"], - "description": "apply weight to switches in objective" + "type": "boolean", + "description": "deprecated: options/objective/enable-switch-state-open-cost", + "deprecated": true + }, + "disable_presolver": { + "type": "boolean", + "description": "deprecated: {solvers/HiGHS/presolve,solvers/Gurobi/Presolve,solvers/KNITRO/presolve}", + "default": false, + "deprecated": true }, "time_elapsed": { - "type": ["array", "number"], - "description": "The time elapsed, in hours for each timestep, or individually after each timestep if an array", + "type": [ + "array", + "number" + ], + "description": "deprecated: options/data/time-elapsed", + "deprecated": true, "items": { "type": "number" } }, "nlp_solver_tol": { "type": "number", - "description": "The tolerance of the nlp solver" + "deprecated": true, + "description": "deprecated: {solvers/Ipopt/tol,solvers/KNITRO/feastol}" }, "mip_solver_tol": { "type": "number", - "description": "The tolerance of the mip solver" + "description": "deprecated: {solvers/HiGHS/primal_feasibility_tolerance,solvers/HiGHS/dual_feasibility_tolerance,solvers/Gurobi/FeasibilityTol,solvers/Juniper/atol}", + "deprecated": true }, "mip_solver_gap": { "type": "number", "exclusiveMinimum": 0, "default": 0.05, "format": "float", - "description": "MIP Gap for MIP/MISOCP solvers" - } - } + "description": "deprecated: {solvers/HiGHS/mip_rel_gap,solvers/Gurobi/MIPGap,solvers/Juniper/mip_gap}", + "deprecated": true + } } } diff --git a/schemas/output-additional_statistics.schema.json b/schemas/output-additional_statistics.schema.json index 437db737..7f1a7223 100644 --- a/schemas/output-additional_statistics.schema.json +++ b/schemas/output-additional_statistics.schema.json @@ -25,60 +25,5 @@ } ] } - ], - "properties": { - "Protection settings": { - "type": "array", - "description": "Example of additional information that can be included in Additional statistics", - "items": { - "type": "object", - "properties": { - "primary pickup (A)": { - "type": "array", - "items": { - "type": "number" - } - }, - "TDS": { - "type": "array", - "items": { - "type": "number" - } - }, - "to": { - "type": "array", - "items": { - "type": "string" - } - }, - "from": { - "type": "array", - "items": { - "type": "string" - } - }, - "TOC": { - "type": "array", - "items": { - "type": "string" - } - }, - "operating time (s)": { - "type": "array", - "items": { - "type": "number" - } - } - }, - "required": [ - "primary pickup (A)", - "TDS", - "to", - "from", - "TOC", - "operating time (s)" - ] - } - } - } + ] } diff --git a/schemas/output-device_action_timeline.schema.json b/schemas/output-device_action_timeline.schema.json index d65e4aa2..f04a5e0a 100644 --- a/schemas/output-device_action_timeline.schema.json +++ b/schemas/output-device_action_timeline.schema.json @@ -10,16 +10,12 @@ "$id": "schemas/device_action_timeline", "title": "Device action timeline", "type": "array", - "x-tags": [ - "output" - ], + "x-tags": ["output"], "description": "Object for each timestep, in order, containing information about the switch topology and a list of each load that has been shed at that timestep.", "examples": [ [ { - "Shedded loads": [ - "load.123" - ], + "Shedded loads": ["load.123"], "Switch configurations": { "switch.123": "open", "switch.456": "closed" @@ -55,10 +51,7 @@ "^.+$": { "type": "string", "title": "switch state", - "enum": [ - "open", - "closed" - ], + "enum": ["open", "closed"], "description": "switch_name: state" } } @@ -76,4 +69,4 @@ } } } -} \ No newline at end of file +} diff --git a/schemas/output-powerflow.schema.json b/schemas/output-powerflow.schema.json index eb08be0b..4fb28b14 100644 --- a/schemas/output-powerflow.schema.json +++ b/schemas/output-powerflow.schema.json @@ -48,7 +48,7 @@ "additionalProperties": false, "description": "Timestep object", "properties": { - "generator": { + "voltage_source": { "type": "object", "additionalProperties": false, "description": "Generation types: diesel generators (`generator`), or substations (`voltage_source`), or solar PV (`solar`), or energy storage (`storage`)", @@ -74,16 +74,21 @@ }, "connections": { "type": "array", - "description": "The connections (terminals) that the generator is connected to, in order", + "description": "The connections (terminals) that the voltage source is connected to, in order", "items": { "type": "integer" } + }, + "inverter": { + "type": "string", + "enum": ["GRID_FOLLOWING", "GRID_FORMING"], + "description": "Indicator for whether the generation object is grid-forming or grid-following" } } } } }, - "voltage_source": { + "generator": { "type": "object", "additionalProperties": false, "description": "Generation types: diesel generators (`generator`), or substations (`voltage_source`), or solar PV (`solar`), or energy storage (`storage`)", @@ -109,10 +114,14 @@ }, "connections": { "type": "array", - "description": "The connections (terminals) that the voltage source is connected to, in order", + "description": "The connections (terminals) that the generator is connected to, in order", "items": { "type": "integer" } + }, + "inverter": { + "type": "string", + "enum": ["GRID_FOLLOWING", "GRID_FORMING"] } } } @@ -148,6 +157,10 @@ "items": { "type": "integer" } + }, + "inverter": { + "type": "string", + "enum": ["GRID_FOLLOWING", "GRID_FORMING"] } } } @@ -183,6 +196,10 @@ "items": { "type": "integer" } + }, + "inverter": { + "type": "string", + "enum": ["GRID_FOLLOWING", "GRID_FORMING"] } } } diff --git a/schemas/output-protection.schema.json b/schemas/output-protection.schema.json index dcdb0287..33ec017c 100644 --- a/schemas/output-protection.schema.json +++ b/schemas/output-protection.schema.json @@ -2,7 +2,6 @@ "title": "protection.schema", "type": "object", "description": "Protection settings outputs", - "required": ["network_model"], "properties": { "network_model": { "type": "object", @@ -29,6 +28,10 @@ "nphases": { "type": "integer", "description": "Number of phases on the bus" + }, + "status": { + "type": "integer", + "enum": [0, 1] } } } @@ -73,6 +76,10 @@ "switch": { "type": "boolean", "description": "Is the line a switch?" + }, + "status": { + "type": "integer", + "enum": [0, 1] } } } @@ -131,6 +138,10 @@ "rating (kVA)": { "type": "number", "description": "Thermal rating of the transformer in kVA" + }, + "status": { + "type": "integer", + "enum": [0, 1] } } } @@ -165,6 +176,10 @@ "nphases": { "type": "integer", "description": "Number of phases in the source object" + }, + "status": { + "type": "integer", + "enum": [0, 1] } } } @@ -210,7 +225,56 @@ "settings": { "type": "array", "description": "Output from Protection optimization algorithm", - "items": {} + "items": { + "type": "object", + "properties": { + "primary pickup (A)": { + "type": "array", + "items": { + "type": "number" + } + }, + "TDS": { + "type": "array", + "items": { + "type": "number" + } + }, + "to": { + "type": "array", + "items": { + "type": "string" + } + }, + "from": { + "type": "array", + "items": { + "type": "string" + } + }, + "TOC": { + "type": "array", + "items": { + "type": "string" + } + }, + "operating time (s)": { + "type": "array", + "items": { + "type": "number" + } + } + }, + "required": [ + "primary pickup (A)", + "TDS", + "to", + "from", + "TOC", + "operating time (s)" + ] + } } - } + }, + "required": ["network_model"] } diff --git a/schemas/output-system_metadata.schema.json b/schemas/output-system_metadata.schema.json index b408e6d8..52bd2ebb 100644 --- a/schemas/output-system_metadata.schema.json +++ b/schemas/output-system_metadata.schema.json @@ -2,47 +2,69 @@ "title": "output.system_metadata.schema", "type": "object", "description": "Metadata about the system that ran the ONM process", - "required": [ - "platform", - "physical_cores", - "logical_processors", - "system_memory", - "julia_max_threads", - "julia_max_procs", - "julia_version" - ], "properties": { "platform": { "type": "string", - "description": "output of Julia Sys.MACHINE" + "description": "output of Julia Sys.MACHINE", + "default": "string(Sys.MACHINE)", + "readOnly": true }, "cpu_info": { "type": "string", - "description": "Output of Julia first(Sys.cpu_info()).model" + "description": "Output of Julia first(Sys.cpu_info()).model", + "default": "string(first(Sys.cpu_info()).model)", + "readOnly": true }, "physical_cores": { "type": "integer", - "description": "Output of Julia Hwloc.num\\_physical\\_cores()" + "description": "Output of Julia Hwloc.num\\_physical\\_cores()", + "example": 32, + "default": "Hwloc.num_physical_cores()", + "readOnly": true }, "logical_processors": { "type": "integer", - "description": "Output of Julia Hwloc.num\\_virtual\\_cores()" + "description": "Output of Julia Hwloc.num\\_virtual\\_cores()", + "default": "Hwloc.num_virtual_cores()", + "example": 16, + "readOnly": true }, "system_memory": { "type": "number", - "description": "Output of Julia Sys.total_memory() in GB" + "description": "Output of Julia Sys.total_memory() in GB", + "default": "round(Int, Sys.total_memory() / 2^20 / 1024)", + "example": 16, + "readOnly": true }, "julia_max_threads": { "type": "integer", - "description": "Output of Julia Threads.nthreads()" + "description": "Output of Julia Threads.nthreads()", + "default": "Threads.nthreads()", + "example": 1, + "readOnly": true }, "julia_max_procs": { "type": "integer", - "description": "Number of processors available to Julia" + "description": "Number of processors available to Julia", + "default": "Distributed.nprocs()", + "example": 1, + "readOnly": true }, "julia_version": { "type": "string", - "description": "Output of Julia.VERSION" + "description": "Output of Julia.VERSION", + "default": "string(Base.VERSION)", + "example": "1.7.2", + "readOnly": true } - } + }, + "required": [ + "platform", + "physical_cores", + "logical_processors", + "system_memory", + "julia_max_threads", + "julia_max_procs", + "julia_version" + ] } diff --git a/schemas/output.schema.json b/schemas/output.schema.json index 4da76e7c..2afd61e9 100644 --- a/schemas/output.schema.json +++ b/schemas/output.schema.json @@ -151,7 +151,10 @@ "Runtime timestamp": { "type": "string", "description": "Time at which ONM algorithm was executed", - "format": "date-time" + "format": "date-time", + "example": "2020-01-01T12:00:00.000", + "default": "string(Dates.now())", + "readOnly": true }, "Simulation time steps": { "$ref": "./output-timesteps.schema.json" @@ -221,7 +224,6 @@ "Storage SOC (%)", "Device action timeline", "Powerflow output", - "Runtime arguments", "Switch changes", "System metadata", "Protection settings" diff --git a/schemas/settings/input-settings-dss.schema.json b/schemas/settings/input-settings-dss.schema.json new file mode 100644 index 00000000..b486f685 --- /dev/null +++ b/schemas/settings/input-settings-dss.schema.json @@ -0,0 +1,37 @@ +{ + "title": "input-settings-dss.schema", + "type": "object", + "definitions": { + "inverterToggle": { + "type": "string", + "enum": [ + "GRID_FORMING", + "grid_forming", + "gfm", + "GRID_FOLLOWING", + "grid_following", + "gfl" + ] + }, + "statusToggle": { + "type": "string", + "enum": ["yes", "no", "YES", "NO", "y", "n", "Y", "N"] + } + }, + "patternProperties": { + "^.+$": { + "type": "object", + "additionalProperties": false, + "properties": { + "inverter": { + "type": "string", + "$ref": "#/definitions/inverterToggle" + }, + "enabled": { + "type": "string", + "$ref": "#/definitions/statusToggle" + } + } + } + } +} diff --git a/schemas/settings/input-settings-generator.schema.json b/schemas/settings/input-settings-generator.schema.json index e318d840..0368227e 100644 --- a/schemas/settings/input-settings-generator.schema.json +++ b/schemas/settings/input-settings-generator.schema.json @@ -58,6 +58,21 @@ "type": ["integer", "string"], "enum": [0, 1, "ENABLED", "DISABLED"], "description": "The status of the generation object, _i.e._ enabled (`1`) or disabled (`0`)" + }, + "cost_pg_parameters": { + "type": "array", + "description": "Cost model polynomial or piecewise parameters", + "items": { + "type": "number" + } + }, + "cost_pg_model": { + "type": "integer", + "description": "Cost model type, 1 = piecewise-linear, 2 = polynomial" + }, + "inverter": { + "type": "string", + "enum": ["GRID_FOLLOWING", "GRID_FORMING"] } } } diff --git a/schemas/settings/input-settings-options.schema.json b/schemas/settings/input-settings-options.schema.json new file mode 100644 index 00000000..5b1bf0a9 --- /dev/null +++ b/schemas/settings/input-settings-options.schema.json @@ -0,0 +1,297 @@ +{ + "title": "input.settings.options.schema", + "type": "object", + "description": "", + "properties": { + "data": { + "type": "object", + "properties": { + "time-elapsed": { + "type": ["number", "array"], + "items": { + "type": "number" + } + }, + "switch-close-actions-ub": { + "type": ["number", "array", "null"], + "default": null, + "items": { + "type": ["number", "null"] + } + }, + "fix-small-numbers": { + "type": "boolean", + "default": false + } + } + }, + "variables": { + "type": "object", + "properties": { + "relax-integer-variables": { + "description": "Flag to relax an integer (binary) variables to be continuous", + "type": "boolean", + "default": false + }, + "unbound-voltage": { + "description": "Flag to disable the upper bounds on voltage. Voltage magnitudes will still have a lower bound of 0.0", + "type": "boolean", + "default": false + }, + "unbound-line-power": { + "description": "Flag to disable bounds on line (branch) power variables", + "type": "boolean", + "default": false + }, + "unbound-line-current": { + "description": "Flag to disable bounds on line (branch) current variables", + "type": "boolean", + "default": false + }, + "unbound-switch-power": { + "description": "Flag to disable bounds on switch power variables", + "type": "boolean", + "default": false + }, + "unbound-transformer-power": { + "description": "Flag to disable bounds on transformer power variables", + "type": "boolean", + "default": false + }, + "unbound-generation-power": { + "description": "Flag to disable bounds on gen (generator, solar, voltage_source) power variables", + "type": "boolean", + "default": false + }, + "unbound-storage-power": { + "description": "Flag to disable bounds on storage variables, including power, charge, discharge, and energy", + "type": "boolean", + "default": false + } + } + }, + "constraints": { + "type": "object", + "properties": { + "disable-grid-forming-inverter-constraint": { + "description": "Flag to disable the grid-forming inverter constraint, which enforces that there be exactly one grid-forming inverter in each energized connected component", + "type": "boolean", + "default": false + }, + "disable-block-isolation-constraint": { + "description": "Flag to disable the block isolation constraint, which requires that switches between load blocks of differing status be open", + "type": "boolean", + "default": false + }, + "disable-radiality-constraint": { + "description": "Flag to disable the radiality constraint, which requires that each energized connected component be radial in its topology (no cycles)", + "type": "boolean", + "default": false + }, + "disable-microgrid-networking": { + "description": "Flag to disable microgrid networking. When enabled, microgrids are allowed to expand (pick up load), but may not network with one another", + "type": "boolean", + "default": false + }, + "disable-current-limit-constraints": { + "description": "Flag to disable current limit constraints (ampacity) on lines and switches", + "type": "boolean", + "default": false + }, + "disable-thermal-limit-constraints": { + "description": "Flag to disable thermal limit constraints (power) on lines, switches, and energy storage", + "type": "boolean", + "default": false + }, + "disable-switch-close-action-limit": { + "description": "Flag to disable the switch close-action limit, which limits the number of closing actions that may occur in a timestep", + "type": "boolean", + "default": false + }, + "disable-storage-unbalance-constraint": { + "description": "Flag to disable the storage unbalance constraint, which requires that grid-following storage devices output power within some factor of one another", + "type": "boolean", + "default": false + }, + "disable-microgrid-expansion": { + "description": "Flag to disable microgrid expansion. When enabled, would prevent microgrids from growing any larger, but not prevent switching actions within the boundary of a microgrid", + "type": "boolean", + "default": false + }, + "enable-strictly-increasing-restoration-constraint": { + "description": "Flag to enable a constraint that requires that load restoration be strictly increasing, that is, load blocks once energized cannot be de-energized in a later timestep", + "type": "boolean", + "default": false + } + } + }, + "objective": { + "type": "object", + "properties": { + "disable-switch-state-change-cost": { + "description": "Flag to disable the cost term in the objective function related to change of state in switches (open-close, close-open)", + "type": "boolean", + "default": false + }, + "enable-switch-state-open-cost": { + "description": "Flag to enable the cost term that adds a cost for open switches based on how much load is between them and some source of energy", + "type": "boolean", + "default": false + }, + "disable-generation-dispatch-cost": { + "description": "Flag to disable the generation cost term", + "type": "boolean", + "default": false + }, + "disable-storage-discharge-cost": { + "description": "Flag to disable the storage discharge cost term, which adds a penalty for storage that is not completely charged to its upper bound", + "type": "boolean", + "default": false + }, + "disable-load-block-weight-cost": { + "description": "Flag to disable the weight terms on the load-block cost term (i.e., make the cost of every shed load block be equal)", + "type": "boolean", + "default": false + }, + "disable-load-block-shed-cost": { + "description": "Flag to disable the cost of not energizing load blocks", + "type": "boolean", + "default": false + } + } + }, + "problem": { + "type": "object", + "properties": { + "operations-algorithm": { + "description": "Choice for operations optimization algorithm, 'rolling-horizon' or 'full-lookahead'", + "type": "string", + "enum": ["rolling-horizon", "full-lookahead"], + "default": "full-lookahead" + }, + "operations-formulation": { + "description": "Choice for operations optimization formulation", + "type": "string", + "enum": [ + "lindistflow", + "transportation", + "acp", + "acr", + "nfa", + "lpubfdiag", + "LPUBFDiagPowerModel", + "ACPUPowerModel", + "ACRUPowerModel", + "NFAUPowerModel" + ], + "default": "LPUBFDiagPowerModel" + }, + "operations-problem-type": { + "description": "Choice for operations optimization problem type, 'block' or 'traditional'", + "type": "string", + "enum": ["block", "traditional"], + "default": "block" + }, + "operations-solver": { + "description": "Choice for operations optimization solver", + "type": "string", + "enum": ["minlp_solver", "mip_solver", "misocp_solver", "nlp_solver"], + "default": "mip_solver" + }, + "dispatch-formulation": { + "description": "Choice for dispatch optimizatioin formulation", + "type": "string", + "enum": [ + "nfa", + "lindistflow", + "acp", + "acr", + "ivr", + "transportation", + "lpubfdiag", + "NFAUPowerModel", + "LPUBFDiagPowerModel", + "ACRUPowerModel", + "ACPUPowerModel", + "IVRUPowerModel" + ], + "default": "LPUBFDiagPowerModel" + }, + "dispatch-solver": { + "description": "Choice for dispatch optimization solver", + "type": "string", + "enum": ["mip_solver", "misocp_solver", "minlp_solver", "nlp_solver"], + "default": "nlp_solver" + }, + "fault-studies-solver": { + "description": "Choice for fault studies optimization solver", + "type": "string", + "enum": ["nlp_solver"], + "default": "nlp_solver" + }, + "stability-solver": { + "description": "Choice for stability analysis optimization solver", + "type": "string", + "enum": ["nlp_solver"], + "default": "nlp_solver" + }, + "stability-formulation": { + "description": "Choice for stability analysis formulation", + "type": "string", + "enum": ["acp", "acr", "ACRUPowerModel", "ACPUPowerModel"], + "default": "ACRUPowerModel" + }, + "concurrent-fault-studies": { + "description": "Flag to run the fault studies concurrently (in parallel, using Distributed)", + "type": "boolean", + "default": true + }, + "concurrent-stability-studies": { + "description": "Flag to run the stability studies concurrently (in parallel, using Distributed)", + "type": "boolean", + "default": true + }, + "skip": { + "description": "List of optimizations or studies to skip, when running using `entrypoint` function", + "type": "array", + "items": { + "type": "string", + "enum": [ + "faults", + "stability", + "switching", + "dispatch", + "protection" + ] + } + } + } + }, + "outputs": { + "type": "object", + "properties": { + "log-level": { + "description": "Setting to adjust the console log-level", + "type": "string", + "enum": ["debug", "info", "warn", "error"], + "default": "warn" + }, + "pretty-print": { + "description": "Flag to enable 'pretty printing' of JSON output", + "type": "boolean", + "default": true + }, + "sparse-fault-studies": { + "description": "Flag to disable the create of sparse fault studies (if no faults are specified, one of every type will be applied at every bus", + "type": "boolean", + "default": true + }, + "debug-output": { + "description": "Flag to enable debugging outputs (the full data structure), when used with entrypoint", + "type": "boolean", + "default": false + } + } + } + } +} diff --git a/schemas/settings/input-settings-solar.schema.json b/schemas/settings/input-settings-solar.schema.json index 1be2e6e5..a704cb58 100644 --- a/schemas/settings/input-settings-solar.schema.json +++ b/schemas/settings/input-settings-solar.schema.json @@ -58,6 +58,21 @@ "type": ["integer", "string"], "enum": [0, 1, "ENABLED", "DISABLED"], "description": "The status of the generation object, _i.e._ enabled (`1`) or disabled (`0`)" + }, + "cost_pg_parameters": { + "type": "array", + "description": "Cost model polynomial or piecewise parameters", + "items": { + "type": "number" + } + }, + "cost_pg_model": { + "type": "integer", + "description": "Cost model type, 1 = piecewise-linear, 2 = polynomial" + }, + "inverter": { + "type": "string", + "enum": ["GRID_FOLLOWING", "GRID_FORMING"] } } } diff --git a/schemas/settings/input-settings-solvers.schema.json b/schemas/settings/input-settings-solvers.schema.json new file mode 100644 index 00000000..9f84a6f1 --- /dev/null +++ b/schemas/settings/input-settings-solvers.schema.json @@ -0,0 +1,203 @@ +{ + "title": "input.settings.solvers.schema", + "type": "object", + "description": "Solver options", + "properties": { + "HiGHS": { + "description": "These are the options used for the HiGHS optimization solver. The options below are a set of sane defaults for this solver, but users are not restricted to these options, and may use any valid options for this solver", + "type": "object", + "properties": { + "output_flag": { + "type": "boolean", + "default": false + }, + "presolve": { + "type": "string", + "default": "choose", + "enum": ["on", "off", "choose"] + }, + "primal_feasibility_tolerance": { + "type": "number", + "default": 0.000001 + }, + "dual_feasibility_tolerance": { + "type": "number", + "default": 0.000001 + }, + "mip_feasibility_tolerance": { + "type": "number", + "default": 0.0001 + }, + "mip_rel_gap": { + "type": "number", + "default": 0.0001 + }, + "small_matrix_value": { + "type": "number", + "default": 1e-12 + }, + "allow_unbounded_or_infeasible": { + "type": "boolean", + "default": true + } + } + }, + "Ipopt": { + "description": "These are the options used for the Ipopt optimization solver. The options below are a set of sane defaults for this solver, but users are not restricted to these options, and may use any valid options for this solver", + "type": "object", + "properties": { + "tol": { + "type": "number", + "default": 0.000001 + }, + "mumps_mem_percent": { + "type": "number", + "default": 200 + }, + "mu_strategy": { + "type": "string", + "default": "adaptive", + "enum": ["adaptive", "monotone"] + }, + "print_level": { + "type": "integer", + "default": 0 + } + } + }, + "Juniper": { + "description": "These are the options used for the Juniper optimization solver. The options below are a set of sane defaults for this solver, but users are not restricted to these options, and may use any valid options for this solver", + "type": "object", + "properties": { + "branch_strategy": { + "type": "string", + "default": ":StrongPseuoCost", + "enum": [ + ":MostInfeasible", + ":PseudoCost", + ":StrongPseuoCost", + ":Reliability" + ] + }, + "log_levels": { + "type": "array", + "items": { + "type": "string", + "enum": [":Table", ":Info", ":Options"] + } + }, + "mip_gap": { + "type": "number", + "default": 0.0001 + }, + "traverse_strategy": { + "type": "string", + "enum": [":DFS", ":BFS", ":DBFS"], + "default": ":BFS" + }, + "allow_almost_solved_integral": { + "type": "boolean", + "default": true + }, + "allow_almost_solved": { + "type": "boolean", + "default": true + }, + "atol": { + "type": "number", + "default": 0.000001 + } + } + }, + "Gurobi": { + "description": "These are the options used for the Gurobi optimization solver. The options below are a set of sane defaults for this solver, but users are not restricted to these options, and may use any valid options for this solver", + "type": "object", + "properties": { + "OutputFlag": { + "type": "integer", + "default": 0 + }, + "GURO_PAR_DUMP": { + "type": "integer", + "default": 0 + }, + "MIPGap": { + "type": "number", + "default": 0.0001 + }, + "FeasibilityTol": { + "type": "number", + "default": 0.000001 + }, + "Quad": { + "type": "integer", + "default": 1 + }, + "NumericFocus": { + "type": "integer", + "default": 3 + }, + "MIPFocus": { + "type": "integer", + "default": 2 + }, + "DualReductions": { + "type": "integer", + "default": 0, + "minimum": -1, + "maximum": 1 + }, + "Presolve": { + "type": "integer", + "default": -1, + "minimum": -1, + "maximum": 2 + } + } + }, + "KNITRO": { + "description": "These are the options used for the KNITRO optimization solver. The options below are a set of sane defaults for this solver, but users are not restricted to these options, and may use any valid options for this solver", + "type": "object", + "properties": { + "outlev": { + "type": "integer", + "minimum": 0, + "default": 0, + "maximum": 6 + }, + "mip_outlevel": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 3 + }, + "opttol": { + "type": "number", + "default": 0.0001 + }, + "feastol": { + "type": "number", + "default": 0.000001 + }, + "algorithm": { + "type": "integer", + "default": 3 + }, + "presolve": { + "type": "integer", + "default": 1 + } + } + }, + "useGurobi": { + "description": "Flag to enable the usage of the Gurobi commercial solver", + "type": "boolean", + "default": false + }, + "useKNITRO": { + "description": "Flag to enable the usage of the KNITRO commercial solver", + "type": "boolean", + "default": false + } + } +} diff --git a/schemas/settings/input-settings-storage.schema.json b/schemas/settings/input-settings-storage.schema.json index e10e974c..956d965c 100644 --- a/schemas/settings/input-settings-storage.schema.json +++ b/schemas/settings/input-settings-storage.schema.json @@ -28,11 +28,11 @@ "description": "currently stored energy in the battery in kWh" }, "qs_lb": { - "type": "number", + "type": ["number", "null"], "description": "lower bound of reactive power that can be outputted in kvar" }, "qs_ub": { - "type": "number", + "type": ["number", "null"], "description": "upper bound of reactive power that can be outputted in kvar" }, "pex": { @@ -48,10 +48,13 @@ "enum": [0, 1, "ENABLED", "DISABLED"], "description": "the status of the asset, _i.e._ enabled (`1`) or disabled (`0`)" }, - "phase_unbalance_factor": { - "type": "number", - "minimum": 0, + "phase_unbalance_ub": { + "type": ["number", "null"], "description": "Percent within which power input/output must be on between phases on the storage object" + }, + "inverter": { + "type": "string", + "enum": ["GRID_FOLLOWING", "GRID_FORMING"] } } } diff --git a/schemas/settings/input-settings-voltage_source.schema.json b/schemas/settings/input-settings-voltage_source.schema.json index 4de2e0fe..605c683e 100644 --- a/schemas/settings/input-settings-voltage_source.schema.json +++ b/schemas/settings/input-settings-voltage_source.schema.json @@ -58,6 +58,22 @@ "type": ["integer", "string"], "enum": [0, 1, "ENABLED", "DISABLED"], "description": "The status of the generation object, _i.e._ enabled (`1`) or disabled (`0`)" + }, + "cost_pg_parameters": { + "type": "array", + "description": "Cost model polynomial or piecewise parameters", + "items": { + "type": "number" + } + }, + "cost_pg_model": { + "type": "integer", + "description": "Cost model type, 1 = piecewise-linear, 2 = polynomial" + }, + "inverter": { + "type": "string", + "enum": ["GRID_FORMING", "GRID_FOLLOWING"], + "default": "GRID_FORMING" } } } diff --git a/src/PowerModelsONM.jl b/src/PowerModelsONM.jl index 6dfb4fb6..713ecc15 100644 --- a/src/PowerModelsONM.jl +++ b/src/PowerModelsONM.jl @@ -11,15 +11,15 @@ module PowerModelsONM using Distributed: pmap # InfrastructureModels ecosystem - import InfrastructureModels - const _IM = InfrastructureModels + import InfrastructureModels as IM + import InfrastructureModels: ismultinetwork, ismultiinfrastructure - import PowerModelsDistribution - import PowerModelsDistribution: ref, var, con, sol, ids, nw_ids, nw_id_default, nws, AbstractUnbalancedPowerModel - const PMD = PowerModelsDistribution + import PowerModelsDistribution as PMD + import PowerModelsDistribution: ref, var, con, sol, ids, nw_ids, nw_id_default, nws + import PowerModelsDistribution: AbstractUnbalancedPowerModel, ACRUPowerModel, ACPUPowerModel, IVRUPowerModel, LPUBFDiagPowerModel, LinDist3FlowPowerModel, NFAUPowerModel, FOTRUPowerModel, FOTPUPowerModel - import PowerModelsProtection - import PowerModelsStability + import PowerModelsProtection as PMP + import PowerModelsStability as PMS # Optimization Modeling import JuMP @@ -28,7 +28,7 @@ module PowerModelsONM import PolyhedralRelaxations import Ipopt - import Cbc + import HiGHS import Juniper import ArgParse @@ -39,22 +39,23 @@ module PowerModelsONM # Logging Tools import Logging import LoggingExtras - import ProgressMeter: @showprogress # Hardware statistics import Hwloc # Network Graphs import Graphs + import EzXML # Import Tools import Requires: @require function __init__() - global _LOGGER = Logging.ConsoleLogger(; meta_formatter=PowerModelsDistribution._pmd_metafmt) + global _LOGGER = Logging.ConsoleLogger(; meta_formatter=PMD._pmd_metafmt) global _DEFAULT_LOGGER = Logging.current_logger() Logging.global_logger(_LOGGER) + PowerModelsONM.set_log_level!(:Info) @require Gurobi="2e9cd046-0924-5485-92f1-d5272153d98b" begin global GRB_ENV = Gurobi.Env() @@ -77,15 +78,17 @@ module PowerModelsONM include("core/solution.jl") include("data_model/checks.jl") + include("data_model/eng2math.jl") include("form/acp.jl") include("form/acr.jl") include("form/apo.jl") - include("form/bf_mx_lin.jl") + include("form/lindistflow.jl") include("form/shared.jl") include("io/events.jl") include("io/faults.jl") + include("io/graphml.jl") include("io/inverters.jl") include("io/json.jl") include("io/network.jl") @@ -95,8 +98,9 @@ module PowerModelsONM include("prob/common.jl") include("prob/dispatch.jl") include("prob/faults.jl") - include("prob/mn_opf_oltc_capc.jl") - include("prob/osw_mld.jl") + include("prob/opf.jl") + include("prob/mld_traditional.jl") + include("prob/mld_block.jl") include("prob/stability.jl") include("prob/switch.jl") diff --git a/src/app/main.jl b/src/app/main.jl index cbe12148..3e8c5d43 100644 --- a/src/app/main.jl +++ b/src/app/main.jl @@ -23,9 +23,7 @@ The main ONM Algorithm, performs the following steps: - [`initialize_output!`](@ref initialize_output!) - [`sanitize_args!`](@ref sanitize_args!) - [`setup_logging!`](@ref setup_logging!) -- [`parse_network`](@ref parse_network!) -- [`parse_events!`](@ref parse_events!) -- [`parse_settings!`](@ref parse_settings!) +- [`prepare_data!`](@ref prepare_data!) - [`build_solver_instances!`](@ref build_solver_instances!) - [`optimize_switches!`](@ref optimize_switches!) - [`optimize_dispatch!`](@ref optimize_dispatch!) @@ -44,43 +42,58 @@ function entrypoint(args::Dict{String,<:Any})::Dict{String,Any} setup_logging!(args) - parse_network!(args) - - parse_settings!(args) - - parse_events!(args) + prepare_data!(args) build_solver_instances!(args) - if !("switching" in get(args, "skip", String[])) + if !("switching" in get_setting(args, ("options","problem","skip"), String[])) optimize_switches!(args) end - if !("dispatch" in get(args, "skip", String[])) + if !("dispatch" in get_setting(args, ("options","problem","skip"), String[])) optimize_dispatch!(args) end - if !("stability" in get(args, "skip", String[])) + if !("stability" in get_setting(args, ("options","problem","skip"), String[])) && haskey(args, "inverters") && !isempty(args["inverters"]) run_stability_analysis!(args) end - if !("faults" in get(args, "skip", String[])) + if !("faults" in get_setting(args, ("options","problem","skip"), String[])) run_fault_studies!(args) end analyze_results!(args) - if !validate_output(args["output_data"]) - @warn "Output data structure failed to validate against its schema" - end + !validate_output(args["output_data"]) && @warn "Output data structure failed to validate against its schema:\n$(evaluate_output(args["output_data"]))" if !isempty(get(args, "output", "")) - write_json(args["output"], args["output_data"]; indent=get(args, "pretty-print", false) ? 2 : missing) + write_json(args["output"], args["output_data"]; indent=get_setting(args, ("options","outputs","pretty-print"), false) ? 2 : missing) end - if get(args, "debug", false) - write_json("debug_onm_$(Dates.format(Dates.now(), "yyyy-mm-dd--HH-MM-SS")).json", args; indent=get(args, "pretty-print", false) ? 2 : missing) + if get_setting(args, ("options","outputs","debug-output"),false) + write_json("debug_onm_$(Dates.format(Dates.now(), "yyyy-mm-dd--HH-MM-SS")).json", args; indent=get_setting(args, ("options","outputs","pretty-print"), false) ? 2 : missing) end return args end + + +""" + prepare_data!(args::Dict{String,<:Any})::Dict{String,Any} + +Performs the data preparation actions + +- [`parse_network`](@ref parse_network!) +- [`parse_events!`](@ref parse_events!) +- [`parse_settings!`](@ref parse_settings!) + +""" +function prepare_data!(args::Dict{String,<:Any})::Dict{String,Any} + parse_network!(args) + + parse_settings!(args) + + parse_events!(args) + + return args +end diff --git a/src/cli/arguments.jl b/src/cli/arguments.jl index 45bb9792..523356f2 100644 --- a/src/cli/arguments.jl +++ b/src/cli/arguments.jl @@ -8,40 +8,6 @@ end parse_commandline(; validate::Bool=true)::Dict{String,Any} Command line argument parsing - -## Supported command line arguments - -- `--network`, `-n` -- `--output`, `-o` -- `--faults`, `-f` -- `--events`, `-e` -- `--inverters`, `-i` -- `--settings`, `-s` -- `--quiet`, `-q` -- `--verbose`, `-v` -- `--debug`, `-d` -- `--gurobi`, `-g` -- `--opt-disp-formulation` -- `--opt-disp-solver` -- `--skip` - -## Depreciated command line arguments - -- `--network-file` -- `--output-file` -- `--problem`, `-p` -- `--formulation` -- `--protection-settings` -- `--debug-export-file` -- `--use-gurobi` -- `--solver-tolerance` -- `--max-switch-actions` -- `--timestep-hours` -- `--voltage-lower-bound` -- `--voltage-upper-bound` -- `--voltage-angle-difference` -- `--clpu-factor` - """ function parse_commandline(; validate::Bool=true)::Dict{String,Any} s = ArgParse.ArgParseSettings( @@ -50,128 +16,51 @@ function parse_commandline(; validate::Bool=true)::Dict{String,Any} autofix_names = false, ) - ArgParse.@add_arg_table! s begin - "--network", "-n" - help = "the power system network data file" - arg_type = String - # required = true - "--output", "-o" - help = "path to output file" - default = "" - arg_type = String - "--faults", "-f" - help = "json file defining faults over which to perform fault study" - default = "" - arg_type = String - "--events", "-e" - help = "Events (contingencies) file" - default = "" - arg_type = String - "--inverters", "-i" - help = "inverter settings file for stability analysis" - default = "" - arg_type = String - "--settings", "-s" - help = "general settings file for setting custom bounds and microgrid metadata" - default = "" - arg_type = String - "--quiet", "-q" - help = "sets log level in ONM to :Error" - action = :store_true - "--verbose", "-v" - help = "info, warn messages for all packages" - action = :store_true - "--debug", "-d" - help = "debug messages and output of full results for each optimization step" - action = :store_true - "--gurobi", "-g" - help = "use the gurobi solver (must have been built with Gurobi.jl / a Gurobi binary, and have license)" - action = :store_true - "--opt-disp-algorithm" - help = "option to choose which algorithm for optimal dispatch: opf (default), oltc, or mld" - default = "opf" - arg_type = String - "--opt-disp-formulation" - help = "mathematical formulation to solve for the final optimal dispatch (lindistflow (default), acr, acp, nfa, fot, fbs)" - default = "lindistflow" - arg_type = String - "--opt-disp-solver" - help = "optimization solver to use for the optimal dispatch problem (nlp_solver (default), misocp_solver, minlp_solver). Needs to match features in chosen opt-disp-formulation" - default = "nlp_solver" - arg_type = String - "--opt-switch-algorithm" - help = "option to switch between the 'global' (default) multinetwork problem formulation and the 'iterative' version" - default = "global" - arg_type = String - "--opt-switch-solver" - help = "option to choose which solver from the build_solver_instances function to use for the optimal switching algorithm" - default = "misocp_solver" - arg_type = String - "--opt-switch-formulation" - help = "mathematical formulation to solve for the optimal switching (lindistflow (default), nfa, fot, fbs)" - default = "lindistflow" - arg_type = String - "--skip" - help = "comma separated list of parts of the algorithm to skip: faults, stability, dispatch, and/or switching" - arg_type = Vector{String} - default = String[] - "--pretty-print" - help = "flag to toggle pretty-printed output json" - action = :store_true - end + runtime_args_schema = load_schema(joinpath(dirname(pathof(PowerModelsONM)), "..", "schemas/input-runtime_arguments.schema.json")) - # Depreciated Command Line Arguments - ArgParse.@add_arg_table! s begin - "--network-file" - help = "DEPRECIATED: use 'network'" - default = "" - arg_type = String - "--output-file" - help = "DEPRECIATED: use 'output'" - default = "" - arg_type = String - "--problem", "-p" - help = "DEPRECIATED: ignored" - default = "opf" - arg_type = String - "--formulation" - help = "DEPRECIATED: use 'opt-disp-formulation'" - default = "" - arg_type = String - "--protection-settings" - help = "DEPRECIATED: ignored" - default = "" - arg_type = String - "--debug-export-file" - help = "DEPRECIATED: use 'debug'" - default = "" - arg_type = String - "--use-gurobi" - help = "DEPRECIATED: use 'gurobi'" - action = :store_true - "--solver-tolerance" - help = "DEPRECIATED: use 'settings'" - arg_type = Float64 - "--max-switch-actions" - help = "DEPRECIATED: use 'settings'" - arg_type = Int - "--timestep-hours" - help = "DEPRECIATED: use 'settings'" - arg_type = Float64 - "--voltage-lower-bound" - help = "DEPRECIATED: use 'settings'" - arg_type = Float64 - "--voltage-upper-bound" - help = "DEPRECIATED: use 'settings'" - arg_type = Float64 - "--voltage-angle-difference" - help = "DEPRECIATED: use 'settings'" - arg_type = Float64 - "--clpu-factor" - help = "DEPRECIATED: use 'settings'" - arg_type = Float64 + rt_args = [] + for (prop_name,prop) in runtime_args_schema.data["properties"] + if prop_name ∈ ["network", "settings", "events", "faults", "inverters", "output", "quiet", "verbose", "debug", "gurobi"] + arg_name = ["--$prop_name", "-$(prop_name[1])"] + else + arg_name = "--$prop_name" + end + + arg_settings = Dict{Symbol,Any}(:help=>get(prop,"description","")) + if prop_name ∈ get(runtime_args_schema.data, "required", []) + arg_settings[:required] = true + end + if prop["type"] == "boolean" + if get(prop, "default", false) + arg_settings[:action] = :store_false + else + arg_settings[:action] = :store_true + end + else + if prop["type"] == "array" + subtype = Dict("string"=>String,"integer"=>Int,"number"=>Float64)[prop["items"]["type"]] + arg_settings[:arg_type] = Vector{subtype} + arg_settings[:default] = Vector{subtype}([]) + if haskey(prop["items"], "enum") + arg_settings[:range_tester] = x->all([_x in prop["items"]["enum"] for _x in x]) + end + else + if haskey(prop, "enum") + arg_settings[:range_tester] = x->x∈prop["enum"] + end + if haskey(prop, "default") + arg_settings[:default] = prop["default"] + end + arg_settings[:arg_type] = Dict("string"=>String,"integer"=>Int,"number"=>Float64)[prop["type"]] + end + end + + push!(rt_args, arg_name) + push!(rt_args, arg_settings) end + ArgParse.add_arg_table!(s, rt_args...) + arguments = ArgParse.parse_args(s) for arg in collect(keys(arguments)) @@ -181,7 +70,7 @@ function parse_commandline(; validate::Bool=true)::Dict{String,Any} end if validate && !validate_runtime_arguments(arguments) - error("invalid runtime arguments detected") + error("invalid runtime arguments detected:\n $(evaluate_runtime_arguments(arguments))") end _deepcopy_args!(arguments) @@ -193,63 +82,43 @@ end """ sanitize_args!(args::Dict{String,<:Any})::Dict{String,Any} -Sanitizes depreciated arguments into the correct new ones, and gives warnings - -## Depreciated argument conversions - -- `network-file` -> `network` -- `output-file` -> `output` -- `protection-settings` -> delete! -- `problem` -> delete! -- `formulation` -> `opt-disp-formulation` -- `debug-export-file` -> `debug=true` -- `use-gurobi` -> `gurobi` - +Sanitizes deprecated arguments into the correct new ones, and gives warnings """ function sanitize_args!(args::Dict{String,<:Any})::Dict{String,Any} _deepcopy_args!(args) - if !isempty(get(args, "network-file", "")) - @warn "'network-file' argument is being depreciated in favor of 'network', please update your code" - args["network"] = pop!(args, "network-file") - end - - if !isempty(get(args, "output-file", "")) - @warn "'output-file' argument is being depreciated in favor of 'output', please update your code" - args["output"] = pop!(args, "output-file") - end + runtime_args_schema = load_schema(joinpath(dirname(pathof(PowerModelsONM)), "..", "schemas/input-runtime_arguments.schema.json")) + deprecated_args = Dict( + prop_name=>Dict( + "description"=>get(prop, "description", ""), + "default"=>get(prop,"default",get(prop,"type","")=="boolean" ? false : missing) + ) for (prop_name,prop) in runtime_args_schema.data["properties"] if get(prop, "deprecated", false) + ) - if !isempty(get(args, "protection-settings", "")) - delete!(args, "protection-settings") - @warn "'protection-settings' argument is depreciated, will be ignored" + if get(args, "quiet", false) + args["log-level"] = "error" end - - if !isempty(get(args, "problem", "")) - delete!(args, "problem") - @warn "'problem' argument is depreciated, will be ignored" + if get(args, "verbose", false) + args["log-level"] = "info" end - - if !isempty(get(args, "formulation", "")) - args["opt-disp-formulation"] = pop!(args, "formulation") - @warn "'formulation' argument is depreciated in favor of 'opt-disp-formulation', please update your code" + if get(args, "debug", false) + args["log-level"] = "debug" end - if haskey(args, "debug-export-file") - if !isempty(get(args, "debug-export-file", "")) - args["debug"] = true + for arg in ["quiet", "verbose", "debug"] + if get(args, arg, false) + @warn "'$arg' argument is deprecated: use 'log-level'" + delete!(args, arg) end - @warn "'debug-export-file' argument is depreciated in favor of the 'debug' flag, file will be outputted to debug_{prob_type}_{current_time}.json, please update your code" - delete!(args, "debug-export-file") - end - - if get(args, "use-gurobi", false) - args["gurobi"] = pop!(args, "use-gurobi") - @warn "'use-gurobi' flag is depreciated in favor of 'gurobi' flag, please update your code" end - for arg in ["solver-tolerance", "max-switch-actions", "timestep-hours", "voltage-lower-bound", "voltage-upper-bound", "voltage-angle-difference", "clpu-factor"] - if haskey(args, arg) - @warn "'$arg' argument is depreciated in favor of 'settings' input file, please update code" + for (arg,v) in collect(args) + if arg ∈ keys(deprecated_args) + if !ismissing(deprecated_args[arg]["default"]) && v == deprecated_args[arg]["default"] + delete!(args, arg) + else + @warn "'$arg' is deprecated: use settings.json @ '$(strip(split(deprecated_args[arg]["description"],":")[end]))'" + end end end diff --git a/src/cli/entrypoint.jl b/src/cli/entrypoint.jl index 0f30a866..7b5880b7 100644 --- a/src/cli/entrypoint.jl +++ b/src/cli/entrypoint.jl @@ -13,11 +13,14 @@ if get(args, "nprocs", 1) > 1 @everywhere Pkg.activate(joinpath(@__DIR__), "..", "..") end -# TODO: Remove use-gurobi when it gets removed from depreciated CLI Arguments -if get(args, "gurobi", false) || get(args, "use-gurobi", false) +if get(args, "gurobi", false) @everywhere import Gurobi end +if get(args, "knitro", false) + @everywhere import KNITRO +end + @everywhere import PowerModelsONM if isinteractive() == false diff --git a/src/core/base.jl b/src/core/base.jl index 41811700..64d1b281 100644 --- a/src/core/base.jl +++ b/src/core/base.jl @@ -5,7 +5,7 @@ JuMP.lower_bound(x::Number) = x JuMP.upper_bound(x::Number) = x "variable_domain helper function for constant values" -_IM.variable_domain(var::Number) = (var, var) +IM.variable_domain(var::Number) = (var, var) "has_lower_bound helper function for constant values" JuMP.has_lower_bound(x::Number) = true @@ -16,38 +16,48 @@ JuMP.has_upper_bound(x::Number) = true "is_binary helper function for constant values" JuMP.is_binary(x::Number) = false -"lower_bound helper function for Affine Expression variables" +""" + JuMP.lower_bound(x::JuMP.AffExpr) + +lower_bound helper function for Affine Expression variables +""" function JuMP.lower_bound(x::JuMP.AffExpr) lb = [] - for (k,v) in x.terms + for (k, v) in x.terms push!(lb, JuMP.lower_bound(k) * v) end - sum(lb) + return sum(lb) end -"upper_bound helper function for Affine Expression variables" +""" + JuMP.upper_bound(x::JuMP.AffExpr) + +upper_bound helper function for Affine Expression variables +""" function JuMP.upper_bound(x::JuMP.AffExpr) ub = [] - for (k,v) in x.terms + for (k, v) in x.terms push!(ub, JuMP.upper_bound(k) * v) end - sum(ub) + return sum(ub) end "has_lower_bound helper function for Affine Expression variables" -JuMP.has_lower_bound(x::JuMP.AffExpr) = all(JuMP.has_lower_bound(k) for (k,_) in x.terms) +JuMP.has_lower_bound(x::JuMP.AffExpr) = all(JuMP.has_lower_bound(k) for (k, _) in x.terms) "has_upper_bound helper function for Affine Expression variables" -JuMP.has_upper_bound(x::JuMP.AffExpr) = all(JuMP.has_upper_bound(k) for (k,_) in x.terms) +JuMP.has_upper_bound(x::JuMP.AffExpr) = all(JuMP.has_upper_bound(k) for (k, _) in x.terms) "is_binary helper function for Affine Expression variables" JuMP.is_binary(x::JuMP.AffExpr) = false """ + IM.variable_domain(var::JuMP.AffExpr) + Computes the valid domain of a given JuMP variable taking into account bounds and the varaible's implicit bounds (e.g., binary). """ -function _IM.variable_domain(var::JuMP.AffExpr) +function IM.variable_domain(var::JuMP.AffExpr) lb = -Inf if JuMP.has_lower_bound(var) lb = JuMP.lower_bound(var) @@ -67,78 +77,164 @@ function _IM.variable_domain(var::JuMP.AffExpr) return (lower_bound=lb, upper_bound=ub) end - """ -general relaxation of binlinear term (McCormick) -``` + IM.relaxation_product(m::JuMP.Model, x::JuMP.AffExpr, y::JuMP.VariableRef, z::JuMP.VariableRef; + default_x_domain::Tuple{Real,Real}=(-Inf, Inf), + default_y_domain::Tuple{Real,Real}=(-Inf, Inf) + ) + +general relaxation of binlinear term (McCormick) for Affine Expressions and VariableRefs + +```julia z >= JuMP.lower_bound(x)*y + JuMP.lower_bound(y)*x - JuMP.lower_bound(x)*JuMP.lower_bound(y) z >= JuMP.upper_bound(x)*y + JuMP.upper_bound(y)*x - JuMP.upper_bound(x)*JuMP.upper_bound(y) z <= JuMP.lower_bound(x)*y + JuMP.upper_bound(y)*x - JuMP.lower_bound(x)*JuMP.upper_bound(y) z <= JuMP.upper_bound(x)*y + JuMP.lower_bound(y)*x - JuMP.upper_bound(x)*JuMP.lower_bound(y) ``` """ -function _IM.relaxation_product(m::JuMP.Model, x::JuMP.AffExpr, y::JuMP.VariableRef, z::JuMP.VariableRef; default_x_domain::Tuple{Real,Real}=(-Inf,Inf), default_y_domain::Tuple{Real,Real}=(-Inf,Inf)) - x_lb, x_ub = _IM.variable_domain(x) - y_lb, y_ub = _IM.variable_domain(y) +function IM.relaxation_product(m::JuMP.Model, x::JuMP.AffExpr, y::JuMP.VariableRef, z::JuMP.VariableRef; + default_x_domain::Tuple{Real,Real}=(-Inf, Inf), + default_y_domain::Tuple{Real,Real}=(-Inf, Inf)) + x_lb, x_ub = IM.variable_domain(x) + y_lb, y_ub = IM.variable_domain(y) x_lb = !isfinite(x_lb) ? default_x_domain[1] : x_lb x_ub = !isfinite(x_ub) ? default_x_domain[2] : x_ub y_lb = !isfinite(y_lb) ? default_y_domain[1] : y_lb y_ub = !isfinite(y_ub) ? default_y_domain[2] : y_ub - JuMP.@constraint(m, z >= x_lb*y + y_lb*x - x_lb*y_lb) - JuMP.@constraint(m, z >= x_ub*y + y_ub*x - x_ub*y_ub) - JuMP.@constraint(m, z <= x_lb*y + y_ub*x - x_lb*y_ub) - JuMP.@constraint(m, z <= x_ub*y + y_lb*x - x_ub*y_lb) + JuMP.@constraint(m, z >= x_lb * y + y_lb * x - x_lb * y_lb) + JuMP.@constraint(m, z >= x_ub * y + y_ub * x - x_ub * y_ub) + JuMP.@constraint(m, z <= x_lb * y + y_ub * x - x_lb * y_ub) + JuMP.@constraint(m, z <= x_ub * y + y_lb * x - x_ub * y_lb) end +@doc raw""" + IM.relaxation_product(m::JuMP.Model, x::Real, y::JuMP.VariableRef, z::JuMP.VariableRef) -""" -general relaxation of binlinear term (McCormick) -``` +general relaxation of binlinear term (McCormick) for Constants and VariableRefs + +```julia z >= JuMP.lower_bound(x)*y + JuMP.lower_bound(y)*x - JuMP.lower_bound(x)*JuMP.lower_bound(y) z >= JuMP.upper_bound(x)*y + JuMP.upper_bound(y)*x - JuMP.upper_bound(x)*JuMP.upper_bound(y) z <= JuMP.lower_bound(x)*y + JuMP.upper_bound(y)*x - JuMP.lower_bound(x)*JuMP.upper_bound(y) z <= JuMP.upper_bound(x)*y + JuMP.lower_bound(y)*x - JuMP.upper_bound(x)*JuMP.lower_bound(y) ``` """ -function _IM.relaxation_product(m::JuMP.Model, x::Float64, y::JuMP.VariableRef, z::JuMP.VariableRef) - x_lb, x_ub = _IM.variable_domain(x) - y_lb, y_ub = _IM.variable_domain(y) - - JuMP.@constraint(m, z >= x_lb*y + y_lb*x - x_lb*y_lb) - JuMP.@constraint(m, z >= x_ub*y + y_ub*x - x_ub*y_ub) - JuMP.@constraint(m, z <= x_lb*y + y_ub*x - x_lb*y_ub) - JuMP.@constraint(m, z <= x_ub*y + y_lb*x - x_ub*y_lb) +function IM.relaxation_product(m::JuMP.Model, x::Real, y::JuMP.VariableRef, z::JuMP.VariableRef) + x_lb, x_ub = IM.variable_domain(x) + y_lb, y_ub = IM.variable_domain(y) + + JuMP.@constraint(m, z >= x_lb * y + y_lb * x - x_lb * y_lb) + JuMP.@constraint(m, z >= x_ub * y + y_ub * x - x_ub * y_ub) + JuMP.@constraint(m, z <= x_lb * y + y_ub * x - x_lb * y_ub) + JuMP.@constraint(m, z <= x_ub * y + y_lb * x - x_ub * y_lb) end - "recursive dictionary merge, similar to update data" -recursive_merge(x::AbstractDict...) = merge(recursive_merge, x...) +recursive_merge_including_vectors(x::AbstractDict...) = merge(recursive_merge_including_vectors, x...) "recursive vector merge, similar to update data" -recursive_merge(x::AbstractVector...) = cat(x...; dims=1) +recursive_merge_including_vectors(x::AbstractVector...) = cat(x...; dims=1) "recursive other merge" -recursive_merge(x...) = x[end] +recursive_merge_including_vectors(x...) = x[end] "recursive dictionary merge, similar to update data, with vectors getting overwritten instead of appended" -recursive_merge_no_vecs(x::AbstractDict...) = merge(recursive_merge_no_vecs, x...) +recursive_merge(x::AbstractDict...) = merge(recursive_merge, x...) "recursive other merge, with vectors getting overwritten instead of appended" -recursive_merge_no_vecs(x...) = x[end] +recursive_merge(x...) = x[end] +""" + recursive_merge_timesteps(x::T, y::U)::promote_type(T,U) where {T<: AbstractVector,U<: AbstractVector} -"helper function to recursively merge timestep vectors (e.g., of dictionaries)" -function recursive_merge_timesteps(x::T, y::U)::promote_type(T,U) where {T<: AbstractVector,U<: AbstractVector} +helper function to recursively merge timestep vectors (e.g., of dictionaries) +""" +function recursive_merge_timesteps(x::T, y::U)::promote_type(T, U) where {T<:AbstractVector,U<:AbstractVector} if !isempty(x) @assert length(x) == length(y) "cannot combine vectors of different lengths" - new = promote_type(T,U)() - for (_x,_y) in zip(x,y) - push!(new, recursive_merge(_x,_y)) + new = promote_type(T, U)() + for (_x, _y) in zip(x, y) + push!(new, recursive_merge_including_vectors(_x, _y)) end return new else return y end end + + +""" + set_dict_value!(a::Dict, key::String, value::Any) + +Helper function to assist in setting nested Dict values +""" +function set_dict_value!(a::Dict, key::String, value::Any) + a[key] = value +end + + +""" + set_dict_value!(a::T, path::Tuple{Vararg{String}}, value::Any) where T <: Dict + +Helper function to assist in setting nested Dict values +""" +function set_dict_value!(a::T, path::Tuple{Vararg{String}}, value::Any) where T <: Dict + if !haskey(a, first(path)) + a[first(path)] = T() + end + + new_path = length(path) == 2 ? path[2] : path[2:end] + + set_dict_value!(a[first(path)], new_path, value) +end + + +""" + convert(value::Any, path::Tuple{Vararg{String}}=tuple()) + +Helper function to assist in converting deprecated settings to their correct types / values +""" +function convert(value::Any, path::Tuple{Vararg{String}}=tuple()) + if haskey(settings_conversions, path) + value = settings_conversions[path](value) + end + + if isa(value, String) && startswith(value, ":") + value = Symbol(value[2:end]) + end + + if isa(value, Vector) && all(isa.(value, String)) && all(startswith.(value,":")) + value = Symbol[Symbol(v[2:end]) for v in value] + end + + return value +end + + +""" + Base.parse(::Type{T}, status::String)::T where T <: PMD.Status + +Parses a string from dss property 'enabled' into PMD.Status +""" +function Base.parse(::Type{T}, status::String)::T where T <: PMD.Status + if lowercase(status) ∈ ["y", "yes", "true"] + return PMD.ENABLED + elseif lowercase(status) ∈ ["n", "no", "false"] + return PMD.DISABLED + end + + @warn "enabled code '$status' not recognized, defaulting to ENABLED" + return PMD.ENABLED +end + +"Parses different options for Status enums in the events schema" +Base.parse(::Type{PMD.Status}, status::PMD.Status)::PMD.Status = status +Base.parse(::Type{PMD.Status}, status::Bool)::PMD.Status = PMD.Status(Int(status)) +Base.parse(::Type{PMD.Status}, status::Int)::PMD.Status = PMD.Status(status) + +"Parses formulation strings from the settings schema into AbstractUnbalancedPowerModels" +function Base.parse(::Type{PMD.AbstractUnbalancedPowerModel}, form::String)::Type + return _get_formulation(form) +end diff --git a/src/core/constraint.jl b/src/core/constraint.jl index 5db20fbd..7780d844 100644 --- a/src/core/constraint.jl +++ b/src/core/constraint.jl @@ -1,91 +1,110 @@ @doc raw""" - constraint_switch_state_max_actions(pm::AbstractUnbalancedPowerModel, nw::Int) + constraint_switch_close_action_limit(pm::AbstractUnbalancedPowerModel, nw::Int) -max actions per timestep switch constraint +Constraint for maximum allowed switch close actions in a single time step, as defined by `ref(pm, nw, :switch_close_actions_ub)` ```math -\sum_{\substack{i\in S}}{\Delta^{sw}_i} \leq z^{swu} +\begin{align} +\Delta^{\gamma}_i,~\forall i \in S & \\ +s.t. & \\ +& \Delta^{\gamma}_i \geq \gamma \left( 1 - \gamma_0 \right) \\ +& \Delta^{\gamma}_i \geq -\gamma \left( 1 - \gamma_0 \right) \\ +& \sum_{\substack{i \in S}} \Delta^{\gamma}_i \leq N_{\gamma=1}^{ub} +\end{align} ``` """ -function constraint_switch_state_max_actions(pm::AbstractSwitchModels, nw::Int) - max_switch_actions = ref(pm, nw, :max_switch_actions) - - delta_switch_states = Dict(l => JuMP.@variable(pm.model, base_name="$(nw)_delta_sw_state_$(l)", start=0) for l in ids(pm, nw, :switch_dispatchable)) - for (l, dsw) in delta_switch_states - state = var(pm, nw, :switch_state, l) - JuMP.@constraint(pm.model, dsw >= state * (1 - JuMP.start_value(state))) - JuMP.@constraint(pm.model, dsw >= -state * (1 - JuMP.start_value(state))) - end +function constraint_switch_close_action_limit(pm::AbstractUnbalancedPowerModel, nw::Int) + switch_close_actions_ub = ref(pm, nw, :switch_close_actions_ub) + + if switch_close_actions_ub < Inf + Δᵞs = Dict(l => JuMP.@variable(pm.model, base_name="$(nw)_delta_switch_state_$(l)", start=0) for l in ids(pm, nw, :switch_dispatchable)) + for (s, Δᵞ) in Δᵞs + γ = var(pm, nw, :switch_state, s) + γ₀ = JuMP.start_value(γ) + JuMP.@constraint(pm.model, Δᵞ >= γ * (1 - γ₀)) + JuMP.@constraint(pm.model, Δᵞ >= -γ * (1 - γ₀)) + end - if max_switch_actions < Inf - JuMP.@constraint(pm.model, sum(dsw for (l, dsw) in delta_switch_states) <= max_switch_actions) + JuMP.@constraint(pm.model, sum(Δᵞ for (l, Δᵞ) in Δᵞs) <= switch_close_actions_ub) end end @doc raw""" - constraint_switch_state_max_actions(pm::AbstractUnbalancedPowerModel, nw_1::Int, nw_2::Int) + constraint_switch_close_action_limit(pm::AbstractUnbalancedPowerModel, nw_1::Int, nw_2::Int) -max actions per timestep switch constraint for multinetwork formulations +Constraint for maximum allowed switch close actions between time steps, as defined by `ref(pm, nw, :switch_close_actions_ub)` ```math -\sum_{\substack{i\in S}}{\Delta^{sw}_i} \leq z^{swu} +\begin{align} +\Delta^{\gamma}_i, ~\forall i \in S & \\ +\gamma^{t}_i, ~\forall i \in S, ~\forall t \in T & \\ +\gamma^{t_1,t_2}_i, ~\forall i \in S, ~\forall (t_1,t_2) \in T & \\ +s.t. & \\ +& \gamma^{t_1,t_2}_i \geq 0 \\ +& \gamma^{t_1,t_2}_i \geq \gamma^{t_2}_i + \gamma^{t_1}_i - 1 \\ +& \gamma^{t_1,t_2}_i \leq \gamma{t_1}_i \\ +& \gamma^{t_1,t_2}_i \leq \gamma{t_2}_i \\ +& \Delta^{\gamma}_i \geq \gamma^{t_2}+i - \gamma^{t_1,t_2}_i \\ +& \Delta^{\gamma}_i \geq \gamma^{t_2}+i + \gamma^{t_1,t_2}_i \\ +& \sum_{\substack{i \in S}} \Delta^{\gamma}_i \leq N_{\gamma=1}^{ub} +\end{align} ``` """ -function constraint_switch_state_max_actions(pm::AbstractSwitchModels, nw_1::Int, nw_2::Int) - max_switch_actions = ref(pm, nw_2, :max_switch_actions) - - delta_switch_states = Dict(l => JuMP.@variable(pm.model, base_name="$(nw_2)_delta_sw_state_$(l)", start=0) for l in ids(pm, nw_2, :switch_dispatchable)) - for (l, dsw) in delta_switch_states - nw_1_state = var(pm, nw_1, :switch_state, l) - nw_2_state = var(pm, nw_2, :switch_state, l) - - nw1_nw2_state = JuMP.@variable(pm.model, base_name="$(nw_1)_$(nw_2)_sw_state_$(l)") - JuMP.@constraint(pm.model, nw1_nw2_state >= 0) - JuMP.@constraint(pm.model, nw1_nw2_state >= nw_2_state + nw_1_state - 1) - JuMP.@constraint(pm.model, nw1_nw2_state <= nw_2_state) - JuMP.@constraint(pm.model, nw1_nw2_state <= nw_1_state) - - JuMP.@constraint(pm.model, dsw >= nw_2_state - nw1_nw2_state) - JuMP.@constraint(pm.model, dsw >= -nw_2_state + nw1_nw2_state) - end +function constraint_switch_close_action_limit(pm::AbstractUnbalancedPowerModel, nw_1::Int, nw_2::Int) + switch_close_actions_ub = ref(pm, nw_2, :switch_close_actions_ub) + + if switch_close_actions_ub < Inf + Δᵞs = Dict(l => JuMP.@variable(pm.model, base_name="$(nw_2)_delta_switch_state_$(l)", start=0) for l in ids(pm, nw_2, :switch_dispatchable)) + for (l, Δᵞ) in Δᵞs + γᵗ¹ = var(pm, nw_1, :switch_state, l) + γᵗ² = var(pm, nw_2, :switch_state, l) + + γᵗ¹ᵗ² = JuMP.@variable(pm.model, base_name="$(nw_1)_$(nw_2)_delta_switch_state_$(l)") + JuMP.@constraint(pm.model, γᵗ¹ᵗ² >= 0) + JuMP.@constraint(pm.model, γᵗ¹ᵗ² >= γᵗ² + γᵗ¹ - 1) + JuMP.@constraint(pm.model, γᵗ¹ᵗ² <= γᵗ²) + JuMP.@constraint(pm.model, γᵗ¹ᵗ² <= γᵗ¹) + + JuMP.@constraint(pm.model, Δᵞ >= γᵗ² - γᵗ¹ᵗ²) + JuMP.@constraint(pm.model, Δᵞ >= -γᵗ² + γᵗ¹ᵗ²) + end - if max_switch_actions < Inf - JuMP.@constraint(pm.model, sum(dsw for (l, dsw) in delta_switch_states) <= max_switch_actions) + JuMP.@constraint(pm.model, sum(Δᵞ for (l, Δᵞ) in Δᵞs) <= switch_close_actions_ub) end end @doc raw""" - constraint_block_isolation(pm::AbstractSwitchModels, nw::Int; relax::Bool=false) + constraint_isolate_block(pm::AbstractUnbalancedPowerModel, nw::Int) constraint to ensure that blocks get properly isolated by open switches by comparing the states of two neighboring blocks. If the neighboring block indicators are not either both 0 or both 1, the switch between them should be OPEN (0) ```math -\begin{align} -& (z^{bl}_{fr}_{i} - z^{bl}_{to}_{i}) \leq z^{sw}_{i}\ \forall i in S \\ -& (z^{bl}_{fr}_{i} - z^{bl}_{fr}_{i}) \geq -z^{sw}_{i}\ \forall i in S -\end{align} +\begin{align*} +& (z^{bl}_{fr} - z^{bl}_{to}) \leq \gamma_{i}\ ~\forall i \in S \\ +& (z^{bl}_{fr} - z^{bl}_{fr}) \geq - \gamma_{i}\ ~\forall i \in S \\ +& z^{bl}_b \leq N_{gen} + N_{strg} + N_{neg load} + \sum_{i \in S \in b} \gamma_i, ~\forall b \in B +\end{align*} +``` -where $$z^{bl}_{fr}_{i}$$ and $$z^{bl}_{to}_{i}$$ are the indicator variables for the blocks on +where $$z^{bl}_{fr}$$ and $$z^{bl}_{to}$$ are the indicator variables for the blocks on either side of switch $$i$$. -``` """ -function constraint_block_isolation(pm::AbstractSwitchModels, nw::Int; relax::Bool=false) +function constraint_isolate_block(pm::AbstractUnbalancedPowerModel, nw::Int) # if switch is closed, both blocks need to be the same status (on or off) for (s, switch) in ref(pm, nw, :switch_dispatchable) z_block_fr = var(pm, nw, :z_block, ref(pm, nw, :bus_block_map, switch["f_bus"])) z_block_to = var(pm, nw, :z_block, ref(pm, nw, :bus_block_map, switch["t_bus"])) - z_switch = var(pm, nw, :switch_state, s) - if relax - JuMP.@constraint(pm.model, (z_block_fr - z_block_to) <= (1-z_switch)) - JuMP.@constraint(pm.model, (z_block_fr - z_block_to) >= -(1-z_switch)) - else # "indicator" constraint - JuMP.@constraint(pm.model, z_switch => {z_block_fr == z_block_to}) - end + γ = var(pm, nw, :switch_state, s) + JuMP.@constraint(pm.model, (z_block_fr - z_block_to) <= (1-γ)) + JuMP.@constraint(pm.model, (z_block_fr - z_block_to) >= -(1-γ)) + + # indicator constraint, for reference + # JuMP.@constraint(pm.model, γ => {z_block_fr == z_block_to}) end # quick determination of blocks to shed: @@ -95,8 +114,8 @@ function constraint_block_isolation(pm::AbstractSwitchModels, nw::Int; relax::Bo for b in ids(pm, nw, :blocks) z_block = var(pm, nw, :z_block, b) - n_gen = length(ref(pm, nw, :block_gens)) - n_strg = length(ref(pm, nw, :block_storages)) + n_gen = length(ref(pm, nw, :block_gens, b)) + n_strg = length(ref(pm, nw, :block_storages, b)) n_neg_loads = length([_b for (_b,ls) in ref(pm, nw, :block_loads) if any(any(ref(pm, nw, :load, l, "pd") .< 0) for l in ls)]) JuMP.@constraint(pm.model, z_block <= n_gen + n_strg + n_neg_loads + sum(var(pm, nw, :switch_state, s) for s in ids(pm, nw, :block_switches) if s in ids(pm, nw, :switch_dispatchable))) @@ -104,42 +123,128 @@ function constraint_block_isolation(pm::AbstractSwitchModels, nw::Int; relax::Bo end +@doc raw""" + constraint_isolate_block_traditional(pm::AbstractUnbalancedPowerModel, nw::Int) + +Constraint to simulate block isolation constraint in the traditional mld problem + +```math +\begin{align} +& z^{bus}_{fr} - z^{bus}_{to} \leq (1-\gamma_i), ~\forall i \in S \\ +& z^{bus}_{fr} - z^{bus}_{to} \geq -(1-\gamma_i), ~\forall i \in S \\ +& z^{d}_i \leq z^{d}_j, ~\forall (i,j) \in D \in B \\ +& z^{d}_i \leq z^{bus}_j, ~\forall i \in D \in B, ~i \in j \in V \in B \\ +& z^{bus}_i \leq z^{bus}_j, ~\forall (i,j) \in V \in B \\ +& z^{bl}_b \leq N_{gen} + N_{strg} + N_{neg load} + \sum_{i \in S \in {b \in B}} \gamma_i, ~\forall b \in B +\end{align} +``` """ +function constraint_isolate_block_traditional(pm::AbstractUnbalancedPowerModel, nw::Int) + # if switch is closed, both buses need to have the same status (on or off) + for (s, switch) in ref(pm, nw, :switch_dispatchable) + z_voltage_fr = var(pm, nw, :z_voltage, switch["f_bus"]) + z_voltage_to = var(pm, nw, :z_voltage, switch["t_bus"]) + + γ = var(pm, nw, :switch_state, s) + JuMP.@constraint(pm.model, (z_voltage_fr - z_voltage_to) <= (1-γ)) + JuMP.@constraint(pm.model, (z_voltage_fr - z_voltage_to) >= -(1-γ)) + # indicator constraint, for reference + # JuMP.@constraint(pm.model, γ => {z_voltage_fr == z_voltage_to}) + end + + # link loads in block + for (block, loads) in ref(pm, nw, :block_loads) + for load in loads + JuMP.@constraint(pm.model, var(pm, nw, :z_demand, load) .<= [var(pm, nw, :z_demand, _load) for _load in filter(x->x!=load, loads)]) + JuMP.@constraint(pm.model, var(pm, nw, :z_demand, load) <= var(pm, nw, :z_voltage, ref(pm, nw, :load, load, "load_bus"))) + end + end + + # link buses in block + for (block, buses) in ref(pm, nw, :blocks) + for bus in buses + if bus in var(pm, nw, :z_voltage) + JuMP.@constraint(pm.model, var(pm, nw, :z_voltage, bus) .<= [var(pm, nw, :z_voltage, _bus) for _bus in filter(x->x!=bus&&_bus in var(pm, nw, :z_voltage), buses)]) + end + end + end + + # quick determination of blocks to shed: + # if no generation resources (gen, storage, or negative loads (e.g., rooftop pv models)) + # and no switches connected to the block are closed, then the island must be shed, + # otherwise, to shed or not will be determined by feasibility + for b in ids(pm, nw, :blocks) + z_voltages = [var(pm, nw, :z_voltage, bus) for bus in ref(pm, nw, :blocks, b) if bus in var(pm, nw, :z_voltage)] + + n_gen = length(ref(pm, nw, :block_gens, b)) + n_strg = length(ref(pm, nw, :block_storages, b)) + n_neg_loads = length([_b for (_b,ls) in ref(pm, nw, :block_loads) if any(any(ref(pm, nw, :load, l, "pd") .< 0) for l in ls)]) + + JuMP.@constraint(pm.model, z_voltages .<= n_gen + n_strg + n_neg_loads + sum(var(pm, nw, :switch_state, s) for s in ids(pm, nw, :block_switches) if s in ids(pm, nw, :switch_dispatchable))) + end +end + + +@doc raw""" constraint_radial_topology(pm::AbstractUnbalancedPowerModel, nw::Int; relax::Bool=false) Constraint to enforce a radial topology -# doi: 10.1109/TSG.2020.2985087 + +See 10.1109/TSG.2020.2985087 + +```math +\begin{align} +\mathbf{\beta} \in \mathbf{\Omega} \\ +\alpha_{ij} \leq \beta_{ij},\forall(i,j) \in L \\ +\sum_{\substack{(j,i_r)\in L}}f^{k}_{ji_r} - \sum_{\substack{(i_r,j)\in L}}f^{k}_{i_rj}=-1,~\forall k \in N\setminus i_r \\ +\sum_{\substack{(j,k)\in L}}f^{k}_{jk} - \sum_{\substack{(k,j)\in L}}f^k_{kj} = 1,~\forall k \in N\setminus i_r \\ +\sum_{\substack{(j,i)\in L}}f^k_{ji}-\sum_{\substack{(i,j)\in L}}f^k_{ij}=0,~\forall k \in N\setminus i_r,\forall i \in N\setminus {i_r,k} \\ +0 \leq f^k_{ij} \leq \lambda_{ij},0 \leq f^k_{ji} \leq \lambda_{ji},\forall k \in N\setminus i_r,\forall(i,j)\in L \\ +\sum_{\substack{(i,j)\in L}}\left(\lambda_{ij} + \lambda_{ji} \right ) = \left | N \right | - 1 \\ +\lambda_{ij} + \lambda_{ji} = \beta_{ij},\forall(i,j)\in L \\ +\lambda_{ij},\lambda_{ji}\in\left \{ 0,1 \right \},\forall(i,j)\in L +\end{align} +``` """ -function constraint_radial_topology(pm::AbstractSwitchModels, nw::Int; relax::Bool=false) +function constraint_radial_topology(pm::AbstractUnbalancedPowerModel, nw::Int; relax::Bool=false) # doi: 10.1109/TSG.2020.2985087 var(pm, nw)[:f] = Dict{Tuple{Int,Int,Int},JuMP.VariableRef}() var(pm, nw)[:lambda] = Dict{Tuple{Int,Int},JuMP.VariableRef}() var(pm, nw)[:beta] = Dict{Tuple{Int,Int},JuMP.VariableRef}() var(pm, nw)[:alpha] = Dict{Tuple{Int,Int},Union{JuMP.VariableRef,Int}}() - N = ids(pm, nw, :blocks) - L = ref(pm, nw, :block_pairs) + # "real" node and branch sets + N₀ = ids(pm, nw, :blocks) + L₀ = ref(pm, nw, :block_pairs) + + # Add "virtual" iᵣ to N + virtual_iᵣ = maximum(N₀)+1 + N = [N₀..., virtual_iᵣ] + iᵣ = [virtual_iᵣ] + + # create a set L of all branches, including virtual branches between iᵣ and all other nodes in L₀ + L = [L₀..., [(virtual_iᵣ, n) for n in N₀]...] + + # create a set L′ that inlcudes the branch reverses + L′ = union(L, Set([(j,i) for (i,j) in L])) - ir = ref(pm, nw, :substation_blocks) + # create variables fᵏ and λ over all L, including virtual branches connected to iᵣ + for (i,j) in L′ + for k in filter(kk->kk∉iᵣ,N) + var(pm, nw, :f)[(k, i, j)] = JuMP.@variable(pm.model, base_name="$(nw)_f_$((k,i,j))", start=0) + end + var(pm, nw, :lambda)[(i,j)] = JuMP.@variable(pm.model, base_name="$(nw)_lambda_$((i,j))", binary=!relax, lower_bound=0, upper_bound=1, start=0) - for (i,j) in L - for k in filter(k->k∉ir,N) - var(pm, nw, :f)[(k, i, j)] = JuMP.@variable(pm.model, base_name="$(nw)_f_$((k,i,j))") - var(pm, nw, :f)[(k, j, i)] = JuMP.@variable(pm.model, base_name="$(nw)_f_$((k,j,i))") + # create variable β over only original set L₀ + if (i,j) ∈ L₀ + var(pm, nw, :beta)[(i,j)] = JuMP.@variable(pm.model, base_name="$(nw)_beta_$((i,j))", lower_bound=0, upper_bound=1, start=0) end - var(pm, nw, :lambda)[(i,j)] = relax ? JuMP.@variable(pm.model, base_name="$(nw)_lambda_$((i,j))", lower_bound=0, upper_bound=1) : JuMP.@variable(pm.model, base_name="$(nw)_lambda_$((i,j))", binary=true) - var(pm, nw, :lambda)[(j,i)] = relax ? JuMP.@variable(pm.model, base_name="$(nw)_lambda_$((j,i))", lower_bound=0, upper_bound=1) : JuMP.@variable(pm.model, base_name="$(nw)_lambda_$((j,i))", binary=true) - var(pm, nw, :beta)[(i,j)] = relax ? JuMP.@variable(pm.model, base_name="$(nw)_beta_$((i,j))", lower_bound=0, upper_bound=1) : JuMP.@variable(pm.model, base_name="$(nw)_beta_$((i,j))", binary=true) end - for (s,sw) in get(PMD.ismultinetwork(pm.data) ? pm.data["nw"]["$(nw)"] : pm.data, "switch", Dict()) + # create an aux varible α that maps to the switch states + for (s,sw) in ref(pm, nw, :switch) (i,j) = (ref(pm, nw, :bus_block_map, sw["f_bus"]), ref(pm, nw, :bus_block_map, sw["t_bus"])) - - if sw[PMD.pmd_math_component_status["switch"]] != PMD.pmd_math_component_status_inactive["switch"] - var(pm, nw, :alpha)[(i,j)] = var(pm, nw, :alpha)[(j,i)] = var(pm, nw, :switch_state, parse(Int,s)) - else - var(pm, nw, :alpha)[(i,j)] = var(pm, nw, :alpha)[(j,i)] = 0 - end + var(pm, nw, :alpha)[(i,j)] = var(pm, nw, :switch_state, s) end f = var(pm, nw, :f) @@ -148,42 +253,44 @@ function constraint_radial_topology(pm::AbstractSwitchModels, nw::Int; relax::Bo α = var(pm, nw, :alpha) # Eq. (1) -> Eqs. (3-8) - for k in filter(kk->kk∉ir,N) + for k in filter(kk->kk∉iᵣ,N) # Eq. (3) - bp_to = filter(((j,i),)->i∈ir&&i!=j,L) - bp_fr = filter(((i,j),)->i∈ir&&i!=j,L) - if !(isempty(bp_fr) && isempty(bp_to)) - c = JuMP.@constraint( - pm.model, - sum(f[(k,j,i)] for (j,i) in bp_to) - - sum(f[(k,i,j)] for (i,j) in bp_fr) - == - -1.0 - ) + for _iᵣ in iᵣ + jiᵣ = filter(((j,i),)->i==_iᵣ&&i!=j,L) + iᵣj = filter(((i,j),)->i==_iᵣ&&i!=j,L) + if !(isempty(jiᵣ) && isempty(iᵣj)) + c = JuMP.@constraint( + pm.model, + sum(f[(k,j,i)] for (j,i) in jiᵣ) - + sum(f[(k,i,j)] for (i,j) in iᵣj) + == + -1.0 + ) + end end # Eq. (4) - bp_to = filter(((j,i),)->i==k&&i!=j,L) - bp_fr = filter(((i,j),)->i==k&&i!=j,L) - if !(isempty(bp_fr) && isempty(bp_to)) + jk = filter(((j,i),)->i==k&&i!=j,L′) + kj = filter(((i,j),)->i==k&&i!=j,L′) + if !(isempty(jk) && isempty(kj)) c = JuMP.@constraint( pm.model, - sum(f[(k,j,k)] for (j,i) in bp_to) - - sum(f[(k,k,j)] for (i,j) in bp_fr) + sum(f[(k,j,k)] for (j,i) in jk) - + sum(f[(k,k,j)] for (i,j) in kj) == 1.0 ) end # Eq. (5) - for i in filter(kk->kk∉ir&&kk!=k,N) - bp_to = filter(((j,ii),)->ii==i&&ii!=j,L) - bp_fr = filter(((ii,j),)->ii==i&&ii!=j,L) - if !(isempty(bp_fr) && isempty(bp_to)) + for i in filter(kk->kk∉iᵣ&&kk!=k,N) + ji = filter(((j,ii),)->ii==i&&ii!=j,L′) + ij = filter(((ii,j),)->ii==i&&ii!=j,L′) + if !(isempty(ji) && isempty(ij)) c = JuMP.@constraint( pm.model, - sum(f[(k,j,i)] for (j,ii) in bp_to) - - sum(f[(k,i,j)] for (ii,j) in bp_fr) + sum(f[(k,j,i)] for (j,ii) in ji) - + sum(f[(k,i,j)] for (ii,j) in ij) == 0.0 ) @@ -202,7 +309,8 @@ function constraint_radial_topology(pm::AbstractSwitchModels, nw::Int; relax::Bo # Eq. (7) JuMP.@constraint(pm.model, sum((λ[(i,j)] + λ[(j,i)]) for (i,j) in L) == length(N) - 1) - for (i,j) in L + # Connect λ and β, map β back to α, over only real switches (L₀) + for (i,j) in L₀ # Eq. (8) JuMP.@constraint(pm.model, λ[(i,j)] + λ[(j,i)] == β[(i,j)]) @@ -210,3 +318,529 @@ function constraint_radial_topology(pm::AbstractSwitchModels, nw::Int; relax::Bo JuMP.@constraint(pm.model, α[(i,j)] <= β[(i,j)]) end end + + +@doc raw""" + constraint_mc_switch_power_open_close( + pm::AbstractUnbalancedPowerModel, + nw::Int, + i::Int, + f_bus::Int, + t_bus::Int, + f_connections::Vector{Int}, + t_connections::Vector{Int} + ) + +generic switch power open/closed constraint + +```math +\begin{align} +& S^{sw}_{i,c} \leq S^{swu}_{i,c} z^{sw}_i\ \forall i \in S,\forall c \in C \\ +& S^{sw}_{i,c} \geq -S^{swu}_{i,c} z^{sw}_i\ \forall i \in S,\forall c \in C +\end{align} +``` +""" +function constraint_mc_switch_power_open_close(pm::AbstractUnbalancedPowerModel, nw::Int, i::Int, f_bus::Int, t_bus::Int, f_connections::Vector{Int}, t_connections::Vector{Int}) + psw = var(pm, nw, :psw, (i, f_bus, t_bus)) + qsw = var(pm, nw, :qsw, (i, f_bus, t_bus)) + + state = var(pm, nw, :switch_state, i) + + rating = min.(fill(1.0, length(f_connections)), PMD._calc_branch_power_max_frto(ref(pm, nw, :switch, i), ref(pm, nw, :bus, f_bus), ref(pm, nw, :bus, t_bus))...) + + for (idx, c) in enumerate(f_connections) + JuMP.@constraint(pm.model, psw[c] <= rating[idx] * state) + JuMP.@constraint(pm.model, psw[c] >= -rating[idx] * state) + JuMP.@constraint(pm.model, qsw[c] <= rating[idx] * state) + JuMP.@constraint(pm.model, qsw[c] >= -rating[idx] * state) + + # Indicator constraint version, for reference + # JuMP.@constraint(pm.model, !state => {psw[c] == 0.0}) + # JuMP.@constraint(pm.model, !state => {qsw[c] == 0.0}) + end +end + + +@doc raw""" + constraint_mc_generator_power_block_on_off( + pm::AbstractUnbalancedPowerModel, + nw::Int, + i::Int, + connections::Vector{Int}, + pmin::Vector{<:Real}, + pmax::Vector{<:Real}, + qmin::Vector{<:Real}, + qmax::Vector{<:Real} + ) + +Generic block mld on/off constraint for generator power + +```math +\begin{align} +S_i \geq z^{bl}_b S^{lb}_i, i \in {b \in B} \\ +S_i \leq z^{bl}_b S^{ub}_i, i \in {b \in B} +\end{align} +``` +""" +function constraint_mc_generator_power_block_on_off(pm::AbstractUnbalancedPowerModel, nw::Int, i::Int, connections::Vector{<:Int}, pmin::Vector{<:Real}, pmax::Vector{<:Real}, qmin::Vector{<:Real}, qmax::Vector{<:Real}) + z_block = var(pm, nw, :z_block, ref(pm, nw, :gen_block_map, i)) + + pg = var(pm, nw, :pg, i) + qg = var(pm, nw, :qg, i) + + for (idx, c) in enumerate(connections) + isfinite(pmin[idx]) && JuMP.@constraint(pm.model, pg[c] >= pmin[idx]*z_block) + isfinite(qmin[idx]) && JuMP.@constraint(pm.model, qg[c] >= qmin[idx]*z_block) + + isfinite(pmax[idx]) && JuMP.@constraint(pm.model, pg[c] <= pmax[idx]*z_block) + isfinite(qmax[idx]) && JuMP.@constraint(pm.model, qg[c] <= qmax[idx]*z_block) + end +end + + +@doc raw""" + constraint_mc_generator_power_traditional_on_off(pm::AbstractUnbalancedPowerModel, nw::Int, i::Int, connections::Vector{Int}, pmin::Vector{<:Real}, pmax::Vector{<:Real}, qmin::Vector{<:Real}, qmax::Vector{<:Real}) + +Generic traditional mld on/off constraint for generator power + +```math +\begin{align} +S_i \geq z^{gen}_i S^{lb}_i \\ +S_i \leq z^{gen}_i S^{ub}_i +\end{align} +``` +""" +function constraint_mc_generator_power_traditional_on_off(pm::AbstractUnbalancedPowerModel, nw::Int, i::Int, connections::Vector{<:Int}, pmin::Vector{<:Real}, pmax::Vector{<:Real}, qmin::Vector{<:Real}, qmax::Vector{<:Real}) + z_gen = var(pm, nw, :z_gen, i) + + pg = var(pm, nw, :pg, i) + qg = var(pm, nw, :qg, i) + + for (idx, c) in enumerate(connections) + isfinite(pmin[idx]) && JuMP.@constraint(pm.model, pg[c] >= pmin[idx]*z_gen) + isfinite(qmin[idx]) && JuMP.@constraint(pm.model, qg[c] >= qmin[idx]*z_gen) + + isfinite(pmax[idx]) && JuMP.@constraint(pm.model, pg[c] <= pmax[idx]*z_gen) + isfinite(qmax[idx]) && JuMP.@constraint(pm.model, qg[c] <= qmax[idx]*z_gen) + end +end + + +@doc raw""" + constraint_mc_storage_block_on_off( + pm::AbstractUnbalancedPowerModel, + nw::Int, + i::Int, + connections::Vector{Int}, + pmin::Real, + pmax::Real, + qmin::Real, + qmax::Real, + charge_ub::Real, + discharge_ub::Real + ) + +block on/off constraint for storage + +```math +\begin{align} +\sum_{\substack{c \in \Gamma}} S_{i,c} \geq z^{bl}_b S^{lb}_i, i \in {b \in B} \\ +\sum_{\substack{c \in \Gamma}} S_{i,c} \leq z^{bl}_b S^{ub}_i, i \in {b \in B} +\end{align} +``` +""" +function constraint_mc_storage_block_on_off(pm::AbstractUnbalancedPowerModel, nw::Int, i::Int, connections::Vector{Int}, pmin::Real, pmax::Real, qmin::Real, qmax::Real, charge_ub::Real, discharge_ub::Real) + z_block = var(pm, nw, :z_block, ref(pm, nw, :storage_block_map, i)) + + ps = [var(pm, nw, :ps, i)[c] for c in connections] + qs = [var(pm, nw, :qs, i)[c] for c in connections] + + isfinite(pmin) && JuMP.@constraint(pm.model, sum(ps) >= z_block*pmin) + isfinite(qmin) && JuMP.@constraint(pm.model, sum(qs) >= z_block*qmin) + + isfinite(pmax) && JuMP.@constraint(pm.model, sum(ps) <= z_block*pmax) + isfinite(qmax) && JuMP.@constraint(pm.model, sum(qs) <= z_block*qmax) +end + + +@doc raw""" + constraint_mc_storage_traditional_on_off( + pm::AbstractUnbalancedPowerModel, + nw::Int, + i::Int, + connections::Vector{Int}, + pmin::Real, + pmax::Real, + qmin::Real, + qmax::Real, + charge_ub::Real, + discharge_ub::Real + ) + +Traditional on/off constraint for storage + +```math +\begin{align} +\sum_{\substack{c \in \Gamma}} S_{i,c} \geq z^{strg}_i S^{lb}_i \\ +\sum_{\substack{c \in \Gamma}} S_{i,c} \leq z^{strg}_i S^{ub}_i +\end{align} +``` +""" +function constraint_mc_storage_traditional_on_off(pm::AbstractUnbalancedPowerModel, nw::Int, i::Int, connections::Vector{Int}, pmin::Real, pmax::Real, qmin::Real, qmax::Real, charge_ub::Real, discharge_ub::Real) + z_storage = var(pm, nw, :z_storage, i) + + ps = [var(pm, nw, :ps, i)[c] for c in connections] + qs = [var(pm, nw, :qs, i)[c] for c in connections] + + isfinite(pmin) && JuMP.@constraint(pm.model, sum(ps) >= z_storage*pmin) + isfinite(qmin) && JuMP.@constraint(pm.model, sum(qs) >= z_storage*qmin) + + isfinite(pmax) && JuMP.@constraint(pm.model, sum(ps) <= z_storage*pmax) + isfinite(qmax) && JuMP.@constraint(pm.model, sum(qs) <= z_storage*qmax) +end + + +@doc raw""" + constraint_mc_storage_phase_unbalance( + pm::AbstractUnbalancedPowerModel, + nw::Int, + i::Int, + connections::Vector{Int}, + unbalance_factor::Real + ) + +Enforces that storage inputs/outputs are (approximately) balanced across each phase, by some `unbalance_factor` + +```math +S^{strg}_{i,c} \geq S^{strg}_{i,d} - f^{unbal} \left( -d^{on}_i S^{strg}_{i,d} + c^{on}_i S^{strg}_{i,d} \right) \forall c,d \in C +S^{strg}_{i,c} \leq S^{strg}_{i,d} + f^{unbal} \left( -d^{on}_i S^{strg}_{i,d} + c^{on}_i S^{strg}_{i,d} \right) \forall c,d \in C +``` +""" +function constraint_mc_storage_phase_unbalance(pm::AbstractUnbalancedPowerModel, nw::Int, i::Int, connections::Vector{Int}, unbalance_factor::Real) + ps = var(pm, nw, :ps, i) + qs = var(pm, nw, :qs, i) + + sc_on = var(pm, nw, :sc_on, i) # ==1 charging (p,q > 0) + sd_on = var(pm, nw, :sd_on, i) # ==1 discharging (p,q < 0) + + sd_on_ps = JuMP.@variable(pm.model, [c in connections], base_name="$(nw)_sd_on_ps_$(i)", lower_bound=0.0) + sc_on_ps = JuMP.@variable(pm.model, [c in connections], base_name="$(nw)_sc_on_ps_$(i)", lower_bound=0.0) + sd_on_qs = JuMP.@variable(pm.model, [c in connections], base_name="$(nw)_sd_on_qs_$(i)", lower_bound=0.0) + sc_on_qs = JuMP.@variable(pm.model, [c in connections], base_name="$(nw)_sc_on_qs_$(i)", lower_bound=0.0) + for c in connections + PolyhedralRelaxations.construct_bilinear_relaxation!(pm.model, sd_on, ps[c], sd_on_ps[c], [0,1], [JuMP.lower_bound(ps[c]), JuMP.upper_bound(ps[c])]) + PolyhedralRelaxations.construct_bilinear_relaxation!(pm.model, sc_on, ps[c], sc_on_ps[c], [0,1], [JuMP.lower_bound(ps[c]), JuMP.upper_bound(ps[c])]) + PolyhedralRelaxations.construct_bilinear_relaxation!(pm.model, sd_on, qs[c], sd_on_qs[c], [0,1], [JuMP.lower_bound(qs[c]), JuMP.upper_bound(qs[c])]) + PolyhedralRelaxations.construct_bilinear_relaxation!(pm.model, sc_on, qs[c], sc_on_qs[c], [0,1], [JuMP.lower_bound(qs[c]), JuMP.upper_bound(qs[c])]) + end + + for (idx,c) in enumerate(connections) + if idx < length(connections) + for d in connections[idx+1:end] + JuMP.@constraint(pm.model, ps[c] >= ps[d] - unbalance_factor*(-1*sd_on_ps[d] + 1*sc_on_ps[d])) + JuMP.@constraint(pm.model, ps[c] <= ps[d] + unbalance_factor*(-1*sd_on_ps[d] + 1*sc_on_ps[d])) + + JuMP.@constraint(pm.model, qs[c] >= qs[d] - unbalance_factor*(-1*sd_on_qs[d] + 1*sc_on_qs[d])) + JuMP.@constraint(pm.model, qs[c] <= qs[d] + unbalance_factor*(-1*sd_on_qs[d] + 1*sc_on_qs[d])) + end + end + end +end + + +@doc raw""" + constraint_storage_complementarity_mi_block_on_off( + pm::AbstractUnbalancedPowerModel, + n::Int, + i::Int, + charge_ub::Real, + discharge_ub::Real + ) + +Nonlinear storage complementarity mi constraint for block mld problem. + +math``` +\begin{align} +c^{on}_i * d^{on}_i == z^{bl}_b, i \in {b \in B} \\ +c^{on}_i c^{ub}_i \geq c_i \\ +d^{on}_i d^{ub}_i \geq d_i +\end{align} +``` +""" +function constraint_storage_complementarity_mi_block_on_off(pm::AbstractUnbalancedPowerModel, n::Int, i::Int, charge_ub::Real, discharge_ub::Real) + sc = var(pm, n, :sc, i) + sd = var(pm, n, :sd, i) + sc_on = var(pm, n, :sc_on, i) + sd_on = var(pm, n, :sd_on, i) + + z_block = var(pm, n, :z_block, ref(pm, n, :storage_block_map, i)) + + JuMP.@constraint(pm.model, sc_on*sd_on == z_block) + JuMP.@constraint(pm.model, sc_on*charge_ub >= sc) + JuMP.@constraint(pm.model, sd_on*discharge_ub >= sd) +end + + +@doc raw""" + constraint_storage_complementarity_mi_traditional_on_off( + pm::AbstractUnbalancedPowerModel, + n::Int, + i::Int, + charge_ub::Real, + discharge_ub::Real + ) + +Nonlinear storage complementarity mi constraint for traditional mld problem. + +math``` +\begin{align} +c^{on}_i d^{on}_i = z^{strg}_i \\ +c^{on}_i c^{ub}_i \geq c_i \\ +d^{on}_i d^{ub}_i \geq d_i +\end{align} +``` +""" +function constraint_storage_complementarity_mi_traditional_on_off(pm::AbstractUnbalancedPowerModel, n::Int, i::Int, charge_ub::Real, discharge_ub::Real) + sc = var(pm, n, :sc, i) + sd = var(pm, n, :sd, i) + sc_on = var(pm, n, :sc_on, i) + sd_on = var(pm, n, :sd_on, i) + + z_storage = var(pm, n, :z_storage, i) + + JuMP.@constraint(pm.model, sc_on*sd_on == z_storage) + JuMP.@constraint(pm.model, sc_on*charge_ub >= sc) + JuMP.@constraint(pm.model, sd_on*discharge_ub >= sd) +end + + +@doc raw""" + constraint_mc_storage_phase_unbalance_grid_following( + pm::AbstractUnbalancedPowerModel, + nw::Int, + i::Int, + connections::Vector{Int}, + unbalance_factor::Real + ) + +Enforces that storage inputs/outputs are (approximately) balanced across each phase, by some `unbalance_factor` on grid-following +inverters only. Requires z_inverter variable + +```math +S^{strg}_{i,c} \geq S^{strg}_{i,d} - f^{unbal} \left( -d^{on}_i S^{strg}_{i,d} + c^{on}_i S^{strg}_{i,d} \right) \forall c,d \in C +S^{strg}_{i,c} \leq S^{strg}_{i,d} + f^{unbal} \left( -d^{on}_i S^{strg}_{i,d} + c^{on}_i S^{strg}_{i,d} \right) \forall c,d \in C +``` +""" +function constraint_mc_storage_phase_unbalance_grid_following(pm::AbstractUnbalancedPowerModel, nw::Int, i::Int, connections::Vector{Int}, unbalance_factor::Real) + z_inverter = var(pm, nw, :z_inverter, (:storage, i)) + + ps = var(pm, nw, :ps, i) + qs = var(pm, nw, :qs, i) + + sc_on = var(pm, nw, :sc_on, i) # ==1 charging (p,q > 0) + sd_on = var(pm, nw, :sd_on, i) # ==1 discharging (p,q < 0) + + sd_on_ps = JuMP.@variable(pm.model, [c in connections], base_name="$(nw)_sd_on_ps_$(i)") + sc_on_ps = JuMP.@variable(pm.model, [c in connections], base_name="$(nw)_sc_on_ps_$(i)") + sd_on_qs = JuMP.@variable(pm.model, [c in connections], base_name="$(nw)_sd_on_qs_$(i)") + sc_on_qs = JuMP.@variable(pm.model, [c in connections], base_name="$(nw)_sc_on_qs_$(i)") + for c in connections + PolyhedralRelaxations.construct_bilinear_relaxation!(pm.model, sd_on, ps[c], sd_on_ps[c], [0,1], [JuMP.lower_bound(ps[c]), JuMP.upper_bound(ps[c])]) + PolyhedralRelaxations.construct_bilinear_relaxation!(pm.model, sc_on, ps[c], sc_on_ps[c], [0,1], [JuMP.lower_bound(ps[c]), JuMP.upper_bound(ps[c])]) + PolyhedralRelaxations.construct_bilinear_relaxation!(pm.model, sd_on, qs[c], sd_on_qs[c], [0,1], [JuMP.lower_bound(qs[c]), JuMP.upper_bound(qs[c])]) + PolyhedralRelaxations.construct_bilinear_relaxation!(pm.model, sc_on, qs[c], sc_on_qs[c], [0,1], [JuMP.lower_bound(qs[c]), JuMP.upper_bound(qs[c])]) + end + + ps_zinverter = JuMP.@variable(pm.model, [c in connections], base_name="$(nw)_ps_zinverter_$(i)") + qs_zinverter = JuMP.@variable(pm.model, [c in connections], base_name="$(nw)_qs_zinverter_$(i)") + for c in connections + PolyhedralRelaxations.construct_bilinear_relaxation!(pm.model, z_inverter, ps[c], ps_zinverter[c], [0,1], [JuMP.lower_bound(ps[c]), JuMP.upper_bound(ps[c])]) + PolyhedralRelaxations.construct_bilinear_relaxation!(pm.model, z_inverter, qs[c], qs_zinverter[c], [0,1], [JuMP.lower_bound(qs[c]), JuMP.upper_bound(qs[c])]) + end + + sd_on_ps_zinverter = JuMP.@variable(pm.model, [c in connections], base_name="$(nw)_sd_on_ps_zinverter_$(i)") + sc_on_ps_zinverter = JuMP.@variable(pm.model, [c in connections], base_name="$(nw)_sc_on_ps_zinverter_$(i)") + sd_on_qs_zinverter = JuMP.@variable(pm.model, [c in connections], base_name="$(nw)_sd_on_qs_zinverter_$(i)") + sc_on_qs_zinverter = JuMP.@variable(pm.model, [c in connections], base_name="$(nw)_sc_on_qs_zinverter_$(i)") + for c in connections + PolyhedralRelaxations.construct_bilinear_relaxation!(pm.model, z_inverter, sd_on_ps[c], sd_on_ps_zinverter[c], [0,1], [JuMP.lower_bound(ps[c]), JuMP.upper_bound(ps[c])]) + PolyhedralRelaxations.construct_bilinear_relaxation!(pm.model, z_inverter, sc_on_ps[c], sc_on_ps_zinverter[c], [0,1], [JuMP.lower_bound(ps[c]), JuMP.upper_bound(ps[c])]) + PolyhedralRelaxations.construct_bilinear_relaxation!(pm.model, z_inverter, sd_on_qs[c], sd_on_qs_zinverter[c], [0,1], [JuMP.lower_bound(qs[c]), JuMP.upper_bound(qs[c])]) + PolyhedralRelaxations.construct_bilinear_relaxation!(pm.model, z_inverter, sc_on_qs[c], sc_on_qs_zinverter[c], [0,1], [JuMP.lower_bound(qs[c]), JuMP.upper_bound(qs[c])]) + end + + for (idx,c) in enumerate(connections) + if idx < length(connections) + for d in connections[idx+1:end] + JuMP.@constraint(pm.model, ps[c]-ps_zinverter[c] >= ps[d] - unbalance_factor*(-1*sd_on_ps[d] + 1*sc_on_ps[d]) - ps_zinverter[d] + unbalance_factor*(-1*sd_on_ps_zinverter[d] + 1*sc_on_ps_zinverter[d])) + JuMP.@constraint(pm.model, ps[c]-ps_zinverter[c] <= ps[d] + unbalance_factor*(-1*sd_on_ps[d] + 1*sc_on_ps[d]) - ps_zinverter[d] - unbalance_factor*(-1*sd_on_ps_zinverter[d] + 1*sc_on_ps_zinverter[d])) + + JuMP.@constraint(pm.model, qs[c]-qs_zinverter[c] >= qs[d] - unbalance_factor*(-1*sd_on_qs[d] + 1*sc_on_qs[d]) - qs_zinverter[d] + unbalance_factor*(-1*sd_on_qs_zinverter[d] + 1*sc_on_qs_zinverter[d])) + JuMP.@constraint(pm.model, qs[c]-qs_zinverter[c] <= qs[d] + unbalance_factor*(-1*sd_on_qs[d] + 1*sc_on_qs[d]) - qs_zinverter[d] - unbalance_factor*(-1*sd_on_qs_zinverter[d] + 1*sc_on_qs_zinverter[d])) + end + end + end +end + + +@doc raw""" + constraint_grid_forming_inverter_per_cc(pm::AbstractUnbalancedPowerModel, nw::Int; relax::Bool=false) + +Constrains each connected component of the load block graph to have only one grid-forming inverter, if the block is enabled + +```math +\begin{align} +\sum_{k \in |{\cal L}|} y^k_{ab} = 1 \forall ab \in \cal S \\ +\sum_{ab \in {\cal T}_k} (1-z_{ab}) - |{\cal T}_k| + 1 \le \sum_{i \in {\cal D}_k} x_i \le 1 \forall k \in \cal L \\ +y^k_{ab} - (1 - z_{ab}) \le \sum_{i \in {\cal D}_k} x_i \le y^k_{ab} + (1 - z_{ab}) \forall k \in \cal L,ab \in {\cal T}_k \\ +y^{k'}_{dc} - (1 - z_{dc}) - (1 - z_{ab}) \le y^{k'}_{ab} \le y^{k'}_{dc} + (1 - z_{dc}) + (1 - z_{ab}) \forall k \in \cal L,ab \in {\cal T}_k, dc \in {\cal T}_k +\end{align} +``` +""" +function constraint_grid_forming_inverter_per_cc(pm::AbstractUnbalancedPowerModel, nw::Int; relax::Bool=false) + # Set of base connected components + L = Set{Int}(ids(pm, nw, :blocks)) + + if !(haskey(var(pm, nw), :y) && !isempty(var(pm, nw, :y))) + # variable representing if switch ab has 'color' k + var(pm, nw)[:y] = Dict{Tuple{Int,Int},JuMP.VariableRef}() + for k in L + for ab in ids(pm, nw, :switch) + var(pm, nw, :y)[(k,ab)] = JuMP.@variable( + pm.model, + base_name="$(nw)_y", + binary=!relax, + lower_bound=0, + upper_bound=1 + ) + end + end + end + + y = var(pm, nw, :y) + z = var(pm, nw, :switch_state) + x = var(pm, nw, :z_inverter) + + for ((t,j), z_inv) in x + if t == :gen && startswith(ref(pm, nw, t, j, "source_id"), "voltage_source") + JuMP.@constraint(pm.model, z_inv == 1) + end + end + + # Eq. (2) + if !(haskey(con(pm, nw), :y) && !isempty(con(pm, nw, :y))) + # constrain each y to have only one color + con(pm, nw)[:y] = Dict{Int,JuMP.ConstraintRef}() + for ab in ids(pm, nw, :switch) + con(pm, nw, :y)[ab] = JuMP.@constraint(pm.model, sum(y[(k,ab)] for k in L) == 1) + end + end + + # Eqs. (3)-(5) + for k in L + Dₖ = ref(pm, nw, :block_inverters, k) + Tₖ = ref(pm, nw, :block_switches, k) + + # Eq. (3) + if !isempty(Dₖ) + JuMP.@constraint(pm.model, sum(x[i] for i in Dₖ) >= sum(1-z[ab] for ab in Tₖ)-length(Tₖ)+1) + JuMP.@constraint(pm.model, sum(x[i] for i in Dₖ) <= 1) + end + + for ab in Tₖ + # Eq. (4) + JuMP.@constraint(pm.model, sum(x[i] for i in Dₖ) >= y[(k, ab)] - (1 - z[ab])) + JuMP.@constraint(pm.model, sum(x[i] for i in Dₖ) <= y[(k, ab)] + (1 - z[ab])) + + for dc in filter(x->x!=ab, Tₖ) + for k′ in L + # Eq. (5) + JuMP.@constraint(pm.model, y[(k′,ab)] >= y[(k′,dc)] - (1 - z[dc]) - (1 - z[ab])) + JuMP.@constraint(pm.model, y[(k′,ab)] <= y[(k′,dc)] + (1 - z[dc]) + (1 - z[ab])) + end + end + end + end +end + + +@doc raw""" + constraint_disable_networking(pm::AbstractUnbalancedPowerModel, nw::Int; relax::Bool=false) + +Constrains each microgrid to not network with another microgrid, while still allowing them to expand. + +```math +\begin{align} +\sum_{k \in |{\cal L}|} y^k_{ab} = 1, \forall ab \in {\cal S}\\ +y^k_{ab} - (1 - z_{ab}) \le x_k^{mg} \le y^k_{ab} + (1 - z_{ab}), \forall k \in {\cal L}\\ +y^{k'}_{dc} - (1 - z_{dc}) - (1 - z_{ab}) \le y^{k'}_{ab} \le y^{k'}_{dc} + (1 - z_{dc}) + (1 - z_{ab}), \forall k \in {\cal L}, \forall ab \in {\cal T}_k, \forall dc \in {\cal T}_k\setminus {ab} +\end{align} +``` +""" +function constraint_disable_networking(pm::AbstractUnbalancedPowerModel, nw::Int; relax::Bool=false) + # Set of base connected components + L = Set{Int}(ids(pm, nw, :blocks)) + + if !(haskey(var(pm, nw), :yy) && !isempty(var(pm, nw, :yy))) + # variable representing if switch ab has 'color' k + var(pm, nw)[:yy] = Dict{Tuple{Int,Int},JuMP.VariableRef}() + for k in L + for ab in ids(pm, nw, :switch) + var(pm, nw, :yy)[(k,ab)] = JuMP.@variable( + pm.model, + base_name="$(nw)_yy", + binary=!relax, + lower_bound=0, + upper_bound=1 + ) + end + end + end + + y = var(pm, nw, :yy) + z = var(pm, nw, :switch_state) + x = Dict{Int,Int}(n => (n in ids(pm, nw, :microgrid_blocks) ? 1 : 0) for n in ids(pm, nw, :blocks)) + + if !(haskey(con(pm, nw), :yy) && !isempty(con(pm, nw, :yy))) + # constrain each y to have only one color + con(pm, nw)[:yy] = Dict{Int,JuMP.ConstraintRef}() + for ab in ids(pm, nw, :switch) + con(pm, nw, :yy)[ab] = JuMP.@constraint(pm.model, sum(y[(k,ab)] for k in L) == 1) + end + end + + # Eqs. (4)-(5) + for k in L + Tₖ = ref(pm, nw, :block_switches, k) + + for ab in Tₖ + # Eq. (4) + JuMP.@constraint(pm.model, x[k] >= y[(k, ab)] - (1 - z[ab])) + JuMP.@constraint(pm.model, x[k] <= y[(k, ab)] + (1 - z[ab])) + + for dc in filter(x->x!=ab, Tₖ) + for k′ in L + # Eq. (5) + JuMP.@constraint(pm.model, y[(k′,ab)] >= y[(k′,dc)] - (1 - z[dc]) - (1 - z[ab])) + JuMP.@constraint(pm.model, y[(k′,ab)] <= y[(k′,dc)] + (1 - z[dc]) + (1 - z[ab])) + end + end + end + end +end + + +""" + constraint_energized_blocks_strictly_increasing(pm::AbstractUnbalancedPowerModel, n_1::Int, n_2::Int) + +Constraint to ensure that the number of energized load blocks from one timestep to another is strictly increasing +and that once energized, a load block cannot be shed in a later timestep. +""" +function constraint_energized_blocks_strictly_increasing(pm::AbstractUnbalancedPowerModel, n_1::Int, n_2::Int) + for block_id in ids(pm, n_2, :blocks) + z_block_n1 = var(pm, n_1, :z_block, block_id) + z_block_n2 = var(pm, n_2, :z_block, block_id) + + JuMP.@constraint(pm.model, z_block_n2 >= z_block_n1) + end +end diff --git a/src/core/constraint_template.jl b/src/core/constraint_template.jl index efb5fa05..e821d484 100644 --- a/src/core/constraint_template.jl +++ b/src/core/constraint_template.jl @@ -1,27 +1,228 @@ """ - constraint_switch_state_max_actions(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default) + constraint_mc_bus_voltage_block_on_off(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default) -max switching actions per timestep constraint +Template function for bus voltage block on/off constraint. """ -function constraint_switch_state_max_actions(pm::AbstractSwitchModels; nw::Int=nw_id_default) - constraint_switch_state_max_actions(pm, nw) +function constraint_mc_bus_voltage_block_on_off(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default) + for (i,bus) in ref(pm, nw, :bus) + constraint_mc_bus_voltage_block_on_off(pm, nw, i, bus["vmin"], bus["vmax"]) + end +end + + +""" + constraint_mc_bus_voltage_traditional_on_off(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default) + +Template function for bus voltage traditional on/off constraint. +""" +function constraint_mc_bus_voltage_traditional_on_off(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default) + for (i,bus) in ref(pm, nw, :bus) + constraint_mc_bus_voltage_traditional_on_off(pm, nw, i, bus["vmin"], bus["vmax"]) + end +end + + +""" + constraint_mc_generator_power_block_on_off(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) + +Template function for generator power block on/off constraint. +""" +function constraint_mc_generator_power_block_on_off(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) + gen = ref(pm, nw, :gen, i) + ncnds = length(gen["connections"]) + + pmin = get(gen, "pmin", fill(-Inf, ncnds)) + pmax = get(gen, "pmax", fill( Inf, ncnds)) + qmin = get(gen, "qmin", fill(-Inf, ncnds)) + qmax = get(gen, "qmax", fill( Inf, ncnds)) + + constraint_mc_generator_power_block_on_off(pm, nw, i, gen["connections"], pmin, pmax, qmin, qmax) +end + + +""" + constraint_mc_generator_power_traditional_on_off(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) + +Template function for generator power traditional on/off constraint. +""" +function constraint_mc_generator_power_traditional_on_off(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) + gen = ref(pm, nw, :gen, i) + ncnds = length(gen["connections"]) + + pmin = get(gen, "pmin", fill(-Inf, ncnds)) + pmax = get(gen, "pmax", fill( Inf, ncnds)) + qmin = get(gen, "qmin", fill(-Inf, ncnds)) + qmax = get(gen, "qmax", fill( Inf, ncnds)) + + constraint_mc_generator_power_traditional_on_off(pm, nw, i, gen["connections"], pmin, pmax, qmin, qmax) end """ - constraint_block_isolation(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default, relax::Bool=false) + constraint_mc_power_balance_shed_block(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) -constraint to ensure that blocks are properly isolated by open switches +Template function for power balance constraints for block load shed. """ -function constraint_block_isolation(pm::AbstractSwitchModels; nw::Int=nw_id_default, relax::Bool=false) - constraint_block_isolation(pm, nw; relax=relax) +function constraint_mc_power_balance_shed_block(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) + bus = ref(pm, nw, :bus, i) + bus_arcs = ref(pm, nw, :bus_arcs_conns_branch, i) + bus_arcs_sw = ref(pm, nw, :bus_arcs_conns_switch, i) + bus_arcs_trans = ref(pm, nw, :bus_arcs_conns_transformer, i) + bus_gens = ref(pm, nw, :bus_conns_gen, i) + bus_storage = ref(pm, nw, :bus_conns_storage, i) + bus_loads = ref(pm, nw, :bus_conns_load, i) + bus_shunts = ref(pm, nw, :bus_conns_shunt, i) + + if !haskey(con(pm, nw), :lam_kcl_r) + con(pm, nw)[:lam_kcl_r] = Dict{Int,Array{JuMP.ConstraintRef}}() + end + + if !haskey(con(pm, nw), :lam_kcl_i) + con(pm, nw)[:lam_kcl_i] = Dict{Int,Array{JuMP.ConstraintRef}}() + end + + constraint_mc_power_balance_shed_block(pm, nw, i, bus["terminals"], bus["grounded"], bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_shunts) +end + + +""" + constraint_mc_power_balance_shed_traditional(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) + +Template function for power balance constraints for traditional load shed. +""" +function constraint_mc_power_balance_shed_traditional(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) + bus = ref(pm, nw, :bus, i) + bus_arcs = ref(pm, nw, :bus_arcs_conns_branch, i) + bus_arcs_sw = ref(pm, nw, :bus_arcs_conns_switch, i) + bus_arcs_trans = ref(pm, nw, :bus_arcs_conns_transformer, i) + bus_gens = ref(pm, nw, :bus_conns_gen, i) + bus_storage = ref(pm, nw, :bus_conns_storage, i) + bus_loads = ref(pm, nw, :bus_conns_load, i) + bus_shunts = ref(pm, nw, :bus_conns_shunt, i) + + if !haskey(con(pm, nw), :lam_kcl_r) + con(pm, nw)[:lam_kcl_r] = Dict{Int,Array{JuMP.ConstraintRef}}() + end + + if !haskey(con(pm, nw), :lam_kcl_i) + con(pm, nw)[:lam_kcl_i] = Dict{Int,Array{JuMP.ConstraintRef}}() + end + + constraint_mc_power_balance_shed_traditional(pm, nw, i, bus["terminals"], bus["grounded"], bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_shunts) +end + + +""" + constraint_storage_complementarity_mi_block_on_off(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) + +Template function for mixed-integer storage complementarity constraints. +""" +function constraint_storage_complementarity_mi_block_on_off(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) + storage = ref(pm, nw, :storage, i) + charge_ub = storage["charge_rating"] + discharge_ub = storage["discharge_rating"] + + constraint_storage_complementarity_mi_block_on_off(pm, nw, i, charge_ub, discharge_ub) +end + + +""" + constraint_storage_complementarity_mi_traditional_on_off(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) + +Template function for mixed-integer storage complementarity constraints. +""" +function constraint_storage_complementarity_mi_traditional_on_off(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) + storage = ref(pm, nw, :storage, i) + charge_ub = storage["charge_rating"] + discharge_ub = storage["discharge_rating"] + + constraint_storage_complementarity_mi_traditional_on_off(pm, nw, i, charge_ub, discharge_ub) +end + + +""" + constraint_mc_storage_block_on_off(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) + +Template function for storage block on/off constraint +""" +function constraint_mc_storage_block_on_off(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) + storage = ref(pm, nw, :storage, i) + charge_ub = storage["charge_rating"] + discharge_ub = storage["discharge_rating"] + + ncnds = length(storage["connections"]) + pmin = zeros(ncnds) + pmax = zeros(ncnds) + qmin = zeros(ncnds) + qmax = zeros(ncnds) + + inj_lb, inj_ub = PMD.ref_calc_storage_injection_bounds(ref(pm, nw, :storage), ref(pm, nw, :bus)) + for (idx,c) in enumerate(storage["connections"]) + pmin[idx] = inj_lb[i][idx] + pmax[idx] = inj_ub[i][idx] + qmin[idx] = max(inj_lb[i][idx], ref(pm, nw, :storage, i, "qmin")) + qmax[idx] = min(inj_ub[i][idx], ref(pm, nw, :storage, i, "qmax")) + end + + constraint_mc_storage_block_on_off(pm, nw, i, storage["connections"], maximum(pmin), minimum(pmax), maximum(qmin), minimum(qmax), charge_ub, discharge_ub) +end + + +""" + constraint_mc_storage_traditional_on_off(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) + +Template function for storage traditional on/off constraint. +""" +function constraint_mc_storage_traditional_on_off(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) + storage = ref(pm, nw, :storage, i) + charge_ub = storage["charge_rating"] + discharge_ub = storage["discharge_rating"] + + ncnds = length(storage["connections"]) + pmin = zeros(ncnds) + pmax = zeros(ncnds) + qmin = zeros(ncnds) + qmax = zeros(ncnds) + + inj_lb, inj_ub = PMD.ref_calc_storage_injection_bounds(ref(pm, nw, :storage), ref(pm, nw, :bus)) + for (idx,c) in enumerate(storage["connections"]) + pmin[idx] = inj_lb[i][idx] + pmax[idx] = inj_ub[i][idx] + qmin[idx] = max(inj_lb[i][idx], ref(pm, nw, :storage, i, "qmin")) + qmax[idx] = min(inj_ub[i][idx], ref(pm, nw, :storage, i, "qmax")) + end + + constraint_mc_storage_traditional_on_off(pm, nw, i, storage["connections"], maximum(pmin), minimum(pmax), maximum(qmin), minimum(qmax), charge_ub, discharge_ub) +end + + + +""" + constraint_mc_storage_losses_block_on_off(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) + +Template function for storage losses block on/off constraint. +""" +function constraint_mc_storage_losses_block_on_off(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) + storage = ref(pm, nw, :storage, i) + constraint_mc_storage_losses_block_on_off(pm, nw, i, storage["storage_bus"], storage["connections"], storage["r"], storage["x"], storage["p_loss"], storage["q_loss"]) +end + + +""" + constraint_mc_storage_losses_traditional_on_off(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) + +Template function for storage losses traditional on/off constraint. +""" +function constraint_mc_storage_losses_traditional_on_off(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) + storage = ref(pm, nw, :storage, i) + constraint_mc_storage_losses_traditional_on_off(pm, nw, i, storage["storage_bus"], storage["connections"], storage["r"], storage["x"], storage["p_loss"], storage["q_loss"]) end """ constraint_mc_storage_phase_unbalance(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) -Constraint template for constraint to enforce balance between phases of ps/qs on storage +Constraint template for constraint to enforce balance between phases of ps/qs on storage. """ function constraint_mc_storage_phase_unbalance(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) strg = ref(pm, nw, :storage, i) @@ -34,11 +235,116 @@ end """ - constraint_mc_transformer_power(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default, fix_taps::Bool=true)::Nothing + constraint_mc_storage_phase_unbalance_grid_following(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) + +Constraint template for constraint to enforce balance between phases of ps/qs on storage for grid-following inverters only. +Requires `z_inverter` variables to indicate if a DER is grid-forming or grid-following +""" +function constraint_mc_storage_phase_unbalance_grid_following(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) + strg = ref(pm, nw, :storage, i) + phase_unbalance_factor = get(strg, "phase_unbalance_factor", Inf) + + if phase_unbalance_factor < Inf + constraint_mc_storage_phase_unbalance_grid_following(pm, nw, i, strg["connections"], phase_unbalance_factor) + end +end + + +""" + constraint_switch_close_action_limit(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default) + +Template function for constraint of maximum switch closes per timestep (allows unlimited switch opens). +""" +function constraint_switch_close_action_limit(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default) + constraint_switch_close_action_limit(pm, nw) +end + + +""" + constraint_isolate_block(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default) + +Template function of constraint to ensure that blocks are properly isolated by open switches in block mld problem. +""" +function constraint_isolate_block(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default) + constraint_isolate_block(pm, nw) +end + + +""" + constraint_isolate_block_traditional(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default) + +Template function for constraint to ensure that blocks are properly isolated by open switches in a traditional mld problem. +""" +function constraint_isolate_block_traditional(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default) + constraint_isolate_block_traditional(pm, nw) +end + + + +""" + constraint_radial_topology(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default, relax::Bool=false) + +Template function radial topology constraint. +""" +function constraint_radial_topology(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default, relax::Bool=false) + constraint_radial_topology(pm, nw; relax=relax) +end + + +""" + constraint_mc_switch_state_open_close(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) + +Voltage and power constraints for open/close switches +""" +function constraint_mc_switch_state_open_close(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) + switch = ref(pm, nw, :switch, i) + + f_bus = switch["f_bus"] + t_bus = switch["t_bus"] + + f_connections = switch["f_connections"] + t_connections = switch["t_connections"] + + constraint_mc_switch_voltage_open_close(pm, nw, i, f_bus, t_bus, f_connections, t_connections) + constraint_mc_switch_power_open_close(pm, nw, i, f_bus, t_bus, f_connections, t_connections) +end + + +""" + constraint_mc_transformer_power_block_on_off(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default, fix_taps::Bool=true) + +Template function for transformer power constraints for block mld problem. +""" +function constraint_mc_transformer_power_block_on_off(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default, fix_taps::Bool=true) + transformer = ref(pm, nw, :transformer, i) + f_bus = transformer["f_bus"] + t_bus = transformer["t_bus"] + f_idx = (i, f_bus, t_bus) + t_idx = (i, t_bus, f_bus) + configuration = transformer["configuration"] + f_connections = transformer["f_connections"] + t_connections = transformer["t_connections"] + tm_set = transformer["tm_set"] + tm_fixed = fix_taps ? ones(Bool, length(tm_set)) : transformer["tm_fix"] + tm_scale = PMD.calculate_tm_scale(transformer, ref(pm, nw, :bus, f_bus), ref(pm, nw, :bus, t_bus)) + pol = transformer["polarity"] + + if configuration == PMD.WYE + constraint_mc_transformer_power_yy_block_on_off(pm, nw, i, f_bus, t_bus, f_idx, t_idx, f_connections, t_connections, pol, tm_set, tm_fixed, tm_scale) + elseif configuration == PMD.DELTA + PMD.constraint_mc_transformer_power_dy(pm, nw, i, f_bus, t_bus, f_idx, t_idx, f_connections, t_connections, pol, tm_set, tm_fixed, tm_scale) + elseif configuration == "zig-zag" + error("Zig-zag not yet supported.") + end +end + + +""" + constraint_mc_transformer_power_traditional_on_off(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default, fix_taps::Bool=true) -Template function for Transformer constraints in Power-voltage space, considering winding type, conductor order, polarity and tap settings. +Template function for transformer power constraints for traditional mld problem. """ -function constraint_mc_transformer_power_on_off(pm::AbstractSwitchModels, i::Int; nw::Int=nw_id_default, fix_taps::Bool=true) +function constraint_mc_transformer_power_traditional_on_off(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default, fix_taps::Bool=true) transformer = ref(pm, nw, :transformer, i) f_bus = transformer["f_bus"] t_bus = transformer["t_bus"] @@ -53,7 +359,7 @@ function constraint_mc_transformer_power_on_off(pm::AbstractSwitchModels, i::Int pol = transformer["polarity"] if configuration == PMD.WYE - constraint_mc_transformer_power_yy_on_off(pm, nw, i, f_bus, t_bus, f_idx, t_idx, f_connections, t_connections, pol, tm_set, tm_fixed, tm_scale) + constraint_mc_transformer_power_yy_traditional_on_off(pm, nw, i, f_bus, t_bus, f_idx, t_idx, f_connections, t_connections, pol, tm_set, tm_fixed, tm_scale) elseif configuration == PMD.DELTA PMD.constraint_mc_transformer_power_dy(pm, nw, i, f_bus, t_bus, f_idx, t_idx, f_connections, t_connections, pol, tm_set, tm_fixed, tm_scale) elseif configuration == "zig-zag" @@ -63,24 +369,31 @@ end """ - constraint_storage_complementarity_mi_on_off(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) + constraint_grid_forming_inverter_per_cc(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default, relax::Bool=false) -Template function for mixed-integer storage complementarity constraints +Template function for constraining the number of grid-forming inverters per connected component in the block mld problem """ -function constraint_storage_complementarity_mi_on_off(pm::AbstractSwitchModels, i::Int; nw::Int=nw_id_default) - storage = ref(pm, nw, :storage, i) - charge_ub = storage["charge_rating"] - discharge_ub = storage["discharge_rating"] +function constraint_grid_forming_inverter_per_cc(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default, relax::Bool=false) + constraint_grid_forming_inverter_per_cc(pm, nw; relax=relax) +end - constraint_storage_complementarity_mi_on_off(pm, nw, i, charge_ub, discharge_ub) + +""" + constraint_mc_inverter_theta_ref(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default) + +Template function for setting the reference bus theta constraint to only the bus with a grid-forming inverter +""" +function constraint_mc_inverter_theta_ref(pm::AbstractUnbalancedPowerModel, i::Int; nw::Int=nw_id_default) + va_ref = get(ref(pm, nw, :bus, i), "va", deg2rad.([0.0, -120.0, 120.0])) + constraint_mc_inverter_theta_ref(pm, nw, i, va_ref) end """ - constraint_radial_topology(pm::AbstractSwitchModels; nw::Int=nw_id_default, relax::Bool=false) + constraint_disable_networking(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default, relax::Bool=false) -Constrains the network to have radial topology +Template function for constraint to disable microgrid networking. """ -function constraint_radial_topology(pm::AbstractSwitchModels; nw::Int=nw_id_default, relax::Bool=false) - constraint_radial_topology(pm, nw; relax=relax) +function constraint_disable_networking(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default, relax::Bool=false) + constraint_disable_networking(pm, nw; relax=relax) end diff --git a/src/core/export.jl b/src/core/export.jl index 0a47b5c3..94038fa6 100644 --- a/src/core/export.jl +++ b/src/core/export.jl @@ -12,5 +12,41 @@ for sym in names(@__MODULE__, all=true) end # explicitly export some PMD exports -export nw_id_default, ref, var, ids, nws, nw_ids, con, sol, optimizer_with_attributes, AbstractUnbalancedPowerModel +export nw_id_default, ref, var, ids, nws, nw_ids, con, sol, optimizer_with_attributes +# explicitly export the PMD PowerModels used in this package +export AbstractUnbalancedPowerModel, ACRUPowerModel, ACPUPowerModel, IVRUPowerModel, LPUBFDiagPowerModel, LinDist3FlowPowerModel, NFAUPowerModel, FOTRUPowerModel, FOTPUPowerModel + +import PowerModelsDistribution: Status +export Status + +import PowerModelsDistribution: SwitchState +export SwitchState + +import PowerModelsDistribution: Dispatchable +export Dispatchable + +# explicity export the PMD Enums used in this package +for status_code_enum in [Status, SwitchState, Dispatchable] + for status_code in instances(status_code_enum) + @eval import PowerModelsDistribution: $(Symbol(status_code)) + @eval export $(Symbol(status_code)) + end +end + +# so that users do not need to import JuMP to use a solver with PowerModels +import JuMP: optimizer_with_attributes +export optimizer_with_attributes + +import JuMP: TerminationStatusCode +export TerminationStatusCode + +import JuMP: ResultStatusCode +export ResultStatusCode + +for status_code_enum in [TerminationStatusCode, ResultStatusCode] + for status_code in instances(status_code_enum) + @eval import JuMP: $(Symbol(status_code)) + @eval export $(Symbol(status_code)) + end +end diff --git a/src/core/logging.jl b/src/core/logging.jl index 1622154a..92ff3469 100644 --- a/src/core/logging.jl +++ b/src/core/logging.jl @@ -14,21 +14,31 @@ function _make_filtered_logger(mods::Vector{<:Module}, level::Logging.LogLevel) end +""" + _make_filtered_logger(mods::Vector{Tuple{<:Module,Logging.LogLevel}}) + +Helper function to create the filtered logger for PMD +""" +function _make_filtered_logger(mods_levels::Vector{Tuple{<:Module,Logging.LogLevel}}) + LoggingExtras.EarlyFilteredLogger(_LOGGER) do log + if any(log._module == mod && log.level < level for (mod,level) in mods_levels) + return false + else + return true + end + end +end + + """ setup_logging!(args::Dict{String,<:Any}) -Configures logging based on runtime arguments, for use inside [`entrypoint`](@ref entrypoint) +Configures logging based on runtime arguments """ function setup_logging!(args::Dict{String,<:Any}) - if get(args, "quiet", false) - set_log_level!(:Error) - elseif get(args, "verbose", false) - set_log_level!(:Info) - elseif get(args, "debug", false) - set_log_level!(:Debug) - else - set_log_level!(:Warn) - end + log_level = get_setting(args, ("options","outputs","log-level"), "warn") + + set_log_level!(Symbol(titlecase(log_level))) end @@ -38,21 +48,30 @@ end Configures logging based `level`, `:Error`, `:Warn`, `:Info`, or `:Debug` """ function set_log_level!(level::Symbol) - mods = [PowerModelsDistribution, PowerModelsProtection, PowerModelsStability, Juniper, JSONSchema] if level == :Error loglevel = Logging.Error - push!(mods, PowerModelsONM) - _IM.silence() + IM.silence() elseif level == :Info loglevel = Logging.Info + IM.logger_config!("info") elseif level == :Debug loglevel = Logging.Debug + IM.logger_config!("debug") else - loglevel = Logging.Error - _IM.silence() + loglevel = Logging.Warn + IM.logger_config!("warn") end - Logging.global_logger(_make_filtered_logger(mods, loglevel)) + mods = [ + (PowerModelsONM, loglevel), + (PMD, loglevel), + (PMP, loglevel), + (PMS, loglevel), + (Juniper, loglevel), + (JSONSchema, Logging.Warn) + ] + + Logging.global_logger(_make_filtered_logger(mods)) end diff --git a/src/core/objective.jl b/src/core/objective.jl index 5b153269..d7ea68cf 100644 --- a/src/core/objective.jl +++ b/src/core/objective.jl @@ -1,18 +1,20 @@ @doc raw""" - objective_mc_min_load_setpoint_delta_switch_iterative(pm::AbstractUnbalancedPowerModel) + objective_min_shed_load_block_rolling_horizon(pm::AbstractUnbalancedPowerModel) - minimum load delta objective with switch scores for iterative algorithm +Minimum block load shed objective for rolling horizon problem. Note that the difference between this and +[`objective_min_shed_load_block`](@ref objective_min_shed_load_block) is that the sum over the switches +in line 2 of the objective is non-optional. ```math -\begin{align} -\mbox{minimize: } & \nonumber \\ -& \sum_{\substack{i\in N,c\in C}}{10 \left (1-z^v_i \right )} + \nonumber \\ -& \sum_{\substack{i\in L,c\in C}}{10 \omega_{i,c}\left |\Re{\left (S^d_i\right )}\right |\left ( 1-z^d_i \right ) } + \nonumber \\ -& \sum_{\substack{i\in S}}{\Delta^{sw}_i} -\end{align} -``` +\begin{align*} +\mbox{minimize: } & \\ +& \sum_{\substack{b \in B,t \in T}} W^{bl}_{b,t} \left(1 - z^{bl}_{b,t} \right) \\ +& + \sum_{\substack{s \in S,t \in T}} \left[ W^{sw}_{s,t} \left(1 - \gamma_{s,t} \right )) + W^{\Delta^{\gamma}}_{s,t}\Delta^{\gamma}_{s,t}\right ]\\ +& + \sum_{\substack{e \in E,t \in T}} \epsilon^{ub}_{e} - \epsilon_{e,t} \\ +& + \sum_{\substack{g \in G,t \in T}} f_1 P_{g,t} + f_0 +\end{align*}``` """ -function objective_mc_min_load_setpoint_delta_switch_iterative(pm::AbstractSwitchModels) +function objective_min_shed_load_block_rolling_horizon(pm::AbstractUnbalancedPowerModel) nw_id_list = sort(collect(nw_ids(pm))) for (i, n) in enumerate(nw_id_list) @@ -38,32 +40,57 @@ function objective_mc_min_load_setpoint_delta_switch_iterative(pm::AbstractSwitc end end + total_energy_ub = sum(strg["energy_rating"] for (n,nw_ref) in nws(pm) for (i,strg) in nw_ref[:storage]) + total_pmax = sum(Float64[all(.!isfinite.(gen["pmax"])) ? 0.0 : sum(gen["pmax"][isfinite.(gen["pmax"])]) for (n,nw_ref) in nws(pm) for (i, gen) in nw_ref[:gen]]) + + total_energy_ub = total_energy_ub <= 1.0 ? 1.0 : total_energy_ub + total_pmax = total_pmax <= 1.0 ? 1.0 : total_pmax + + n_dispatchable_switches = Dict(n => length(ids(pm, n, :switch_dispatchable)) for n in nw_ids(pm)) + for (n,nswitch) in n_dispatchable_switches + if nswitch < 1 + n_dispatchable_switches[n] = 1 + end + end + + obj_opts = Dict(n=>ref(pm, n, :options, "objective") for n in nw_ids(pm)) + + if first(obj_opts).second["disable-load-block-weight-cost"] + block_weights = Dict(n => Dict(i => 1.0 for i in ids(pm, n, :blocks)) for n in nw_ids(pm)) + else + block_weights = Dict(n => ref(pm, n, :block_weights) for n in nw_ids(pm)) + end + JuMP.@objective(pm.model, Min, sum( - sum( ref(pm, n, :block_weights, i) * (1-var(pm, n, :z_block, i)) for (i,block) in nw_ref[:blocks]) + - sum( 1e-3 * ref(pm, n, :switch_scores, l)*(1-var(pm, n, :switch_state, l)) for l in ids(pm, n, :switch_dispatchable) ) + - sum( 1e-2 * sum(var(pm, n, :delta_sw_state, l)) for l in ids(pm, n, :switch_dispatchable)) + - sum( strg["energy_rating"] - var(pm, n, :se, i) for (i,strg) in nw_ref[:storage]) + - sum( sum(get(gen, "cost", [ 1.0, 0.0])[2] * var(pm, n, :pg, i)[c] + get(gen, "cost", [ 1.0, 0.0])[1] for c in gen["connections"]) for (i,gen) in nw_ref[:gen]) + sum( block_weights[n][i] * Int(!obj_opts[n]["disable-load-block-shed-cost"]) * (1-var(pm, n, :z_block, i)) for (i,block) in nw_ref[:blocks]) + + sum( ref(pm, n, :switch_scores, l)*(1-var(pm, n, :switch_state, l)) for l in ids(pm, n, :switch_dispatchable) ) + + sum( Int(!obj_opts[n]["disable-switch-state-change-cost"]) * sum(var(pm, n, :delta_sw_state, l)) for l in ids(pm, n, :switch_dispatchable)) / n_dispatchable_switches[n] + + sum( Int(!obj_opts[n]["disable-storage-discharge-cost"]) * (strg["energy_rating"] - var(pm, n, :se, i)) for (i,strg) in nw_ref[:storage]) / total_energy_ub + + sum( Int(!obj_opts[n]["disable-generation-dispatch-cost"]) * sum(get(gen, "cost", [0.0, 0.0])[2] * var(pm, n, :pg, i)[c] + get(gen, "cost", [0.0, 0.0])[1] for c in gen["connections"]) for (i,gen) in nw_ref[:gen]) / total_energy_ub for (n, nw_ref) in nws(pm)) ) end @doc raw""" - objective_mc_min_load_setpoint_delta_switch_global(pm::AbstractUnbalancedPowerModel) + objective_min_shed_load_traditional_rolling_horizon(pm::AbstractUnbalancedPowerModel) -minimum load delta objective without switch scores for global algorithm +Minimum block load shed objective for rolling horizon problem. Note that the difference between this and +[`objective_min_shed_load_traditional`](@ref objective_min_shed_load_traditional) is that the sum over the switches +in line 2 of the objective is non-optional. ```math -\begin{align} -\mbox{minimize: } & \nonumber \\ -& \sum_{\substack{i\in N,c\in C}}{10 \left (1-z^v_i \right )} + \nonumber \\ -& \sum_{\substack{i\in S}}{\Delta^{sw}_i} -\end{align} +\begin{align*} +\mbox{minimize: } & \\ +& \sum_{\substack{l \in L,t \in T}} W^{d}_{l,t} \left(1 - z^{d}_{l,t} \right) \\ +& + \sum_{\substack{s \in S,t \in T}} \left[ W^{sw}_{s,t} \left(1 - \gamma_{s,t} \right )) + W^{\Delta^{\gamma}}_{s,t}\Delta^{\gamma}_{s,t}\right ]\\ +& + \sum_{\substack{e \in E,t \in T}} \epsilon^{ub}_{e} - \epsilon_{e,t} \\ +& + \sum_{\substack{g \in G,t \in T}} f_1 P_{g,t} + f_0 +\end{align*} ``` """ -function objective_mc_min_load_setpoint_delta_switch_global(pm::AbstractSwitchModels) +function objective_min_shed_load_traditional_rolling_horizon(pm::AbstractUnbalancedPowerModel) nw_id_list = sort(collect(nw_ids(pm))) for (i, n) in enumerate(nw_id_list) @@ -89,27 +116,219 @@ function objective_mc_min_load_setpoint_delta_switch_global(pm::AbstractSwitchMo end end + obj_opts = Dict(n=>ref(pm, n, :options, "objective") for n in nw_ids(pm)) + no_weights = first(obj_opts).second["disable-load-block-weight-cost"] + + load_weights = Dict( + n => Dict( + l => no_weights ? 1.0 : ref(pm, n, :block_weights, b) / length(ref(pm, n, :block_loads, b)) for b in ids(pm, n, :blocks) for l in ref(pm, n, :block_loads, b) + ) for n in nw_ids(pm) + ) + + total_energy_ub = sum(strg["energy_rating"] for (n,nw_ref) in nws(pm) for (i,strg) in nw_ref[:storage]) + total_pmax = sum(Float64[all(.!isfinite.(gen["pmax"])) ? 0.0 : sum(gen["pmax"][isfinite.(gen["pmax"])]) for (n,nw_ref) in nws(pm) for (i, gen) in nw_ref[:gen]]) + + total_energy_ub = total_energy_ub <= 1.0 ? 1.0 : total_energy_ub + total_pmax = total_pmax <= 1.0 ? 1.0 : total_pmax + + n_dispatchable_switches = Dict(n => length(ids(pm, n, :switch_dispatchable)) for n in nw_ids(pm)) + for (n,nswitch) in n_dispatchable_switches + if nswitch < 1 + n_dispatchable_switches[n] = 1 + end + end + + JuMP.@objective(pm.model, Min, + sum( + sum( load_weights[n][i] * (1 - Int(obj_opts[n]["disable-load-block-weight-cost"])) * (1-var(pm, n, :z_demand, i)) for i in ids(pm, n, :load)) + + sum( ref(pm, n, :switch_scores, l)*(1-var(pm, n, :switch_state, l)) for l in ids(pm, n, :switch_dispatchable) ) + + sum( Int(!obj_opts[n]["disable-switch-state-change-cost"]) * sum(var(pm, n, :delta_sw_state, l)) for l in ids(pm, n, :switch_dispatchable)) / n_dispatchable_switches[n] + + sum( Int(!obj_opts[n]["disable-storage-discharge-cost"]) * (strg["energy_rating"] - var(pm, n, :se, i)) for (i,strg) in nw_ref[:storage]) / total_energy_ub + + sum( Int(!obj_opts[n]["disable-generation-dispatch-cost"]) * sum(get(gen, "cost", [0.0, 0.0])[2] * var(pm, n, :pg, i)[c] + get(gen, "cost", [0.0, 0.0])[1] for c in gen["connections"]) for (i,gen) in nw_ref[:gen]) / total_energy_ub + for (n, nw_ref) in nws(pm)) + ) +end + + +@doc raw""" + objective_min_shed_load_block(pm::AbstractUnbalancedPowerModel) + +Minimum block load shed objective for rolling horizon problem. Note that the difference between this and +[`objective_min_shed_load_block_rolling_horizon`](@ref objective_min_shed_load_block_rolling_horizon) is that the +sum over the switches in line 2 of the objective is optional, as determined by user inputs in the model, i.e., +`enable_switch_state_open_cost` (default: false), and `disable-switch-state-change-cost` (default: false). + +```math +\begin{align*} +\mbox{minimize: } & \\ +& \sum_{\substack{b \in B,t \in T}} W^{bl}_{b,t} \left(1 - z^{bl}_{b,t} \right) \\ +& + \sum_{\substack{s \in S,t \in T}} \left[ W^{sw}_{s,t} \left(1 - \gamma_{s,t} \right )) + W^{\Delta^{\gamma}}_{s,t}\Delta^{\gamma}_{s,t}\right ]\\ +& + \sum_{\substack{e \in E,t \in T}} \epsilon^{ub}_{e} - \epsilon_{e,t} \\ +& + \sum_{\substack{g \in G,t \in T}} f_1 P_{g,t} + f_0 +\end{align*}``` +""" +function objective_min_shed_load_block(pm::AbstractUnbalancedPowerModel) + nw_id_list = sort(collect(nw_ids(pm))) + + for (i, n) in enumerate(nw_id_list) + nw_ref = ref(pm, n) + + var(pm, n)[:delta_sw_state] = JuMP.@variable( + pm.model, + [i in ids(pm, n, :switch_dispatchable)], + base_name="$(n)_$(i)_delta_sw_state", + start = 0 + ) + + for (s,switch) in nw_ref[:switch_dispatchable] + z_switch = var(pm, n, :switch_state, s) + if i == 1 + JuMP.@constraint(pm.model, var(pm, n, :delta_sw_state, s) >= (JuMP.start_value(z_switch) - z_switch)) + JuMP.@constraint(pm.model, var(pm, n, :delta_sw_state, s) >= -(JuMP.start_value(z_switch) - z_switch)) + else # multinetwork + z_switch_prev = var(pm, nw_id_list[i-1], :switch_state, s) + JuMP.@constraint(pm.model, var(pm, n, :delta_sw_state, s) >= (z_switch_prev - z_switch)) + JuMP.@constraint(pm.model, var(pm, n, :delta_sw_state, s) >= -(z_switch_prev - z_switch)) + end + end + end + + total_energy_ub = sum(strg["energy_rating"] for (n,nw_ref) in nws(pm) for (i,strg) in nw_ref[:storage]) + total_pmax = sum(Float64[all(.!isfinite.(gen["pmax"])) ? 0.0 : sum(gen["pmax"][isfinite.(gen["pmax"])]) for (n,nw_ref) in nws(pm) for (i, gen) in nw_ref[:gen]]) + + total_energy_ub = total_energy_ub <= 1.0 ? 1.0 : total_energy_ub + total_pmax = total_pmax <= 1.0 ? 1.0 : total_pmax + + n_dispatchable_switches = Dict(n => length(ids(pm, n, :switch_dispatchable)) for n in nw_ids(pm)) + for (n,nswitch) in n_dispatchable_switches + if nswitch < 1 + n_dispatchable_switches[n] = 1 + end + end + + obj_opts = Dict(n=>ref(pm, n, :options, "objective") for n in nw_ids(pm)) + + if first(obj_opts).second["disable-load-block-weight-cost"] + block_weights = Dict(n => Dict(i => 1.0 for i in ids(pm, n, :blocks)) for n in nw_ids(pm)) + else + block_weights = Dict(n => ref(pm, n, :block_weights) for n in nw_ids(pm)) + end + JuMP.@objective(pm.model, Min, sum( - sum( ref(pm, n, :block_weights, i) * (1-var(pm, n, :z_block, i)) for (i,block) in nw_ref[:blocks]) + - sum( 1e-1 * Int(get(ref(pm, n), :apply_switch_scores, false)) * ref(pm, n, :switch_scores, l)*(1-var(pm, n, :switch_state, l)) for l in ids(pm, n, :switch_dispatchable) ) + - sum( Int(get(ref(pm, n), :disable_switch_penalty, false)) * sum(var(pm, n, :delta_sw_state, l)) for l in ids(pm, n, :switch_dispatchable)) + - sum( strg["energy_rating"] - var(pm, n, :se, i) for (i,strg) in nw_ref[:storage]) + - sum( sum(get(gen, "cost", [1.0, 0.0])[2] * var(pm, n, :pg, i)[c] + get(gen, "cost", [1.0, 0.0])[1] for c in gen["connections"]) for (i,gen) in nw_ref[:gen]) + sum( block_weights[n][i] * Int(!obj_opts[n]["disable-load-block-shed-cost"]) * (1-var(pm, n, :z_block, i)) for (i,block) in nw_ref[:blocks]) + + sum( Int(obj_opts[n]["enable-switch-state-open-cost"]) * ref(pm, n, :switch_scores, l)*(1-var(pm, n, :switch_state, l)) for l in ids(pm, n, :switch_dispatchable) ) + + sum( Int(!obj_opts[n]["disable-switch-state-change-cost"]) * sum(var(pm, n, :delta_sw_state, l)) for l in ids(pm, n, :switch_dispatchable)) / n_dispatchable_switches[n] + + sum( Int(!obj_opts[n]["disable-storage-discharge-cost"]) * (strg["energy_rating"] - var(pm, n, :se, i)) for (i,strg) in nw_ref[:storage]) / total_energy_ub + + sum( Int(!obj_opts[n]["disable-generation-dispatch-cost"]) * sum(get(gen, "cost", [0.0, 0.0])[2] * var(pm, n, :pg, i)[c] + get(gen, "cost", [0.0, 0.0])[1] for c in gen["connections"]) for (i,gen) in nw_ref[:gen]) / total_energy_ub for (n, nw_ref) in nws(pm)) ) end +@doc raw""" + objective_min_shed_load_traditional(pm::AbstractUnbalancedPowerModel) + +Minimum block load shed objective for rolling horizon problem. Note that the difference between this and +[`objective_min_shed_load_traditional_rolling_horizon`](@ref objective_min_shed_load_traditional_rolling_horizon) is that the +sum over the switches in line 2 of the objective is optional, as determined by user inputs in the model, i.e., +`enable_switch_state_open_cost` (default: false), and `disable-switch-state-change-cost` (default: false). + +```math +\begin{align*} +\mbox{minimize: } & \\ +& \sum_{\substack{l \in L,t \in T}} W^{d}_{l,t} \left(1 - z^{d}_{l,t} \right) \\ +& + \sum_{\substack{s \in S,t \in T}} \left[ W^{sw}_{s,t} \left(1 - \gamma_{s,t} \right )) + W^{\Delta^{\gamma}}_{s,t}\Delta^{\gamma}_{s,t}\right ]\\ +& + \sum_{\substack{e \in E,t \in T}} \epsilon^{ub}_{e} - \epsilon_{e,t} \\ +& + \sum_{\substack{g \in G,t \in T}} f_1 P_{g,t} + f_0 +\end{align*} +``` """ +function objective_min_shed_load_traditional(pm::AbstractUnbalancedPowerModel) + nw_id_list = sort(collect(nw_ids(pm))) + + for (i, n) in enumerate(nw_id_list) + nw_ref = ref(pm, n) + + var(pm, n)[:delta_sw_state] = JuMP.@variable( + pm.model, + [i in ids(pm, n, :switch_dispatchable)], + base_name="$(n)_$(i)_delta_sw_state", + start = 0 + ) + + for (s,switch) in nw_ref[:switch_dispatchable] + z_switch = var(pm, n, :switch_state, s) + if i == 1 + JuMP.@constraint(pm.model, var(pm, n, :delta_sw_state, s) >= (JuMP.start_value(z_switch) - z_switch)) + JuMP.@constraint(pm.model, var(pm, n, :delta_sw_state, s) >= -(JuMP.start_value(z_switch) - z_switch)) + else # multinetwork + z_switch_prev = var(pm, nw_id_list[i-1], :switch_state, s) + JuMP.@constraint(pm.model, var(pm, n, :delta_sw_state, s) >= (z_switch_prev - z_switch)) + JuMP.@constraint(pm.model, var(pm, n, :delta_sw_state, s) >= -(z_switch_prev - z_switch)) + end + end + end + + obj_opts = Dict(n=>ref(pm, n, :options, "objective") for n in nw_ids(pm)) + no_weights = first(obj_opts).second["disable-load-block-weight-cost"] + + load_weights = Dict( + n => Dict( + l => no_weights ? 1.0 : ref(pm, n, :block_weights, b) / length(ref(pm, n, :block_loads, b)) for b in ids(pm, n, :blocks) for l in ref(pm, n, :block_loads, b) + ) for n in nw_ids(pm) + ) + + total_energy_ub = sum(strg["energy_rating"] for (n,nw_ref) in nws(pm) for (i,strg) in nw_ref[:storage]) + total_pmax = sum(Float64[all(.!isfinite.(gen["pmax"])) ? 0.0 : sum(gen["pmax"][isfinite.(gen["pmax"])]) for (n,nw_ref) in nws(pm) for (i, gen) in nw_ref[:gen]]) + + total_energy_ub = total_energy_ub <= 1.0 ? 1.0 : total_energy_ub + total_pmax = total_pmax <= 1.0 ? 1.0 : total_pmax + + n_dispatchable_switches = Dict(n => length(ids(pm, n, :switch_dispatchable)) for n in nw_ids(pm)) + for (n,nswitch) in n_dispatchable_switches + if nswitch < 1 + n_dispatchable_switches[n] = 1 + end + end + + JuMP.@objective(pm.model, Min, + sum( + sum( load_weights[n][i] * (1 - Int(obj_opts[n]["disable-load-block-shed-cost"])) * (1-var(pm, n, :z_demand, i)) for i in ids(pm, n, :load)) + + sum( Int(obj_opts[n]["enable-switch-state-open-cost"]) * ref(pm, n, :switch_scores, l)*(1-var(pm, n, :switch_state, l)) for l in ids(pm, n, :switch_dispatchable) ) + + sum( Int(!obj_opts[n]["disable-switch-state-change-cost"]) * sum(var(pm, n, :delta_sw_state, l)) for l in ids(pm, n, :switch_dispatchable)) / n_dispatchable_switches[n] + + sum( Int(!obj_opts[n]["disable-storage-discharge-cost"]) * (strg["energy_rating"] - var(pm, n, :se, i)) for (i,strg) in nw_ref[:storage]) / total_energy_ub + + sum( Int(!obj_opts[n]["disable-generation-dispatch-cost"]) * sum(get(gen, "cost", [0.0, 0.0])[2] * var(pm, n, :pg, i)[c] + get(gen, "cost", [0.0, 0.0])[1] for c in gen["connections"]) for (i,gen) in nw_ref[:gen]) / total_energy_ub + for (n, nw_ref) in nws(pm)) + ) +end + + +@doc raw""" objective_mc_min_storage_utilization(pm::AbstractUnbalancedPowerModel) Minimizes the amount of storage that gets utilized in favor of using all available generation first + +```math +\begin{align*} +\mbox{minimize: } & \\ +& \sum_{\substack{e \in E,t \in T}} \epsilon^{ub}_{e} - \epsilon_{e,t} \\ +\end{align*} +``` """ function objective_mc_min_storage_utilization(pm::AbstractUnbalancedPowerModel) + total_energy_ub = sum(strg["energy_rating"] for (n,nw_ref) in nws(pm) for (i,strg) in nw_ref[:storage]) + total_pmax = sum(Float64[all(.!isfinite.(gen["pmax"])) ? 0.0 : sum(gen["pmax"][isfinite.(gen["pmax"])]) for (n,nw_ref) in nws(pm) for (i, gen) in nw_ref[:gen]]) + + total_energy_ub = total_energy_ub <= 1.0 ? 1.0 : total_energy_ub + total_pmax = total_pmax <= 1.0 ? 1.0 : total_pmax + + obj_opts = Dict(n=>ref(pm, n, :options, "objective") for n in nw_ids(pm)) + JuMP.@objective(pm.model, Min, sum( - sum( strg["energy_rating"] - var(pm, n, :se, i) for (i,strg) in nw_ref[:storage]) + sum( Int(!obj_opts[n]["disable-storage-discharge-cost"]) * (strg["energy_rating"] - var(pm, n, :se, i)) for (i,strg) in nw_ref[:storage]) / total_energy_ub + + sum( Int(!obj_opts[n]["disable-generation-dispatch-cost"]) * sum(get(gen, "cost", [0.0, 0.0])[2] * var(pm, n, :pg, i)[c] + get(gen, "cost", [0.0, 0.0])[1] for c in gen["connections"]) for (i,gen) in nw_ref[:gen]) / total_energy_ub for (n, nw_ref) in nws(pm)) ) end diff --git a/src/core/ref.jl b/src/core/ref.jl index b60d86a5..7c23f1da 100644 --- a/src/core/ref.jl +++ b/src/core/ref.jl @@ -1,6 +1,10 @@ -"Ref extension to add load blocks to ref" +""" + _ref_add_load_blocks!(ref::Dict{Symbol,<:Any}, data::Dict{String,<:Any}) + +Ref extension to add load blocks to ref at a single time step +""" function _ref_add_load_blocks!(ref::Dict{Symbol,<:Any}, data::Dict{String,<:Any}) - ref[:blocks] = Dict{Int,Set}(i => block for (i,block) in enumerate(PMD.calc_connected_components(data; type="load_blocks", check_enabled=false))) + ref[:blocks] = Dict{Int,Set}(i => block for (i,block) in enumerate(PMD.calc_connected_components(data; type="load_blocks", check_enabled=true))) ref[:bus_block_map] = Dict{Int,Int}(bus => b for (b,block) in ref[:blocks] for bus in block) ref[:block_branches] = Dict{Int,Set}(b => Set{Int}() for (b,_) in ref[:blocks]) ref[:block_loads] = Dict{Int,Set}(i => Set{Int}() for (i,_) in ref[:blocks]) @@ -10,6 +14,8 @@ function _ref_add_load_blocks!(ref::Dict{Symbol,<:Any}, data::Dict{String,<:Any} ref[:block_storages] = Dict{Int,Set{Int}}(i => Set{Int}() for (i,_) in ref[:blocks]) ref[:microgrid_blocks] = Dict{Int,String}() ref[:substation_blocks] = Vector{Int}() + ref[:bus_inverters] = Dict{Int,Set{Tuple{Symbol,Int}}}(i => Set{Tuple{Symbol,Int}}() for (i,_) in ref[:bus]) + ref[:block_inverters] = Dict{Int,Set{Tuple{Symbol,Int}}}(b => Set{Tuple{Symbol,Int}}() for (b,_) in ref[:blocks]) for (b,bus) in ref[:bus] if !isempty(get(bus, "microgrid_id", "")) @@ -36,11 +42,15 @@ function _ref_add_load_blocks!(ref::Dict{Symbol,<:Any}, data::Dict{String,<:Any} for (g,gen) in ref[:gen] push!(ref[:block_gens][ref[:bus_block_map][gen["gen_bus"]]], g) startswith(gen["source_id"], "voltage_source") && push!(ref[:substation_blocks], ref[:bus_block_map][gen["gen_bus"]]) + push!(ref[:bus_inverters][gen["gen_bus"]], (:gen, g)) + push!(ref[:block_inverters][ref[:bus_block_map][gen["gen_bus"]]], (:gen, g)) end ref[:gen_block_map] = Dict{Int,Int}(gen => b for (b,block_gens) in ref[:block_gens] for gen in block_gens) for (s,strg) in ref[:storage] push!(ref[:block_storages][ref[:bus_block_map][strg["storage_bus"]]], s) + push!(ref[:bus_inverters][strg["storage_bus"]], (:storage, s)) + push!(ref[:block_inverters][ref[:bus_block_map][strg["storage_bus"]]], (:storage, s)) end ref[:storage_block_map] = Dict{Int,Int}(strg => b for (b,block_storages) in ref[:block_storages] for strg in block_storages) @@ -67,19 +77,15 @@ function _ref_add_load_blocks!(ref::Dict{Symbol,<:Any}, data::Dict{String,<:Any} end end - # For radiality constraints, need **all** switches, even disabled ones - switches = get(data, "switch", Dict{String,Any}()) + # Build block pairs for radiality constraints ref[:block_pairs] = filter(((x,y),)->x!=y, Set{Tuple{Int,Int}}( - Set([(ref[:bus_block_map][sw["f_bus"]],ref[:bus_block_map][sw["t_bus"]]) for (_,sw) in switches]) + Set([(ref[:bus_block_map][sw["f_bus"]],ref[:bus_block_map][sw["t_bus"]]) for (_,sw) in ref[:switch]]), )) - if get(data, "disable_networking", false) - ref[:substation_blocks] = union(Set(ref[:substation_blocks]), Set(ref[:microgrid_blocks])) - end - ref[:neighbors] = Dict{Int,Vector{Int}}(i => Graphs.neighbors(ref[:block_graph], i) for i in Graphs.vertices(ref[:block_graph])) ref[:switch_scores] = Dict{Int,Real}(s => 0.0 for (s,_) in ref[:switch]) + total_line_losses = sum(Float64[LinearAlgebra.norm(br["br_r"] .+ 1im*br["br_x"]) for (_,br) in ref[:branch]]) for type in ["storage", "gen"] for (id,obj) in ref[Symbol(type)] if obj[PMD.pmd_math_component_status[type]] != PMD.pmd_math_component_status_inactive[type] @@ -91,11 +97,13 @@ function _ref_add_load_blocks!(ref::Dict{Symbol,<:Any}, data::Dict{String,<:Any} for (i,b) in enumerate(reverse(path[2:end])) block_line_losses = 0.0 # to help with degeneracy for line_id in ref[:block_branches][b] - block_line_losses += LinearAlgebra.norm(ref[:branch][line_id]["br_r"] .+ 1im*ref[:branch][line_id]["br_x"]) + block_line_losses += 1e-2 * LinearAlgebra.norm(ref[:branch][line_id]["br_r"] .+ 1im*ref[:branch][line_id]["br_x"]) end - cumulative_weight += ref[:block_weights][b] + cumulative_weight += 1e-2 * ref[:block_weights][b] + b_prev = path[end-i] - ref[:switch_scores][ref[:block_graph_edge_map][Graphs.Edge(b_prev,b)]] += cumulative_weight - block_line_losses + adjusted_cumulative_weight = cumulative_weight - (total_line_losses == 0.0 ? 0.0 : block_line_losses / total_line_losses) + ref[:switch_scores][ref[:block_graph_edge_map][Graphs.Edge(b_prev,b)]] += adjusted_cumulative_weight < 0 ? 0.0 : adjusted_cumulative_weight end end end @@ -107,24 +115,67 @@ end """ ref_add_load_blocks!(ref::Dict{Symbol,<:Any}, data::Dict{String,<:Any}) -Ref extension to add load blocks to ref +Ref extension to add load blocks to ref for all time steps """ function ref_add_load_blocks!(ref::Dict{Symbol,<:Any}, data::Dict{String,<:Any}) PMD.apply_pmd!(_ref_add_load_blocks!, ref, data; apply_to_subnetworks=true) end -"Ref extension to add max_switch_actions to ref, and set to Inf if option is missing" -function _ref_add_max_switch_actions!(ref::Dict{Symbol,<:Any}, data::Dict{String,<:Any}) - ref[:max_switch_actions] = get(data, "max_switch_actions", Inf) +""" + ref_add_options!(ref::Dict{Symbol,<:Any}, data::Dict{String,<:Any}) + +Ref extension to add options to ref for all time steps +""" +function ref_add_options!(ref::Dict{Symbol,<:Any}, data::Dict{String,<:Any}) + PMD.apply_pmd!(_ref_add_options!, ref, data; apply_to_subnetworks=true) end """ - ref_add_max_switch_actions!(ref::Dict{Symbol,<:Any}, data::Dict{String,<:Any}) + _ref_add_options!(ref::Dict{Symbol,<:Any}, data::Dict{String,<:Any}) -Ref extension to add max_switch_actions to ref, and set to Inf if option is missing +Ref extension to add options to ref for all time steps """ -function ref_add_max_switch_actions!(ref::Dict{Symbol,<:Any}, data::Dict{String,<:Any}) - PMD.apply_pmd!(_ref_add_max_switch_actions!, ref, data; apply_to_subnetworks=true) +function _ref_add_options!(ref::Dict{Symbol,<:Any}, data::Dict{String,<:Any}) + ref[:options] = recursive_merge(build_default_settings()["options"], get(data, "options", Dict{String,Any}())) +end + + +""" + _correct_branch_directions!(switches::Dict{String,<:Any}, ref::Dict{Symbol,<:Any})::Dict{String,Any} + +Helper function that will attempt to make a directed graph that is strong-connected by adjusting the +switch directions starting from the voltage_sources +""" +function _correct_switch_directions!(switches::Dict{String,<:Any}, blocks::Dict{Int,Set}, bus_block_map::Dict{Int,Int}, substation_blocks::Vector{Int})::Dict{String,Any} + bl_switches = Dict(i => Set([]) for (i,_) in blocks) + for (id,sw) in switches + push!(bl_switches[bus_block_map[sw["f_bus"]]], id) + push!(bl_switches[bus_block_map[sw["t_bus"]]], id) + end + touched_sw = Set() + touched_bl = Set() + todo = Set(substation_blocks) + while !isempty(todo) + _bl = pop!(todo) + if !(_bl in touched_bl) + push!(touched_bl, _bl) + for sw_id in filter(x->!(x in touched_sw), bl_switches[_bl]) + push!(touched_sw, sw_id) + if bus_block_map[switches["$sw_id"]["t_bus"]] == _bl + _f_bus = deepcopy(switches["$sw_id"]["f_bus"]) + _t_bus = deepcopy(switches["$sw_id"]["t_bus"]) + switches["$sw_id"]["f_bus"] = _t_bus + switches["$sw_id"]["t_bus"] = _f_bus + push!(todo, bus_block_map[_f_bus]) + @debug "fixed directionality of $(switches["$sw_id"]["name"])" + else + push!(todo, bus_block_map[switches["$sw_id"]["t_bus"]]) + end + end + end + end + + return switches end diff --git a/src/core/solution.jl b/src/core/solution.jl index 77fc8d7d..fbd9a5c1 100644 --- a/src/core/solution.jl +++ b/src/core/solution.jl @@ -1,4 +1,8 @@ -"helper function to update switch settings from a solution" +""" + _update_switch_settings!(data::Dict{String,<:Any}, solution::Dict{String,<:Any}) + +Helper function to update switch settings from a solution, for the rolling horizon algorithm. +""" function _update_switch_settings!(data::Dict{String,<:Any}, solution::Dict{String,<:Any}) for (id, switch) in get(solution, "switch", Dict{String,Any}()) if haskey(switch, "state") @@ -8,7 +12,11 @@ function _update_switch_settings!(data::Dict{String,<:Any}, solution::Dict{Strin end -"helper function to update storage capacity for the next subnetwork based on a solution" +""" + _update_storage_capacity!(data::Dict{String,<:Any}, solution::Dict{String,<:Any}) + +Helper function to update storage capacity for the next subnetwork based on a solution, for the rolling horizon algorithm. +""" function _update_storage_capacity!(data::Dict{String,<:Any}, solution::Dict{String,<:Any}) for (i, strg) in get(solution, "storage", Dict()) data["storage"][i]["_energy"] = deepcopy(data["storage"][i]["energy"]) @@ -32,7 +40,7 @@ end """ apply_switch_solutions(network::Dict{String,<:Any}, optimal_switching_results::Dict{String,<:Any})::Dict{String,Any} -Creates a copy of the `network` with the solution copied in from `optimal_switching_results` +Creates a copy of the `network` with the solution copied in from `optimal_switching_results`. """ function apply_switch_solutions(network::Dict{String,<:Any}, optimal_switching_results::Dict{String,<:Any})::Dict{String,Any} mn_data = deepcopy(network) @@ -47,9 +55,9 @@ end """ build_result(aim::AbstractUnbalancedPowerModel, solve_time; solution_processors=[]) -Version of `InfrastructureModels.build_result` that includes `"mip_gap"` in the results dictionary, if it exists +Version of `InfrastructureModels.build_result` that includes `"mip_gap"` in the results dictionary, if it exists. """ -function _IM.build_result(aim::AbstractSwitchModels, solve_time; solution_processors=[]) +function IM.build_result(aim::AbstractUnbalancedPowerModel, solve_time; solution_processors=[]) # try-catch is needed until solvers reliably support ResultCount() result_count = 1 try @@ -61,7 +69,7 @@ function _IM.build_result(aim::AbstractSwitchModels, solve_time; solution_proces solution = Dict{String,Any}() if result_count > 0 - solution = _IM.build_solution(aim, post_processors=solution_processors) + solution = IM.build_solution(aim, post_processors=solution_processors) else @warn "model has no results, solution cannot be built" end @@ -71,8 +79,8 @@ function _IM.build_result(aim::AbstractSwitchModels, solve_time; solution_proces "termination_status" => JuMP.termination_status(aim.model), "primal_status" => JuMP.primal_status(aim.model), "dual_status" => JuMP.dual_status(aim.model), - "objective" => _IM._guard_objective_value(aim.model), - "objective_lb" => _IM._guard_objective_bound(aim.model), + "objective" => IM._guard_objective_value(aim.model), + "objective_lb" => IM._guard_objective_bound(aim.model), "solve_time" => solve_time, "solution" => solution, ) @@ -91,17 +99,23 @@ end """ solution_reference_buses!(pm::AbstractUnbalancedPowerModel, sol::Dict{String,Any}) -Raises bus_type from math model up to solution for reporting +Raises `bus_type` from math model up to solution for reporting, across all time steps. """ function solution_reference_buses!(pm::AbstractUnbalancedPowerModel, sol::Dict{String,Any}) - PMD.apply_pmd!(_solution_reference_buses!, pm.data, sol; apply_to_subnetworks=true) + if !PMD.ismultinetwork(PMD.get_pmd_data(pm.data)) && PMD.ismultinetwork(PMD.get_pmd_data(sol)) + _sol = PMD.get_pmd_data(sol)["nw"]["0"] + else + _sol = sol + end + + PMD.apply_pmd!(_solution_reference_buses!, pm.data, _sol; apply_to_subnetworks=true) end """ _solution_reference_buses!(data::Dict{String,<:Any}, sol::Dict{String,<:Any}) -Raises bus_type from math model up to solution for reporting +Raises `bus_type` from math model up to solution for reporting, from a single time step. """ function _solution_reference_buses!(data::Dict{String,<:Any}, sol::Dict{String,<:Any}) if !haskey(sol, "bus") && !isempty(get(data, "bus", Dict())) @@ -116,3 +130,140 @@ function _solution_reference_buses!(data::Dict{String,<:Any}, sol::Dict{String,< end end end + + +""" + solution_statuses!(pm::AbstractUnbalancedPowerModel, sol::Dict{String,Any}) + +Converts all `status` fields in a solution `sol` from Float64 to `Status` enum, for all time steps. +""" +function solution_statuses!(pm::AbstractUnbalancedPowerModel, sol::Dict{String,Any}) + PMD.apply_pmd!(_solution_statuses!, sol; apply_to_subnetworks=true) +end + + +""" + _solution_statuses!(sol::Dict{String,<:Any}) + +Converts all `status` fields in a solution `sol` from Float64 to `Status` enum, for a single time step. +""" +function _solution_statuses!(sol::Dict{String,<:Any}) + for type in PMD.pmd_math_asset_types + for (i,obj) in get(sol, type, Dict{String,Any}()) + if haskey(obj, "status") && isa(obj["status"], Real) + sol[type][i]["status"] = PMD.Status(round(Int, obj["status"])) + end + end + end +end + + +""" + solution_inverter!(pm::AbstractUnbalancedPowerModel, sol::Dict{String,Any}) + +Converts `inverter` to Inverter enum, across all time steps. +""" +function solution_inverter!(pm::AbstractUnbalancedPowerModel, sol::Dict{String,Any}) + if !PMD.ismultinetwork(PMD.get_pmd_data(pm.data)) && PMD.ismultinetwork(PMD.get_pmd_data(sol)) + _sol = PMD.get_pmd_data(sol)["nw"]["0"] + else + _sol = sol + end + + PMD.apply_pmd!(_solution_inverter!, pm.data, _sol; apply_to_subnetworks=true) +end + + +""" + _solution_inverter!(data::Dict{String,<:Any}, sol::Dict{String,<:Any}) + +Converts `inverter` to Inverter enum, from a single time step. +""" +function _solution_inverter!(data::Dict{String,<:Any}, sol::Dict{String,<:Any}) + for t in ["gen", "storage"] + if haskey(sol, t) + for (_,obj) in sol[t] + if haskey(obj, "inverter") + obj["inverter"] = Inverter(round(Int, obj["inverter"])) + end + end + end + end +end + + +""" + solution_blocks!(pm::AbstractUnbalancedPowerModel, sol::Dict{String,Any}) + +Adds block ids (as generated in the ref), and microgrid_ids to the solution +""" +function solution_blocks!(pm::AbstractUnbalancedPowerModel, sol::Dict{String,Any}) + PMD.apply_pmd!(PowerModelsONM._solution_blocks!, sol, pm.ref; apply_to_subnetworks=true) +end + + +""" + _solution_statuses!(sol::Dict{String,<:Any}, ref::Dict{Symbol,<:Any}) + +Adds block ids (as generated in the `ref`), and microgrid_ids to the solution +""" +function _solution_blocks!(sol::Dict{String,<:Any}, ref::Dict{Symbol,<:Any}) + for (id, block) in ref[:blocks] + for bus_id in block + sol["bus"]["$bus_id"]["block_id"] = id + if id in keys(ref[:microgrid_blocks]) + sol["bus"]["$bus_id"]["microgrid_id"] = ref[:microgrid_blocks][id] + end + end + end + + for t in [:load, :gen, :storage] + for (id,_) in ref[t] + block_id = ref[Symbol("$(t)_block_map")][id] + sol[string(t)]["$id"]["block_id"] = block_id + if block_id in keys(ref[:microgrid_blocks]) + sol[string(t)]["$id"]["microgrid_id"] = ref[:microgrid_blocks][block_id] + end + end + end +end + + +""" + PowerModelsDistribution.apply_pmd!(func!::Function, data::Dict{String,<:Any}, ref::Dict{Symbol,<:Any}; apply_to_subnetworks::Bool=true, kwargs...) + +Version of `apply_pmd!` that supports `ref::Dict{Symbol,<:Any}` +""" +function PMD.apply_pmd!(func!::Function, data::Dict{String,<:Any}, ref::Dict{Symbol,<:Any}; apply_to_subnetworks::Bool=true, kwargs...) + data_it = IM.ismultiinfrastructure(data) ? data["it"][PMD.pmd_it_name] : data + ref_it = IM.ismultiinfrastructure(ref) ? ref[:it][PMD.pmd_it_sym] : ref + + if PMD.ismultinetwork(data_it) && apply_to_subnetworks + @assert PMD.ismultinetwork(ref_it) + for (nw, nw_data) in data_it["nw"] + func!(nw_data, ref_it[:nw][parse(Int, nw)]; kwargs...) + end + else + func!(data_it, ref_it; kwargs...) + end +end + + +""" + InfrastructureModels.ismultiinfrastructure(ref::Dict{Symbol,<:Any}) + +version of `ismultiinfrastructure` that works on `ref::Dict{Symbol,<:Any}` +""" +function IM.ismultiinfrastructure(ref::Dict{Symbol,<:Any}) + haskey(ref, :it) +end + + +""" + PowerModelsDistribution.ismultinetwork(ref::Dict{Symbol,<:Any}) + +Version of `ismultinetwork` that works on `ref::Dict{Symbol,<:Any}` +""" +function PMD.ismultinetwork(ref::Dict{Symbol,<:Any}) + haskey(ref, :nw) +end diff --git a/src/core/types.jl b/src/core/types.jl index b6303890..275b9f6a 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -1,50 +1,6 @@ -"Abstract Switch Model for NFAU formulation" -abstract type AbstractUnbalancedNFASwitchModel <: PMD.AbstractUnbalancedNFAModel end - -"Abstract Switch Model for LPUBFDiag formulation" -abstract type LPUBFSwitchModel <: PMD.LPUBFDiagModel end - -"Abstract Switch Model for SOCNLPUBF formulation" -abstract type SOCUBFSwitchModel <: PMD.SOCNLPUBFModel end - -"Abstract Switch Model for ACPU formulation" -abstract type AbstractUnbalancedACPSwitchModel <: PMD.AbstractUnbalancedACPModel end - -"Abstract Switch Model for ACRU formulation" -abstract type AbstractUnbalancedACRSwitchModel <: PMD.AbstractUnbalancedACRModel end - -"SwitchPowerModel struct for NFAU formulation" -mutable struct NFAUSwitchPowerModel <: AbstractUnbalancedNFASwitchModel PMD.@pmd_fields end - -"SwitchPowerModel struct for LPUBFDiag formulation" -mutable struct LPUBFSwitchPowerModel <: LPUBFSwitchModel PMD.@pmd_fields end - -"SwitchPowerModel struct for SOCNLPUBF formulation" -mutable struct SOCUBFSwitchPowerModel <: SOCUBFSwitchModel PMD.@pmd_fields end - -"SwitchPowerModel struct for ACPU formulation" -mutable struct ACPUSwitchPowerModel <: AbstractUnbalancedACPSwitchModel PMD.@pmd_fields end - -"SwitchPowerModel struct for ACRU formulation" -mutable struct ACRUSwitchPowerModel <: AbstractUnbalancedACRSwitchModel PMD.@pmd_fields end - -"Collection of all Switch Models" -const AbstractSwitchModels = Union{AbstractUnbalancedNFASwitchModel, LPUBFSwitchModel, SOCUBFSwitchModel, AbstractUnbalancedACPSwitchModel, AbstractUnbalancedACRSwitchModel} - -"Collection of only UBF Switch Models" -const AbstractUBFSwitchModels = Union{LPUBFSwitchModel, SOCUBFSwitchModel} - -"Collection of Non-Linear Switch Models" -const AbstractNLPSwitchModels = Union{AbstractUnbalancedACPSwitchModel, AbstractUnbalancedACRSwitchModel} - -"Collection of Quadratic Switch Models" -const AbstractQPSwitchModels = Union{SOCUBFSwitchModel} - -"Collection of Linear Switch Models" -const AbstractLPSwitchModels = Union{AbstractUnbalancedNFASwitchModel, LPUBFSwitchModel} "string to PowerModelsDistribution type conversion for opt-disp-formulation" -const _dispatch_formulations = Dict{String,Any}( +const _formulation_lookup = Dict{String,Type}( "acr" => PMD.ACRUPowerModel, "acrupowermodel" => PMD.ACRUPowerModel, "acp" => PMD.ACPUPowerModel, @@ -62,30 +18,46 @@ const _dispatch_formulations = Dict{String,Any}( "fbs" => PMD.FBSUBFPowerModel, ) -"helper function to convert from opt-disp-formulation string to PowerModelsDistribution Type" -_get_dispatch_formulation(form_string::String) = _dispatch_formulations[lowercase(form_string)] -"helper function to convert from PowerModelsDistribution Type to PowerModelsDistribution Type" -_get_dispatch_formulation(form::Type) = form +""" + _get_formulation(form_string::String) -"string to PowerModelsONM type conversion for opt-switch-formulation" -const _switch_formulations = Dict{String,Any}( - "acp" => ACPUSwitchPowerModel, - "acpuswitchpowermodel" => ACPUSwitchPowerModel, - "acr" => ACRUSwitchPowerModel, - "acruswitchpowermodel" => ACRUSwitchPowerModel, - "lindistflow" => LPUBFSwitchPowerModel, - "lpubfdiag" => LPUBFSwitchPowerModel, - "lpubf" => LPUBFSwitchPowerModel, - "lpubfswitchpowermodel" => LPUBFSwitchPowerModel, - "nfa" => NFAUSwitchPowerModel, - "nfauswitchpowermodel" => NFAUSwitchPowerModel, - "soc" => SOCUBFSwitchPowerModel, - "socubfswitchmodel" => SOCUBFSwitchPowerModel, -) +helper function to convert from opt-disp-formulation, opt-switch-formulation string to PowerModelsDistribution Type +""" +_get_formulation(form_string::String)::Type = _formulation_lookup[lowercase(form_string)] + + +""" + _get_formulation(form::Type) + +helper function to convert from PowerModelsDistribution Type to PowerModelsDistribution Type +""" +_get_formulation(form::Type)::Type = form + + +"Inverter Enum to indicate device is operating in grid-follow or grid-forming mode" +@enum Inverter GRID_FOLLOWING GRID_FORMING +@doc "Inverter acting as grid-following" GRID_FOLLOWING +@doc "Inverter acting as grid-forming" GRID_FORMING + + +""" + Base.parse(::Type{T}, inverter::String)::T where T <: Inverter + +Parses the 'inverter' property from dss settings schema into an Inverter enum +""" +function Base.parse(::Type{T}, inverter::String)::T where T <: Inverter + if lowercase(inverter) ∈ ["grid_forming", "gfm"] + return GRID_FORMING + elseif lowercase(inverter) ∈ ["grid_following", "gfl"] + return GRID_FOLLOWING + end -"helper function to convert from opt-switch-formulation string to PowerModelsONM Type" -_get_switch_formulation(form_string::String) = _switch_formulations[lowercase(form_string)] + @warn "inverter code '$inverter' not recognized, defaulting to GRID_FORMING" + return GRID_FORMING +end -"helper function to convert from PowerModelsONM type to PowerModelsONM Type" -_get_switch_formulation(form::Type) = form +"Parses different options for Inverter enums in the settings schema" +Base.parse(::Type{Inverter}, inverter::Inverter)::Inverter = inverter +Base.parse(::Type{Inverter}, status::Bool)::Inverter = Inverter(Int(status)) +Base.parse(::Type{Inverter}, status::Int)::Inverter = Inverter(status) diff --git a/src/core/variable.jl b/src/core/variable.jl index 4f4a4995..3b4ded20 100644 --- a/src/core/variable.jl +++ b/src/core/variable.jl @@ -1,80 +1,227 @@ -""" - variable_mc_block_indicator(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default, relax::Bool=false, report::Bool=true) +@doc raw""" + variable_block_indicator( + pm::AbstractUnbalancedPowerModel; + nw::Int=nw_id_default, + relax::Bool=false, + report::Bool=true + ) -create variables for block status by load block +Create variables for block status by load block, $$z^{bl}_i\in{0,1}~\forall i \in B$$, binary if `relax=false`. +Variables will appear in solution if `report=true`. """ -function variable_mc_block_indicator(pm::AbstractSwitchModels; nw::Int=nw_id_default, relax::Bool=false, report::Bool=true) - if relax - z_block = var(pm, nw)[:z_block] = JuMP.@variable(pm.model, - [i in ids(pm, nw, :blocks)], base_name="$(nw)_z_block", - lower_bound = 0, - upper_bound = 1, - start = 1 - ) - else - z_block = var(pm, nw)[:z_block] = JuMP.@variable(pm.model, - [i in ids(pm, nw, :blocks)], base_name="$(nw)_z_block", - lower_bound = 0, - upper_bound = 1, - binary = true, - start = 1 - ) - end +function variable_block_indicator(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default, relax::Bool=false, report::Bool=true) + z_block = var(pm, nw)[:z_block] = JuMP.@variable(pm.model, + [i in ids(pm, nw, :blocks)], base_name="$(nw)_z_block", + binary=!relax, + lower_bound=0, + upper_bound=1, + start=1 + ) - report && _IM.sol_component_value(pm, PMD.pmd_it_sym, nw, :bus, :status, ids(pm, nw, :bus), Dict{Int,Any}(i => var(pm, nw, :z_block, ref(pm, nw, :bus_block_map, i)) for i in ids(pm, nw, :bus))) - report && _IM.sol_component_value(pm, PMD.pmd_it_sym, nw, :load, :status, ids(pm, nw, :load), Dict{Int,Any}(i => var(pm, nw, :z_block, ref(pm, nw, :load_block_map, i)) for i in ids(pm, nw, :load))) - report && _IM.sol_component_value(pm, PMD.pmd_it_sym, nw, :shunt, :status, ids(pm, nw, :shunt), Dict{Int,Any}(i => var(pm, nw, :z_block, ref(pm, nw, :shunt_block_map, i)) for i in ids(pm, nw, :shunt))) - report && _IM.sol_component_value(pm, PMD.pmd_it_sym, nw, :gen, :status, ids(pm, nw, :gen), Dict{Int,Any}(i => var(pm, nw, :z_block, ref(pm, nw, :gen_block_map, i)) for i in ids(pm, nw, :gen))) - report && _IM.sol_component_value(pm, PMD.pmd_it_sym, nw, :storage, :status, ids(pm, nw, :storage), Dict{Int,Any}(i => var(pm, nw, :z_block, ref(pm, nw, :storage_block_map, i)) for i in ids(pm, nw, :storage))) + report && IM.sol_component_value(pm, PMD.pmd_it_sym, nw, :bus, :status, ids(pm, nw, :bus), Dict{Int,Any}(i => var(pm, nw, :z_block, ref(pm, nw, :bus_block_map, i)) for i in ids(pm, nw, :bus))) + report && IM.sol_component_value(pm, PMD.pmd_it_sym, nw, :load, :status, ids(pm, nw, :load), Dict{Int,Any}(i => var(pm, nw, :z_block, ref(pm, nw, :load_block_map, i)) for i in ids(pm, nw, :load))) + report && IM.sol_component_value(pm, PMD.pmd_it_sym, nw, :shunt, :status, ids(pm, nw, :shunt), Dict{Int,Any}(i => var(pm, nw, :z_block, ref(pm, nw, :shunt_block_map, i)) for i in ids(pm, nw, :shunt))) + report && IM.sol_component_value(pm, PMD.pmd_it_sym, nw, :gen, :status, ids(pm, nw, :gen), Dict{Int,Any}(i => var(pm, nw, :z_block, ref(pm, nw, :gen_block_map, i)) for i in ids(pm, nw, :gen))) + report && IM.sol_component_value(pm, PMD.pmd_it_sym, nw, :storage, :status, ids(pm, nw, :storage), Dict{Int,Any}(i => var(pm, nw, :z_block, ref(pm, nw, :storage_block_map, i)) for i in ids(pm, nw, :storage))) end -""" - variable_mc_switch_fixed(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default, report::Bool=false) +@doc raw""" + variable_switch_state( + pm::AbstractUnbalancedPowerModel; + nw::Int=nw_id_default, + report::Bool=true, + relax::Bool=false + ) -Fixed switches set to constant values for multinetwork formulation (we need all switches) +Create variables for switch state (open/close) variables, $$\gamma_i\in{0,1}~\forall i \in S$$, binary if `relax=false`. +Variables for non-dispatchable switches will be constants, rather than `VariableRef`. Variables will appear in +solution if `report=true`. """ -function variable_mc_switch_fixed(pm::AbstractSwitchModels; nw::Int=nw_id_default, report::Bool=false) - dispatchable_switches = collect(ids(pm, nw, :switch_dispatchable)) +function variable_switch_state(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default, report::Bool=true, relax::Bool=false) + if ref(pm, nw, :options, "constraints")["disable-microgrid-expansion"] + dispatchable_switches = [i for (i,sw) in ref(pm, nw, :switch) if !isempty(get(ref(pm, nw, :bus, sw["f_bus"]), "microgrid_id", "")) && !isempty(get(ref(pm, nw, :bus, sw["t_bus"]), "microgrid_id", "")) && ref(pm, nw, :bus, sw["f_bus"], "microgrid_id") == ref(pm, nw, :bus, sw["t_bus"], "microgrid_id")] + else + dispatchable_switches = collect(ids(pm, nw, :switch_dispatchable)) + end + + state = var(pm, nw)[:switch_state] = Dict{Int,Any}( + l => JuMP.@variable( + pm.model, + base_name="$(nw)_switch_state_$(l)", + binary=!relax, + lower_bound=0, + upper_bound=1, + start=PMD.comp_start_value(ref(pm, nw, :switch, l), "state_start", get(ref(pm, nw, :switch, l), "state", 1)) + ) for l in dispatchable_switches + ) + + # create variables (constants) for 'fixed' (non-dispatchable) switches fixed_switches = [i for i in ids(pm, nw, :switch) if i ∉ dispatchable_switches] for i in fixed_switches var(pm, nw, :switch_state)[i] = ref(pm, nw, :switch, i, "state") end - report && _IM.sol_component_value(pm, PMD.pmd_it_sym, nw, :switch, :status, fixed_switches, Dict{Int,Any}(i => var(pm, nw, :switch_state, i) for i in fixed_switches)) + report && IM.sol_component_value(pm, PMD.pmd_it_sym, nw, :switch, :state, dispatchable_switches, state) + report && IM.sol_component_value(pm, PMD.pmd_it_sym, nw, :switch, :state, fixed_switches, Dict{Int,Any}(i => var(pm, nw, :switch_state, i) for i in fixed_switches)) end +@doc raw""" + variable_mc_storage_power_mi_on_off( + pm::AbstractUnbalancedPowerModel; + nw::Int=nw_id_default, + relax::Bool=false, + bounded::Bool=true, + report::Bool=true + ) + +Variables for storage, *omitting* the storage indicator $$z^{strg}_i$$ variable: + +```math +\begin{align} +p^{strg}_i,~\forall i \in S \\ +q^{strg}_i,~\forall i \in S \\ +q^{sc}_{i},~\forall i \in S \\ +\epsilon_i,~\forall i \in S \\ +c^{strg}_i,~\forall i \in S \\ +c^{on}_i \in {0,1},~\forall i \in S \\ +d^{on}_i \in {0,1},~\forall i \in S \\ +\end{align} +``` + +$$c^{on}_i$$, $$d^{on}_i$$ will be binary if `relax=false`. Variables will appear in solution if `report=true`. """ - variable_mc_switch_state(pm::AbstractSwitchModels; nw::Int=nw_id_default, report::Bool=true, relax::Bool=false) +function variable_mc_storage_power_mi_on_off(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default, relax::Bool=false, bounded::Bool=true, report::Bool=true) + PMD.variable_mc_storage_power_real_on_off(pm; nw=nw, bounded=bounded, report=report) + PMD.variable_mc_storage_power_imaginary_on_off(pm; nw=nw, bounded=bounded, report=report) + PMD.variable_mc_storage_power_control_imaginary_on_off(pm; nw=nw, bounded=bounded, report=report) + PMD.variable_mc_storage_current(pm; nw=nw, bounded=bounded, report=report) + PMD.variable_storage_energy(pm; nw=nw, bounded=bounded, report=report) + PMD.variable_storage_charge(pm; nw=nw, bounded=bounded, report=report) + PMD.variable_storage_discharge(pm; nw=nw, bounded=bounded, report=report) + PMD.variable_storage_complementary_indicator(pm; nw=nw, relax=relax, report=report) +end + -switch state (open/close) variables +@doc raw""" + variable_bus_voltage_indicator( + pm::AbstractUnbalancedPowerModel; + nw::Int=nw_id_default, + relax::Bool=false, + report::Bool=true + ) + +Variables for switching buses on/off $$z^{bus}_i,~\forall i \in N$$, binary if `relax=false`. +Variables will appear in solution if `report=true`. """ -function variable_mc_switch_state(pm::AbstractSwitchModels; nw::Int=nw_id_default, report::Bool=true, relax::Bool=false) - if relax - state = var(pm, nw)[:switch_state] = Dict{Int,Any}(l => JuMP.@variable( - pm.model, - base_name="$(nw)_switch_state_$(l)", - lower_bound = 0, - upper_bound = 1, - start = PMD.comp_start_value(ref(pm, nw, :switch, l), "state_start", get(ref(pm, nw, :switch, l), "state", 0)) - ) for l in ids(pm, nw, :switch_dispatchable)) - else - state = var(pm, nw)[:switch_state] = Dict{Int,Any}(l => JuMP.@variable( - pm.model, - base_name="$(nw)_switch_state_$(l)", - lower_bound = 0, - upper_bound = 1, - binary = true, - start = PMD.comp_start_value(ref(pm, nw, :switch, l), "state_start", get(ref(pm, nw, :switch, l), "state", 0)) - ) for l in ids(pm, nw, :switch_dispatchable)) - end +function variable_bus_voltage_indicator(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default, relax::Bool=false, report::Bool=true) + z_voltage = var(pm, nw)[:z_voltage] = JuMP.@variable( + pm.model, + [i in ids(pm, nw, :bus)], + base_name="$(nw)_z_voltage", + binary=!relax, + lower_bound=0, + upper_bound=1, + start=1 + ) + + report && IM.sol_component_value(pm, PMD.pmd_it_sym, nw, :bus, :status, ids(pm, nw, :bus), z_voltage) +end + + +@doc raw""" + variable_generator_indicator(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default, relax::Bool=false, report::Bool=true) + +Variables for switching generators on/off $$z^{gen}_i,~\forall i \in G$$, binary if `relax=false`. +Variables will appear in solution if `report=true`. +""" +function variable_generator_indicator(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default, relax::Bool=false, report::Bool=true) + z_gen = var(pm, nw)[:z_gen] = JuMP.@variable( + pm.model, + [i in ids(pm, nw, :gen)], + base_name="$(nw)_z_gen", + binary=!relax, + lower_bound=0, + upper_bound=1, + start=1 + ) + + report && IM.sol_component_value(pm, PMD.pmd_it_sym, nw, :gen, :status, ids(pm, nw, :gen), z_gen) +end + + +@doc raw""" + variable_storage_indicator(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default, relax::Bool=false, report::Bool=true) - report && _IM.sol_component_value(pm, PMD.pmd_it_sym, nw, :switch, :state, ids(pm, nw, :switch_dispatchable), state) +Variables for switching storage on/off $$z^{strg}_i,~\forall i \in E$$, binary if `relax=false`. +Variables will appear in solution if `report=true`. +""" +function variable_storage_indicator(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default, relax::Bool=false, report::Bool=true) + z_storage = var(pm, nw)[:z_storage] = JuMP.@variable( + pm.model, + [i in ids(pm, nw, :storage)], + base_name="$(nw)_z_storage", + binary=!relax, + lower_bound=0, + upper_bound=1, + start=1 + ) + + report && IM.sol_component_value(pm, PMD.pmd_it_sym, nw, :storage, :status, ids(pm, nw, :storage), z_storage) end -"do nothing, already have z_block; to fix usage of PMD.variable_mc_storage_power_mi_on_off" -function PowerModelsDistribution.variable_mc_storage_indicator(pm::AbstractSwitchModels; nw::Int=nw_id_default, relax::Bool=false, report::Bool=true) +@doc raw""" + variable_load_indicator(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default, relax::Bool=false, report::Bool=true) + +Variables for switching loads on/off $$z^{d}_i,~\forall i \in L$$, binary if `relax=false`. +Variables will appear in solution if `report=true`. +""" +function variable_load_indicator(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default, relax::Bool=false, report::Bool=true) + z_demand = var(pm, nw)[:z_demand] = JuMP.@variable( + pm.model, + [i in ids(pm, nw, :load)], + base_name="$(nw)_z_demand", + binary=!relax, + lower_bound=0, + upper_bound=1, + start=1 + ) + + report && IM.sol_component_value(pm, PMD.pmd_it_sym, nw, :load, :status, ids(pm, nw, :load), z_demand) +end + + +""" + variable_inverter_indicator(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default, relax::Bool=false, report::Bool=true) + +Variables for indicating whether a DER (storage or gen) is in grid-forming mode (1) or grid-following mode (0), binary is `relax=false`. +Variables will appear in solution if `report=true`. If "inverter"==GRID_FOLLOWING on the device, the inverter variable will be a constant. +""" +function variable_inverter_indicator(pm::AbstractUnbalancedPowerModel; nw::Int=nw_id_default, relax::Bool=false, report::Bool=true) + z_inverter = var(pm, nw)[:z_inverter] = Dict{Tuple{Symbol,Int},Union{JuMP.VariableRef,Int}}() + for t in [:storage, :gen] + for i in ids(pm, nw, t) + if Int(get(ref(pm, nw, t, i), "inverter", GRID_FORMING)) == 1 + var(pm, nw, :z_inverter)[(t,i)] = JuMP.@variable( + pm.model, + base_name="$(nw)_$(t)_z_inverter", + binary=!relax, + lower_bound=0, + upper_bound=1, + start=PMD.comp_start_value(ref(pm, nw, t, i), "inverter_start", Int(get(ref(pm, nw, t, i), "inverter", GRID_FORMING))), + ) + else + # GRID_FOLLOWING only + var(pm, nw, :z_inverter)[(t,i)] = Int(ref(pm, nw, t, i, "inverter")) + end + end + end + + if report + IM.sol_component_value(pm, PMD.pmd_it_sym, nw, :storage, :inverter, [i for ((t,i),_) in var(pm, nw, :z_inverter) if t == :storage], Dict{Int,Union{JuMP.VariableRef,Int}}(i => v for ((t,i),v) in filter(x->x.first[1]==:storage, z_inverter))) + IM.sol_component_value(pm, PMD.pmd_it_sym, nw, :gen, :inverter, [i for ((t,i),_) in var(pm, nw, :z_inverter) if t == :gen], Dict{Int,Union{JuMP.VariableRef,Int}}(i => v for ((t,i),v) in filter(x->x.first[1]==:gen, z_inverter))) + end end diff --git a/src/data_model/checks.jl b/src/data_model/checks.jl index 077e346e..19f7caf5 100644 --- a/src/data_model/checks.jl +++ b/src/data_model/checks.jl @@ -1,10 +1,24 @@ -"validates dict or vector structure against json schema using JSONSchema.jl" +""" + _validate_against_schema( + data::Union{Dict{String,<:Any}, Vector}, + schema::JSONSchema.Schema + )::Bool + +Validates dict or vector structure `data` against json `schema` using JSONSchema.jl. +""" function _validate_against_schema(data::Union{Dict{String,<:Any}, Vector}, schema::JSONSchema.Schema)::Bool JSONSchema.validate(data, schema) === nothing end -"validates dict or vector structure against json schema given by `schema_name`" +""" + _validate_against_schema( + data::Union{Dict{String,<:Any}, Vector}, + schema_name::String + )::Bool + +Validates dict or vector structure `data` against json schema given by `schema_name`. +""" function _validate_against_schema(data::Union{Dict{String,<:Any}, Vector}, schema_name::String)::Bool _validate_against_schema(data, load_schema(joinpath(dirname(pathof(PowerModelsONM)), "..", "schemas", "$(schema_name).schema.json"))) end @@ -13,7 +27,7 @@ end """ validate_runtime_arguments(data::Dict)::Bool -Validates runtime_arguments data against models/runtime_arguments schema +Validates runtime_arguments `data` against models/runtime_arguments schema """ validate_runtime_arguments(data::Dict)::Bool = _validate_against_schema(data, "input-runtime_arguments") @@ -21,7 +35,7 @@ validate_runtime_arguments(data::Dict)::Bool = _validate_against_schema(data, "i """ validate_events(data::Vector{Dict})::Bool -Validates events data against models/events schema +Validates events `data` against models/events schema """ validate_events(data::Vector)::Bool = _validate_against_schema(data, "input-events") @@ -29,7 +43,7 @@ validate_events(data::Vector)::Bool = _validate_against_schema(data, "input-even """ validate_inverters(data::Dict)::Bool -Validates inverter data against models/inverters schema +Validates inverter `data` against models/inverters schema """ validate_inverters(data::Dict)::Bool = _validate_against_schema(data, "input-inverters") @@ -37,7 +51,7 @@ validate_inverters(data::Dict)::Bool = _validate_against_schema(data, "input-inv """ validate_faults(data::Dict)::Bool -Validates fault input data against models/faults schema +Validates fault input `data` against models/faults schema """ validate_faults(data::Dict)::Bool = _validate_against_schema(data, "input-faults") @@ -45,7 +59,7 @@ validate_faults(data::Dict)::Bool = _validate_against_schema(data, "input-faults """ validate_settings(data::Dict)::Bool -Validates runtime_settings data against models/runtime_settings schema +Validates runtime_settings `data` against models/runtime_settings schema """ validate_settings(data::Dict)::Bool = _validate_against_schema(data, "input-settings") @@ -53,19 +67,38 @@ validate_settings(data::Dict)::Bool = _validate_against_schema(data, "input-sett """ validate_output(data::Dict)::Bool -Validates output data against models/outputs schema +Validates output `data` against models/outputs schema """ validate_output(data::Dict)::Bool = _validate_against_schema(data, "output") -"helper function to give detailed output on JSON Schema validation of output dict" +""" + evaluate_output(data::Dict) + +Helper function to give detailed output on JSON Schema validation of output `data` +""" evaluate_output(data::Dict) = JSONSchema.validate(data, load_schema(joinpath(dirname(pathof(PowerModelsONM)), "..", "schemas", "output.schema.json"))) -"helper function to give detailed output on JSON Schema validation of events dict" + +""" + evaluate_events(data::Dict) + +Helper function to give detailed output on JSON Schema validation of events `data` +""" evaluate_events(data::Dict) = JSONSchema.validate(data, load_schema(joinpath(dirname(pathof(PowerModelsONM)), "..", "schemas", "input-events.schema.json"))) -"helper function to give detailed output on JSON Schema validation of settings dict" + +""" + evaluate_settings(data::Dict) + +Helper function to give detailed output on JSON Schema validation of settings `data` +""" evaluate_settings(data::Dict) = JSONSchema.validate(data, load_schema(joinpath(dirname(pathof(PowerModelsONM)), "..", "schemas", "input-settings.schema.json"))) -"helper function to give detailed output on JSON Schema validation of runtime arguments dict" + +""" + evaluate_runtime_arguments(data::Dict) + +Helper function to give detailed output on JSON Schema validation of runtime arguments `data` +""" evaluate_runtime_arguments(data::Dict) = JSONSchema.validate(data, load_schema(joinpath(dirname(pathof(PowerModelsONM)), "..", "schemas", "input-runtime_arguments.schema.json"))) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl new file mode 100644 index 00000000..36aa6cba --- /dev/null +++ b/src/data_model/eng2math.jl @@ -0,0 +1,31 @@ +"default eng2math passthrough" +const _eng2math_passthrough_default = Dict{String,Vector{String}}( + "root"=>String[ + "options", + "switch_close_actions_ub", + ], + "load"=>String["priority"], + "bus"=>String["microgrid_id"], + "generator"=>String["inverter"], + "solar"=>String["inverter"], + "storage"=>String["phase_unbalance_ub", "inverter"], + "voltage_source"=>["inverter"], +) + + +"default global_keys passthrough" +const _default_global_keys = Set{String}(["options", "solvers"]) + + +""" + transform_data_model(eng::T; global_keys::Set{String}=Set{String}(), eng2math_passthrough::Dict{String,<:Vector{<:String}}=Dict{String,Vector{String}}(), kwargs...)::T where T <: Dict{String,Any} + +ONM-specific version of `PowerModelsDistribution.transform_data_model` that includes the necessary default `eng2math_passthrough` and `global_keys`. +""" +function transform_data_model(eng::T; global_keys::Set{String}=Set{String}(), eng2math_passthrough::Dict{String,<:Vector{<:String}}=Dict{String,Vector{String}}(), kwargs...)::T where T <: Dict{String,Any} + PMD.transform_data_model( + eng; + global_keys=union(_default_global_keys, global_keys), + eng2math_passthrough=recursive_merge_including_vectors(_eng2math_passthrough_default, eng2math_passthrough), + ) +end diff --git a/src/form/acp.jl b/src/form/acp.jl index 7b200e19..21b7741a 100644 --- a/src/form/acp.jl +++ b/src/form/acp.jl @@ -1,7 +1,7 @@ @doc raw""" - constraint_mc_switch_state_on_off(pm::AbstractUnbalancedACPSwitchModel, nw::Int, i::Int, f_bus::Int, t_bus::Int, f_connections::Vector{Int}, t_connections::Vector{Int}; relax::Bool=false) + constraint_mc_switch_state_voltage_open_closed(pm::PMD.AbstractUnbalancedACPModel, nw::Int, i::Int, f_bus::Int, t_bus::Int, f_connections::Vector{Int}, t_connections::Vector{Int}) -Linear switch power on/off constraint for ACPU form. If `relax`, an [indicator constraint](https://jump.dev/JuMP.jl/stable/manual/constraints/#Indicator-constraints) is used. +Linear switch power on/off constraint for ACPU form. ```math \begin{align} @@ -11,7 +11,7 @@ Linear switch power on/off constraint for ACPU form. If `relax`, an [indicator c \end{align} ``` """ -function PowerModelsDistribution.constraint_mc_switch_state_on_off(pm::AbstractUnbalancedACPSwitchModel, nw::Int, i::Int, f_bus::Int, t_bus::Int, f_connections::Vector{Int}, t_connections::Vector{Int}; relax::Bool=false) +function constraint_mc_switch_voltage_open_close(pm::PMD.AbstractUnbalancedACPModel, nw::Int, i::Int, f_bus::Int, t_bus::Int, f_connections::Vector{Int}, t_connections::Vector{Int}) vm_fr = var(pm, nw, :vm, f_bus) vm_to = var(pm, nw, :vm, t_bus) va_fr = var(pm, nw, :va, f_bus) @@ -29,37 +29,36 @@ function PowerModelsDistribution.constraint_mc_switch_state_on_off(pm::AbstractU vmin = max.(fill(0.0, length(f_vmax)), f_vmin, t_vmin) vmax = min.(fill(2.0, length(f_vmax)), f_vmax, t_vmax) - angmin = get(ref(pm, nw, :switch, i), "angmin", fill(-5.0, length(f_connections))) - angmax = get(ref(pm, nw, :switch, i), "angmax", fill( 5.0, length(f_connections))) + angmin = get(ref(pm, nw, :switch, i), "angmin", deg2rad.(fill(-5.0, length(f_connections)))) + angmax = get(ref(pm, nw, :switch, i), "angmax", deg2rad.(fill( 5.0, length(f_connections)))) - z = var(pm, nw, :switch_state, i) + state = var(pm, nw, :switch_state, i) for (idx, (fc, tc)) in enumerate(zip(f_connections, t_connections)) - if relax - JuMP.@constraint(pm.model, vm_fr[fc] - vm_to[tc] <= (vmax[idx]-vmin[idx]) * (1-z)) - JuMP.@constraint(pm.model, vm_fr[fc] - vm_to[tc] >= -(vmax[idx]-vmin[idx]) * (1-z)) + JuMP.@constraint(pm.model, vm_fr[fc] - vm_to[tc] <= (vmax[idx]-vmin[idx]) * (1-state)) + JuMP.@constraint(pm.model, vm_fr[fc] - vm_to[tc] >= -(vmax[idx]-vmin[idx]) * (1-state)) - JuMP.@constraint(pm.model, va_fr[fc] - va_to[tc] <= (angmax[idx]-angmin[idx]) * (1-z)) - JuMP.@constraint(pm.model, va_fr[fc] - va_to[tc] >= -(angmax[idx]-angmin[idx]) * (1-z)) - else - JuMP.@constraint(pm.model, z => {vm_fr[fc] == vm_to[tc]}) - JuMP.@constraint(pm.model, z => {va_fr[fc] == va_to[tc]}) - end + JuMP.@constraint(pm.model, va_fr[fc] - va_to[tc] <= (angmax[idx]-angmin[idx]) * (1-state)) + JuMP.@constraint(pm.model, va_fr[fc] - va_to[tc] >= -(angmax[idx]-angmin[idx]) * (1-state)) + + # Indicator constraint version, for reference + # JuMP.@constraint(pm.model, state => {vm_fr[fc] == vm_to[tc]}) + # JuMP.@constraint(pm.model, state => {va_fr[fc] == va_to[tc]}) end end """ - constraint_mc_power_balance_shed(pm::AbstractUnbalancedACPSwitchModel, nw::Int, i::Int, + constraint_mc_power_balance_shed_block(pm::PMD.AbstractUnbalancedACPModel, nw::Int, i::Int, terminals::Vector{Int}, grounded::Vector{Bool}, bus_arcs::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_sw::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_trans::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_gens::Vector{Tuple{Int,Vector{Int}}}, bus_storage::Vector{Tuple{Int,Vector{Int}}}, bus_loads::Vector{Tuple{Int,Vector{Int}}}, bus_shunts::Vector{Tuple{Int,Vector{Int}}} ) -KCL for load shed problem with transformers (ACPU Form) +KCL for block load shed problem with transformers (ACPU Form) """ -function PowerModelsDistribution.constraint_mc_power_balance_shed(pm::AbstractUnbalancedACPSwitchModel, nw::Int, i::Int, +function constraint_mc_power_balance_shed_block(pm::PMD.AbstractUnbalancedACPModel, nw::Int, i::Int, terminals::Vector{Int}, grounded::Vector{Bool}, bus_arcs::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_sw::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_trans::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_gens::Vector{Tuple{Int,Vector{Int}}}, bus_storage::Vector{Tuple{Int,Vector{Int}}}, @@ -156,7 +155,7 @@ function PowerModelsDistribution.constraint_mc_power_balance_shed(pm::AbstractUn con(pm, nw, :lam_kcl_r)[i] = cstr_p con(pm, nw, :lam_kcl_i)[i] = cstr_q - if _IM.report_duals(pm) + if IM.report_duals(pm) sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q end @@ -164,11 +163,11 @@ end """ - constraint_mc_bus_voltage_magnitude_on_off(pm::AbstractUnbalancedACPSwitchModel, nw::Int, i::Int, vmin::Vector{<:Real}, vmax::Vector{<:Real}) + constraint_mc_bus_voltage_magnitude_block_on_off(pm::PMD.AbstractUnbalancedACPModel, nw::Int, i::Int, vmin::Vector{<:Real}, vmax::Vector{<:Real}) -on/off bus voltage magnitude squared constraint for relaxed formulations +on/off bus voltage magnitude block constraint for relaxed formulations """ -function PowerModelsDistribution.constraint_mc_bus_voltage_magnitude_on_off(pm::AbstractUnbalancedACPSwitchModel, nw::Int, i::Int, vmin::Vector{<:Real}, vmax::Vector{<:Real}) +function constraint_mc_bus_voltage_magnitude_block_on_off(pm::PMD.AbstractUnbalancedACPModel, nw::Int, i::Int, vmin::Vector{<:Real}, vmax::Vector{<:Real}) vm = var(pm, nw, :vm, i) z_block = var(pm, nw, :z_block, ref(pm, nw, :bus_block_map, i)) @@ -176,20 +175,44 @@ function PowerModelsDistribution.constraint_mc_bus_voltage_magnitude_on_off(pm:: grounded = ref(pm, nw, :bus, i)["grounded"] for (idx,t) in [(idx,t) for (idx,t) in enumerate(terminals) if !grounded[idx]] - if isfinite(vmax[idx]) - JuMP.@constraint(pm.model, vm[t] <= vmax[idx]*z_block) - end + isfinite(vmax[idx]) && JuMP.@constraint(pm.model, vm[t] <= vmax[idx]*z_block) + isfinite(vmin[idx]) && JuMP.@constraint(pm.model, vm[t] >= vmin[idx]*z_block) + end +end - if isfinite(vmin[idx]) - JuMP.@constraint(pm.model, vm[t] >= vmin[idx]*z_block) - end + +""" + constraint_mc_bus_voltage_magnitude_traditional_on_off(pm::PMD.AbstractUnbalancedACPModel, nw::Int, i::Int, vmin::Vector{<:Real}, vmax::Vector{<:Real}) + +on/off bus voltage magnitude traditional constraint for relaxed formulations +""" +function constraint_mc_bus_voltage_magnitude_traditional_on_off(pm::PMD.AbstractUnbalancedACPModel, nw::Int, i::Int, vmin::Vector{<:Real}, vmax::Vector{<:Real}) + vm = var(pm, nw, :vm, i) + z_voltage = var(pm, nw, :z_voltage, i) + + terminals = ref(pm, nw, :bus, i)["terminals"] + grounded = ref(pm, nw, :bus, i)["grounded"] + + for (idx,t) in [(idx,t) for (idx,t) in enumerate(terminals) if !grounded[idx]] + isfinite(vmax[idx]) && JuMP.@constraint(pm.model, vm[t] <= vmax[idx]*z_voltage) + isfinite(vmin[idx]) && JuMP.@constraint(pm.model, vm[t] >= vmin[idx]*z_voltage) end end +""" +""" +constraint_mc_bus_voltage_block_on_off(pm::PMD.AbstractUnbalancedACPModel, nw::Int, i::Int, vmin::Vector{<:Real}, vmax::Vector{<:Real}) = constraint_mc_bus_voltage_magnitude_block_on_off(pm, nw, i, vmin, vmax) + + +""" +""" +constraint_mc_bus_voltage_traditional_on_off(pm::PMD.AbstractUnbalancedACPModel, nw::Int, i::Int, vmin::Vector{<:Real}, vmax::Vector{<:Real}) = constraint_mc_bus_voltage_magnitude_traditional_on_off(pm, nw, i, vmin, vmax) + + """ constraint_mc_transformer_power_yy_on_off( - pm::AbstractUnbalancedACPSwitchModel, + pm::PMD.AbstractUnbalancedACPModel, nw::Int, trans_id::Int, f_bus::Int, @@ -210,7 +233,7 @@ Links to and from power and voltages in a wye-wye transformer, assumes tm_fixed w_fr_i=(pol_i*tm_scale*tm_i)^2w_to_i ``` """ -function constraint_mc_transformer_power_yy_on_off(pm::AbstractUnbalancedACPSwitchModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx::Tuple{Int,Int,Int}, t_idx::Tuple{Int,Int,Int}, f_connections::Vector{Int}, t_connections::Vector{Int}, pol::Int, tm_set::Vector{<:Real}, tm_fixed::Vector{Bool}, tm_scale::Real) +function constraint_mc_transformer_power_yy_block_on_off(pm::PMD.AbstractUnbalancedACPModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx::Tuple{Int,Int,Int}, t_idx::Tuple{Int,Int,Int}, f_connections::Vector{Int}, t_connections::Vector{Int}, pol::Int, tm_set::Vector{<:Real}, tm_fixed::Vector{Bool}, tm_scale::Real) z_block = var(pm, nw, :z_block, ref(pm, nw, :bus_block_map, f_bus)) transformer = ref(pm, nw, :transformer, trans_id) @@ -267,35 +290,58 @@ end """ - constraint_mc_storage_losses_on_off(pm::AbstractUnbalancedACPSwitchModel, i::Int; nw::Int=nw_id_default) + constraint_mc_storage_losses_block_on_off(pm::PMD.AbstractUnbalancedACPModel, nw::Int, i::Int, bus::Int, connections::Vector{Int}, r::Real, x::Real, p_loss::Real, q_loss::Real) Non-linear storage loss constraint """ -function constraint_mc_storage_losses_on_off(pm::AbstractUnbalancedACPSwitchModel, i::Int; nw::Int=nw_id_default) - storage = ref(pm, nw, :storage, i) +function constraint_mc_storage_losses_block_on_off(pm::PMD.AbstractUnbalancedACPModel, nw::Int, i::Int, bus::Int, connections::Vector{Int}, r::Real, x::Real, p_loss::Real, q_loss::Real) z_block = var(pm, nw, :z_block, ref(pm, nw, :storage_block_map, i)) - vm = var(pm, nw, :vm, storage["storage_bus"]) + vm = var(pm, nw, :vm, bus) ps = var(pm, nw, :ps, i) qs = var(pm, nw, :qs, i) sc = var(pm, nw, :sc, i) sd = var(pm, nw, :sd, i) qsc = var(pm, nw, :qsc, i) - p_loss = storage["p_loss"] - q_loss = storage["q_loss"] - r = storage["r"] - x = storage["x"] + JuMP.@NLconstraint(pm.model, + sum(ps[c] for c in connections) + (sd - sc) + == + (p_loss + r * sum((ps[c]^2 + qs[c]^2)/vm[c]^2 for (idx,c) in enumerate(connections))) * z_block + ) + + JuMP.@NLconstraint(pm.model, + sum(qs[c] for c in connections) + == + (qsc + q_loss + x * sum((ps[c]^2 + qs[c]^2)/vm[c]^2 for (idx,c) in enumerate(connections))) * z_block + ) +end + + +""" + constraint_mc_storage_losses_traditional_on_off(pm::PMD.AbstractUnbalancedACPModel, nw::Int, i::Int, bus::Int, connections::Vector{Int}, r::Real, x::Real, p_loss::Real, q_loss::Real) + +Non-linear storage loss constraint +""" +function constraint_mc_storage_losses_traditional_on_off(pm::PMD.AbstractUnbalancedACPModel, nw::Int, i::Int, bus::Int, connections::Vector{Int}, r::Real, x::Real, p_loss::Real, q_loss::Real) + z_storage = var(pm, nw, :z_storage, i) + + vm = var(pm, nw, :vm, bus) + ps = var(pm, nw, :ps, i) + qs = var(pm, nw, :qs, i) + sc = var(pm, nw, :sc, i) + sd = var(pm, nw, :sd, i) + qsc = var(pm, nw, :qsc, i) JuMP.@NLconstraint(pm.model, - sum(ps[c] for c in storage["connections"]) + (sd - sc) + sum(ps[c] for c in connections) + (sd - sc) == - (p_loss + r * sum((ps[c]^2 + qs[c]^2)/vm[c]^2 for (idx,c) in enumerate(storage["connections"]))) * z_block + (p_loss + r * sum((ps[c]^2 + qs[c]^2)/vm[c]^2 for (idx,c) in enumerate(connections))) * z_storage ) JuMP.@NLconstraint(pm.model, - sum(qs[c] for c in storage["connections"]) + sum(qs[c] for c in connections) == - (qsc + q_loss + x * sum((ps[c]^2 + qs[c]^2)/vm[c]^2 for (idx,c) in enumerate(storage["connections"]))) * z_block + (qsc + q_loss + x * sum((ps[c]^2 + qs[c]^2)/vm[c]^2 for (idx,c) in enumerate(connections))) * z_storage ) end diff --git a/src/form/acr.jl b/src/form/acr.jl index f38cb7f5..b5417886 100644 --- a/src/form/acr.jl +++ b/src/form/acr.jl @@ -1,16 +1,16 @@ @doc raw""" - constraint_mc_switch_state_on_off(pm::AbstractUnbalancedACRSwitchModel, nw::Int, i::Int, f_bus::Int, t_bus::Int, f_connections::Vector{Int}, t_connections::Vector{Int}; relax::Bool=false) + constraint_mc_switch_voltage_open_close(pm::PMD.AbstractUnbalancedACRModel, nw::Int, i::Int, f_bus::Int, t_bus::Int, f_connections::Vector{Int}, t_connections::Vector{Int}) -Linear switch power on/off constraint for LPUBFDiagModel. If `relax`, an [indicator constraint](https://jump.dev/JuMP.jl/stable/manual/constraints/#Indicator-constraints) is used. +nonlinear switch power on/off constraint for ac-rect form ```math \begin{align} -& w^{fr}_{i,c} - w^{to}_{i,c} \leq \left ( v^u_{i,c} \right )^2 \left ( 1 - z^{sw}_i \right )\ \forall i \in S,\forall c \in C \\ -& w^{fr}_{i,c} - w^{to}_{i,c} \geq -\left ( v^u_{i,c}\right )^2 \left ( 1 - z^{sw}_i \right )\ \forall i \in S,\forall c \in C +& \\ +& \end{align} ``` """ -function PowerModelsDistribution.constraint_mc_switch_state_on_off(pm::AbstractUnbalancedACRSwitchModel, nw::Int, i::Int, f_bus::Int, t_bus::Int, f_connections::Vector{Int}, t_connections::Vector{Int}; relax::Bool=false) +function constraint_mc_switch_voltage_open_close(pm::PMD.AbstractUnbalancedACRModel, nw::Int, i::Int, f_bus::Int, t_bus::Int, f_connections::Vector{Int}, t_connections::Vector{Int}) vr_fr = var(pm, nw, :vr, f_bus) vr_to = var(pm, nw, :vr, t_bus) vi_fr = var(pm, nw, :vi, f_bus) @@ -28,31 +28,30 @@ function PowerModelsDistribution.constraint_mc_switch_state_on_off(pm::AbstractU vmin = max.(fill(0.0, length(f_vmax)), f_vmin, t_vmin) vmax = min.(fill(2.0, length(f_vmax)), f_vmax, t_vmax) - z = var(pm, nw, :switch_state, i) + state = var(pm, nw, :switch_state, i) for (idx, (fc, tc)) in enumerate(zip(f_connections, t_connections)) - if relax - JuMP.@NLconstraint(pm.model, (vr_fr[fc]^2 + vi_fr[fc]^2) - (vr_to[tc]^2 + vi_to[tc]^2) <= (vmax[idx]^2-vmin[idx]^2) * (1-z)) - JuMP.@NLconstraint(pm.model, (vr_fr[fc]^2 + vi_fr[fc]^2) - (vr_to[tc]^2 + vi_to[tc]^2) >= -(vmax[idx]^2-vmin[idx]^2) * (1-z)) - else - JuMP.@constraint(pm.model, z => {vr_fr[fc] == vr_to[tc]}) - JuMP.@constraint(pm.model, z => {vi_fr[fc] == vi_to[tc]}) - end + JuMP.@NLconstraint(pm.model, (vr_fr[fc]^2 + vi_fr[fc]^2) - (vr_to[tc]^2 + vi_to[tc]^2) <= (vmax[idx]^2-vmin[idx]^2) * (1-state)) + JuMP.@NLconstraint(pm.model, (vr_fr[fc]^2 + vi_fr[fc]^2) - (vr_to[tc]^2 + vi_to[tc]^2) >= -(vmax[idx]^2-vmin[idx]^2) * (1-state)) + + # Indicator constraint version, for reference + # JuMP.@constraint(pm.model, state => {vr_fr[fc] == vr_to[tc]}) + # JuMP.@constraint(pm.model, state => {vi_fr[fc] == vi_to[tc]}) end end """ - constraint_mc_power_balance_shed(pm::AbstractUnbalancedACRSwitchModel, nw::Int, i::Int, + constraint_mc_power_balance_shed_block(pm::PMD.AbstractUnbalancedACRModel, nw::Int, i::Int, terminals::Vector{Int}, grounded::Vector{Bool}, bus_arcs::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_sw::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_trans::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_gens::Vector{Tuple{Int,Vector{Int}}}, bus_storage::Vector{Tuple{Int,Vector{Int}}}, bus_loads::Vector{Tuple{Int,Vector{Int}}}, bus_shunts::Vector{Tuple{Int,Vector{Int}}} ) -KCL for load shed problem with transformers (ACRU Form) +KCL for block load shed problem with transformers (ac-rect form) """ -function PowerModelsDistribution.constraint_mc_power_balance_shed(pm::AbstractUnbalancedACRSwitchModel, nw::Int, i::Int, terminals::Vector{Int}, grounded::Vector{Bool}, bus_arcs::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_sw::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_trans::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_gens::Vector{Tuple{Int,Vector{Int}}}, bus_storage::Vector{Tuple{Int,Vector{Int}}}, bus_loads::Vector{Tuple{Int,Vector{Int}}}, bus_shunts::Vector{Tuple{Int,Vector{Int}}}) +function constraint_mc_power_balance_shed_block(pm::PMD.AbstractUnbalancedACRModel, nw::Int, i::Int, terminals::Vector{Int}, grounded::Vector{Bool}, bus_arcs::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_sw::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_trans::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_gens::Vector{Tuple{Int,Vector{Int}}}, bus_storage::Vector{Tuple{Int,Vector{Int}}}, bus_loads::Vector{Tuple{Int,Vector{Int}}}, bus_shunts::Vector{Tuple{Int,Vector{Int}}}) z_block = var(pm, nw, :z_block, ref(pm, nw, :bus_block_map, i)) vr = var(pm, nw, :vr, i) @@ -111,7 +110,7 @@ function PowerModelsDistribution.constraint_mc_power_balance_shed(pm::AbstractUn con(pm, nw, :lam_kcl_r)[i] = cstr_p con(pm, nw, :lam_kcl_i)[i] = cstr_q - if _IM.report_duals(pm) + if IM.report_duals(pm) sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q end @@ -119,11 +118,11 @@ end """ - constraint_mc_bus_voltage_magnitude_on_off(pm::AbstractUnbalancedACRSwitchModel, nw::Int, i::Int, vmin::Vector{<:Real}, vmax::Vector{<:Real}) + constraint_mc_bus_voltage_magnitude_block_on_off(pm::PMD.AbstractUnbalancedACRModel, nw::Int, i::Int, vmin::Vector{<:Real}, vmax::Vector{<:Real}) -on/off bus voltage magnitude squared constraint for relaxed formulations +on/off block bus voltage magnitude squared constraint for ac-rect form """ -function PowerModelsDistribution.constraint_mc_bus_voltage_magnitude_on_off(pm::AbstractUnbalancedACRSwitchModel, nw::Int, i::Int, vmin::Vector{<:Real}, vmax::Vector{<:Real}) +function constraint_mc_bus_voltage_magnitude_block_on_off(pm::PMD.AbstractUnbalancedACRModel, nw::Int, i::Int, vmin::Vector{<:Real}, vmax::Vector{<:Real}) vr = var(pm, nw, :vr, i) vi = var(pm, nw, :vi, i) @@ -133,20 +132,46 @@ function PowerModelsDistribution.constraint_mc_bus_voltage_magnitude_on_off(pm:: grounded = ref(pm, nw, :bus, i)["grounded"] for (idx,t) in [(idx,t) for (idx,t) in enumerate(terminals) if !grounded[idx]] - if isfinite(vmax[idx]) - JuMP.@constraint(pm.model, vr[t]^2 + vi[t]^2 <= vmax[idx]^2*z_block) - end + isfinite(vmax[idx]) && JuMP.@constraint(pm.model, vr[t]^2 + vi[t]^2 <= vmax[idx]^2*z_block) + isfinite(vmin[idx]) && JuMP.@constraint(pm.model, vr[t]^2 + vi[t]^2 >= vmin[idx]^2*z_block) + end +end - if isfinite(vmin[idx]) - JuMP.@constraint(pm.model, vr[t]^2 + vi[t]^2 >= vmin[idx]^2*z_block) - end + +""" + constraint_mc_bus_voltage_magnitude_traditional_on_off(pm::PMD.AbstractUnbalancedACRModel, nw::Int, i::Int, vmin::Vector{<:Real}, vmax::Vector{<:Real}) + +on/off block bus voltage magnitude squared constraint for ac-rect form +""" +function constraint_mc_bus_voltage_magnitude_traditional_on_off(pm::PMD.AbstractUnbalancedACRModel, nw::Int, i::Int, vmin::Vector{<:Real}, vmax::Vector{<:Real}) + vr = var(pm, nw, :vr, i) + vi = var(pm, nw, :vi, i) + + z_voltage = var(pm, nw, :z_voltage, i) + + terminals = ref(pm, nw, :bus, i)["terminals"] + grounded = ref(pm, nw, :bus, i)["grounded"] + + for (idx,t) in [(idx,t) for (idx,t) in enumerate(terminals) if !grounded[idx]] + isfinite(vmax[idx]) && JuMP.@constraint(pm.model, vr[t]^2 + vi[t]^2 <= vmax[idx]^2*z_voltage) + isfinite(vmin[idx]) && JuMP.@constraint(pm.model, vr[t]^2 + vi[t]^2 >= vmin[idx]^2*z_voltage) end end """ - constraint_mc_transformer_power_yy_on_off( - pm::AbstractUnbalancedACRSwitchModel, +""" +constraint_mc_bus_voltage_block_on_off(pm::PMD.AbstractUnbalancedACRModel, nw::Int, i::Int, vmin::Vector{<:Real}, vmax::Vector{<:Real}) = constraint_mc_bus_voltage_magnitude_block_on_off(pm, nw, i, vmin, vmax) + + +""" +""" +constraint_mc_bus_voltage_traditional_on_off(pm::PMD.AbstractUnbalancedACRModel, nw::Int, i::Int, vmin::Vector{<:Real}, vmax::Vector{<:Real}) = constraint_mc_bus_voltage_magnitude_traditional_on_off(pm, nw, i, vmin, vmax) + + +""" + constraint_mc_transformer_power_yy_block_on_off( + pm::PMD.AbstractUnbalancedACRModel, nw::Int, trans_id::Int, f_bus::Int, @@ -163,7 +188,7 @@ end Links to and from power and voltages in a wye-wye transformer, assumes tm_fixed is true """ -function constraint_mc_transformer_power_yy_on_off(pm::AbstractUnbalancedACRSwitchModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx::Tuple{Int,Int,Int}, t_idx::Tuple{Int,Int,Int}, f_connections::Vector{Int}, t_connections::Vector{Int}, pol::Int, tm_set::Vector{<:Real}, tm_fixed::Vector{Bool}, tm_scale::Real) +function constraint_mc_transformer_power_yy_block_on_off(pm::PMD.AbstractUnbalancedACRModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx::Tuple{Int,Int,Int}, t_idx::Tuple{Int,Int,Int}, f_connections::Vector{Int}, t_connections::Vector{Int}, pol::Int, tm_set::Vector{<:Real}, tm_fixed::Vector{Bool}, tm_scale::Real) z_block = var(pm, nw, :z_block, ref(pm, nw, :bus_block_map, f_bus)) transformer = ref(pm, nw, :transformer, trans_id) @@ -190,7 +215,7 @@ function constraint_mc_transformer_power_yy_on_off(pm::AbstractUnbalancedACRSwit JuMP.@constraint(pm.model, vr_fr[fc] == pol*tm_scale*tm[idx]*vr_to[tc]) JuMP.@constraint(pm.model, vi_fr[fc] == pol*tm_scale*tm[idx]*vi_to[tc]) - # with regcontrol + # TODO: with regcontrol if haskey(transformer,"controls") # v_ref = transformer["controls"]["vreg"][idx] # δ = transformer["controls"]["band"][idx] @@ -220,36 +245,111 @@ end """ - constraint_mc_storage_losses_on_off(pm::AbstractUnbalancedACRSwitchModel, i::Int; nw::Int=nw_id_default) + constraint_mc_storage_losses_block_on_off(pm::PMD.AbstractUnbalancedACRModel, nw::Int, i::Int, bus::Int, connections::Vector{Int}, r::Real, x::Real, p_loss::Real, q_loss::Real) -Non-linear storage losses constraint for ACRU Form. +Nonlinear storage losses constraint for ac-rect form. """ -function constraint_mc_storage_losses_on_off(pm::AbstractUnbalancedACRSwitchModel, i::Int; nw::Int=nw_id_default) - storage = ref(pm, nw, :storage, i) +function constraint_mc_storage_losses_block_on_off(pm::PMD.AbstractUnbalancedACRModel, nw::Int, i::Int, bus::Int, connections::Vector{Int}, r::Real, x::Real, p_loss::Real, q_loss::Real) z_block = var(pm, nw, :z_block, ref(pm, nw, :storage_block_map, i)) - vr = var(pm, nw, :vr, storage["storage_bus"]) - vi = var(pm, nw, :vi, storage["storage_bus"]) + vr = var(pm, nw, :vr, bus) + vi = var(pm, nw, :vi, bus) ps = var(pm, nw, :ps, i) qs = var(pm, nw, :qs, i) sc = var(pm, nw, :sc, i) sd = var(pm, nw, :sd, i) qsc = var(pm, nw, :qsc, i) - p_loss = storage["p_loss"] - q_loss = storage["q_loss"] - r = storage["r"] - x = storage["x"] + JuMP.@NLconstraint(pm.model, + (sum(ps[c] for c in connections) + (sd - sc)) * z_block + == + (p_loss + r * sum((ps[c]^2 + qs[c]^2)/(vr[c]^2 + vi[c]^2) for (idx,c) in enumerate(connections))) * z_block + ) JuMP.@NLconstraint(pm.model, - (sum(ps[c] for c in storage["connections"]) + (sd - sc)) * z_block + sum(qs[c] for c in connections) == - (p_loss + r * sum((ps[c]^2 + qs[c]^2)/(vr[c]^2 + vi[c]^2) for (idx,c) in enumerate(storage["connections"]))) * z_block + (qsc + q_loss + x * sum((ps[c]^2 + qs[c]^2)/(vr[c]^2 + vi[c]^2) for (idx,c) in enumerate(connections))) * z_block + ) +end + + +""" + constraint_mc_storage_losses_traditional_on_off(pm::PMD.AbstractUnbalancedACRModel, nw::Int, i::Int, bus::Int, connections::Vector{Int}, r::Real, x::Real, p_loss::Real, q_loss::Real) + +Nonlinear storage losses constraint for ac-rect form. +""" +function constraint_mc_storage_losses_traditional_on_off(pm::PMD.AbstractUnbalancedACRModel, nw::Int, i::Int, bus::Int, connections::Vector{Int}, r::Real, x::Real, p_loss::Real, q_loss::Real) + z_storage = var(pm, nw, :z_storage, i) + + vr = var(pm, nw, :vr, bus) + vi = var(pm, nw, :vi, bus) + ps = var(pm, nw, :ps, i) + qs = var(pm, nw, :qs, i) + sc = var(pm, nw, :sc, i) + sd = var(pm, nw, :sd, i) + qsc = var(pm, nw, :qsc, i) + + JuMP.@NLconstraint(pm.model, + (sum(ps[c] for c in connections) + (sd - sc)) * z_storage + == + (p_loss + r * sum((ps[c]^2 + qs[c]^2)/(vr[c]^2 + vi[c]^2) for (idx,c) in enumerate(connections))) * z_storage ) JuMP.@NLconstraint(pm.model, - sum(qs[c] for c in storage["connections"]) + sum(qs[c] for c in connections) == - (qsc + q_loss + x * sum((ps[c]^2 + qs[c]^2)/(vr[c]^2 + vi[c]^2) for (idx,c) in enumerate(storage["connections"]))) * z_block + (qsc + q_loss + x * sum((ps[c]^2 + qs[c]^2)/(vr[c]^2 + vi[c]^2) for (idx,c) in enumerate(connections))) * z_storage ) end + + +@doc raw""" + constraint_mc_inverter_theta_ref(pm::PMD.AbstractUnbalancedACRModel, nw::Int, i::Int, va_ref::Vector{<:Real}) + +Creates phase angle constraints at reference buses for the ACR formulation + +math``` +\begin{align} +\Im(V) = \tan(V_a^{ref}) \Re(V) +\end{align} +``` +""" +function constraint_mc_inverter_theta_ref(pm::PMD.AbstractUnbalancedACRModel, nw::Int, i::Int, va_ref::Vector{<:Real}) + terminals = ref(pm, nw, :bus, i, "terminals") + vr = var(pm, nw, :vr, i) + vi = var(pm, nw, :vi, i) + inverter_objects = ref(pm, nw, :bus_inverters, i) + + # deal with cases first where tan(theta)==Inf or tan(theta)==0 + for (idx, t) in enumerate(terminals) + if JuMP.has_upper_bound(vr[t]) && JuMP.has_upper_bound(vi[t]) + if va_ref[t] == pi/2 + JuMP.@constraint(pm.model, vr[t] <= JuMP.upper_bound(vr[t]) * (1-sum(var(pm, nw, :z_inverter, inv_obj) for inv_obj in inverter_objects))) + JuMP.@constraint(pm.model, vr[t] >= -JuMP.upper_bound(vr[t]) * (1-sum(var(pm, nw, :z_inverter, inv_obj) for inv_obj in inverter_objects))) + JuMP.@constraint(pm.model, vi[t] >= -JuMP.upper_bound(vi[t]) * (1-sum(var(pm, nw, :z_inverter, inv_obj) for inv_obj in inverter_objects))) + elseif va_ref[t] == -pi/2 + JuMP.@constraint(pm.model, vr[t] <= JuMP.upper_bound(vr[t]) * (1-sum(var(pm, nw, :z_inverter, inv_obj) for inv_obj in inverter_objects))) + JuMP.@constraint(pm.model, vr[t] >= -JuMP.upper_bound(vr[t]) * (1-sum(var(pm, nw, :z_inverter, inv_obj) for inv_obj in inverter_objects))) + JuMP.@constraint(pm.model, vi[t] <= JuMP.upper_bound(vi[t]) * (1-sum(var(pm, nw, :z_inverter, inv_obj) for inv_obj in inverter_objects))) + elseif va_ref[t] == 0 + JuMP.@constraint(pm.model, vr[t] >= -JuMP.upper_bound(vr[t]) * (1-sum(var(pm, nw, :z_inverter, inv_obj) for inv_obj in inverter_objects))) + JuMP.@constraint(pm.model, vi[t] <= JuMP.upper_bound(vi[t]) * (1-sum(var(pm, nw, :z_inverter, inv_obj) for inv_obj in inverter_objects))) + JuMP.@constraint(pm.model, vi[t] >= -JuMP.upper_bound(vi[t]) * (1-sum(var(pm, nw, :z_inverter, inv_obj) for inv_obj in inverter_objects))) + elseif va_ref[t] == pi + JuMP.@constraint(pm.model, vr[t] >= -JuMP.upper_bound(vr[t]) * (1-sum(var(pm, nw, :z_inverter, inv_obj) for inv_obj in inverter_objects))) + JuMP.@constraint(pm.model, vi[t] <= JuMP.upper_bound(vi[t]) * (1-sum(var(pm, nw, :z_inverter, inv_obj) for inv_obj in inverter_objects))) + JuMP.@constraint(pm.model, vi[t] >= -JuMP.upper_bound(vi[t]) * (1-sum(var(pm, nw, :z_inverter, inv_obj) for inv_obj in inverter_objects))) + else + JuMP.@constraint(pm.model, vi[t] - tan(va_ref[idx])*vr[t] <= deg2rad(60.0) * (1-sum(var(pm, nw, :z_inverter, inv_obj) for inv_obj in inverter_objects))) + JuMP.@constraint(pm.model, vi[t] - tan(va_ref[idx])*vr[t] >= -deg2rad(60.0) * (1-sum(var(pm, nw, :z_inverter, inv_obj) for inv_obj in inverter_objects))) + # va_ref also implies a sign for vr, vi + if 0<=va_ref[t] && va_ref[t] <= pi + JuMP.@constraint(pm.model, vi[t] >= -JuMP.upper_bound(vi[t]) * (1-sum(var(pm, nw, :z_inverter, inv_obj) for inv_obj in inverter_objects))) + else + JuMP.@constraint(pm.model, vi[t] <= JuMP.upper_bound(vi[t]) * (1-sum(var(pm, nw, :z_inverter, inv_obj) for inv_obj in inverter_objects))) + end + end + end + end +end diff --git a/src/form/apo.jl b/src/form/apo.jl index d748db82..894fa9ec 100644 --- a/src/form/apo.jl +++ b/src/form/apo.jl @@ -1,14 +1,14 @@ @doc raw""" - constraint_mc_switch_state_on_off(pm::AbstractUnbalancedNFASwitchModel, nw::Int, i::Int, f_bus::Int, t_bus::Int, f_connections::Vector{Int}, t_connections::Vector{Int}; relax::Bool=false) + constraint_mc_switch_voltage_open_close(pm::PMD.AbstractUnbalancedNFAModel, nw::Int, i::Int, f_bus::Int, t_bus::Int, f_connections::Vector{Int}, t_connections::Vector{Int}) No voltage variables, do nothing """ -function PowerModelsDistribution.constraint_mc_switch_state_on_off(pm::AbstractUnbalancedNFASwitchModel, nw::Int, i::Int, f_bus::Int, t_bus::Int, f_connections::Vector{Int}, t_connections::Vector{Int}; relax::Bool=false) +function constraint_mc_switch_voltage_open_close(pm::PMD.AbstractUnbalancedNFAModel, nw::Int, i::Int, f_bus::Int, t_bus::Int, f_connections::Vector{Int}, t_connections::Vector{Int}) end @doc raw""" - constraint_mc_switch_power_on_off(pm::AbstractUnbalancedNFASwitchModel, nw::Int, f_idx::Tuple{Int,Int,Int}; relax::Bool=false) + constraint_mc_switch_power_open_close(pm::PMD.AbstractUnbalancedNFAModel, nw::Int, i::Int, f_bus::Int, t_bus::Int, f_connections::Vector{Int}, t_connections::Vector{Int}) Linear switch power on/off constraint for Active Power Only Models. If `relax`, an [indicator constraint](https://jump.dev/JuMP.jl/stable/manual/constraints/#Indicator-constraints) is used. @@ -19,69 +19,78 @@ Linear switch power on/off constraint for Active Power Only Models. If `relax`, \end{align} ``` """ -function PowerModelsDistribution.constraint_mc_switch_power_on_off(pm::AbstractUnbalancedNFASwitchModel, nw::Int, f_idx::Tuple{Int,Int,Int}; relax::Bool=false) - i, f_bus, t_bus = f_idx +function constraint_mc_switch_power_open_close(pm::PMD.AbstractUnbalancedNFAModel, nw::Int, i::Int, f_bus::Int, t_bus::Int, f_connections::Vector{Int}, t_connections::Vector{Int}) + psw = var(pm, nw, :psw, (i, f_bus, t_bus)) - psw = var(pm, nw, :psw, f_idx) + state = var(pm, nw, :switch_state, i) - z = var(pm, nw, :switch_state, i) + rating = min.(fill(1.0, length(f_connections)), PMD._calc_branch_power_max_frto(ref(pm, nw, :switch, i), ref(pm, nw, :bus, f_bus), ref(pm, nw, :bus, t_bus))...) - connections = ref(pm, nw, :switch, i)["f_connections"] + for (idx, c) in enumerate(f_connections) + JuMP.@constraint(pm.model, psw[c] <= rating[idx] * state) + JuMP.@constraint(pm.model, psw[c] >= -rating[idx] * state) - switch = ref(pm, nw, :switch, i) - - rating = min.(fill(100.0, length(connections)), PMD._calc_branch_power_max_frto(switch, ref(pm, nw, :bus, f_bus), ref(pm, nw, :bus, t_bus))...) - - for (idx, c) in enumerate(connections) - if relax - JuMP.@constraint(pm.model, psw[c] <= rating[idx] * z) - JuMP.@constraint(pm.model, psw[c] >= -rating[idx] * z) - else - JuMP.@constraint(pm.model, !z => {psw[c] == 0.0}) - end + # Indicator constraint version, for reference + # JuMP.@constraint(pm.model, !state => {psw[c] == 0.0}) end end "do nothing, no voltage variables" -function PowerModelsDistribution.variable_mc_bus_voltage_on_off(pm::AbstractUnbalancedNFASwitchModel; nw::Int=nw_id_default) +function constraint_mc_bus_voltage_block_on_off(::PMD.AbstractUnbalancedNFAModel, ::Int, ::Int, ::Vector{<:Real}, ::Vector{<:Real}) end "do nothing, no voltage variables" -function PowerModelsDistribution.constraint_mc_bus_voltage_on_off(pm::AbstractUnbalancedNFASwitchModel; nw::Int=nw_id_default) +function constraint_mc_bus_voltage_traditional_on_off(::PMD.AbstractUnbalancedNFAModel, ::Int, ::Int, ::Vector{<:Real}, ::Vector{<:Real}) end -"on/off constraint for generators" -function PowerModelsDistribution.constraint_mc_gen_power_on_off(pm::AbstractUnbalancedNFASwitchModel, nw::Int64, i::Int64, connections::Vector{Int64}, pmin::Vector{<:Real}, pmax::Vector{<:Real}, ::Vector{<:Real}, ::Vector{<:Real}) +""" + constraint_mc_generator_power_block_on_off(pm::PMD.AbstractUnbalancedNFAModel, nw::Int, i::Int, connections::Vector{Int}, pmin::Vector{<:Real}, pmax::Vector{<:Real}, ::Vector{<:Real}, ::Vector{<:Real}) + +on/off block constraint for generators for NFA model +""" +function constraint_mc_generator_power_block_on_off(pm::PMD.AbstractUnbalancedNFAModel, nw::Int, i::Int, connections::Vector{<:Int}, pmin::Vector{<:Real}, pmax::Vector{<:Real}, qmin::Vector{<:Real}, qmax::Vector{<:Real}) pg = var(pm, nw, :pg, i) z_block = var(pm, nw, :z_block, ref(pm, nw, :gen_block_map, i)) for (idx, c) in enumerate(connections) - if isfinite(pmax[idx]) - JuMP.@constraint(pm.model, pg[c] <= pmax[idx]*z_block) - end + isfinite(pmax[idx]) && JuMP.@constraint(pm.model, pg[c] <= pmax[idx]*z_block) + isfinite(pmin[idx]) && JuMP.@constraint(pm.model, pg[c] >= pmin[idx]*z_block) + end +end - if isfinite(pmin[idx]) - JuMP.@constraint(pm.model, pg[c] >= pmin[idx]*z_block) - end + +""" + constraint_mc_generator_power_traditional_on_off(pm::PMD.AbstractUnbalancedNFAModel, nw::Int, i::Int, connections::Vector{Int}, pmin::Vector{<:Real}, pmax::Vector{<:Real}, ::Vector{<:Real}, ::Vector{<:Real}) + +on/off traditional constraint for generators for NFAU form +""" +function constraint_mc_generator_power_traditional_on_off(pm::PMD.AbstractUnbalancedNFAModel, nw::Int, i::Int, connections::Vector{<:Int}, pmin::Vector{<:Real}, pmax::Vector{<:Real}, qmin::Vector{<:Real}, qmax::Vector{<:Real}) + pg = var(pm, nw, :pg, i) + + z_gen = var(pm, nw, :z_gen, i) + + for (idx, c) in enumerate(connections) + isfinite(pmax[idx]) && JuMP.@constraint(pm.model, pg[c] <= pmax[idx]*z_gen) + isfinite(pmin[idx]) && JuMP.@constraint(pm.model, pg[c] >= pmin[idx]*z_gen) end end @doc raw""" - constraint_mc_power_balance_shed(pm::AbstractUnbalancedNFASwitchModel, nw::Int, i::Int, + constraint_mc_power_balance_shed_block(pm::PMD.AbstractUnbalancedNFAModel, nw::Int, i::Int, terminals::Vector{Int}, grounded::Vector{Bool}, bus_arcs::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_sw::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_trans::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_gens::Vector{Tuple{Int,Vector{Int}}}, bus_storage::Vector{Tuple{Int,Vector{Int}}}, bus_loads::Vector{Tuple{Int,Vector{Int}}}, bus_shunts::Vector{Tuple{Int,Vector{Int}}} ) -KCL for load shed problem with transformers (ACRU Form) +KCL for block load shed problem with transformers (NFAU Form) """ -function PowerModelsDistribution.constraint_mc_power_balance_shed(pm::AbstractUnbalancedNFASwitchModel, nw::Int, i::Int, terminals::Vector{Int}, grounded::Vector{Bool}, bus_arcs::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_sw::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_trans::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_gens::Vector{Tuple{Int,Vector{Int}}}, bus_storage::Vector{Tuple{Int,Vector{Int}}}, bus_loads::Vector{Tuple{Int,Vector{Int}}}, bus_shunts::Vector{Tuple{Int,Vector{Int}}}) +function constraint_mc_power_balance_shed_block(pm::PMD.AbstractUnbalancedNFAModel, nw::Int, i::Int, terminals::Vector{Int}, grounded::Vector{Bool}, bus_arcs::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_sw::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_trans::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_gens::Vector{Tuple{Int,Vector{Int}}}, bus_storage::Vector{Tuple{Int,Vector{Int}}}, bus_loads::Vector{Tuple{Int,Vector{Int}}}, bus_shunts::Vector{Tuple{Int,Vector{Int}}}) p = get(var(pm, nw), :p, Dict()); PMD._check_var_keys(p, bus_arcs, "active power", "branch") psw = get(var(pm, nw), :psw, Dict()); PMD._check_var_keys(psw, bus_arcs_sw, "active power", "switch") pt = get(var(pm, nw), :pt, Dict()); PMD._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") @@ -100,7 +109,7 @@ function PowerModelsDistribution.constraint_mc_power_balance_shed(pm::AbstractUn for (l,conns) in bus_loads for c in conns - _IM.relaxation_product(pm.model, pd[l][c], z_block, pd_zblock[l][c]) + IM.relaxation_product(pm.model, pd[l][c], z_block, pd_zblock[l][c]) end end @@ -122,50 +131,137 @@ function PowerModelsDistribution.constraint_mc_power_balance_shed(pm::AbstractUn con(pm, nw, :lam_kcl_r)[i] = cstr_p con(pm, nw, :lam_kcl_i)[i] = [] - if _IM.report_duals(pm) + if IM.report_duals(pm) sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p sol(pm, nw, :bus, i)[:lam_kcl_i] = [] end end +@doc raw""" + constraint_mc_power_balance_shed_traditional(pm::PMD.AbstractUnbalancedNFAModel, nw::Int, i::Int, + terminals::Vector{Int}, grounded::Vector{Bool}, bus_arcs::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, + bus_arcs_sw::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_trans::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, + bus_gens::Vector{Tuple{Int,Vector{Int}}}, bus_storage::Vector{Tuple{Int,Vector{Int}}}, + bus_loads::Vector{Tuple{Int,Vector{Int}}}, bus_shunts::Vector{Tuple{Int,Vector{Int}}} + ) + +KCL for traditional load shed problem with transformers (NFAU Form) """ - constraint_mc_storage_on_off(pm::AbstractUnbalancedNFASwitchModel, nw::Int, i::Int, connections::Vector{Int}, pmin::Real, pmax::Real, qmin::Real, qmax::Real, charge_ub::Real, discharge_ub::Real) +function constraint_mc_power_balance_shed_traditional(pm::PMD.AbstractUnbalancedNFAModel, nw::Int, i::Int, terminals::Vector{Int}, grounded::Vector{Bool}, bus_arcs::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_sw::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_trans::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_gens::Vector{Tuple{Int,Vector{Int}}}, bus_storage::Vector{Tuple{Int,Vector{Int}}}, bus_loads::Vector{Tuple{Int,Vector{Int}}}, bus_shunts::Vector{Tuple{Int,Vector{Int}}}) + p = get(var(pm, nw), :p, Dict()); PMD._check_var_keys(p, bus_arcs, "active power", "branch") + psw = get(var(pm, nw), :psw, Dict()); PMD._check_var_keys(psw, bus_arcs_sw, "active power", "switch") + pt = get(var(pm, nw), :pt, Dict()); PMD._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") + pg = get(var(pm, nw), :pg, Dict()); PMD._check_var_keys(pg, bus_gens, "active power", "generator") + ps = get(var(pm, nw), :ps, Dict()); PMD._check_var_keys(ps, bus_storage, "active power", "storage") + pd = get(var(pm, nw), :pd_bus, Dict()); PMD._check_var_keys(pd, bus_loads, "active power", "load") -on/off constraint for storage in NFAU Form. + z_demand = Dict(l => var(pm, nw, :z_demand, l) for (l,_) in bus_loads) + + Gt, _ = PMD._build_bus_shunt_matrices(pm, nw, terminals, bus_shunts) + + cstr_p = [] + + ungrounded_terminals = [(idx,t) for (idx,t) in enumerate(terminals) if !grounded[idx]] + + pd_zdemand = Dict{Int,JuMP.Containers.DenseAxisArray{JuMP.VariableRef}}(l => JuMP.@variable(pm.model, [c in conns], base_name="$(nw)_pd_zdemand_$(l)") for (l,conns) in bus_loads) + + for (l,conns) in bus_loads + for c in conns + IM.relaxation_product(pm.model, pd[l][c], z_demand[l], pd_zdemand[l][c]) + end + end + + for (idx, t) in ungrounded_terminals + cp = JuMP.@constraint(pm.model, + sum(p[a][t] for (a, conns) in bus_arcs if t in conns) + + sum(psw[a_sw][t] for (a_sw, conns) in bus_arcs_sw if t in conns) + + sum(pt[a_trans][t] for (a_trans, conns) in bus_arcs_trans if t in conns) + == + sum(pg[g][t] for (g, conns) in bus_gens if t in conns) + - sum(ps[s][t] for (s, conns) in bus_storage if t in conns) + - sum(pd_zdemand[l][t] for (l, conns) in bus_loads if t in conns) + - LinearAlgebra.diag(Gt)[idx] + ) + push!(cstr_p, cp) + end + # omit reactive constraint + + con(pm, nw, :lam_kcl_r)[i] = cstr_p + con(pm, nw, :lam_kcl_i)[i] = [] + + if IM.report_duals(pm) + sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p + sol(pm, nw, :bus, i)[:lam_kcl_i] = [] + end +end + + +""" + constraint_mc_storage_block_on_off(pm::PMD.AbstractUnbalancedNFAModel, nw::Int, i::Int, connections::Vector{Int}, pmin::Real, pmax::Real, ::Real, ::Real, ::Real, ::Real) + +block on/off constraint for storage in NFAU Form. """ -function PowerModelsDistribution.constraint_mc_storage_on_off(pm::AbstractUnbalancedNFASwitchModel, nw::Int, i::Int, connections::Vector{Int}, pmin::Real, pmax::Real, qmin::Real, qmax::Real, charge_ub::Real, discharge_ub::Real) +function constraint_mc_storage_block_on_off(pm::PMD.AbstractUnbalancedNFAModel, nw::Int, i::Int, connections::Vector{Int}, pmin::Real, pmax::Real, ::Real, ::Real, ::Real, ::Real) z_block = var(pm, nw, :z_block, ref(pm, nw, :storage_block_map, i)) ps = [var(pm, nw, :ps, i)[c] for c in connections] - JuMP.@constraint(pm.model, sum(ps) <= z_block*pmax) - JuMP.@constraint(pm.model, sum(ps) >= z_block*pmin) + isfinite(pmin) && JuMP.@constraint(pm.model, sum(ps) >= z_block*pmin) + isfinite(pmax) && JuMP.@constraint(pm.model, sum(ps) <= z_block*pmax) +end + + +""" + constraint_mc_storage_traditional_on_off(pm::PMD.AbstractUnbalancedNFAModel, nw::Int, i::Int, connections::Vector{Int}, pmin::Real, pmax::Real, ::Real, ::Real, ::Real, ::Real) + +traditional on/off constraint for storage in NFAU Form. +""" +function constraint_mc_storage_traditional_on_off(pm::PMD.AbstractUnbalancedNFAModel, nw::Int, i::Int, connections::Vector{Int}, pmin::Real, pmax::Real, ::Real, ::Real, ::Real, ::Real) + z_storage = var(pm, nw, :z_storage, i) + + ps = [var(pm, nw, :ps, i)[c] for c in connections] + + isfinite(pmin) && JuMP.@constraint(pm.model, sum(ps) >= z_storage*pmin) + isfinite(pmax) && JuMP.@constraint(pm.model, sum(ps) <= z_storage*pmax) end """ - constraint_mc_storage_losses_on_off(pm::AbstractUnbalancedNFASwitchModel, i::Int; nw::Int=nw_id_default) + constraint_mc_storage_losses_block_on_off(pm::PMD.AbstractUnbalancedNFAModel, nw::Int, i::Int, bus::Int, connections::Vector{Int}, ::Real, ::Real, ::Real, ::Real) Neglects all losses (lossless model), NFAU Form. """ -function constraint_mc_storage_losses_on_off(pm::AbstractUnbalancedNFASwitchModel, i::Int; nw::Int=nw_id_default) - storage = ref(pm, nw, :storage, i) +function constraint_mc_storage_losses_block_on_off(pm::PMD.AbstractUnbalancedNFAModel, nw::Int, i::Int, bus::Int, connections::Vector{Int}, ::Real, ::Real, ::Real, ::Real) z_block = var(pm, nw, :z_block, ref(pm, nw, :storage_block_map, i)) - p_loss = storage["p_loss"] - conductors = storage["connections"] + ps = var(pm, nw, :ps, i) + sc = var(pm, nw, :sc, i) + sd = var(pm, nw, :sd, i) + + JuMP.@constraint(pm.model, sum(ps[c] for c in connections) + (sd - sc) == 0.0 * z_block) +end + + +""" + constraint_mc_storage_losses_traditional_on_off(pm::PMD.AbstractUnbalancedNFAModel, nw::Int, i::Int, bus::Int, connections::Vector{Int}, ::Real, ::Real, ::Real, ::Real) + +Neglects all losses (lossless model), NFAU Form. +""" +function constraint_mc_storage_losses_traditional_on_off(pm::PMD.AbstractUnbalancedNFAModel, nw::Int, i::Int, bus::Int, connections::Vector{Int}, ::Real, ::Real, ::Real, ::Real) + storage = ref(pm, nw, :storage, i) + z_storage = var(pm, nw, :z_storage, i) ps = var(pm, nw, :ps, i) sc = var(pm, nw, :sc, i) sd = var(pm, nw, :sd, i) - JuMP.@constraint(pm.model, sum(ps[c] for c in conductors) + (sd - sc) == 0.0) + JuMP.@constraint(pm.model, sum(ps[c] for c in connections) + (sd - sc) == 0.0 * z_storage) end @doc raw""" - constraint_mc_transformer_power(pm::AbstractUnbalancedNFASwitchModel, i::Int; nw::Int=nw_id_default) + constraint_mc_transformer_power_block_on_off(pm::PMD.AbstractUnbalancedNFAModel, i::Int; nw::Int=nw_id_default, fix_taps::Bool=false) transformer active power only constraint pf=-pt @@ -173,7 +269,7 @@ transformer active power only constraint pf=-pt p_f[fc] == -pt[tc] ``` """ -function constraint_mc_transformer_power_on_off(pm::AbstractUnbalancedNFASwitchModel, i::Int; nw::Int=nw_id_default, fix_taps::Bool=false) +function constraint_mc_transformer_power_block_on_off(pm::PMD.AbstractUnbalancedNFAModel, i::Int; nw::Int=nw_id_default, fix_taps::Bool=false) transformer = ref(pm, nw, :transformer, i) pf = var(pm, nw, :pt, (i, transformer["f_bus"], transformer["t_bus"])) @@ -183,3 +279,119 @@ function constraint_mc_transformer_power_on_off(pm::AbstractUnbalancedNFASwitchM JuMP.@constraint(pm.model, pf[fc] == -pt[tc]) end end + +""" +""" +constraint_mc_transformer_power_traditional_on_off(pm::PMD.AbstractUnbalancedNFAModel, i::Int; nw::Int=nw_id_default, fix_taps::Bool=false) = constraint_mc_transformer_power_block_on_off(pm, i; nw=nw, fix_taps=fix_taps) + + +@doc raw""" + constraint_storage_complementarity_mi_block_on_off(pm::Union{PMD.LPUBFDiagModel,PMD.AbstractUnbalancedNFAModel}, n::Int, i::Int, charge_ub::Real, discharge_ub::Real) + +linear storage complementarity mi constraint for block mld problem + +math``` +sc_{on} + sd_{on} == z_{block} +``` +""" +function constraint_storage_complementarity_mi_block_on_off(pm::PMD.AbstractUnbalancedNFAModel, n::Int, i::Int, charge_ub::Real, discharge_ub::Real) + sc = var(pm, n, :sc, i) + sd = var(pm, n, :sd, i) + sc_on = var(pm, n, :sc_on, i) + sd_on = var(pm, n, :sd_on, i) + + z_block = var(pm, n, :z_block, ref(pm, n, :storage_block_map, i)) + + JuMP.@constraint(pm.model, sc_on + sd_on == z_block) + JuMP.@constraint(pm.model, sc_on*charge_ub >= sc) + JuMP.@constraint(pm.model, sd_on*discharge_ub >= sd) +end + + +@doc raw""" + constraint_storage_complementarity_mi_traditional_on_off( + pm::Union{PMD.LPUBFDiagModel,PMD.AbstractUnbalancedNFAModel}, + n::Int, + i::Int, + charge_ub::Real, + discharge_ub::Real + ) + +linear storage complementarity mi constraint for traditional mld problem + +math``` +sc_{on} + sd_{on} == z_{block} +``` +""" +function constraint_storage_complementarity_mi_traditional_on_off(pm::PMD.AbstractUnbalancedNFAModel, n::Int, i::Int, charge_ub::Real, discharge_ub::Real) + sc = var(pm, n, :sc, i) + sd = var(pm, n, :sd, i) + sc_on = var(pm, n, :sc_on, i) + sd_on = var(pm, n, :sd_on, i) + + z_storage = var(pm, n, :z_storage, i) + + JuMP.@constraint(pm.model, sc_on + sd_on == z_storage) + JuMP.@constraint(pm.model, sc_on*charge_ub >= sc) + JuMP.@constraint(pm.model, sd_on*discharge_ub >= sd) +end + + +"nothing to do, no voltage angle variables" +function constraint_mc_inverter_theta_ref(::PMD.AbstractUnbalancedNFAModel, ::Int; nw::Int=nw_id_default) +end + + + +@doc raw""" + constraint_mc_storage_phase_unbalance_grid_following( + pm::AbstractUnbalancedPowerModel, + nw::Int, + i::Int, + connections::Vector{Int}, + unbalance_factor::Real + ) + +Enforces that storage inputs/outputs are (approximately) balanced across each phase, by some `unbalance_factor` on grid-following +inverters only. Requires z_inverter variable. Variant for Active Power Only models. + +```math +S^{strg}_{i,c} \geq S^{strg}_{i,d} - f^{unbal} \left( -d^{on}_i S^{strg}_{i,d} + c^{on}_i S^{strg}_{i,d} \right) \forall c,d \in C +``` +""" +function constraint_mc_storage_phase_unbalance_grid_following(pm::PMD.AbstractUnbalancedActivePowerModel, nw::Int, i::Int, connections::Vector{Int}, unbalance_factor::Real) + z_inverter = var(pm, nw, :z_inverter, (:storage, i)) + + ps = var(pm, nw, :ps, i) + + sc_on = var(pm, nw, :sc_on, i) # ==1 charging (p,q > 0) + sd_on = var(pm, nw, :sd_on, i) # ==1 discharging (p,q < 0) + + sd_on_ps = JuMP.@variable(pm.model, [c in connections], base_name="$(nw)_sd_on_ps_$(i)") + sc_on_ps = JuMP.@variable(pm.model, [c in connections], base_name="$(nw)_sc_on_ps_$(i)") + for c in connections + PolyhedralRelaxations.construct_bilinear_relaxation!(pm.model, sd_on, ps[c], sd_on_ps[c], [0,1], [JuMP.lower_bound(ps[c]), JuMP.upper_bound(ps[c])]) + PolyhedralRelaxations.construct_bilinear_relaxation!(pm.model, sc_on, ps[c], sc_on_ps[c], [0,1], [JuMP.lower_bound(ps[c]), JuMP.upper_bound(ps[c])]) + end + + ps_zinverter = JuMP.@variable(pm.model, [c in connections], base_name="$(nw)_ps_zinverter_$(i)") + for c in connections + PolyhedralRelaxations.construct_bilinear_relaxation!(pm.model, z_inverter, ps[c], ps_zinverter[c], [0,1], [JuMP.lower_bound(ps[c]), JuMP.upper_bound(ps[c])]) + end + + sd_on_ps_zinverter = JuMP.@variable(pm.model, [c in connections], base_name="$(nw)_sd_on_ps_zinverter_$(i)") + sc_on_ps_zinverter = JuMP.@variable(pm.model, [c in connections], base_name="$(nw)_sc_on_ps_zinverter_$(i)") + for c in connections + PolyhedralRelaxations.construct_bilinear_relaxation!(pm.model, z_inverter, sd_on_ps[c], sd_on_ps_zinverter[c], [0,1], [JuMP.lower_bound(ps[c]), JuMP.upper_bound(ps[c])]) + PolyhedralRelaxations.construct_bilinear_relaxation!(pm.model, z_inverter, sc_on_ps[c], sc_on_ps_zinverter[c], [0,1], [JuMP.lower_bound(ps[c]), JuMP.upper_bound(ps[c])]) + end + + for (idx,c) in enumerate(connections) + if idx < length(connections) + for d in connections[idx+1:end] + JuMP.@constraint(pm.model, ps[c]-ps_zinverter[c] >= ps[d] - unbalance_factor*(-1*sd_on_ps[d] + 1*sc_on_ps[d]) - ps_zinverter[d] + unbalance_factor*(-1*sd_on_ps_zinverter[d] + 1*sc_on_ps_zinverter[d])) + JuMP.@constraint(pm.model, ps[c]-ps_zinverter[c] <= ps[d] + unbalance_factor*(-1*sd_on_ps[d] + 1*sc_on_ps[d]) - ps_zinverter[d] - unbalance_factor*(-1*sd_on_ps_zinverter[d] + 1*sc_on_ps_zinverter[d])) + end + end + end +end diff --git a/src/form/bf_mx_lin.jl b/src/form/bf_mx_lin.jl deleted file mode 100644 index dfb05f51..00000000 --- a/src/form/bf_mx_lin.jl +++ /dev/null @@ -1,286 +0,0 @@ -@doc raw""" - constraint_mc_switch_state_on_off(pm::LPUBFSwitchModel, nw::Int, i::Int, f_bus::Int, t_bus::Int, f_connections::Vector{Int}, t_connections::Vector{Int}; relax::Bool=false) - -Linear switch power on/off constraint for LPUBFDiagModel. If `relax`, an [indicator constraint](https://jump.dev/JuMP.jl/stable/manual/constraints/#Indicator-constraints) is used. - -```math -\begin{align} -& w^{fr}_{i,c} - w^{to}_{i,c} \leq \left ( v^u_{i,c} \right )^2 \left ( 1 - z^{sw}_i \right )\ \forall i \in S,\forall c \in C \\ -& w^{fr}_{i,c} - w^{to}_{i,c} \geq -\left ( v^u_{i,c}\right )^2 \left ( 1 - z^{sw}_i \right )\ \forall i \in S,\forall c \in C -\end{align} -``` -""" -function PowerModelsDistribution.constraint_mc_switch_state_on_off(pm::LPUBFSwitchModel, nw::Int, i::Int, f_bus::Int, t_bus::Int, f_connections::Vector{Int}, t_connections::Vector{Int}; relax::Bool=false) - w_fr = var(pm, nw, :w, f_bus) - w_to = var(pm, nw, :w, t_bus) - - f_bus = ref(pm, nw, :bus, f_bus) - t_bus = ref(pm, nw, :bus, t_bus) - - f_vmax = f_bus["vmax"][[findfirst(isequal(c), f_bus["terminals"]) for c in f_connections]] - t_vmax = t_bus["vmax"][[findfirst(isequal(c), t_bus["terminals"]) for c in t_connections]] - - vmax = min.(fill(2.0, length(f_bus["vmax"])), f_vmax, t_vmax) - - z = var(pm, nw, :switch_state, i) - - for (idx, (fc, tc)) in enumerate(zip(f_connections, t_connections)) - if relax - JuMP.@constraint(pm.model, w_fr[fc] - w_to[tc] <= vmax[idx].^2 * (1-z)) - JuMP.@constraint(pm.model, w_fr[fc] - w_to[tc] >= -vmax[idx].^2 * (1-z)) - else - JuMP.@constraint(pm.model, z => {w_fr[fc] == w_to[tc]}) - end - end -end - - -""" - constraint_mc_power_balance_shed(pm::LPUBFSwitchModel, nw::Int, i::Int, - terminals::Vector{Int}, grounded::Vector{Bool}, bus_arcs::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, - bus_arcs_sw::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_trans::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, - bus_gens::Vector{Tuple{Int,Vector{Int}}}, bus_storage::Vector{Tuple{Int,Vector{Int}}}, - bus_loads::Vector{Tuple{Int,Vector{Int}}}, bus_shunts::Vector{Tuple{Int,Vector{Int}}} - ) - -KCL for load shed problem with transformers (LinDistFlow Form) -""" -function PowerModelsDistribution.constraint_mc_power_balance_shed(pm::LPUBFSwitchModel, nw::Int, i::Int, terminals::Vector{Int}, grounded::Vector{Bool}, bus_arcs::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_sw::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_trans::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_gens::Vector{Tuple{Int,Vector{Int}}}, bus_storage::Vector{Tuple{Int,Vector{Int}}}, bus_loads::Vector{Tuple{Int,Vector{Int}}}, bus_shunts::Vector{Tuple{Int,Vector{Int}}}) - w = var(pm, nw, :w, i) - p = get(var(pm, nw), :p, Dict()); PMD._check_var_keys(p, bus_arcs, "active power", "branch") - q = get(var(pm, nw), :q, Dict()); PMD._check_var_keys(q, bus_arcs, "reactive power", "branch") - pg = get(var(pm, nw), :pg, Dict()); PMD._check_var_keys(pg, bus_gens, "active power", "generator") - qg = get(var(pm, nw), :qg, Dict()); PMD._check_var_keys(qg, bus_gens, "reactive power", "generator") - ps = get(var(pm, nw), :ps, Dict()); PMD._check_var_keys(ps, bus_storage, "active power", "storage") - qs = get(var(pm, nw), :qs, Dict()); PMD._check_var_keys(qs, bus_storage, "reactive power", "storage") - psw = get(var(pm, nw), :psw, Dict()); PMD._check_var_keys(psw, bus_arcs_sw, "active power", "switch") - qsw = get(var(pm, nw), :qsw, Dict()); PMD._check_var_keys(qsw, bus_arcs_sw, "reactive power", "switch") - pt = get(var(pm, nw), :pt, Dict()); PMD._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") - qt = get(var(pm, nw), :qt, Dict()); PMD._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") - pd = get(var(pm, nw), :pd_bus, Dict()); PMD._check_var_keys(pd, bus_loads, "active power", "load") - qd = get(var(pm, nw), :qd_bus, Dict()); PMD._check_var_keys(qd, bus_loads, "reactive power", "load") - z_block = var(pm, nw, :z_block, ref(pm, nw, :bus_block_map, i)) - - uncontrolled_shunts = Tuple{Int,Vector{Int}}[] - controlled_shunts = Tuple{Int,Vector{Int}}[] - - if !isempty(bus_shunts) && any(haskey(ref(pm, nw, :shunt, sh), "controls") for (sh, conns) in bus_shunts) - for (sh, conns) in bus_shunts - if haskey(ref(pm, nw, :shunt, sh), "controls") - push!(controlled_shunts, (sh,conns)) - else - push!(uncontrolled_shunts, (sh, conns)) - end - end - else - uncontrolled_shunts = bus_shunts - end - - Gt, _ = PMD._build_bus_shunt_matrices(pm, nw, terminals, bus_shunts) - _, Bt = PMD._build_bus_shunt_matrices(pm, nw, terminals, uncontrolled_shunts) - - cstr_p = [] - cstr_q = [] - - ungrounded_terminals = [(idx,t) for (idx,t) in enumerate(terminals) if !grounded[idx]] - - pd_zblock = Dict{Int,JuMP.Containers.DenseAxisArray{JuMP.VariableRef}}(l => JuMP.@variable(pm.model, [c in conns], base_name="$(nw)_pd_zblock_$(l)") for (l,conns) in bus_loads) - qd_zblock = Dict{Int,JuMP.Containers.DenseAxisArray{JuMP.VariableRef}}(l => JuMP.@variable(pm.model, [c in conns], base_name="$(nw)_qd_zblock_$(l)") for (l,conns) in bus_loads) - - for (l,conns) in bus_loads - for c in conns - _IM.relaxation_product(pm.model, pd[l][c], z_block, pd_zblock[l][c]) - _IM.relaxation_product(pm.model, qd[l][c], z_block, qd_zblock[l][c]) - end - end - - for (idx, t) in ungrounded_terminals - cp = JuMP.@constraint(pm.model, - sum(p[a][t] for (a, conns) in bus_arcs if t in conns) - + sum(psw[a_sw][t] for (a_sw, conns) in bus_arcs_sw if t in conns) - + sum(pt[a_trans][t] for (a_trans, conns) in bus_arcs_trans if t in conns) - == - sum(pg[g][t] for (g, conns) in bus_gens if t in conns) - - sum(ps[s][t] for (s, conns) in bus_storage if t in conns) - - sum(pd_zblock[l][t] for (l, conns) in bus_loads if t in conns) - - sum((w[t] * LinearAlgebra.diag(Gt')[idx]) for (sh, conns) in bus_shunts if t in conns) - ) - push!(cstr_p, cp) - - for (sh, sh_conns) in controlled_shunts - if t in sh_conns - cq_cap = var(pm, nw, :capacitor_reactive_power, sh)[t] - cap_state = var(pm, nw, :capacitor_state, sh)[t] - bs = LinearAlgebra.diag(ref(pm, nw, :shunt, sh, "bs"))[findfirst(isequal(t), sh_conns)] - w_lb, w_ub = _IM.variable_domain(w[t]) - - # tie to z_block - JuMP.@constraint(pm.model, cap_state <= z_block) - - # McCormick envelope constraints - JuMP.@constraint(pm.model, cq_cap ≥ bs*cap_state*w_lb) - JuMP.@constraint(pm.model, cq_cap ≥ bs*w[t] + bs*cap_state*w_ub - bs*w_ub*z_block) - JuMP.@constraint(pm.model, cq_cap ≤ bs*cap_state*w_ub) - JuMP.@constraint(pm.model, cq_cap ≤ bs*w[t] + bs*cap_state*w_lb - bs*w_lb*z_block) - end - end - - cq = JuMP.@constraint(pm.model, - sum(q[a][t] for (a, conns) in bus_arcs if t in conns) - + sum(qsw[a_sw][t] for (a_sw, conns) in bus_arcs_sw if t in conns) - + sum(qt[a_trans][t] for (a_trans, conns) in bus_arcs_trans if t in conns) - == - sum(qg[g][t] for (g, conns) in bus_gens if t in conns) - - sum(qs[s][t] for (s, conns) in bus_storage if t in conns) - - sum(qd_zblock[l][t] for (l, conns) in bus_loads if t in conns) - - sum((-w[t] * LinearAlgebra.diag(Bt')[idx]) for (sh, conns) in uncontrolled_shunts if t in conns) - - sum(-var(pm, nw, :capacitor_reactive_power, sh)[t] for (sh, conns) in controlled_shunts if t in conns) - ) - push!(cstr_q, cq) - end - - PMD.con(pm, nw, :lam_kcl_r)[i] = cstr_p - PMD.con(pm, nw, :lam_kcl_i)[i] = cstr_q - - if _IM.report_duals(pm) - sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p - sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q - end -end - - -""" - constraint_mc_bus_voltage_magnitude_sqr_on_off(pm::LPUBFSwitchModel, nw::Int, i::Int, vmin::Vector{<:Real}, vmax::Vector{<:Real}) - -on/off bus voltage magnitude squared constraint for relaxed formulations -""" -function PowerModelsDistribution.constraint_mc_bus_voltage_magnitude_sqr_on_off(pm::LPUBFSwitchModel, nw::Int, i::Int, vmin::Vector{<:Real}, vmax::Vector{<:Real}) - w = var(pm, nw, :w, i) - z_block = var(pm, nw, :z_block, ref(pm, nw, :bus_block_map, i)) - - terminals = ref(pm, nw, :bus, i)["terminals"] - grounded = ref(pm, nw, :bus, i)["grounded"] - - for (idx,t) in [(idx,t) for (idx,t) in enumerate(terminals) if !grounded[idx]] - if isfinite(vmax[idx]) - JuMP.@constraint(pm.model, w[t] <= vmax[idx]^2*z_block) - end - - if isfinite(vmin[idx]) - JuMP.@constraint(pm.model, w[t] >= vmin[idx]^2*z_block) - end - end -end - - -""" - constraint_mc_transformer_power_yy_on_off( - pm::LPUBFSwitchModel, - nw::Int, - trans_id::Int, - f_bus::Int, - t_bus::Int, - f_idx::Tuple{Int,Int,Int}, - t_idx::Tuple{Int,Int,Int}, - f_connections::Vector{Int}, - t_connections::Vector{Int}, - pol::Int, - tm_set::Vector{<:Real}, - tm_fixed::Vector{Bool}, - tm_scale::Real - ) - -Links to and from power and voltages in a wye-wye transformer, assumes tm_fixed is true - -```math -w_fr_i=(pol_i*tm_scale*tm_i)^2w_to_i -``` -""" -function constraint_mc_transformer_power_yy_on_off(pm::LPUBFSwitchModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx::Tuple{Int,Int,Int}, t_idx::Tuple{Int,Int,Int}, f_connections::Vector{Int}, t_connections::Vector{Int}, pol::Int, tm_set::Vector{<:Real}, tm_fixed::Vector{Bool}, tm_scale::Real) - z_block = var(pm, nw, :z_block, ref(pm, nw, :bus_block_map, f_bus)) - - tm = [tm_fixed[idx] ? tm_set[idx] : var(pm, nw, :tap, trans_id)[idx] for (idx,(fc,tc)) in enumerate(zip(f_connections,t_connections))] - transformer = ref(pm, nw, :transformer, trans_id) - - p_fr = [var(pm, nw, :pt, f_idx)[p] for p in f_connections] - p_to = [var(pm, nw, :pt, t_idx)[p] for p in t_connections] - q_fr = [var(pm, nw, :qt, f_idx)[p] for p in f_connections] - q_to = [var(pm, nw, :qt, t_idx)[p] for p in t_connections] - - w_fr = var(pm, nw, :w)[f_bus] - w_to = var(pm, nw, :w)[t_bus] - - tmsqr = [tm_fixed[i] ? tm[i]^2 : JuMP.@variable(pm.model, base_name="$(nw)_tmsqr_$(trans_id)_$(f_connections[i])", start=JuMP.start_value(tm[i])^2, lower_bound=JuMP.has_lower_bound(tm[i]) ? JuMP.lower_bound(tm[i])^2 : 0.9^2, upper_bound=JuMP.has_upper_bound(tm[i]) ? JuMP.upper_bound(tm[i])^2 : 1.1^2) for i in 1:length(tm)] - - for (idx, (fc, tc)) in enumerate(zip(f_connections, t_connections)) - if tm_fixed[idx] - JuMP.@constraint(pm.model, w_fr[fc] == (pol*tm_scale*tm[idx])^2*w_to[tc]) - else - PMD.PolyhedralRelaxations.construct_univariate_relaxation!(pm.model, x->x^2, tm[idx], tmsqr[idx], [JuMP.has_lower_bound(tm[idx]) ? JuMP.lower_bound(tm[idx]) : 0.9, JuMP.has_upper_bound(tm[idx]) ? JuMP.upper_bound(tm[idx]) : 1.1], false) - - tmsqr_w_to = JuMP.@variable(pm.model, base_name="$(nw)_tmsqr_w_to_$(trans_id)_$(t_bus)_$(tc)") - PMD.PolyhedralRelaxations.construct_bilinear_relaxation!(pm.model, tmsqr[idx], w_to[tc], tmsqr_w_to, [JuMP.lower_bound(tmsqr[idx]), JuMP.upper_bound(tmsqr[idx])], [JuMP.has_lower_bound(w_to[tc]) ? JuMP.lower_bound(w_to[tc]) : 0.0, JuMP.has_upper_bound(w_to[tc]) ? JuMP.upper_bound(w_to[tc]) : 1.1^2]) - - JuMP.@constraint(pm.model, w_fr[fc] == (pol*tm_scale)^2*tmsqr_w_to) - - # with regcontrol - if haskey(transformer,"controls") - # TODO: fix LPUBFDiag version of transformer controls for on_off - # v_ref = transformer["controls"]["vreg"][idx] - # δ = transformer["controls"]["band"][idx] - # r = transformer["controls"]["r"][idx] - # x = transformer["controls"]["x"][idx] - - # # linearized voltage squared: w_drop = (2⋅r⋅p+2⋅x⋅q) - # w_drop = JuMP.@expression(pm.model, 2*r*p_to[idx] + 2*x*q_to[idx]) - - # # (v_ref-δ)^2 ≤ w_fr-w_drop ≤ (v_ref+δ)^2 - # # w_fr/1.1^2 ≤ w_to ≤ w_fr/0.9^2 - # w_drop_z_block = JuMP.@variable(pm.model, base_name="$(nw)_w_drop_z_block_$(trans_id)_$(idx)") - - # _IM.relaxation_product(pm.model, w_drop, z_block, w_drop_z_block; default_x_domain=(0.9^2, 1.1^2), default_y_domain=(0, 1)) - - # w_fr_lb = JuMP.has_lower_bound(w_fr[fc]) && JuMP.lower_bound(w_fr[fc]) > 0 ? JuMP.lower_bound(w_fr[fc]) : 0.9^2 - # w_fr_ub = JuMP.has_upper_bound(w_fr[fc]) && isfinite(JuMP.upper_bound(w_fr[fc])) ? JuMP.upper_bound(w_fr[fc]) : 1.1^2 - - # JuMP.@constraint(pm.model, w_fr[fc] ≥ z_block * (v_ref - δ)^2 + w_drop_z_block) - # JuMP.@constraint(pm.model, w_fr[fc] ≤ z_block * (v_ref + δ)^2 + w_drop_z_block) - # JuMP.@constraint(pm.model, w_fr[fc]/w_fr_ub ≤ w_to[tc]) - # JuMP.@constraint(pm.model, w_fr[fc]/w_fr_lb ≥ w_to[tc]) - end - end - end - - JuMP.@constraint(pm.model, p_fr + p_to .== 0) - JuMP.@constraint(pm.model, q_fr + q_to .== 0) -end - - -""" - constraint_mc_storage_losses_on_off(pm::LPUBFSwitchModel, i::Int; nw::Int=nw_id_default) - -Neglects the active and reactive loss terms associated with the squared current magnitude. -""" -function constraint_mc_storage_losses_on_off(pm::LPUBFSwitchModel, i::Int; nw::Int=nw_id_default) - storage = ref(pm, nw, :storage, i) - z_block = var(pm, nw, :z_block, ref(pm, nw, :storage_block_map, i)) - - p_loss, q_loss = storage["p_loss"], storage["q_loss"] - conductors = storage["connections"] - - ps = var(pm, nw, :ps, i) - qs = var(pm, nw, :qs, i) - sc = var(pm, nw, :sc, i) - sd = var(pm, nw, :sd, i) - qsc = var(pm, nw, :qsc, i) - - qsc_zblock = JuMP.@variable(pm.model, base_name="$(nw)_qd_zblock_$(i)") - - JuMP.@constraint(pm.model, qsc_zblock >= JuMP.lower_bound(qsc) * z_block) - JuMP.@constraint(pm.model, qsc_zblock >= JuMP.upper_bound(qsc) * z_block + qsc - JuMP.upper_bound(qsc)) - JuMP.@constraint(pm.model, qsc_zblock <= JuMP.upper_bound(qsc) * z_block) - JuMP.@constraint(pm.model, qsc_zblock <= qsc + JuMP.lower_bound(qsc) * z_block - JuMP.lower_bound(qsc)) - - JuMP.@constraint(pm.model, sum(ps[c] for c in conductors) + (sd - sc) == p_loss * z_block) - JuMP.@constraint(pm.model, sum(qs[c] for c in conductors) == qsc_zblock + q_loss * z_block) -end diff --git a/src/form/lindistflow.jl b/src/form/lindistflow.jl new file mode 100644 index 00000000..429bb1ad --- /dev/null +++ b/src/form/lindistflow.jl @@ -0,0 +1,540 @@ +@doc raw""" + constraint_mc_switch_voltage_open_close(pm::PMD.LPUBFDiagModel, nw::Int, i::Int, f_bus::Int, t_bus::Int, f_connections::Vector{Int}, t_connections::Vector{Int}) + +Linear switch power on/off constraint for LPUBFDiagModel. + +```math +\begin{align} +& w^{fr}_{i,c} - w^{to}_{i,c} \leq \left ( v^u_{i,c} \right )^2 \left ( 1 - z^{sw}_i \right )\ \forall i \in S,\forall c \in C \\ +& w^{fr}_{i,c} - w^{to}_{i,c} \geq -\left ( v^u_{i,c}\right )^2 \left ( 1 - z^{sw}_i \right )\ \forall i \in S,\forall c \in C +\end{align} +``` +""" +function constraint_mc_switch_voltage_open_close(pm::PMD.LPUBFDiagModel, nw::Int, i::Int, f_bus::Int, t_bus::Int, f_connections::Vector{Int}, t_connections::Vector{Int}) + w_fr = var(pm, nw, :w, f_bus) + w_to = var(pm, nw, :w, t_bus) + + f_bus = ref(pm, nw, :bus, f_bus) + t_bus = ref(pm, nw, :bus, t_bus) + + f_vmax = f_bus["vmax"][[findfirst(isequal(c), f_bus["terminals"]) for c in f_connections]] + t_vmax = t_bus["vmax"][[findfirst(isequal(c), t_bus["terminals"]) for c in t_connections]] + + vmax = min.(fill(2.0, length(f_bus["vmax"])), f_vmax, t_vmax) + + z = var(pm, nw, :switch_state, i) + + for (idx, (fc, tc)) in enumerate(zip(f_connections, t_connections)) + JuMP.@constraint(pm.model, w_fr[fc] - w_to[tc] <= vmax[idx].^2 * (1-z)) + JuMP.@constraint(pm.model, w_fr[fc] - w_to[tc] >= -vmax[idx].^2 * (1-z)) + + # Indicator constraint version, for reference + # JuMP.@constraint(pm.model, z => {w_fr[fc] == w_to[tc]}) + end +end + + +""" + constraint_mc_power_balance_shed_block(pm::PMD.LPUBFDiagModel, nw::Int, i::Int, + terminals::Vector{Int}, grounded::Vector{Bool}, bus_arcs::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, + bus_arcs_sw::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_trans::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, + bus_gens::Vector{Tuple{Int,Vector{Int}}}, bus_storage::Vector{Tuple{Int,Vector{Int}}}, + bus_loads::Vector{Tuple{Int,Vector{Int}}}, bus_shunts::Vector{Tuple{Int,Vector{Int}}} + ) + +KCL for block load shed problem with transformers (LinDistFlow Form) +""" +function constraint_mc_power_balance_shed_block(pm::PMD.LPUBFDiagModel, nw::Int, i::Int, terminals::Vector{Int}, grounded::Vector{Bool}, bus_arcs::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_sw::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_trans::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_gens::Vector{Tuple{Int,Vector{Int}}}, bus_storage::Vector{Tuple{Int,Vector{Int}}}, bus_loads::Vector{Tuple{Int,Vector{Int}}}, bus_shunts::Vector{Tuple{Int,Vector{Int}}}) + w = var(pm, nw, :w, i) + p = get(var(pm, nw), :p, Dict()); PMD._check_var_keys(p, bus_arcs, "active power", "branch") + q = get(var(pm, nw), :q, Dict()); PMD._check_var_keys(q, bus_arcs, "reactive power", "branch") + pg = get(var(pm, nw), :pg, Dict()); PMD._check_var_keys(pg, bus_gens, "active power", "generator") + qg = get(var(pm, nw), :qg, Dict()); PMD._check_var_keys(qg, bus_gens, "reactive power", "generator") + ps = get(var(pm, nw), :ps, Dict()); PMD._check_var_keys(ps, bus_storage, "active power", "storage") + qs = get(var(pm, nw), :qs, Dict()); PMD._check_var_keys(qs, bus_storage, "reactive power", "storage") + psw = get(var(pm, nw), :psw, Dict()); PMD._check_var_keys(psw, bus_arcs_sw, "active power", "switch") + qsw = get(var(pm, nw), :qsw, Dict()); PMD._check_var_keys(qsw, bus_arcs_sw, "reactive power", "switch") + pt = get(var(pm, nw), :pt, Dict()); PMD._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") + qt = get(var(pm, nw), :qt, Dict()); PMD._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") + pd = get(var(pm, nw), :pd_bus, Dict()); PMD._check_var_keys(pd, bus_loads, "active power", "load") + qd = get(var(pm, nw), :qd_bus, Dict()); PMD._check_var_keys(qd, bus_loads, "reactive power", "load") + z_block = var(pm, nw, :z_block, ref(pm, nw, :bus_block_map, i)) + + uncontrolled_shunts = Tuple{Int,Vector{Int}}[] + controlled_shunts = Tuple{Int,Vector{Int}}[] + + if !isempty(bus_shunts) && any(haskey(ref(pm, nw, :shunt, sh), "controls") for (sh, conns) in bus_shunts) + for (sh, conns) in bus_shunts + if haskey(ref(pm, nw, :shunt, sh), "controls") + push!(controlled_shunts, (sh,conns)) + else + push!(uncontrolled_shunts, (sh, conns)) + end + end + else + uncontrolled_shunts = bus_shunts + end + + Gt, _ = PMD._build_bus_shunt_matrices(pm, nw, terminals, bus_shunts) + _, Bt = PMD._build_bus_shunt_matrices(pm, nw, terminals, uncontrolled_shunts) + + cstr_p = [] + cstr_q = [] + + ungrounded_terminals = [(idx,t) for (idx,t) in enumerate(terminals) if !grounded[idx]] + + pd_zblock = Dict{Int,JuMP.Containers.DenseAxisArray{JuMP.VariableRef}}(l => JuMP.@variable(pm.model, [c in conns], base_name="$(nw)_pd_zblock_$(l)") for (l,conns) in bus_loads) + qd_zblock = Dict{Int,JuMP.Containers.DenseAxisArray{JuMP.VariableRef}}(l => JuMP.@variable(pm.model, [c in conns], base_name="$(nw)_qd_zblock_$(l)") for (l,conns) in bus_loads) + + for (l,conns) in bus_loads + for c in conns + IM.relaxation_product(pm.model, pd[l][c], z_block, pd_zblock[l][c]) + IM.relaxation_product(pm.model, qd[l][c], z_block, qd_zblock[l][c]) + end + end + + for (idx, t) in ungrounded_terminals + cp = JuMP.@constraint(pm.model, + sum(p[a][t] for (a, conns) in bus_arcs if t in conns) + + sum(psw[a_sw][t] for (a_sw, conns) in bus_arcs_sw if t in conns) + + sum(pt[a_trans][t] for (a_trans, conns) in bus_arcs_trans if t in conns) + == + sum(pg[g][t] for (g, conns) in bus_gens if t in conns) + - sum(ps[s][t] for (s, conns) in bus_storage if t in conns) + - sum(pd_zblock[l][t] for (l, conns) in bus_loads if t in conns) + - sum((w[t] * LinearAlgebra.diag(Gt')[idx]) for (sh, conns) in bus_shunts if t in conns) + ) + push!(cstr_p, cp) + + for (sh, sh_conns) in controlled_shunts + if t in sh_conns + cq_cap = var(pm, nw, :capacitor_reactive_power, sh)[t] + cap_state = var(pm, nw, :capacitor_state, sh)[t] + bs = LinearAlgebra.diag(ref(pm, nw, :shunt, sh, "bs"))[findfirst(isequal(t), sh_conns)] + w_lb, w_ub = IM.variable_domain(w[t]) + + # tie to z_block + JuMP.@constraint(pm.model, cap_state <= z_block) + + # McCormick envelope constraints + JuMP.@constraint(pm.model, cq_cap ≥ bs*cap_state*w_lb) + JuMP.@constraint(pm.model, cq_cap ≥ bs*w[t] + bs*cap_state*w_ub - bs*w_ub*z_block) + JuMP.@constraint(pm.model, cq_cap ≤ bs*cap_state*w_ub) + JuMP.@constraint(pm.model, cq_cap ≤ bs*w[t] + bs*cap_state*w_lb - bs*w_lb*z_block) + end + end + + cq = JuMP.@constraint(pm.model, + sum(q[a][t] for (a, conns) in bus_arcs if t in conns) + + sum(qsw[a_sw][t] for (a_sw, conns) in bus_arcs_sw if t in conns) + + sum(qt[a_trans][t] for (a_trans, conns) in bus_arcs_trans if t in conns) + == + sum(qg[g][t] for (g, conns) in bus_gens if t in conns) + - sum(qs[s][t] for (s, conns) in bus_storage if t in conns) + - sum(qd_zblock[l][t] for (l, conns) in bus_loads if t in conns) + - sum((-w[t] * LinearAlgebra.diag(Bt')[idx]) for (sh, conns) in uncontrolled_shunts if t in conns) + - sum(-var(pm, nw, :capacitor_reactive_power, sh)[t] for (sh, conns) in controlled_shunts if t in conns) + ) + push!(cstr_q, cq) + end + + PMD.con(pm, nw, :lam_kcl_r)[i] = cstr_p + PMD.con(pm, nw, :lam_kcl_i)[i] = cstr_q + + if IM.report_duals(pm) + sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p + sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q + end +end + + +""" + constraint_mc_power_balance_shed_traditional(pm::PMD.LPUBFDiagModel, nw::Int, i::Int, terminals::Vector{Int}, grounded::Vector{Bool}, bus_arcs::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_sw::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_trans::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_gens::Vector{Tuple{Int,Vector{Int}}}, bus_storage::Vector{Tuple{Int,Vector{Int}}}, bus_loads::Vector{Tuple{Int,Vector{Int}}}, bus_shunts::Vector{Tuple{Int,Vector{Int}}}) + +KCL for traditional load shed problem with transformers (LinDistFlow Form) +""" +function constraint_mc_power_balance_shed_traditional(pm::PMD.LPUBFDiagModel, nw::Int, i::Int, terminals::Vector{Int}, grounded::Vector{Bool}, bus_arcs::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_sw::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_arcs_trans::Vector{Tuple{Tuple{Int,Int,Int},Vector{Int}}}, bus_gens::Vector{Tuple{Int,Vector{Int}}}, bus_storage::Vector{Tuple{Int,Vector{Int}}}, bus_loads::Vector{Tuple{Int,Vector{Int}}}, bus_shunts::Vector{Tuple{Int,Vector{Int}}}) + w = var(pm, nw, :w, i) + p = get(var(pm, nw), :p, Dict()); PMD._check_var_keys(p, bus_arcs, "active power", "branch") + q = get(var(pm, nw), :q, Dict()); PMD._check_var_keys(q, bus_arcs, "reactive power", "branch") + pg = get(var(pm, nw), :pg, Dict()); PMD._check_var_keys(pg, bus_gens, "active power", "generator") + qg = get(var(pm, nw), :qg, Dict()); PMD._check_var_keys(qg, bus_gens, "reactive power", "generator") + ps = get(var(pm, nw), :ps, Dict()); PMD._check_var_keys(ps, bus_storage, "active power", "storage") + qs = get(var(pm, nw), :qs, Dict()); PMD._check_var_keys(qs, bus_storage, "reactive power", "storage") + psw = get(var(pm, nw), :psw, Dict()); PMD._check_var_keys(psw, bus_arcs_sw, "active power", "switch") + qsw = get(var(pm, nw), :qsw, Dict()); PMD._check_var_keys(qsw, bus_arcs_sw, "reactive power", "switch") + pt = get(var(pm, nw), :pt, Dict()); PMD._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") + qt = get(var(pm, nw), :qt, Dict()); PMD._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") + pd = get(var(pm, nw), :pd_bus, Dict()); PMD._check_var_keys(pd, bus_loads, "active power", "load") + qd = get(var(pm, nw), :qd_bus, Dict()); PMD._check_var_keys(qd, bus_loads, "reactive power", "load") + + z_voltage= var(pm, nw, :z_voltage, i) + z_demand = Dict(l => var(pm, nw, :z_demand, l) for (l,_) in bus_loads) + + uncontrolled_shunts = Tuple{Int,Vector{Int}}[] + controlled_shunts = Tuple{Int,Vector{Int}}[] + + if !isempty(bus_shunts) && any(haskey(ref(pm, nw, :shunt, sh), "controls") for (sh, conns) in bus_shunts) + for (sh, conns) in bus_shunts + if haskey(ref(pm, nw, :shunt, sh), "controls") + push!(controlled_shunts, (sh,conns)) + else + push!(uncontrolled_shunts, (sh, conns)) + end + end + else + uncontrolled_shunts = bus_shunts + end + + Gt, _ = PMD._build_bus_shunt_matrices(pm, nw, terminals, bus_shunts) + _, Bt = PMD._build_bus_shunt_matrices(pm, nw, terminals, uncontrolled_shunts) + + cstr_p = [] + cstr_q = [] + + ungrounded_terminals = [(idx,t) for (idx,t) in enumerate(terminals) if !grounded[idx]] + + pd_zdemand = Dict{Int,JuMP.Containers.DenseAxisArray{JuMP.VariableRef}}(l => JuMP.@variable(pm.model, [c in conns], base_name="$(nw)_pd_zdemand_$(l)") for (l,conns) in bus_loads) + qd_zdemand = Dict{Int,JuMP.Containers.DenseAxisArray{JuMP.VariableRef}}(l => JuMP.@variable(pm.model, [c in conns], base_name="$(nw)_qd_zdemand_$(l)") for (l,conns) in bus_loads) + + for (l,conns) in bus_loads + for c in conns + IM.relaxation_product(pm.model, pd[l][c], z_demand[l], pd_zdemand[l][c]) + IM.relaxation_product(pm.model, qd[l][c], z_demand[l], qd_zdemand[l][c]) + end + end + + for (idx, t) in ungrounded_terminals + cp = JuMP.@constraint(pm.model, + sum(p[a][t] for (a, conns) in bus_arcs if t in conns) + + sum(psw[a_sw][t] for (a_sw, conns) in bus_arcs_sw if t in conns) + + sum(pt[a_trans][t] for (a_trans, conns) in bus_arcs_trans if t in conns) + == + sum(pg[g][t] for (g, conns) in bus_gens if t in conns) + - sum(ps[s][t] for (s, conns) in bus_storage if t in conns) + - sum(pd_zdemand[l][t] for (l, conns) in bus_loads if t in conns) + - sum((w[t] * LinearAlgebra.diag(Gt')[idx]) for (sh, conns) in bus_shunts if t in conns) + ) + push!(cstr_p, cp) + + for (sh, sh_conns) in controlled_shunts + if t in sh_conns + cq_cap = var(pm, nw, :capacitor_reactive_power, sh)[t] + cap_state = var(pm, nw, :capacitor_state, sh)[t] + bs = PMD.diag(ref(pm, nw, :shunt, sh, "bs"))[findfirst(isequal(t), sh_conns)] + w_lb, w_ub = IM.variable_domain(w[t]) + + # tie to z_voltage + JuMP.@constraint(pm.model, cap_state <= z_voltage) + + # McCormick envelope constraints + JuMP.@constraint(pm.model, cq_cap ≥ bs*cap_state*w_lb) + JuMP.@constraint(pm.model, cq_cap ≥ bs*w[t] + bs*cap_state*w_ub - bs*w_ub*z_voltage) + JuMP.@constraint(pm.model, cq_cap ≤ bs*cap_state*w_ub) + JuMP.@constraint(pm.model, cq_cap ≤ bs*w[t] + bs*cap_state*w_lb - bs*w_lb*z_voltage) + end + end + + cq = JuMP.@constraint(pm.model, + sum(q[a][t] for (a, conns) in bus_arcs if t in conns) + + sum(qsw[a_sw][t] for (a_sw, conns) in bus_arcs_sw if t in conns) + + sum(qt[a_trans][t] for (a_trans, conns) in bus_arcs_trans if t in conns) + == + sum(qg[g][t] for (g, conns) in bus_gens if t in conns) + - sum(qs[s][t] for (s, conns) in bus_storage if t in conns) + - sum(qd_zdemand[l][t] for (l, conns) in bus_loads if t in conns) + - sum((-w[t] * LinearAlgebra.diag(Bt')[idx]) for (sh, conns) in uncontrolled_shunts if t in conns) + - sum(-var(pm, nw, :capacitor_reactive_power, sh)[t] for (sh, conns) in controlled_shunts if t in conns) + ) + push!(cstr_q, cq) + end + + PMD.con(pm, nw, :lam_kcl_r)[i] = cstr_p + PMD.con(pm, nw, :lam_kcl_i)[i] = cstr_q + + if IM.report_duals(pm) + sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p + sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q + end +end + + +@doc raw""" + constraint_mc_transformer_power_yy_block_on_off( + pm::PMD.LPUBFDiagModel, + nw::Int, + trans_id::Int, + f_bus::Int, + t_bus::Int, + f_idx::Tuple{Int,Int,Int}, + t_idx::Tuple{Int,Int,Int}, + f_connections::Vector{Int}, + t_connections::Vector{Int}, + pol::Int, + tm_set::Vector{<:Real}, + tm_fixed::Vector{Bool}, + tm_scale::Real + ) + +Links to and from power and voltages in a wye-wye transformer, assumes tm_fixed is true + +```math +w_fr_i=(pol_i*tm_scale*tm_i)^2w_to_i +``` +""" +function constraint_mc_transformer_power_yy_block_on_off(pm::PMD.LPUBFDiagModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx::Tuple{Int,Int,Int}, t_idx::Tuple{Int,Int,Int}, f_connections::Vector{Int}, t_connections::Vector{Int}, pol::Int, tm_set::Vector{<:Real}, tm_fixed::Vector{Bool}, tm_scale::Real) + z_block = var(pm, nw, :z_block, ref(pm, nw, :bus_block_map, f_bus)) + + tm = [tm_fixed[idx] ? tm_set[idx] : var(pm, nw, :tap, trans_id)[idx] for (idx,(fc,tc)) in enumerate(zip(f_connections,t_connections))] + transformer = ref(pm, nw, :transformer, trans_id) + + p_fr = [var(pm, nw, :pt, f_idx)[p] for p in f_connections] + p_to = [var(pm, nw, :pt, t_idx)[p] for p in t_connections] + q_fr = [var(pm, nw, :qt, f_idx)[p] for p in f_connections] + q_to = [var(pm, nw, :qt, t_idx)[p] for p in t_connections] + + w_fr = var(pm, nw, :w)[f_bus] + w_to = var(pm, nw, :w)[t_bus] + + tmsqr = [tm_fixed[i] ? tm[i]^2 : JuMP.@variable(pm.model, base_name="$(nw)_tmsqr_$(trans_id)_$(f_connections[i])", start=JuMP.start_value(tm[i])^2, lower_bound=JuMP.has_lower_bound(tm[i]) ? JuMP.lower_bound(tm[i])^2 : 0.9^2, upper_bound=JuMP.has_upper_bound(tm[i]) ? JuMP.upper_bound(tm[i])^2 : 1.1^2) for i in 1:length(tm)] + + for (idx, (fc, tc)) in enumerate(zip(f_connections, t_connections)) + if tm_fixed[idx] + JuMP.@constraint(pm.model, w_fr[fc] == (pol*tm_scale*tm[idx])^2*w_to[tc]) + else + PMD.PolyhedralRelaxations.construct_univariate_relaxation!(pm.model, x->x^2, tm[idx], tmsqr[idx], [JuMP.has_lower_bound(tm[idx]) ? JuMP.lower_bound(tm[idx]) : 0.9, JuMP.has_upper_bound(tm[idx]) ? JuMP.upper_bound(tm[idx]) : 1.1], false) + + tmsqr_w_to = JuMP.@variable(pm.model, base_name="$(nw)_tmsqr_w_to_$(trans_id)_$(t_bus)_$(tc)") + PMD.PolyhedralRelaxations.construct_bilinear_relaxation!(pm.model, tmsqr[idx], w_to[tc], tmsqr_w_to, [JuMP.lower_bound(tmsqr[idx]), JuMP.upper_bound(tmsqr[idx])], [JuMP.has_lower_bound(w_to[tc]) ? JuMP.lower_bound(w_to[tc]) : 0.0, JuMP.has_upper_bound(w_to[tc]) ? JuMP.upper_bound(w_to[tc]) : 1.1^2]) + + JuMP.@constraint(pm.model, w_fr[fc] == (pol*tm_scale)^2*tmsqr_w_to) + + # with regcontrol + if haskey(transformer,"controls") + # TODO: fix LPUBFDiag version of transformer controls for on_off + # v_ref = transformer["controls"]["vreg"][idx] + # δ = transformer["controls"]["band"][idx] + # r = transformer["controls"]["r"][idx] + # x = transformer["controls"]["x"][idx] + + # # linearized voltage squared: w_drop = (2⋅r⋅p+2⋅x⋅q) + # w_drop = JuMP.@expression(pm.model, 2*r*p_to[idx] + 2*x*q_to[idx]) + + # # (v_ref-δ)^2 ≤ w_fr-w_drop ≤ (v_ref+δ)^2 + # # w_fr/1.1^2 ≤ w_to ≤ w_fr/0.9^2 + # w_drop_z_block = JuMP.@variable(pm.model, base_name="$(nw)_w_drop_z_block_$(trans_id)_$(idx)") + + # IM.relaxation_product(pm.model, w_drop, z_block, w_drop_z_block; default_x_domain=(0.9^2, 1.1^2), default_y_domain=(0, 1)) + + # w_fr_lb = JuMP.has_lower_bound(w_fr[fc]) && JuMP.lower_bound(w_fr[fc]) > 0 ? JuMP.lower_bound(w_fr[fc]) : 0.9^2 + # w_fr_ub = JuMP.has_upper_bound(w_fr[fc]) && isfinite(JuMP.upper_bound(w_fr[fc])) ? JuMP.upper_bound(w_fr[fc]) : 1.1^2 + + # JuMP.@constraint(pm.model, w_fr[fc] ≥ z_block * (v_ref - δ)^2 + w_drop_z_block) + # JuMP.@constraint(pm.model, w_fr[fc] ≤ z_block * (v_ref + δ)^2 + w_drop_z_block) + # JuMP.@constraint(pm.model, w_fr[fc]/w_fr_ub ≤ w_to[tc]) + # JuMP.@constraint(pm.model, w_fr[fc]/w_fr_lb ≥ w_to[tc]) + end + end + end + + JuMP.@constraint(pm.model, p_fr + p_to .== 0) + JuMP.@constraint(pm.model, q_fr + q_to .== 0) +end + + +@doc raw""" + constraint_mc_transformer_power_yy_traditional_on_off(pm::PMD.LPUBFDiagModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx::Tuple{Int,Int,Int}, t_idx::Tuple{Int,Int,Int}, f_connections::Vector{Int}, t_connections::Vector{Int}, pol::Int, tm_set::Vector{<:Real}, tm_fixed::Vector{Bool}, tm_scale::Real) + + Links to and from power and voltages in a wye-wye transformer, assumes tm_fixed is true + +```math +w_fr_i=(pol_i*tm_scale*tm_i)^2w_to_i +``` +""" +function constraint_mc_transformer_power_yy_traditional_on_off(pm::PMD.LPUBFDiagModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx::Tuple{Int,Int,Int}, t_idx::Tuple{Int,Int,Int}, f_connections::Vector{Int}, t_connections::Vector{Int}, pol::Int, tm_set::Vector{<:Real}, tm_fixed::Vector{Bool}, tm_scale::Real) + # z_voltage_fr = var(pm, nw, :z_voltage, f_bus) + # z_voltage_to = var(pm, nw, :z_voltage, t_bus) + + tm = [tm_fixed[idx] ? tm_set[idx] : var(pm, nw, :tap, trans_id)[idx] for (idx,(fc,tc)) in enumerate(zip(f_connections,t_connections))] + transformer = ref(pm, nw, :transformer, trans_id) + + p_fr = [var(pm, nw, :pt, f_idx)[p] for p in f_connections] + p_to = [var(pm, nw, :pt, t_idx)[p] for p in t_connections] + q_fr = [var(pm, nw, :qt, f_idx)[p] for p in f_connections] + q_to = [var(pm, nw, :qt, t_idx)[p] for p in t_connections] + + w_fr = var(pm, nw, :w)[f_bus] + w_to = var(pm, nw, :w)[t_bus] + + tmsqr = [tm_fixed[i] ? tm[i]^2 : JuMP.@variable(pm.model, base_name="$(nw)_tmsqr_$(trans_id)_$(f_connections[i])", start=JuMP.start_value(tm[i])^2, lower_bound=JuMP.has_lower_bound(tm[i]) ? JuMP.lower_bound(tm[i])^2 : 0.9^2, upper_bound=JuMP.has_upper_bound(tm[i]) ? JuMP.upper_bound(tm[i])^2 : 1.1^2) for i in 1:length(tm)] + + for (idx, (fc, tc)) in enumerate(zip(f_connections, t_connections)) + if tm_fixed[idx] + JuMP.@constraint(pm.model, w_fr[fc] == (pol*tm_scale*tm[idx])^2*w_to[tc]) + else + PMD.PolyhedralRelaxations.construct_univariate_relaxation!(pm.model, x->x^2, tm[idx], tmsqr[idx], [JuMP.has_lower_bound(tm[idx]) ? JuMP.lower_bound(tm[idx]) : 0.9, JuMP.has_upper_bound(tm[idx]) ? JuMP.upper_bound(tm[idx]) : 1.1], false) + + tmsqr_w_to = JuMP.@variable(pm.model, base_name="$(nw)_tmsqr_w_to_$(trans_id)_$(t_bus)_$(tc)") + PMD.PolyhedralRelaxations.construct_bilinear_relaxation!(pm.model, tmsqr[idx], w_to[tc], tmsqr_w_to, [JuMP.lower_bound(tmsqr[idx]), JuMP.upper_bound(tmsqr[idx])], [JuMP.has_lower_bound(w_to[tc]) ? JuMP.lower_bound(w_to[tc]) : 0.0, JuMP.has_upper_bound(w_to[tc]) ? JuMP.upper_bound(w_to[tc]) : 1.1^2]) + + JuMP.@constraint(pm.model, w_fr[fc] == (pol*tm_scale)^2*tmsqr_w_to) + + # with regcontrol + if haskey(transformer,"controls") + # TODO: fix LPUBFDiag version of transformer controls for on_off + # v_ref = transformer["controls"]["vreg"][idx] + # δ = transformer["controls"]["band"][idx] + # r = transformer["controls"]["r"][idx] + # x = transformer["controls"]["x"][idx] + + # # linearized voltage squared: w_drop = (2⋅r⋅p+2⋅x⋅q) + # w_drop = JuMP.@expression(pm.model, 2*r*p_to[idx] + 2*x*q_to[idx]) + + # # (v_ref-δ)^2 ≤ w_fr-w_drop ≤ (v_ref+δ)^2 + # # w_fr/1.1^2 ≤ w_to ≤ w_fr/0.9^2 + # w_drop_z_block = JuMP.@variable(pm.model, base_name="$(nw)_w_drop_z_block_$(trans_id)_$(idx)") + + # IM.relaxation_product(pm.model, w_drop, z_block, w_drop_z_block; default_x_domain=(0.9^2, 1.1^2), default_y_domain=(0, 1)) + + # w_fr_lb = JuMP.has_lower_bound(w_fr[fc]) && JuMP.lower_bound(w_fr[fc]) > 0 ? JuMP.lower_bound(w_fr[fc]) : 0.9^2 + # w_fr_ub = JuMP.has_upper_bound(w_fr[fc]) && isfinite(JuMP.upper_bound(w_fr[fc])) ? JuMP.upper_bound(w_fr[fc]) : 1.1^2 + + # JuMP.@constraint(pm.model, w_fr[fc] ≥ z_block * (v_ref - δ)^2 + w_drop_z_block) + # JuMP.@constraint(pm.model, w_fr[fc] ≤ z_block * (v_ref + δ)^2 + w_drop_z_block) + # JuMP.@constraint(pm.model, w_fr[fc]/w_fr_ub ≤ w_to[tc]) + # JuMP.@constraint(pm.model, w_fr[fc]/w_fr_lb ≥ w_to[tc]) + end + end + end + + JuMP.@constraint(pm.model, p_fr + p_to .== 0) + JuMP.@constraint(pm.model, q_fr + q_to .== 0) +end + + +""" + constraint_mc_storage_losses_block_on_off(pm::PMD.LPUBFDiagModel, nw::Int, i::Int, bus::Int, connections::Vector{Int}, r::Real, x::Real, p_loss::Real, q_loss::Real) + +Neglects the active and reactive loss terms associated with the squared current magnitude. +""" +function constraint_mc_storage_losses_block_on_off(pm::PMD.LPUBFDiagModel, nw::Int, i::Int, bus::Int, connections::Vector{Int}, ::Real, ::Real, p_loss::Real, q_loss::Real) + z_block = var(pm, nw, :z_block, ref(pm, nw, :storage_block_map, i)) + + ps = var(pm, nw, :ps, i) + qs = var(pm, nw, :qs, i) + sc = var(pm, nw, :sc, i) + sd = var(pm, nw, :sd, i) + qsc = var(pm, nw, :qsc, i) + + if JuMP.has_lower_bound(qsc) && JuMP.has_upper_bound(qsc) + qsc_zblock = JuMP.@variable(pm.model, base_name="$(nw)_qd_zblock_$(i)") + + JuMP.@constraint(pm.model, qsc_zblock >= JuMP.lower_bound(qsc) * z_block) + JuMP.@constraint(pm.model, qsc_zblock >= JuMP.upper_bound(qsc) * z_block + qsc - JuMP.upper_bound(qsc)) + JuMP.@constraint(pm.model, qsc_zblock <= JuMP.upper_bound(qsc) * z_block) + JuMP.@constraint(pm.model, qsc_zblock <= qsc + JuMP.lower_bound(qsc) * z_block - JuMP.lower_bound(qsc)) + + JuMP.@constraint(pm.model, sum(qs[c] for c in connections) == qsc_zblock + q_loss * z_block) + else + # Note that this is not supported in LP solvers when z_block is continuous + JuMP.@constraint(pm.model, sum(qs[c] for c in connections) == qsc * z_block + q_loss * z_block) + end + JuMP.@constraint(pm.model, sum(ps[c] for c in connections) + (sd - sc) == p_loss * z_block) +end + + +""" + constraint_mc_storage_losses_traditional_on_off(pm::PMD.LPUBFDiagModel, nw::Int, i::Int, bus::Int, connections::Vector{Int}, r::Real, x::Real, p_loss::Real, q_loss::Real) + +Neglects the active and reactive loss terms associated with the squared current magnitude. +""" +function constraint_mc_storage_losses_traditional_on_off(pm::PMD.LPUBFDiagModel, nw::Int, i::Int, bus::Int, connections::Vector{Int}, r::Real, x::Real, p_loss::Real, q_loss::Real) + z_storage = var(pm, nw, :z_storage, i) + + ps = var(pm, nw, :ps, i) + qs = var(pm, nw, :qs, i) + sc = var(pm, nw, :sc, i) + sd = var(pm, nw, :sd, i) + qsc = var(pm, nw, :qsc, i) + + qsc_z_storage = JuMP.@variable(pm.model, base_name="$(nw)_qd_z_storage_$(i)") + + JuMP.@constraint(pm.model, qsc_z_storage >= JuMP.lower_bound(qsc) * z_storage) + JuMP.@constraint(pm.model, qsc_z_storage >= JuMP.upper_bound(qsc) * z_storage + qsc - JuMP.upper_bound(qsc)) + JuMP.@constraint(pm.model, qsc_z_storage <= JuMP.upper_bound(qsc) * z_storage) + JuMP.@constraint(pm.model, qsc_z_storage <= qsc + JuMP.lower_bound(qsc) * z_storage - JuMP.lower_bound(qsc)) + + JuMP.@constraint(pm.model, sum(ps[c] for c in connections) + (sd - sc) == p_loss * z_storage) + JuMP.@constraint(pm.model, sum(qs[c] for c in connections) == qsc_z_storage + q_loss * z_storage) +end + + +@doc raw""" + constraint_storage_complementarity_mi_block_on_off(pm::PMD.LPUBFDiagModel, n::Int, i::Int, charge_ub::Real, discharge_ub::Real) + +linear storage complementarity mi constraint for block mld problem + +math``` +sc_{on} + sd_{on} == z_{block} +``` +""" +function constraint_storage_complementarity_mi_block_on_off(pm::PMD.LPUBFDiagModel, n::Int, i::Int, charge_ub::Real, discharge_ub::Real) + sc = var(pm, n, :sc, i) + sd = var(pm, n, :sd, i) + sc_on = var(pm, n, :sc_on, i) + sd_on = var(pm, n, :sd_on, i) + + z_block = var(pm, n, :z_block, ref(pm, n, :storage_block_map, i)) + + JuMP.@constraint(pm.model, sc_on + sd_on == z_block) + JuMP.@constraint(pm.model, sc_on*charge_ub >= sc) + JuMP.@constraint(pm.model, sd_on*discharge_ub >= sd) +end + + +@doc raw""" + constraint_storage_complementarity_mi_traditional_on_off(pm::PMD.LPUBFDiagModel, n::Int, i::Int, charge_ub::Real, discharge_ub::Real) + +linear storage complementarity mi constraint for traditional mld problem + +math``` +sc_{on} + sd_{on} == z_{block} +``` +""" +function constraint_storage_complementarity_mi_traditional_on_off(pm::PMD.LPUBFDiagModel, n::Int, i::Int, charge_ub::Real, discharge_ub::Real) + sc = var(pm, n, :sc, i) + sd = var(pm, n, :sd, i) + sc_on = var(pm, n, :sc_on, i) + sd_on = var(pm, n, :sd_on, i) + + z_storage = var(pm, n, :z_storage, i) + + JuMP.@constraint(pm.model, sc_on + sd_on == z_storage) + JuMP.@constraint(pm.model, sc_on*charge_ub >= sc) + JuMP.@constraint(pm.model, sd_on*discharge_ub >= sd) +end + + +""" + constraint_mc_inverter_theta_ref(pm::PMD.LPUBFDiagModel, nw::Int, i::Int, ::Vector{<:Real}) + +Constrains a bus with a connected grid-forming inverter to have a reference bus constraint +""" +function constraint_mc_inverter_theta_ref(pm::PMD.LPUBFDiagModel, nw::Int, i::Int, ::Vector{<:Real}) + w = [var(pm, nw, :w, i)[t] for t in ref(pm, nw, :bus, i)["terminals"]] + inverter_objects = ref(pm, nw, :bus_inverters, i) + z_inverters = [var(pm, nw, :z_inverter, inv_obj) for inv_obj in inverter_objects] + + vmax = min(ref(pm, nw, :bus, i, "vmax")..., 2.0) + + if length(w) > 1 && !isempty(inverter_objects) + for t in 2:length(w) + # Indicator constraint version, for reference + # JuMP.@constraint(pm.model, sum(z_inverters) => { w[t] == w[1]}) + + JuMP.@constraint(pm.model, w[t] - w[1] <= vmax^2 * (1 - sum(z_inverters))) + JuMP.@constraint(pm.model, w[t] - w[1] >= -vmax^2 * (1 - sum(z_inverters))) + + end + end +end diff --git a/src/form/shared.jl b/src/form/shared.jl index 80a44d0f..ca420f26 100644 --- a/src/form/shared.jl +++ b/src/form/shared.jl @@ -1,163 +1,97 @@ @doc raw""" - constraint_mc_switch_power_on_off(pm::AbstractSwitchModels, nw::Int, f_idx::Tuple{Int,Int,Int}; relax::Bool=false) + constraint_mc_bus_voltage_magnitude_sqr_block_on_off( + pm::PMD.AbstractUnbalancedWModels, + nw::Int, + i::Int, + vmin::Vector{<:Real}, + vmax::Vector{<:Real} + ) -Linear switch power on/off constraint. If `relax`, an [indicator constraint](https://jump.dev/JuMP.jl/stable/manual/constraints/#Indicator-constraints) is used. +on/off block bus voltage magnitude squared constraint for W models ```math -\begin{align} -& S^{sw}_{i,c} \leq S^{swu}_{i,c} z^{sw}_i\ \forall i \in S,\forall c \in C \\ -& S^{sw}_{i,c} \geq -S^{swu}_{i,c} z^{sw}_i\ \forall i \in S,\forall c \in C -\end{align} ``` """ -function PowerModelsDistribution.constraint_mc_switch_power_on_off(pm::AbstractSwitchModels, nw::Int, f_idx::Tuple{Int,Int,Int}; relax::Bool=false) - i, f_bus, t_bus = f_idx +function constraint_mc_bus_voltage_magnitude_sqr_block_on_off(pm::PMD.AbstractUnbalancedWModels, nw::Int, i::Int, vmin::Vector{<:Real}, vmax::Vector{<:Real}) + w = var(pm, nw, :w, i) + z_block = var(pm, nw, :z_block, ref(pm, nw, :bus_block_map, i)) - psw = var(pm, nw, :psw, f_idx) - qsw = var(pm, nw, :qsw, f_idx) + terminals = ref(pm, nw, :bus, i)["terminals"] + grounded = ref(pm, nw, :bus, i)["grounded"] - z = var(pm, nw, :switch_state, i) - - connections = ref(pm, nw, :switch, i)["f_connections"] - - switch = ref(pm, nw, :switch, i) - - rating = min.(fill(1.0, length(connections)), PMD._calc_branch_power_max_frto(switch, ref(pm, nw, :bus, f_bus), ref(pm, nw, :bus, t_bus))...) - - for (idx, c) in enumerate(connections) - if relax - JuMP.@constraint(pm.model, psw[c] <= rating[idx] * z) - JuMP.@constraint(pm.model, psw[c] >= -rating[idx] * z) - JuMP.@constraint(pm.model, qsw[c] <= rating[idx] * z) - JuMP.@constraint(pm.model, qsw[c] >= -rating[idx] * z) - else - JuMP.@constraint(pm.model, !z => {psw[c] == 0.0}) - JuMP.@constraint(pm.model, !z => {qsw[c] == 0.0}) - end + for (idx,t) in [(idx,t) for (idx,t) in enumerate(terminals) if !grounded[idx]] + isfinite(vmax[idx]) && JuMP.@constraint(pm.model, w[t] <= vmax[idx]^2*z_block) + isfinite(vmin[idx]) && JuMP.@constraint(pm.model, w[t] >= vmin[idx]^2*z_block) end end -""" - constraint_mc_gen_power_on_off(pm::AbstractSwitchModels, nw::Int, i::Int, connections::Vector{<:Int}, pmin::Vector{<:Real}, pmax::Vector{<:Real}, qmin::Vector{<:Real}, qmax::Vector{<:Real}) - -on/off constraint for generators -""" -function PowerModelsDistribution.constraint_mc_gen_power_on_off(pm::AbstractSwitchModels, nw::Int, i::Int, connections::Vector{<:Int}, pmin::Vector{<:Real}, pmax::Vector{<:Real}, qmin::Vector{<:Real}, qmax::Vector{<:Real}) - pg = var(pm, nw, :pg, i) - qg = var(pm, nw, :qg, i) - - z_block = var(pm, nw, :z_block, ref(pm, nw, :gen_block_map, i)) - - for (idx, c) in enumerate(connections) - if isfinite(pmax[idx]) - JuMP.@constraint(pm.model, pg[c] <= pmax[idx]*z_block) - end - - if isfinite(pmin[idx]) - JuMP.@constraint(pm.model, pg[c] >= pmin[idx]*z_block) - end - - if isfinite(qmax[idx]) - JuMP.@constraint(pm.model, qg[c] <= qmax[idx]*z_block) - end - - if isfinite(qmin[idx]) - JuMP.@constraint(pm.model, qg[c] >= qmin[idx]*z_block) - end - end -end +@doc raw""" + constraint_mc_bus_voltage_magnitude_sqr_traditional_on_off( + pm::PMD.AbstractUnbalancedWModels, + nw::Int, + i::Int, + vmin::Vector{<:Real}, + vmax::Vector{<:Real} + ) +on/off traditional bus voltage magnitude squared constraint for W models +```math +``` """ - constraint_mc_storage_on_off(pm::AbstractSwitchModels, nw::Int, i::Int, connections::Vector{Int}, pmin::Real, pmax::Real, qmin::Real, qmax::Real, charge_ub::Real, discharge_ub::Real) - -on/off constraint for storage -""" -function PowerModelsDistribution.constraint_mc_storage_on_off(pm::AbstractSwitchModels, nw::Int, i::Int, connections::Vector{Int}, pmin::Real, pmax::Real, qmin::Real, qmax::Real, charge_ub::Real, discharge_ub::Real) - z_block = var(pm, nw, :z_block, ref(pm, nw, :storage_block_map, i)) +function constraint_mc_bus_voltage_magnitude_sqr_traditional_on_off(pm::PMD.AbstractUnbalancedWModels, nw::Int, i::Int, vmin::Vector{<:Real}, vmax::Vector{<:Real}) + w = var(pm, nw, :w, i) + z_voltage = var(pm, nw, :z_voltage, i) - ps = [var(pm, nw, :ps, i)[c] for c in connections] - qs = [var(pm, nw, :qs, i)[c] for c in connections] + terminals = ref(pm, nw, :bus, i)["terminals"] + grounded = ref(pm, nw, :bus, i)["grounded"] - JuMP.@constraint(pm.model, sum(ps) <= z_block*pmax) - JuMP.@constraint(pm.model, sum(ps) >= z_block*pmin) - - JuMP.@constraint(pm.model, sum(qs) <= z_block*qmax) - JuMP.@constraint(pm.model, sum(qs) >= z_block*qmin) + for (idx,t) in [(idx,t) for (idx,t) in enumerate(terminals) if !grounded[idx]] + isfinite(vmax[idx]) && JuMP.@constraint(pm.model, w[t] <= vmax[idx]^2*z_voltage) + isfinite(vmin[idx]) && JuMP.@constraint(pm.model, w[t] >= vmin[idx]^2*z_voltage) + end end """ - constraint_mc_storage_phase_unbalance(pm::AbstractSwitchModels, nw::Int, i::Int, connections::Vector{Int}, unbalance_factor::Float64) + constraint_mc_bus_voltage_block_on_off(pm::PMD.AbstractUnbalancedWModels, nw::Int, i::Int, vmin::Vector{<:Real}, vmax::Vector{<:Real}) -Enforces that storage inputs/outputs are (approximately) balanced across each phase, by some `unbalance_factor` +Redirects to `constraint_mc_bus_voltage_magnitude_sqr_block_on_off` for `AbstractUnbalancedWModels` """ -function constraint_mc_storage_phase_unbalance(pm::AbstractUnbalancedPowerModel, nw::Int, i::Int, connections::Vector{Int}, unbalance_factor::Float64) - ps = var(pm, nw, :ps, i) - qs = var(pm, nw, :qs, i) - - sc_on = var(pm, nw, :sc_on, i) # ==1 charging (p,q > 0) - sd_on = var(pm, nw, :sd_on, i) # ==1 discharging (p,q < 0) - - sd_on_ps = JuMP.@variable(pm.model, [c in connections], base_name="$(nw)_sd_on_ps_$(i)", lower_bound=0.0) - sc_on_ps = JuMP.@variable(pm.model, [c in connections], base_name="$(nw)_sc_on_ps_$(i)", lower_bound=0.0) - sd_on_qs = JuMP.@variable(pm.model, [c in connections], base_name="$(nw)_sd_on_qs_$(i)", lower_bound=0.0) - sc_on_qs = JuMP.@variable(pm.model, [c in connections], base_name="$(nw)_sc_on_qs_$(i)", lower_bound=0.0) - for c in connections - PolyhedralRelaxations.construct_bilinear_relaxation!(pm.model, sd_on, ps[c], sd_on_ps[c], [0,1], [JuMP.lower_bound(ps[c]), JuMP.upper_bound(ps[c])]) - PolyhedralRelaxations.construct_bilinear_relaxation!(pm.model, sc_on, ps[c], sc_on_ps[c], [0,1], [JuMP.lower_bound(ps[c]), JuMP.upper_bound(ps[c])]) - PolyhedralRelaxations.construct_bilinear_relaxation!(pm.model, sd_on, qs[c], sd_on_qs[c], [0,1], [JuMP.lower_bound(qs[c]), JuMP.upper_bound(qs[c])]) - PolyhedralRelaxations.construct_bilinear_relaxation!(pm.model, sc_on, qs[c], sc_on_qs[c], [0,1], [JuMP.lower_bound(qs[c]), JuMP.upper_bound(qs[c])]) - end - - for (idx,c) in enumerate(connections) - if idx < length(connections) - for d in connections[idx+1:end] - JuMP.@constraint(pm.model, ps[c] >= ps[d] - unbalance_factor*(-1*sd_on_ps[d] + 1*sc_on_ps[d])) - JuMP.@constraint(pm.model, ps[c] <= ps[d] + unbalance_factor*(-1*sd_on_ps[d] + 1*sc_on_ps[d])) - - JuMP.@constraint(pm.model, qs[c] >= qs[d] - unbalance_factor*(-1*sd_on_qs[d] + 1*sc_on_qs[d])) - JuMP.@constraint(pm.model, qs[c] <= qs[d] + unbalance_factor*(-1*sd_on_qs[d] + 1*sc_on_qs[d])) - end - end - end -end +constraint_mc_bus_voltage_block_on_off(pm::PMD.AbstractUnbalancedWModels, nw::Int, i::Int, vmin::Vector{<:Real}, vmax::Vector{<:Real}) = constraint_mc_bus_voltage_magnitude_sqr_block_on_off(pm, nw, i, vmin, vmax) """ - constraint_storage_complementarity_mi_on_off(pm::AbstractLPSwitchModels, n::Int, i::Int, charge_ub::Float64, discharge_ub::Float64) + constraint_mc_bus_voltage_traditional_on_off(pm::PMD.AbstractUnbalancedWModels, nw::Int, i::Int, vmin::Vector{<:Real}, vmax::Vector{<:Real}) -sc_on + sd_on == z_block +Redirects to `constraint_mc_bus_voltage_magnitude_sqr_traditional_on_off` for `AbstractUnbalancedWModels` """ -function constraint_storage_complementarity_mi_on_off(pm::AbstractLPSwitchModels, n::Int, i::Int, charge_ub::Float64, discharge_ub::Float64) - sc = var(pm, n, :sc, i) - sd = var(pm, n, :sd, i) - sc_on = var(pm, n, :sc_on, i) - sd_on = var(pm, n, :sd_on, i) - - z_block = var(pm, n, :z_block, ref(pm, n, :storage_block_map, i)) +constraint_mc_bus_voltage_traditional_on_off(pm::PMD.AbstractUnbalancedWModels, nw::Int, i::Int, vmin::Vector{<:Real}, vmax::Vector{<:Real}) = constraint_mc_bus_voltage_magnitude_sqr_traditional_on_off(pm, nw, i, vmin, vmax) - JuMP.@constraint(pm.model, sc_on + sd_on == z_block) - JuMP.@constraint(pm.model, sc_on*charge_ub >= sc) - JuMP.@constraint(pm.model, sd_on*discharge_ub >= sd) -end +@doc raw""" + constraint_mc_inverter_theta_ref(pm::PMD.AbstractUnbalancedPolarModels, nw::Int, i::Int, va_ref::Vector{<:Real}) -""" - constraint_storage_complementarity_mi_on_off(pm::AbstractNLPSwitchModels, n::Int, i::Int, charge_ub::Float64, discharge_ub::Float64) +Phase angle constraints at reference buses for the Unbalanced Polar models -sc_on * sd_on == z_block +math``` +\begin{align*} +V_a - V^{ref}_a \leq 60^{\circ} * (1-\sum{z_{inv}}) +V_a - V^{ref}_a \geq -60^{\circ} * (1-\sum{z_{inv}}) +\end{align*} +``` """ -function constraint_storage_complementarity_mi_on_off(pm::AbstractNLPSwitchModels, n::Int, i::Int, charge_ub::Float64, discharge_ub::Float64) - sc = var(pm, n, :sc, i) - sd = var(pm, n, :sd, i) - sc_on = var(pm, n, :sc_on, i) - sd_on = var(pm, n, :sd_on, i) - - z_block = var(pm, n, :z_block, ref(pm, n, :storage_block_map, i)) - - JuMP.@constraint(pm.model, sc_on*sd_on == z_block) - JuMP.@constraint(pm.model, sc_on*charge_ub >= sc) - JuMP.@constraint(pm.model, sd_on*discharge_ub >= sd) +function constraint_mc_inverter_theta_ref(pm::PMD.AbstractUnbalancedPolarModels, nw::Int, i::Int, va_ref::Vector{<:Real}) + terminals = ref(pm, nw, :bus, i)["terminals"] + va = var(pm, nw, :va, i) + inverter_objects = ref(pm, nw, :bus_inverters, i) + z_inverters = [var(pm, nw, :z_inverter, inv_obj) for inv_obj in inverter_objects] + + if !isempty(inverter_objects) + for (idx,t) in enumerate(terminals) + JuMP.@constraint(pm.model, va[t] - va_ref[idx] <= deg2rad(60) * (1-sum(z_inverters))) + JuMP.@constraint(pm.model, va[t] - va_ref[idx] >= -deg2rad(60) * (1-sum(z_inverters))) + end + end end diff --git a/src/io/events.jl b/src/io/events.jl index f2656e49..765f8bc0 100644 --- a/src/io/events.jl +++ b/src/io/events.jl @@ -1,5 +1,9 @@ """ - parse_events!(args::Dict{String,<:Any}; validate::Bool=true, apply::Bool=true)::Dict{String,Any} + parse_events!( + args::Dict{String,<:Any}; + validate::Bool=true, + apply::Bool=true + )::Dict{String,Any} Parses events file in-place using [`parse_events`](@ref parse_events), for use inside of [`entrypoint`](@ref entrypoint). @@ -9,7 +13,7 @@ If `apply`, will apply the events to the multinetwork data structure. If `validate=true` (default), the parsed data structure will be validated against the latest [Events Schema](@ref Events-Schema). """ -function parse_events!(args::Dict{String,<:Any}; validate::Bool=true, apply::Bool=true)::Dict{String,Any} +function parse_events!(args::Dict{String,<:Any}; validate::Bool=true, apply::Bool=true)::Union{Dict{String,Any},Vector{Dict{String,Any}}} if !isempty(get(args, "events", "")) if isa(args["events"], String) if isa(get(args, "network", ""), Dict) @@ -20,7 +24,7 @@ function parse_events!(args::Dict{String,<:Any}; validate::Bool=true, apply::Boo args["events"] = parse_events(args["events"]) end elseif isa(args["events"], Vector) && isa(get(args, "network", ""), Dict) - parse_events(args["events"], args["network"]) + args["events"] = parse_events(args["events"], args["network"]) end else args["events"] = Dict{String,Any}() @@ -30,7 +34,7 @@ function parse_events!(args::Dict{String,<:Any}; validate::Bool=true, apply::Boo if isa(get(args, "network", ""), Dict) apply_events!(args) else - error("cannot apply events, no multinetwork is loaded in 'network'") + @error("cannot apply events, no multinetwork is loaded in 'network'") end end @@ -39,7 +43,10 @@ end """ - parse_events(events_file::String; validate::Bool=true)::Vector{Dict{String,Any}} + parse_events( + events_file::String; + validate::Bool=true + )::Vector{Dict{String,Any}} Parses an events file into a raw events data structure @@ -58,20 +65,24 @@ function parse_events(events_file::String; validate::Bool=true)::Vector{Dict{Str end -"helper function to convert JSON data types to native data types (Enums) in events" +""" + _convert_event_data_types!( + events::Vector{<:Dict{String,<:Any}} + )::Vector{Dict{String,Any}} + +Helper function to convert JSON data types to native data types (Enums) in events. +""" function _convert_event_data_types!(events::Vector{<:Dict{String,<:Any}})::Vector{Dict{String,Any}} for event in events for (k,v) in event["event_data"] - if k == "dispatchable" - event["event_data"][k] = PMD.Dispatchable(Int(v)) - end - - if k == "state" - event["event_data"][k] = Dict("open" => PMD.OPEN, "closed" => PMD.CLOSED)[lowercase(string(v))] - end - - if k == "status" - event["event_data"][k] = PMD.Status(Int(v)) + if k ∈ ["state", "dispatchable", "status"] + if isa(v, String) + event["event_data"][k] = getproperty(PMD, Symbol(uppercase(v))) + elseif isa(v, Int) + event["event_data"][k] = getproperty(PMD, k == "state" ? :SwitchState : Symbol(titlecase(k)))(v) + elseif isa(v, Bool) + event["event_data"][k] = getproperty(PMD, k == "state" ? :SwitchState : Symbol(titlecase(k)))(Int(v)) + end end end end @@ -81,7 +92,10 @@ end """ - parse_events(raw_events::Vector{<:Dict{String,<:Any}}, mn_data::Dict{String,<:Any})::Dict{String,Any} + parse_events( + raw_events::Vector{<:Dict{String,<:Any}}, + mn_data::Dict{String,<:Any} + )::Dict{String,Any} Converts `raw_events`, e.g. loaded from JSON, and therefore in the format Vector{Dict}, to an internal data structure that closely matches the multinetwork data structure for easy merging (applying) to the multinetwork data structure. @@ -120,12 +134,14 @@ function parse_events(raw_events::Vector{<:Dict{String,<:Any}}, mn_data::Dict{St if event["event_type"] == "switch" switch_id = _find_switch_id_from_source_id(mn_data["nw"][n], event["affected_asset"]) - events[n]["switch"][switch_id] = Dict{String,Any}( - k => v for (k,v) in event["event_data"] - ) + if !ismissing(switch_id) + events[n]["switch"][switch_id] = Dict{String,Any}( + k => v for (k,v) in event["event_data"] + ) + end elseif event["event_type"] == "fault" switch_ids = _find_switch_ids_by_faulted_asset(mn_data["nw"][n], event["affected_asset"]) - n_next = _find_next_nw_id_from_fault_duration(mn_data, n, event["event_data"]["duration"]) + n_next = _find_next_nw_id_from_fault_duration(mn_data, n, event["event_data"]["duration_ms"]) if !ismissing(n_next) if !haskey(events, n_next) @@ -156,7 +172,11 @@ end """ - parse_events(events_file::String, mn_data::Dict{String,<:Any}; validate::Bool=true)::Dict{String,Any} + parse_events( + events_file::String, + mn_data::Dict{String,<:Any}; + validate::Bool=true + )::Dict{String,Any} Parses raw events from `events_file` and passes it to [`parse_events`](@ref parse_events) to convert to the native data type. @@ -182,7 +202,10 @@ end """ - apply_events(network::Dict{String,<:Any}, events::Dict{String,<:Any})::Dict{String,Any} + apply_events( + network::Dict{String,<:Any}, + events::Dict{String,<:Any} + )::Dict{String,Any} Creates a copy of the multinetwork data structure `network` and applies the events in `events` to that data. @@ -207,24 +230,70 @@ function apply_events(network::Dict{String,<:Any}, events::Dict{String,<:Any}):: end -"helper function to find a switch id in the network model based on the dss `source_id`" -function _find_switch_id_from_source_id(network::Dict{String,<:Any}, source_id::String)::String +""" + _find_switch_id_from_source_id( + network::Dict{String,<:Any}, + source_id::String + )::Union{String,Missing} + +Helper function to find a switch id in the network model based on the dss `source_id` +""" +function _find_switch_id_from_source_id(network::Dict{String,<:Any}, source_id::String)::Union{String,Missing} for (id, switch) in get(network, "switch", Dict()) if switch["source_id"] == lowercase(source_id) return id end end - error("switch '$(source_id)' not found in network model, aborting") + @info "events parsing: switch '$(source_id)' not found in network model, skipping" + return missing end "helper function to find which switches need to be opened to isolate a fault on asset given by `source_id`" function _find_switch_ids_by_faulted_asset(network::Dict{String,<:Any}, source_id::String)::Vector{String} - # TODO algorithm for isolating faults (heuristic) + data = deepcopy(network) + data["data_model"] = PMD.ENGINEERING + + blocks = Dict{Int,Set}(i => block for (i,block) in enumerate(PMD.calc_connected_components(data; type="load_blocks", check_enabled=true))) + bus2block_map = Dict(bus => block_id for (block_id,block) in blocks for bus in block) + + source_id_map = Dict{String,Tuple{String,String}}( + obj["source_id"] => (obj_type,obj_id) for obj_type in PMD.pmd_eng_asset_types for (obj_id,obj) in get(data,obj_type,Dict()) if haskey(obj,"source_id") + ) + + (type,obj_id) = source_id_map[lowercase(source_id)] + obj = get(get(data, type, Dict()), obj_id, Dict()) + + affected_blocks = Set() + if type in PMD._eng_edge_elements + if type == "transformer" && haskey(obj, "bus") + affected_blocks = Set([bus2block_map[bus] for bus in obj["bus"]]) + elseif haskey(obj, "f_bus") && haskey(obj, "t_bus") + affected_blocks = Set([bus2block_map[obj["f_bus"]], bus2block_map[obj["t_bus"]]]) + end + elseif haskey(obj, "bus") + affected_blocks = Set([bus2block_map[obj["bus"]]]) + end + + affected_switches = String[] + for (s,switch) in get(network, "switch", Dict()) + if bus2block_map[switch["f_bus"]] in affected_blocks || bus2block_map[switch["t_bus"]] in affected_blocks + push!(affected_switches, s) + end + end + + return affected_switches end -"helper function to find the multinetwork id of the subnetwork corresponding most closely to a `timestep`" +""" + _find_nw_id_from_timestep( + network::Dict{String,<:Any}, + timestep::Union{Real,String} + )::String + +Helper function to find the multinetwork id of the subnetwork of `network` corresponding most closely to a `timestep`. +""" function _find_nw_id_from_timestep(network::Dict{String,<:Any}, timestep::Union{Real,String})::String @assert PMD.ismultinetwork(network) "network data structure is not multinetwork" @@ -263,13 +332,21 @@ function _find_nw_id_from_timestep(network::Dict{String,<:Any}, timestep::Union{ end -"helper function to find the next timestep following a fault given its duration in ms" +""" + _find_next_nw_id_from_fault_duration( + network::Dict{String,<:Any}, + nw_id::String, + duration::Real + )::Union{String,Missing} + +Helper function to find the next timestep following a fault given its duration in ms +""" function _find_next_nw_id_from_fault_duration(network::Dict{String,<:Any}, nw_id::String, duration::Real)::Union{String,Missing} current_timestep = network["mn_lookup"][nw_id] mn_lookup_reverse = Dict{Any,String}(v => k for (k,v) in network["mn_lookup"]) timesteps = sort(collect(values(network["mn_lookup"]))) - dist = timesteps .- current_timestep + (duration / 3.6e6) # duration is in ms, timestep in hours + dist = timesteps .- (current_timestep .+ (duration / 3.6e6)) # duration is in ms, timestep in hours if all(dist .< 0) return missing else @@ -280,14 +357,45 @@ end """ - build_events_file(case_file::String, events_file::String; custom_events::Vector{Dict{String,Any}}=Dict{String,Any}[], default_switch_state::PMD.SwitchState=PMD.CLOSED, default_switch_dispatchable::PMD.Dispatchable=PMD.YES) + build_events(case_file::String; kwargs...)::Vector{Dict{String,Any}} -A helper function to assist in making (very) simple events files with some default settings for switches +A helper function to assist in making rudamentary events data structure with some default settings for switches from a network case at path `case_file`. """ -function build_events_file(case_file::String, events_file::String; custom_events::Vector{Dict{String,Any}}=Dict{String,Any}[], default_switch_state::PMD.SwitchState=PMD.CLOSED, default_switch_dispatchable::PMD.Dispatchable=PMD.YES, default_switch_status::Union{Missing,PMD.Status}=missing) +build_events(case_file::String; kwargs...)::Vector{Dict{String,Any}} = build_events(PMD.parse_file(case_file); kwargs...) + + +""" + build_events( + eng::Dict{String,<:Any}; + custom_events::Vector{Dict{String,Any}}=Dict{String,Any}[], + default_switch_state::Union{PMD.SwitchState,String}=PMD.CLOSED, + default_switch_dispatchable::Union{PMD.Dispatchable,Bool}=PMD.YES, + default_switch_status::Union{Missing,PMD.Status,Int}=missing + )::Vector{Dict{String,Any}} + +A helper function to assist in making rudamentary events data structure with some default settings for switches. + +- `eng::Dict{String,<:Any}` is the input case data structure +- `custom_events` is a Vector of *events* that will be applied **after** the automatic generation of events based off of the `default` kwargs +- `default_switch_state::Union{PMD.SwitchState,String}` (default: `CLOSED`) is the toggle for the default state to apply to every switch +- `default_switch_dispatchable::Union{PMD.Dispatchable,Bool}` (default: `YES`) is the toggle for the default dispatchability (controllability) of every switch +- `default_switch_status::Union{Missing,PMD.Status,Int}` (default: `missing`) is the toggle for the default status (whether the switch appears in the model at all or not) of every switch. If `missing` will default to the status given by the model. +""" +function build_events( + eng::Dict{String,<:Any}; + custom_events::Vector{Dict{String,Any}}=Dict{String,Any}[], + default_switch_state::Union{PMD.SwitchState,String}=PMD.CLOSED, + default_switch_dispatchable::Union{PMD.Dispatchable,Bool}=PMD.YES, + default_switch_status::Union{Missing,PMD.Status,Int}=missing + )::Vector{Dict{String,Any}} + + @assert !PMD.ismultinetwork(eng) "this function cannot utilize multinetwork data" + events = Dict{String,Any}[] - eng = PMD.parse_file(case_file) + default_switch_state = isa(default_switch_state, String) ? getproperty(PMD, Symbol(uppercase(default_switch_state))) : default_switch_state + default_switch_dispatchable = isa(default_switch_dispatchable, Bool) ? PMD.Dispatchable(Int(default_switch_dispatchable)) : default_switch_dispatchable + default_switch_status = isa(default_switch_status, Int) ? PMD.Status(default_switch_status) : default_switch_status for (s, switch) in get(eng, "switch", Dict()) push!( @@ -297,18 +405,79 @@ function build_events_file(case_file::String, events_file::String; custom_events "event_type" => "switch", "affected_asset" => switch["source_id"], "event_data" => Dict{String,Any}( - "type" => "breaker", - "state" => lowercase(string(default_switch_state)), - "dispatchable" => Bool(Int(default_switch_dispatchable)), - "status" => ismissing(default_switch_status) ? Int(switch["status"]) : Int(default_switch_status), + "state" => string(default_switch_state), + "dispatchable" => string(default_switch_dispatchable), + "status" => ismissing(default_switch_status) ? string(switch["status"]) : string(default_switch_status), ) ) ) end - append!(events, custom_events) + converted_custom_events = Dict{String,Any}[] + for event in custom_events + converted_event = Dict{String,Any}() + + if get(event, "event_type", "switch") == "switch" + for (k,v) in event + converted_event[k] = v + if k == "event_data" + for (_k,_v) in v + converted_event[k][_k] = _v + if _k == "state" && !isa(_v, PMD.SwitchState) + converted_event[k][_k] = lowercase(_v) == "closed" ? PMD.CLOSED : PMD.OPEN + elseif _k == "status" && !isa(_v, PMD.Status) + converted_event[k][_k] = PMD.Status(_v) + elseif _k == "dispatchable" && !isa(_v, PMD.Dispatchable) + converted_event[k][_k] = PMD.Dispatchable(Int(_v)) + end + converted_event[k][_k] = string(converted_event[k][_k]) + end + end + end + push!(converted_custom_events, converted_event) + else + # nothing to do + push!(converted_custom_events, event) + end + end + + append!(events, converted_custom_events) + + return events +end + + +""" + build_events_file(case_file::String, io::IO; kwargs...) + +A helper function to save a rudamentary events data structure to `io` from a network case at path `case_file`. +""" +build_events_file(case_file::String, io::IO; kwargs...) = JSON.print(io, build_events(case_file; kwargs...)) + + +""" + build_events_file(eng::Dict{String,<:Any}, io::IO; kwargs...) + +A helper function to save a rudamentary events data structure to `io` from a network case `eng`. +""" +build_events_file(eng::Dict{String,<:Any}, io::IO; kwargs...) = JSON.print(io, build_events(eng; kwargs...)) + +""" + build_events_file(case_file::String, events_file::String; kwargs...) + +A helper function to build a rudamentary `events_file` from a network case at path `case_file`. +""" +build_events_file(case_file::String, events_file::String; kwargs...) = build_events_file(PMD.parse_file(case_file), events_file; kwargs...) + + +""" + build_events_file(eng::Dict{String,<:Any}, events_file::String; kwargs...) + +A helper function to build a rudamentary `events_file` from a network case `eng`. +""" +function build_events_file(eng::Dict{String,<:Any}, events_file::String; kwargs...) open(events_file, "w") do io - JSON.print(io, events) + build_events_file(eng, io; kwargs...) end end diff --git a/src/io/faults.jl b/src/io/faults.jl index 1d9c4ed3..9c622e39 100644 --- a/src/io/faults.jl +++ b/src/io/faults.jl @@ -1,5 +1,8 @@ """ - parse_faults(faults_file::String; validate::Bool=true)::Dict{String,Any} + parse_faults( + faults_file::String; + validate::Bool=true + )::Dict{String,Any} Parses fault JSON input files which have the same structure as the outputs from `PowerModelsProtection.build_mc_fault_stuides` @@ -61,7 +64,11 @@ function parse_faults(faults_file::String; validate::Bool=true)::Dict{String,Any end -"helper function to help parse data types for multiconductor fault study data structures from JSON" +""" + _fix_fault_data_types!(faults::Dict{String,<:Any}) + +Helper function to help parse data types for multiconductor fault study data structures from JSON `faults`. +""" function _fix_fault_data_types!(faults::Dict{String,<:Any}) if isa(faults, Dict) for (k,v) in faults @@ -83,7 +90,7 @@ end """ count_faults(faults::Dict{String,<:Any})::Int -Helper function to count the total number of faults +Helper function to count the total number of faults. """ function count_faults(faults::Dict{String,<:Any})::Int count = 0 diff --git a/src/io/graphml.jl b/src/io/graphml.jl new file mode 100644 index 00000000..918a5b93 --- /dev/null +++ b/src/io/graphml.jl @@ -0,0 +1,508 @@ +""" + InfrastructureGraph + +Abstract type for Infrastructure graph structures +""" +abstract type InfrastructureGraph end + +""" + UnnestedGraph <: InfrastructureGraph + +Unnested graph structure, where the attributes are + +```julia +node::Vector{Pair{String,Dict{String,String}}} +edge::Vector{Dict{String,String}} +``` +""" +mutable struct UnnestedGraph <: InfrastructureGraph + node::Vector{Pair{String,Dict{String,String}}} + edge::Vector{Dict{String,String}} +end + + +""" + NestedGraph <: InfrastructureGraph + +Nested Graph structure, where the attributes are + +```julia +node::Dict{String,UnnestedGraph} +edge::Vector{Dict{String,String}} +``` +""" +mutable struct NestedGraph <: InfrastructureGraph + node::Dict{String,UnnestedGraph} + edge::Vector{Dict{String,String}} +end + + +""" + add_root_graphml_node!(doc::EzXML.Document)::EzXML.Node + +Helper function to build 'graphml' root XML Node for GraphML XML documents +""" +function add_root_graphml_node!(doc::EzXML.Document)::EzXML.Node + graphml = EzXML.ElementNode("graphml") + EzXML.setroot!(doc, graphml) + + return graphml +end + + +""" + build_graphml_node(id::String)::EzXML.Node + +Helper function to build graph 'node' XML Node for GraphML XML documents +""" +function build_graphml_node(id::String)::EzXML.Node + node = EzXML.ElementNode("node") + EzXML.link!(node, EzXML.AttributeNode("id", id)) + + return node +end + + +""" + build_graphml_edge(id::String, source::String, target::String)::EzXML.Node + +Helper function to build an 'edge' XML Node object for GraphML XML documents +""" +function build_graphml_edge(id::String, source::String, target::String)::EzXML.Node + edge = EzXML.ElementNode("edge") + EzXML.link!(edge, EzXML.AttributeNode("id", id)) + EzXML.link!(edge, EzXML.AttributeNode("source", source)) + EzXML.link!(edge, EzXML.AttributeNode("target", target)) + + return edge +end + + +""" + build_graphml_key(id::String, is_for::String, attr_name::String, attr_type::String, default::Any=missing)::EzXML.Node + +Helper function to build an XML AttributeNode for attribute data for GraphML XML documents +""" +function build_graphml_key(id::String, is_for::String, attr_name::String, attr_type::String, default::Any=missing)::EzXML.Node + key = EzXML.ElementNode("key") + EzXML.link!(key, EzXML.AttributeNode("id", id)) + EzXML.link!(key, EzXML.AttributeNode("attr.name", attr_name)) + EzXML.link!(key, EzXML.AttributeNode("attr.type", attr_type)) + EzXML.link!(key, EzXML.AttributeNode("for", is_for)) + if !ismissing(default) + default_element = EzXML.ElementNode("default") + EzXML.setnodecontent!(default_element, default) + EzXML.link!(key, default_element) + end + + return key +end + + +""" + build_graphml_graph(id::String, directed::Bool=false)::EzXML.Node + +Helper function to build a 'graph' XML Node for GraphML XML documents +""" +function build_graphml_graph(id::String, directed::Bool=false)::EzXML.Node + graph = EzXML.ElementNode("graph") + EzXML.link!(graph, EzXML.AttributeNode("id", id)) + EzXML.link!(graph, EzXML.AttributeNode("edgedefault", directed ? "directed" : "undirected")) + + return graph +end + + +""" + add_graphml_data!(node::EzXML.Node, key::String, value::Any)::EzXML.Node + +Helper function to add an AttributeNode with `key` and `value` to a `node` +""" +function add_graphml_data!(node::EzXML.Node, key::String, value::Any)::EzXML.Node + data = EzXML.ElementNode("data") + EzXML.link!(data, EzXML.AttributeNode("key", key)) + EzXML.setnodecontent!(data, value) + + EzXML.link!(node, data) + + return node +end + + +""" + build_unnested_graph(eng::Dict{String,<:Any})::UnnestedGraph + +Helper function to build an UnnestedGraph from `eng` network data. +""" +function build_unnested_graph(eng::Dict{String,<:Any})::UnnestedGraph + + bus_node_map = Dict{String,Int}(b => i-1 for (i,b) in enumerate(keys(get(eng, "bus", Dict())))) + + gr = UnnestedGraph( + Pair[ + "n$(i)"=>Dict( + "type"=>"bus", + "source_id"=>"bus.$bus", + Dict(k=>string(v) for (k,v) in get(eng["bus"], bus, Dict()))... + ) for (bus,i) in bus_node_map + ], + Dict{String,String}[] + ) + + edge_count = 0 + for t in PMD._eng_edge_elements + for (id,obj) in get(eng, t, Dict()) + if t == "transformer" && haskey(obj, "bus") + for (j,b1) in enumerate(obj["bus"][1:(end-1)]) + for b2 in obj["bus"][(j+1):end] + + edge = Dict( + "name"=>id, + "id"=>"e$edge_count", + "source" => "n$(bus_node_map[b1])", + "target" => "n$(bus_node_map[b2])", + "type"=>t, + Dict(k=>string(v) for (k,v) in obj)... + ) + edge_count += 1 + + push!(gr.edge, edge) + end + end + else + edge = Dict{String,String}( + "name"=>id, + "id"=>"e$edge_count", + "source"=>"n$(bus_node_map[obj["f_bus"]])", + "target"=>"n$(bus_node_map[obj["t_bus"]])", + "type"=>t, + Dict(k=>string(v) for (k,v) in obj)... + ) + edge_count += 1 + + push!(gr.edge, edge) + end + end + end + + for t in filter(x->x!="bus",PMD._eng_node_elements) + for (i,obj) in get(eng, t, Dict()) + + bus_node_map["$t.$i"] = length(bus_node_map) + + node = Dict{String,String}( + "type" => t, + Dict(k => string(v) for (k,v) in obj)... + ) + + push!(gr.node, "n$(bus_node_map["$t.$i"])"=>node) + + edge = Dict{String,String}( + "name"=>"virtual_edge.$t", + "id"=>"e$edge_count", + "source"=>"n$(bus_node_map["$t.$i"])", + "target"=>"n$(bus_node_map[obj["bus"]])", + "type"=>"virtual_edge" + ) + + edge_count += 1 + + push!(gr.edge, edge) + end + end + + return gr +end + + +""" + build_nested_graph(eng::Dict{String,Any})::NestedGraph + +Helper function to build a NestedGraph of network data `eng` +""" +function build_nested_graph(eng::Dict{String,Any})::NestedGraph + @assert !ismultinetwork(eng) "This function does not take multinetwork data" + @assert PMD.iseng(eng) "This function only takes ENGINEERING data models" + + cc = Dict(i-1 => block for (i,block) in enumerate(PMD.calc_connected_components(eng; type="load_blocks", check_enabled=true))) + bus2bl = Dict(bus => i for (i,block) in cc for bus in block) + bus_bl_node_map = Dict(i => Dict(bus => n-1 for (n,bus) in enumerate(block)) for (i,block) in cc) + node_2_bus_map = Dict("n$i::n$node" => bus for (i,nodes) in bus_bl_node_map for (bus,node) in nodes) + + gr = NestedGraph( + Dict{String,UnnestedGraph}( + "n$i" => UnnestedGraph( + Pair[ + "n$(i)::n$(bus_bl_node_map[i][bus])"=>Dict( + "type"=>"bus", + "source_id"=>"bus.$bus", + Dict(k=>string(v) for (k,v) in get(eng["bus"], bus,Dict()))... + ) for bus in bl + ], + Dict{String,String}[] + ) for (i,bl) in cc + ), + Dict{String,String}[] + ) + + edge_count = 0 + for t in PMD._eng_edge_elements + for (id,obj) in get(eng, t, Dict()) + if t == "transformer" && haskey(obj, "bus") + for (j,b1) in enumerate(obj["bus"][1:(end-1)]) + for b2 in obj["bus"][(j+1):end] + bid_fr = bus2bl[b1] + bid_to = bus2bl[b2] + + edge = Dict( + "name"=>id, + "id"=>"e$edge_count", + "source"=>"n$(bid_fr)::n$(bus_bl_node_map[bid_fr][b1])", + "target"=>"n$(bid_to)::n$(bus_bl_node_map[bid_to][b2])", + "type"=>t, + Dict(k=>string(v) for (k,v) in obj)... + ) + edge_count += 1 + + if bid_fr == bid_to + push!(gr.node["n$bid_fr"].edge, edge) + else + push!(gr.edge, edge) + end + end + end + else + bid_fr = bus2bl[obj["f_bus"]] + bid_to = bus2bl[obj["t_bus"]] + + edge = Dict{String,String}( + "name"=>id, + "id"=>"e$edge_count", + "source"=>"n$(bid_fr)::n$(bus_bl_node_map[bid_fr][obj["f_bus"]])", + "target"=>"n$(bid_to)::n$(bus_bl_node_map[bid_to][obj["t_bus"]])", + "type"=>t, + Dict(k=>string(v) for (k,v) in obj)... + ) + edge_count += 1 + + if bid_fr == bid_to + push!(gr.node["n$bid_fr"].edge, edge) + else + push!(gr.edge, edge) + end + end + end + end + + for t in filter(x->x!="bus",PMD._eng_node_elements) + for (i,obj) in get(eng, t, Dict()) + bid_fr = bid_to = bus2bl[obj["bus"]] + + bus_bl_node_map[bid_fr]["$t.$i"] = length(bus_bl_node_map[bid_fr]) + + node = Dict{String,String}( + "type" => t, + Dict(k => string(v) for (k,v) in obj)... + ) + + push!(gr.node["n$(bid_fr)"].node, "n$(bid_fr)::n$(bus_bl_node_map[bid_fr]["$t.$i"])"=>node) + + node_2_bus_map["n$(bid_fr)::n$(bus_bl_node_map[bid_fr]["$t.$i"])"] = "$t.$i" + + edge = Dict{String,String}( + "name"=>"virtual_edge.$t", + "id"=>"e$edge_count", + "source"=>"n$(bid_fr)::n$(bus_bl_node_map[bid_fr]["$t.$i"])", + "target"=>"n$(bid_to)::n$(bus_bl_node_map[bid_to][obj["bus"]])", + "type"=>"virtual_edge" + ) + + edge_count += 1 + + push!(gr.node["n$bid_fr"].edge, edge) + end + end + + return gr +end + + +""" + build_graphml_document(eng::Dict{String,<:Any}; type::Type="nested") + +Helper function to build GraphML XML document from a `eng` network data structure. + +`type` controls whether the resulting graph is a NestedGraph, i.e., buses are contained within load blocks, +or a UnnestedGraph, where node groups are not utilized. +""" +build_graphml_document(eng::Dict{String,<:Any}; type::String="nested") = type == "nested" ? build_graphml_document(build_nested_graph(eng)) : build_graphml_document(build_unnested_graph(eng)) + + +""" + build_graphml_document(gr::NestedGraph)::EzXML.Document + +Helper function to build GraphML XML document from a NestedGraph +""" +function build_graphml_document(gr::NestedGraph)::EzXML.Document + node_key_map = Dict() + edge_key_map = Dict() + key_int = 0 + + doc = EzXML.XMLDocument() + graphml = add_root_graphml_node!(doc) + + graph = build_graphml_graph("G", false) + + for ((node_id),subgraph) in gr.node + node = build_graphml_node(node_id) + if "type" ∉ keys(node_key_map) + EzXML.link!(graphml, build_graphml_key("d$key_int", "node", "type", "string")) + node_key_map["type"] = "d$key_int" + key_int += 1 + end + add_graphml_data!(node, node_key_map["type"], "block") + + if !isempty(subgraph.edge) + subgr = build_graphml_graph("$(node_id)::", false) + + for (sub_node_id,sub_node) in subgraph.node + subn = build_graphml_node(sub_node_id) + for (k,v) in sub_node + if k ∉ keys(node_key_map) + EzXML.link!(graphml, build_graphml_key("d$key_int", "node", k, "string")) + node_key_map[k] = "d$key_int" + key_int += 1 + end + add_graphml_data!(subn, node_key_map[k], v) + end + EzXML.link!(subgr, subn) + end + for edge in subgraph.edge + sube = build_graphml_edge(edge["id"], edge["source"], edge["target"]) + for (k,v) in filter(x->x.first∉["id","source","target"], edge) + if k ∉ keys(edge_key_map) + EzXML.link!(graphml, build_graphml_key("d$key_int", "edge", k, "string")) + edge_key_map[k] = "d$key_int" + key_int += 1 + end + add_graphml_data!(sube, edge_key_map[k], v) + end + EzXML.link!(subgr, sube) + end + + EzXML.link!(node, subgr) + end + EzXML.link!(graph, node) + end + + for edge in gr.edge + source_block, source_node = split(edge["source"], "::") + target_block, target_node = split(edge["target"], "::") + + if isempty(gr.node[source_block].edge) + source = source_block + else + source = edge["source"] + end + + if isempty(gr.node[target_block].edge) + target = target_block + else + target = edge["target"] + end + + ed = build_graphml_edge(edge["id"], source, target) + for (k,v) in filter(x->x.first∉["id","source","target"], edge) + if k ∉ keys(edge_key_map) + EzXML.link!(graphml, build_graphml_key("d$key_int", "edge", k, "string")) + edge_key_map[k] = "d$key_int" + key_int += 1 + end + add_graphml_data!(ed, edge_key_map[k], v) + end + + EzXML.link!(graph, ed) + end + + EzXML.link!(graphml, graph) + + return doc +end + + +""" + build_graphml_document(gr::UnnestedGraph)::EzXML.Document + +Helper function to build GraphML XML document from an UnnestedGraph +""" +function build_graphml_document(gr::UnnestedGraph)::EzXML.Document + node_key_map = Dict() + edge_key_map = Dict() + key_int = 0 + + doc = EzXML.XMLDocument() + graphml = add_root_graphml_node!(doc) + + graph = build_graphml_graph("G", false) + + for (node_id,node_data) in gr.node + node = build_graphml_node(node_id) + + for (k,v) in node_data + if k ∉ keys(node_key_map) + EzXML.link!(graphml, build_graphml_key("d$key_int", "node", k, "string")) + node_key_map[k] = "d$key_int" + key_int += 1 + end + add_graphml_data!(node, node_key_map[k], v) + end + + EzXML.link!(graph, node) + end + + for edge in gr.edge + ed = build_graphml_edge(edge["id"], edge["source"], edge["target"]) + for (k,v) in filter(x->x.first∉["id","source","target"], edge) + if k ∉ keys(edge_key_map) + EzXML.link!(graphml, build_graphml_key("d$key_int", "edge", k, "string")) + edge_key_map[k] = "d$key_int" + key_int += 1 + end + add_graphml_data!(ed, edge_key_map[k], v) + end + + EzXML.link!(graph, ed) + end + + EzXML.link!(graphml, graph) + + return doc +end + + +""" + save_graphml(io::IO, eng::Dict{String,<:Any}; type::String="nested") + +Save a GraphML XML document built from `eng` network data to IO stream. + +`type` controls whether the resulting graph is a NestedGraph, i.e., buses are contained within load blocks, +or a UnnestedGraph, where node groups are not utilized. +""" +function save_graphml(io::IO, eng::Dict{String,<:Any}; type::String="nested") + EzXML.prettyprint(io, build_graphml_document(eng; type=type)) +end + + +""" + save_graphml(graphml_file::String, eng::Dict{String,<:Any}; type::String="nested") + +Save a GraphML XML document built from `eng` network data to `graphml_file`. + +`type` controls whether the resulting graph is a NestedGraph, i.e., buses are contained within load blocks, +or a UnnestedGraph, where node groups are not utilized. +""" +function save_graphml(graphml_file::String, eng::Dict{String,<:Any}; type::String="nested") + open(graphml_file, "w") do io + save_graphml(io, eng; type=type) + end +end diff --git a/src/io/inverters.jl b/src/io/inverters.jl index af2e471b..514872e3 100644 --- a/src/io/inverters.jl +++ b/src/io/inverters.jl @@ -1,5 +1,8 @@ """ - parse_inverters(inverter_file::String; validate::Bool=true)::Dict{String,Any} + parse_inverters( + inverter_file::String; + validate::Bool=true + )::Dict{String,Any} Parses an inverters JSON file, used in [`run_stability_analysis!`](@ref run_stability_analysis!) @@ -8,7 +11,7 @@ Parses an inverters JSON file, used in [`run_stability_analysis!`](@ref run_stab If `validate=true` (default), the parsed data structure will be validated against the latest [Inverters Schema](@ref Inverters-Schema). """ function parse_inverters(inverter_file::String; validate::Bool=true)::Dict{String,Any} - inverters = PowerModelsStability.parse_json(inverter_file) + inverters = PMS.parse_json(inverter_file) if validate && !validate_inverters(inverters) error("'inverters' file could not be validated") diff --git a/src/io/json.jl b/src/io/json.jl index d60c2538..0cd100e4 100644 --- a/src/io/json.jl +++ b/src/io/json.jl @@ -6,3 +6,75 @@ Loads a JSON Schema for validation, fixing the ref paths inside the schemas on l function load_schema(file::String)::JSONSchema.Schema return JSONSchema.Schema(JSON.parsefile(file); parent_dir=joinpath(dirname(pathof(PowerModelsONM)), "..")) end + + +""" + correct_json_import!(data::Dict{String,<:Any}) + +Helper function to assist in converting to correct Julia data types when importing +JSON files, like settings or events. +""" +function correct_json_import!(data::Dict{String,<:Any}) + for (k, v) in data + if isa(v, Dict) + correct_json_import!(v) + else + PMD._fix_enums!(data, k, data[k]) + PowerModelsONM._fix_enums!(data, k, data[k]) + PowerModelsONM._fix_symbols!(data, k, data[k]) + PMD._fix_arrays!(data, k, data[k]) + PowerModelsONM._fix_nulls!(data, k, data[k]) + PMD._fix_nulls!(data, k, data[k]) + end + end + return data +end + + +"helper function to convert stringified enums" +function _fix_enums!(obj, prop, val) + if isa(val, String) && uppercase(val) == val && Symbol(val) in names(PowerModelsONM) + obj[prop] = getfield(PowerModelsONM, Symbol(val)) + end +end + + +"helper function to convert stringified Symbols" +function _fix_symbols!(obj, prop, val) + obj[prop] = convert(val) +end + + +"helper function to fix null values from json (usually Inf or NaN)" +function _fix_nulls!(obj, prop, val) + if endswith(prop, "-ub") + fill_val = Inf + elseif endswith(prop, "-lb") + fill_val = -Inf + else + return + end + + if isa(val, Matrix) && any(val .=== nothing) + @debug "a 'null' was encountered in the json import, making an assumption that null values in $prop = $fill_val" + valdtype = valtype(val) + if isa(valdtype, Union) + dtype = [getproperty(valdtype, n) for n in propertynames(valdtype) if getproperty(valdtype, n) != Nothing][end] + else + dtype = valdtype + end + val[val .=== nothing] .= fill_val + obj[prop] = Matrix{valtype(val) == Nothing ? typeof(fill_val) : valtype(val)}(val) + elseif isa(val, Vector) && any(v === nothing for v in val) + @debug "a 'null' was encountered in the json import, making an assumption that null values in $prop = $fill_val" + obj[prop] = Vector{valtype(val) == Nothing ? typeof(fill_val) : valtype(val)}([v === nothing ? fill_val : v for v in val]) + elseif val === nothing + @debug "a 'null' was encountered in the json import, making an assumption that null values in $prop = $fill_val" + obj[prop] = fill_val + end +end + + + +"Helper to ensure that Symbols get exported as strings prefaced with a ':'" +JSON.lower(p::Symbol) = ":$(p)" diff --git a/src/io/network.jl b/src/io/network.jl index a8ea39f8..4390cb1c 100644 --- a/src/io/network.jl +++ b/src/io/network.jl @@ -7,7 +7,9 @@ data structure under `args["base_network"]` """ function parse_network!(args::Dict{String,<:Any})::Dict{String,Any} if isa(args["network"], String) - args["base_network"], args["network"] = parse_network(args["network"]; fix_small_numbers=get(args, "fix-small-numbers", false)) + args["base_network"], args["network"] = parse_network( + args["network"] + ) end return args["network"] @@ -15,19 +17,53 @@ end """ - parse_network(network_file::String)::Tuple{Dict{String,Any},Dict{String,Any}} + parse_network( + network_file::String + )::Tuple{Dict{String,Any},Dict{String,Any}} Parses network file given by runtime arguments into its base network, i.e., not expanded into a multinetwork, and multinetwork, which is the multinetwork `ENGINEERING` representation of the network. """ -function parse_network(network_file::String; fix_small_numbers::Bool=false)::Tuple{Dict{String,Any},Dict{String,Any}} - eng = PMD.parse_file(network_file; dss2eng_extensions=[PowerModelsProtection._dss2eng_solar_dynamics!, PowerModelsProtection._dss2eng_gen_dynamics!, _dss2eng_protection!], transformations=[PMD.apply_kron_reduction!], import_all=true) +function parse_network(network_file::String; dss2eng_extensions=Function[], transformations=Function[], import_all=true, kwargs...)::Tuple{Dict{String,Any},Dict{String,Any}} + eng = parse_file( + network_file; + dss2eng_extensions=dss2eng_extensions, + transformations=transformations, + import_all=import_all, + kwargs... + ) - if fix_small_numbers - PMD.adjust_small_line_impedances!(eng; min_impedance_val=1e-1) - PMD.adjust_small_line_admittances!(eng; min_admittance_val=1e-1) - PMD.adjust_small_line_lengths!(eng; min_length_val=10.0) - end + mn_eng = make_multinetwork(eng) + + return eng, mn_eng +end + + +""" + parse_file(network_file::String; dss2eng_extensions=Function[], transformations=Function[], import_all=true, kwargs...) + +ONM version of `PowerModelsDistribution.parse_file`, which includes some `dss2eng_extensions` and `transformations` by default +""" +function parse_file(network_file::String; dss2eng_extensions=Function[], transformations=Function[], import_all=true, kwargs...) + eng = PMD.parse_file( + network_file; + dss2eng_extensions=[ + PMP._dss2eng_solar_dynamics!, + PMP._dss2eng_gen_dynamics!, + PMP._dss2eng_curve!, + PMP._dss2eng_fuse!, + PMP._dss2eng_ct!, + PMP._dss2eng_relay!, + _dss2eng_protection_locations!, + dss2eng_extensions... + ], + transformations=[PMD.apply_kron_reduction!, transformations...], + import_all=import_all, + kwargs... + ) + + # Add default switch_close_actions_ub + eng["switch_close_actions_ub"] = Inf # TODO: add more elegant cost model adjustments for (id,obj) in get(eng, "solar", Dict()) @@ -35,27 +71,30 @@ function parse_network(network_file::String; fix_small_numbers::Bool=false)::Tup eng["solar"][id]["cost_pg_parameters"] = [0.0, 0.0] end - mn_eng = PMD.make_multinetwork(eng) - - return eng, mn_eng + return eng end """ - _dss2eng_protection!(eng::Dict{String,<:Any}, dss::Dict{String,<:Any}) + _dss2eng_protection!( + eng::Dict{String,<:Any}, + dss::Dict{String,<:Any} + ) -Extension function for converting opendss protection into protection objects for protection optimization +Extension function for converting opendss protection into protection objects for protection optimization. """ -function _dss2eng_protection!(eng::Dict{String,<:Any}, dss::Dict{String,<:Any}) +function _dss2eng_protection_locations!(eng::Dict{String,<:Any}, dss::Dict{String,<:Any}) for type in ["relay", "recloser", "fuse"] - if !isempty(get(dss, type, Dict())) + if !isempty(get(dss, type, Dict())) && !haskey(eng, type) eng[type] = Dict{String,Any}() end for (id, dss_obj) in get(dss, type, Dict()) - eng[type][id] = Dict{String,Any}( - "location" => dss_obj["monitoredobj"], - ) + if !haskey(eng[type], id) + eng[type][id] = Dict{String,Any}() + end + eng[type][id]["location"] = dss_obj["monitoredobj"] + eng[type][id]["monitor_type"] = string(split(dss_obj["monitoredobj"], ".")[1]) end end end @@ -73,17 +112,17 @@ const _pnm2eng_objects = Dict{String,Vector{String}}( """ get_protection_network_model!(args::Dict{String,<:Any}) -Builds a network data model for use in Protection settings optimization +Builds a network data model for use in Protection settings optimization. """ function get_protection_network_model!(args::Dict{String,<:Any}) - args["output_data"]["Protection settings"]["network_model"] = get_protection_network_model(args["base_network"]) + args["output_data"]["Protection settings"]["network_model"] = get_protection_network_model(get(args, "base_network", Dict{String,Any}())) end """ get_protection_network_model(base_eng::Dict{String,<:Any}) -Builds a network data model for use in Protection optimization +Builds a network data model for use in Protection optimization from the base network model `base_eng`. """ function get_protection_network_model(base_eng::Dict{String,<:Any}) pnm = Dict{String,Vector{Dict{String,Any}}}( @@ -100,6 +139,7 @@ function get_protection_network_model(base_eng::Dict{String,<:Any}) "name" => id, "phases" => obj["terminals"], "nphases" => length(obj["terminals"]), + "status" => Int(obj["status"]), )) end end @@ -114,6 +154,7 @@ function get_protection_network_model(base_eng::Dict{String,<:Any}) "t_connections" => obj["t_connections"], "nphases" => length(obj["f_connections"]), "switch" => type == "switch", + "status" => Int(obj["status"]), )) end end @@ -129,6 +170,7 @@ function get_protection_network_model(base_eng::Dict{String,<:Any}) "nwindings" => length(obj["bus"]), "nphases" => length(first(obj["connections"])), "configuration" => string.(obj["configuration"]), + "status" => Int(obj["status"]), )) end end @@ -141,6 +183,7 @@ function get_protection_network_model(base_eng::Dict{String,<:Any}) "bus" => obj["bus"], "connections" => obj["connections"], "nphases" => length(obj["connections"]), + "status" => Int(obj["status"]), )) end end @@ -166,22 +209,41 @@ Gets bus types (PQ, PV, ref, isolated) for each timestep from the optimal dispat and assigns it to `args["output_data"]["Protection settings"]["bus_types"]` """ function get_timestep_bus_types!(args::Dict{String,<:Any})::Vector{Dict{String,String}} - args["output_data"]["Protection settings"]["bus_types"] = get_timestep_bus_types(get(get(args, "optimal_dispatch_result", Dict{String,Any}()), "solution", Dict{String,Any}()), get(args, "network", Dict{String,Any}())) + args["output_data"]["Protection settings"]["bus_types"] = get_timestep_bus_types( + get(get(args, "optimal_dispatch_result", Dict{String,Any}()), "solution", Dict{String,Any}()), get(args, "network", Dict{String,Any}()) + ) end """ - get_timestep_bus_types(optimal_dispatch_solution::Dict{String,<:Any}, network::Dict{String,<:Any})::Vector{Dict{String,String}} + get_timestep_bus_types(::Dict{String,<:Any}, ::String)::Vector{Dict{String,String}} -Gets bus types (PQ, PV, ref, isolated) for each timestep from the optimal dispatch solution +Helper function for the variant where `args["network"]` hasn't been parsed yet. +""" +get_timestep_bus_types(::Dict{String,<:Any}, ::String)::Vector{Dict{String,String}} = Dict{String,String}[] + + +""" + get_timestep_bus_types( + optimal_dispatch_solution::Dict{String,<:Any}, + network::Dict{String,<:Any} + )::Vector{Dict{String,String}} + +Gets bus types (PQ, PV, ref, isolated) for each timestep from the `optimal_dispatch_solution` """ function get_timestep_bus_types(optimal_dispatch_solution::Dict{String,<:Any}, network::Dict{String,<:Any})::Vector{Dict{String,String}} timesteps = Dict{String,String}[] for n in sort(parse.(Int, collect(keys(get(optimal_dispatch_solution, "nw", Dict()))))) + nw = network["nw"]["$n"] + buses = collect(keys(get(nw, "bus", Dict{String,Any}()))) + vsource_buses = [vs["bus"] for (_,vs) in get(network["nw"]["$n"], "voltage_source", Dict()) if vs["status"] == PMD.ENABLED] timestep = Dict{String,String}() - for (id,bus) in get(optimal_dispatch_solution["nw"]["$n"], "bus", Dict()) + nw_sol_bus = get(optimal_dispatch_solution["nw"]["$n"], "bus", Dict()) + for id in buses + bus = get(nw_sol_bus, id, Dict("bus_type"=>4)) + timestep[id] = Dict{Int,String}(1=>"pq",2=>"pv",3=>"ref",4=>"isolated")[get(bus, "bus_type", 1)] if id in vsource_buses timestep[id] = "ref" @@ -192,3 +254,34 @@ function get_timestep_bus_types(optimal_dispatch_solution::Dict{String,<:Any}, n return timesteps end + + +""" + make_multinetwork(eng::Dict{String,<:Any}; global_keys::Set{String}=Set{String}(), time_elapsed::Union{Real,Vector{<:Real},Missing}=missing, kwargs...) + +ONM-specific version of make_multinetwork that adds in switch_close_actions_ub +""" +function make_multinetwork(eng::Dict{String,<:Any}; global_keys::Set{String}=Set{String}(), time_elapsed::Union{Real,Vector{<:Real},Missing}=missing, kwargs...) + mn_eng = PMD.make_multinetwork(eng; global_keys=union(global_keys, Set{String}(["options", "solvers"])), time_elapsed=ismissing(time_elapsed) ? get(eng, "time_elapsed", missing) : time_elapsed, kwargs...) + + switch_close_actions_ub = get(get(get(mn_eng, "options", Dict()), "data", Dict()), "switch-close-actions-ub", missing) + if !ismissing(switch_close_actions_ub) + set_switch_close_actions_ub!(mn_eng, switch_close_actions_ub) + end + + return mn_eng +end + + +""" + set_switch_close_actions_ub!(mn_eng::Dict{String,<:Any}, switch_close_actions_ub::Union{Vector{<:Real},Real}) + +Helper function to populate switch_close_actions_ub per timestep in a multinetwork data structure. +""" +function set_switch_close_actions_ub!(mn_eng::Dict{String,<:Any}, switch_close_actions_ub::Union{Vector{<:Real},Real}) + @assert PMD.ismultinetwork(mn_eng) + + for n in sort(parse.(Int,collect(keys(mn_eng["nw"])))) + mn_eng["nw"]["$n"]["switch_close_actions_ub"] = isa(switch_close_actions_ub, Vector) ? switch_close_actions_ub[n] : switch_close_actions_ub + end +end diff --git a/src/io/output.jl b/src/io/output.jl index a9188f58..a41d8085 100644 --- a/src/io/output.jl +++ b/src/io/output.jl @@ -1,62 +1,104 @@ +"Lookup for JSON to Julia type conversions" +const _json_schema_type_conversions = Dict{Union{Missing,String},Type}( + "string"=>String, + "array"=>Vector, + "integer"=>Int, + "number"=>Real, + "boolean"=>Bool, + missing=>Any, + "object"=>Dict{String,Any}, + "null"=>Union{Real,Nothing,Missing}, # Inf,NaN,missing +) + + +""" + _recursive_initialize_output_from_schema!(output::Dict{String,<:Any}, schema_properties::Dict{String,<:Any}) + +Helper function to initialize the output data structure from the output schema +""" +function _recursive_initialize_output_from_schema!(output::Dict{String,<:Any}, schema_properties::Dict{String,<:Any}) + for (prop_name,prop) in schema_properties + if haskey(prop, "\$ref") + _prop = prop["\$ref"] + else + _prop = prop + end + + if get(_prop, "type", "") == "object" + output[prop_name] = Dict{String,Any}() + _recursive_initialize_output_from_schema!(output[prop_name], get(_prop, "properties", Dict{String,Any}())) + elseif get(_prop, "type", "") == "array" + if haskey(_prop["items"], "\$ref") + raw_subtype = get(_prop["items"]["\$ref"], "type", missing) + else + raw_subtype = get(_prop["items"], "type", missing) + end + + if isa(raw_subtype, Vector) + subtype = Union{[_json_schema_type_conversions[t] for t in raw_subtype]...} + else + subtype = _json_schema_type_conversions[raw_subtype] + end + + if subtype == Vector + raw_subsubtype = get(_prop["items"]["items"], "type", missing) + if isa(raw_subsubtype, Vector) + subsubtype = Union{[_json_schema_type_conversions[t] for t in raw_subsubtype]...} + else + subsubtype = _json_schema_type_conversions[raw_subsubtype] + end + output[prop_name] = Vector{subtype{subsubtype}}([]) + else + output[prop_name] = Vector{subtype}([]) + end + elseif get(_prop, "readOnly", false) + output[prop_name] = @eval $(Meta.parse(_prop["default"])) + end + end + return output +end + + """ initialize_output(args::Dict{String,<:Any})::Dict{String,Any} Initializes the empty data structure for "output_data" """ -function initialize_output(args::Dict{String,<:Any})::Dict{String,Any} - _deepcopy_args!(args) - - Dict{String,Any}( - "Runtime arguments" => deepcopy(args["raw_args"]), - "Simulation time steps" => Any[], - "Load served" => Dict{String,Any}( - "Feeder load (%)" => Real[], - "Microgrid load (%)" => Real[], - "Bonus load via microgrid (%)" => Real[], - ), - "Generator profiles" => Dict{String,Any}( - "Grid mix (kW)" => Real[], - "Solar DG (kW)" => Real[], - "Energy storage (kW)" => Real[], - "Diesel DG (kW)" => Real[], - ), - "Voltages" => Dict{String,Any}( - "Min voltage (p.u.)" => Real[], - "Mean voltage (p.u.)" => Real[], - "Max voltage (p.u.)" => Real[], - ), - "Storage SOC (%)" => Real[], - "Device action timeline" => Dict{String,Any}[], - "Powerflow output" => Dict{String,Any}[], - "Additional statistics" => Dict{String,Any}(), - "Events" => Dict{String,Any}[], - "Fault currents" => Dict{String,Any}[], - "Small signal stable" => Bool[], - "Runtime timestamp" => "$(Dates.now())", - "Optimal switching metadata" => Dict{String,Any}[], - "Optimal dispatch metadata" => Dict{String,Any}(), - "Fault studies metadata" => Dict{String,Any}[], - "System metadata" => Dict{String,Any}( - "platform" => string(Sys.MACHINE), - "cpu_info" => string(first(Sys.cpu_info()).model), - "physical_cores" => Hwloc.num_physical_cores(), - "logical_processors" => Hwloc.num_virtual_cores(), - "system_memory" => round(Int, Sys.total_memory() / 2^20 / 1024), - "julia_max_threads" => Threads.nthreads(), - "julia_max_procs" => Distributed.nprocs(), - "julia_version" => string(Base.VERSION), - ), - "Protection settings" => Dict{String,Any}( - "network_model" => Dict{String,Vector{Dict{String,Any}}}(), - "bus_types" => Vector{Dict{String,String}}(), - "settings" => Vector{Vector{Dict{String,Any}}}(), # TODO - ), - ) +function initialize_output(raw_args::Union{Dict{String,<:Any},Missing}; current_args::Dict{String,<:Any}=Dict{String,Any}())::Dict{String,Any} + output_schema = load_schema(joinpath(dirname(pathof(PowerModelsONM)), "..", "schemas/output.schema.json")) + + output = Dict{String,Any}() + output = _recursive_initialize_output_from_schema!(output, get(output_schema.data, "properties", Dict{String,Any}())) + + if !ismissing(raw_args) + output["Runtime arguments"] = deepcopy(raw_args) + else + runtime_arg_schema = load_schema(joinpath(dirname(pathof(PowerModelsONM)), "../schemas/input-runtime_arguments.schema.json")) + rt_arg_type = Dict(k=>v["type"] for (k,v) in runtime_arg_schema.data["properties"]) + _raw_args = Dict{String,Any}() + for (k,v) in current_args + if k in keys(rt_arg_type) && isa(v, _json_schema_type_conversions[rt_arg_type[k]]) + _raw_args[k] = deepcopy(v) + end + end + if !haskey(_raw_args, "network") && haskey(current_args, "base_network") + _raw_args["network"] = get(current_args["base_network"], "files", [""])[1] + end + if !isempty(_raw_args) && haskey(_raw_args, "network") + output["Runtime arguments"] = _raw_args + end + end + + return output end """ - write_json(file::String, data::Dict{String,<:Any}; indent::Union{Int,Missing}=missing) + write_json( + file::String, + data::Dict{String,<:Any}; + indent::Union{Int,Missing}=missing + ) Write JSON `data` to `file`. If `!ismissing(indent)`, JSON will be pretty-formatted with `indent` """ @@ -77,7 +119,7 @@ end Initializes the output data strucutre inside of the args dict at "output_data" """ function initialize_output!(args::Dict{String,<:Any})::Dict{String,Any} - args["output_data"] = initialize_output(args) + args["output_data"] = initialize_output(get(args, "raw_args", missing); current_args=args) end @@ -98,13 +140,19 @@ Adds information and statistics to "output_data", including - `"Switch changes"`: [`get_timestep_switch_changes!`](@ref get_timestep_switch_changes!) - `"Small signal stability"`: [`get_timestep_stability!`](@ref get_timestep_stability!) - `"Fault currents"`: [`get_timestep_fault_currents!`](@ref get_timestep_fault_currents!) +- `"Optimal dispatch metadata"`: [`get_timestep_dispatch_optimization_metadata!`](@ref get_timestep_dispatch_optimization_metadata!) +- `"Optimal switching metadata"`: [`get_timestep_switch_optimization_metadata!`](@ref get_timestep_switch_optimization_metadata!) """ function analyze_results!(args::Dict{String,<:Any})::Dict{String,Any} if !haskey(args, "output_data") initialize_output!(args) end - args["output_data"]["Simulation time steps"] = [args["network"]["mn_lookup"]["$n"] for n in sort([parse(Int,i) for i in keys(args["network"]["mn_lookup"])]) ] + if isa(get(args, "network", ""), Dict) + args["output_data"]["Simulation time steps"] = [args["network"]["mn_lookup"]["$n"] for n in sort([parse(Int,i) for i in keys(args["network"]["mn_lookup"])]) ] + else + args["output_data"]["Simulation time steps"] = Real[] + end args["output_data"]["Events"] = get(args, "raw_events", Dict{String,Any}[]) get_timestep_voltage_statistics!(args) @@ -114,6 +162,7 @@ function analyze_results!(args::Dict{String,<:Any})::Dict{String,Any} get_timestep_storage_soc!(args) get_timestep_dispatch!(args) + get_timestep_inverter_states!(args) # must run after get_timestep_dispatch! get_timestep_dispatch_optimization_metadata!(args) get_timestep_device_actions!(args) diff --git a/src/io/settings.jl b/src/io/settings.jl index 74268145..c7185f30 100644 --- a/src/io/settings.jl +++ b/src/io/settings.jl @@ -1,9 +1,22 @@ +"Lookup for deprecated settings conversions that are not 1-to-1" +const settings_conversions = Dict{Tuple{Vararg{String}},Function}( + ("solvers","HiGHS","presolve") => x->x ? "off" : "choose", + ("solvers","Gurobi","Presolve") => x->x ? 0 : -1, + ("solvers","KNITRO","presolve") => x->Int(!x), + ("options","problem","operations-algorithm") => x->x∈["complete horizon", "global"] ? "full-lookahead" : x∈["rolling horizon", "iterative"] ? "rolling-horizon" : x, +) + + """ - parse_settings!(args::Dict{String,<:Any}; apply::Bool=true, validate::Bool=true)::Dict{String,Any} + parse_settings!( + args::Dict{String,<:Any}; + apply::Bool=true, + validate::Bool=true + )::Dict{String,Any} Parses settings file specifed in runtime arguments in-place -Will attempt to convert depreciated runtime arguments to appropriate network settings +Will attempt to convert deprecated runtime arguments to appropriate network settings data structure. ## Validation @@ -13,49 +26,216 @@ If `validate=true` (default), the parsed data structure will be validated agains function parse_settings!(args::Dict{String,<:Any}; apply::Bool=true, validate::Bool=true)::Dict{String,Any} if !isempty(get(args, "settings", "")) if isa(args["settings"], String) - args["settings"] = parse_settings(args["settings"]; validate=validate) + settings = parse_settings(args["settings"]; validate=validate) + + # Handle deprecated command line arguments + correct_deprecated_settings!(settings) + correct_deprecated_runtime_args!(args, settings) + + args["settings"] = settings end else - args["settings"] = Dict{String,Any}() + args["settings"] = build_default_settings() end - # Handle depreciated command line arguments - _convert_depreciated_runtime_args!(args, args["settings"], args["base_network"], length(args["network"]["nw"])) - - apply && apply_settings!(args) + apply && isa(get(args, "network", ""), Dict) && apply_settings!(args) return args["settings"] end -"helper function to convert depreciated runtime arguments to their appropriate network settings structure" -function _convert_depreciated_runtime_args!(runtime_args::Dict{String,<:Any}, settings::Dict{String,<:Any}, base_network::Dict{String,<:Any}, timesteps::Int)::Tuple{Dict{String,Any},Dict{String,Any}} - haskey(runtime_args, "voltage-lower-bound") && _convert_voltage_bound_to_settings!(settings, base_network, "vm_lb", pop!(runtime_args, "voltage-lower-bound")) - haskey(runtime_args, "voltage-upper-bound") && _convert_voltage_bound_to_settings!(settings, base_network, "vm_ub", pop!(runtime_args, "voltage-upper-bound")) - haskey(runtime_args, "voltage-angle-difference") && _convert_to_settings!(settings, base_network, "line", "vad_lb", -runtime_args["voltage-angle-difference"]) - haskey(runtime_args, "voltage-angle-difference") && _convert_to_settings!(settings, base_network, "line", "vad_ub", pop!(runtime_args, "voltage-angle-difference")) - haskey(runtime_args, "clpu-factor") && _convert_to_settings!(settings, base_network, "load", "clpu_factor", pop!(runtime_args, "clpu-factor"); multiphase=false) +""" + correct_settings!(settings::Dict{Strinig,<:Any}) + +Helper function to correct deprecated settings and convert JSON types to Julia types +""" +function correct_settings!(settings::Dict{String,<:Any})::Dict{String,Any} + correct_json_import!(settings) + correct_deprecated_settings!(settings) + + return settings +end + + +""" + build_default_settings()::Dict{String,Any} + +Builds a set of default settings from the settings schema +""" +function build_default_settings()::Dict{String,Any} + settings_schema = load_schema(joinpath(dirname(pathof(PowerModelsONM)), "..", "schemas/input-settings.schema.json")) + + settings = init_settings_default!(Dict{String,Any}(), settings_schema.data) + + return filter(x->!isempty(x.second),correct_json_import!(settings)) +end + - for k in ["disable-switch-penalty", "apply-switch-scores", "disable-radial-constraint", "disable-isolation-constraint", "max-switch-actions"] - if haskey(runtime_args, k) - settings[replace(k, "-"=>"_")] = pop!(runtime_args, k) +""" + init_settings_default!(settings::T, schema::T)::T where T <: Dict{String,Any} + +Helper function to walk through the settings schema to initalize the default set of settings. +""" +function init_settings_default!(settings::T, schema::T)::T where T <: Dict{String,Any} + if haskey(schema, "properties") + for (prop_name,props) in schema["properties"] + if !get(props, "deprecated", false) + if isa(props, Dict) && haskey(props, "properties") + settings[prop_name] = Dict{String,Any}() + init_settings_default!(settings[prop_name], props) + elseif isa(props, Dict) && haskey(props, "\$ref") + settings[prop_name] = Dict{String,Any}() + init_settings_default!(settings[prop_name], props["\$ref"]) + else + if haskey(props, "default") + settings[prop_name] = props["default"] + end + end + end end end - if haskey(runtime_args, "timestep-hours") - settings["time_elapsed"] = fill(pop!(runtime_args, "timestep-hours"), timesteps) + return settings +end + + +""" + get_deprecated_properties(schema::JSONSchema.Schema)::Dict{String,Any} + +Walks through the settings schema to collect the deprecated properities +""" +function get_deprecated_properties(schema::JSONSchema.Schema)::Dict{String,Any} + get_deprecated_properties(schema.data) +end + + +""" + get_deprecated_properties(schema::T; deprecated_properties::Union{T,Missing}=missing)::T where T <: Dict{String,Any} + +Recursive function to walk through a schema to discover the deprecated properties +""" +function get_deprecated_properties(schema::T; deprecated_properties::Union{T,Missing}=missing)::T where T <: Dict{String,Any} + if ismissing(deprecated_properties) + deprecated_properties = T() end - if haskey(runtime_args, "solver-tolerance") - settings["nlp_solver_tol"] = pop!(runtime_args, "solver-tolerance") + for (prop_name, prop) in get(schema, "properties", T()) + if get(prop, "deprecated", false) + deprecated_properties[prop_name] = [] + rmatch = match(r"deprecated:\s*{*([\w\/\_\-\,]+)}*", get(prop,"description","")) + if rmatch !== nothing + paths = [string.(split(item, "/")) for item in split(rmatch.captures[1],",")] + for path in paths + new_path = [] + for segment in path + if segment == "missing" + segment = missing + end + push!(new_path, segment) + end + push!(deprecated_properties[prop_name], Tuple(new_path)) + end + end + elseif haskey(prop, "\$ref") + _dps = get_deprecated_properties(prop["\$ref"]) + if !isempty(_dps) + deprecated_properties[prop_name] = _dps + end + elseif haskey(prop, "properties") + _dps = get_deprecated_properties(prop) + if !isempty(_dps) + deprecated_properties[prop_name] = _dps + end + end + end + + return deprecated_properties +end + + +""" + correct_deprecated_properties!(orig_properties::T, new_properties::T, deprecated_properties::T)::Tuple{T,T} where T <: Dict{String,Any} + +Helper function for correcting properties that have been deprecated in `orig_properties` into `new_properties` +""" +function correct_deprecated_properties!(orig_properties::T, new_properties::T, deprecated_properties::T)::Tuple{T,T} where T <: Dict{String,Any} + for prop in keys(filter(x->x.first∈keys(deprecated_properties),orig_properties)) + new_properties[prop] = pop!(orig_properties, prop) + end + correct_deprecated_properties!(new_properties, deprecated_properties) + + return orig_properties, new_properties +end + + +""" + correct_deprecated_properties!(properties::T, deprecated_properties::T)::T where T <: Dict{String,Any} + +Helper function for correcting properties that have been deprecated in `properties` +""" +function correct_deprecated_properties!(properties::T, deprecated_properties::T)::T where T <: Dict{String,Any} + for prop in keys(filter(x->x.first∈keys(deprecated_properties),properties)) + paths = deprecated_properties[prop] + + if isa(paths, Dict) + correct_deprecated_properties!(properties[prop], paths) + else + v = pop!(properties, prop) + for path in paths + if !ismissing(path) + set_dict_value!(properties, path, convert(v,path)) + end + end + end end + return properties +end + + +""" + correct_deprecated_settings!(settings::T)::T where T <: Dict{String,Any} + +Helper function for correcting deprecated properties in `settings` +""" +function correct_deprecated_settings!(settings::T)::T where T <: Dict{String,Any} + settings_schema = load_schema(joinpath(dirname(pathof(PowerModelsONM)), "..", "schemas/input-settings.schema.json")) + + deprecated_settings = get_deprecated_properties(settings_schema) + + settings = correct_deprecated_properties!(settings, deprecated_settings) + + return settings +end + + +""" + convert_deprecated_runtime_args!( + runtime_args::Dict{String,<:Any}, + settings::Dict{String,<:Any}, + base_network::Dict{String,<:Any}, + timesteps::Int + )::Tuple{Dict{String,Any},Dict{String,Any}} + +Helper function to convert deprecated runtime arguments to their appropriate network settings structure +""" +function correct_deprecated_runtime_args!(runtime_args::T, settings::T)::Tuple{T,T} where T <: Dict{String,Any} + rt_args_schema = load_schema(joinpath(dirname(pathof(PowerModelsONM)), "..", "schemas/input-runtime_arguments.schema.json")) + + deprecated_args = get_deprecated_properties(rt_args_schema) + + runtime_args, settings = correct_deprecated_properties!(runtime_args, settings, deprecated_args) + return runtime_args, settings end """ - parse_settings(settings_file::String; validate::Bool=true)::Dict{String,Any} + parse_settings( + settings_file::String; + validate::Bool=true + correct::Bool=true + )::Dict{String,Any} Parses network settings JSON file. @@ -63,161 +243,280 @@ Parses network settings JSON file. If `validate=true` (default), the parsed data structure will be validated against the latest [Settings Schema](@ref Settings-Schema). """ -function parse_settings(settings_file::String; validate::Bool=true)::Dict{String,Any} - settings = JSON.parsefile(settings_file) +function parse_settings(settings_file::String; validate::Bool=true, correct::Bool=true)::Dict{String,Any} + user_settings = JSON.parsefile(settings_file) - if validate && !validate_settings(settings) - error("'settings' file could not be validated") + if validate && !validate_settings(user_settings) + error("'settings' file could not be validated:\n$(evaluate_settings(user_settings))") end - PMD.correct_json_import!(settings) + correct && correct_settings!(user_settings) - return settings + return recursive_merge(build_default_settings(), user_settings) end """ apply_settings!(args::Dict{String,Any})::Dict{String,Any} -Applies settings to the network +Applies settings to the network. """ function apply_settings!(args::Dict{String,Any})::Dict{String,Any} - args["network"] = apply_settings(args["network"], get(args, "settings", Dict())) + args["base_network"] = apply_settings(args["base_network"], get(args, "settings", Dict())) + args["network"] = make_multinetwork(args["base_network"]) end """ - apply_settings(network::Dict{String,<:Any}, settings::Dict{String,<:Any})::Dict{String,Any} + apply_settings( + network::Dict{String,<:Any}, + settings::Dict{String,<:Any} + )::Dict{String,Any} -Applies `settings` to multinetwork `network` +Applies `settings` to single-network `network` """ -function apply_settings(network::Dict{String,<:Any}, settings::Dict{String,<:Any})::Dict{String,Any} - mn_data = deepcopy(network) +function apply_settings(network::T, settings::T; multinetwork::Bool=true)::T where T <: Dict{String,Any} + @assert !PMD.ismultinetwork(network) - for (s, setting) in settings - if s in PMD.pmd_eng_asset_types - _apply_to_network!(mn_data, s, setting) - elseif s == "time_elapsed" - PMD.set_time_elapsed!(mn_data, setting) - elseif s == "max_switch_actions" - for n in sort([parse(Int, i) for i in keys(mn_data["nw"])]) - mn_data["nw"]["$n"][s] = isa(setting, Vector) ? setting[n] : setting - end - elseif s ∈ ["disable_networking", "disable_switch_penalty", "apply_switch_scores", "disable_radial_constraint", "disable_isolation_constraint"] - for (_,nw) in mn_data["nw"] - nw[s] = setting - end - elseif s == "settings" - for n in sort([parse(Int, i) for i in keys(mn_data["nw"])]) - for (k,v) in setting - if isa(v, Dict) - merge!(mn_data["nw"]["$n"]["settings"][k], v) - else - mn_data["nw"]["$n"]["settings"][k] = v - end - end - end - end + eng = recursive_merge(recursive_merge(deepcopy(network), filter(x->x.first!="dss",settings)), parse_dss_settings(get(settings, "dss", Dict{String,Any}()), network)) + + if get_option(eng, ("options","data","fix-small-numbers"), false) + @info "fix-small-numbers algorithm applied" + PMD.adjust_small_line_impedances!(eng; min_impedance_val=1e-1) + PMD.adjust_small_line_admittances!(eng; min_admittance_val=1e-1) + PMD.adjust_small_line_lengths!(eng; min_length_val=10.0) + end + + if !ismissing(get_option(eng, ("options","data","time-elapsed"))) + eng["time_elapsed"] = multinetwork ? eng["options"]["data"]["time-elapsed"] : eng["options"]["data"]["time-elapsed"][1] end - mn_data + eng["switch_close_actions_ub"] = multinetwork ? get_option(eng, ("options","data","switch-close-actions-ub"), Inf) : get_option(eng, ("options","data","switch-close-actions-ub"), Inf)[1] + + if !ismissing(get_option(eng, ("options","outputs","log-level"))) + set_log_level!(Symbol(titlecase(settings["options"]["outputs"]["log-level"]))) + end + + return eng end -"converts depreciated global settings, e.g. voltage-lower-bound, to the proper way to specify settings" -function _convert_to_settings!(settings::Dict{String,<:Any}, base_network::Dict{String,<:Any}, asset_type::String, property::String, value::Any; multiphase::Bool=true) - if haskey(base_network, asset_type) - if !haskey(settings, asset_type) - settings[asset_type] = Dict{String,Any}() - end +""" + set_option!(network::Dict{String,<:Any}, path::Tuple{Vararg{String}}, value::Any) - for (id, asset) in base_network[asset_type] - if !haskey(settings[asset_type], id) - settings[asset_type][id] = Dict{String,Any}() - end +Helper function to set a property in a `network` data structure at `path` to `value` +""" +function set_option!(network::Dict{String,<:Any}, path::Tuple{Vararg{String}}, value::Any) + if ismultinetwork(network) + mn_data = network["nw"] + _set_property!(network, path, value) + else + mn_data = Dict{String,Any}("0" => network) + end - nphases = asset_type == "bus" ? length(asset["terminals"]) : asset_type in PMD._eng_edge_elements ? asset_type == "transformer" && haskey(asset, "bus") ? length(asset["connections"][1]) : length(asset["f_connections"]) : length(asset["connections"]) + for (_, nw) in mn_data + _set_property!(nw, path, value) + end + + return network +end - settings[asset_type][id][property] = multiphase ? fill(value, nphases) : value + +""" + _set_property!(data::Dict{String,<:Any}, path::Tuple{Vararg{String}}, value::Any) + +Helper function to set a property to value at an arbitrary nested path in a dictionary +""" +function _set_property!(data::Dict{String,<:Any}, path::Tuple{Vararg{String}}, value::Any) + if length(path) > 1 + if !haskey(data, path[1]) + data[path[1]] = Dict{String,Any}() end + _set_property!(data[path[1]], path[2:end], value) + else + data[path[1]] = value end end -"helper function to convert" -function _convert_voltage_bound_to_settings!(settings::Dict{String,<:Any}, base_network::Dict{String,<:Any}, bound_name::String, bound_value::Real) - if !haskey(settings, "bus") - settings["bus"] = Dict{String,Any}() +""" + set_options!(settings::Dict{String,<:Any}, options::Dict{Tuple{Vararg{String}},<:Any}) + +Helper function to set multiple properties in an `options` at path::Tuple{Vararg{String}} to value::Any. +This does not rebuild the network data structure. +""" +function set_options!(network::Dict{String,<:Any}, options::Dict{<:Tuple{Vararg{String}},<:Any}) + for (path,value) in options + set_option!(network, path, value) end +end - bus_vbase, line_vbase = PMD.calc_voltage_bases(base_network, base_network["settings"]["vbases_default"]) - for (id,bus) in get(base_network, "bus", Dict()) - if !haskey(settings["bus"], id) - settings["bus"][id] = Dict{String,Any}() - end - settings["bus"][id][bound_name] = fill(bound_value * bus_vbase[id], length(bus["terminals"])) +""" + set_setting!(args::Dict{String,<:Any}, path::Tuple{Vararg{String}}, value::Any) + +Helper function to set an option at `path` to `value` and then regenerate the multinetwork data from `args`. +""" +function set_setting!(args::Dict{String,<:Any}, path::Tuple{Vararg{String}}, value::Any) + _set_property!(args, ("settings", path...), value) + + apply_settings!(args) + apply_events!(args) +end + + +""" + set_settings!(args, options::Dict{Tuple{Vararg{String}},<:Any}) + +Helper function to set multiple options at `path` to `value` and then regenerate the multinetwork data from `args`, +where the paths are the keys of the `options` input dictionary. +""" +function set_settings!(args::Dict{String,<:Any}, options::Dict{<:Tuple{Vararg{String}},<:Any}) + for (path,value) in options + _set_property!(args, ("settings", path...), value) end + + apply_settings!(args) + apply_events!(args) end -"helper function that applies settings to the network objects of `type`" -function _apply_to_network!(network::Dict{String,<:Any}, type::String, data::Dict{String,<:Any}) - for (_,nw) in network["nw"] - if haskey(nw, type) - for (id, _data) in data - if haskey(nw[type], id) - merge!(nw[type][id], _data) - end - end - end +""" + get_option(network::Dict{String,<:Any}, path::Tuple{Vararg{String}}, default::Any=missing)::Any + +Helper function to get a property at an arbitrary nested path in a network dictionary, returning the +default value if path does not exist. +""" +function get_option(network::Dict{String,<:Any}, path::Tuple{Vararg{String}}, default::Any=missing)::Any + if length(path) > 1 + return get_option(get(network, path[1], Dict{String,Any}()), path[2:end], default) + else + return get(network, path[1], default) end + end """ - build_settings_file( - network_file::String, - settings_file::String="ieee13_settings.json"; + get_setting(args::Dict{String,Any}, path::Tuple{Vararg{String}}, default::Any=missing)::Any + +Helper function to get a property in settings at an arbitrary nested path in an `args` dictionary, returning the +default value if path does not exist. +""" +function get_setting(args::Dict{String,Any}, path::Tuple{Vararg{String}}, default::Any=missing)::Any + return get_option(args, ("settings", path...), default) +end + + +""" + get_option(settings_file::String, path::Tuple{Vararg{String}}, default::Any=missing)::Any + +Helper function for variant where `settings_file` has not been parsed yet. +""" +get_option(settings_file::String, path::Tuple{Vararg{String}}, default::Any=missing)::Any = get_option(path[1] == "settings" ? Dict{String,Any}("settings"=>parse_settings(settings_file)) : parse_settings(settings_file), path, default) + + +""" + build_settings(network_file::String; kwargs...) + +Helper function for variant where `network_file` has not been parsed yet. +""" +build_settings(network_file::String; kwargs...) = build_settings(PMD.parse_file(network_file; transformations=[PMD.apply_kron_reduction!]); kwargs...) + + +""" + build_settings( + eng::Dict{String,<:Any}; max_switch_actions::Union{Missing,Int,Vector{Int}}, - vm_lb_pu::Union{Missing,Float64}=missing, - vm_ub_pu::Union{Missing,Float64}=missing, - vad_deg::Union{Missing,Float64}=missing, - line_limit_mult::Float64=1.0, - sbase_default::Union{Missing,Float64}=missing, - time_elapsed::Union{Missing,Float64,Vector{Float64}}=missing, + vm_lb_pu::Union{Missing,Real}=missing, + vm_ub_pu::Union{Missing,Real}=missing, + vad_deg::Union{Missing,Real}=missing, + line_limit_mult::Real=1.0, + sbase_default::Union{Missing,Real}=missing, + time_elapsed::Union{Missing,Real,Vector{Real}}=missing, autogen_microgrid_ids::Bool=true, custom_settings::Dict{String,<:Any}=Dict{String,Any}(), - mip_solver_gap::Float64=0.05, - nlp_solver_tol::Float64=1e-4, - mip_solver_tol::Float64=1e-4, - clpu_factor::Union{Missing,Float64}=missing, + mip_solver_gap::Real=0.05, + nlp_solver_tol::Real=1e-4, + mip_solver_tol::Real=1e-4, + clpu_factor::Union{Missing,Real}=missing, disable_switch_penalty::Bool=false, - ) - -Helper function to build a ieee13_settings.json file for use with ONM. + apply_switch_scores::Bool=false, + disable_radial_constraint::Bool=false, + disable_isolation_constraint::Bool=false, + disable_inverter_constraint::Bool=false, + storage_phase_unbalance_factor::Union{Missing,Real}=missing, + disable_presolver::Bool=false, + )::Dict{String,Any} + +**Deprecated**: This function is deprecated in favor of [`build_settings_new`](build_settings_new) + +Helper function to build a settings file (json) for use with ONM. If properties are `missing` they will not be set. + +- `network_file::String` is the path to the input network file (dss) +- `settings_file::String` is the path to the output settings file (json) +- `max_switch_actions::Union{Int,Vector{Int}}` can be used to specify how many actions per time step, + maximum, may be performed. Refers in particular to switch close actions. Can be specified as a single interger, + which will be applied to each time step, or as a list, one number per time step. (default: `missing`) +- `vm_lb_pu::Real` can be used to specify the lower bound voltages on every bus in per-unit. (default: `missing`) +- `vm_ub_pu::Real` can be used to specify the upper bound voltages on every bus in per-unit. (default: `missing`) +- `vad_deg::Real` can be used to specify the lower/upper bound (range around 0.0) for every line in degrees + (default: `missing`) +- `line_limit_mult::Real` can be used to apply a multiplicative factor to every line, switch, and transformer + power/current rating (default: `1.0`) +- `sbase_default::Real` can be used to tune the sbase factor used to convert to per-unit, which may help with + optimization stability (default: `missing`) +- `time_elapsed::Union{Real,Vector{Real}}` can be used to adjust the time step duration. Can be specified as a + single number, or as a list, one number per time step. (default: `missing`) +- `autogen_microgrid_ids::Bool` toggles the automatic generation of microgrid 'ids', which are used in the ONM + algorithm and statistical analyses (default: `missing`) +- `custom_settings:Dict{String,Any}` can be used to pass custom settings that will be applied **after** all of the + autogenerated settings have been created (therefore, it will overwrite any autogenerated settings that it conflicts + with via a recursive merge) +- `mip_solver_gap::Real` can be used to tune the acceptable gap for the MIP solver (default: `0.05`, i.e., 5%) +- `nlp_solver_tol::Real` can be used to tune the accceptable tolerance for constraint violations in the NLP solvers + (default: `0.0001`) +- `mip_solver_tol::Real` can be used to tune the acceptable tolerance for constraint violations in the MIP solvers + (default: `0.0001`) +- `clpu_factor::Real` can be used to set a factor for the cold-load pickup estimation (default: missing) +- `disable_switch_penalty::Bool` is a toggle for disabling the penalty applied to switching actions in the + objective function (default: `false`) +- `apply_switch_scores::Bool` is a toggle to enable switch actions weights applied in the objective function + (default: `false`) +- `disable_radial_constraint::Bool` is a toggle to disable the radiality constraint in the switching problem + (default: `false`) +- `disable_isolation_constraint::Bool` is a toggle to disable the block isolation constraint in the switching + problem (default: `false`) +- `disable_presolver::Bool` is a toggle to disable presolvers on built-in solvers that support it (Gurobi, + KNITRO) (default: `false`) +- `storage_phase_unbalance_factor::Real` is a way to set the `phase_unbalance_factor` on *all* storage devices + (default: `missing`) """ -function build_settings_file( - network_file::String, - settings_file::String="ieee13_settings.json"; - max_switch_actions::Union{Missing,Int,Vector{Int}}, - vm_lb_pu::Union{Missing,Float64}=missing, - vm_ub_pu::Union{Missing,Float64}=missing, - vad_deg::Union{Missing,Float64}=missing, - line_limit_mult::Float64=1.0, - sbase_default::Union{Missing,Float64}=missing, - time_elapsed::Union{Missing,Float64,Vector{Float64}}=missing, +function build_settings( + eng::Dict{String,<:Any}; + max_switch_actions::Union{Missing,Int,Vector{Int}}=missing, + vm_lb_pu::Union{Missing,Real}=missing, + vm_ub_pu::Union{Missing,Real}=missing, + vad_deg::Union{Missing,Real}=missing, + line_limit_mult::Real=1.0, + sbase_default::Union{Missing,Real}=missing, + time_elapsed::Union{Missing,Real,Vector{Real}}=missing, autogen_microgrid_ids::Bool=true, custom_settings::Dict{String,<:Any}=Dict{String,Any}(), - mip_solver_gap::Float64=0.05, - nlp_solver_tol::Float64=1e-4, - mip_solver_tol::Float64=1e-4, - clpu_factor::Union{Missing,Float64}=missing, - disable_switch_penalty::Bool=false, - ) - - eng = PMD.parse_file(network_file; transformations=[PMD.apply_kron_reduction!]) - n_steps = length(first(eng["time_series"]).second["values"]) + mip_solver_gap::Union{Real,Missing}=missing, + nlp_solver_tol::Union{Real,Missing}=missing, + mip_solver_tol::Union{Real,Missing}=missing, + clpu_factor::Union{Missing,Real}=missing, + disable_switch_penalty::Union{Missing,Bool}=missing, + apply_switch_scores::Union{Missing,Bool}=missing, + disable_radial_constraint::Union{Missing,Bool}=missing, + disable_isolation_constraint::Union{Missing,Bool}=missing, + disable_inverter_constraint::Union{Missing,Bool}=missing, + storage_phase_unbalance_factor::Union{Missing,Real}=missing, + disable_presolver::Union{Missing,Bool}=missing, + correct::Bool=true, + )::Dict{String,Any} + n_steps = !haskey(eng, "time_series") ? 1 : length(first(eng["time_series"]).second["values"]) settings = Dict{String,Any}( "settings" => Dict{String,Any}("sbase_default"=>ismissing(sbase_default) ? eng["settings"]["sbase_default"] : sbase_default), @@ -230,11 +529,20 @@ function build_settings_file( "solar" => Dict{String,Any}(), "load" => Dict{String,Any}(), "shunt" => Dict{String,Any}(), - "mip_solver_gap" => mip_solver_gap, - "nlp_solver_tol" => nlp_solver_tol, - "mip_solver_tol" => mip_solver_tol, ) + if !ismissing(mip_solver_gap) + settings["mip_solver_gap"] = mip_solver_gap + end + if !ismissing(nlp_solver_tol) + settings["nlp_solver_tol"] = nlp_solver_tol + end + if !ismissing(mip_solver_tol) + settings["mip_solver_tol"] = mip_solver_tol + end + + settings = recursive_merge(build_default_settings(), settings) + if !ismissing(time_elapsed) if !isa(time_elapsed, Vector) time_elapsed = fill(time_elapsed, n_steps) @@ -249,7 +557,24 @@ function build_settings_file( settings["max_switch_actions"] = max_switch_actions end - settings["disable_switch_penalty"] = disable_switch_penalty + if !ismissing(disable_switch_penalty) + settings["disable_switch_penalty"] = disable_switch_penalty + end + if !ismissing(apply_switch_scores) + settings["apply_switch_scores"] = apply_switch_scores + end + if !ismissing(disable_isolation_constraint) + settings["disable_isolation_constraint"] = disable_isolation_constraint + end + if !ismissing(disable_radial_constraint) + settings["disable_radial_constraint"] = disable_radial_constraint + end + if !ismissing(disable_inverter_constraint) + settings["disable_inverter_constraint"] = disable_inverter_constraint + end + if !ismissing(disable_presolver) + settings["disable_presolver"] = disable_presolver + end # Generate bus microgrid_ids if autogen_microgrid_ids @@ -280,7 +605,7 @@ function build_settings_file( # Generate settings for buses PMD.apply_voltage_bounds!(eng; vm_lb=vm_lb_pu, vm_ub=vm_ub_pu, exclude=String[vs["bus"] for (_,vs) in get(eng, "voltage_source", Dict())]) - for (b, bus) in eng["bus"] + for (b, bus) in get(eng, "bus", Dict()) if !(b in String[vs["bus"] for (_,vs) in get(eng, "voltage_source", Dict())]) settings["bus"][b] = merge( get(settings["bus"], b, Dict{String,Any}()), @@ -297,7 +622,7 @@ function build_settings_file( # Generate settings for loads if !ismissing(clpu_factor) - for (l,_) in eng["load"] + for (l,_) in get(eng, "load", Dict()) settings["load"][l] = merge( get(settings["load"], l, Dict{String,Any}()), Dict{String,Any}( @@ -310,7 +635,7 @@ function build_settings_file( # Generate settings for lines PMD.adjust_line_limits!(eng, line_limit_mult) !ismissing(vad_deg) && PMD.apply_voltage_angle_difference_bounds!(eng, vad_deg) - for (l, line) in eng["line"] + for (l, line) in get(eng, "line", Dict()) settings["line"][l] = merge( get(settings["line"], l, Dict{String,Any}()), Dict{String,Any}( @@ -322,7 +647,7 @@ function build_settings_file( end # Generate settings for switches - for (s, switch) in eng["switch"] + for (s, switch) in get(eng, "switch", Dict()) settings["switch"][s] = merge( get(settings["switch"], s, Dict{String,Any}()), Dict{String,Any}( @@ -333,7 +658,7 @@ function build_settings_file( # Generate settings for transformers PMD.adjust_transformer_limits!(eng, line_limit_mult) - for (t, transformer) in eng["transformer"] + for (t, transformer) in get(eng, "transformer", Dict()) settings["transformer"][t] = merge( get(settings["transformer"], t, Dict{String,Any}()), Dict{String,Any}( @@ -342,10 +667,271 @@ function build_settings_file( ) end - settings = recursive_merge_no_vecs(settings, custom_settings) + if !ismissing(storage_phase_unbalance_factor) + for (i,strg) in get(eng, "storage", Dict()) + settings["storage"][i] = Dict{String,Any}( + "phase_unbalance_factor" => storage_phase_unbalance_factor + ) + end + end + + settings = recursive_merge(settings, custom_settings) + + correct && correct_settings!(settings) + + return settings +end + + +""" + build_settings_file(network_file::String, settings_file::String; kwargs...) + +Builds and writes a `settings_file::String` by parsing a `network_file` +""" +function build_settings_file(network_file::String, settings_file::String; kwargs...) + open(settings_file, "w") do io + build_settings_file(PMD.parse_file(network_file; transformations=[PMD.apply_kron_reduction!]), io; kwargs...) + end +end + + +""" + build_settings_file(eng::Dict{String,<:Any}, settings_file::String; kwargs...) + +Builds and writes a `settings_file::String` from a network data set `eng::Dict{String,Any}` +""" +function build_settings_file(eng::Dict{String,<:Any}, settings_file::String; kwargs...) + open(settings_file, "w") do io + build_settings_file(eng, io; kwargs...) + end +end + + +""" + build_settings_file( + network_file::String, + settings_file::String="settings.json"; + kwargs... + ) + +Helper function to write a settings structure to an `io` for use with ONM from a network data +structure `eng::Dict{String,<:Any}`. +""" +function build_settings_file( + eng::Dict{String,<:Any}, + io::IO; + kwargs... + ) + + settings = build_settings( + eng; + kwargs... + ) + + JSON.print(io, settings) +end + + +""" + parse_dss_settings(dss_settings::T, eng::T)::T where T <: Dict{String,Any} + +Parses the dss settings schema into a ENGINEERING-compatible settings structure +""" +function parse_dss_settings(dss_settings::T, eng::T)::T where T <: Dict{String,Any} + settings = T() + + source_id_map = Dict{String,Tuple{String,String}}( + obj["source_id"] => (obj_type,obj_id) for obj_type in PMD.pmd_eng_asset_types for (obj_id,obj) in get(eng,obj_type,Dict()) if haskey(obj,"source_id") + ) + + for (source_id,obj) in dss_settings + if haskey(source_id_map, lowercase(source_id)) + (eng_obj_type,eng_obj_id) = source_id_map[lowercase(source_id)] + else + @warn "cannot find dss object '$(lowercase(source_id))' in the data model, skipping" + continue + end + + if !haskey(settings, eng_obj_type) + settings[eng_obj_type] = Dict{String,Any}() + end + + if !haskey(settings[eng_obj_type],eng_obj_id) + settings[eng_obj_type][eng_obj_id] = Dict{String,Any}() + end + + if haskey(obj, "enabled") + settings[eng_obj_type][eng_obj_id]["status"] = parse(PMD.Status, obj["enabled"]) + end + + if haskey(obj, "inverter") + settings[eng_obj_type][eng_obj_id]["inverter"] = parse(Inverter, obj["inverter"]) + end + end + + return settings +end + + +""" + build_settings_new( + eng::Dict{String,<:Any}; + raw_settings::Dict{String,<:Any}=Dict{String,Any}(), + switch_close_actions_ub::Union{Real}=missing, + timestep_hours::Union{Missing,Real}=missing, + vm_lb_pu::Union{Missing,Real}=missing, + vm_ub_pu::Union{Missing,Real}=missing, + vad_deg::Union{Missing,Real}=missing, + line_limit_multiplier::Real=1.0, + transformer_limit_multiplier::Real=1.0, + generate_microgrid_ids::Bool=true, + cold_load_pickup_factor::Union{Missing,Real}=missing, + storage_phase_unbalance_factor::Union{Missing,Real}=missing, + )::Dict{String,Any} + +New version of the `build_settings` function. A number of the flags have been moved to `raw_settings`, which should follow +the format of the settings schema. +""" +function build_settings_new( + eng::Dict{String,<:Any}; + raw_settings::Dict{String,<:Any}=Dict{String,Any}(), + switch_close_actions_ub::Union{Real}=missing, + timestep_hours::Union{Missing,Real}=missing, + vm_lb_pu::Union{Missing,Real}=missing, + vm_ub_pu::Union{Missing,Real}=missing, + vad_deg::Union{Missing,Real}=missing, + line_limit_multiplier::Real=1.0, + transformer_limit_multiplier::Real=1.0, + generate_microgrid_ids::Bool=true, + cold_load_pickup_factor::Union{Missing,Real}=missing, + storage_phase_unbalance_factor::Union{Missing,Real}=missing, + )::Dict{String,Any} + n_steps = !haskey(eng, "time_series") ? 1 : length(first(eng["time_series"]).second["values"]) + + settings = build_default_settings() + + + if !ismissing(timestep_hours) + if !isa(timestep_hours, Vector) + timestep_hours = fill(timestep_hours, n_steps) + end + _set_property!(settings, ("options", "data", "time-elapsed"), timestep_hours) + end + + if !ismissing(switch_close_actions_ub) + if !isa(switch_close_actions_ub, Vector) + switch_close_actions_ub = fill(switch_close_actions_ub, n_steps) + end + _set_property!(settings, ("options", "data", "switch-close-actions-ub"), switch_close_actions_ub) + end + + # Generate bus microgrid_ids + if generate_microgrid_ids + # merge in switch default settings + for (id, switch) in get(raw_settings, "switch", Dict()) + eng["switch"][id] = merge(eng["switch"][id], deepcopy(switch)) + end + + # identify load blocks + blocks = PMD.identify_load_blocks(eng) + + # build list of blocks with enabled generation + gen_blocks = [ + bl for bl in blocks if ( + any(g["bus"] in bl && g["status"] == PMD.ENABLED for (_,g) in get(eng, "storage", Dict())) || + any(g["bus"] in bl && g["status"] == PMD.ENABLED for (_,g) in get(eng, "solar", Dict())) || + any(g["bus"] in bl && g["status"] == PMD.ENABLED for (_,g) in get(eng, "generator", Dict())) + ) + ] + + # assign microgrid ids + for (i,b) in enumerate(gen_blocks) + for bus in b + eng["bus"][bus]["microgrid_id"] = "$i" + end + end + end + + # Generate settings for buses + PMD.apply_voltage_bounds!(eng; vm_lb=vm_lb_pu, vm_ub=vm_ub_pu, exclude=String[vs["bus"] for (_,vs) in get(eng, "voltage_source", Dict())]) + for (b, bus) in get(eng, "bus", Dict()) + if !(b in String[vs["bus"] for (_,vs) in get(eng, "voltage_source", Dict())]) + haskey(bus, "vm_lb") && _set_property!(settings, ("bus", b, "vm_lb"), bus["vm_lb"]) + haskey(bus, "vm_ub") && _set_property!(settings, ("bus", b, "vm_ub"), bus["vm_ub"]) + haskey(bus, "microgrid_id") && _set_property!(settings, ("bus", b, "microgrid_id"), bus["microgrid_id"]) + end + end + + # Generate settings for loads + if !ismissing(cold_load_pickup_factor) + for l in keys(get(eng, "load", Dict())) + _set_property!(settings, ("load", l, "clpu_factor"), cold_load_pickup_factor) + end + end + + # Generate settings for lines + PMD.adjust_line_limits!(eng, line_limit_multiplier) + !ismissing(vad_deg) && PMD.apply_voltage_angle_difference_bounds!(eng, vad_deg) + for (l, line) in get(eng, "line", Dict()) + haskey(line, "vad_lb") && _set_property!(settings, ("line", l, "vad_lb"), line["vad_lb"]) + haskey(line, "vad_ub") && _set_property!(settings, ("line", l, "vad_ub"), line["vad_ub"]) + haskey(line, "cm_ub") && _set_property!(settings, ("line", l, "cm_ub"), line["cm_ub"]) + end + + # Generate settings for switches + for (s, switch) in get(eng, "switch", Dict()) + haskey(switch, "cm_ub") && _set_property!(settings, ("switch", s, "cm_ub"), switch["cm_ub"]) + end + + # Generate settings for transformers + PMD.adjust_transformer_limits!(eng, transformer_limit_multiplier) + for (t, transformer) in get(eng, "transformer", Dict()) + haskey(transformer, "sm_ub") && _set_property!(transformer, ("transformer", t, "sm_ub"), transformer["sm_ub"]) + end + + if !ismissing(storage_phase_unbalance_factor) + for i in keys(get(eng, "storage", Dict())) + _set_property!(settings, ("storage", i, "phase_unbalance_factor"), storage_phase_unbalance_factor) + end + end + + settings = recursive_merge(settings, raw_settings) + + return settings +end + + +""" + build_settings_new(eng::Dict{String,<:Any}, io::IO; kwargs...) + +Builds and writes settings to an `io::IO` from a network data set `eng::Dict{String,Any}` +""" +build_settings_new(eng::Dict{String,<:Any}, io::IO; kwargs...) = JSON.print(io, build_settings_new(eng; kwargs...), 2) + + +""" + build_settings_new(network_file::String; kwargs...) + +Builds settings from a network_file +""" +build_settings_new(network_file::String; kwargs...) = build_settings_new(parse_network(network_file)[1]; kwargs...) - # Save the ieee13_settings.json file + +""" + build_settings_new(eng::Dict{String,<:Any}, settings_file::String; kwargs...) + +Builds and writes settings to a `settings_file::String` from a network data set `eng::Dict{String,Any}` +""" +function build_settings_new(eng::Dict{String,<:Any}, settings_file::String; kwargs...) open(settings_file, "w") do io - JSON.print(io, settings) + build_settings_new(eng, io; kwargs) end end + + +""" + build_settings_new(network_file::String, settings_file::String; kwargs...) + +Builds and writes settings to a `settings_file::String` from a network data set at `network_file::String` +""" +build_settings_new(network_file::String, settings_file::String; kwargs...) = build_settings_new(parse_network(network_file)[1], settings_file; kwargs) diff --git a/src/prob/common.jl b/src/prob/common.jl index 2d99cac3..ad74e333 100644 --- a/src/prob/common.jl +++ b/src/prob/common.jl @@ -1,46 +1,125 @@ -"default eng2math passthrough" -const _eng2math_passthrough = Dict{String,Vector{String}}( - "root"=>String[ - "max_switch_actions", - "disable_networking", - "disable_switch_penalty", - "apply_switch_scores", - "disable_radial_constraint", - "disable_isolation_constraint", - ], - "load"=>String["priority"], - "bus"=>String["microgrid_id"], - "storage"=>String["phase_unbalance_factor"], -) - -"default ref_extensions for solve_mc_model" -const _ref_extensions = Function[ref_add_load_blocks!, ref_add_max_switch_actions!] - -"default solution processors" -const _solution_processors = Function[PMD.sol_data_model!, PowerModelsONM.solution_reference_buses!] +"default ref_extension functions" +const _default_ref_extensions = Function[ + ref_add_load_blocks!, + ref_add_options!, +] +"default solution_processor functions" +const _default_solution_processors = Function[ + PMD.sol_data_model!, + solution_reference_buses!, + solution_statuses!, + solution_inverter!, + PowerModelsONM.solution_blocks!, +] """ - build_solver_instances!(args::Dict{String,<:Any})::Dict{String,JuMP.MOI.OptimizerWithAttributes} + solve_onm_model( + data::Union{Dict{String,<:Any}, String}, + model_type::Type, + solver::Any, + model_builder::Function; + solution_processors::Vector{Function}=Function[], + eng2math_passthrough::Dict{String,Vector{String}}=Dict{String,Vector{String}}(), + ref_extensions::Vector{Function}=Function[], + multinetwork::Bool=false, + global_keys::Set{String}=Set{String}(), + kwargs... + )::Dict{String,Any} + +Custom version of `PowerModelsDistribution.solve_mc_model` that automatically includes the solution processors, +ref extensions and eng2math_passthroughs required for ONM problems. +""" +function solve_onm_model( + data::Union{Dict{String,<:Any}, String}, + model_type::Type, + solver::Any, + model_builder::Function; + solution_processors::Vector{Function}=Function[], + eng2math_passthrough::Dict{String,Vector{String}}=Dict{String,Vector{String}}(), + ref_extensions::Vector{Function}=Function[], + multinetwork::Bool=false, + global_keys::Set{String}=Set{String}(), + kwargs...)::Dict{String,Any} + + return PMD.solve_mc_model( + data, + model_type, + solver, + model_builder; + multinetwork=multinetwork, + solution_processors=Function[ + _default_solution_processors..., + solution_processors... + ], + ref_extensions=Function[ + _default_ref_extensions..., + ref_extensions... + ], + eng2math_passthrough=recursive_merge_including_vectors(_eng2math_passthrough_default, eng2math_passthrough), + global_keys=union(_default_global_keys,global_keys), + kwargs... + ) +end + + +""" +instantiate_onm_model( + data::Union{Dict{String,<:Any}, String}, + model_type::Type, + model_builder::Function; + eng2math_passthrough::Dict{String,Vector{String}}=Dict{String,Vector{String}}(), + ref_extensions::Vector{Function}=Function[], + multinetwork::Bool=false, + global_keys::Set{String}=Set{String}(), + kwargs... + ) + +ONM-specific version of PowerModelsDistribution.instantiate_mc_model +""" +function instantiate_onm_model( + data::Union{Dict{String,<:Any}, String}, + model_type::Type, + model_builder::Function; + eng2math_passthrough::Dict{String,Vector{String}}=Dict{String,Vector{String}}(), + ref_extensions::Vector{Function}=Function[], + multinetwork::Bool=false, + global_keys::Set{String}=Set{String}(), + kwargs...) + + return PMD.instantiate_mc_model( + data, + model_type, + model_builder; + multinetwork=multinetwork, + ref_extensions=Function[ + _default_ref_extensions..., + ref_extensions... + ], + eng2math_passthrough=recursive_merge_including_vectors(_eng2math_passthrough_default, eng2math_passthrough), + global_keys=union(_default_global_keys, global_keys), + kwargs... + ) +end + + +""" + build_solver_instances!(args::Dict{String,<:Any})::Dict{String,Any} Creates the Optimizers in-place (within the args dict data structure), for use inside [`entrypoint`](@ref entrypoint), using [`build_solver_instances`](@ref build_solver_instances), assigning them to `args["solvers"]`` """ -function build_solver_instances!(args::Dict{String,<:Any})::Dict{String,JuMP.MOI.OptimizerWithAttributes} +function build_solver_instances!(args::Dict{String,<:Any})::Dict{String,Any} + solver_opts = get(get(args, "network", Dict()), "solvers", Dict{String,Any}()) + log_level = get_setting(args, ("options","outputs","log-level"), "warn") + args["solvers"] = build_solver_instances(; nlp_solver = get(get(args, "solvers", Dict()), "nlp_solver", missing), - nlp_solver_options = isa(get(args, "settings", ""), String) ? missing : get(args["settings"], "nlp_solver_options", missing), mip_solver = get(get(args, "solvers", Dict()), "mip_solver", missing), - mip_solver_options = isa(get(args, "settings", ""), String) ? missing : get(args["settings"], "nlp_solver_options", missing), minlp_solver = get(get(args, "solvers", Dict()), "minlp_solver", missing), - minlp_solver_options = isa(get(args, "settings", ""), String) ? missing : get(args["settings"], "nlp_solver_options", missing), misocp_solver = get(get(args, "solvers", Dict()), "misocp_solver", missing), - feas_tol=isa(get(args, "settings", ""), String) ? 1e-4 : get(get(args, "settings", Dict()), "nlp_solver_tol", 1e-4), - mip_gap_tol=isa(get(args, "settings", ""), String) ? 0.05 : get(get(args, "settings", Dict()), "mip_solver_gap", 0.05), - verbose=get(args, "verbose", false), - debug=get(args, "debug", false), - gurobi=get(args, "gurobi", false), - knitro=get(args, "knitro", false), + solver_options=solver_opts, + log_level=log_level, ) end @@ -51,16 +130,9 @@ end mip_solver::Union{Missing,JuMP.MOI.OptimizerWithAttributes}=missing, minlp_solver::Union{Missing,JuMP.MOI.OptimizerWithAttributes}=missing, misocp_solver::Union{Missing,JuMP.MOI.OptimizerWithAttributes}=missing, - nlp_solver_options::Union{Missing,Vector{Pair}}=missing, - mip_solver_options::Union{Missing,Vector{Pair}}=missing, - minlp_solver_options::Union{Missing,Vector{Pair}}=missing, - feas_tol::Float64=1e-6, - mip_gap_tol::Float64=1e-4, - gurobi::Bool=false, - knitro::Bool=false, - verbose::Bool=false, - debug::Bool=false, - )::Dict{String,JuMP.MOI.OptimizerWithAttributes} + solver_options::Dict{String,<:Any}=Dict{String,Any}(), + log_level::String="warn", + )::Dict{String,Any} Returns solver instances as a Dict ready for use with JuMP Models, for NLP (`"nlp_solver"`), MIP (`"mip_solver"`), MINLP (`"minlp_solver"`), and (MI)SOC (`"misocp_solver"`) problems. @@ -68,134 +140,105 @@ Returns solver instances as a Dict ready for use with JuMP Models, for NLP (`"nl - `mip_solver` (default: `missing`): If missing, will use Cbc as MIP solver, or Gurobi if `gurobi==true` - `minlp_solver` (default: `missing`): If missing, will use Juniper with `nlp_solver` and `mip_solver`, of KNITRO if `knitro=true` - `misocp_solver` (default: `missing`): If missing will use Juniper with `mip_solver`, or Gurobi if `gurobi==true` -- `nlp_solver_options` (default: `missing`): If missing, will use some default nlp solver options -- `mip_solver_options` (default: `missing`): If missing, will use some default mip solver options -- `minlp_solver_options` (default: `missing`): If missing, will use some default minlp solver options -- `feas_tol` (default: `1e-4`): The solver tolerance -- `mip_gap_tol` (default: 0.05): The desired MIP Gap for the MIP Solver -- `verbose` (default: `false`): Sets the verbosity of the solvers -- `debug` (default: `false`): Sets the verbosity of the solvers even higher (if available) -- `gurobi` (default: `false`): Use Gurobi for MIP / MISOC solvers -- `knitro` (default: `false`): Use KNITRO for NLP / MINLP solvers +- `solver_options` (default: Dict{String,Any}()) +- `log_level` (default: "warn") """ function build_solver_instances(; nlp_solver::Union{Missing,JuMP.MOI.OptimizerWithAttributes}=missing, mip_solver::Union{Missing,JuMP.MOI.OptimizerWithAttributes}=missing, minlp_solver::Union{Missing,JuMP.MOI.OptimizerWithAttributes}=missing, misocp_solver::Union{Missing,JuMP.MOI.OptimizerWithAttributes}=missing, - nlp_solver_options::Union{Missing,Vector{Pair}}=missing, - mip_solver_options::Union{Missing,Vector{Pair}}=missing, - minlp_solver_options::Union{Missing,Vector{Pair}}=missing, - feas_tol::Float64=1e-6, - mip_gap_tol::Float64=1e-4, - gurobi::Bool=false, - knitro::Bool=false, - verbose::Bool=false, - debug::Bool=false, - )::Dict{String,JuMP.MOI.OptimizerWithAttributes} + solver_options::Dict{String,<:Any}=Dict{String,Any}(), + log_level::String="warn", + )::Dict{String,Any} if ismissing(nlp_solver) - if knitro - if ismissing(nlp_solver_options) - nlp_solver_options = Pair[ - "outlev" => debug ? 3 : verbose ? 2 : 0, - "mip_outlevel" => debug ? 2 : verbose ? 1 : 0, - "opttol" => mip_gap_tol, - "feastol" => feas_tol, - "algorithm" => 3, - "presolve" => 0, - ] + if get(solver_options, "useKNITRO", false) + opts = get(solver_options, "KNITRO", Dict{String,Any}()) + if log_level == "debug" + opts["outlev"] = 3 + opts["mip_outlevel"] = 3 + elseif log_level == "info" + opts["outlev"] = 2 + opts["mip_outlevel"] = 2 end nlp_solver = optimizer_with_attributes( () -> KNITRO.Optimizer(;license_manager=KN_LMC), - nlp_solver_options... + opts... ) else - if ismissing(nlp_solver_options) - nlp_solver_options = Pair[ - "tol" => feas_tol, - "print_level" => debug ? 5 : verbose ? 3 : 0, - "mumps_mem_percent" => 200, - "mu_strategy" => "adaptive", - ] + opts = get(solver_options, "Ipopt", Dict{String,Any}()) + if log_level == "debug" + opts["print_level"] = 5 + elseif log_level == "info" + opts["print_level"] = 3 end nlp_solver = optimizer_with_attributes( Ipopt.Optimizer, - nlp_solver_options... + opts... ) end end if ismissing(mip_solver) - if gurobi - if ismissing(mip_solver_options) - mip_solver_options = Pair[ - # output settings - "OutputFlag" => verbose || debug ? 1 : 0, - "GURO_PAR_DUMP" => debug ? 1 : 0, - # tolerance settings - "MIPGap" => mip_gap_tol, - "FeasibilityTol" => feas_tol, - # MIP settings - "MIPFocus" => 2, - # presolve settings - "DualReductions" => 0, - ] + if get(solver_options, "useGurobi", false) + opts = get(solver_options, "Gurobi", Dict{String,Any}()) + if log_level == "debug" + opts["OutputFlag"] = 1 + elseif log_level == "info" + opts["OutputFlag"] = 1 end mip_solver = optimizer_with_attributes( () -> Gurobi.Optimizer(GRB_ENV), - mip_solver_options... + opts... ) else - if ismissing(mip_solver_options) - mip_solver_options = Pair[ - "loglevel" => verbose || debug ? 1 : 0, - ] + opts = get(solver_options, "HiGHS", Dict{String,Any}()) + if log_level == "debug" + opts["output_flag"] = true + elseif log_level == "info" + opts["output_flag"] = true end mip_solver = optimizer_with_attributes( - Cbc.Optimizer, - mip_solver_options... + HiGHS.Optimizer, + opts... ) end end if ismissing(minlp_solver) - if knitro - if ismissing(minlp_solver_options) - minlp_solver_options = Pair[ - "outlev" => debug ? 3 : verbose ? 2 : 0, - "mip_outlevel" => debug ? 2 : verbose ? 1 : 0, - "opttol" => mip_gap_tol, - "feastol" => feas_tol, - "algorithm" => 3, - "presolve" => 0, - ] + if get(solver_options, "useKNITRO", false) + opts = get(solver_options, "KNITRO", Dict{String,Any}()) + if log_level == "debug" + opts["outlev"] = 3 + opts["mip_outlevel"] = 3 + elseif log_level == "info" + opts["outlev"] = 2 + opts["mip_outlevel"] = 2 end minlp_solver = optimizer_with_attributes( () -> KNITRO.Optimizer(;license_manager=KN_LMC), - minlp_solver_options... + opts... ) else - if ismissing(minlp_solver_options) - minlp_solver_options = Pair[ - "nl_solver" => nlp_solver, - "mip_solver" => mip_solver, - "branch_strategy" => :MostInfeasible, - "log_levels" => debug ? [:Error,:Warn,:Info] : verbose ? [:Error,:Warn] : [], - "mip_gap" => mip_gap_tol, - "traverse_strategy" => :DFS, - ] + opts = get(solver_options, "Juniper", Dict{String,Any}()) + if log_level == "debug" + opts["log_levels"] = [:Table,:Info,:Options] + elseif log_level == "info" + opts["log_levels"] = [:Info,:Options] end minlp_solver = optimizer_with_attributes( Juniper.Optimizer, - minlp_solver_options... + "nl_solver" => nlp_solver, + "mip_solver" => mip_solver, + opts... ) end end if ismissing(misocp_solver) - if gurobi + if get(solver_options, "useKNITRO", false) misocp_solver = mip_solver else misocp_solver = minlp_solver diff --git a/src/prob/dispatch.jl b/src/prob/dispatch.jl index b673bb86..bb0ea482 100644 --- a/src/prob/dispatch.jl +++ b/src/prob/dispatch.jl @@ -1,32 +1,34 @@ """ - optimize_dispatch!(args::Dict{String,<:Any}; update_network_data::Bool=false, solver::String=get(args, "opt-disp-solver", "nlp_solver"))::Dict{String,Any} + optimize_dispatch!( + args::Dict{String,<:Any}; + solver::Union{Missing,String}=missing + )::Dict{String,Any} Solves optimal dispatch problem in-place, for use in [`entrypoint`](@ref entrypoint), using [`optimize_dispatch`](@ref optimize_dispatch). If you are using this to optimize after running [`optimize_switches!`](@ref optimize_switches!), this assumes that the correct switch states from those results have already been propagated into `args["network"]` -If `update_network_data` (default: false) the results of the optimization will be automatically merged into -`args["network"]`. `solver` (default: `"nlp_solver"`) specifies which solver to use for the OPF problem from `args["solvers"]` """ -function optimize_dispatch!(args::Dict{String,<:Any}; update_network_data::Bool=false, solver::String=get(args, "opt-disp-solver", "nlp_solver"))::Dict{String,Any} - args["opt-disp-formulation"] = _get_dispatch_formulation(get(args, "opt-disp-formulation", "lindistflow")) +function optimize_dispatch!(args::Dict{String,<:Any}; solver::Union{Missing,String}=missing)::Dict{String,Any} + prob_opts = get(get(args["network"], "options", Dict()), "problem", Dict()) + solver = ismissing(solver) ? get(prob_opts, "dispatch-solver", "nlp_solver") : solver + formulation = _get_formulation(get(prob_opts, "dispatch-formulation", PMD.LPUBFDiagPowerModel)) - if update_network_data - args["network"] = apply_switch_solutions!(args["network"], get(args, "optimal_switching_results", Dict{String,Any}())) - end - - args["optimal_dispatch_result"] = optimize_dispatch(args["network"], args["opt-disp-formulation"], args["solvers"][solver]; switching_solutions=get(args, "optimal_switching_results", missing)) - - update_network_data && recursive_merge(args["network"], get(args["optimal_dispatch_result"], "solution", Dict{String, Any}())) + args["optimal_dispatch_result"] = optimize_dispatch(args["network"], formulation, args["solvers"][solver]; switching_solutions=get(args, "optimal_switching_results", missing)) return args["optimal_dispatch_result"] end """ - optimize_dispatch(network::Dict{String,<:Any}, formulation::Type, solver; switching_solutions::Union{Missing,Dict{String,<:Any}}=missing)::Dict{String,Any} + optimize_dispatch( + network::Dict{String,<:Any}, + formulation::Type, + solver; + switching_solutions::Union{Missing,Dict{String,<:Any}}=missing + )::Dict{String,Any} Solve a multinetwork optimal power flow (`solve_mn_mc_opf`) using `formulation` and `solver` """ @@ -34,30 +36,30 @@ function optimize_dispatch(network::Dict{String,<:Any}, formulation::Type, solve data = _prepare_dispatch_data(network, switching_solutions) @info "running optimal dispatch with $(formulation)" - solve_mn_mc_opf_oltc_capc( - data, - formulation, - solver; - solution_processors=[PMD.sol_data_model!, solution_reference_buses!], - eng2math_passthrough=Dict{String,Vector{String}}( - "storage"=>String["phase_unbalance_factor"] - ) - ) + solve_mn_opf(data, formulation, solver) end -"prepares data for running a optimal dispatch problem, copying in solutions from the switching results, if present" +""" + _prepare_dispatch_data( + network::Dict{String,<:Any}, + switching_solutions::Union{Missing,Dict{String,<:Any}}=missing + )::Dict{String,Any} + +Helper function to prepare data for running a optimal dispatch problem, copying in solutions from the switching results, if present. +""" function _prepare_dispatch_data(network::Dict{String,<:Any}, switching_solutions::Union{Missing,Dict{String,<:Any}}=missing)::Dict{String,Any} data = deepcopy(network) if !ismissing(switching_solutions) for (n, results) in switching_solutions - shed = String[] - nw = get(results, "solution", Dict()) - for (i,bus) in get(nw, "bus", Dict()) - if round(Int, get(bus, "status", 1)) != 1 + shed = collect(keys(filter(x->x.second["status"] != PMD.ENABLED, data["nw"][n]["bus"]))) + + for (i,bus) in get(data["nw"][n], "bus", Dict()) + obj_sol = get(get(nw, "bus", Dict()), i, Dict()) + if get(obj_sol, "status", PMD.DISABLED) != PMD.ENABLED data["nw"]["$n"]["bus"][i]["status"] = PMD.DISABLED push!(shed, i) end @@ -65,30 +67,42 @@ function _prepare_dispatch_data(network::Dict{String,<:Any}, switching_solutions for type in ["load", "shunt", "generator", "solar", "voltage_source", "storage"] for (i,obj) in get(data["nw"]["$n"], type, Dict{String,Any}()) - if obj["bus"] in shed + obj_sol = get(get(nw, type, Dict()), i, Dict()) + if obj["bus"] in shed || get(obj_sol, "status", obj["status"]) == PMD.DISABLED data["nw"]["$n"][type][i]["status"] = PMD.DISABLED end + if type ∈ ["storage", "solar", "voltage_source", "generator"] && haskey(obj_sol, "inverter") + data["nw"]["$n"][type][i]["inverter"] = obj_sol["inverter"] + data["nw"]["$n"][type][i]["control_mode"] = obj_sol["inverter"] == GRID_FORMING ? PMD.ISOCHRONOUS : PMD.FREQUENCYDROOP + end end end for (i,line) in get(data["nw"]["$n"], "line", Dict()) - if line["f_bus"] in shed || line["t_bus"] in shed + obj_sol = get(get(nw, "line", Dict()), i, Dict()) + if line["f_bus"] in shed || line["t_bus"] in shed || get(obj_sol, "status", line["status"]) == PMD.DISABLED data["nw"]["$n"]["line"][i]["status"] = PMD.DISABLED end end for (i,switch) in get(data["nw"]["$n"], "switch", Dict()) - if haskey(nw, "switch") && haskey(nw["switch"], i) && haskey(nw["switch"][i], "state") + obj_sol = get(get(nw, "switch", Dict()), i, Dict()) + + if haskey(obj_sol, "state") data["nw"]["$n"]["switch"][i]["state"] = nw["switch"][i]["state"] end + data["nw"]["$n"]["switch"][i]["dispatchable"] = PMD.NO - if switch["f_bus"] in shed || switch["t_bus"] in shed + if switch["f_bus"] in shed || switch["t_bus"] in shed || get(obj_sol, "status", switch["status"]) == PMD.DISABLED data["nw"]["$n"]["switch"][i]["status"] = PMD.DISABLED end end for (i,transformer) in get(data["nw"]["$n"], "transformer", Dict()) - if any(bus in shed for bus in transformer["bus"]) + # TODO: debug controls formulations in transformer constraints + haskey(transformer, "controls") && delete!(data["nw"]["$n"]["transformer"][i], "controls") + obj_sol = get(get(nw, "transformer", Dict()), i, Dict()) + if any(bus in shed for bus in transformer["bus"]) || get(obj_sol, "status", transformer["status"]) == PMD.DISABLED data["nw"]["$n"]["transformer"][i]["status"] = PMD.DISABLED end end diff --git a/src/prob/faults.jl b/src/prob/faults.jl index b7e55f3f..aa7686ba 100644 --- a/src/prob/faults.jl +++ b/src/prob/faults.jl @@ -1,25 +1,43 @@ """ - run_fault_studies!(args::Dict{String,<:Any}; validate::Bool=true, solver::String="nlp_solver")::Dict{String,Any} + run_fault_studies!( + args::Dict{String,<:Any}; + validate::Bool=true, + solver::String="nlp_solver" + )::Dict{String,Any} Runs fault studies using `args["faults"]`, if defined, and stores the results in-place in `args["fault_stuides_results"]`, for use in [`entrypoint`](@ref entrypoint), using [`run_fault_studies`](@ref run_fault_studies) """ -function run_fault_studies!(args::Dict{String,<:Any}; validate::Bool=true, solver::String="nlp_solver")::Dict{String,Any} +function run_fault_studies!(args::Dict{String,<:Any}; validate::Bool=true)::Dict{String,Any} if !isempty(get(args, "faults", "")) if isa(args["faults"], String) args["faults"] = parse_faults(args["faults"]; validate=validate) end else - args["faults"] = PowerModelsProtection.build_mc_fault_study(args["base_network"]) + args["faults"] = PMP.build_mc_sparse_fault_study(args["base_network"]) end - args["fault_studies_results"] = run_fault_studies(args["network"], args["solvers"][solver]; faults=args["faults"], switching_solutions=get(args, "optimal_switching_results", missing), dispatch_solution=get(args, "optimal_dispatch_result", missing), distributed=get(args, "nprocs", 1) > 1) + args["fault_studies_results"] = run_fault_studies( + args["network"], + args["solvers"][get_setting(args, ("options", "problem", "fault-studies-solver"), "nlp_solver")]; + faults=args["faults"], + switching_solutions=get(args, "optimal_switching_results", missing), + dispatch_solution=get(args, "optimal_dispatch_result", missing), + distributed=get_setting(args, ("options", "problem", "concurrent-fault-studies"), true) + ) end """ - run_fault_studies(network::Dict{String,<:Any}, solver; faults::Dict{String,<:Any}=Dict{String,Any}(), switching_solutions::Union{Missing,Dict{String,<:Any}}=missing, dispatch_solution::Union{Missing,Dict{String,<:Any}}=missing, distributed::Bool=false)::Dict{String,Any} + run_fault_studies( + network::Dict{String,<:Any}, + solver; + faults::Dict{String,<:Any}=Dict{String,Any}(), + switching_solutions::Union{Missing,Dict{String,<:Any}}=missing, + dispatch_solution::Union{Missing,Dict{String,<:Any}}=missing, + distributed::Bool=false + )::Dict{String,Any} Runs fault studies defined in ieee13_faults.json. If no faults file is provided, it will automatically generate faults using `PowerModelsProtection.build_mc_fault_study`. @@ -31,17 +49,24 @@ Uses [`run_fault_study`](@ref run_fault_study) to solve the actual fault study. `solver` will determine which instantiated solver is used, `"nlp_solver"` or `"juniper_solver"` """ -function run_fault_studies(network::Dict{String,<:Any}, solver; faults::Dict{String,<:Any}=Dict{String,Any}(), switching_solutions::Union{Missing,Dict{String,<:Any}}=missing, dispatch_solution::Union{Missing,Dict{String,<:Any}}=missing, distributed::Bool=false)::Dict{String,Any} +function run_fault_studies( + network::Dict{String,<:Any}, + solver; + faults::Dict{String,<:Any}=Dict{String,Any}(), + switching_solutions::Union{Missing,Dict{String,<:Any}}=missing, + dispatch_solution::Union{Missing,Dict{String,<:Any}}=missing, + distributed::Bool=false + )::Dict{String,Any} mn_data = _prepare_fault_study_multinetwork_data(network, switching_solutions, dispatch_solution) switch_states = Dict{String,Dict{String,PMD.SwitchState}}(n => Dict{String,PMD.SwitchState}(s => sw["state"] for (s,sw) in get(nw, "switch", Dict())) for (n,nw) in get(mn_data, "nw", Dict())) - shedded_buses = Dict{String,Vector{String}}(n => String[] for (n,nw) in get(mn_data, "nw", Dict())) + shedded_buses = Dict{String,Vector{String}}(n => collect(keys(filter(x->x.second["status"] == PMD.DISABLED, mn_data["nw"][n]["bus"]))) for (n,nw) in get(mn_data, "nw", Dict())) if !ismissing(switching_solutions) for (n,shed) in shedded_buses nw = get(switching_solutions["$n"], "solution", Dict{String,Any}()) for (i,bus) in get(nw, "bus", Dict()) - if round(Int, get(bus, "status", 1)) != 1 + if get(bus, "status", PMD.ENABLED) == PMD.DISABLED push!(shed, i) end end @@ -58,7 +83,7 @@ function run_fault_studies(network::Dict{String,<:Any}, solver; faults::Dict{Str end if isempty(faults) - faults = PowerModelsProtection.build_mc_fault_study(first(network["nw"]).second) + faults = PMP.build_mc_fault_study(first(network["nw"]).second) end fault_studies_results = Dict{String,Any}() @@ -75,7 +100,7 @@ function run_fault_studies(network::Dict{String,<:Any}, solver; faults::Dict{Str end end else - _results = @showprogress pmap(ns; distributed=distributed) do n + _results = pmap(ns; distributed=distributed) do n _faults = filter(x->!(x.first in shedded_buses["$(n)"]), faults) if (n > 1 && switch_states["$(n)"] == switch_states["$(n-1)"]) || isempty(_faults) # skip identical configurations or all faults missing @@ -102,7 +127,11 @@ end """ - run_fault_study(subnetwork::Dict{String,<:Any}, faults::Dict{String,<:Any}, solver)::Dict{String,Any} + run_fault_study( + subnetwork::Dict{String,<:Any}, + faults::Dict{String,<:Any}, + solver + )::Dict{String,Any} Uses `PowerModelsProtection.solve_mc_fault_study` to solve multiple faults defined in `faults`, applied to `subnetwork`, i.e., not a multinetwork, using a nonlinear `solver`. @@ -110,12 +139,24 @@ to `subnetwork`, i.e., not a multinetwork, using a nonlinear `solver`. Requires the use of `PowerModelsDistribution.IVRUPowerModel`. """ function run_fault_study(subnetwork::Dict{String,<:Any}, faults::Dict{String,<:Any}, solver)::Dict{String,Any} - PowerModelsProtection.solve_mc_fault_study(subnetwork, faults, solver) + PMP.solve_mc_fault_study(subnetwork, faults, solver) end -"helper function that helps to prepare all of the subnetworks for use in `PowerModelsProtection.solve_mc_fault_study`" -function _prepare_fault_study_multinetwork_data(network::Dict{String,<:Any}, switching_solutions::Union{Missing,Dict{String,<:Any}}=missing, dispatch_solution::Union{Missing,Dict{String,<:Any}}=missing) +""" + _prepare_fault_study_multinetwork_data( + network::Dict{String,<:Any}, + switching_solutions::Union{Missing,Dict{String,<:Any}}=missing, + dispatch_solution::Union{Missing,Dict{String,<:Any}}=missing + ) + +Helper function that helps to prepare all of the subnetworks for use in `PowerModelsProtection.solve_mc_fault_study` +""" +function _prepare_fault_study_multinetwork_data( + network::Dict{String,<:Any}, + switching_solutions::Union{Missing,Dict{String,<:Any}}=missing, + dispatch_solution::Union{Missing,Dict{String,<:Any}}=missing + )::Dict{String,Any} data = _prepare_dispatch_data(network, switching_solutions) if !ismissing(dispatch_solution) @@ -127,7 +168,7 @@ function _prepare_fault_study_multinetwork_data(network::Dict{String,<:Any}, swi data["nw"]["$n"]["bus"][i]["vm"] = nw_sol["bus"][i]["vm"] end if haskey(nw_sol["bus"][i], "va") - data["nw"]["$n"]["bus"][i]["vm"] = nw_sol["bus"][i]["va"] + data["nw"]["$n"]["bus"][i]["va"] = nw_sol["bus"][i]["va"] end end end @@ -141,12 +182,12 @@ function _prepare_fault_study_multinetwork_data(network::Dict{String,<:Any}, swi for type in ["solar", "storage", "generator"] if haskey(nw, type) for (i,obj) in nw[type] - if obj["status"] == PMD.ENABLED + if haskey(obj, "inverter") && obj["inverter"] == GRID_FORMING data["nw"]["$n"][type][i]["grid_forming"] = true bus = data["nw"]["$n"]["bus"]["$(obj["bus"])"] if !haskey(bus, "vm") - data["nw"]["$n"]["bus"]["$(obj["bus"])"]["vm"] = ones(length(bus["terminals"]))[bus["terminals"]] + data["nw"]["$n"]["bus"]["$(obj["bus"])"]["vm"] = [ones(3)..., zeros(length(bus["terminals"]))...][bus["terminals"]] end if !haskey(bus, "va") data["nw"]["$n"]["bus"]["$(obj["bus"])"]["va"] = [0.0, -120.0, 120.0, zeros(length(bus["terminals"]))...][bus["terminals"]] diff --git a/src/prob/mld_block.jl b/src/prob/mld_block.jl new file mode 100644 index 00000000..9c128f04 --- /dev/null +++ b/src/prob/mld_block.jl @@ -0,0 +1,465 @@ +""" + solve_mn_block_mld( + data::Dict{String,<:Any}, + model_type::Type, + solver; + kwargs... + )::Dict{String,Any} + +Solves a __multinetwork__ multiconductor optimal switching (mixed-integer) problem using `model_type` and `solver` + +Calls back to PowerModelsDistribution.solve_mc_model, and therefore will accept any valid `kwargs` +for that function. See PowerModelsDistribution [documentation](https://lanl-ansi.github.io/PowerModelsDistribution.jl/latest) +for more details. +""" +function solve_mn_block_mld(data::Dict{String,<:Any}, model_type::Type, solver; kwargs...)::Dict{String,Any} + solve_onm_model(data, model_type, solver, build_mn_block_mld; multinetwork=true, kwargs...) +end + + +""" + build_mn_block_mld(pm::PMD.AbstractUBFModels) + +Multinetwork load shedding problem for Branch Flow model +""" +function build_mn_block_mld(pm::PMD.AbstractUBFModels) + for n in nw_ids(pm) + var_opts = ref(pm, n, :options, "variables") + con_opts = ref(pm, n, :options, "constraints") + + variable_block_indicator(pm; nw=n, relax=var_opts["relax-integer-variables"]) + !con_opts["disable-grid-forming-inverter-constraint"] && variable_inverter_indicator(pm; nw=n, relax=var_opts["relax-integer-variables"]) + + PMD.variable_mc_bus_voltage_on_off(pm; nw=n, bounded=!var_opts["unbound-voltage"]) + + PMD.variable_mc_branch_current(pm; nw=n, bounded=!var_opts["unbound-line-current"]) + PMD.variable_mc_branch_power(pm; nw=n, bounded=!var_opts["unbound-line-power"]) + + PMD.variable_mc_switch_power(pm; nw=n, bounded=!var_opts["unbound-switch-power"]) + variable_switch_state(pm; nw=n, relax=var_opts["relax-integer-variables"]) + + PMD.variable_mc_transformer_power(pm; nw=n, bounded=!var_opts["unbound-transformer-power"]) + PMD.variable_mc_oltc_transformer_tap(pm; nw=n) + + PMD.variable_mc_generator_power_on_off(pm; nw=n, bounded=!var_opts["unbound-generation-power"]) + + variable_mc_storage_power_mi_on_off(pm; nw=n, relax=var_opts["relax-integer-variables"], bounded=!var_opts["unbound-storage-power"]) + + PMD.variable_mc_load_power(pm; nw=n) + + PMD.variable_mc_capcontrol(pm; nw=n, relax=var_opts["relax-integer-variables"]) + + PMD.constraint_mc_model_current(pm; nw=n) + + !con_opts["disable-grid-forming-inverter-constraint"] && constraint_grid_forming_inverter_per_cc(pm; nw=n, relax=var_opts["relax-integer-variables"]) + + for i in ids(pm, n, :bus) + if con_opts["disable-grid-forming-inverter-constraint"] + PMD.constraint_mc_theta_ref(pm, i; nw=n) + else + constraint_mc_inverter_theta_ref(pm, i; nw=n) + end + end + + constraint_mc_bus_voltage_block_on_off(pm; nw=n) + + for i in ids(pm, n, :gen) + !var_opts["unbound-generation-power"] && constraint_mc_generator_power_block_on_off(pm, i; nw=n) + end + + for i in ids(pm, n, :load) + PMD.constraint_mc_load_power(pm, i; nw=n) + end + + for i in ids(pm, n, :bus) + constraint_mc_power_balance_shed_block(pm, i; nw=n) + end + + for i in ids(pm, n, :storage) + constraint_storage_complementarity_mi_block_on_off(pm, i; nw=n) + constraint_mc_storage_block_on_off(pm, i; nw=n) + constraint_mc_storage_losses_block_on_off(pm, i; nw=n) + !con_opts["disable-thermal-limit-constraints"] && !var_opts["unbound-storage-power"] && PMD.constraint_mc_storage_thermal_limit(pm, i; nw=n) + !con_opts["disable-storage-unbalance-constraint"] && constraint_mc_storage_phase_unbalance_grid_following(pm, i; nw=n) + end + + for i in ids(pm, n, :branch) + PMD.constraint_mc_power_losses(pm, i; nw=n) + PMD.constraint_mc_model_voltage_magnitude_difference(pm, i; nw=n) + PMD.constraint_mc_voltage_angle_difference(pm, i; nw=n) + + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_thermal_limit_from(pm, i; nw=n) + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_thermal_limit_to(pm, i; nw=n) + + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_ampacity_from(pm, i; nw=n) + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_ampacity_to(pm, i; nw=n) + end + + con_opts["disable-microgrid-networking"] && constraint_disable_networking(pm; nw=n, relax=var_opts["relax-integer-variables"]) + + !con_opts["disable-radiality-constraint"] && constraint_radial_topology(pm; nw=n, relax=var_opts["relax-integer-variables"]) + + !con_opts["disable-block-isolation-constraint"] && constraint_isolate_block(pm; nw=n) + + for i in ids(pm, n, :switch) + constraint_mc_switch_state_open_close(pm, i; nw=n) + + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_switch_thermal_limit(pm, i; nw=n) + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_switch_ampacity(pm, i; nw=n) + end + + for i in ids(pm, n, :transformer) + constraint_mc_transformer_power_block_on_off(pm, i; nw=n, fix_taps=false) + end + end + + network_ids = sort(collect(nw_ids(pm))) + + n_1 = network_ids[1] + + for i in ids(pm, :storage; nw=n_1) + PMD.constraint_storage_state(pm, i; nw=n_1) + end + + !ref(pm, n_1, :options, "constraints")["disable-switch-close-action-limit"] && constraint_switch_close_action_limit(pm; nw=n_1) + + for n_2 in network_ids[2:end] + !ref(pm, n_2, :options, "constraints")["disable-switch-close-action-limit"] && constraint_switch_close_action_limit(pm, n_1, n_2) + + for i in ids(pm, :storage; nw=n_2) + PMD.constraint_storage_state(pm, i, n_1, n_2) + end + + n_1 = n_2 + end + + objective_min_shed_load_block(pm) +end + + +""" + build_mn_block_mld(pm::AbstractUnbalancedPowerModel) + +Multinetwork load shedding problem for Bus Injection models +""" +function build_mn_block_mld(pm::AbstractUnbalancedPowerModel) + for n in nw_ids(pm) + var_opts = ref(pm, n, :options, "variables") + con_opts = ref(pm, n, :options, "constraints") + + variable_block_indicator(pm; nw=n, relax=var_opts["relax-integer-variables"]) + !con_opts["disable-grid-forming-inverter-constraint"] && variable_inverter_indicator(pm; nw=n, relax=var_opts["relax-integer-variables"]) + + PMD.variable_mc_bus_voltage_on_off(pm; nw=n, bounded=!var_opts["unbound-voltage"]) + + PMD.variable_mc_branch_power(pm; nw=n, bounded=!var_opts["unbound-line-power"]) + + PMD.variable_mc_switch_power(pm; nw=n, bounded=!var_opts["unbound-switch-power"]) + variable_switch_state(pm; nw=n, relax=var_opts["relax-integer-variables"]) + + PMD.variable_mc_transformer_power(pm; nw=n, bounded=!var_opts["unbound-transformer-power"]) + PMD.variable_mc_oltc_transformer_tap(pm; nw=n) + + PMD.variable_mc_generator_power_on_off(pm; nw=n, bounded=!var_opts["unbound-generation-power"]) + + variable_mc_storage_power_mi_on_off(pm; nw=n, relax=var_opts["relax-integer-variables"], bounded=!var_opts["unbound-storage-power"]) + + PMD.variable_mc_load_power(pm; nw=n) + + PMD.variable_mc_capcontrol(pm; nw=n, relax=var_opts["relax-integer-variables"]) + + PMD.constraint_mc_model_voltage(pm; nw=n) + + !con_opts["disable-grid-forming-inverter-constraint"] && constraint_grid_forming_inverter_per_cc(pm; nw=n, relax=var_opts["relax-integer-variables"]) + + for i in ids(pm, n, :bus) + if con_opts["disable-grid-forming-inverter-constraint"] + PMD.constraint_mc_theta_ref(pm, i; nw=n) + else + constraint_mc_inverter_theta_ref(pm, i; nw=n) + end + end + + constraint_mc_bus_voltage_block_on_off(pm; nw=n) + + for i in ids(pm, n, :gen) + !var_opts["unbound-generation-power"] && constraint_mc_generator_power_block_on_off(pm, i; nw=n) + end + + for i in ids(pm, n, :load) + PMD.constraint_mc_load_power(pm, i; nw=n) + end + + for i in ids(pm, n, :bus) + constraint_mc_power_balance_shed_block(pm, i; nw=n) + end + + for i in ids(pm, n, :storage) + constraint_storage_complementarity_mi_block_on_off(pm, i; nw=n) + constraint_mc_storage_block_on_off(pm, i; nw=n) + constraint_mc_storage_losses_block_on_off(pm, i; nw=n) + !con_opts["disable-thermal-limit-constraints"] && !var_opts["unbound-storage-power"] && PMD.constraint_mc_storage_thermal_limit(pm, i; nw=n) + !con_opts["disable-storage-unbalance-constraint"] && constraint_mc_storage_phase_unbalance_grid_following(pm, i; nw=n) + end + + for i in ids(pm, n, :branch) + PMD.constraint_mc_ohms_yt_from(pm, i; nw=n) + PMD.constraint_mc_ohms_yt_to(pm, i; nw=n) + PMD.constraint_mc_voltage_angle_difference(pm, i; nw=n) + + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_thermal_limit_from(pm, i; nw=n) + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_thermal_limit_to(pm, i; nw=n) + + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_ampacity_from(pm, i; nw=n) + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_ampacity_to(pm, i; nw=n) + end + + con_opts["disable-microgrid-networking"] && constraint_disable_networking(pm; nw=n, relax=var_opts["relax-integer-variables"]) + !con_opts["disable-radiality-constraint"] && constraint_radial_topology(pm; nw=n, relax=var_opts["relax-integer-variables"]) + !con_opts["disable-block-isolation-constraint"] && constraint_isolate_block(pm; nw=n) + for i in ids(pm, n, :switch) + constraint_mc_switch_state_open_close(pm, i; nw=n) + + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_switch_thermal_limit(pm, i; nw=n) + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_switch_ampacity(pm, i; nw=n) + end + + for i in ids(pm, n, :transformer) + constraint_mc_transformer_power_block_on_off(pm, i; nw=n, fix_taps=false) + end + end + + network_ids = sort(collect(nw_ids(pm))) + + n_1 = network_ids[1] + + for i in ids(pm, :storage; nw=n_1) + PMD.constraint_storage_state(pm, i; nw=n_1) + end + + !ref(pm, n_1, :options, "constraints")["disable-switch-close-action-limit"] && constraint_switch_close_action_limit(pm; nw=n_1) + + for n_2 in network_ids[2:end] + !ref(pm, n_2, :options, "constraints")["disable-switch-close-action-limit"] && constraint_switch_close_action_limit(pm, n_1, n_2) + + for i in ids(pm, :storage; nw=n_2) + PMD.constraint_storage_state(pm, i, n_1, n_2) + end + + n_1 = n_2 + end + + objective_min_shed_load_block(pm) +end + + +""" + solve_block_mld( + data::Dict{String,<:Any}, + model_type::Type, + solver; + kwargs... + )::Dict{String,Any} + +Solves a multiconductor optimal switching (mixed-integer) problem using `model_type` and `solver` + +Calls back to PowerModelsDistribution.solve_mc_model, and therefore will accept any valid `kwargs` +for that function. See PowerModelsDistribution [documentation](https://lanl-ansi.github.io/PowerModelsDistribution.jl/latest) +for more details. +""" +function solve_block_mld(data::Dict{String,<:Any}, model_type::Type, solver; kwargs...)::Dict{String,Any} + solve_onm_model(data, model_type, solver, build_block_mld; multinetwork=false, kwargs...) +end + + +""" + build_block_mld(pm::PMD.AbstractUBFModels) + +Single-network load shedding problem for Branch Flow model +""" +function build_block_mld(pm::PMD.AbstractUBFModels) + var_opts = ref(pm, :options, "variables") + con_opts = ref(pm, :options, "constraints") + + variable_block_indicator(pm; relax=var_opts["relax-integer-variables"]) + !con_opts["disable-grid-forming-inverter-constraint"] && variable_inverter_indicator(pm; relax=var_opts["relax-integer-variables"]) + + PMD.variable_mc_bus_voltage_on_off(pm; bounded=!var_opts["unbound-voltage"]) + + PMD.variable_mc_branch_current(pm; bounded=!var_opts["unbound-line-current"]) + PMD.variable_mc_branch_power(pm; bounded=!var_opts["unbound-line-power"]) + + PMD.variable_mc_switch_power(pm; bounded=!var_opts["unbound-switch-power"]) + variable_switch_state(pm; relax=var_opts["relax-integer-variables"]) + + PMD.variable_mc_transformer_power(pm; bounded=!var_opts["unbound-transformer-power"]) + PMD.variable_mc_oltc_transformer_tap(pm) + + PMD.variable_mc_generator_power_on_off(pm; bounded=!var_opts["unbound-generation-power"]) + + variable_mc_storage_power_mi_on_off(pm; bounded=!var_opts["unbound-storage-power"], relax=var_opts["relax-integer-variables"]) + + PMD.variable_mc_load_power(pm) + + PMD.variable_mc_capcontrol(pm; relax=var_opts["relax-integer-variables"]) + + PMD.constraint_mc_model_current(pm) + + !con_opts["disable-grid-forming-inverter-constraint"] && constraint_grid_forming_inverter_per_cc(pm; relax=var_opts["relax-integer-variables"]) + + for i in ids(pm, :bus) + if con_opts["disable-grid-forming-inverter-constraint"] + PMD.constraint_mc_theta_ref(pm, i) + else + constraint_mc_inverter_theta_ref(pm, i) + end + end + + constraint_mc_bus_voltage_block_on_off(pm) + + for i in ids(pm, :gen) + !var_opts["unbound-generation-power"] && constraint_mc_generator_power_block_on_off(pm, i) + end + + for i in ids(pm, :load) + PMD.constraint_mc_load_power(pm, i) + end + + for i in ids(pm, :bus) + constraint_mc_power_balance_shed_block(pm, i) + end + + for i in ids(pm, :storage) + PMD.constraint_storage_state(pm, i) + constraint_storage_complementarity_mi_block_on_off(pm, i) + constraint_mc_storage_block_on_off(pm, i) + constraint_mc_storage_losses_block_on_off(pm, i) + !con_opts["disable-thermal-limit-constraints"] && !var_opts["unbound-storage-power"] && PMD.constraint_mc_storage_thermal_limit(pm, i) + !con_opts["disable-storage-unbalance-constraint"] && constraint_mc_storage_phase_unbalance_grid_following(pm, i) + end + + for i in ids(pm, :branch) + PMD.constraint_mc_power_losses(pm, i) + PMD.constraint_mc_model_voltage_magnitude_difference(pm, i) + PMD.constraint_mc_voltage_angle_difference(pm, i) + + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_thermal_limit_from(pm, i) + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_thermal_limit_to(pm, i) + + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_ampacity_from(pm, i) + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_ampacity_to(pm, i) + end + + !con_opts["disable-switch-close-action-limit"] && constraint_switch_close_action_limit(pm) + con_opts["disable-microgrid-networking"] && constraint_disable_networking(pm; relax=var_opts["relax-integer-variables"]) + !con_opts["disable-radiality-constraint"] && constraint_radial_topology(pm; relax=var_opts["relax-integer-variables"]) + !con_opts["disable-block-isolation-constraint"] && constraint_isolate_block(pm) + for i in ids(pm, :switch) + constraint_mc_switch_state_open_close(pm, i) + + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_switch_thermal_limit(pm, i) + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_switch_ampacity(pm, i) + end + + for i in ids(pm, :transformer) + constraint_mc_transformer_power_block_on_off(pm, i; fix_taps=false) + end + + objective_min_shed_load_block_rolling_horizon(pm) +end + + +""" + build_block_mld(pm::AbstractUnbalancedPowerModel) + +Single network load shedding problem for Bus Injection model +""" +function build_block_mld(pm::AbstractUnbalancedPowerModel) + var_opts = ref(pm, :options, "variables") + con_opts = ref(pm, :options, "constraints") + + variable_block_indicator(pm; relax=var_opts["relax-integer-variables"]) + !con_opts["disable-grid-forming-inverter-constraint"] && variable_inverter_indicator(pm; relax=var_opts["relax-integer-variables"]) + + PMD.variable_mc_bus_voltage_on_off(pm; bounded=!var_opts["unbound-voltage"]) + + PMD.variable_mc_branch_power(pm; bounded=!var_opts["unbound-line-power"]) + + PMD.variable_mc_switch_power(pm; bounded=!var_opts["unbound-switch-power"]) + variable_switch_state(pm; relax=var_opts["relax-integer-variables"]) + + PMD.variable_mc_transformer_power(pm; bounded=!var_opts["unbound-transformer-power"]) + PMD.variable_mc_oltc_transformer_tap(pm) + + PMD.variable_mc_generator_power_on_off(pm; bounded=!var_opts["unbound-generation-power"]) + + variable_mc_storage_power_mi_on_off(pm; relax=var_opts["relax-integer-variables"], bounded=!var_opts["unbound-storage-power"]) + + PMD.variable_mc_load_power(pm) + + PMD.variable_mc_capcontrol(pm; relax=var_opts["relax-integer-variables"]) + + PMD.constraint_mc_model_voltage(pm) + + !con_opts["disable-grid-forming-inverter-constraint"] && constraint_grid_forming_inverter_per_cc(pm; relax=var_opts["relax-integer-variables"]) + + for i in ids(pm, :bus) + if con_opts["disable-grid-forming-inverter-constraint"] + PMD.constraint_mc_theta_ref(pm, i) + else + constraint_mc_inverter_theta_ref(pm, i) + end + end + + constraint_mc_bus_voltage_block_on_off(pm) + + for i in ids(pm, :gen) + !var_opts["unbound-generation-power"] && constraint_mc_generator_power_block_on_off(pm, i) + end + + for i in ids(pm, :load) + PMD.constraint_mc_load_power(pm, i) + end + + for i in ids(pm, :bus) + constraint_mc_power_balance_shed_block(pm, i) + end + + for i in ids(pm, :storage) + PMD.constraint_storage_state(pm, i) + constraint_storage_complementarity_mi_block_on_off(pm, i) + constraint_mc_storage_block_on_off(pm, i) + constraint_mc_storage_losses_block_on_off(pm, i) + !con_opts["disable-thermal-limit-constraints"] && !var_opts["unbound-storage-power"] && PMD.constraint_mc_storage_thermal_limit(pm, i) + !con_opts["disable-storage-unbalance-constraint"] && constraint_mc_storage_phase_unbalance_grid_following(pm, i) + end + + for i in ids(pm, :branch) + PMD.constraint_mc_ohms_yt_from(pm, i) + PMD.constraint_mc_ohms_yt_to(pm, i) + PMD.constraint_mc_voltage_angle_difference(pm, i) + + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_thermal_limit_from(pm, i) + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_thermal_limit_to(pm, i) + + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_ampacity_from(pm, i) + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_ampacity_to(pm, i) + end + + !con_opts["disable-switch-close-action-limit"] && constraint_switch_close_action_limit(pm) + con_opts["disable-microgrid-networking"] && constraint_disable_networking(pm; relax=var_opts["relax-integer-variables"]) + !con_opts["disable-radiality-constraint"] && constraint_radial_topology(pm; relax=var_opts["relax-integer-variables"]) + !con_opts["disable-block-isolation-constraint"] && constraint_isolate_block(pm) + for i in ids(pm, :switch) + constraint_mc_switch_state_open_close(pm, i) + + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_switch_thermal_limit(pm, i) + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_switch_ampacity(pm, i) + end + + for i in ids(pm, :transformer) + constraint_mc_transformer_power_block_on_off(pm, i; fix_taps=false) + end + + objective_min_shed_load_block_rolling_horizon(pm) +end diff --git a/src/prob/mld_traditional.jl b/src/prob/mld_traditional.jl new file mode 100644 index 00000000..fd86d118 --- /dev/null +++ b/src/prob/mld_traditional.jl @@ -0,0 +1,474 @@ +""" + solve_mn_traditional_mld( + data::Dict{String,<:Any}, + model_type::Type, + solver; + kwargs... + )::Dict{String,Any} + +Solves a __multinetwork__ multiconductor traditional mld problem using `model_type` and `solver` + +Calls back to PowerModelsDistribution.solve_mc_model, and therefore will accept any valid `kwargs` +for that function. See PowerModelsDistribution [documentation](https://lanl-ansi.github.io/PowerModelsDistribution.jl/latest) +for more details. +""" +function solve_mn_traditional_mld(data::Dict{String,<:Any}, model_type::Type, solver; kwargs...)::Dict{String,Any} + solve_onm_model(data, model_type, solver, build_mn_traditional_mld; multinetwork=true, kwargs...) +end + + +""" + build_mn_traditional_mld(pm::PMD.AbstractUBFModels) + +Multinetwork load shedding problem for Branch Flow model +""" +function build_mn_traditional_mld(pm::PMD.AbstractUBFModels) + for n in nw_ids(pm) + var_opts = ref(pm, n, :options, "variables") + con_opts = ref(pm, n, :options, "constraints") + + !con_opts["disable-grid-forming-inverter-constraint"] && variable_inverter_indicator(pm; nw=n, relax=var_opts["relax-integer-variables"]) + + variable_bus_voltage_indicator(pm; nw=n, relax=var_opts["relax-integer-variables"]) + PMD.variable_mc_bus_voltage_on_off(pm; nw=n, bounded=!var_opts["unbound-voltage"]) + + PMD.variable_mc_branch_current(pm; nw=n, bounded=!var_opts["unbound-line-current"]) + PMD.variable_mc_branch_power(pm; nw=n, bounded=!var_opts["unbound-line-power"]) + + PMD.variable_mc_switch_power(pm; nw=n, bounded=!var_opts["unbound-switch-power"]) + variable_switch_state(pm; nw=n, relax=var_opts["relax-integer-variables"]) + + PMD.variable_mc_transformer_power(pm; nw=n, bounded=!var_opts["unbound-transformer-power"]) + PMD.variable_mc_oltc_transformer_tap(pm; nw=n) + + variable_generator_indicator(pm; nw=n, relax=var_opts["relax-integer-variables"]) + PMD.variable_mc_generator_power_on_off(pm; nw=n, bounded=!var_opts["unbound-generation-power"]) + + variable_storage_indicator(pm; nw=n, relax=var_opts["relax-integer-variables"]) + variable_mc_storage_power_mi_on_off(pm; nw=n, relax=var_opts["relax-integer-variables"], bounded=!var_opts["unbound-storage-power"]) + + variable_load_indicator(pm; nw=n, relax=var_opts["relax-integer-variables"]) + PMD.variable_mc_load_power(pm; nw=n) + + PMD.variable_mc_capcontrol(pm; nw=n, relax=var_opts["relax-integer-variables"]) + + PMD.constraint_mc_model_current(pm; nw=n) + + !con_opts["disable-grid-forming-inverter-constraint"] && constraint_grid_forming_inverter_per_cc(pm; nw=n, relax=var_opts["relax-integer-variables"]) + + for i in ids(pm, n, :bus) + if con_opts["disable-grid-forming-inverter-constraint"] + PMD.constraint_mc_theta_ref(pm, i; nw=n) + else + constraint_mc_inverter_theta_ref(pm, i; nw=n) + end + end + + constraint_mc_bus_voltage_traditional_on_off(pm; nw=n) + + for i in ids(pm, n, :gen) + constraint_mc_generator_power_traditional_on_off(pm, i; nw=n) + end + + for i in ids(pm, n, :load) + PMD.constraint_mc_load_power(pm, i; nw=n) + end + + for i in ids(pm, n, :bus) + constraint_mc_power_balance_shed_traditional(pm, i; nw=n) + end + + for i in ids(pm, n, :storage) + constraint_storage_complementarity_mi_traditional_on_off(pm, i; nw=n) + constraint_mc_storage_traditional_on_off(pm, i; nw=n) + constraint_mc_storage_losses_traditional_on_off(pm, i; nw=n) + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_storage_thermal_limit(pm, i; nw=n) + !con_opts["disable-storage-unbalance-constraint"] && constraint_mc_storage_phase_unbalance_grid_following(pm, i; nw=n) + end + + for i in ids(pm, n, :branch) + PMD.constraint_mc_power_losses(pm, i; nw=n) + PMD.constraint_mc_model_voltage_magnitude_difference(pm, i; nw=n) + PMD.constraint_mc_voltage_angle_difference(pm, i; nw=n) + + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_thermal_limit_from(pm, i; nw=n) + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_thermal_limit_to(pm, i; nw=n) + + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_ampacity_from(pm, i; nw=n) + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_ampacity_to(pm, i; nw=n) + end + + con_opts["disable-microgrid-networking"] && constraint_disable_networking(pm; nw=n, relax=var_opts["relax-integer-variables"]) + !con_opts["disable-radiality-constraint"] && constraint_radial_topology(pm; nw=n, relax=var_opts["relax-integer-variables"]) + !con_opts["disable-block-isolation-constraint"] && constraint_isolate_block_traditional(pm; nw=n) + for i in ids(pm, n, :switch) + constraint_mc_switch_state_open_close(pm, i; nw=n) + + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_switch_thermal_limit(pm, i; nw=n) + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_switch_ampacity(pm, i; nw=n) + end + + for i in ids(pm, n, :transformer) + constraint_mc_transformer_power_traditional_on_off(pm, i; nw=n, fix_taps=false) + end + end + + network_ids = sort(collect(nw_ids(pm))) + + n_1 = network_ids[1] + + for i in ids(pm, :storage; nw=n_1) + PMD.constraint_storage_state(pm, i; nw=n_1) + end + + !ref(pm, n_1, :options, "constraints")["disable-switch-close-action-limit"] && constraint_switch_close_action_limit(pm; nw=n_1) + + for n_2 in network_ids[2:end] + !ref(pm, n_2, :options, "constraints")["disable-switch-close-action-limit"] && constraint_switch_close_action_limit(pm, n_1, n_2) + + for i in ids(pm, :storage; nw=n_2) + PMD.constraint_storage_state(pm, i, n_1, n_2) + end + + n_1 = n_2 + end + + objective_min_shed_load_traditional(pm) +end + + +""" + build_mn_traditional_mld(pm::PMD.AbstractUnbalancedPowerModel) + +Multinetwork load shedding problem for Bus Injection model +""" +function build_mn_traditional_mld(pm::PMD.AbstractUnbalancedPowerModel) + for n in nw_ids(pm) + var_opts = ref(pm, n, :options, "variables") + con_opts = ref(pm, n, :options, "constraints") + + !con_opts["disable-grid-forming-inverter-constraint"] && variable_inverter_indicator(pm; nw=n, relax=var_opts["relax-integer-variables"]) + + variable_bus_voltage_indicator(pm; nw=n, relax=var_opts["relax-integer-variables"]) + PMD.variable_mc_bus_voltage_on_off(pm; nw=n, bounded=!var_opts["unbound-voltage"]) + + PMD.variable_mc_branch_power(pm; nw=n, bounded=!var_opts["unbound-line-power"]) + + PMD.variable_mc_switch_power(pm; nw=n, bounded=!var_opts["unbound-switch-power"]) + variable_switch_state(pm; nw=n, relax=var_opts["relax-integer-variables"]) + + PMD.variable_mc_transformer_power(pm; nw=n, bounded=!var_opts["unbound-transformer-power"]) + PMD.variable_mc_oltc_transformer_tap(pm; nw=n) + + variable_generator_indicator(pm; nw=n, relax=var_opts["relax-integer-variables"]) + PMD.variable_mc_generator_power_on_off(pm; nw=n, bounded=!var_opts["unbound-generation-power"]) + + variable_storage_indicator(pm; nw=n, relax=var_opts["relax-integer-variables"]) + variable_mc_storage_power_mi_on_off(pm; nw=n, relax=var_opts["relax-integer-variables"], bounded=!var_opts["unbound-storage-power"]) + + variable_load_indicator(pm; nw=n, relax=var_opts["relax-integer-variables"]) + PMD.variable_mc_load_power(pm; nw=n) + + PMD.variable_mc_capcontrol(pm; nw=n, relax=var_opts["relax-integer-variables"]) + + PMD.constraint_mc_model_voltage(pm; nw=n) + + !con_opts["disable-grid-forming-inverter-constraint"] && constraint_grid_forming_inverter_per_cc(pm; nw=n, relax=var_opts["relax-integer-variables"]) + + for i in ids(pm, n, :bus) + if con_opts["disable-grid-forming-inverter-constraint"] + PMD.constraint_mc_theta_ref(pm, i; nw=n) + else + constraint_mc_inverter_theta_ref(pm, i; nw=n) + end + end + + constraint_mc_bus_voltage_traditional_on_off(pm; nw=n) + + for i in ids(pm, n, :gen) + constraint_mc_generator_power_traditional_on_off(pm, i; nw=n) + end + + for i in ids(pm, n, :load) + PMD.constraint_mc_load_power(pm, i; nw=n) + end + + for i in ids(pm, n, :bus) + constraint_mc_power_balance_shed_traditional(pm, i; nw=n) + end + + for i in ids(pm, n, :storage) + constraint_storage_complementarity_mi_traditional_on_off(pm, i; nw=n) + constraint_mc_storage_traditional_on_off(pm, i; nw=n) + constraint_mc_storage_losses_traditional_on_off(pm, i; nw=n) + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_storage_thermal_limit(pm, i; nw=n) + !con_opts["disable-storage-unbalance-constraint"] && constraint_mc_storage_phase_unbalance_grid_following(pm, i; nw=n) + end + + for i in ids(pm, n, :branch) + PMD.constraint_mc_ohms_yt_from(pm, i; nw=n) + PMD.constraint_mc_ohms_yt_to(pm, i; nw=n) + PMD.constraint_mc_voltage_angle_difference(pm, i; nw=n) + + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_thermal_limit_from(pm, i; nw=n) + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_thermal_limit_to(pm, i; nw=n) + + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_ampacity_from(pm, i; nw=n) + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_ampacity_to(pm, i; nw=n) + end + + con_opts["disable-microgrid-networking"] && constraint_disable_networking(pm; nw=n, relax=var_opts["relax-integer-variables"]) + !con_opts["disable-radiality-constraint"] && constraint_radial_topology(pm; nw=n, relax=var_opts["relax-integer-variables"]) + !con_opts["disable-block-isolation-constraint"] && constraint_isolate_block_traditional(pm; nw=n) + for i in ids(pm, n, :switch) + constraint_mc_switch_state_open_close(pm, i; nw=n) + + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_switch_thermal_limit(pm, i; nw=n) + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_switch_ampacity(pm, i; nw=n) + end + + for i in ids(pm, n, :transformer) + constraint_mc_transformer_power_traditional_on_off(pm, i; nw=n, fix_taps=false) + end + end + + network_ids = sort(collect(nw_ids(pm))) + + n_1 = network_ids[1] + + for i in ids(pm, :storage; nw=n_1) + PMD.constraint_storage_state(pm, i; nw=n_1) + end + + !ref(pm, n_1, :options, "constraints")["disable-switch-close-action-limit"] && constraint_switch_close_action_limit(pm; nw=n_1) + + for n_2 in network_ids[2:end] + !ref(pm, n_2, :options, "constraints")["disable-switch-close-action-limit"] && constraint_switch_close_action_limit(pm, n_1, n_2) + + for i in ids(pm, :storage; nw=n_2) + PMD.constraint_storage_state(pm, i, n_1, n_2) + end + + n_1 = n_2 + end + + objective_min_shed_load_traditional(pm) +end + + +""" + solve_traditional_mld( + data::Dict{String,<:Any}, + model_type::Type, + solver; + kwargs... + )::Dict{String,Any} + +Solves a multiconductor traditional mld problem using `model_type` and `solver` + +Calls back to PowerModelsDistribution.solve_mc_model, and therefore will accept any valid `kwargs` +for that function. See PowerModelsDistribution [documentation](https://lanl-ansi.github.io/PowerModelsDistribution.jl/latest) +for more details. +""" +function solve_traditional_mld(data::Dict{String,<:Any}, model_type::Type, solver; kwargs...)::Dict{String,Any} + solve_onm_model(data, model_type, solver, build_traditional_mld; multinetwork=false, kwargs...) +end + + +""" + build_traditional_mld(pm::PMD.AbstractUBFModels) + +Single-network load shedding problem for Branch Flow model +""" +function build_traditional_mld(pm::PMD.AbstractUBFModels) + var_opts = ref(pm, :options, "variables") + con_opts = ref(pm, :options, "constraints") + + !con_opts["disable-grid-forming-inverter-constraint"] && variable_inverter_indicator(pm; relax=var_opts["relax-integer-variables"]) + + variable_bus_voltage_indicator(pm; relax=var_opts["relax-integer-variables"]) + PMD.variable_mc_bus_voltage_on_off(pm; bounded=!var_opts["unbound-voltage"]) + + PMD.variable_mc_branch_current(pm; bounded=!var_opts["unbound-line-current"]) + PMD.variable_mc_branch_power(pm; bounded=!var_opts["unbound-line-power"]) + + PMD.variable_mc_switch_power(pm; bounded=!var_opts["unbound-switch-power"]) + variable_switch_state(pm; relax=var_opts["relax-integer-variables"]) + + PMD.variable_mc_transformer_power(pm; bounded=!var_opts["unbound-transformer-power"]) + PMD.variable_mc_oltc_transformer_tap(pm) + + variable_generator_indicator(pm; relax=var_opts["relax-integer-variables"]) + PMD.variable_mc_generator_power_on_off(pm; bounded=!var_opts["unbound-generation-power"]) + + variable_storage_indicator(pm; relax=var_opts["relax-integer-variables"]) + variable_mc_storage_power_mi_on_off(pm; relax=var_opts["relax-integer-variables"], bounded=!var_opts["unbound-storage-power"]) + + variable_load_indicator(pm; relax=var_opts["relax-integer-variables"]) + PMD.variable_mc_load_power(pm) + + PMD.variable_mc_capcontrol(pm; relax=var_opts["relax-integer-variables"]) + + PMD.constraint_mc_model_current(pm) + + !con_opts["disable-grid-forming-inverter-constraint"] && constraint_grid_forming_inverter_per_cc(pm; relax=var_opts["relax-integer-variables"]) + + for i in ids(pm, :bus) + if con_opts["disable-grid-forming-inverter-constraint"] + PMD.constraint_mc_theta_ref(pm, i) + else + constraint_mc_inverter_theta_ref(pm, i) + end + end + + constraint_mc_bus_voltage_traditional_on_off(pm) + + for i in ids(pm, :gen) + constraint_mc_generator_power_traditional_on_off(pm, i) + end + + for i in ids(pm, :load) + PMD.constraint_mc_load_power(pm, i) + end + + for i in ids(pm, :bus) + constraint_mc_power_balance_shed_traditional(pm, i) + end + + for i in ids(pm, :storage) + PMD.constraint_storage_state(pm, i) + constraint_storage_complementarity_mi_traditional_on_off(pm, i) + constraint_mc_storage_traditional_on_off(pm, i) + constraint_mc_storage_losses_traditional_on_off(pm, i) + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_storage_thermal_limit(pm, i) + !con_opts["disable-storage-unbalance-constraint"] && constraint_mc_storage_phase_unbalance_grid_following(pm, i) + end + + for i in ids(pm, :branch) + PMD.constraint_mc_power_losses(pm, i) + PMD.constraint_mc_model_voltage_magnitude_difference(pm, i) + PMD.constraint_mc_voltage_angle_difference(pm, i) + + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_thermal_limit_from(pm, i) + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_thermal_limit_to(pm, i) + + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_ampacity_from(pm, i) + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_ampacity_to(pm, i) + end + + !con_opts["disable-switch-close-action-limit"] && constraint_switch_close_action_limit(pm) + con_opts["disable-microgrid-networking"] && constraint_disable_networking(pm; relax=var_opts["relax-integer-variables"]) + !con_opts["disable-radiality-constraint"] && constraint_radial_topology(pm; relax=var_opts["relax-integer-variables"]) + !con_opts["disable-block-isolation-constraint"] && constraint_isolate_block_traditional(pm) + for i in ids(pm, :switch) + constraint_mc_switch_state_open_close(pm, i) + + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_switch_thermal_limit(pm, i) + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_switch_ampacity(pm, i) + end + + for i in ids(pm, :transformer) + constraint_mc_transformer_power_traditional_on_off(pm, i; fix_taps=false) + end + + objective_min_shed_load_traditional_rolling_horizon(pm) +end + + +""" + build_traditional_mld(pm::PMD.AbstractUnbalancedPowerModel) + +Single-network load shedding problem for Bus Injection model +""" +function build_traditional_mld(pm::PMD.AbstractUnbalancedPowerModel) + var_opts = ref(pm, :options, "variables") + con_opts = ref(pm, :options, "constraints") + + !con_opts["disable-grid-forming-inverter-constraint"] && variable_inverter_indicator(pm; relax=var_opts["relax-integer-variables"]) + + variable_bus_voltage_indicator(pm; relax=var_opts["relax-integer-variables"]) + PMD.variable_mc_bus_voltage_on_off(pm; bounded=!var_opts["unbound-voltage"]) + + PMD.variable_mc_branch_power(pm; bounded=!var_opts["unbound-line-power"]) + + PMD.variable_mc_switch_power(pm; bounded=!var_opts["unbound-switch-power"]) + variable_switch_state(pm; relax=var_opts["relax-integer-variables"]) + + PMD.variable_mc_transformer_power(pm; bounded=!var_opts["unbound-transformer-power"]) + PMD.variable_mc_oltc_transformer_tap(pm) + + variable_generator_indicator(pm; relax=var_opts["relax-integer-variables"]) + PMD.variable_mc_generator_power_on_off(pm; bounded=!var_opts["unbound-generation-power"]) + + variable_storage_indicator(pm; relax=var_opts["relax-integer-variables"]) + variable_mc_storage_power_mi_on_off(pm; relax=var_opts["relax-integer-variables"], bounded=!var_opts["unbound-storage-power"]) + + variable_load_indicator(pm; relax=var_opts["relax-integer-variables"]) + PMD.variable_mc_load_power(pm) + + PMD.variable_mc_capcontrol(pm; relax=var_opts["relax-integer-variables"]) + + PMD.constraint_mc_model_voltage(pm) + + !con_opts["disable-grid-forming-inverter-constraint"] && constraint_grid_forming_inverter_per_cc(pm; relax=var_opts["relax-integer-variables"]) + + for i in ids(pm, :bus) + if con_opts["disable-grid-forming-inverter-constraint"] + PMD.constraint_mc_theta_ref(pm, i) + else + constraint_mc_inverter_theta_ref(pm, i) + end + end + + constraint_mc_bus_voltage_traditional_on_off(pm) + + for i in ids(pm, :gen) + constraint_mc_generator_power_traditional_on_off(pm, i) + end + + for i in ids(pm, :load) + PMD.constraint_mc_load_power(pm, i) + end + + for i in ids(pm, :bus) + constraint_mc_power_balance_shed_traditional(pm, i) + end + + for i in ids(pm, :storage) + PMD.constraint_storage_state(pm, i) + constraint_storage_complementarity_mi_traditional_on_off(pm, i) + constraint_mc_storage_traditional_on_off(pm, i) + constraint_mc_storage_losses_traditional_on_off(pm, i) + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_storage_thermal_limit(pm, i) + !con_opts["disable-storage-unbalance-constraint"] && constraint_mc_storage_phase_unbalance_grid_following(pm, i) + end + + for i in ids(pm, :branch) + PMD.constraint_mc_ohms_yt_from(pm, i) + PMD.constraint_mc_ohms_yt_to(pm, i) + PMD.constraint_mc_voltage_angle_difference(pm, i) + + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_thermal_limit_from(pm, i) + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_thermal_limit_to(pm, i) + + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_ampacity_from(pm, i) + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_ampacity_to(pm, i) + end + + !con_opts["disable-switch-close-action-limit"] && constraint_switch_close_action_limit(pm) + con_opts["disable-microgrid-networking"] && constraint_disable_networking(pm; relax=var_opts["relax-integer-variables"]) + !con_opts["disable-radiality-constraint"] && constraint_radial_topology(pm; relax=var_opts["relax-integer-variables"]) + !con_opts["disable-block-isolation-constraint"] && constraint_isolate_block_traditional(pm) + for i in ids(pm, :switch) + constraint_mc_switch_state_open_close(pm, i) + + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_switch_thermal_limit(pm, i) + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_switch_ampacity(pm, i) + end + + for i in ids(pm, :transformer) + constraint_mc_transformer_power_traditional_on_off(pm, i; fix_taps=false) + end + + objective_min_shed_load_traditional_rolling_horizon(pm) +end diff --git a/src/prob/mn_opf_oltc_capc.jl b/src/prob/mn_opf_oltc_capc.jl deleted file mode 100644 index 4a504705..00000000 --- a/src/prob/mn_opf_oltc_capc.jl +++ /dev/null @@ -1,209 +0,0 @@ -""" - solve_mn_mc_opf_oltc_capc(data::Union{Dict{String,<:Any},String}, model_type::Type, solver; - solution_processors::Vector{Function}=Function[], - eng2math_passthrough::Dict{String,Vector{String}}=Dict{String,Vector{String}}(), - ref_extensions::Vector{Function}=Function[], kwargs... - )::Dict{String,Any} - -Solve OPF with capacitor control -""" -function solve_mn_mc_opf_oltc_capc(data::Union{Dict{String,<:Any},String}, model_type::Type, solver; - solution_processors::Vector{Function}=Function[], - eng2math_passthrough::Dict{String,Vector{String}}=Dict{String,Vector{String}}(), - ref_extensions::Vector{Function}=Function[], kwargs...)::Dict{String,Any} - return PMD.solve_mc_model( - data, - model_type, - solver, - PowerModelsONM.build_mn_mc_opf_oltc_capc; - multinetwork=true, - solution_processors=Function[_solution_processors..., solution_processors...], - ref_extensions=Function[_ref_extensions..., ref_extensions...], - eng2math_passthrough=recursive_merge(_eng2math_passthrough, eng2math_passthrough), - kwargs... - ) -end - - -""" - build_mn_mc_opf_oltc_capc(pm::AbstractUnbalancedPowerModel) - -constructor for bus injection opf -""" -function build_mn_mc_opf_oltc_capc(pm::AbstractUnbalancedPowerModel) - for n in nw_ids(pm) - PMD.variable_mc_bus_voltage(pm; nw=n) - - PMD.variable_mc_branch_power(pm; nw=n) - PMD.variable_mc_switch_power(pm; nw=n) - PMD.variable_mc_transformer_power(pm; nw=n) - - PMD.variable_mc_oltc_transformer_tap(pm; nw=n) - - PMD.variable_mc_generator_power(pm; nw=n) - PMD.variable_mc_load_power(pm; nw=n) - PMD.variable_mc_storage_power_mi(pm; nw=n, relax=true) - - PMD.variable_mc_capcontrol(pm; nw=n, relax=true) - - PMD.constraint_mc_model_voltage(pm; nw=n) - - for i in ids(pm, n, :ref_buses) - PMD.constraint_mc_theta_ref(pm, i; nw=n) - end - - # generators should be constrained before KCL, or Pd/Qd undefined - for i in ids(pm, n, :gen) - PMD.constraint_mc_generator_power(pm, i; nw=n) - end - - # loads should be constrained before KCL, or Pd/Qd undefined - for i in ids(pm, n, :load) - PMD.constraint_mc_load_power(pm, i; nw=n) - end - - for i in ids(pm, n, :bus) - PMD.constraint_mc_power_balance_capc(pm, i; nw=n) - end - - for i in ids(pm, n, :storage) - PMD.constraint_storage_complementarity_mi(pm, i; nw=n) - PMD.constraint_mc_storage_losses(pm, i; nw=n) - PMD.constraint_mc_storage_thermal_limit(pm, i; nw=n) - constraint_mc_storage_phase_unbalance(pm, i; nw=n) - end - - for i in ids(pm, n, :branch) - PMD.constraint_mc_ohms_yt_from(pm, i; nw=n) - PMD.constraint_mc_ohms_yt_to(pm, i; nw=n) - PMD.constraint_mc_voltage_angle_difference(pm, i; nw=n) - - PMD.constraint_mc_thermal_limit_from(pm, i; nw=n) - PMD.constraint_mc_thermal_limit_to(pm, i; nw=n) - - PMD.constraint_mc_ampacity_from(pm, i; nw=n) - PMD.constraint_mc_ampacity_to(pm, i; nw=n) - end - - for i in ids(pm, n, :switch) - PMD.constraint_mc_switch_state(pm, i; nw=n) - PMD.constraint_mc_switch_thermal_limit(pm, i; nw=n) - PMD.constraint_mc_switch_ampacity(pm, i; nw=n) - end - - for i in ids(pm, n, :transformer) - PMD.constraint_mc_transformer_power(pm, i; nw=n, fix_taps=false) - end - - end - - network_ids = sort(collect(nw_ids(pm))) - - n_1 = network_ids[1] - - for i in ids(pm, :storage; nw=n_1) - PMD.constraint_storage_state(pm, i; nw=n_1) - end - - for n_2 in network_ids[2:end] - for i in ids(pm, :storage; nw=n_2) - PMD.constraint_storage_state(pm, i, n_1, n_2) - end - - n_1 = n_2 - end - - objective_mc_min_storage_utilization(pm) -end - - -""" - build_mn_mc_opf_oltc_capc(pm::PMD.AbstractUBFModels) - -constructor for branch flow opf -""" -function build_mn_mc_opf_oltc_capc(pm::PMD.AbstractUBFModels) - for n in nw_ids(pm) - PMD.variable_mc_bus_voltage(pm; nw=n) - - PMD.variable_mc_branch_current(pm; nw=n) - PMD.variable_mc_branch_power(pm; nw=n) - PMD.variable_mc_switch_power(pm; nw=n) - PMD.variable_mc_transformer_power(pm; nw=n) - - PMD.variable_mc_oltc_transformer_tap(pm; nw=n) - - PMD.variable_mc_generator_power(pm; nw=n) - PMD.variable_mc_load_power(pm; nw=n) - PMD.variable_mc_storage_power_mi(pm; nw=n, relax=true) - - PMD.variable_mc_capcontrol(pm; nw=n, relax=true) - - PMD.constraint_mc_model_current(pm; nw=n) - - for i in ids(pm, n, :ref_buses) - PMD.constraint_mc_theta_ref(pm, i; nw=n) - end - - # gens should be constrained before KCL, or Pd/Qd undefined - for i in ids(pm, n, :gen) - PMD.constraint_mc_generator_power(pm, i; nw=n) - end - - # loads should be constrained before KCL, or Pd/Qd undefined - for i in ids(pm, n, :load) - PMD.constraint_mc_load_power(pm, i; nw=n) - end - - for i in ids(pm, n, :bus) - PMD.constraint_mc_power_balance_capc(pm, i; nw=n) - end - - for i in ids(pm, n, :storage) - PMD.constraint_storage_complementarity_mi(pm, i; nw=n) - PMD.constraint_mc_storage_losses(pm, i; nw=n) - PMD.constraint_mc_storage_thermal_limit(pm, i; nw=n) - constraint_mc_storage_phase_unbalance(pm, i; nw=n) - end - - for i in ids(pm, n, :branch) - PMD.constraint_mc_power_losses(pm, i; nw=n) - PMD.constraint_mc_model_voltage_magnitude_difference(pm, i; nw=n) - PMD.constraint_mc_voltage_angle_difference(pm, i; nw=n) - - PMD.constraint_mc_thermal_limit_from(pm, i; nw=n) - PMD.constraint_mc_thermal_limit_to(pm, i; nw=n) - - PMD.constraint_mc_ampacity_from(pm, i; nw=n) - PMD.constraint_mc_ampacity_to(pm, i; nw=n) - end - - for i in ids(pm, n, :switch) - PMD.constraint_mc_switch_state(pm, i; nw=n) - PMD.constraint_mc_switch_thermal_limit(pm, i; nw=n) - PMD.constraint_mc_switch_ampacity(pm, i; nw=n) - end - - for i in ids(pm, n, :transformer) - PMD.constraint_mc_transformer_power(pm, i; nw=n, fix_taps=false) - end - end - - network_ids = sort(collect(nw_ids(pm))) - - n_1 = network_ids[1] - - for i in ids(pm, :storage; nw=n_1) - PMD.constraint_storage_state(pm, i; nw=n_1) - end - - for n_2 in network_ids[2:end] - for i in ids(pm, :storage; nw=n_2) - PMD.constraint_storage_state(pm, i, n_1, n_2) - end - - n_1 = n_2 - end - - objective_mc_min_storage_utilization(pm) -end diff --git a/src/prob/opf.jl b/src/prob/opf.jl new file mode 100644 index 00000000..512e86b0 --- /dev/null +++ b/src/prob/opf.jl @@ -0,0 +1,210 @@ +""" + solve_mn_opf( + data::Dict{String,<:Any}, + model_type::Type, + solver; + kwargs... + )::Dict{String,Any} + +Solve multinetwork OPF with transformer tap and capacitor control +""" +function solve_mn_opf(data::Dict{String,<:Any}, model_type::Type, solver; kwargs...)::Dict{String,Any} + solve_onm_model(data, model_type, solver, build_mn_opf; multinetwork=true, kwargs...) +end + + +""" + build_mn_opf(pm::AbstractUnbalancedPowerModel) + +constructor for bus injection opf +""" +function build_mn_opf(pm::AbstractUnbalancedPowerModel) + for n in nw_ids(pm) + var_opts = ref(pm, n, :options, "variables") + con_opts = ref(pm, n, :options, "constraints") + + PMD.variable_mc_bus_voltage(pm; nw=n, bounded=!var_opts["unbound-voltage"]) + + PMD.variable_mc_branch_power(pm; nw=n, bounded=!var_opts["unbound-line-power"]) + PMD.variable_mc_switch_power(pm; nw=n, bounded=!var_opts["unbound-switch-power"]) + PMD.variable_mc_transformer_power(pm; nw=n, bounded=!var_opts["unbound-transformer-power"]) + + PMD.variable_mc_oltc_transformer_tap(pm; nw=n) + + PMD.variable_mc_generator_power(pm; nw=n, bounded=!var_opts["unbound-generation-power"]) + PMD.variable_mc_load_power(pm; nw=n) + PMD.variable_mc_storage_power_mi(pm; nw=n, relax=true, bounded=!var_opts["unbound-storage-power"]) + + PMD.variable_mc_capcontrol(pm; nw=n, relax=true) + + PMD.constraint_mc_model_voltage(pm; nw=n) + + for i in ids(pm, n, :ref_buses) + PMD.constraint_mc_theta_ref(pm, i; nw=n) + end + + # generators should be constrained before KCL, or Pd/Qd undefined + for i in ids(pm, n, :gen) + PMD.constraint_mc_generator_power(pm, i; nw=n) + end + + # loads should be constrained before KCL, or Pd/Qd undefined + for i in ids(pm, n, :load) + PMD.constraint_mc_load_power(pm, i; nw=n) + end + + for i in ids(pm, n, :bus) + PMD.constraint_mc_power_balance_capc(pm, i; nw=n) + end + + for i in ids(pm, n, :storage) + PMD.constraint_storage_complementarity_mi(pm, i; nw=n) + PMD.constraint_mc_storage_losses(pm, i; nw=n) + !con_opts["disable-thermal-limit-constraints"] && !var_opts["unbound-storage-power"] && PMD.constraint_mc_storage_thermal_limit(pm, i; nw=n) + if Int(get(ref(pm, n, :storage, i), "inverter", GRID_FORMING)) == Int(GRID_FOLLOWING) + !con_opts["disable-storage-unbalance-constraint"] && constraint_mc_storage_phase_unbalance(pm, i; nw=n) + end + end + + for i in ids(pm, n, :branch) + PMD.constraint_mc_ohms_yt_from(pm, i; nw=n) + PMD.constraint_mc_ohms_yt_to(pm, i; nw=n) + PMD.constraint_mc_voltage_angle_difference(pm, i; nw=n) + + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_thermal_limit_from(pm, i; nw=n) + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_thermal_limit_to(pm, i; nw=n) + + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_ampacity_from(pm, i; nw=n) + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_ampacity_to(pm, i; nw=n) + end + + for i in ids(pm, n, :switch) + PMD.constraint_mc_switch_state(pm, i; nw=n) + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_switch_thermal_limit(pm, i; nw=n) + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_switch_ampacity(pm, i; nw=n) + end + + for i in ids(pm, n, :transformer) + PMD.constraint_mc_transformer_power(pm, i; nw=n, fix_taps=false) + end + + end + + network_ids = sort(collect(nw_ids(pm))) + + n_1 = network_ids[1] + + for i in ids(pm, :storage; nw=n_1) + PMD.constraint_storage_state(pm, i; nw=n_1) + end + + for n_2 in network_ids[2:end] + for i in ids(pm, :storage; nw=n_2) + PMD.constraint_storage_state(pm, i, n_1, n_2) + end + + n_1 = n_2 + end + + objective_mc_min_storage_utilization(pm) +end + + +""" + build_mn_opf(pm::PMD.AbstractUBFModels) + +constructor for branch flow opf +""" +function build_mn_opf(pm::PMD.AbstractUBFModels) + for n in nw_ids(pm) + var_opts = ref(pm, n, :options, "variables") + con_opts = ref(pm, n, :options, "constraints") + + PMD.variable_mc_bus_voltage(pm; nw=n) + + PMD.variable_mc_branch_current(pm; nw=n, bounded=!var_opts["unbound-line-current"]) + PMD.variable_mc_branch_power(pm; nw=n, bounded=!var_opts["unbound-line-power"]) + + PMD.variable_mc_switch_power(pm; nw=n, bounded=!var_opts["unbound-switch-power"]) + + PMD.variable_mc_transformer_power(pm; nw=n, bounded=!var_opts["unbound-transformer-power"]) + PMD.variable_mc_oltc_transformer_tap(pm; nw=n) + + PMD.variable_mc_generator_power(pm; nw=n, bounded=!var_opts["unbound-generation-power"]) + + PMD.variable_mc_storage_power_mi(pm; nw=n, relax=true, bounded=!var_opts["unbound-storage-power"]) + + PMD.variable_mc_load_power(pm; nw=n) + + PMD.variable_mc_capcontrol(pm; nw=n, relax=true) + + PMD.constraint_mc_model_current(pm; nw=n) + + for i in ids(pm, n, :ref_buses) + PMD.constraint_mc_theta_ref(pm, i; nw=n) + end + + # gens should be constrained before KCL, or Pd/Qd undefined + for i in ids(pm, n, :gen) + PMD.constraint_mc_generator_power(pm, i; nw=n) + end + + # loads should be constrained before KCL, or Pd/Qd undefined + for i in ids(pm, n, :load) + PMD.constraint_mc_load_power(pm, i; nw=n) + end + + for i in ids(pm, n, :bus) + PMD.constraint_mc_power_balance_capc(pm, i; nw=n) + end + + for i in ids(pm, n, :storage) + PMD.constraint_storage_complementarity_mi(pm, i; nw=n) + PMD.constraint_mc_storage_losses(pm, i; nw=n) + !con_opts["disable-thermal-limit-constraints"] && !var_opts["unbound-storage-power"] && PMD.constraint_mc_storage_thermal_limit(pm, i; nw=n) + if Int(get(ref(pm, n, :storage, i), "inverter", GRID_FORMING)) == Int(GRID_FOLLOWING) + !con_opts["disable-storage-unbalance-constraint"] && constraint_mc_storage_phase_unbalance(pm, i; nw=n) + end + end + + for i in ids(pm, n, :branch) + PMD.constraint_mc_power_losses(pm, i; nw=n) + PMD.constraint_mc_model_voltage_magnitude_difference(pm, i; nw=n) + PMD.constraint_mc_voltage_angle_difference(pm, i; nw=n) + + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_thermal_limit_from(pm, i; nw=n) + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_thermal_limit_to(pm, i; nw=n) + + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_ampacity_from(pm, i; nw=n) + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_ampacity_to(pm, i; nw=n) + end + + for i in ids(pm, n, :switch) + PMD.constraint_mc_switch_state(pm, i; nw=n) + !con_opts["disable-thermal-limit-constraints"] && PMD.constraint_mc_switch_thermal_limit(pm, i; nw=n) + !con_opts["disable-current-limit-constraints"] && PMD.constraint_mc_switch_ampacity(pm, i; nw=n) + end + + for i in ids(pm, n, :transformer) + PMD.constraint_mc_transformer_power(pm, i; nw=n, fix_taps=false) + end + end + + network_ids = sort(collect(nw_ids(pm))) + + n_1 = network_ids[1] + + for i in ids(pm, :storage; nw=n_1) + PMD.constraint_storage_state(pm, i; nw=n_1) + end + + for n_2 in network_ids[2:end] + for i in ids(pm, :storage; nw=n_2) + PMD.constraint_storage_state(pm, i, n_1, n_2) + end + + n_1 = n_2 + end + + objective_mc_min_storage_utilization(pm) +end diff --git a/src/prob/osw_mld.jl b/src/prob/osw_mld.jl deleted file mode 100644 index 7af3ee94..00000000 --- a/src/prob/osw_mld.jl +++ /dev/null @@ -1,436 +0,0 @@ -""" - solve_mn_mc_osw_mld_mi(data::Union{Dict{String,<:Any}, String}, model_type::Type, solver; - solution_processors::Vector{Function}=Function[], - eng2math_passthrough::Dict{String,Vector{String}}=Dict{String,Vector{String}}(), - ref_extensions::Vector{Function}=Function[], kwargs... - )::Dict{String,Any} - -Solves a __multinetwork__ multiconductor optimal switching (mixed-integer) problem using `model_type` and `solver` - -Calls back to PowerModelsDistribution.solve_mc_model, and therefore will accept any valid `kwargs` -for that function. See PowerModelsDistribution [documentation](https://lanl-ansi.github.io/PowerModelsDistribution.jl/latest) -for more details. -""" -function solve_mn_mc_osw_mld_mi(data::Union{Dict{String,<:Any}, String}, model_type::Type, solver; - solution_processors::Vector{Function}=Function[], - eng2math_passthrough::Dict{String,Vector{String}}=Dict{String,Vector{String}}(), - ref_extensions::Vector{Function}=Function[], kwargs...)::Dict{String,Any} - - return PMD.solve_mc_model( - data, - model_type, - solver, - build_mn_mc_osw_mld_mi; - multinetwork=true, - solution_processors=Function[_solution_processors..., solution_processors...], - ref_extensions=Function[_ref_extensions..., ref_extensions...], - eng2math_passthrough=recursive_merge(_eng2math_passthrough, eng2math_passthrough), - kwargs... - ) -end - - -""" - build_mn_mc_osw_mld_mi(pm::AbstractUBFSwitchModels) - -Multinetwork load shedding problem for Branch Flow model -""" -function build_mn_mc_osw_mld_mi(pm::AbstractUBFSwitchModels) - for n in nw_ids(pm) - variable_mc_block_indicator(pm; nw=n, relax=false) - - PMD.variable_mc_bus_voltage_on_off(pm; nw=n) - - PMD.variable_mc_branch_current(pm; nw=n) - PMD.variable_mc_branch_power(pm; nw=n) - PMD.variable_mc_switch_power(pm; nw=n) - variable_mc_switch_state(pm; nw=n, relax=false) - variable_mc_switch_fixed(pm; nw=n) - PMD.variable_mc_transformer_power(pm; nw=n) - - PMD.variable_mc_oltc_transformer_tap(pm; nw=n) - - PMD.variable_mc_generator_power_on_off(pm; nw=n) - PMD.variable_mc_storage_power_mi_on_off(pm; nw=n, relax=false) - PMD.variable_mc_load_power(pm; nw=n) - - PMD.variable_mc_capcontrol(pm; nw=n, relax=false) - - PMD.constraint_mc_model_current(pm; nw=n) - - for i in ids(pm, n, :ref_buses) - PMD.constraint_mc_theta_ref(pm, i; nw=n) - end - - PMD.constraint_mc_bus_voltage_on_off(pm; nw=n) - - for i in ids(pm, n, :gen) - PMD.constraint_mc_gen_power_on_off(pm, i; nw=n) - end - - for i in ids(pm, n, :load) - PMD.constraint_mc_load_power(pm, i; nw=n) - end - - for i in ids(pm, n, :bus) - PMD.constraint_mc_power_balance_shed(pm, i; nw=n) - end - - for i in ids(pm, n, :storage) - constraint_storage_complementarity_mi_on_off(pm, i; nw=n) - PMD.constraint_mc_storage_on_off(pm, i; nw=n) - constraint_mc_storage_losses_on_off(pm, i; nw=n) - PMD.constraint_mc_storage_thermal_limit(pm, i; nw=n) - constraint_mc_storage_phase_unbalance(pm, i; nw=n) - end - - for i in ids(pm, n, :branch) - PMD.constraint_mc_power_losses(pm, i; nw=n) - PMD.constraint_mc_model_voltage_magnitude_difference(pm, i; nw=n) - PMD.constraint_mc_voltage_angle_difference(pm, i; nw=n) - - PMD.constraint_mc_thermal_limit_from(pm, i; nw=n) - PMD.constraint_mc_thermal_limit_to(pm, i; nw=n) - - PMD.constraint_mc_ampacity_from(pm, i; nw=n) - PMD.constraint_mc_ampacity_to(pm, i; nw=n) - end - - !get(ref(pm, n), :disable_radial_constraint, false) && constraint_radial_topology(pm; nw=n, relax=false) - !get(ref(pm, n), :disable_isolation_constraint, false) && constraint_block_isolation(pm; nw=n, relax=true) - for i in ids(pm, n, :switch) - PMD.constraint_mc_switch_state_on_off(pm, i; nw=n, relax=true) - - PMD.constraint_mc_switch_thermal_limit(pm, i; nw=n) - PMD.constraint_mc_switch_ampacity(pm, i; nw=n) - end - - for i in ids(pm, n, :transformer) - constraint_mc_transformer_power_on_off(pm, i; nw=n, fix_taps=false) - end - end - - network_ids = sort(collect(nw_ids(pm))) - - n_1 = network_ids[1] - - for i in ids(pm, :storage; nw=n_1) - PMD.constraint_storage_state(pm, i; nw=n_1) - end - - constraint_switch_state_max_actions(pm; nw=n_1) - - for n_2 in network_ids[2:end] - constraint_switch_state_max_actions(pm, n_1, n_2) - - for i in ids(pm, :storage; nw=n_2) - PMD.constraint_storage_state(pm, i, n_1, n_2) - end - - n_1 = n_2 - end - - objective_mc_min_load_setpoint_delta_switch_global(pm) -end - - -""" - build_mn_mc_osw_mld_mi(pm::AbstractSwitchModels) - -Multinetwork load shedding problem for Bus Injection models -""" -function build_mn_mc_osw_mld_mi(pm::AbstractSwitchModels) - for n in nw_ids(pm) - variable_mc_block_indicator(pm; nw=n, relax=false) - - PMD.variable_mc_bus_voltage_on_off(pm; nw=n) - - PMD.variable_mc_branch_power(pm; nw=n) - PMD.variable_mc_switch_power(pm; nw=n) - variable_mc_switch_state(pm; nw=n, relax=false) - variable_mc_switch_fixed(pm; nw=n) - PMD.variable_mc_transformer_power(pm; nw=n) - - PMD.variable_mc_oltc_transformer_tap(pm; nw=n) - - PMD.variable_mc_generator_power_on_off(pm; nw=n) - PMD.variable_mc_storage_power_mi_on_off(pm; nw=n, relax=false) - PMD.variable_mc_load_power(pm; nw=n) - - PMD.variable_mc_capcontrol(pm; nw=n, relax=false) - - PMD.constraint_mc_model_voltage(pm; nw=n) - - for i in ids(pm, n, :ref_buses) - PMD.constraint_mc_theta_ref(pm, i; nw=n) - end - - PMD.constraint_mc_bus_voltage_on_off(pm; nw=n) - - for i in ids(pm, n, :gen) - PMD.constraint_mc_gen_power_on_off(pm, i; nw=n) - end - - for i in ids(pm, n, :load) - PMD.constraint_mc_load_power(pm, i; nw=n) - end - - for i in ids(pm, n, :bus) - PMD.constraint_mc_power_balance_shed(pm, i; nw=n) - end - - for i in ids(pm, n, :storage) - constraint_storage_complementarity_mi_on_off(pm, i; nw=n) - PMD.constraint_mc_storage_on_off(pm, i; nw=n) - constraint_mc_storage_losses_on_off(pm, i; nw=n) - PMD.constraint_mc_storage_thermal_limit(pm, i; nw=n) - constraint_mc_storage_phase_unbalance(pm, i; nw=n) - end - - for i in ids(pm, n, :branch) - PMD.constraint_mc_ohms_yt_from(pm, i; nw=n) - PMD.constraint_mc_ohms_yt_to(pm, i; nw=n) - PMD.constraint_mc_voltage_angle_difference(pm, i; nw=n) - - PMD.constraint_mc_thermal_limit_from(pm, i; nw=n) - PMD.constraint_mc_thermal_limit_to(pm, i; nw=n) - - PMD.constraint_mc_ampacity_from(pm, i; nw=n) - PMD.constraint_mc_ampacity_to(pm, i; nw=n) - end - - !get(ref(pm, n), :disable_radial_constraint, false) && constraint_radial_topology(pm; nw=n, relax=false) - !get(ref(pm, n), :disable_isolation_constraint, false) && constraint_block_isolation(pm; nw=n, relax=true) - for i in ids(pm, n, :switch) - PMD.constraint_mc_switch_state_on_off(pm, i; nw=n, relax=true) - - PMD.constraint_mc_switch_thermal_limit(pm, i; nw=n) - PMD.constraint_mc_switch_ampacity(pm, i; nw=n) - end - - for i in ids(pm, n, :transformer) - constraint_mc_transformer_power_on_off(pm, i; nw=n, fix_taps=false) - end - end - - network_ids = sort(collect(nw_ids(pm))) - - n_1 = network_ids[1] - - for i in ids(pm, :storage; nw=n_1) - PMD.constraint_storage_state(pm, i; nw=n_1) - end - - constraint_switch_state_max_actions(pm; nw=n_1) - - for n_2 in network_ids[2:end] - constraint_switch_state_max_actions(pm, n_1, n_2) - - for i in ids(pm, :storage; nw=n_2) - PMD.constraint_storage_state(pm, i, n_1, n_2) - end - - n_1 = n_2 - end - - objective_mc_min_load_setpoint_delta_switch_global(pm) -end - - -""" - solve_mc_osw_mld_mi(data::Union{Dict{String,<:Any}, String}, model_type::Type, solver; - solution_processors::Vector{Function}=Function[], - eng2math_passthrough::Dict{String,Vector{String}}=Dict{String,Vector{String}}(), - ref_extensions::Vector{Function}=Function[], kwargs... - )::Dict{String,Any} - -Solves a multiconductor optimal switching (mixed-integer) problem using `model_type` and `solver` - -Calls back to PowerModelsDistribution.solve_mc_model, and therefore will accept any valid `kwargs` -for that function. See PowerModelsDistribution [documentation](https://lanl-ansi.github.io/PowerModelsDistribution.jl/latest) -for more details. -""" -function solve_mc_osw_mld_mi(data::Union{Dict{String,<:Any}, String}, model_type::Type, solver; - solution_processors::Vector{Function}=Function[], - eng2math_passthrough::Dict{String,Vector{String}}=Dict{String,Vector{String}}(), - ref_extensions::Vector{Function}=Function[], kwargs...)::Dict{String,Any} - return PMD.solve_mc_model( - data, - model_type, - solver, - build_mc_osw_mld_mi; - multinetwork=false, - solution_processors=Function[_solution_processors..., solution_processors...], - ref_extensions=Function[_ref_extensions..., ref_extensions...], - eng2math_passthrough=recursive_merge(_eng2math_passthrough, eng2math_passthrough), - kwargs...) -end - - -""" - build_mc_osw_mld_mi(pm::AbstractUBFSwitchModels) - -Single-network load shedding problem for Branch Flow model -""" -function build_mc_osw_mld_mi(pm::AbstractUBFSwitchModels) - variable_mc_block_indicator(pm; relax=false) - - PMD.variable_mc_bus_voltage_on_off(pm) - - PMD.variable_mc_branch_current(pm) - PMD.variable_mc_branch_power(pm) - PMD.variable_mc_switch_power(pm) - variable_mc_switch_state(pm; relax=false) - variable_mc_switch_fixed(pm) - PMD.variable_mc_transformer_power(pm) - - PMD.variable_mc_oltc_transformer_tap(pm) - - PMD.variable_mc_generator_power_on_off(pm) - PMD.variable_mc_storage_power_mi_on_off(pm; relax=false) - PMD.variable_mc_load_power(pm) - - PMD.variable_mc_capcontrol(pm, relax=false) - - PMD.constraint_mc_model_current(pm) - - for i in ids(pm, :ref_buses) - PMD.constraint_mc_theta_ref(pm, i) - end - - PMD.constraint_mc_bus_voltage_on_off(pm) - - for i in ids(pm, :gen) - PMD.constraint_mc_gen_power_on_off(pm, i) - end - - for i in ids(pm, :load) - PMD.constraint_mc_load_power(pm, i) - end - - for i in ids(pm, :bus) - PMD.constraint_mc_power_balance_shed(pm, i) - end - - for i in ids(pm, :storage) - PMD.constraint_storage_state(pm, i) - constraint_storage_complementarity_mi_on_off(pm, i) - PMD.constraint_mc_storage_on_off(pm, i) - constraint_mc_storage_losses_on_off(pm, i) - PMD.constraint_mc_storage_thermal_limit(pm, i) - constraint_mc_storage_phase_unbalance(pm, i) - end - - for i in ids(pm, :branch) - PMD.constraint_mc_power_losses(pm, i) - PMD.constraint_mc_model_voltage_magnitude_difference(pm, i) - PMD.constraint_mc_voltage_angle_difference(pm, i) - - PMD.constraint_mc_thermal_limit_from(pm, i) - PMD.constraint_mc_thermal_limit_to(pm, i) - - PMD.constraint_mc_ampacity_from(pm, i) - PMD.constraint_mc_ampacity_to(pm, i) - end - - constraint_switch_state_max_actions(pm) - !get(ref(pm), :disable_radial_constraint, false) && constraint_radial_topology(pm; relax=false) - !get(ref(pm), :disable_isolation_constraint, false) && constraint_block_isolation(pm; relax=true) - for i in ids(pm, :switch) - PMD.constraint_mc_switch_state_on_off(pm, i; relax=true) - - PMD.constraint_mc_switch_thermal_limit(pm, i) - PMD.constraint_mc_switch_ampacity(pm, i) - end - - for i in ids(pm, :transformer) - constraint_mc_transformer_power_on_off(pm, i; fix_taps=false) - end - - objective_mc_min_load_setpoint_delta_switch_iterative(pm) -end - - -""" - build_mc_osw_mld_mi(pm::AbstractSwitchModels) - -Single network load shedding problem for Bus Injection model -""" -function build_mc_osw_mld_mi(pm::AbstractSwitchModels) - for n in nw_ids(pm) - variable_mc_block_indicator(pm; relax=false) - - PMD.variable_mc_bus_voltage_on_off(pm) - - PMD.variable_mc_branch_power(pm) - PMD.variable_mc_switch_power(pm) - variable_mc_switch_state(pm; relax=false) - variable_mc_switch_fixed(pm) - PMD.variable_mc_transformer_power(pm) - - PMD.variable_mc_oltc_transformer_tap(pm) - - PMD.variable_mc_generator_power_on_off(pm) - PMD.variable_mc_storage_power_mi_on_off(pm; relax=false) - PMD.variable_mc_load_power(pm) - - PMD.variable_mc_capcontrol(pm; relax=false) - - PMD.constraint_mc_model_voltage(pm) - - for i in ids(pm, n, :ref_buses) - PMD.constraint_mc_theta_ref(pm, i) - end - - PMD.constraint_mc_bus_voltage_on_off(pm) - - for i in ids(pm, n, :gen) - PMD.constraint_mc_gen_power_on_off(pm, i) - end - - for i in ids(pm, n, :load) - PMD.constraint_mc_load_power(pm, i) - end - - for i in ids(pm, n, :bus) - PMD.constraint_mc_power_balance_shed(pm, i) - end - - for i in ids(pm, n, :storage) - PMD.constraint_storage_state(pm, i) - constraint_storage_complementarity_mi_on_off(pm, i) - PMD.constraint_mc_storage_on_off(pm, i) - constraint_mc_storage_losses_on_off(pm, i) - PMD.constraint_mc_storage_thermal_limit(pm, i) - constraint_mc_storage_phase_unbalance(pm, i) - end - - for i in ids(pm, n, :branch) - PMD.constraint_mc_ohms_yt_from(pm, i) - PMD.constraint_mc_ohms_yt_to(pm, i) - PMD.constraint_mc_voltage_angle_difference(pm, i) - - PMD.constraint_mc_thermal_limit_from(pm, i) - PMD.constraint_mc_thermal_limit_to(pm, i) - - PMD.constraint_mc_ampacity_from(pm, i) - PMD.constraint_mc_ampacity_to(pm, i) - end - - constraint_switch_state_max_actions(pm) - !get(ref(pm), :disable_radial_constraint, false) && constraint_radial_topology(pm; relax=false) - !get(ref(pm), :disable_isolation_constraint, false) && constraint_block_isolation(pm; relax=true) - for i in ids(pm, n, :switch) - PMD.constraint_mc_switch_state_on_off(pm, i; relax=true) - - PMD.constraint_mc_switch_thermal_limit(pm, i) - PMD.constraint_mc_switch_ampacity(pm, i) - end - - for i in ids(pm, n, :transformer) - constraint_mc_transformer_power_on_off(pm, i; fix_taps=false) - end - end - - objective_mc_min_load_setpoint_delta_switch_iterative(pm) -end diff --git a/src/prob/stability.jl b/src/prob/stability.jl index e229cffb..1d3942f7 100644 --- a/src/prob/stability.jl +++ b/src/prob/stability.jl @@ -1,5 +1,10 @@ """ - run_stability_analysis!(args::Dict{String,<:Any}; validate::Bool=true, formulation::Type=PMD.ACRUPowerModel, solver::String="nlp_solver")::Dict{String,Bool} + run_stability_analysis!( + args::Dict{String,<:Any}; + validate::Bool=true, + formulation::Type=PMD.ACRUPowerModel, + solver::String="nlp_solver" + )::Dict{String,Bool} Runs small signal stability analysis using PowerModelsStability and determines if each timestep configuration is stable, in-place, storing the results in `args["stability_results"]`, for use in [`entrypoint`](@ref entrypoint), Uses @@ -13,7 +18,7 @@ polar coordinates. `solver` (default: `"nlp_solver"`) specifies which solver in `args["solvers"]` to use for the stability analysis (NLP OPF) """ -function run_stability_analysis!(args::Dict{String,<:Any}; validate::Bool=true, formulation::Type=PMD.ACRUPowerModel, solver::String="nlp_solver")::Dict{String,Bool} +function run_stability_analysis!(args::Dict{String,<:Any}; validate::Bool=true)::Dict{String,Bool} if !isempty(get(args, "inverters", "")) if isa(args["inverters"], String) args["inverters"] = parse_inverters(args["inverters"]; validate=validate) @@ -26,12 +31,26 @@ function run_stability_analysis!(args::Dict{String,<:Any}; validate::Bool=true, ) end - args["stability_results"] = run_stability_analysis(args["network"], args["inverters"], args["solvers"][solver]; formulation=formulation, switching_solutions=get(args, "optimal_switching_results", missing), distributed=get(args, "nprcos", 1) > 1) + args["stability_results"] = run_stability_analysis( + args["network"], + args["inverters"], + args["solvers"][get_setting(args, ("options", "problem", "stability-solver"), "nlp_solver")]; + formulation=parse(AbstractUnbalancedPowerModel, get_setting(args, ("options", "problem", "stability-formulation"))), + switching_solutions=get(args, "optimal_switching_results", missing), + distributed=get_setting(args, ("options","problem","concurrent-stability-studies"), true) + ) end """ - run_stability_analysis(network, inverters::Dict{String,<:Any}, solver; formulation::Type=PMD.ACRUPowerModel, switching_solutions::Union{Missing,Dict{String,<:Any}}=missing, distributed::Bool=false)::Dict{String,Bool} + run_stability_analysis( + network::Dict{String,<:Any}, + inverters::Dict{String,<:Any}, + solver; + formulation::Type=PMD.ACRUPowerModel, + switching_solutions::Union{Missing,Dict{String,<:Any}}=missing, + distributed::Bool=false + )::Dict{String,Bool} Runs small signal stability analysis using PowerModelsStability and determines if each timestep configuration is stable @@ -43,7 +62,14 @@ polar coordinates. `solver` for stability analysis (NLP OPF) """ -function run_stability_analysis(network, inverters::Dict{String,<:Any}, solver; formulation::Type=PMD.ACRUPowerModel, switching_solutions::Union{Missing,Dict{String,<:Any}}=missing, distributed::Bool=false)::Dict{String,Bool} +function run_stability_analysis( + network::Dict{String,<:Any}, + inverters::Dict{String,<:Any}, + solver; + formulation::Type=PMD.ACRUPowerModel, + switching_solutions::Union{Missing,Dict{String,<:Any}}=missing, + distributed::Bool=false + )::Dict{String,Bool} mn_data = _prepare_stability_multinetwork_data(network, inverters, switching_solutions) ns = sort([parse(Int, i) for i in keys(mn_data["nw"])]) @@ -53,7 +79,7 @@ function run_stability_analysis(network, inverters::Dict{String,<:Any}, solver; push!(is_stable, run_stability_analysis(mn_data["nw"]["$n"], inverters["omega0"], inverters["rN"], solver; formulation=formulation)) end else - is_stable = @showprogress pmap(ns; distributed=distributed) do n + is_stable = pmap(ns; distributed=distributed) do n run_stability_analysis(mn_data["nw"]["$n"], inverters["omega0"], inverters["rN"], solver; formulation=formulation) end end @@ -63,15 +89,21 @@ end """ - run_stability_analysis(subnetwork::Dict{String,<:Any}, omega0::Real, rN::Int, solver; formulation::Type=PMD.ACPUPowerModel)::Bool + run_stability_analysis( + subnetwork::Dict{String,<:Any}, + omega0::Real, + rN::Int, + solver; + formulation::Type=PMD.ACPUPowerModel + )::Bool Runs stability analysis on a single subnetwork (not a multinetwork) using a nonlinear `solver`. """ function run_stability_analysis(subnetwork::Dict{String,<:Any}, omega0::Real, rN::Int, solver; formulation::Type=PMD.ACPUPowerModel)::Bool - math_model = PowerModelsStability.transform_data_model(subnetwork) - opf_solution = PowerModelsStability.solve_mc_opf(math_model, formulation, solver; solution_processors=[PMD.sol_data_model!]) + math_model = PMS.transform_data_model(subnetwork) + opf_solution = PMS.solve_mc_opf(math_model, formulation, solver; solution_processors=[PMD.sol_data_model!]) - Atot = PowerModelsStability.PMS.get_global_stability_matrix(math_model, opf_solution, omega0, rN) + Atot = PMS.get_global_stability_matrix(math_model, opf_solution, omega0, rN) eigValList = LinearAlgebra.eigvals(Atot) statusTemp = true for eig in eigValList @@ -84,8 +116,22 @@ function run_stability_analysis(subnetwork::Dict{String,<:Any}, omega0::Real, rN end -"helper function to prepare the multinetwork data for stability analysis (adds inverters, data_model)" -function _prepare_stability_multinetwork_data(network::Dict{String,<:Any}, inverters::Dict{String,<:Any}, switching_solutions::Union{Missing,Dict{String,<:Any}}=missing, dispatch_solution::Union{Missing,Dict{String,<:Any}}=missing)::Dict{String,Any} +""" + _prepare_stability_multinetwork_data( + network::Dict{String,<:Any}, + inverters::Dict{String,<:Any}, + switching_solutions::Union{Missing,Dict{String,<:Any}}=missing, + dispatch_solution::Union{Missing,Dict{String,<:Any}}=missing + )::Dict{String,Any} + +Helper function to prepare the multinetwork data for stability analysis (adds inverters, data_model). +""" +function _prepare_stability_multinetwork_data( + network::Dict{String,<:Any}, + inverters::Dict{String,<:Any}, + switching_solutions::Union{Missing,Dict{String,<:Any}}=missing, + dispatch_solution::Union{Missing,Dict{String,<:Any}}=missing + )::Dict{String,Any} mn_data = _prepare_dispatch_data(network, switching_solutions) for (n, nw) in mn_data["nw"] @@ -97,7 +143,7 @@ function _prepare_stability_multinetwork_data(network::Dict{String,<:Any}, inver push!(_inverters, _inv) end end - PowerModelsStability.add_inverters!(nw, merge(filter(x->x.first!="inverters", inverters), Dict{String,Any}("inverters" => _inverters))) + PMS.add_inverters!(nw, merge(filter(x->x.first!="inverters", inverters), Dict{String,Any}("inverters" => _inverters))) end return mn_data diff --git a/src/prob/switch.jl b/src/prob/switch.jl index ab4e8654..c8ed2523 100644 --- a/src/prob/switch.jl +++ b/src/prob/switch.jl @@ -7,23 +7,42 @@ using [`optimize_switches`] Uses LPUBFDiagPowerModel (LinDist3Flow), and therefore requires `args["solvers"]["misocp_solver"]` to be specified """ function optimize_switches!(args::Dict{String,<:Any})::Dict{String,Any} - args["optimal_switching_results"] = optimize_switches(args["network"], args["solvers"][get(args, "opt-switch-solver", "misocp_solver")]; formulation=_get_switch_formulation(get(args, "opt-switch-formulation", "lindistflow")), algorithm=get(args, "opt-switch-algorithm", "global")) + args["optimal_switching_results"] = optimize_switches( + args["network"], + args["solvers"][get_setting(args, ("options", "problem", "operations-solver"), "mip_solver")]; + formulation=parse(AbstractUnbalancedPowerModel, (get_setting(args, ("options","problem","operations-formulation"), "LPUBFDiagPowerModel"))), + algorithm=get_setting(args, ("options", "problem", "operations-algorithm"), "full-lookahead"), + problem=get_setting(args, ("options", "problem", "operations-problem-type"), "block") + ) end """ - optimize_switches(network::Dict{String,<:Any}, solver; formulation::Type=LPUBFSwitchPowerModel, algorithm::String="global")::Dict{String,Any} - -Iterates over all subnetworks in a multinetwork data structure `network`, in order, and solves -the optimal switching / MLD problem sequentially, updating the next timestep with the new switch -configurations and storage energies from the solved timestep. + optimize_switches( + network::Dict{String,<:Any}, + solver; + formulation::Type=PMD.LPUBFDiagPowerModel, + algorithm::String="full-lookahead" + )::Dict{String,Any} + +- `algorithm::String`, if `"rolling-horizon"`, iterates over all subnetworks in a multinetwork data structure `network`, in order, + and solves the optimal switching / MLD problem sequentially, updating the next timestep with the new switch configurations + and storage energies from the solved timestep. Otherwise, if `"full-lookahead"`, will solve all time steps in a single optimization + problem (default: `"full-lookahead"`) """ -function optimize_switches(network::Dict{String,<:Any}, solver; formulation::Type=LPUBFSwitchPowerModel, algorithm::String="global")::Dict{String,Any} +function optimize_switches( + network::Dict{String,<:Any}, + solver; + formulation::Type=PMD.LPUBFDiagPowerModel, + algorithm::String="full-lookahead", + problem::String="block" + )::Dict{String,Any} results = Dict{String,Any}() - @info "running $(algorithm) optimal switching algorithm with $(formulation)" - if algorithm == "global" - _results = solve_mn_mc_osw_mld_mi( + @info "running $(algorithm)-$(problem) optimal switching algorithm with $(formulation)" + if algorithm == "full-lookahead" + prob = problem=="traditional" ? solve_mn_traditional_mld : solve_mn_block_mld + _results = prob( network, formulation, solver @@ -31,17 +50,17 @@ function optimize_switches(network::Dict{String,<:Any}, solver; formulation::Typ opt_results = filter(x->x.first!="solution", _results) results = Dict{String,Any}(n => merge(Dict{String,Any}("solution"=>nw), opt_results) for (n,nw) in get(get(_results, "solution", Dict{String,Any}()), "nw", Dict{String,Any}())) - elseif algorithm == "iterative" + elseif algorithm == "rolling-horizon" mn_data = _prepare_optimal_switching_data(network) ns = sort([parse(Int, i) for i in keys(mn_data["nw"])]) - @showprogress length(ns) "Running switch optimization (mld)... " for n in ns + for n in ns if haskey(results, "$(n-1)") && haskey(results["$(n-1)"], "solution") _update_switch_settings!(mn_data["nw"]["$n"], results["$(n-1)"]["solution"]) _update_storage_capacity!(mn_data["nw"]["$n"], results["$(n-1)"]["solution"]) end - results["$n"] = optimize_switches(mn_data["nw"]["$n"], solve_mc_osw_mld_mi, solver; formulation=formulation) + results["$n"] = optimize_switches(mn_data["nw"]["$n"], problem=="traditional" ? solve_traditional_mld : solve_block_mld, solver; formulation=formulation) end else @warn "'algorithm=$(algorithm)' not recognized, skipping switch optimization" @@ -52,13 +71,18 @@ end """ - optimize_switches(subnetwork::Dict{String,<:Any}, prob::Function, solver; formulation=LPUBFSwitchPowerModel)::Dict{String,Any} + optimize_switches( + subnetwork::Dict{String,<:Any}, + prob::Function, + solver; + formulation=PMD.LPUBFDiagPowerModel + )::Dict{String,Any} Optimizes switch states for load shedding on a single subnetwork (not a multinetwork), using `prob` Optionally, a PowerModelsDistribution `formulation` can be set independently, but is LinDist3Flow by default. """ -function optimize_switches(subnetwork::Dict{String,<:Any}, prob::Function, solver; formulation=LPUBFSwitchPowerModel)::Dict{String,Any} +function optimize_switches(subnetwork::Dict{String,<:Any}, prob::Function, solver; formulation=PMD.LPUBFDiagPowerModel)::Dict{String,Any} prob( subnetwork, formulation, @@ -67,7 +91,11 @@ function optimize_switches(subnetwork::Dict{String,<:Any}, prob::Function, solve end -"helper function to prepare optimal switching data structure" +""" + _prepare_optimal_switching_data(network::Dict{String,<:Any})::Dict{String,Any} + +Helper function to prepare optimal switching data structure. +""" function _prepare_optimal_switching_data(network::Dict{String,<:Any})::Dict{String,Any} mn_data = deepcopy(network) for (n,nw) in mn_data["nw"] diff --git a/src/stats/actions.jl b/src/stats/actions.jl index f5c46ecb..d730566c 100644 --- a/src/stats/actions.jl +++ b/src/stats/actions.jl @@ -10,7 +10,18 @@ end """ - get_timestep_device_actions(network::Dict{String,<:Any}, optimal_switching_results::Dict{String,<:Any})::Vector{Dict{String,Any}} + get_timestep_device_actions(::String, ::Dict{String,<:Any})::Vector{Dict{String,Any}} + +Helper function for the variant where `args["network"]` hasn't been parsed yet. +""" +get_timestep_device_actions(::String, ::Dict{String,<:Any})::Vector{Dict{String,Any}} = Dict{String,Any}[] + + +""" + get_timestep_device_actions( + network::Dict{String,<:Any}, + optimal_switching_results::Dict{String,<:Any} + )::Vector{Dict{String,Any}} From the multinetwork `network`, determines the switch configuration at each timestep. If the switch does not exist in `mld_results`, the state will default back to the state given in the original network. This could happen if the switch @@ -35,7 +46,7 @@ function get_timestep_device_actions(network::Dict{String,<:Any}, optimal_switch for (id, load) in get(network["nw"]["$n"], "load", Dict()) load_solution = get(get(get(get(optimal_switching_results, "$n", Dict()), "solution", Dict()), "load", Dict()), id, Dict()) - if round(Int, get(load_solution, "status", 1)) != 1 + if get(load_solution, "status", PMD.ENABLED) != PMD.ENABLED push!(_out["Shedded loads"], id) end end @@ -59,10 +70,22 @@ end """ - get_timestep_switch_changes(network::Dict{String,<:Any}, optimal_switching_results::Dict{String,<:Any}=Dict{String,Any}())::Vector{Vector{String}} + get_timestep_switch_changes(::String, ::Dict{String,<:Any})::Vector{Vector{String}} -Gets a list of switches whose state has changed between timesteps (always expect the first timestep to be an empty list). -This expects the solutions from the MLD problem to have been merged into `network` +Helper function for the variant where `args["network"]` hasn't been parsed yet. +""" +get_timestep_switch_changes(::String, optimal_switching_results::Dict{String,<:Any}=Dict{String,Any}())::Vector{Vector{String}} = String[] + + +""" + get_timestep_switch_changes( + network::Dict{String,<:Any}, + optimal_switching_results::Dict{String,<:Any}=Dict{String,Any}() + )::Vector{Vector{String}} + +Gets a list of switches whose state has changed between timesteps (always expect the +first timestep to be an empty list). This expects the solutions from the MLD problem to +have been merged into `network` """ function get_timestep_switch_changes(network::Dict{String,<:Any}, optimal_switching_results::Dict{String,<:Any}=Dict{String,Any}())::Vector{Vector{String}} switch_changes = Vector{String}[] @@ -90,26 +113,32 @@ end """ - get_timestep_switch_optimization_metadata!(args::Dict{String,<:Any})::Vector{Dict{String,Any}} + get_timestep_switch_optimization_metadata!( + args::Dict{String,<:Any} + )::Vector{Dict{String,Any}} -Retrieves the switching optimization results metadata from the optimal switching solution via [`get_timestep_switch_optimization_metadata`](@ref get_timestep_switch_optimization_metadata) +Retrieves the switching optimization results metadata from the optimal switching solution via +[`get_timestep_switch_optimization_metadata`](@ref get_timestep_switch_optimization_metadata) and applies it in-place to args, for use with [`entrypoint`](@ref entrypoint) """ function get_timestep_switch_optimization_metadata!(args::Dict{String,<:Any})::Vector{Dict{String,Any}} - args["output_data"]["Optimal switching metadata"] = get_timestep_switch_optimization_metadata(get(args, "optimal_switching_results", Dict{String,Any}()); opt_switch_algorithm=get(args, "opt-switch-algorithm", "global")) + args["output_data"]["Optimal switching metadata"] = get_timestep_switch_optimization_metadata(get(args, "optimal_switching_results", Dict{String,Any}()); opt_switch_algorithm=get(args, "opt-switch-algorithm", "full-lookahead")) end """ - get_timestep_switch_optimization_metadata(optimal_switching_results::Dict{String,Any}; opt_switch_algorithm::String="iterative")::Vector{Dict{String,Any}} + get_timestep_switch_optimization_metadata( + optimal_switching_results::Dict{String,Any}; + opt_switch_algorithm::String="full-lookahead" + )::Vector{Dict{String,Any}} -Gets the metadata from the optimal switching results for each timestep, returning a list of Dicts (if opt_switch_algorithm="iterative), or a list with a single -Dict (if opt_switch_algorithm="global"). +Gets the metadata from the optimal switching results for each timestep, returning a list of `Dicts` +(if `opt_switch_algorithm="iterative`), or a list with a single `Dict` (if `opt_switch_algorithm="full-lookahead"`). """ -function get_timestep_switch_optimization_metadata(optimal_switching_results::Dict{String,Any}; opt_switch_algorithm::String="iterative")::Vector{Dict{String,Any}} +function get_timestep_switch_optimization_metadata(optimal_switching_results::Dict{String,Any}; opt_switch_algorithm::String="full-lookahead")::Vector{Dict{String,Any}} results_metadata = Dict{String,Any}[] - if opt_switch_algorithm == "global" && !isempty(optimal_switching_results) + if opt_switch_algorithm == "full-lookahead" && !isempty(optimal_switching_results) push!(results_metadata, filter(x->x.first!="solution", first(optimal_switching_results).second)) else ns = sort([parse(Int, n) for n in keys(optimal_switching_results)]) diff --git a/src/stats/analysis.jl b/src/stats/analysis.jl index eea45d6c..c9083201 100644 --- a/src/stats/analysis.jl +++ b/src/stats/analysis.jl @@ -1,19 +1,19 @@ """ - get_timestep_microgrid_networks(output::String, network::Dict{String,<:Any})::Vector{Vector{Vector{String}}} + get_timestep_microgrid_networks_from_output_file(output::String, network::Dict{String,<:Any})::Vector{Vector{Vector{String}}} Analytics for determining when microgrids network from output file """ -function get_timestep_microgrid_networks(output::String, network::Dict{String,<:Any})::Vector{Vector{Vector{String}}} - output = JSON.parsefile(output) +function get_timestep_microgrid_networks_from_output_file(output_file::String, network::Dict{String,<:Any})::Vector{Vector{Vector{String}}} + output = JSON.parsefile(output_file) actions = get(output, "Device action timestep", []) - switch_config = Dict{String,PowerModelsDistribution.SwitchState}[] + switch_config = Dict{String,PMD.SwitchState}[] for timestep in actions - _switch_config = Dict{String,PowerModelsDistribution.SwitchState}() + _switch_config = Dict{String,PMD.SwitchState}() for (id, state) in get(timestep, "Switch configurations", Dict()) - _switch_config[id] = Dict("closed"=>PowerModelsDistribution.CLOSED, "open"=>PowerModelsDistribution.OPEN)[lowercase(state)] + _switch_config[id] = Dict("closed"=>PMD.CLOSED, "open"=>PMD.OPEN)[lowercase(state)] end push!(switch_config, _switch_config) end @@ -27,8 +27,10 @@ end Collects microgrid networks per timestep and assigns them to 'Device action timestep'/'Microgrid networks' """ -function get_timestep_microgrid_networks!(args::Dict{String,<:Any})::Vector{Dict{String,Any}} - args["output_data"]["Device action timeline"] = recursive_merge_timesteps(args["output_data"]["Device action timeline"], [Dict{String,Vector{Vector{String}}}("Microgrid networks" => _mg_networks) for _mg_networks in get_timestep_microgrid_networks(get(args, "network", Dict{String,Any}()), get(args, "optimal_switching_results", Dict{String,Any}()))]) +function get_timestep_microgrid_networks!(args::Dict{String,<:Any})::Union{Vector{Dict{String,Any}},Nothing} + if isa(get(args, "network", ""), Dict) + args["output_data"]["Device action timeline"] = recursive_merge_timesteps(args["output_data"]["Device action timeline"], [Dict{String,Vector{Vector{String}}}("Microgrid networks" => _mg_networks) for _mg_networks in get_timestep_microgrid_networks(get(args, "network", Dict{String,Any}()), get(args, "optimal_switching_results", Dict{String,Any}()))]) + end end @@ -45,7 +47,7 @@ function get_timestep_microgrid_networks(network::Dict{String,Any}, switching_re nw = mn_data["nw"]["$n"] nw["data_model"] = mn_data["data_model"] - sr = get(switching_results, "$n", Dict{String,Any}()) + sr = get(get(switching_results, "$n", Dict{String,Any}()), "solution", Dict{String,Any}()) switch_config = Dict{String,PMD.SwitchState}(s => get(sw, "state", nw["switch"][s]["state"]) for (s,sw) in get(sr, "switch", Dict{String,Any}())) push!(microgrid_networks, get_microgrid_networks(nw; switch_config=switch_config)) diff --git a/src/stats/dispatch.jl b/src/stats/dispatch.jl index 5fbea5f9..e3d929af 100644 --- a/src/stats/dispatch.jl +++ b/src/stats/dispatch.jl @@ -1,5 +1,7 @@ """ - get_timestep_voltage_statistics!(args::Dict{String,<:Any})::Dict{String,Vector{Real}} + get_timestep_voltage_statistics!( + args::Dict{String,<:Any} + )::Dict{String,Vector{Real}} Gets voltage statistics min, mean, max for each timestep in-place in args, for use in [`entrypoint`][@ref entrypoint], using [`get_timestep_voltage_statistics`](@ref get_timestep_voltage_statistics) @@ -10,14 +12,31 @@ end """ - get_voltage_min_mean_max(solution::Dict{String,<:Any}, data::Dict{String,<:Any}; make_per_unit::Bool=true)::Tuple{Real,Real,Real} + get_timestep_voltage_statistics(::Dict{String,<:Any}, ::String) + +Helper function for the variant where `args["network"]` hasn't been parsed yet. +""" +get_timestep_voltage_statistics(::Dict{String,<:Any}, ::String) = Dict{String,Vector{Real}}( + "Min voltage (p.u.)" => Real[], + "Mean voltage (p.u.)" => Real[], + "Max voltage (p.u.)" => Real[], +) + + +""" + get_voltage_min_mean_max( + solution::Dict{String,<:Any}, + data::Dict{String,<:Any}; + make_per_unit::Bool=true + )::Tuple{Real,Real,Real} Calculates the minimum, mean, and maximum of the voltages across a network (not a multinetwork) `data` is used to convert the units to per_unit if `make_per_unit` and the data is not already per_unit. -If `make_per_unit` (default: true), will return voltage statistics in per-unit representation. If `make_per_unit` is false, and there are different -voltage bases across the network, the statistics will not make sense. +If `make_per_unit` (default: true), will return voltage statistics in per-unit representation. +If `make_per_unit` is false, and there are different voltage bases across the network, the statistics will +not make sense. """ function get_voltage_min_mean_max(solution::Dict{String,<:Any}, data::Dict{String,<:Any}; make_per_unit::Bool=true)::Tuple{Real,Real,Real} if make_per_unit @@ -32,12 +51,18 @@ end """ - get_timestep_voltage_statistics(solution::Dict{String,<:Any}, network::Dict{String,<:Any}; make_per_unit::Bool=true)::Dict{String,Vector{Real}} - -Returns statistics on the Minimum, Mean, and Maximum voltages for each timestep using [`get_voltage_min_mean_max`](@ref get_voltage_min_mean_max) - -If `make_per_unit` (default: true), will return voltage statistics in per-unit representation. If `make_per_unit` is false, and there are different -voltage bases across the network, the statistics will not make sense. + get_timestep_voltage_statistics( + solution::Dict{String,<:Any}, + network::Dict{String,<:Any}; + make_per_unit::Bool=true + )::Dict{String,Vector{Real}} + +Returns statistics on the Minimum, Mean, and Maximum voltages for each timestep using +[`get_voltage_min_mean_max`](@ref get_voltage_min_mean_max) + +If `make_per_unit` (default: true), will return voltage statistics in per-unit representation. +If `make_per_unit` is false, and there are different voltage bases across the network, the +statistics will not make sense. """ function get_timestep_voltage_statistics(solution::Dict{String,<:Any}, network::Dict{String,<:Any}; make_per_unit::Bool=true)::Dict{String,Vector{Real}} voltages = Dict{String,Vector{Real}}( @@ -61,7 +86,9 @@ end """ - get_timestep_dispatch!(args::Dict{String,<:Any})::Vector{Dict{String,Any}} + get_timestep_dispatch!( + args::Dict{String,<:Any} + )::Vector{Dict{String,Any}} Gets the optimal dispatch results in-place in args, for use in [`entrypoint`](@ref entrypoint), using [`get_timestep_dispatch`](@ref get_timestep_dispatch). @@ -72,10 +99,21 @@ end """ - get_timestep_dispatch(solution::Dict{String,<:Any}, network::Dict{String,<:Any})::Vector{Dict{String,Any}} + get_timestep_dispatch(::Dict{String,<:Any}, ::String)::Vector{Dict{String,Any}} + +Helper function for the variant where `args["network"]` hasn't been parsed yet. +""" +get_timestep_dispatch(::Dict{String,<:Any}, ::String)::Vector{Dict{String,Any}} = Dict{String,Any}[] + + +""" + get_timestep_dispatch( + solution::Dict{String,<:Any}, + network::Dict{String,<:Any} + )::Vector{Dict{String,Any}} -Returns the dispatch information for generation assets (generator, storage, solar, voltage_source) and bus voltage magnitudes in SI units for each timestep -from the optimal dispatch `solution` +Returns the dispatch information for generation assets (generator, storage, solar, voltage_source) and +bus voltage magnitudes in SI units for each timestep from the optimal dispatch `solution` """ function get_timestep_dispatch(solution::Dict{String,<:Any}, network::Dict{String,<:Any})::Vector{Dict{String,Any}} dispatch = Dict{String,Any}[] @@ -143,9 +181,66 @@ end """ - get_timestep_dispatch_optimization_metadata!(args::Dict{String,<:Any})::Dict{String,Any} + get_timestep_inverter_states!(args::Dict{String,<:Any})::Vector{Dict{String,Any}} + +Adds field "inverter" to power flow output for inverter objects, i.e., storage +generator, voltage_source, solar. See [`get_timestep_inverter_states`](@ref get_timestep_inverter_states) +""" +function get_timestep_inverter_states!(args::Dict{String,<:Any})::Vector{Dict{String,Any}} + states = get_timestep_inverter_states(get(args, "optimal_switching_results", Dict{String,Any}())) + + _powerflow_outputs = Dict{String,Any}[] + powerflow_outputs = get(args["output_data"], "Powerflow output", Dict{String,Any}()) + if !isempty(powerflow_outputs) && !isempty(states) && length(powerflow_outputs) == length(states) + for (pfo, state) in zip(powerflow_outputs, states) + push!(_powerflow_outputs, recursive_merge_including_vectors(pfo, state)) + end + elseif !isempty(powerflow_outputs) && !isempty(states) && length(powerflow_outputs) != length(states) + @error "size of inverter states timeline and powerflow timeline are different" + else + _powerflow_outputs = states + end + + args["output_data"]["Powerflow output"] = _powerflow_outputs + + return states +end + + +""" + get_timestep_inverter_states(optimal_switching_results::Dict{String,<:Any})::Vector{Dict{String,Any}} + +Gets 'inverter' state for each generation object at each timestep from `optimal_switching_results`. Defaults +to `GRID_FORMING` if no inverter state is available. +""" +function get_timestep_inverter_states(optimal_switching_results::Dict{String,<:Any})::Vector{Dict{String,Any}} + timesteps = Dict{String,Any}[] + for n in sort(parse.(Int, collect(keys(optimal_switching_results)))) + sol = get(optimal_switching_results["$n"], "solution", Dict()) + timestep = Dict{String,Any}() + for t in ["generator", "solar", "storage", "voltage_source"] + if haskey(sol, t) && !isempty(sol[t]) + timestep[t] = Dict{String,Any}() + for (i,obj) in sol[t] + timestep[t][i] = Dict{String,Any}("inverter"=>string(get(obj, "inverter", GRID_FORMING))) + end + end + end + + push!(timesteps, timestep) + end + + return timesteps +end + + +""" + get_timestep_dispatch_optimization_metadata!( + args::Dict{String,<:Any} + )::Dict{String,Any} -Retrieves the switching optimization results metadata from the optimal switching solution via [`get_timestep_dispatch_optimization_metadata`](@ref get_timestep_dispatch_optimization_metadata) +Retrieves the switching optimization results metadata from the optimal switching solution via +[`get_timestep_dispatch_optimization_metadata`](@ref get_timestep_dispatch_optimization_metadata) and applies it in-place to args, for use with [`entrypoint`](@ref entrypoint) """ function get_timestep_dispatch_optimization_metadata!(args::Dict{String,<:Any})::Dict{String,Any} @@ -154,10 +249,12 @@ end """ - get_timestep_dispatch_optimization_metadata(optimal_dispatch_result::Dict{String,Any})::Dict{String,Any} + get_timestep_dispatch_optimization_metadata( + optimal_dispatch_result::Dict{String,Any} + )::Dict{String,Any} -Gets the metadata from the optimal switching results for each timestep, returning a list of Dicts (if opt_switch_algorithm="iterative), or a list with a single -Dict (if opt_switch_algorithm="global"). +Gets the metadata from the optimal switching results for each timestep, returning a list of Dicts +(if `opt_switch_algorithm="rolling-horizon"`), or a list with a single Dict (if `opt_switch_algorithm="full-lookahead"`). """ function get_timestep_dispatch_optimization_metadata(optimal_dispatch_result::Dict{String,Any})::Dict{String,Any} return _sanitize_results_metadata!(filter(x->x.first!="solution", optimal_dispatch_result)) diff --git a/src/stats/fault.jl b/src/stats/fault.jl index 855d32c3..24c49a1a 100644 --- a/src/stats/fault.jl +++ b/src/stats/fault.jl @@ -1,16 +1,23 @@ """ - get_timestep_fault_currents!(args::Dict{String,<:Any})::Vector{Dict{String,Any}} + get_timestep_fault_currents!( + args::Dict{String,<:Any} + )::Vector{Dict{String,Any}} Gets fault currents for switches and corresponding fault from study in-place in args, for use in [`entrypoint`](@ref entrypoint), using [`get_timestep_fault_currents`](@ref get_timestep_fault_currents). """ function get_timestep_fault_currents!(args::Dict{String,<:Any})::Vector{Dict{String,Any}} - args["output_data"]["Fault currents"] = get_timestep_fault_currents(get(args, "fault_studies_results", Dict{String,Any}()), get(args, "faults", Dict{String,Any}()), args["network"]) + args["output_data"]["Fault currents"] = get_timestep_fault_currents(get(args, "fault_studies_results", Dict{String,Any}()), get(args, "faults", Dict{String,Any}()), args["network"]; ret_protection_only=!(get(args, "debug", false) || get(args, "verbose", false))) end """ - get_timestep_fault_currents(fault_studies_results::Dict{String,<:Any}, faults::Dict{String,<:Any}, network::Dict{String,<:Any})::Vector{Dict{String,Any}} + get_timestep_fault_currents( + fault_studies_results::Dict{String,<:Any}, + faults::Dict{String,<:Any}, + network::Dict{String,<:Any}; + ret_protection_only::Bool=false + )::Vector{Dict{String,Any}} Gets information about the results of fault studies at each timestep, including: @@ -25,8 +32,11 @@ Gets information about the results of fault studies at each timestep, including: - the positive-sequence fault current `|I1| (A)` - the negative-sequence fault current `|I2| (A)` - the bus voltage from the from-side of the switch `|V| (V)` + +`ret_protection_only==false` indicates that currents and voltages should be returned for all lines where switch=y, and if +`true`, should only return switches for which a protection device is defined (recloser, relay, fuse) """ -function get_timestep_fault_currents(fault_studies_results::Dict{String,<:Any}, faults::Dict{String,<:Any}, network::Dict{String,<:Any})::Vector{Dict{String,Any}} +function get_timestep_fault_currents(fault_studies_results::Dict{String,<:Any}, faults::Dict{String,<:Any}, network::Dict{String,<:Any}; ret_protection_only::Bool=true)::Vector{Dict{String,Any}} fault_currents = Dict{String,Any}[] for n in sort([parse(Int, i) for i in keys(fault_studies_results)]) @@ -61,6 +71,17 @@ function get_timestep_fault_currents(fault_studies_results::Dict{String,<:Any}, ) for (id, switch) in get(network["nw"]["$n"], "switch", Dict()) ), ) + + if ret_protection_only + protection_locations = Set() + for pt in _pnm2eng_objects["protection"] + for (i,pd) in get(network["nw"]["$n"], pt, Dict()) + haskey(pd, "location") && !isempty(pd["location"]) && push!(protection_locations, string(replace(pd["location"], "line."=>""))) + end + end + + _fault_currents[bus_id][fault_type][fault_id]["switch"] = filter(x->x.first∈protection_locations, _fault_currents[bus_id][fault_type][fault_id]["switch"]) + end end end end @@ -70,15 +91,33 @@ function get_timestep_fault_currents(fault_studies_results::Dict{String,<:Any}, return fault_currents end -"Special case where the faults string was not parsed" -get_timestep_fault_currents(fault_studies_results::Dict{String,<:Any}, faults::String, network::Dict{String,<:Any})::Vector{Dict{String,Any}} = get_timestep_fault_currents(fault_studies_results, Dict{String,Any}(), network) +""" + get_timestep_fault_currents( + fault_studies_results::Dict{String,<:Any}, + faults::String, + network::Dict{String,<:Any} + )::Vector{Dict{String,Any}} +Special case where the faults string was not parsed +""" +get_timestep_fault_currents(fault_studies_results::Dict{String,<:Any}, faults::String, network::Dict{String,<:Any}; ret_protection_only::Bool=false)::Vector{Dict{String,Any}} = get_timestep_fault_currents(fault_studies_results, Dict{String,Any}(), network; ret_protection_only=ret_protection_only) + + +""" + get_timestep_fault_currents(::Dict{String,<:Any}, ::String, ::String; ret_protection_only::Bool=false)::Vector{Dict{String,Any}} + +Helper function for the variant where `args["network"]` hasn't been parsed yet. +""" +get_timestep_fault_currents(::Dict{String,<:Any}, ::String, ::String; ret_protection_only::Bool=false)::Vector{Dict{String,Any}} = Dict{String,Any}[] """ - get_timestep_fault_study_metadata!(args::Dict{String,<:Any})::Vector{Dict{String,Any}} + get_timestep_fault_study_metadata!( + args::Dict{String,<:Any} + )::Vector{Dict{String,Any}} -Retrieves the switching optimization results metadata from the optimal switching solution via [`get_timestep_fault_study_metadata`](@ref get_timestep_fault_study_metadata) +Retrieves the switching optimization results metadata from the optimal switching solution via +[`get_timestep_fault_study_metadata`](@ref get_timestep_fault_study_metadata) and applies it in-place to args, for use with [`entrypoint`](@ref entrypoint) """ function get_timestep_fault_study_metadata!(args::Dict{String,<:Any})::Vector{Dict{String,Any}} @@ -87,10 +126,12 @@ end """ - get_timestep_fault_study_metadata(fault_studies_results::Dict{String,Any})::Vector{Dict{String,Any}} + get_timestep_fault_study_metadata( + fault_studies_results::Dict{String,Any} + )::Vector{Dict{String,Any}} -Gets the metadata from the optimal switching results for each timestep, returning a list of Dicts (if opt_switch_algorithm="iterative), or a list with a single -Dict (if opt_switch_algorithm="global"). +Gets the metadata from the optimal switching results for each timestep, returning a list of Dicts +(if `opt_switch_algorithm="rolling-horizon"`), or a list with a single Dict (if `opt_switch_algorithm="full-lookahead"`). """ function get_timestep_fault_study_metadata(fault_studies_results::Dict{String,Any})::Vector{Dict{String,Any}} results_metadata = Dict{String,Any}[] diff --git a/src/stats/microgrid.jl b/src/stats/microgrid.jl index 664bda53..7db37888 100644 --- a/src/stats/microgrid.jl +++ b/src/stats/microgrid.jl @@ -1,5 +1,7 @@ """ - get_timestep_load_served!(args::Dict{String,<:Any})::Dict{String,Vector{Real}} + get_timestep_load_served!( + args::Dict{String,<:Any} + )::Dict{String,Vector{Real}} Gets Load served statistics in-place in args, for use in [`entrypoint`](@ref entrypoint), using [`get_timestep_load_served`](@ref get_timestep_load_served). @@ -10,10 +12,30 @@ end """ - get_timestep_load_served(solution::Dict{String,<:Any}, network::Dict{String,<:Any})::Dict{String,Vector{Real}} + get_timestep_load_served(::Dict{String,<:Any}, network::String, switching_solution::Union{Missing,Dict{String,<:Any}}=missing) -Returns Load statistics from an optimal dispatch `solution`, and compares to the base load (non-shedded) in `network`, -giving statistics for +Helper function for the variant where `args["network"]` hasn't been parsed yet. +""" +get_timestep_load_served(::Dict{String,<:Any}, network::String, switching_solution::Union{Missing,Dict{String,<:Any}}=missing) = Dict{String,Vector{Real}}( + "Feeder load (%)" => Real[], + "Microgrid load (%)" => Real[], + "Bonus load via microgrid (%)" => Real[], + "Total load (%)" => Real[], + "Feeder customers (%)" => Real[], + "Microgrid customers (%)" => Real[], + "Bonus customers via microgrid (%)" => Real[], + "Total customers (%)" => Real[], +) + + +""" + get_timestep_load_served( + solution::Dict{String,<:Any}, + network::Dict{String,<:Any} + )::Dict{String,Vector{Real}} + +Returns Load statistics from an optimal dispatch `solution`, and compares to the +base load (non-shedded) in `network`, giving statistics for - `"Feeder load (%)"`: How much load is the feeder supporting, - `"Microgrid load (%)"`: How much load is(are) the microgrid(s) supporting, @@ -100,7 +122,7 @@ function get_timestep_load_served(dispatch_solution::Dict{String,<:Any}, network total_load_served = 0.0 total_cust_served = 0 - mg_ncustomers = sum(Int64[length(mg_loads) for (mg,mg_loads) in microgrid_loads]) + mg_ncustomers = sum(Int[length(mg_loads) for (mg,mg_loads) in microgrid_loads]) total_mg_load = sum(Float64[sum(abs.(load["pd_nom"])) for (id,load) in get(network["nw"]["$n"], "load", Dict()) if id ∈ keys(load2mg) && load["status"] == PMD.ENABLED]) mg_bonus_ncustomers = length(get(network["nw"]["$n"], "load", Dict())) - mg_ncustomers @@ -174,7 +196,9 @@ end """ - get_timestep_generator_profiles!(args::Dict{String,<:Any})::Dict{String,Vector{Real}} + get_timestep_generator_profiles!( + args::Dict{String,<:Any} + )::Dict{String,Vector{Real}} Gets generator profile statistics for each timestep in-place in args, for use in [`entrypoint`](@ref entrypoint), using [`get_timestep_generator_profiles`](@ref get_timestep_generator_profiles) @@ -185,7 +209,9 @@ end """ - get_timestep_generator_profiles(solution::Dict{String,<:Any})::Dict{String,Vector{Real}} + get_timestep_generator_profiles( + solution::Dict{String,<:Any} + )::Dict{String,Vector{Real}} Returns statistics about the generator profiles from the optimal dispatch `solution`: @@ -214,10 +240,13 @@ end """ - get_timestep_storage_soc!(args::Dict{String,<:Any})::Vector{Real} + get_timestep_storage_soc!( + args::Dict{String,<:Any} + )::Vector{Real} -Gets storage energy remaining percentage for each timestep in-place in args, for use in [`entrypoint`](@ref entrypoint), -using [`get_timestep_storage_soc`](@ref get_timestep_storage_soc) +Gets storage energy remaining percentage for each timestep in-place in args, +for use in [`entrypoint`](@ref entrypoint), using +[`get_timestep_storage_soc`](@ref get_timestep_storage_soc) """ function get_timestep_storage_soc!(args::Dict{String,<:Any})::Vector{Real} args["output_data"]["Storage SOC (%)"] = get_timestep_storage_soc(get(get(args, "optimal_dispatch_result", Dict{String,Any}()), "solution", Dict{String,Any}()), args["network"]) @@ -225,7 +254,18 @@ end """ - get_timestep_storage_soc(solution::Dict{String,<:Any}, network::Dict{String,<:Any})::Vector{Real} + get_timestep_storage_soc(::Dict{String,<:Any}, ::String) + +Helper function for the variant where `args["network"]` hasn't been parsed yet. +""" +get_timestep_storage_soc(::Dict{String,<:Any}, ::String) = Real[] + + +""" + get_timestep_storage_soc( + solution::Dict{String,<:Any}, + network::Dict{String,<:Any} + )::Vector{Real} Returns the storage state of charge, i.e., how much energy is remaining in all of the the energy storage DER based on the optimal dispatch `solution`. Needs `network` to give percentage. @@ -248,7 +288,7 @@ function get_timestep_storage_soc(solution::Dict{String,<:Any}, network::Dict{St elseif i == 1 energy = 0.0 energy_ub = 0.0 - for (s,strg) in network["nw"]["$n"]["storage"] + for (s,strg) in get(network["nw"]["$n"], "storage", Dict()) energy += get(strg, "energy", 0.0) energy_ub += strg["energy_ub"] end diff --git a/src/stats/stability.jl b/src/stats/stability.jl index 891d2769..cb9bbb78 100644 --- a/src/stats/stability.jl +++ b/src/stats/stability.jl @@ -1,5 +1,7 @@ """ - get_timestep_stability!(args::Dict{String,<:Any})::Vector{Bool} + get_timestep_stability!( + args::Dict{String,<:Any} + )::Vector{Bool} Gets the stability at each timestep and applies it in-place to args, for use in [`entrypoint`](@ref entrypoint), using [`get_timestep_stability`](@ref get_timestep_stability) @@ -11,9 +13,11 @@ end # TODO replace when stability features are more complex """ - get_timestep_stability(is_stable::Dict{String,Bool})::Vector{Bool} + get_timestep_stability( + is_stable::Dict{String,Bool} + )::Vector{Bool} -This is a placeholder function that simple passes through the is_stable Vector +This is a placeholder function that simple passes through the `is_stable` Vector back, until the Stability feature gets more complex. """ get_timestep_stability(is_stable::Dict{String,Bool})::Vector{Bool} = Bool[is_stable["$n"] for n in sort([parse(Int,i) for i in keys(is_stable)])] diff --git a/src/stats/utils.jl b/src/stats/utils.jl index 34624426..77099656 100644 --- a/src/stats/utils.jl +++ b/src/stats/utils.jl @@ -1,4 +1,8 @@ -"turns any fields that is not a number into a string" +""" + _sanitize_results_metadata!(metadata::Dict{String,<:Any})::Dict{String,Any} + +Helper function to turn any field that is not a `Real` into a `String`. +""" function _sanitize_results_metadata!(metadata::Dict{String,<:Any})::Dict{String,Any} for (k,v) in metadata if !(typeof(v) <: Real) diff --git a/test/args.jl b/test/args.jl index 54115bd7..e9155aac 100644 --- a/test/args.jl +++ b/test/args.jl @@ -1,32 +1,48 @@ -@testset "depreciated arguments" begin +@testset "deprecated arguments" begin raw_args = Dict{String,Any}( - "network-file" => "../test/data/ieee13_feeder.dss", - "output-file" => "../test/data/test_output.json", - "problem" => "opf", - "formulation" => "acr", - "protection-settings" => "../test/data/protection_settings.xlsx", - "debug-export-file" => "../test/data/debug.json", - "use-gurobi" => true, - "solver-tolerance" => 1e-6, - "max-switch-actions" => 1, - "timestep-hours" => 0.1667, - "voltage-lower-bound" => 0.9, - "voltage-upper-bound" => 1.1, - "voltage-angle-difference" => 5.0, - "clpu-factor" => 2.0, + "quiet" => true, + "verbose" => true, + "debug" => true, + "opt-disp-algorithm" => true, + "opt-disp-formulation" => true, + "opt-disp-solver" => true, + "opt-switch-algorithm" => true, + "opt-switch-problem" => true, + "opt-switch-solver" => true, + "opt-switch-formulation" => true, + "disable-isolation-constraint" => true, + "disable-radial-constraint" => true, + "disable-inverter-constraint" => true, + "disable-presolver" => true, + "disable-networking" => true, + "fix-small-numbers" => true, + "disable-switch-penalty" => true, + "apply-switch-scores" => true, + "nprocs" => 2, ) args = sanitize_args!(deepcopy(raw_args)) - @test args["network"] == raw_args["network-file"] && !haskey(args, "network-file") - @test args["output"] == raw_args["output-file"] && !haskey(args, "output-file") - @test !haskey(args, "problem") - @test args["opt-disp-formulation"] == raw_args["formulation"] && !haskey(args, "formulation") - @test !haskey(args, "protection-settings") - @test args["debug"] && !haskey(args, "debug-export-file") - @test args["gurobi"] && !haskey(args, "use-gurobi") + @test args["log-level"] == "debug" && !haskey(args, "quiet") && !haskey(args, "verbose") && !haskey(args, "debug") - @test all(haskey(args, k) && args[k] == raw_args[k] for k in ["solver-tolerance", "max-switch-actions", "timestep-hours", "voltage-lower-bound", "voltage-upper-bound", "voltage-angle-difference", "clpu-factor"]) + @test all(haskey(args, k) && args[k] == raw_args[k] for k in [ + "opt-disp-algorithm", + "opt-disp-formulation", + "opt-disp-solver", + "opt-switch-algorithm", + "opt-switch-problem", + "opt-switch-solver", + "opt-switch-formulation", + "disable-switch-penalty", + "apply-switch-scores", + "disable-isolation-constraint", + "disable-radial-constraint", + "disable-inverter-constraint", + "disable-presolver", + "disable-networking", + "fix-small-numbers", + "nprocs" + ]) @test haskey(args, "raw_args") && args["raw_args"] == raw_args @@ -43,21 +59,16 @@ "-g", "--opt-disp-formulation", "acr", "--opt-disp-solver", "misocp_solver", - "-p", "opf", - "--protection-settings", "../test/data/protection_settings.xlsx", - "--solver-tolerance", "0.0001", - "--max-switch-actions", "1", - "--timestep-hours", "1", - "--voltage-lower-bound", "0.9", - "--voltage-upper-bound", "1.1", - "--voltage-angle-difference", "5", - "--clpu-factor", "2", "--skip", "faults, stability", "--pretty-print", + "--disable-presolver", + "--disable-isolation-constraint", + "--disable-radial-constraint", + "--disable-inverter-constraint", ] ) - args = parse_commandline() + args = sanitize_args!(parse_commandline()) delete!(args, "raw_args") @test args == Dict{String,Any}( @@ -67,28 +78,16 @@ "inverters" => "../test/data/ieee13_inverters.json", "settings" => "../test/data/ieee13_settings.json", "events" => "../test/data/ieee13_events.json", - "quiet" => true, - "verbose" => true, - "debug" => true, + "log-level" => "debug", "gurobi" => true, "opt-disp-formulation" => "acr", "opt-disp-solver" => "misocp_solver", - "problem" => "opf", - "protection-settings" => "../test/data/protection_settings.xlsx", - "solver-tolerance" => 1e-4, - "max-switch-actions" => 1, - "timestep-hours" => 1.0, - "voltage-lower-bound" => 0.9, - "voltage-upper-bound" => 1.1, - "voltage-angle-difference" => 5.0, - "clpu-factor" => 2.0, "skip" => String["faults", "stability"], "pretty-print" => true, - # flags are always stored, even if not set - "use-gurobi" => false, - "opt-switch-formulation" => "lindistflow", - "opt-switch-algorithm" => "global", - "opt-switch-solver" => "misocp_solver", - "opt-disp-algorithm" => "opf", + "disable-presolver" => true, + "disable-isolation-constraint" => true, + "disable-radial-constraint" => true, + "disable-inverter-constraint" => true, + "nprocs" => 1, ) end diff --git a/test/data.jl b/test/data.jl index 28769bec..0eee844e 100644 --- a/test/data.jl +++ b/test/data.jl @@ -1,4 +1,12 @@ @testset "data handling checks" begin - @test PowerModelsONM._get_dispatch_formulation(PMD.ACPUPowerModel) == PMD.ACPUPowerModel - @test PowerModelsONM._get_switch_formulation(LPUBFSwitchPowerModel) == LPUBFSwitchPowerModel + @test PowerModelsONM._get_formulation(PMD.ACPUPowerModel) == PMD.ACPUPowerModel +end + +@testset "test custom onm developer functions" begin + eng = parse_network("../test/data/ieee13_feeder.dss")[1] + math = transform_data_model(eng) + + pm = instantiate_onm_model(eng, NFAUPowerModel, build_block_mld) + + @test pm.data == math end diff --git a/test/data/ieee13_events.json b/test/data/ieee13_events.json index b6936bc5..d5fcb22e 100644 --- a/test/data/ieee13_events.json +++ b/test/data/ieee13_events.json @@ -6,7 +6,6 @@ "event_data": { "dispatchable": false, "state": "open", - "type": "breaker", "status": 1 } }, @@ -15,10 +14,9 @@ "event_type": "switch", "timestep": 1, "event_data": { - "dispatchable": false, - "state": "open", - "type": "breaker", - "status": 1 + "dispatchable": "NO", + "state": "OPEN", + "status": "ENABLED" } }, { @@ -26,9 +24,9 @@ "event_type": "switch", "timestep": 1, "event_data": { - "dispatchable": false, - "state": "open", - "type": "breaker" + "dispatchable": "NO", + "state": "OPEN", + "status": "ENABLED" } }, { @@ -36,9 +34,9 @@ "event_type": "switch", "timestep": 1, "event_data": { - "dispatchable": false, - "state": "open", - "type": "breaker" + "dispatchable": "NO", + "state": "OPEN", + "status": "ENABLED" } }, { @@ -46,9 +44,9 @@ "event_type": "switch", "timestep": 1, "event_data": { - "dispatchable": false, - "state": "open", - "type": "breaker" + "dispatchable": "NO", + "state": "OPEN", + "status": "ENABLED" } }, { @@ -56,10 +54,9 @@ "event_type": "switch", "timestep": 1, "event_data": { - "dispatchable": false, - "state": "open", - "type": "tie", - "status": 1 + "dispatchable": "NO", + "state": "OPEN", + "status": "ENABLED" } }, @@ -68,7 +65,7 @@ "event_type": "switch", "timestep": 2, "event_data": { - "dispatchable": true + "dispatchable": "YES" } }, { @@ -76,7 +73,7 @@ "event_type": "switch", "timestep": 2, "event_data": { - "dispatchable": true + "dispatchable": "YES" } }, { @@ -84,7 +81,7 @@ "event_type": "switch", "timestep": 2, "event_data": { - "dispatchable": true + "dispatchable": "YES" } }, { @@ -92,7 +89,7 @@ "event_type": "switch", "timestep": 2, "event_data": { - "dispatchable": true + "dispatchable": "YES" } }, { @@ -100,7 +97,7 @@ "event_type": "switch", "timestep": 2, "event_data": { - "dispatchable": true + "dispatchable": "YES" } }, { @@ -108,7 +105,7 @@ "event_type": "switch", "timestep": 2, "event_data": { - "dispatchable": true + "dispatchable": "YES" } } ] diff --git a/test/data/ieee13_fault_events.json b/test/data/ieee13_fault_events.json new file mode 100644 index 00000000..40409ce4 --- /dev/null +++ b/test/data/ieee13_fault_events.json @@ -0,0 +1,10 @@ +[ + { + "timestep": 1, + "event_type": "fault", + "affected_asset": "Line.702703", + "event_data": { + "duration_ms": 100000000 + } + } +] diff --git a/test/data/ieee13_faults.json b/test/data/ieee13_faults.json index 8ae96933..949b5613 100644 --- a/test/data/ieee13_faults.json +++ b/test/data/ieee13_faults.json @@ -1,5 +1,5 @@ { - "702": { + "692": { "3p": { "1": { "name": "1", @@ -16,7 +16,7 @@ [0, 0, 0] ], "fault_type": "3p", - "bus": "701" + "bus": "692" } }, "ll": { @@ -33,7 +33,7 @@ [0, 0] ], "fault_type": "ll", - "bus": "701" + "bus": "692" } }, "lg": { @@ -50,7 +50,7 @@ [0, 0] ], "fault_type": "lg", - "bus": "701" + "bus": "692" } } } diff --git a/test/data/ieee13_feeder.dss b/test/data/ieee13_feeder.dss index 4974bf1b..f9c3a52e 100644 --- a/test/data/ieee13_feeder.dss +++ b/test/data/ieee13_feeder.dss @@ -131,7 +131,7 @@ New Line.684652 Phases=1 Bus1=684.1 Bus2=652.1 LineCode=mtx607 Le New Line.671692 Phases=3 Bus1=671 Bus2=692 Switch=y r1=0 r0=0 x1=0.000 x0=0.000 c1=0.000 c0=0.000 normamps=800 emergamps=1000 !MICROGRID DEFINITIONS -New Line.671700 Phases=3 Bus1=670 Bus2=700 Switch=y r1=1e-4 r0=1e-4 x1=0.000 x0=0.000 c1=0.000 c0=0.000 enabled=y +New Line.671700 Phases=3 Bus1=670 Bus2=700 Switch=y r1=0 r0=0 x1=0.000 x0=0.000 c1=0.000 c0=0.000 enabled=y New Line.700701 Phases=3 Bus1=700.1.2.3 Bus2=701.1.2.3 LineCode=mtx601 Length=800 units=ft New Load.700 Phases=3 Bus1=700.1.2.3 Conn=Wye Model=1 kv=4.16 kw=10 kvar=3 daily=microgrid1b New Load.701 Phases=3 Bus1=701.1.2.3 Conn=Wye Model=1 kv=4.16 kw=15 kvar=5 daily=microgrid1b @@ -139,15 +139,16 @@ New Load.701 Phases=3 Bus1=701.1.2.3 Conn=Wye Model=1 kv=4.16 kw=15 kvar=5 da New PVSystem.PV_mg1b Phases=3 Bus1=700.1.2.3 kv=4.16 conn=wye irradiance=1 Pmpp=25 kva=35 pf=0.74 daily=pvdaily New Storage.Battery_mg1b Phases=3 Bus1=700 kwhstored=5 kwhrated=50 kwrated=10 %idlingkw=0 %idlingkvar=0 %effcharge=100 %effdischarge=100 %charge=100 %discharge=100 %r=0 %x=0 enabled=y -New Line.701702 Phases=3 Bus1=701 Bus2=702 Switch=y r1=1e-4 r0=1e-4 x1=0.000 x0=0.000 c1=0.000 c0=0.000 enabled=y -New Line.702703 Phases=3 Bus1=702.1.2.3 Bus2=703.1.2.3 LineCode=mtx601 Length=300 units=ft +New Line.701702 Phases=3 Bus1=701 Bus2=702 Switch=y r1=0 r0=0 x1=0.000 x0=0.000 c1=0.000 c0=0.000 enabled=y +New Line.702703 Phases=3 Bus1=702.1.2.3 Bus2=703.1.2.3 LineCode=mtx601 Length=900 units=ft New Load.702 Phases=3 Bus1=702.1.2.3 conn=wye model=1 kv=4.16 kw=50 kvar=20 daily=microgrid1a New Load.703 Phases=3 Bus1=703.1.2.3 conn=wye model=1 kv=4.16 kw=135 kvar=100 daily=microgrid1a New PVSystem.PV_mg1a Phases=3 Bus1=703.1.2.3 kv=4.16 conn=wye irradiance=1 Pmpp=25 kva=210 pf=0.74 daily=pvdaily New Storage.Battery_mg1a Phases=3 Bus1=700 kwhstored=25 kwhrated=200 kwrated=50 %idlingkw=0 %idlingkvar=0 %effcharge=100 %effdischarge=100 %charge=100 %discharge=100 %r=0 %x=0 enabled=y -New Line.703800 Phases=3 Bus1=703 Bus2=800 Switch=y r1=0 r0=0 x1=0 x0=0 c1=0 c0=0 enabled=y normamps=50 emergamps=50 +New Line.703800 Phases=3 Bus1=703 Bus2=800aux Switch=y r1=0 r0=0 x1=0 x0=0 c1=0 c0=0 enabled=y normamps=50 emergamps=50 +New Line.800800aux Phases=3 Bus1=800aux Bus2=800 LineCode=mtx601 Length=1000 units=ft New Line.800801 Phases=3 Bus1=800 Bus2=801 Switch=y r1=0 r0=0 x1=0 x0=0 c1=0 c0=0 enabled=y normamps=50 emergamps=50 New Load.801 Phases=3 Bus1=801 conn=wye model=1 kv=4.16 kw=200.0 kvar=120.0 daily=microgrid1c @@ -155,13 +156,16 @@ New Load.801 Phases=3 Bus1=801 conn=wye model=1 kv=4.16 kw=200.0 kvar=120.0 dail New Storage.Battery_mg1c Phases=3 Bus1=801 kwhstored=500 kwhrated=1000 kwrated=250 %idlingkw=0 %idlingkvar=0 %effcharge=100 %effdischarge=100 %charge=100 %discharge=100 %r=0 %x=0 enabled=y New Line.801675 Phases=3 Bus1=801 Bus2=675aux Switch=y r1=0 r0=0 x1=0 x0=0 c1=0.0 c0=0.0 normamps=50 emergamps=50 enabled=n ! tie switch -New Line.675675aux Phases=3 Bus1=675 Bus2=675aux LineCode=mtx601 Length=10 units=mi normamps=800 emergamps=800 +New Line.675675aux Phases=3 Bus1=675 Bus2=675aux LineCode=mtx601 Length=15 units=mi normamps=800 emergamps=800 New Generator.675 Phases=3 Bus1=675 kv=2.4 kva=500 kw=500 kvar=350 New Relay.801675 monitoredobj=line.801675 New Recloser.671700 monitoredobj=line.671700 phasetrip=140 +!New CapControl.cap1 Capacitor=Cap1 element=Line.692675 ptphase=3 terminal=2 type=voltage ptratio=1 ctratio=1 ONsetting=2500 OFFsetting=2540 VoltOverride=N +!New CapControl.cap2 Capacitor=Cap2 element=Line.684611 terminal=3 ptphase=3 type=kvar ptratio=1 ctratio=1 ONsetting=0 OFFsetting=0 VoltOverride=Y Vmin=0 Vmax=10000 + Set Voltagebases=[115, 4.16, .48] calcv diff --git a/test/data/ieee13_settings.json b/test/data/ieee13_settings.json index 7c71dac0..4e08c9a0 100644 --- a/test/data/ieee13_settings.json +++ b/test/data/ieee13_settings.json @@ -1,5 +1,6 @@ { - "voltage_source": { "source": { "pg_lb": [0.0, 0.0, 0.0] } }, + "time_elapsed": [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], + "disable_inverter_constraint": false, "line": { "632670": { "vad_ub": [5.0, 5.0, 5.0], @@ -42,6 +43,11 @@ "cm_ub": [null, null, null], "vad_lb": [-5.0, -5.0, -5.0] }, + "800800aux": { + "vad_ub": [5.0, 5.0, 5.0], + "cm_ub": [null, null, null], + "vad_lb": [-5.0, -5.0, -5.0] + }, "702703": { "vad_ub": [5.0, 5.0, 5.0], "cm_ub": [null, null, null], @@ -64,7 +70,25 @@ "vad_lb": [-5.0, -5.0, -5.0] } }, + "disable_presolver": false, + "apply_switch_scores": true, + "voltage_source": { + "source": { "qg_lb": [0.0, 0.0, 0.0], "pg_lb": [0.0, 0.0, 0.0] } + }, + "disable_isolation_constraint": false, + "dss": { + "Generator.675": { "inverter": "GRID_FORMING" }, + "PVSystem.PV_mg1a": { "inverter": "gfm" }, + "PVSystem.PV_mg1b": { "inverter": "gfm" }, + "Storage.Battery_mg1a": { "inverter": "GRID_FORMING" }, + "Storage.Battery_mg1b": { "inverter": "gfm" }, + "Storage.Battery_mg1c": { "inverter": "grid_forming" } + }, "bus": { + "800aux": { + "vm_ub": [2.641954831811728, 2.641954831811728, 2.641954831811728], + "vm_lb": [2.161599407845959, 2.161599407845959, 2.161599407845959] + }, "671": { "vm_ub": [2.641954831811728, 2.641954831811728, 2.641954831811728], "vm_lb": [2.161599407845959, 2.161599407845959, 2.161599407845959] @@ -156,20 +180,29 @@ "vm_lb": [2.161599407845959, 2.161599407845959] } }, - "nlp_solver_tol": 0.0001, - "mip_solver_tol": 0.0001, - "mip_solver_gap": 0.05, + "nlp_solver_tol": 1.0e-5, + "mip_solver_tol": 1.0e-5, + "mip_solver_gap": 0.0001, "settings": { "sbase_default": 1000.0 }, "transformer": { - "xfm1": { "sm_ub": null }, - "reg1": { "sm_ub": null }, - "sub": { "sm_ub": null } + "xfm1": { "sm_ub": 25000.0 }, + "reg1": { "sm_ub": 25000.0 }, + "sub": { "sm_ub": 25000.0 } }, "shunt": {}, "solar": {}, - "storage": {}, + "storage": { + "battery_mg1a": { "phase_unbalance_factor": 0.0 }, + "battery_mg1c": { "phase_unbalance_factor": 0.0 }, + "battery_mg1b": { "phase_unbalance_factor": 0.0 } + }, "switch": { - "801675": { "cm_ub": [25.0, 25.0, 25.0] }, + "801675": { + "cm_ub": [25.0, 25.0, 25.0], + "state": "OPEN", + "status": "ENABLED", + "dispatchable": "YES" + }, "671692": { "cm_ub": [null, null, null] }, "671700": { "cm_ub": [null, null, null] }, "703800": { "cm_ub": [null, null, null] }, @@ -204,5 +237,6 @@ "646_2": { "clpu_factor": 2.0 } }, "max_switch_actions": [1, 1, 1, 1, 1, 1, 1, 1], - "disable_switch_penalty": false + "disable_switch_penalty": false, + "disable_radial_constraint": false } diff --git a/test/faults.jl b/test/faults.jl index 9c2036f8..a002cb6e 100644 --- a/test/faults.jl +++ b/test/faults.jl @@ -3,17 +3,71 @@ "network" => "../test/data/ieee13_feeder.dss", "settings" => "../test/data/ieee13_settings.json", "faults" => "../test/data/ieee13_faults.json", - "skip" => ["stability", "dispatch", "switching"], + "events" => "../test/data/ieee13_events.json", + "opt-switch-solver" => "mip_solver", + "opt-disp-formulation" => "acp", + "opt-switch-algorithm" => "iterative", + "quiet" => true, + "skip" => ["stability"], ) entrypoint(args) - @test all(isapprox.(args["fault_studies_results"]["1"]["702"]["3p"]["1"]["solution"]["fault"]["1"]["cf"], [22500.0, 24400.0, 21500.0]; atol=1e2)) - @test all(isapprox.(args["fault_studies_results"]["1"]["702"]["ll"]["1"]["solution"]["fault"]["1"]["cf"], [18200.0, 18200.0]; atol=1e2)) - @test all(isapprox.(args["fault_studies_results"]["1"]["702"]["lg"]["1"]["solution"]["fault"]["1"]["cf"], [14200.0]; atol=1e2)) + @test args["fault_studies_results"]["5"]["692"]["3p"]["1"]["termination_status"] == PowerModelsONM.JuMP.LOCALLY_SOLVED + @test args["fault_studies_results"]["5"]["692"]["ll"]["1"]["termination_status"] == PowerModelsONM.JuMP.LOCALLY_SOLVED + @test args["fault_studies_results"]["5"]["692"]["lg"]["1"]["termination_status"] == PowerModelsONM.JuMP.LOCALLY_SOLVED +end + +@testset "test fault study algorithms - no concurrency" begin + args = Dict{String,Any}( + "network" => "../test/data/ieee13_feeder.dss", + "settings" => "../test/data/ieee13_settings.json", + "faults" => "../test/data/ieee13_faults.json", + "events" => "../test/data/ieee13_events.json", + "opt-switch-solver" => "mip_solver", + "opt-disp-formulation" => "acp", + "opt-switch-algorithm" => "iterative", + "quiet" => true, + "skip" => ["stability"], + ) + prepare_data!(args) + set_setting!(args, ("options","problem","concurrent-fault-studies"), false) - @test all(isapprox.(args["output_data"]["Fault currents"][1]["702"]["3p"]["1"]["switch"]["701702"]["|I| (A)"], [5670.0, 6880.0, 5910.0]; atol=1e1)) - @test all(isapprox.(args["output_data"]["Fault currents"][1]["702"]["ll"]["1"]["switch"]["701702"]["|I| (A)"], [6690.0, 5500.0, 193.0]; atol=1e1)) - @test all(isapprox.(args["output_data"]["Fault currents"][1]["702"]["lg"]["1"]["switch"]["701702"]["|I| (A)"], [4510.0, 2200.0, 1490.0]; atol=1e1)) + entrypoint(args) + + @test args["fault_studies_results"]["5"]["692"]["3p"]["1"]["termination_status"] == PowerModelsONM.JuMP.LOCALLY_SOLVED + @test args["fault_studies_results"]["5"]["692"]["ll"]["1"]["termination_status"] == PowerModelsONM.JuMP.LOCALLY_SOLVED + @test args["fault_studies_results"]["5"]["692"]["lg"]["1"]["termination_status"] == PowerModelsONM.JuMP.LOCALLY_SOLVED +end + +@testset "test fault study algorithms - missing dispatch solution" begin + args = Dict{String,Any}( + "network" => "../test/data/ieee13_feeder.dss", + "settings" => "../test/data/ieee13_settings.json", + "faults" => "../test/data/ieee13_faults.json", + "events" => "../test/data/ieee13_events.json", + "opt-switch-solver" => "mip_solver", + "opt-switch-algorithm" => "iterative", + "quiet" => true, + "skip" => ["stability", "dispatch"], + ) + entrypoint(args) + + @test args["fault_studies_results"]["5"]["692"]["3p"]["1"]["termination_status"] == PowerModelsONM.JuMP.LOCALLY_SOLVED + @test args["fault_studies_results"]["5"]["692"]["ll"]["1"]["termination_status"] == PowerModelsONM.JuMP.LOCALLY_SOLVED + @test args["fault_studies_results"]["5"]["692"]["lg"]["1"]["termination_status"] == PowerModelsONM.JuMP.LOCALLY_SOLVED +end + +@testset "test fault study algorithms - no fault inputs" begin + args = Dict{String,Any}( + "network" => "../test/data/ieee13_feeder.dss", + "settings" => "../test/data/ieee13_settings.json", + "events" => "../test/data/ieee13_events.json", + "opt-switch-solver" => "mip_solver", + "opt-switch-algorithm" => "iterative", + "quiet" => true, + "skip" => ["stability"], + ) + entrypoint(args) - @test all(isapprox.(args["output_data"]["Fault currents"][1]["702"]["lg"]["1"]["switch"]["701702"]["|V| (V)"], [0.15, 2.51, 2.36]; atol=1e-2)) + @test count_faults(args["faults"]) == 193 end diff --git a/test/graphml.jl b/test/graphml.jl new file mode 100644 index 00000000..4452a576 --- /dev/null +++ b/test/graphml.jl @@ -0,0 +1,27 @@ +@testset "test graphml io" begin + eng = parse_file("../test/data/ieee13_feeder.dss") + + @testset "test nested graph" begin + graph = build_nested_graph(eng) + @test length(graph.node) == 6 + @test length(graph.edge) == 6 + + save_graphml("../test/data/ieee13_nested.graphml", eng; type="nested") + open("../test/data/ieee13_nested.graphml", "r") do io + @test length(readlines(io)) == 1461 + end + rm("../test/data/ieee13_nested.graphml") + end + + @testset "test unnested graph" begin + graph = build_unnested_graph(eng) + @test length(graph.node) == 57 + @test length(graph.edge) == 57 + + save_graphml("../test/data/ieee13_unnested.graphml", eng; type="unnested") + open("../test/data/ieee13_unnested.graphml", "r") do io + @test length(readlines(io)) == 1431 + end + rm("../test/data/ieee13_unnested.graphml") + end +end diff --git a/test/io.jl b/test/io.jl index 93e12819..e8594ad9 100644 --- a/test/io.jl +++ b/test/io.jl @@ -1,5 +1,5 @@ @testset "test io functions" begin - base_network, network = parse_network("../test/data/ieee13_feeder.dss") + base_network, network = parse_network("../test/data/ieee13_feeder.dss"); @testset "test network parsing" begin @test PMD.ismultinetwork(network) @@ -33,42 +33,190 @@ @test _network["nw"]["3"]["switch"]["671700"]["dispatchable"] == PMD.YES end + @testset "test fault events parsing" begin + events = parse_events("../test/data/ieee13_fault_events.json", network) + + @test events["1"]["switch"]["701702"]["state"] == PMD.OPEN + @test events["1"]["switch"]["701702"]["dispatchable"] == PMD.NO + @test events["1"]["switch"]["703800"]["state"] == PMD.OPEN + @test events["1"]["switch"]["703800"]["dispatchable"] == PMD.NO + @test length(events) == 1 + @test length(events["1"]["switch"]) == 2 + + _network = apply_events(network, events) + + for (n,nw) in _network["nw"] + @test nw["switch"]["701702"]["dispatchable"] == PMD.NO + @test nw["switch"]["703800"]["dispatchable"] == PMD.NO + end + end + + @testset "test parse events from args structure with no network" begin + args = Dict{String,Any}("events" => "../test/data/ieee13_fault_events.json") + + events = parse_events!(args) + + @test isa(events, Vector{Dict{String,Any}}) + end + + @testset "test build events from helper function" begin + events = build_events( + base_network; + default_switch_state=PMD.OPEN, + default_switch_dispatchable=PMD.NO, + default_switch_status=PMD.DISABLED + ) + + @test length(events) == 6 + @test all(event["event_data"]["state"] == "OPEN" for event in events) + @test all(event["event_data"]["status"] == "DISABLED" for event in events) + @test all(event["event_data"]["dispatchable"] == "NO" for event in events) + + custom_events = Dict{String,Any}[ + Dict{String,Any}( + "timestep" => 2, + "event_type" => "switch", + "affected_asset" => "line.801675", + "event_data" => Dict{String,Any}( + "status" => 0, + "state" => "open", + "dispatchable" => false + ) + ) + ] + + events = build_events( + base_network; + custom_events=custom_events, + default_switch_state=PMD.OPEN, + default_switch_dispatchable=PMD.NO, + default_switch_status=PMD.DISABLED + ) + + @test events[end] == Dict{String,Any}( + "timestep" => 2, + "event_type" => "switch", + "affected_asset" => "line.801675", + "event_data" => Dict{String,Any}( + "status" => "DISABLED", + "state" => "OPEN", + "dispatchable" => "NO" + ) + ) + end + + @testset "test parse raw events from args structure" begin + args = Dict{String,Any}( + "events" => parse_events("../test/data/ieee13_fault_events.json"), + "network" => network, + ) + + events = parse_events!(args) + + @test isa(events, Dict) + @test isa(args["events"], Dict) + @test length(events) == 1 + @test length(events["1"]["switch"]) == 2 + end + @testset "test settings parsing" begin settings = parse_settings("../test/data/ieee13_settings.json") - _network = apply_settings(network, settings) + _network = make_multinetwork(apply_settings(base_network, settings)) @test all(all(l["clpu_factor"] == 2.0 for l in values(nw["load"])) for nw in values(_network["nw"])) - @test all(nw["max_switch_actions"] == 1 for nw in values(_network["nw"])) + @test all(nw["switch_close_actions_ub"] == 1 for nw in values(_network["nw"])) @test all(nw["time_elapsed"] == 1.0 for nw in values(_network["nw"])) + + @test all(nw["generator"]["675"]["inverter"] == GRID_FORMING for nw in values(_network["nw"])) + @test all(nw["solar"]["pv_mg1a"]["inverter"] == GRID_FORMING for nw in values(_network["nw"])) + @test all(nw["solar"]["pv_mg1b"]["inverter"] == GRID_FORMING for nw in values(_network["nw"])) + @test all(nw["storage"]["battery_mg1a"]["inverter"] == GRID_FORMING for nw in values(_network["nw"])) + @test all(nw["storage"]["battery_mg1b"]["inverter"] == GRID_FORMING for nw in values(_network["nw"])) + @test all(nw["storage"]["battery_mg1c"]["inverter"] == GRID_FORMING for nw in values(_network["nw"])) + end + + @testset "test parse empty settings string" begin + args = Dict{String,Any}("settings" => "") + settings = parse_settings!(args) + + ds = build_default_settings() + + @test settings == build_default_settings() + end + + @testset "test getter and setter functions" begin + args = Dict{String,Any}( + "settings" => "../test/data/ieee13_settings.json", + "network" => "../test/data/ieee13_feeder.dss", + "events" => "../test/data/ieee13_events.json", + ) + prepare_data!(args) + + orig_args = deepcopy(args) + + set_setting!(args, ("options", "variables", "unbound-voltage"), true) + + @test get_setting(args, ("options", "variables", "unbound-voltage")) + @test orig_args["network"]["nw"]["1"]["switch"] == orig_args["network"]["nw"]["1"]["switch"] + + set_settings!(args, Dict( + ("options", "variables", "unbound-line-power") => true, + ("options", "variables", "unbound-transformer-power") => true + )) + + @test get_setting(args, ("options", "variables", "unbound-line-power")) + @test get_setting(args, ("options", "variables", "unbound-transformer-power")) + + set_option!(args["network"], ("options", "variables", "unbound-line-current"), true) + @test get_option(args["network"], ("options", "variables", "unbound-line-current")) + @test all(get_option(nw, ("options", "variables", "unbound-line-current")) for (n,nw) in args["network"]["nw"]) + + set_options!(args["network"], Dict( + ("options", "variables", "unbound-generation-power") => true, + ("options", "variables", "unbound-storage-power") => true, + )) + @test get_option(args["network"], ("options", "variables", "unbound-generation-power")) + @test all(get_option(nw, ("options", "variables", "unbound-generation-power")) for (n,nw) in args["network"]["nw"]) + @test get_option(args["network"], ("options", "variables", "unbound-storage-power")) + @test all(get_option(nw, ("options", "variables", "unbound-storage-power")) for (n,nw) in args["network"]["nw"]) end @testset "test runtime args to settings conversion" begin args = Dict{String,Any}( - "solver-tolerance" => 1e-4, - "max-switch-actions" => 1, - "timestep-hours" => 0.1667, - "voltage-lower-bound" => 0.9, - "voltage-upper-bound" => 1.1, - "voltage-angle-difference" => 5.0, - "clpu-factor" => 2.0, + "nlp_solver_tol" => 1e-4, + "mip_solver_tol" => 1e-4, + "mip_solver_gap" => 0.01, + "max_switch_actions" => 1, + "time_elapsed" => 0.1667, + "disable_presolver" => true, + "disable_networking" => true, + "disable_radial_constraint" => true, + "disable_isolation_constraint" => true, + "disable_inverter_constraint" => true, + "disable_switch_penalty" => true, + "apply_switch_scores" => true, ) orig_keys = collect(keys(args)) - args, settings = PowerModelsONM._convert_depreciated_runtime_args!(args, Dict{String,Any}(), base_network, length(network["nw"])) + settings = correct_settings!(args) @test all(!haskey(args, k) for k in orig_keys) - @test settings["nlp_solver_tol"] == 1e-4 - _network = apply_settings(network, settings) - @test all(all(l["clpu_factor"] == 2.0 for l in values(nw["load"])) for nw in values(_network["nw"])) - @test all(nw["max_switch_actions"] == 1 for nw in values(_network["nw"])) - @test all(nw["time_elapsed"] == 0.1667 for nw in values(_network["nw"])) + @test settings["solvers"]["Ipopt"]["tol"] == 1e-4 + @test settings["solvers"]["HiGHS"]["primal_feasibility_tolerance"] == 1e-4 + @test settings["solvers"]["HiGHS"]["mip_rel_gap"] == 0.01 + @test settings["options"]["data"]["time-elapsed"] == 0.1667 + @test settings["options"]["data"]["switch-close-actions-ub"] == 1 + @test settings["options"]["constraints"]["disable-microgrid-networking"] + @test settings["options"]["constraints"]["disable-radiality-constraint"] + @test settings["options"]["constraints"]["disable-block-isolation-constraint"] + @test settings["options"]["constraints"]["disable-grid-forming-inverter-constraint"] + @test settings["options"]["objective"]["disable-switch-state-change-cost"] + @test settings["options"]["objective"]["enable-switch-state-open-cost"] - bus_vbase, line_vbase = PMD.calc_voltage_bases(base_network, base_network["settings"]["vbases_default"]) - @test all(all(isapprox.(bus["vm_lb"], 0.9 * bus_vbase[id])) for (id,bus) in settings["bus"]) - @test all(all(isapprox.(bus["vm_ub"], 1.1 * bus_vbase[id])) for (id,bus) in settings["bus"]) - - @test all(all(line["vad_lb"] .== -5.0) && all(line["vad_ub"] .== 5.0) for (_,line) in settings["line"]) + _network = make_multinetwork(apply_settings(base_network, settings)) + @test all(nw["switch_close_actions_ub"] == 1 for nw in values(_network["nw"])) + @test all(nw["time_elapsed"] == 0.1667 for nw in values(_network["nw"])) end @testset "test inverters parsing" begin @@ -82,4 +230,160 @@ @test all(isa(fault["g"], Matrix) && isa(fault["b"], Matrix) for (bus,fts) in faults for (ft,fids) in fts for (fid,fault) in fids) @test all(isa(fault["connections"], Vector{Int}) for (bus,fts) in faults for (ft,fids) in fts for (fid,fault) in fids) end + + @testset "test build settings" begin + custom_settings = Dict{String,Any}( + "switch" => Dict{String,Any}( + "801675" => Dict{String,Any}( + "cm_ub" => [25.0, 25.0, 25.0], + "state"=>"OPEN", + "dispatchable"=>"YES", + "status"=>"ENABLED" + ), + "800801" => Dict{String,Any}("cm_ub" => [25.0, 25.0, 25.0]) + ), + "voltage_source" => Dict{String,Any}( + "source" => Dict{String,Any}( + "pg_lb" => [0.0, 0.0, 0.0], + "qg_lb" => [0.0, 0.0, 0.0], + ) + ), + "transformer" => Dict{String,Any}( + "xfm1" => Dict{String,Any}( "sm_ub" => 25000.0 ), + "reg1" => Dict{String,Any}( "sm_ub" => 25000.0 ), + "sub" => Dict{String,Any}( "sm_ub" => 25000.0 ), + ), + "dss" => Dict{String,Any}( + "Generator.675" => Dict{String,Any}("inverter" => "GRID_FORMING"), + "PVSystem.PV_mg1a" => Dict{String,Any}("inverter" => "gfm"), + "PVSystem.PV_mg1b" => Dict{String,Any}("inverter" => "gfm"), + "Storage.Battery_mg1a" => Dict{String,Any}("inverter" => "GRID_FORMING"), + "Storage.Battery_mg1b" => Dict{String,Any}("inverter" => "gfm"), + "Storage.Battery_mg1c" => Dict{String,Any}("inverter" => "grid_forming") + ), + ) + + settings = build_settings( + "../test/data/ieee13_feeder.dss"; + time_elapsed=1.0, + clpu_factor=2.0, + max_switch_actions=1, + disable_switch_penalty=false, + apply_switch_scores=true, + disable_presolver=false, + mip_solver_gap=0.0001, + nlp_solver_tol=0.00001, + mip_solver_tol=0.00001, + sbase_default=1000.0, + vm_lb_pu=0.9, + vm_ub_pu=1.1, + vad_deg=5.0, + line_limit_mult=Inf, + storage_phase_unbalance_factor=0.0, + custom_settings=custom_settings, + disable_isolation_constraint=false, + disable_radial_constraint=false, + disable_inverter_constraint=false, + ) + + local_settings = parse_settings("../test/data/ieee13_settings.json") + + @test settings == local_settings + + # for debugging this test + # for (k,v) in settings + # if v != local_settings[k] + # @warn k v local_settings[k] + # end + # end + end + + @testset "test build_settings_new" begin + custom_settings = Dict{String,Any}( + "settings" => Dict{String,Any}( + "sbase_default" => 1000.0, + ), + "switch" => Dict{String,Any}( + "801675" => Dict{String,Any}( + "cm_ub" => [25.0, 25.0, 25.0], + "state"=>OPEN, + "dispatchable"=>YES, + "status"=>ENABLED + ), + "800801" => Dict{String,Any}("cm_ub" => [25.0, 25.0, 25.0]) + ), + "voltage_source" => Dict{String,Any}( + "source" => Dict{String,Any}( + "pg_lb" => [0.0, 0.0, 0.0], + "qg_lb" => [0.0, 0.0, 0.0], + ) + ), + "transformer" => Dict{String,Any}( + "xfm1" => Dict{String,Any}( "sm_ub" => 25000.0 ), + "reg1" => Dict{String,Any}( "sm_ub" => 25000.0 ), + "sub" => Dict{String,Any}( "sm_ub" => 25000.0 ), + ), + "dss" => Dict{String,Any}( + "Generator.675" => Dict{String,Any}("inverter" => GRID_FORMING), + "PVSystem.PV_mg1a" => Dict{String,Any}("inverter" => "gfm"), + "PVSystem.PV_mg1b" => Dict{String,Any}("inverter" => "gfm"), + "Storage.Battery_mg1a" => Dict{String,Any}("inverter" => GRID_FORMING), + "Storage.Battery_mg1b" => Dict{String,Any}("inverter" => "gfm"), + "Storage.Battery_mg1c" => Dict{String,Any}("inverter" => "grid_forming") + ), + "options" => Dict{String,Any}( + "objective" => Dict{String,Any}( + "enable-switch-state-open-cost" => true, + ) + ), + "solvers" => Dict{String,Any}( + "Ipopt" => Dict{String,Any}( + "tol" => 1e-5 + ), + "HiGHS" => Dict{String,Any}( + "primal_feasibility_tolerance" => 1e-5, + "dual_feasibility_tolerance" => 1e-5, + "mip_rel_gap" => 0.0001, + ), + "Gurobi" => Dict{String,Any}( + "FeasibilityTol" => 1e-5, + "MIPGap" => 1e-4 + ), + "Juniper" => Dict{String,Any}( + "atol" => 1e-5, + "mip_gap" => 1e-4, + ), + "KNITRO" => Dict{String,Any}( + "feastol" => 1e-5, + "opttol" => 1e-4 + ) + ) + ) + + settings = build_settings_new( + "../test/data/ieee13_feeder.dss"; + raw_settings = custom_settings, + switch_close_actions_ub=1, + timestep_hours=1.0, + vm_lb_pu=0.9, + vm_ub_pu=1.1, + vad_deg=5.0, + line_limit_multiplier=Inf, + transformer_limit_multiplier=Inf, + generate_microgrid_ids=true, + cold_load_pickup_factor=2.0, + storage_phase_unbalance_factor=0.0, + ) + + local_settings = parse_settings("../test/data/ieee13_settings.json") + + @test settings == filter(x->!isempty(x.second), local_settings) + end + + @testset "test faults io" begin + faults = parse_faults("../test/data/ieee13_faults.json") + + @test count_faults(faults) == 3 + end end + diff --git a/test/mld.jl b/test/mld.jl new file mode 100644 index 00000000..15c42f14 --- /dev/null +++ b/test/mld.jl @@ -0,0 +1,117 @@ +@testset "test optimal switching" begin + orig_args = Dict{String,Any}( + "network" => "../test/data/ieee13_feeder.dss", + "settings" => "../test/data/ieee13_settings.json", + "events" => "../test/data/ieee13_events.json", + "quiet" => true, + "skip" => ["faults", "stability", "dispatch"], + "opt-switch-solver" => "mip_solver", + ) + + @testset "test iterative optimal switching" begin + @testset "test iterative optimal switching - lindistflow - block" begin + args = deepcopy(orig_args) + args["opt-switch-formulation"] = "lindistflow" + args["opt-switch-algorithm"] = "iterative" + args["opt-switch-problem"] = "block" + + entrypoint(args) + + @test get_timestep_device_actions!(args) == Dict{String, Any}[Dict("Shedded loads" => ["692_3", "675b", "675a", "692_1", "702", "703", "675c"], "Switch configurations" => Dict("801675" => "open", "671692" => "open", "671700" => "open", "703800" => "open", "800801" => "open", "701702" => "open")), Dict("Shedded loads" => ["701", "702", "700", "703"], "Switch configurations" => Dict("801675" => "closed", "671692" => "open", "671700" => "open", "703800" => "open", "800801" => "open", "701702" => "open")), Dict("Shedded loads" => ["702", "703"], "Switch configurations" => Dict("801675" => "closed", "671692" => "open", "671700" => "closed", "703800" => "open", "800801" => "open", "701702" => "open")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "closed", "671692" => "closed", "671700" => "closed", "703800" => "open", "800801" => "open", "701702" => "open")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "closed", "671692" => "closed", "671700" => "closed", "703800" => "open", "800801" => "open", "701702" => "closed")), Dict("Shedded loads" => ["801"], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "closed", "703800" => "open", "800801" => "closed", "701702" => "closed")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "closed", "703800" => "closed", "800801" => "closed", "701702" => "closed")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "closed", "703800" => "closed", "800801" => "closed", "701702" => "closed"))] + end + + @testset "test iterative optimal switching - lindistflow - traditional" begin + args = deepcopy(orig_args) + args["opt-switch-formulation"] = "lindistflow" + args["opt-switch-algorithm"] = "iterative" + args["opt-switch-problem"] = "traditional" + + entrypoint(args) + + @test get_timestep_device_actions!(args) == Dict{String, Any}[Dict("Shedded loads" => ["692_3", "675b", "675a", "692_1", "702", "703", "675c"], "Switch configurations" => Dict("801675" => "open", "671692" => "open", "671700" => "open", "703800" => "open", "800801" => "open", "701702" => "open")), Dict("Shedded loads" => ["701", "702", "700", "703"], "Switch configurations" => Dict("801675" => "closed", "671692" => "open", "671700" => "open", "703800" => "open", "800801" => "open", "701702" => "open")), Dict("Shedded loads" => ["702", "703"], "Switch configurations" => Dict("801675" => "closed", "671692" => "open", "671700" => "open", "703800" => "open", "800801" => "open", "701702" => "closed")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "closed", "671692" => "closed", "671700" => "open", "703800" => "open", "800801" => "open", "701702" => "closed")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "closed", "671692" => "closed", "671700" => "open", "703800" => "closed", "800801" => "open", "701702" => "closed")), Dict("Shedded loads" => ["801"], "Switch configurations" => Dict("801675" => "closed", "671692" => "closed", "671700" => "open", "703800" => "closed", "800801" => "closed", "701702" => "closed")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "closed", "671692" => "closed", "671700" => "open", "703800" => "closed", "800801" => "closed", "701702" => "closed")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "closed", "671692" => "closed", "671700" => "open", "703800" => "closed", "800801" => "closed", "701702" => "closed"))] + end + + @testset "test iterative optimal switching - nfa - block" begin + args = deepcopy(orig_args) + args["opt-switch-formulation"] = "nfa" + args["opt-switch-algorithm"] = "iterative" + args["opt-switch-problem"] = "block" + + entrypoint(args) + + @test get_timestep_device_actions!(args) == Dict{String, Any}[Dict("Shedded loads" => ["692_3", "675b", "675a", "692_1", "702", "703", "675c"], "Switch configurations" => Dict("801675" => "open", "671692" => "open", "671700" => "open", "703800" => "open", "800801" => "open", "701702" => "open")), Dict("Shedded loads" => ["701", "702", "700", "703"], "Switch configurations" => Dict("801675" => "closed", "671692" => "open", "671700" => "open", "703800" => "open", "800801" => "open", "701702" => "open")), Dict("Shedded loads" => ["702", "703"], "Switch configurations" => Dict("801675" => "closed", "671692" => "open", "671700" => "closed", "703800" => "open", "800801" => "open", "701702" => "open")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "closed", "671692" => "closed", "671700" => "closed", "703800" => "open", "800801" => "open", "701702" => "open")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "closed", "671692" => "closed", "671700" => "closed", "703800" => "open", "800801" => "open", "701702" => "closed")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "closed", "671692" => "closed", "671700" => "closed", "703800" => "open", "800801" => "closed", "701702" => "closed")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "closed", "671692" => "closed", "671700" => "closed", "703800" => "open", "800801" => "closed", "701702" => "closed")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "closed", "671692" => "closed", "671700" => "closed", "703800" => "open", "800801" => "closed", "701702" => "closed"))] + end + + @testset "test iterative optimal switching - nfa - traditional" begin + args = deepcopy(orig_args) + args["opt-switch-formulation"] = "nfa" + args["opt-switch-algorithm"] = "iterative" + args["opt-switch-problem"] = "traditional" + + entrypoint(args) + + @test get_timestep_device_actions!(args) == Dict{String, Any}[Dict("Shedded loads" => ["692_3", "675b", "675a", "692_1", "702", "703", "675c"], "Switch configurations" => Dict("801675" => "open", "671692" => "open", "671700" => "open", "703800" => "open", "800801" => "open", "701702" => "open")), Dict("Shedded loads" => ["701", "702", "700", "703"], "Switch configurations" => Dict("801675" => "closed", "671692" => "open", "671700" => "open", "703800" => "open", "800801" => "open", "701702" => "open")), Dict("Shedded loads" => ["702", "703"], "Switch configurations" => Dict("801675" => "closed", "671692" => "open", "671700" => "open", "703800" => "open", "800801" => "open", "701702" => "closed")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "closed", "671692" => "closed", "671700" => "open", "703800" => "open", "800801" => "open", "701702" => "closed")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "closed", "671692" => "closed", "671700" => "open", "703800" => "open", "800801" => "closed", "701702" => "closed")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "closed", "671692" => "closed", "671700" => "open", "703800" => "closed", "800801" => "closed", "701702" => "closed")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "closed", "671692" => "closed", "671700" => "open", "703800" => "closed", "800801" => "closed", "701702" => "closed")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "closed", "671692" => "closed", "671700" => "open", "703800" => "closed", "800801" => "closed", "701702" => "closed"))] + end + end + + @testset "test global optimal switching" begin + @testset "test global optimal switching - lindistflow - block" begin + args = deepcopy(orig_args) + args["opt-switch-algorithm"] = "global" + args["opt-switch-formulation"] = "lindistflow" + args["opt-switch-problem"] = "block" + + entrypoint(args) + + @test args["optimal_switching_results"]["5"]["solution"]["load"]["801"]["status"] == PMD.DISABLED + @test isapprox(args["optimal_switching_results"]["1"]["objective"], 81.07; atol=1) + end + + @testset "test global optimal switching - lindistflow - block - radial-disabled" begin + args = deepcopy(orig_args) + args["opt-switch-algorithm"] = "global" + args["opt-switch-formulation"] = "lindistflow" + args["opt-switch-problem"] = "block" + args["disable-radial-constraint"] = true + + entrypoint(args) + + @test isapprox(args["optimal_switching_results"]["1"]["objective"], 79.54; atol=1) + end + + @testset "test global optimal switching - lindistflow - traditional - radial-disabled - inverter-disabled" begin + args = deepcopy(orig_args) + args["opt-switch-algorithm"] = "global" + args["opt-switch-formulation"] = "lindistflow" + args["opt-switch-problem"] = "traditional" + args["disable-radial-constraint"] = true + args["disable-inverter-constraint"] = true + + entrypoint(args) + + @test isapprox(args["optimal_switching_results"]["1"]["objective"], 78.90; atol=1) + end + + @testset "test global optimal switching - nfa - block" begin + args = deepcopy(orig_args) + args["opt-switch-algorithm"] = "global" + args["opt-switch-formulation"] = "nfa" + args["opt-switch-problem"] = "block" + + entrypoint(args) + + @test isapprox(args["optimal_switching_results"]["1"]["objective"], 69.66; atol=1) + end + + @testset "test global optimal switching - nfa - traditional" begin + args = deepcopy(orig_args) + args["opt-switch-algorithm"] = "global" + args["opt-switch-formulation"] = "nfa" + args["opt-switch-problem"] = "traditional" + + entrypoint(args) + + @test isapprox(args["optimal_switching_results"]["1"]["objective"], 69.66; atol=1) + end + end +end diff --git a/test/nlp.jl b/test/nlp.jl new file mode 100644 index 00000000..a8010184 --- /dev/null +++ b/test/nlp.jl @@ -0,0 +1,37 @@ +@testset "test nlp formulations" begin + eng = parse_file("../test/data/ieee13_feeder.dss") + + settings = parse_settings("../test/data/ieee13_settings.json") + + eng = apply_settings(eng, settings; multinetwork=false) + + for (s,switch) in eng["switch"] + switch["state"] = OPEN + end + + eng["switch_close_actions_ub"] = Inf + + set_options!( + eng, + Dict( + ("options","constraints","disable-grid-forming-inverter-constraint") => true, + ("options","constraints","disable-storage-unbalance-constraint") => true, + ) + ) + + @testset "test block mld - acp" begin + result = solve_block_mld(eng, ACPUPowerModel, minlp_solver) + + @test result["termination_status"] == LOCALLY_SOLVED + @test length(filter(x->x.second["state"]==CLOSED, result["solution"]["switch"])) == 5 + @test isapprox(result["objective"], 6.97; atol=0.1) + end + + @testset "test block mld - acr" begin + result = solve_block_mld(eng, ACRUPowerModel, minlp_solver) + + @test result["termination_status"] == LOCALLY_SOLVED + @test length(filter(x->x.second["state"]==CLOSED, result["solution"]["switch"])) == 5 + @test isapprox(result["objective"], 6.97; atol=0.1) + end +end diff --git a/test/opf.jl b/test/opf.jl index fcfd039f..67c12469 100644 --- a/test/opf.jl +++ b/test/opf.jl @@ -1,45 +1,81 @@ @testset "test optimal dispatch" begin - args = Dict{String,Any}( + orig_args = Dict{String,Any}( "network" => "../test/data/ieee13_feeder.dss", "settings" => "../test/data/ieee13_settings.json", - "skip" => ["faults", "switching", "stability", "dispatch"], + "skip" => ["faults", "switching", "stability"], + "quiet" => true, ) - entrypoint(args) - vbase, _ = PowerModelsDistribution.calc_voltage_bases(args["base_network"], args["base_network"]["settings"]["vbases_default"]) + @testset "test nfa opf" begin + args = deepcopy(orig_args) + args["opt-disp-formulation"] = "nfa" - args["opt-disp-formulation"] = "nfa" - optimize_dispatch!(args) + entrypoint(args) - @test isapprox(args["optimal_dispatch_result"]["objective"], 0.55; atol=1e-2) + @test isapprox(args["optimal_dispatch_result"]["objective"], 4.85; atol=1e-2) - v_stats = get_timestep_voltage_statistics(args["optimal_dispatch_result"]["solution"], args["network"]) - @test all(all(v .== 0) for v in values(v_stats)) + vbase, _ = PMD.calc_voltage_bases(args["base_network"], args["base_network"]["settings"]["vbases_default"]) - disp_sol = get_timestep_dispatch(args["optimal_dispatch_result"]["solution"], args["network"]) - @test all(all(all(switch["voltage (V)"] .== 0) for switch in values(timestep["switch"])) for timestep in disp_sol) + v_stats = get_timestep_voltage_statistics(args["optimal_dispatch_result"]["solution"], args["network"]) + @test all(all(v .== 0) for v in values(v_stats)) - args["opt-disp-formulation"] = "lindistflow" - optimize_dispatch!(args) - @test isapprox(args["optimal_dispatch_result"]["objective"], 0.55; atol=1e-2) - @test all(isapprox.(args["optimal_dispatch_result"]["solution"]["nw"]["7"]["bus"]["801"]["vm"] ./ vbase["801"], [1.03, 1.03, 1.03]; atol=1e-2)) - @test all(isapprox.(args["optimal_dispatch_result"]["solution"]["nw"]["7"]["bus"]["675"]["vm"] ./ vbase["675"], [1.02, 1.04, 1.01]; atol=1e-2)) + disp_sol = get_timestep_dispatch(args["optimal_dispatch_result"]["solution"], args["network"]) + @test all(all(all(switch["voltage (V)"] .== 0) for switch in values(timestep["switch"])) for timestep in disp_sol) + end - args["opt-disp-formulation"] = "acr" - optimize_dispatch!(args) + @testset "test lindistflow opf" begin + args = deepcopy(orig_args) + args["opt-disp-formulation"] = "lindistflow" + entrypoint(args) - @test isapprox(args["optimal_dispatch_result"]["objective"], 3.33; atol=1e-2) - @test all(isapprox.(args["optimal_dispatch_result"]["solution"]["nw"]["7"]["bus"]["801"]["vm"] ./ vbase["801"], [1.04, 1.06, 1.04]; atol=1e-2)) - @test all(isapprox.(args["optimal_dispatch_result"]["solution"]["nw"]["7"]["bus"]["801"]["va"], [-3.34, -121.53, 117.30]; atol=1e0)) - @test all(isapprox.(args["optimal_dispatch_result"]["solution"]["nw"]["7"]["bus"]["675"]["vm"] ./ vbase["675"], [1.03, 1.08, 1.03]; atol=1e-2)) - @test all(isapprox.(args["optimal_dispatch_result"]["solution"]["nw"]["7"]["bus"]["675"]["va"], [-5.07, -121.82, 116.63]; atol=1e0)) + vbase, _ = PMD.calc_voltage_bases(args["base_network"], args["base_network"]["settings"]["vbases_default"]) - args["opt-disp-formulation"] = "acp" - optimize_dispatch!(args) + @test isapprox(args["optimal_dispatch_result"]["objective"], 4.85; atol=1e-2) + @test all(isapprox.(args["optimal_dispatch_result"]["solution"]["nw"]["7"]["bus"]["801"]["vm"] ./ vbase["801"], [1.02, 1.02, 1.02]; atol=1e-2)) + @test all(isapprox.(args["optimal_dispatch_result"]["solution"]["nw"]["7"]["bus"]["675"]["vm"] ./ vbase["675"], [1.00, 1.03, 1.00]; atol=1e-2)) + end - @test isapprox(args["optimal_dispatch_result"]["objective"], 3.33; atol=1e-2) - @test all(isapprox.(args["optimal_dispatch_result"]["solution"]["nw"]["7"]["bus"]["801"]["vm"] ./ vbase["801"], [1.04, 1.06, 1.03]; atol=1e-2)) - @test all(isapprox.(args["optimal_dispatch_result"]["solution"]["nw"]["7"]["bus"]["801"]["va"], [-3.34, -121.53, 117.30]; atol=1e0)) - @test all(isapprox.(args["optimal_dispatch_result"]["solution"]["nw"]["7"]["bus"]["675"]["vm"] ./ vbase["675"], [1.03, 1.08, 1.03]; atol=1e-2)) - @test all(isapprox.(args["optimal_dispatch_result"]["solution"]["nw"]["7"]["bus"]["675"]["va"], [-5.07, -121.82, 116.63]; atol=1e0)) + @testset "test acr opf" begin + args = deepcopy(orig_args) + args["opt-disp-formulation"] = "acr" + entrypoint(args) + + vbase, _ = PMD.calc_voltage_bases(args["base_network"], args["base_network"]["settings"]["vbases_default"]) + + @test isapprox(args["optimal_dispatch_result"]["objective"], 5.13; atol=2e-2) + @test all(isapprox.(args["optimal_dispatch_result"]["solution"]["nw"]["7"]["bus"]["801"]["vm"] ./ vbase["801"], [1.04, 1.06, 1.04]; atol=1e-2)) + @test all(isapprox.(args["optimal_dispatch_result"]["solution"]["nw"]["7"]["bus"]["801"]["va"], [-3.31, -121.51, 117.33]; atol=1e0)) + @test all(isapprox.(args["optimal_dispatch_result"]["solution"]["nw"]["7"]["bus"]["675"]["vm"] ./ vbase["675"], [1.03, 1.08, 1.03]; atol=1e-2)) + @test all(isapprox.(args["optimal_dispatch_result"]["solution"]["nw"]["7"]["bus"]["675"]["va"], [-5.03, -121.80, 116.66]; atol=1e0)) + end + + @testset "test acp opf" begin + args = deepcopy(orig_args) + args["opt-disp-formulation"] = "acp" + entrypoint(args) + + vbase, _ = PMD.calc_voltage_bases(args["base_network"], args["base_network"]["settings"]["vbases_default"]) + + @test isapprox(args["optimal_dispatch_result"]["objective"], 5.13; atol=2e-2) + @test all(isapprox.(args["optimal_dispatch_result"]["solution"]["nw"]["7"]["bus"]["801"]["vm"] ./ vbase["801"], [1.04, 1.06, 1.04]; atol=1e-2)) + @test all(isapprox.(args["optimal_dispatch_result"]["solution"]["nw"]["7"]["bus"]["801"]["va"], [-3.31, -121.51, 117.33]; atol=1e0)) + @test all(isapprox.(args["optimal_dispatch_result"]["solution"]["nw"]["7"]["bus"]["675"]["vm"] ./ vbase["675"], [1.03, 1.08, 1.03]; atol=1e-2)) + @test all(isapprox.(args["optimal_dispatch_result"]["solution"]["nw"]["7"]["bus"]["675"]["va"], [-5.03, -121.80, 116.66]; atol=1e0)) + end + + @testset "test fix-small-numbers nfa opf" begin + args = deepcopy(orig_args) + prepare_data!(args) + + set_settings!(args, Dict( + ("options","data","fix-small-numbers") => true, + ("options","problem","dispatch-formulation") => "nfa" + )) + + build_solver_instances!(args) + + result = optimize_dispatch!(args) + + @test isapprox(result["objective"], 4.85; atol=1e-2) + end end diff --git a/test/osw.jl b/test/osw.jl deleted file mode 100644 index 2ef9e7ff..00000000 --- a/test/osw.jl +++ /dev/null @@ -1,44 +0,0 @@ -@testset "test optimal switching" begin - orig_args = Dict{String,Any}( - "network" => "../test/data/ieee13_feeder.dss", - "settings" => "../test/data/ieee13_settings.json", - "events" => "../test/data/ieee13_events.json", - "apply-switch-scores" => true, - "skip" => ["faults", "stability", "dispatch"], - "opt-switch-solver" => "mip_solver" - ) - - @testset "test iterative optimal switching" begin - @testset "test iterative optimal switching - lindistflow" begin - args = deepcopy(orig_args) - args["opt-switch-formulation"] = "lindistflow" - args["opt-switch-algorithm"] = "iterative" - - entrypoint(args) - - @test get_timestep_device_actions!(args) == Dict{String, Any}[Dict("Shedded loads" => ["692_3", "675b", "675a", "692_1", "702", "703", "675c"], "Switch configurations" => Dict("801675" => "open", "671692" => "open", "671700" => "open", "703800" => "open", "800801" => "open", "701702" => "open")), Dict("Shedded loads" => ["692_3", "675b", "675a", "692_1", "702", "703", "675c"], "Switch configurations" => Dict("801675" => "open", "671692" => "open", "671700" => "closed", "703800" => "open", "800801" => "open", "701702" => "open")), Dict("Shedded loads" => ["702", "703"], "Switch configurations" => Dict("801675" => "closed", "671692" => "open", "671700" => "closed", "703800" => "open", "800801" => "open", "701702" => "open")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "closed", "703800" => "open", "800801" => "open", "701702" => "open")), Dict("Shedded loads" => ["801"], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "closed", "703800" => "open", "800801" => "open", "701702" => "closed")), Dict("Shedded loads" => ["801"], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "closed", "703800" => "open", "800801" => "closed", "701702" => "closed")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "closed", "703800" => "closed", "800801" => "closed", "701702" => "closed")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "closed", "703800" => "closed", "800801" => "closed", "701702" => "closed"))] - end - end - - @testset "test global optimal switching" begin - @testset "test global optimal switching - lindistflow" begin - args = deepcopy(orig_args) - args["opt-switch-algorithm"] = "global" - args["opt-switch-formulation"] = "lindistflow" - - entrypoint(args) - - @test get_timestep_device_actions!(args) == Dict{String, Any}[Dict("Shedded loads" => ["692_3", "675b", "675a", "692_1", "702", "703", "675c"], "Switch configurations" => Dict("801675" => "open", "671692" => "open", "671700" => "open", "703800" => "open", "800801" => "open", "701702" => "open")), Dict("Shedded loads" => ["692_3", "675b", "675a", "692_1", "702", "703", "675c"], "Switch configurations" => Dict("801675" => "open", "671692" => "open", "671700" => "closed", "703800" => "open", "800801" => "open", "701702" => "open")), Dict("Shedded loads" => ["702", "703"], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "closed", "703800" => "open", "800801" => "open", "701702" => "open")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "closed", "703800" => "open", "800801" => "open", "701702" => "closed")), Dict("Shedded loads" => ["801"], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "closed", "703800" => "open", "800801" => "closed", "701702" => "closed")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "closed", "703800" => "closed", "800801" => "closed", "701702" => "closed")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "closed", "703800" => "closed", "800801" => "closed", "701702" => "closed")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "closed", "703800" => "closed", "800801" => "closed", "701702" => "closed"))] - end - - @testset "test global optimal switching - nfa" begin - args = deepcopy(orig_args) - args["opt-switch-algorithm"] = "global" - args["opt-switch-formulation"] = "nfa" - - entrypoint(args) - - @test get_timestep_device_actions!(args) == Dict{String, Any}[Dict("Shedded loads" => ["692_3", "675b", "675a", "692_1", "702", "703", "675c"], "Switch configurations" => Dict("801675" => "open", "671692" => "open", "671700" => "open", "703800" => "open", "800801" => "open", "701702" => "open")), Dict("Shedded loads" => ["692_3", "675b", "675a", "692_1", "702", "703", "675c"], "Switch configurations" => Dict("801675" => "open", "671692" => "open", "671700" => "closed", "703800" => "open", "800801" => "open", "701702" => "open")), Dict("Shedded loads" => ["702", "703"], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "closed", "703800" => "open", "800801" => "open", "701702" => "open")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "closed", "703800" => "open", "800801" => "open", "701702" => "closed")), Dict("Shedded loads" => ["801"], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "closed", "703800" => "open", "800801" => "closed", "701702" => "closed")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "closed", "703800" => "closed", "800801" => "closed", "701702" => "closed")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "closed", "703800" => "closed", "800801" => "closed", "701702" => "closed")), Dict("Shedded loads" => String[], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "closed", "703800" => "closed", "800801" => "closed", "701702" => "closed"))] - end - end -end diff --git a/test/runtests.jl b/test/runtests.jl index 1e929521..a152a174 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -5,9 +5,35 @@ Distributed.addprocs(3) @everywhere using PowerModelsONM import JSON -import PowerModelsDistribution - -const PMD = PowerModelsDistribution +import PowerModelsDistribution as PMD + +import Juniper +import Ipopt +import HiGHS + +minlp_solver = optimizer_with_attributes( + Juniper.Optimizer, + "nl_solver" => optimizer_with_attributes(Ipopt.Optimizer, "tol"=>1e-6, "mu_strategy"=>"adaptive", "print_level"=>0), + "mip_solver" => optimizer_with_attributes( + HiGHS.Optimizer, + "presolve" => "off", + "primal_feasibility_tolerance" => 1e-6, + "dual_feasibility_tolerance" => 1e-6, + "mip_feasibility_tolerance" => 1e-6, + "mip_rel_gap" => 0.0001, + "small_matrix_value" => 1e-12, + "allow_unbounded_or_infeasible" => true, + "output_flag" => true, + "random_seed" => 1, + ), + "mip_gap" => 0.0001, + "atol" => 1e-6, + "allow_almost_solved_integral" => true, + "allow_almost_solved" => true, + "feasibility_pump" => true, + "seed" => 1, + "log_levels" => [:Info, :Table, :Options], +) using Test @@ -21,9 +47,11 @@ silence!() # inputs include("io.jl") include("data.jl") + include("graphml.jl") # problems - include("osw.jl") + include("mld.jl") + include("nlp.jl") include("opf.jl") include("faults.jl") include("stability.jl") diff --git a/test/stability.jl b/test/stability.jl index ca1d6143..5e006f16 100644 --- a/test/stability.jl +++ b/test/stability.jl @@ -3,7 +3,8 @@ "network" => "../test/data/ieee13_feeder.dss", "settings" => "../test/data/ieee13_settings.json", "inverters" => "../test/data/ieee13_inverters.json", - "skip" => ["dispatch", "switching", "faults"] + "skip" => ["dispatch", "switching", "faults"], + "quiet" => true, ) entrypoint(args) @@ -11,3 +12,31 @@ # TODO once more complex stability features are available, needs better tests @test all(!r for r in values(args["stability_results"])) end + +@testset "test small signal stability - single processor" begin + args = Dict{String,Any}( + "network" => "../test/data/ieee13_feeder.dss", + "settings" => "../test/data/ieee13_settings.json", + "inverters" => "../test/data/ieee13_inverters.json", + ) + prepare_data!(args) + set_setting!(args, ("options","problem","concurrent-stability-studies"), false) + build_solver_instances!(args) + + run_stability_analysis!(args) + + @test all(!r for r in values(args["stability_results"])) +end + +@testset "test small signal stability - no inverters" begin + args = Dict{String,Any}( + "network" => "../test/data/ieee13_feeder.dss", + "settings" => "../test/data/ieee13_settings.json", + ) + prepare_data!(args) + build_solver_instances!(args) + + run_stability_analysis!(args) + + @test all(!r for r in values(args["stability_results"])) +end diff --git a/test/stats.jl b/test/stats.jl index 84aeecf8..daa647fc 100644 --- a/test/stats.jl +++ b/test/stats.jl @@ -4,14 +4,14 @@ "events" => "../test/data/ieee13_events.json", "settings" => "../test/data/ieee13_settings.json", "inverters" => "../test/data/ieee13_inverters.json", - "output" => "test_output.json", - "pretty-print" => true, "faults" => "../test/data/ieee13_faults.json", - "apply-switch-scores" => true, + "disable-networking" => true, "opt-switch-algorithm" => "global", + "opt-switch-problem" => "block", "opt-switch-solver" => "mip_solver", + "opt-switch-formulation" => "lindistflow", "opt-disp-formulation" => "lindistflow", - "quiet" => true + "quiet" => true, ) args = entrypoint(deepcopy(orig_args)) @@ -21,11 +21,11 @@ end @testset "test action stats" begin - @test args["output_data"]["Device action timeline"] == Any[Dict{String, Any}("Shedded loads" => ["692_3", "675b", "675a", "692_1", "702", "703", "675c"], "Microgrid networks" => [["2"], ["4"], ["1"], ["3"]], "Switch configurations" => Dict("801675" => "open", "671692" => "open", "671700" => "open", "703800" => "open", "800801" => "open", "701702" => "open")), Dict{String, Any}("Shedded loads" => ["692_3", "675b", "675a", "692_1", "702", "703", "675c"], "Microgrid networks" => [["2"], ["4"], ["1"], ["3"]], "Switch configurations" => Dict("801675" => "open", "671692" => "open", "671700" => "closed", "703800" => "open", "800801" => "open", "701702" => "open")), Dict{String, Any}("Shedded loads" => ["702", "703"], "Microgrid networks" => [["2"], ["4"], ["1"], ["3"]], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "closed", "703800" => "open", "800801" => "open", "701702" => "open")), Dict{String, Any}("Shedded loads" => String[], "Microgrid networks" => [["2"], ["4"], ["1"], ["3"]], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "closed", "703800" => "open", "800801" => "open", "701702" => "closed")), Dict{String, Any}("Shedded loads" => ["801"], "Microgrid networks" => [["2"], ["4"], ["1"], ["3"]], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "closed", "703800" => "open", "800801" => "closed", "701702" => "closed")), Dict{String, Any}("Shedded loads" => String[], "Microgrid networks" => [["2"], ["4"], ["1"], ["3"]], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "closed", "703800" => "closed", "800801" => "closed", "701702" => "closed")), Dict{String, Any}("Shedded loads" => String[], "Microgrid networks" => [["2"], ["4"], ["1"], ["3"]], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "closed", "703800" => "closed", "800801" => "closed", "701702" => "closed")), Dict{String, Any}("Shedded loads" => String[], "Microgrid networks" => [["2"], ["4"], ["1"], ["3"]], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "closed", "703800" => "closed", "800801" => "closed", "701702" => "closed"))] + @test args["output_data"]["Device action timeline"] == Any[Dict{String, Any}("Shedded loads" => ["692_3", "675b", "675a", "692_1", "701", "702", "700", "703", "675c"], "Microgrid networks" => [["2"], ["4"], ["1"], ["3"]], "Switch configurations" => Dict("801675" => "open", "671692" => "open", "671700" => "open", "703800" => "open", "800801" => "open", "701702" => "open")), Dict{String, Any}("Shedded loads" => ["701", "702", "700", "703"], "Microgrid networks" => [["2"], ["4"], ["1"], ["3"]], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "open", "703800" => "open", "800801" => "open", "701702" => "open")), Dict{String, Any}("Shedded loads" => ["702", "801", "703"], "Microgrid networks" => [["2"], ["4"], ["1"], ["3"]], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "open", "703800" => "open", "800801" => "closed", "701702" => "open")), Dict{String, Any}("Shedded loads" => ["702", "801", "703"], "Microgrid networks" => [["2"], ["4"], ["1"], ["3"]], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "open", "703800" => "open", "800801" => "closed", "701702" => "open")), Dict{String, Any}("Shedded loads" => ["702", "801", "703"], "Microgrid networks" => [["2"], ["4"], ["1"], ["3"]], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "open", "703800" => "open", "800801" => "closed", "701702" => "open")), Dict{String, Any}("Shedded loads" => ["702", "801", "703"], "Microgrid networks" => [["2"], ["4"], ["1"], ["3"]], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "open", "703800" => "open", "800801" => "closed", "701702" => "open")), Dict{String, Any}("Shedded loads" => ["702", "703"], "Microgrid networks" => [["2"], ["4"], ["1"], ["3"]], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "open", "703800" => "open", "800801" => "closed", "701702" => "open")), Dict{String, Any}("Shedded loads" => ["702", "703"], "Microgrid networks" => [["2"], ["4"], ["1"], ["3"]], "Switch configurations" => Dict("801675" => "open", "671692" => "closed", "671700" => "open", "703800" => "open", "800801" => "closed", "701702" => "open"))] - @test args["output_data"]["Switch changes"] == [String[], ["671700"], ["671692"], ["701702"], ["800801"], ["703800"], String[], String[]] + @test args["output_data"]["Switch changes"] == [String[], ["671692"], ["800801"], String[], String[], String[], String[], String[]] - @test all(isapprox.(metadata["mip_gap"], 0.0; atol=1e-4) for metadata in args["output_data"]["Optimal switching metadata"]) + @test all(isapprox(metadata["mip_gap"], 0.0; atol=1e-4) for metadata in args["output_data"]["Optimal switching metadata"]) end @testset "test dispatch stats" begin @@ -35,13 +35,13 @@ @test all(isapprox.(args["output_data"]["Powerflow output"][1]["voltage_source"]["source"]["real power setpoint (kW)"], [756.4, 775.4, 780.2]; atol=1e0)) @test all(isapprox.(args["output_data"]["Powerflow output"][1]["voltage_source"]["source"]["reactive power setpoint (kVar)"], [437.1, 420.6, 445.4]; atol=1e0)) - @test all(isapprox.(args["output_data"]["Powerflow output"][3]["solar"]["pv_mg1b"]["real power setpoint (kW)"], [2.33, 2.33, 2.33]; atol=1e-1)) - @test all(isapprox.(args["output_data"]["Powerflow output"][3]["solar"]["pv_mg1b"]["reactive power setpoint (kVar)"], [-2.35, -2.35, -2.35]; atol=1e-1)) + @test all(isapprox.(args["output_data"]["Powerflow output"][3]["solar"]["pv_mg1b"]["real power setpoint (kW)"], [4.66588, 4.66588, 4.66588]; atol=1e-1)) + @test args["output_data"]["Powerflow output"][3]["solar"]["pv_mg1b"]["inverter"] == "GRID_FOLLOWING" @test all(isapprox.(args["output_data"]["Powerflow output"][1]["switch"]["671692"]["real power flow (kW)"], [0.0, 0.0, 0.0]; atol=1e-1)) @test all(isapprox.(args["output_data"]["Powerflow output"][1]["switch"]["671692"]["reactive power flow (kVar)"], [0.0, 0.0, 0.0]; atol=1e-1)) - @test all(isapprox.(args["output_data"]["Powerflow output"][7]["switch"]["703800"]["voltage (V)"], args["output_data"]["Powerflow output"][7]["bus"]["800"]["voltage (V)"]; atol=1e-4)) + @test all(isapprox.(args["output_data"]["Powerflow output"][7]["switch"]["703800"]["voltage (V)"], args["output_data"]["Powerflow output"][7]["bus"]["703"]["voltage (V)"]; atol=1e-4)) @test args["output_data"]["Optimal dispatch metadata"]["termination_status"] == "LOCALLY_SOLVED" @@ -49,21 +49,21 @@ end @testset "test fault stats" begin - @test all(isempty(args["output_data"]["Fault studies metadata"][i]) for i in 1:3) - @test all(!isempty(args["output_data"]["Fault studies metadata"][i]) for i in 4:8) + @test all(isempty(args["output_data"]["Fault studies metadata"][i]) for i in 1:1) + @test all(!isempty(args["output_data"]["Fault studies metadata"][i]) for i in 2:8) end @testset "test microgrid stats" begin - @test all(isapprox.(args["output_data"]["Storage SOC (%)"], [36.4, 34.8, 26.8, 15.6, 59.6, 80.0, 100.0, 100.0]; atol=1e0)) + @test all(isapprox.(args["output_data"]["Storage SOC (%)"], [38.4, 32.0, 41.92, 43.72, 45.16, 45.2, 28.4, 14.0]; atol=1e0)) - @test all(isapprox.(args["output_data"]["Load served"]["Bonus load via microgrid (%)"], [0.0, 0.0, 6.83669, 9.16209, 8.48947, 1.61033, 0.596868, 7.76111]; atol=1e-1)) - @test all(isapprox.(args["output_data"]["Load served"]["Feeder load (%)"], [94.2578, 94.2376, 87.4482, 84.8377, 85.5033, 92.4015, 93.4218, 86.3617]; atol=1e-1)) - @test all(isapprox.(args["output_data"]["Load served"]["Microgrid load (%)"], [14.2464, 17.9733, 73.7795, 92.0362, 73.6313, 93.4418, 92.0706, 92.3986]; atol=1e-1)) + @test all(isapprox.(args["output_data"]["Load served"]["Bonus load via microgrid (%)"], [0.0, 8.79711, 8.79711, 7.7496, 7.7496, 8.05424, 8.05424, 8.40272]; atol=1e-1)) + @test all(isapprox.(args["output_data"]["Load served"]["Feeder load (%)"], [94.2578, 85.4332, 85.4332, 86.2254, 86.2254, 86.0053, 86.0053, 85.7404]; atol=1e-1)) + @test all(isapprox.(args["output_data"]["Load served"]["Microgrid load (%)"], [9.49758, 75.4587, 51.9408, 64.7275, 61.1883, 53.406, 82.2704, 81.8946]; atol=1e-1)) - @test all(isapprox.(args["output_data"]["Generator profiles"]["Diesel DG (kW)"], [0.0, 0.0, 248.33, 267.026, 267.681, 279.225, 271.254, 257.449]; atol=1e0)) - @test all(isapprox.(args["output_data"]["Generator profiles"]["Energy storage (kW)"], [75.0, 20.0, 100.0, 140.0, -59.9999, -255.0, -250.0, -5.70086e-5]; atol=1e0)) - @test all(isapprox.(args["output_data"]["Generator profiles"]["Solar DG (kW)"], [0.0, 0.0, 6.99746, 124.757, 99.5236, 37.179, 0.0, 0.0]; atol=1e0)) - @test all(isapprox.(args["output_data"]["Generator profiles"]["Grid mix (kW)"], [2312.14, 2396.65, 2498.43, 3072.2, 3094.07, 3523.36, 3326.71, 2864.77]; atol=1e1)) + @test all(isapprox.(args["output_data"]["Generator profiles"]["Diesel DG (kW)"], [0.0, 249.479, 249.479, 264.355, 264.355, 259.293, 259.293, 254.398]; atol=1e0)) + @test all(isapprox.(args["output_data"]["Generator profiles"]["Energy storage (kW)"], [50.0, 80.0, 5.99997, -22.5, -18.0, -0.50003, 210.0, 180.0]; atol=1e0)) + @test all(isapprox.(args["output_data"]["Generator profiles"]["Solar DG (kW)"], [0.0, 0.0, 14.0, 35.0, 28.0, 10.5, 0.0, 0.0]; atol=1e0)) + @test all(isapprox.(args["output_data"]["Generator profiles"]["Grid mix (kW)"], [2312.14, 2422.82, 2422.82, 2941.33, 2941.33, 2768.79, 2768.79, 2595.85]; atol=1e1)) end @testset "test stability stats" begin @@ -79,4 +79,13 @@ @test isa(_args["events"], Dict{String,Any}) && isempty(_args["events"]) end + + @testset "test missing output_data" begin + _args = deepcopy(orig_args) + delete!(_args, "output_data") + + analyze_results!(_args) + + @test haskey(_args, "output_data") && !isempty(_args["output_data"]) + end end