From 5b04b56fe26cac02519b15b582684d11e09e1788 Mon Sep 17 00:00:00 2001 From: lixiangk1 <72464565+lixiangk1@users.noreply.github.com> Date: Tue, 23 Jan 2024 13:17:19 -0700 Subject: [PATCH 01/11] Add per kW and per kWh installed capacity based battery O&M --- src/core/energy_storage/electric_storage.jl | 8 ++++++++ src/core/reopt.jl | 8 +++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/core/energy_storage/electric_storage.jl b/src/core/energy_storage/electric_storage.jl index 2d84bd281..26ea93737 100644 --- a/src/core/energy_storage/electric_storage.jl +++ b/src/core/energy_storage/electric_storage.jl @@ -150,6 +150,8 @@ end installed_cost_per_kwh::Real = 455.0 replace_cost_per_kw::Real = 715.0 replace_cost_per_kwh::Real = 318.0 + om_cost_per_kw::Real = 0.0 + om_cost_per_kwh::Real = 0.0 inverter_replacement_year::Int = 10 battery_replacement_year::Int = 10 macrs_option_years::Int = 7 @@ -182,6 +184,8 @@ Base.@kwdef struct ElectricStorageDefaults installed_cost_per_kwh::Real = 455.0 replace_cost_per_kw::Real = 715.0 replace_cost_per_kwh::Real = 318.0 + om_cost_per_kw::Real = 0.0 + om_cost_per_kwh::Real = 0.0 inverter_replacement_year::Int = 10 battery_replacement_year::Int = 10 macrs_option_years::Int = 7 @@ -220,6 +224,8 @@ struct ElectricStorage <: AbstractElectricStorage installed_cost_per_kwh::Real replace_cost_per_kw::Real replace_cost_per_kwh::Real + om_cost_per_kw::Real + om_cost_per_kwh::Real inverter_replacement_year::Int battery_replacement_year::Int macrs_option_years::Int @@ -307,6 +313,8 @@ struct ElectricStorage <: AbstractElectricStorage s.installed_cost_per_kwh, replace_cost_per_kw, replace_cost_per_kwh, + s.om_cost_per_kw, + s.om_cost_per_kwh, s.inverter_replacement_year, s.battery_replacement_year, s.macrs_option_years, diff --git a/src/core/reopt.jl b/src/core/reopt.jl index 17d2b1082..11a8b096d 100644 --- a/src/core/reopt.jl +++ b/src/core/reopt.jl @@ -362,9 +362,11 @@ function build_reopt!(m::JuMP.AbstractModel, p::REoptInputs) sum( p.s.storage.attr[b].net_present_cost_per_kwh * m[:dvStorageEnergy][b] for b in p.s.storage.types.all ) )) - @expression(m, TotalPerUnitSizeOMCosts, p.third_party_factor * p.pwf_om * - sum( p.om_cost_per_kw[t] * m[:dvSize][t] for t in p.techs.all ) - ) + @expression(m, TotalPerUnitSizeOMCosts, p.third_party_factor * p.pwf_om * ( + sum(p.om_cost_per_kw[t] * m[:dvSize][t] for t in p.techs.all) + + sum(p.s.storage.attr[b].om_cost_per_kw * m[:dvStoragePower][b] for b in p.s.storage.types.elec) + + sum(p.s.storage.attr[b].om_cost_per_kwh * m[:dvStorageEnergy][b] for b in p.s.storage.types.elec) + )) add_elec_utility_expressions(m, p) From 8627a8d500de4a24af635dc3124e4482711bb8e1 Mon Sep 17 00:00:00 2001 From: lixiangk1 <72464565+lixiangk1@users.noreply.github.com> Date: Wed, 24 Jan 2024 11:20:16 -0700 Subject: [PATCH 02/11] Add O&M financials to ElectricStorage results --- src/results/electric_storage.jl | 6 + test/Manifest.toml | 391 ++++++++++++++++++++++++++++++++ 2 files changed, 397 insertions(+) create mode 100644 test/Manifest.toml diff --git a/src/results/electric_storage.jl b/src/results/electric_storage.jl index 4c2b1483c..ac24ee7c1 100644 --- a/src/results/electric_storage.jl +++ b/src/results/electric_storage.jl @@ -34,6 +34,12 @@ function add_electric_storage_results(m::JuMP.AbstractModel, p::REoptInputs, d:: r["initial_capital_cost"] = r["size_kwh"] * p.s.storage.attr[b].installed_cost_per_kwh + r["size_kw"] * p.s.storage.attr[b].installed_cost_per_kw + StoragePerUnitOMCosts = p.third_party_factor * p.pwf_om * (p.s.storage.attr[b].om_cost_per_kw * m[Symbol("dvStoragePower")][b] + + p.s.storage.attr[b].om_cost_per_kwh * m[Symbol("dvStorageEnergy")][b]) + + r["lifecycle_om_cost_after_tax"] = round(value(StoragePerUnitOMCosts) * (1 - p.s.financial.owner_tax_rate_fraction), digits=0) + r["year_one_om_cost_before_tax"] = round(value(StoragePerUnitOMCosts) / (p.pwf_om * p.third_party_factor), digits=0) + if p.s.storage.attr[b].model_degradation r["state_of_health"] = value.(m[:SOH]).data / value.(m[:dvStorageEnergy])["ElectricStorage"]; r["maintenance_cost"] = value(m[:degr_cost]) diff --git a/test/Manifest.toml b/test/Manifest.toml new file mode 100644 index 000000000..436a81cf2 --- /dev/null +++ b/test/Manifest.toml @@ -0,0 +1,391 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.8.0" +manifest_format = "2.0" +project_hash = "cf327925f9ed4a23852062425dc15cc7a29452ac" + +[[deps.ArgTools]] +uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" +version = "1.1.1" + +[[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 = "f1f03a9fa24271160ed7e73051fba3c1a759b53f" +uuid = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +version = "1.4.0" + +[[deps.Bzip2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "9e2a6b69137e6969bab0152632dcb3bc108c8bdd" +uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0" +version = "1.0.8+1" + +[[deps.ChainRulesCore]] +deps = ["Compat", "LinearAlgebra", "SparseArrays"] +git-tree-sha1 = "c1deebd76f7a443d527fc0430d5758b8b2112ed8" +uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" +version = "1.19.1" + +[[deps.ChangesOfVariables]] +deps = ["InverseFunctions", "LinearAlgebra", "Test"] +git-tree-sha1 = "2fba81a302a7be671aefe194f0525ef231104e7f" +uuid = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0" +version = "0.1.8" + +[[deps.CodecBzip2]] +deps = ["Bzip2_jll", "Libdl", "TranscodingStreams"] +git-tree-sha1 = "c0ae2a86b162fb5d7acc65269b469ff5b8a73594" +uuid = "523fee87-0ab8-5b00-afb7-3ecf72e48cfd" +version = "0.8.1" + +[[deps.CodecZlib]] +deps = ["TranscodingStreams", "Zlib_jll"] +git-tree-sha1 = "cd67fc487743b2f0fd4380d4cbd3a24660d0eec8" +uuid = "944b1d66-785c-5afd-91f1-9de20f533193" +version = "0.7.3" + +[[deps.CommonSubexpressions]] +deps = ["MacroTools", "Test"] +git-tree-sha1 = "7b8a93dba8af7e3b42fecabf646260105ac373f7" +uuid = "bbf7d656-a473-5ed7-a52c-81e309532950" +version = "0.3.0" + +[[deps.Compat]] +deps = ["Dates", "LinearAlgebra", "TOML", "UUIDs"] +git-tree-sha1 = "75bd5b6fc5089df449b5d35fa501c846c9b6549b" +uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" +version = "4.12.0" + +[[deps.CompilerSupportLibraries_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" +version = "0.5.2+0" + +[[deps.DataStructures]] +deps = ["Compat", "InteractiveUtils", "OrderedCollections"] +git-tree-sha1 = "ac67408d9ddf207de5cfa9a97e114352430f01ed" +uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" +version = "0.18.16" + +[[deps.Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[deps.DelimitedFiles]] +deps = ["Mmap"] +uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab" + +[[deps.DiffResults]] +deps = ["StaticArraysCore"] +git-tree-sha1 = "782dd5f4561f5d267313f23853baaaa4c52ea621" +uuid = "163ba53b-c6d8-5494-b064-1a9d43ac40c5" +version = "1.1.0" + +[[deps.DiffRules]] +deps = ["IrrationalConstants", "LogExpFunctions", "NaNMath", "Random", "SpecialFunctions"] +git-tree-sha1 = "23163d55f885173722d1e4cf0f6110cdbaf7e272" +uuid = "b552c78f-8df3-52c6-915a-8e097449b14b" +version = "1.15.1" + +[[deps.DocStringExtensions]] +deps = ["LibGit2"] +git-tree-sha1 = "2fb1e02f2b635d0845df5d7c167fec4dd739b00d" +uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +version = "0.9.3" + +[[deps.DotEnv]] +git-tree-sha1 = "d48ae0052378d697f8caf0855c4df2c54a97e580" +uuid = "4dc1fcf4-5e3b-5448-94ab-0c38ec0385c1" +version = "0.3.1" + +[[deps.Downloads]] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.6.0" + +[[deps.FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" + +[[deps.ForwardDiff]] +deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "LinearAlgebra", "LogExpFunctions", "NaNMath", "Preferences", "Printf", "Random", "SpecialFunctions", "StaticArrays"] +git-tree-sha1 = "cf0fe81336da9fb90944683b8c41984b08793dad" +uuid = "f6369f11-7733-5829-9624-2563aa707210" +version = "0.10.36" + +[[deps.HiGHS]] +deps = ["HiGHS_jll", "MathOptInterface", "PrecompileTools", "SparseArrays"] +git-tree-sha1 = "2b650f670ad691b94f506e88b4f3ab591a13f809" +uuid = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" +version = "1.8.0" + +[[deps.HiGHS_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"] +git-tree-sha1 = "10bf0ecdf70f643bfc1948a6af0a98be3950a3fc" +uuid = "8fd58aa0-07eb-5a78-9b36-339c94fd15ea" +version = "1.6.0+0" + +[[deps.InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" + +[[deps.InverseFunctions]] +deps = ["Test"] +git-tree-sha1 = "68772f49f54b479fa88ace904f6127f0a3bb2e46" +uuid = "3587e190-3f89-42d0-90ee-14403ec27112" +version = "0.1.12" + +[[deps.IrrationalConstants]] +git-tree-sha1 = "630b497eafcc20001bba38a4651b327dcfc491d2" +uuid = "92d709cd-6900-40b7-9082-c6be49f344b6" +version = "0.2.2" + +[[deps.JLLWrappers]] +deps = ["Artifacts", "Preferences"] +git-tree-sha1 = "7e5d6779a1e09a36db2a7b6cff50942a0a7d0fca" +uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" +version = "1.5.0" + +[[deps.JSON]] +deps = ["Dates", "Mmap", "Parsers", "Unicode"] +git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "0.21.4" + +[[deps.JuMP]] +deps = ["LinearAlgebra", "MacroTools", "MathOptInterface", "MutableArithmetics", "OrderedCollections", "PrecompileTools", "Printf", "SparseArrays"] +git-tree-sha1 = "adef1dfbafeab635664fb3249b58be8d290ed49d" +uuid = "4076af6c-e467-56ae-b986-b466b2749572" +version = "1.18.1" + +[[deps.LibCURL]] +deps = ["LibCURL_jll", "MozillaCACerts_jll"] +uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" +version = "0.6.3" + +[[deps.LibCURL_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] +uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" +version = "7.84.0+0" + +[[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" +version = "1.10.2+0" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" + +[[deps.LinearAlgebra]] +deps = ["Libdl", "libblastrampoline_jll"] +uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" + +[[deps.LogExpFunctions]] +deps = ["ChainRulesCore", "ChangesOfVariables", "DocStringExtensions", "InverseFunctions", "IrrationalConstants", "LinearAlgebra"] +git-tree-sha1 = "7d6dd4e9212aebaeed356de34ccf262a3cd415aa" +uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688" +version = "0.3.26" + +[[deps.Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" + +[[deps.MacroTools]] +deps = ["Markdown", "Random"] +git-tree-sha1 = "2fa9ee3e63fd3a4f7a9a4f4744a52f4856de82df" +uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +version = "0.5.13" + +[[deps.Markdown]] +deps = ["Base64"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" + +[[deps.MathOptInterface]] +deps = ["BenchmarkTools", "CodecBzip2", "CodecZlib", "DataStructures", "ForwardDiff", "JSON", "LinearAlgebra", "MutableArithmetics", "NaNMath", "OrderedCollections", "PrecompileTools", "Printf", "SparseArrays", "SpecialFunctions", "Test", "Unicode"] +git-tree-sha1 = "e2ae8cf5ac6daf5a3959f7f6ded9c2028b61d09d" +uuid = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" +version = "1.25.1" + +[[deps.MbedTLS_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" +version = "2.28.0+0" + +[[deps.Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" + +[[deps.MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2022.2.1" + +[[deps.MutableArithmetics]] +deps = ["LinearAlgebra", "SparseArrays", "Test"] +git-tree-sha1 = "806eea990fb41f9b36f1253e5697aa645bf6a9f8" +uuid = "d8a4904e-b15c-11e9-3269-09a3773c0cb0" +version = "1.4.0" + +[[deps.NaNMath]] +deps = ["OpenLibm_jll"] +git-tree-sha1 = "0877504529a3e5c3343c6f8b4c0381e57e4387e4" +uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" +version = "1.0.2" + +[[deps.NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +version = "1.2.0" + +[[deps.OpenBLAS_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" +version = "0.3.20+0" + +[[deps.OpenLibm_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "05823500-19ac-5b8b-9628-191a04bc5112" +version = "0.8.1+0" + +[[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 = "dfdf5519f235516220579f949664f1bf44e741c5" +uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +version = "1.6.3" + +[[deps.Parsers]] +deps = ["Dates", "PrecompileTools", "UUIDs"] +git-tree-sha1 = "8489905bcdbcfac64d1daa51ca07c0d8f0283821" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "2.8.1" + +[[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" +version = "1.8.0" + +[[deps.PrecompileTools]] +deps = ["Preferences"] +git-tree-sha1 = "03b4c25b43cb84cee5c90aa9b5ea0a78fd848d2f" +uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +version = "1.2.0" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "00805cd429dcb4870060ff49ef443486c262e38e" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.4.1" + +[[deps.Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[deps.Profile]] +deps = ["Printf"] +uuid = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79" + +[[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.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" + +[[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 = "e2cfc4012a19088254b3950b85c3c1d8882d864d" +uuid = "276daf66-3868-5448-9aa4-cd146d93841b" +version = "2.3.1" + +[[deps.StaticArrays]] +deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore", "Statistics"] +git-tree-sha1 = "f68dd04d131d9a8a8eb836173ee8f105c360b0c5" +uuid = "90137ffa-7385-5640-81b9-e52037218182" +version = "1.9.1" + +[[deps.StaticArraysCore]] +git-tree-sha1 = "36b3d696ce6366023a0ea192b4cd442268995a0d" +uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" +version = "1.4.2" + +[[deps.Statistics]] +deps = ["LinearAlgebra", "SparseArrays"] +uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.0" + +[[deps.Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +version = "1.10.0" + +[[deps.Test]] +deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[[deps.TranscodingStreams]] +deps = ["Random", "Test"] +git-tree-sha1 = "1fbeaaca45801b4ba17c251dd8603ef24801dd84" +uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" +version = "0.10.2" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" + +[[deps.Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" + +[[deps.Xpress]] +deps = ["Libdl", "LinearAlgebra", "MathOptInterface", "SparseArrays"] +git-tree-sha1 = "29c47b54b6938852a598fc6761ed927aad61f10e" +uuid = "9e70acf3-d6c9-5be6-b5bd-4e2c73e3e054" +version = "0.16.2" + +[[deps.Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.2.12+3" + +[[deps.libblastrampoline_jll]] +deps = ["Artifacts", "Libdl", "OpenBLAS_jll"] +uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" +version = "5.1.1+0" + +[[deps.nghttp2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.48.0+0" + +[[deps.p7zip_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "17.4.0+0" From 564365f4f393f1e9ccc13119aa8101d88fedd537 Mon Sep 17 00:00:00 2001 From: lixiangk1 <72464565+lixiangk1@users.noreply.github.com> Date: Wed, 28 Feb 2024 21:26:17 -0700 Subject: [PATCH 03/11] Add battery self discharge, defaulted to zero --- src/constraints/storage_constraints.jl | 2 ++ src/core/energy_storage/electric_storage.jl | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/constraints/storage_constraints.jl b/src/constraints/storage_constraints.jl index e13c7d8fb..c7e5b493e 100644 --- a/src/constraints/storage_constraints.jl +++ b/src/constraints/storage_constraints.jl @@ -57,6 +57,7 @@ function add_elec_storage_dispatch_constraints(m, p, b; _n="") + p.s.storage.attr[b].grid_charge_efficiency * m[Symbol("dvGridToStorage"*_n)][b, ts] - m[Symbol("dvDischargeFromStorage"*_n)][b,ts] / p.s.storage.attr[b].discharge_efficiency ) + - ((p.s.storage.attr[b].daily_self_discharge_fraction/24/p.hours_per_time_step) * m[Symbol("dvStoredEnergy"*_n)][b, ts]) ) # Constraint (4h): state-of-charge for electrical storage - no grid @@ -65,6 +66,7 @@ function add_elec_storage_dispatch_constraints(m, p, b; _n="") sum(p.s.storage.attr[b].charge_efficiency * m[Symbol("dvProductionToStorage"*_n)][b,t,ts] for t in p.techs.elec) - m[Symbol("dvDischargeFromStorage"*_n)][b, ts] / p.s.storage.attr[b].discharge_efficiency ) + - ((p.s.storage.attr[b].daily_self_discharge_fraction/24/p.hours_per_time_step) * m[Symbol("dvStoredEnergy"*_n)][b, ts]) ) # Constraint (4i)-1: Dispatch to electrical storage is no greater than power capacity diff --git a/src/core/energy_storage/electric_storage.jl b/src/core/energy_storage/electric_storage.jl index 26ea93737..b496f1c2a 100644 --- a/src/core/energy_storage/electric_storage.jl +++ b/src/core/energy_storage/electric_storage.jl @@ -166,6 +166,7 @@ end model_degradation::Bool = false degradation::Dict = Dict() minimum_avg_soc_fraction::Float64 = 0.0 + daily_self_discharge_fraction::Float64 = 0.0 ``` """ Base.@kwdef struct ElectricStorageDefaults @@ -200,6 +201,7 @@ Base.@kwdef struct ElectricStorageDefaults model_degradation::Bool = false degradation::Dict = Dict() minimum_avg_soc_fraction::Float64 = 0.0 + daily_self_discharge_fraction::Float64 = 0.0 end @@ -242,6 +244,7 @@ struct ElectricStorage <: AbstractElectricStorage model_degradation::Bool degradation::Degradation minimum_avg_soc_fraction::Float64 + daily_self_discharge_fraction::Float64 function ElectricStorage(d::Dict, f::Financial) s = ElectricStorageDefaults(;d...) @@ -330,7 +333,8 @@ struct ElectricStorage <: AbstractElectricStorage net_present_cost_per_kwh, s.model_degradation, degr, - s.minimum_avg_soc_fraction + s.minimum_avg_soc_fraction, + s.daily_self_discharge_fraction ) end end From c2f3c5d8c29918055a77d35c9103b7eaf7a81711 Mon Sep 17 00:00:00 2001 From: lixiangk1 <72464565+lixiangk1@users.noreply.github.com> Date: Thu, 28 Mar 2024 17:54:40 -0600 Subject: [PATCH 04/11] Add battery self-discharge to MPC model --- src/mpc/structs.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mpc/structs.jl b/src/mpc/structs.jl index 14280665b..f6b8b8d1b 100644 --- a/src/mpc/structs.jl +++ b/src/mpc/structs.jl @@ -242,6 +242,7 @@ Base.@kwdef struct MPCElectricStorage <: AbstractElectricStorage max_kw::Float64 = size_kw max_kwh::Float64 = size_kwh minimum_avg_soc_fraction::Float64 = 0.0 + daily_self_discharge_fraction::Float64 = 0.0 end From 37e30836faa5b5f019371e993a681ab769167c5e Mon Sep 17 00:00:00 2001 From: lixiangk1 <72464565+lixiangk1@users.noreply.github.com> Date: Fri, 29 Mar 2024 11:45:16 -0600 Subject: [PATCH 05/11] Enable BESS O&M for multiple sites --- src/results/electric_storage.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/results/electric_storage.jl b/src/results/electric_storage.jl index ac24ee7c1..5dfba4d09 100644 --- a/src/results/electric_storage.jl +++ b/src/results/electric_storage.jl @@ -34,8 +34,8 @@ function add_electric_storage_results(m::JuMP.AbstractModel, p::REoptInputs, d:: r["initial_capital_cost"] = r["size_kwh"] * p.s.storage.attr[b].installed_cost_per_kwh + r["size_kw"] * p.s.storage.attr[b].installed_cost_per_kw - StoragePerUnitOMCosts = p.third_party_factor * p.pwf_om * (p.s.storage.attr[b].om_cost_per_kw * m[Symbol("dvStoragePower")][b] + - p.s.storage.attr[b].om_cost_per_kwh * m[Symbol("dvStorageEnergy")][b]) + StoragePerUnitOMCosts = p.third_party_factor * p.pwf_om * (p.s.storage.attr[b].om_cost_per_kw * m[Symbol("dvStoragePower"*_n)][b] + + p.s.storage.attr[b].om_cost_per_kwh * m[Symbol("dvStorageEnergy"*_n)][b]) r["lifecycle_om_cost_after_tax"] = round(value(StoragePerUnitOMCosts) * (1 - p.s.financial.owner_tax_rate_fraction), digits=0) r["year_one_om_cost_before_tax"] = round(value(StoragePerUnitOMCosts) / (p.pwf_om * p.third_party_factor), digits=0) From 9ee741ec42c0b4b1d103ff87902f8beb529b6363 Mon Sep 17 00:00:00 2001 From: lixiangk1 <72464565+lixiangk1@users.noreply.github.com> Date: Mon, 1 Apr 2024 20:25:31 -0600 Subject: [PATCH 06/11] Add code comments and tests for battery O&M and self-discharge; update the changelog --- CHANGELOG.md | 9 +++- src/core/energy_storage/electric_storage.jl | 8 ++-- test/runtests.jl | 52 +++++++++++++++++++++ test/scenarios/storage_om.json | 43 +++++++++++++++++ 4 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 test/scenarios/storage_om.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 8672c2ead..b1898e582 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,14 @@ Classify the change according to the following categories: ### Deprecated ### Removed -## Develop 2024-03-26 +## Develop 2024-04-01 +### Added +- Added new inputs **om_cost_per_kw** and **om_cost_per_kwh** to `ElectricStorage` for modeling capacity-based O&M +- Added new input **daily_self_discharge_fraction** to `ElectricStorage` for modeling battery self-discharge +- Added new outputs **lifecycle_om_cost_after_tax** and **year_one_om_cost_before_tax** to `ElectricStorage` +- Added testsets **Electric Storage O&M** and **Electric Storage Self-Discharge** in `test/runtests.jl` + +## v0.44.0 ### Added - in `src/settings.jl`, added new const **INDICATOR_COMPATIBLE_SOLVERS** - in `src/settings.jl`, added new member **solver_name** within the settings object. This is currently not connected to the solver but does determine whether indicator constraints are modeled or if their big-M workarounds are used. diff --git a/src/core/energy_storage/electric_storage.jl b/src/core/energy_storage/electric_storage.jl index c41995266..42ca244b8 100644 --- a/src/core/energy_storage/electric_storage.jl +++ b/src/core/energy_storage/electric_storage.jl @@ -150,8 +150,8 @@ end installed_cost_per_kwh::Real = 455.0 replace_cost_per_kw::Real = 715.0 replace_cost_per_kwh::Real = 318.0 - om_cost_per_kw::Real = 0.0 - om_cost_per_kwh::Real = 0.0 + om_cost_per_kw::Real = 0.0 # Capacity-based O&M costs in $/kW-rated/year. When including battery replacement costs, the user should ensure that O&M costs do not account for augmentation/replacement. + om_cost_per_kwh::Real = 0.0 # Capacity-based O&M costs in $/kWh-rated/year. When including battery replacement costs, the user should ensure that O&M costs do not account for augmentation/replacement. inverter_replacement_year::Int = 10 battery_replacement_year::Int = 10 macrs_option_years::Int = 7 @@ -166,8 +166,8 @@ end model_degradation::Bool = false degradation::Dict = Dict() minimum_avg_soc_fraction::Float64 = 0.0 - daily_self_discharge_fraction::Float64 = 0.0 -``` + daily_self_discharge_fraction::Float64 = 0.0 # Battery self-discharge as a fraction per day loss based on kWh stored in each timestep +``` """ Base.@kwdef struct ElectricStorageDefaults off_grid_flag::Bool = false diff --git a/test/runtests.jl b/test/runtests.jl index b59171d26..953e6c763 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -520,6 +520,58 @@ else # run HiGHS tests @test reliability_results["mean_cumulative_survival_final_time_step"] ≈ 0.817586 atol=0.001 end + @testset "Electric Storage O&M" begin + model = Model(optimizer_with_attributes(HiGHS.Optimizer, "output_flag" => false, "log_to_console" => false)) + data = JSON.parsefile("./scenarios/storage_om.json") + + data["ElectricStorage"]["om_cost_per_kw"] = 10 + data["ElectricStorage"]["om_cost_per_kwh"] = 0 + + s = Scenario(data) + inputs = REoptInputs(s) + results = run_reopt(model, inputs) + + @test results["ElectricStorage"]["year_one_om_cost_before_tax"] ≈ 400 atol=0.01 + @test results["ElectricStorage"]["lifecycle_om_cost_after_tax"] ≈ 6272 atol=0.01 + + data["ElectricStorage"]["om_cost_per_kw"] = 0 + data["ElectricStorage"]["om_cost_per_kwh"] = 5 + + model = Model(optimizer_with_attributes(HiGHS.Optimizer, "output_flag" => false, "log_to_console" => false)) + s = Scenario(data) + inputs = REoptInputs(s) + results = run_reopt(model, inputs) + + @test results["ElectricStorage"]["year_one_om_cost_before_tax"] ≈ 400 atol=0.01 + @test results["ElectricStorage"]["lifecycle_om_cost_after_tax"] ≈ 6272 atol=0.01 + end + + @testset "Electric Storage Self-Discharge" begin + model = Model(optimizer_with_attributes(HiGHS.Optimizer, "output_flag" => false, "log_to_console" => false)) + data = JSON.parsefile("./scenarios/storage_om.json") + + data["ElectricStorage"]["soc_init_fraction"] = 1.0 + data["ElectricStorage"]["soc_min_fraction"] = 0.0 + data["ElectricStorage"]["daily_self_discharge_fraction"] = 0.0025 + + s = Scenario(data) + inputs = REoptInputs(s) + results_self_discharge = run_reopt(model, inputs) + + model = Model(optimizer_with_attributes(HiGHS.Optimizer, "output_flag" => false, "log_to_console" => false)) + data["ElectricStorage"]["daily_self_discharge_fraction"] = 0.0 + + s = Scenario(data) + inputs = REoptInputs(s) + results_no_self_discharge = run_reopt(model, inputs) + + @test results_self_discharge["ElectricStorage"]["year_one_om_cost_before_tax"] ≈ 0 atol=0.01 + @test results_self_discharge["ElectricStorage"]["lifecycle_om_cost_after_tax"] ≈ 0 atol=0.01 + @test sum(results_no_self_discharge["ElectricStorage"]["storage_to_load_series_kw"]) - + sum(results_self_discharge["ElectricStorage"]["storage_to_load_series_kw"]) ≈ 15.382 atol=0.01 + + end + @testset "Imported Xpress Test Suite" begin @testset "Heating loads and addressable load fraction" begin # Default LargeOffice CRB with SpaceHeatingLoad and DomesticHotWaterLoad are served by ExistingBoiler diff --git a/test/scenarios/storage_om.json b/test/scenarios/storage_om.json new file mode 100644 index 000000000..167db02f9 --- /dev/null +++ b/test/scenarios/storage_om.json @@ -0,0 +1,43 @@ +{ + "Site": { + "longitude": -118.1164613, + "latitude": 34.5794343, + "roof_squarefeet": 5000.0, + "land_acres": 1.0 + }, + "ElectricLoad": { + "doe_reference_name": "RetailStore", + "annual_kwh": 10000000.0, + "year": 2017 + }, + "ElectricStorage": { + "min_kw": 40, + "max_kw": 40, + "min_kwh": 80, + "max_kwh": 80, + "can_grid_charge": false, + "replace_cost_per_kw": 460.0, + "replace_cost_per_kwh": 230.0, + "installed_cost_per_kw": 1000.0, + "installed_cost_per_kwh": 500.0, + "total_itc_fraction": 0.0, + "macrs_option_years": 0, + "macrs_bonus_fraction": 0 + }, + "ElectricTariff": { + "urdb_label": "5ed6c1a15457a3367add15ae" + }, + "PV": { + "min_kw": 0.0, + "max_kw": 0.0 + }, + "Financial": { + "elec_cost_escalation_rate_fraction": 0.026, + "offtaker_discount_rate_fraction": 0.05, + "owner_discount_rate_fraction": 0.05, + "analysis_years": 20, + "offtaker_tax_rate_fraction": 0, + "owner_tax_rate_fraction": 0, + "om_cost_escalation_rate_fraction": 0.025 + } +} From e2a0cacea0f8605278b28f5be1b9c643d6a502ce Mon Sep 17 00:00:00 2001 From: lixiangk1 <72464565+lixiangk1@users.noreply.github.com> Date: Tue, 2 Apr 2024 11:16:42 -0600 Subject: [PATCH 07/11] Update code comments --- src/core/energy_storage/electric_storage.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/energy_storage/electric_storage.jl b/src/core/energy_storage/electric_storage.jl index 42ca244b8..733d95fb4 100644 --- a/src/core/energy_storage/electric_storage.jl +++ b/src/core/energy_storage/electric_storage.jl @@ -150,8 +150,8 @@ end installed_cost_per_kwh::Real = 455.0 replace_cost_per_kw::Real = 715.0 replace_cost_per_kwh::Real = 318.0 - om_cost_per_kw::Real = 0.0 # Capacity-based O&M costs in $/kW-rated/year. When including battery replacement costs, the user should ensure that O&M costs do not account for augmentation/replacement. - om_cost_per_kwh::Real = 0.0 # Capacity-based O&M costs in $/kWh-rated/year. When including battery replacement costs, the user should ensure that O&M costs do not account for augmentation/replacement. + om_cost_per_kw::Real = 0.0 # Capacity-based O&M costs in \$/kW-rated/year. When including battery replacement costs, the user should ensure that O&M costs do not account for augmentation/replacement. + om_cost_per_kwh::Real = 0.0 # Capacity-based O&M costs in \$/kWh-rated/year. When including battery replacement costs, the user should ensure that O&M costs do not account for augmentation/replacement. inverter_replacement_year::Int = 10 battery_replacement_year::Int = 10 macrs_option_years::Int = 7 From 14f3ced04b5aa61f215e0a67808905993ad90bc2 Mon Sep 17 00:00:00 2001 From: lixiangk1 <72464565+lixiangk1@users.noreply.github.com> Date: Fri, 19 Apr 2024 11:52:13 -0600 Subject: [PATCH 08/11] Update storage self-discharge input from daily to per timestep --- CHANGELOG.md | 2 +- src/constraints/storage_constraints.jl | 4 ++-- src/core/energy_storage/electric_storage.jl | 8 ++++---- src/mpc/structs.jl | 2 +- test/runtests.jl | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16ab3477f..cb49beefe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ Classify the change according to the following categories: ## Develop 2024-04-19 ### Added - Added new inputs **om_cost_per_kw** and **om_cost_per_kwh** to `ElectricStorage` for modeling capacity-based O&M -- Added new input **daily_self_discharge_fraction** to `ElectricStorage` for modeling battery self-discharge +- Added new input **per_timestep_self_discharge_fraction** to `ElectricStorage` for modeling battery self-discharge - Added new outputs **lifecycle_om_cost_after_tax** and **year_one_om_cost_before_tax** to `ElectricStorage` - Added testsets **Electric Storage O&M** and **Electric Storage Self-Discharge** in `test/runtests.jl` diff --git a/src/constraints/storage_constraints.jl b/src/constraints/storage_constraints.jl index c7e5b493e..73ade5169 100644 --- a/src/constraints/storage_constraints.jl +++ b/src/constraints/storage_constraints.jl @@ -57,7 +57,7 @@ function add_elec_storage_dispatch_constraints(m, p, b; _n="") + p.s.storage.attr[b].grid_charge_efficiency * m[Symbol("dvGridToStorage"*_n)][b, ts] - m[Symbol("dvDischargeFromStorage"*_n)][b,ts] / p.s.storage.attr[b].discharge_efficiency ) - - ((p.s.storage.attr[b].daily_self_discharge_fraction/24/p.hours_per_time_step) * m[Symbol("dvStoredEnergy"*_n)][b, ts]) + - (p.s.storage.attr[b].per_timestep_self_discharge_fraction * m[Symbol("dvStoredEnergy"*_n)][b, ts]) ) # Constraint (4h): state-of-charge for electrical storage - no grid @@ -66,7 +66,7 @@ function add_elec_storage_dispatch_constraints(m, p, b; _n="") sum(p.s.storage.attr[b].charge_efficiency * m[Symbol("dvProductionToStorage"*_n)][b,t,ts] for t in p.techs.elec) - m[Symbol("dvDischargeFromStorage"*_n)][b, ts] / p.s.storage.attr[b].discharge_efficiency ) - - ((p.s.storage.attr[b].daily_self_discharge_fraction/24/p.hours_per_time_step) * m[Symbol("dvStoredEnergy"*_n)][b, ts]) + - (p.s.storage.attr[b].per_timestep_self_discharge_fraction * m[Symbol("dvStoredEnergy"*_n)][b, ts]) ) # Constraint (4i)-1: Dispatch to electrical storage is no greater than power capacity diff --git a/src/core/energy_storage/electric_storage.jl b/src/core/energy_storage/electric_storage.jl index 64780e7cc..8e3f31d49 100644 --- a/src/core/energy_storage/electric_storage.jl +++ b/src/core/energy_storage/electric_storage.jl @@ -167,7 +167,7 @@ end model_degradation::Bool = false degradation::Dict = Dict() minimum_avg_soc_fraction::Float64 = 0.0 - daily_self_discharge_fraction::Float64 = 0.0 # Battery self-discharge as a fraction per day loss based on kWh stored in each timestep + per_timestep_self_discharge_fraction::Float64 = 0.0 # Battery self-discharge as a fraction per timestep loss based on kWh stored in each timestep ``` """ Base.@kwdef struct ElectricStorageDefaults @@ -203,7 +203,7 @@ Base.@kwdef struct ElectricStorageDefaults model_degradation::Bool = false degradation::Dict = Dict() minimum_avg_soc_fraction::Float64 = 0.0 - daily_self_discharge_fraction::Float64 = 0.0 + per_timestep_self_discharge_fraction::Float64 = 0.0 end @@ -247,7 +247,7 @@ struct ElectricStorage <: AbstractElectricStorage model_degradation::Bool degradation::Degradation minimum_avg_soc_fraction::Float64 - daily_self_discharge_fraction::Float64 + per_timestep_self_discharge_fraction::Float64 function ElectricStorage(d::Dict, f::Financial) s = ElectricStorageDefaults(;d...) @@ -338,7 +338,7 @@ struct ElectricStorage <: AbstractElectricStorage s.model_degradation, degr, s.minimum_avg_soc_fraction, - s.daily_self_discharge_fraction + s.per_timestep_self_discharge_fraction ) end end diff --git a/src/mpc/structs.jl b/src/mpc/structs.jl index 6981a09c0..b8e16d244 100644 --- a/src/mpc/structs.jl +++ b/src/mpc/structs.jl @@ -242,7 +242,7 @@ Base.@kwdef struct MPCElectricStorage <: AbstractElectricStorage max_kw::Float64 = size_kw max_kwh::Float64 = size_kwh minimum_avg_soc_fraction::Float64 = 0.0 - daily_self_discharge_fraction::Float64 = 0.0 + per_timestep_self_discharge_fraction::Float64 = 0.0 end diff --git a/test/runtests.jl b/test/runtests.jl index d466a733b..e224b0785 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -552,14 +552,14 @@ else # run HiGHS tests data["ElectricStorage"]["soc_init_fraction"] = 1.0 data["ElectricStorage"]["soc_min_fraction"] = 0.0 - data["ElectricStorage"]["daily_self_discharge_fraction"] = 0.0025 + data["ElectricStorage"]["per_timestep_self_discharge_fraction"] = 0.0025/24 s = Scenario(data) inputs = REoptInputs(s) results_self_discharge = run_reopt(model, inputs) model = Model(optimizer_with_attributes(HiGHS.Optimizer, "output_flag" => false, "log_to_console" => false)) - data["ElectricStorage"]["daily_self_discharge_fraction"] = 0.0 + data["ElectricStorage"]["per_timestep_self_discharge_fraction"] = 0.0 s = Scenario(data) inputs = REoptInputs(s) From 5eea67e32f026b78707d713b441ac4416fa908fe Mon Sep 17 00:00:00 2001 From: lixiangk1 <72464565+lixiangk1@users.noreply.github.com> Date: Mon, 8 Jul 2024 22:01:50 -0600 Subject: [PATCH 09/11] Add capacity-based self-discharge, update tests --- CHANGELOG.md | 3 ++- src/constraints/storage_constraints.jl | 6 ++++-- src/core/energy_storage/electric_storage.jl | 12 +++++++---- src/mpc/structs.jl | 3 ++- test/runtests.jl | 22 +++++++++++++++------ 5 files changed, 32 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e37b28a8..e09487631 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,8 @@ Classify the change according to the following categories: ## Develop 2024-07-6 ### Added - Added new inputs **om_cost_per_kw** and **om_cost_per_kwh** to `ElectricStorage` for modeling capacity-based O&M -- Added new input **per_timestep_self_discharge_fraction** to `ElectricStorage` for modeling battery self-discharge +- Added new input **soc_based_per_ts_self_discharge_fraction** to `ElectricStorage` for modeling SOC-based battery self-discharge +- Added new input **capacity_based_per_ts_self_discharge_fraction** to `ElectricStorage` for modeling capacity-based battery self-discharge - Added new outputs **lifecycle_om_cost_after_tax** and **year_one_om_cost_before_tax** to `ElectricStorage` - Added testsets **Electric Storage O&M** and **Electric Storage Self-Discharge** in `test/runtests.jl` diff --git a/src/constraints/storage_constraints.jl b/src/constraints/storage_constraints.jl index 33270d5f2..1a38ab58e 100644 --- a/src/constraints/storage_constraints.jl +++ b/src/constraints/storage_constraints.jl @@ -57,7 +57,8 @@ function add_elec_storage_dispatch_constraints(m, p, b; _n="") + p.s.storage.attr[b].grid_charge_efficiency * m[Symbol("dvGridToStorage"*_n)][b, ts] - m[Symbol("dvDischargeFromStorage"*_n)][b,ts] / p.s.storage.attr[b].discharge_efficiency ) - - (p.s.storage.attr[b].per_timestep_self_discharge_fraction * m[Symbol("dvStoredEnergy"*_n)][b, ts]) + - (p.s.storage.attr[b].soc_based_per_ts_self_discharge_fraction * m[Symbol("dvStoredEnergy"*_n)][b, ts]) + - (p.s.storage.attr[b].capacity_based_per_ts_self_discharge_fraction * m[Symbol("dvStorageEnergy"*_n)][b]) ) # Constraint (4h): state-of-charge for electrical storage - no grid @@ -66,7 +67,8 @@ function add_elec_storage_dispatch_constraints(m, p, b; _n="") sum(p.s.storage.attr[b].charge_efficiency * m[Symbol("dvProductionToStorage"*_n)][b,t,ts] for t in p.techs.elec) - m[Symbol("dvDischargeFromStorage"*_n)][b, ts] / p.s.storage.attr[b].discharge_efficiency ) - - (p.s.storage.attr[b].per_timestep_self_discharge_fraction * m[Symbol("dvStoredEnergy"*_n)][b, ts]) + - (p.s.storage.attr[b].soc_based_per_ts_self_discharge_fraction * m[Symbol("dvStoredEnergy"*_n)][b, ts]) + - (p.s.storage.attr[b].capacity_based_per_ts_self_discharge_fraction * m[Symbol("dvStorageEnergy"*_n)][b]) ) # Constraint (4i)-1: Dispatch to electrical storage is no greater than power capacity diff --git a/src/core/energy_storage/electric_storage.jl b/src/core/energy_storage/electric_storage.jl index a21ba780d..a99f0e3af 100644 --- a/src/core/energy_storage/electric_storage.jl +++ b/src/core/energy_storage/electric_storage.jl @@ -167,7 +167,8 @@ end model_degradation::Bool = false degradation::Dict = Dict() minimum_avg_soc_fraction::Float64 = 0.0 - per_timestep_self_discharge_fraction::Float64 = 0.0 # Battery self-discharge as a fraction per timestep loss based on kWh stored in each timestep + capacity_based_per_ts_self_discharge_fraction::Float64 = 0.0 # Battery self-discharge as a fraction per timestep loss based on the rated kWh capacity of the sized storage system. + soc_based_per_ts_self_discharge_fraction::Float64 = 0.0 # Battery self-discharge as a fraction per timestep loss based on kWh stored in each timestep ``` """ Base.@kwdef struct ElectricStorageDefaults @@ -203,7 +204,8 @@ Base.@kwdef struct ElectricStorageDefaults model_degradation::Bool = false degradation::Dict = Dict() minimum_avg_soc_fraction::Float64 = 0.0 - per_timestep_self_discharge_fraction::Float64 = 0.0 + capacity_based_per_ts_self_discharge_fraction::Float64 = 0.0 + soc_based_per_ts_self_discharge_fraction::Float64 = 0.0 end @@ -247,7 +249,8 @@ struct ElectricStorage <: AbstractElectricStorage model_degradation::Bool degradation::Degradation minimum_avg_soc_fraction::Float64 - per_timestep_self_discharge_fraction::Float64 + capacity_based_per_ts_self_discharge_fraction::Float64 + soc_based_per_ts_self_discharge_fraction::Float64 function ElectricStorage(d::Dict, f::Financial) s = ElectricStorageDefaults(;d...) @@ -338,7 +341,8 @@ struct ElectricStorage <: AbstractElectricStorage s.model_degradation, degr, s.minimum_avg_soc_fraction, - s.per_timestep_self_discharge_fraction + s.capacity_based_per_ts_self_discharge_fraction, + s.soc_based_per_ts_self_discharge_fraction ) end end diff --git a/src/mpc/structs.jl b/src/mpc/structs.jl index b8e16d244..0c725d324 100644 --- a/src/mpc/structs.jl +++ b/src/mpc/structs.jl @@ -242,7 +242,8 @@ Base.@kwdef struct MPCElectricStorage <: AbstractElectricStorage max_kw::Float64 = size_kw max_kwh::Float64 = size_kwh minimum_avg_soc_fraction::Float64 = 0.0 - per_timestep_self_discharge_fraction::Float64 = 0.0 + capacity_based_per_ts_self_discharge_fraction::Float64 = 0.0 + soc_based_per_ts_self_discharge_fraction::Float64 = 0.0 end diff --git a/test/runtests.jl b/test/runtests.jl index 6beec90c7..be81c5040 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -589,23 +589,33 @@ else # run HiGHS tests data["ElectricStorage"]["soc_init_fraction"] = 1.0 data["ElectricStorage"]["soc_min_fraction"] = 0.0 - data["ElectricStorage"]["per_timestep_self_discharge_fraction"] = 0.0025/24 + data["ElectricStorage"]["soc_based_per_ts_self_discharge_fraction"] = 0.0025/24 s = Scenario(data) inputs = REoptInputs(s) - results_self_discharge = run_reopt(model, inputs) + results_soc_based_self_discharge = run_reopt(model, inputs) model = Model(optimizer_with_attributes(HiGHS.Optimizer, "output_flag" => false, "log_to_console" => false)) - data["ElectricStorage"]["per_timestep_self_discharge_fraction"] = 0.0 + data["ElectricStorage"]["soc_based_per_ts_self_discharge_fraction"] = 0.0 s = Scenario(data) inputs = REoptInputs(s) results_no_self_discharge = run_reopt(model, inputs) - @test results_self_discharge["ElectricStorage"]["year_one_om_cost_before_tax"] ≈ 0 atol=0.01 - @test results_self_discharge["ElectricStorage"]["lifecycle_om_cost_after_tax"] ≈ 0 atol=0.01 + model = Model(optimizer_with_attributes(HiGHS.Optimizer, "output_flag" => false, "log_to_console" => false)) + data["ElectricStorage"]["capacity_based_per_ts_self_discharge_fraction"] = 0.0025/24 + + s = Scenario(data) + inputs = REoptInputs(s) + results_capacity_based_self_discharge = run_reopt(model, inputs) + + @test results_soc_based_self_discharge["ElectricStorage"]["year_one_om_cost_before_tax"] ≈ 0 atol=0.01 + @test results_soc_based_self_discharge["ElectricStorage"]["lifecycle_om_cost_after_tax"] ≈ 0 atol=0.01 @test sum(results_no_self_discharge["ElectricStorage"]["storage_to_load_series_kw"]) - - sum(results_self_discharge["ElectricStorage"]["storage_to_load_series_kw"]) ≈ 15.382 atol=0.01 + sum(results_soc_based_self_discharge["ElectricStorage"]["storage_to_load_series_kw"]) ≈ 15.382 atol=0.01 + + calculated_output = round((80 - (80 * 0.0025/24 * 8760)) * sqrt(0.975) * 0.96, digits = 3) + @test sum(results_capacity_based_self_discharge["ElectricStorage"]["storage_to_load_series_kw"]) ≈ calculated_output atol=0.01 end @testset "Disaggregated Heating Loads" begin From 1ff7a31a86ae0888889f0fc9420498931ba2d995 Mon Sep 17 00:00:00 2001 From: lixiangk1 <72464565+lixiangk1@users.noreply.github.com> Date: Mon, 21 Oct 2024 12:12:43 -0600 Subject: [PATCH 10/11] Update thermal decay variables to match electric storage self-discharge --- src/constraints/storage_constraints.jl | 12 ++++++---- src/core/energy_storage/thermal_storage.jl | 24 ++++++++++++------- test/scenarios/ghp_inputs.json | 4 ++-- .../heat_cool_energy_balance_inputs.json | 4 ++-- test/scenarios/re_emissions_with_thermal.json | 2 +- 5 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/constraints/storage_constraints.jl b/src/constraints/storage_constraints.jl index 1a38ab58e..6af7232ea 100644 --- a/src/constraints/storage_constraints.jl +++ b/src/constraints/storage_constraints.jl @@ -111,11 +111,12 @@ function add_hot_thermal_storage_dispatch_constraints(m, p, b; _n="") @constraint(m, [b in p.s.storage.types.hot, ts in p.time_steps], m[Symbol("dvStoredEnergy"*_n)][b,ts] == m[Symbol("dvStoredEnergy"*_n)][b,ts-1] + (1/p.s.settings.time_steps_per_hour) * ( p.s.storage.attr[b].charge_efficiency * sum(m[Symbol("dvHeatToStorage"*_n)][b,t,q,ts] for t in union(p.techs.heating, p.techs.chp), q in p.heating_loads) - - sum(m[Symbol("dvHeatFromStorage"*_n)][b,q,ts] for q in p.heating_loads) / p.s.storage.attr[b].discharge_efficiency - - p.s.storage.attr[b].thermal_decay_rate_fraction * m[Symbol("dvStorageEnergy"*_n)][b] + sum(m[Symbol("dvHeatFromStorage"*_n)][b,q,ts] for q in p.heating_loads) / p.s.storage.attr[b].discharge_efficiency ) + - (p.s.storage.attr[b].soc_based_per_ts_thermal_decay_fraction * m[Symbol("dvStoredEnergy"*_n)][b, ts]) + - (p.s.storage.attr[b].capacity_based_per_ts_thermal_decay_fraction * m[Symbol("dvStorageEnergy"*_n)][b]) ) - + #Constraint (4n)-1: Dispatch to and from thermal storage is no greater than power capacity @constraint(m, [b in p.s.storage.types.hot, ts in p.time_steps], m[Symbol("dvStoragePower"*_n)][b] >= @@ -154,9 +155,10 @@ function add_cold_thermal_storage_dispatch_constraints(m, p, b; _n="") @constraint(m, ColdTESInventoryCon[b in p.s.storage.types.cold, ts in p.time_steps], m[Symbol("dvStoredEnergy"*_n)][b,ts] == m[Symbol("dvStoredEnergy"*_n)][b,ts-1] + (1/p.s.settings.time_steps_per_hour) * ( sum(p.s.storage.attr[b].charge_efficiency * m[Symbol("dvProductionToStorage"*_n)][b,t,ts] for t in p.techs.cooling) - - m[Symbol("dvDischargeFromStorage"*_n)][b,ts]/p.s.storage.attr[b].discharge_efficiency - - p.s.storage.attr[b].thermal_decay_rate_fraction * m[Symbol("dvStorageEnergy"*_n)][b] + m[Symbol("dvDischargeFromStorage"*_n)][b,ts]/p.s.storage.attr[b].discharge_efficiency ) + - (p.s.storage.attr[b].soc_based_per_ts_thermal_decay_fraction * m[Symbol("dvStoredEnergy"*_n)][b, ts]) + - (p.s.storage.attr[b].capacity_based_per_ts_thermal_decay_fraction * m[Symbol("dvStorageEnergy"*_n)][b]) ) #Constraint (4n)-2: Dispatch to and from thermal storage is no greater than power capacity diff --git a/src/core/energy_storage/thermal_storage.jl b/src/core/energy_storage/thermal_storage.jl index 41ffb6aff..a0703578f 100644 --- a/src/core/energy_storage/thermal_storage.jl +++ b/src/core/energy_storage/thermal_storage.jl @@ -15,7 +15,8 @@ Cold thermal energy storage sytem; specifically, a chilled water system used to soc_min_fraction::Float64 = 0.1 # Minimum allowable TES thermal state of charge soc_init_fraction::Float64 = 0.5 # TES thermal state of charge at first hour of optimization installed_cost_per_gal::Float64 = 1.50 # Thermal energy-based cost of TES (e.g. volume of the tank) - thermal_decay_rate_fraction::Float64 = 0.0004 # Thermal loss (gain) rate as a fraction of energy storage capacity, per hour (frac*energy_capacity/hr = kw_thermal) + soc_based_per_ts_thermal_decay_fraction = 0.0 # Thermal loss (gain) rate as a fraction of energy storage capacity per timestep (frac*energy_capacity = kw_thermal); the provided default is for an hourly thermal loss/gain + capacity_based_per_ts_thermal_decay_fraction = 0.0004 # Thermal loss (gain) rate as a fraction of the energy storaged in each timestep (frac*energy_stored = kw_thermal) om_cost_per_gal::Float64 = 0.0 # Yearly fixed O&M cost dependent on storage energy size macrs_option_years::Int = 7 macrs_bonus_fraction::Float64 = 0.6 @@ -33,7 +34,8 @@ Base.@kwdef struct ColdThermalStorageDefaults <: AbstractThermalStorageDefaults soc_min_fraction::Float64 = 0.1 soc_init_fraction::Float64 = 0.5 installed_cost_per_gal::Float64 = 1.50 - thermal_decay_rate_fraction::Float64 = 0.0004 + soc_based_per_ts_thermal_decay_fraction::Float64 = 0.0 + capacity_based_per_ts_thermal_decay_fraction::Float64 = 0.0004 om_cost_per_gal::Float64 = 0.0 macrs_option_years::Int = 7 macrs_bonus_fraction::Float64 = 0.6 @@ -55,7 +57,8 @@ end soc_min_fraction::Float64 = 0.1 soc_init_fraction::Float64 = 0.5 installed_cost_per_gal::Float64 = 1.50 - thermal_decay_rate_fraction::Float64 = 0.0004 + soc_based_per_ts_thermal_decay_fraction = 0.0 + capacity_based_per_ts_thermal_decay_fraction = 0.0004 om_cost_per_gal::Float64 = 0.0 macrs_option_years::Int = 7 macrs_bonus_fraction::Float64 = 0.6 @@ -76,7 +79,8 @@ Base.@kwdef struct HotThermalStorageDefaults <: AbstractThermalStorageDefaults soc_min_fraction::Float64 = 0.1 soc_init_fraction::Float64 = 0.5 installed_cost_per_gal::Float64 = 1.50 - thermal_decay_rate_fraction::Float64 = 0.0004 + soc_based_per_ts_thermal_decay_fraction::Float64 = 0.0 + capacity_based_per_ts_thermal_decay_fraction::Float64 = 0.0004 om_cost_per_gal::Float64 = 0.0 macrs_option_years::Int = 7 macrs_bonus_fraction::Float64 = 0.6 @@ -105,7 +109,8 @@ struct ColdThermalStorage <: AbstractThermalStorage soc_min_fraction::Float64 soc_init_fraction::Float64 installed_cost_per_gal::Float64 - thermal_decay_rate_fraction::Float64 + soc_based_per_ts_thermal_decay_fraction::Float64 + capacity_based_per_ts_thermal_decay_fraction::Float64 om_cost_per_gal::Float64 macrs_option_years::Int macrs_bonus_fraction::Float64 @@ -154,7 +159,8 @@ struct ColdThermalStorage <: AbstractThermalStorage s.soc_min_fraction, s.soc_init_fraction, s.installed_cost_per_gal, - s.thermal_decay_rate_fraction, + s.soc_based_per_ts_thermal_decay_fraction, + s.capacity_based_per_ts_thermal_decay_fraction, s.om_cost_per_gal, s.macrs_option_years, s.macrs_bonus_fraction, @@ -188,7 +194,8 @@ struct HotThermalStorage <: AbstractThermalStorage soc_min_fraction::Float64 soc_init_fraction::Float64 installed_cost_per_gal::Float64 - thermal_decay_rate_fraction::Float64 + soc_based_per_ts_thermal_decay_fraction::Float64 + capacity_based_per_ts_thermal_decay_fraction::Float64 om_cost_per_gal::Float64 macrs_option_years::Int macrs_bonus_fraction::Float64 @@ -240,7 +247,8 @@ struct HotThermalStorage <: AbstractThermalStorage s.soc_min_fraction, s.soc_init_fraction, s.installed_cost_per_gal, - s.thermal_decay_rate_fraction, + s.soc_based_per_ts_thermal_decay_fraction, + s.capacity_based_per_ts_thermal_decay_fraction, s.om_cost_per_gal, s.macrs_option_years, s.macrs_bonus_fraction, diff --git a/test/scenarios/ghp_inputs.json b/test/scenarios/ghp_inputs.json index 43b816527..abd01faad 100644 --- a/test/scenarios/ghp_inputs.json +++ b/test/scenarios/ghp_inputs.json @@ -59,11 +59,11 @@ "ColdThermalStorage": { "min_gal": 10, "max_gal": 10, - "thermal_decay_rate_fraction": 0.0 + "capacity_based_per_ts_thermal_decay_fraction": 0.0 }, "HotThermalStorage": { "min_gal": 10, "max_gal": 10, - "thermal_decay_rate_fraction": 0.0 + "capacity_based_per_ts_thermal_decay_fraction": 0.0 } } \ No newline at end of file diff --git a/test/scenarios/heat_cool_energy_balance_inputs.json b/test/scenarios/heat_cool_energy_balance_inputs.json index 6d8af917e..30aecc394 100644 --- a/test/scenarios/heat_cool_energy_balance_inputs.json +++ b/test/scenarios/heat_cool_energy_balance_inputs.json @@ -74,13 +74,13 @@ "min_gal": 20000.0, "max_gal": 20000.0, "internal_efficiency_fraction": 0.97, - "thermal_decay_rate_fraction": 0.004 + "capacity_based_per_ts_thermal_decay_fraction": 0.004 }, "ColdThermalStorage":{ "min_gal": 30000.0, "max_gal": 30000.0, "internal_efficiency_fraction": 0.97, - "thermal_decay_rate_fraction": 0.004, + "capacity_based_per_ts_thermal_decay_fraction": 0.004, "soc_init_fraction": 0.1 }, "AbsorptionChiller": { diff --git a/test/scenarios/re_emissions_with_thermal.json b/test/scenarios/re_emissions_with_thermal.json index fbc0346f1..747f1792e 100644 --- a/test/scenarios/re_emissions_with_thermal.json +++ b/test/scenarios/re_emissions_with_thermal.json @@ -79,7 +79,7 @@ "soc_min_fraction": 0.1 , "soc_init_fraction": 0.5 , "installed_cost_per_gal": 3.0 , - "thermal_decay_rate_fraction": 0.004 , + "capacity_based_per_ts_thermal_decay_fraction": 0.004 , "om_cost_per_gal": 0.0 , "macrs_option_years": 0 , "macrs_bonus_fraction": 0.0 From 78793c0493d8b5daac38ed8660fe33e91f7a5110 Mon Sep 17 00:00:00 2001 From: lixiangk1 <72464565+lixiangk1@users.noreply.github.com> Date: Mon, 21 Oct 2024 13:34:40 -0600 Subject: [PATCH 11/11] Remove extra minus sign --- src/constraints/storage_constraints.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constraints/storage_constraints.jl b/src/constraints/storage_constraints.jl index 9df637469..a34c287ff 100644 --- a/src/constraints/storage_constraints.jl +++ b/src/constraints/storage_constraints.jl @@ -178,7 +178,7 @@ function add_cold_thermal_storage_dispatch_constraints(m, p, b; _n="") @constraint(m, ColdTESInventoryCon[ts in p.time_steps], m[Symbol("dvStoredEnergy"*_n)][b,ts] == m[Symbol("dvStoredEnergy"*_n)][b,ts-1] + p.hours_per_time_step * ( sum(p.s.storage.attr[b].charge_efficiency * m[Symbol("dvProductionToStorage"*_n)][b,t,ts] for t in p.techs.cooling) - - m[Symbol("dvDischargeFromStorage"*_n)][b,ts]/p.s.storage.attr[b].discharge_efficiency - + m[Symbol("dvDischargeFromStorage"*_n)][b,ts]/p.s.storage.attr[b].discharge_efficiency ) - (p.s.storage.attr[b].soc_based_per_ts_thermal_decay_fraction * m[Symbol("dvStoredEnergy"*_n)][b, ts]) - (p.s.storage.attr[b].capacity_based_per_ts_thermal_decay_fraction * m[Symbol("dvStorageEnergy"*_n)][b])