From da3322a901ea0c1867a954bcc5c83d0f17344a0c Mon Sep 17 00:00:00 2001 From: wbecker Date: Sun, 14 Dec 2025 19:46:32 -0700 Subject: [PATCH 1/2] Fix Boiler results for non-hourly timesteps and unit conversions --- src/results/boiler.jl | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/results/boiler.jl b/src/results/boiler.jl index 3b0beaee0..bb5ee7c83 100644 --- a/src/results/boiler.jl +++ b/src/results/boiler.jl @@ -24,13 +24,12 @@ function add_boiler_results(m::JuMP.AbstractModel, p::REoptInputs, d::Dict; _n=" r["size_mmbtu_per_hour"] = round(value(m[Symbol("dvSize"*_n)]["Boiler"]) / KWH_PER_MMBTU, digits=3) r["fuel_consumption_series_mmbtu_per_hour"] = round.(value.(m[:dvFuelUsage]["Boiler", ts] for ts in p.time_steps) / KWH_PER_MMBTU, digits=3) - r["annual_fuel_consumption_mmbtu"] = round(sum(r["fuel_consumption_series_mmbtu_per_hour"]), digits=3) + r["annual_fuel_consumption_mmbtu"] = round(sum(r["fuel_consumption_series_mmbtu_per_hour"]) / p.s.settings.time_steps_per_hour, digits=3) r["thermal_production_series_mmbtu_per_hour"] = round.(sum(value.(m[:dvHeatingProduction]["Boiler", q, ts] for ts in p.time_steps) for q in p.heating_loads) ./ KWH_PER_MMBTU, digits=5) - r["annual_thermal_production_mmbtu"] = round(sum(r["thermal_production_series_mmbtu_per_hour"]), digits=3) - - if !isempty(p.s.storage.types.hot) + r["annual_thermal_production_mmbtu"] = round(sum(r["thermal_production_series_mmbtu_per_hour"]) / p.s.settings.time_steps_per_hour, digits=3) + if !isempty(p.s.storage.types.hot) @expression(m, NewBoilerToHotTESKW[ts in p.time_steps], sum(m[:dvHeatToStorage][b,"Boiler",q,ts] for b in p.s.storage.types.hot, q in p.heating_loads) ) @@ -48,7 +47,7 @@ function add_boiler_results(m::JuMP.AbstractModel, p::REoptInputs, d::Dict; _n=" NewBoilerToSteamTurbine = zeros(length(p.time_steps)) @expression(m, NewBoilerToSteamTurbineByQuality[q in p.heating_loads, ts in p.time_steps], 0.0) end - r["thermal_to_steamturbine_series_mmbtu_per_hour"] = round.(value.(NewBoilerToSteamTurbine), digits=3) + r["thermal_to_steamturbine_series_mmbtu_per_hour"] = round.(value.(NewBoilerToSteamTurbine) ./ KWH_PER_MMBTU, digits=3) BoilerToLoad = @expression(m, [ts in p.time_steps], sum(value.(m[:dvHeatingProduction]["Boiler", q, ts]) for q in p.heating_loads) - NewBoilerToHotTESKW[ts] - NewBoilerToSteamTurbine[ts] From c8d92668f6ec90408a58d800d8256ec2aba8c7dd Mon Sep 17 00:00:00 2001 From: wbecker Date: Sun, 14 Dec 2025 19:49:46 -0700 Subject: [PATCH 2/2] Add temp dev Boiler + SteamTurbine test files --- test/scenarios/boiler_steamturbine.json | 51 +++++++++++++++++++++++++ test/test_boiler_steamturbine.jl | 47 +++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 test/scenarios/boiler_steamturbine.json create mode 100644 test/test_boiler_steamturbine.jl diff --git a/test/scenarios/boiler_steamturbine.json b/test/scenarios/boiler_steamturbine.json new file mode 100644 index 000000000..9893ae6b3 --- /dev/null +++ b/test/scenarios/boiler_steamturbine.json @@ -0,0 +1,51 @@ +{ + "Site": { + "latitude": 37.78, + "longitude": -122.45 + }, + "ElectricLoad": { + "doe_reference_name": "Hospital", + "annual_kwh": 5000000.0 + }, + "SpaceHeatingLoad": { + "doe_reference_name": "Hospital", + "annual_mmbtu": 10000.0 + }, + "DomesticHotWaterLoad": { + "doe_reference_name": "Hospital", + "annual_mmbtu": 2000.0 + }, + "ElectricTariff": { + "blended_annual_energy_rate": 0.10, + "blended_annual_demand_rate": 10.0 + }, + "ExistingBoiler":{ + "fuel_cost_per_mmbtu": 8.0, + "efficiency": 0.75, + "can_supply_steam_turbine": false + }, + "Boiler": { + "min_mmbtu_per_hour": 0.0, + "max_mmbtu_per_hour": 100.0, + "efficiency": 0.8, + "fuel_type": "natural_gas", + "fuel_cost_per_mmbtu": 8.0, + "installed_cost_per_mmbtu_per_hour": 50000.0, + "om_cost_per_mmbtu_per_hour": 500.0, + "om_cost_per_mmbtu": 0.5, + "can_supply_steam_turbine": true + }, + "SteamTurbine": { + "min_kw": 0.0, + "max_kw": 5000.0, + "electric_produced_to_thermal_consumed_ratio": 0.30, + "thermal_produced_to_thermal_consumed_ratio": 0.50, + "installed_cost_per_kw": 1000.0, + "om_cost_per_kw": 10.0, + "om_cost_per_kwh": 0.01, + "can_wholesale": false, + "can_curtail": false, + "macrs_bonus_fraction": 0.0, + "macrs_option_years": 5 + } +} diff --git a/test/test_boiler_steamturbine.jl b/test/test_boiler_steamturbine.jl new file mode 100644 index 000000000..c09b73b63 --- /dev/null +++ b/test/test_boiler_steamturbine.jl @@ -0,0 +1,47 @@ +using Revise +using REopt +using JSON +using DelimitedFiles +using PlotlyJS +using Dates +using Test +using JuMP +using HiGHS +using DotEnv +DotEnv.load!() + +############### Boiler + SteamTurbine with simplified parameters ################### +# This test uses the simpler SteamTurbine setup with electric_produced_to_thermal_consumed_ratio +# and thermal_produced_to_thermal_consumed_ratio instead of detailed steam parameters + +input_data = JSON.parsefile("./scenarios/boiler_steamturbine.json") +s = Scenario(input_data) +inputs = REoptInputs(s) + +m1 = Model(optimizer_with_attributes(HiGHS.Optimizer, "mip_rel_gap" => 0.001, "output_flag" => false, "log_to_console" => false)) +m2 = Model(optimizer_with_attributes(HiGHS.Optimizer, "mip_rel_gap" => 0.001, "output_flag" => false, "log_to_console" => false)) +results = run_reopt([m1,m2], inputs) + +println("\n=== Boiler + SteamTurbine Test Results ===") +println("Financial NPV: ", round(results["Financial"]["npv"], digits=2)) +println("Financial LCC: ", round(results["Financial"]["lcc"], digits=2)) +println("\nBoiler:") +println(" Size (MMBtu/hr): ", round(results["Boiler"]["size_mmbtu_per_hour"], digits=2)) +println(" Annual thermal production (MMBtu): ", round(results["Boiler"]["annual_thermal_production_mmbtu"], digits=2)) +println(" Annual fuel consumption (MMBtu): ", round(results["Boiler"]["annual_fuel_consumption_mmbtu"], digits=2)) +println(" Thermal to SteamTurbine (MMBtu): ", round(sum(results["Boiler"]["thermal_to_steamturbine_series_mmbtu_per_hour"]), digits=2)) +println("\nSteamTurbine:") +println(" Size (kW): ", round(results["SteamTurbine"]["size_kw"], digits=2)) +println(" Annual electric production (kWh): ", round(results["SteamTurbine"]["annual_electric_production_kwh"], digits=2)) +println(" Annual thermal consumption (MMBtu): ", round(results["SteamTurbine"]["annual_thermal_consumption_mmbtu"], digits=2)) +println(" Annual thermal production (MMBtu): ", round(results["SteamTurbine"]["annual_thermal_production_mmbtu"], digits=2)) + +# Verify energy balance: Boiler thermal to ST should match ST thermal consumption +boiler_to_st = sum(results["Boiler"]["thermal_to_steamturbine_series_mmbtu_per_hour"]) +st_thermal_in = results["SteamTurbine"]["annual_thermal_consumption_mmbtu"] +println("\nEnergy Balance Check:") +println(" Boiler->SteamTurbine: ", round(boiler_to_st, digits=2), " MMBtu") +println(" SteamTurbine thermal in: ", round(st_thermal_in, digits=2), " MMBtu") +println(" Difference: ", round(abs(boiler_to_st - st_thermal_in), digits=2), " MMBtu") + +println("=============================================\n")