diff --git a/.gitignore b/.gitignore index 89c0ed8..8f1bec6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ *.jl.*.cov *.jl.cov *.jl.mem -Manifest.toml \ No newline at end of file +/Manifest.toml +/tmp +/.vscode \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/Examples/Colberg_Morari_1990/ColbergMorari.jl b/Examples/Colberg_Morari_1990/ColbergMorari.jl new file mode 100644 index 0000000..a6d341d --- /dev/null +++ b/Examples/Colberg_Morari_1990/ColbergMorari.jl @@ -0,0 +1,31 @@ +# Workflow using XLSX input: +# 1. Import necessary packages: +using CompHENS +using Plots +using JuMP +using HiGHS + +# 2. Specify path to xlsx file +file_path_xlsx = joinpath(@__DIR__, "CompHENS_interface_ColbergMorari.xlsx") + +# 3. Construct the appropriate kind of problem: Here it is a `ClassicHENSProblem` +prob = ClassicHENSProblem(file_path_xlsx; ΔT_min = 20.0) + +# 4. Subdivide into intervals and attain the hot and cold composite curves. +intervals = CompHENS.generate_heat_cascade_intervals(prob) +hot_ref_enthalpy, cold_ref_enthalpy = 0.0, 172.596 +sorted_intervals = intervals +ylabel = "T [°C or K]" +xlabel = "Heat duty Q" + +plt = CompHENS.plot_composite_curve(sorted_intervals; hot_ref_enthalpy, cold_ref_enthalpy, ylabel = "T [°C or K]", xlabel = "Heat duty Q") +ylims!((300,700)) + +# 5. Solve subproblem 1: minimum utilities. + +# Using formulation of Prob. 16.5 Biegler, Grossmann, Westerberg book. Pg. 533. + +min_utils = solve_minimum_utilities_subproblem(prob) + + + diff --git a/Examples/Colberg_Morari_1990/CompHENS_interface_ColbergMorari.xlsx b/Examples/Colberg_Morari_1990/CompHENS_interface_ColbergMorari.xlsx new file mode 100755 index 0000000..22b0294 Binary files /dev/null and b/Examples/Colberg_Morari_1990/CompHENS_interface_ColbergMorari.xlsx differ diff --git a/Manifest.toml b/Manifest.toml deleted file mode 100644 index f45eecf..0000000 --- a/Manifest.toml +++ /dev/null @@ -1,2 +0,0 @@ -# This file is machine-generated - editing it directly is not advised - diff --git a/Project.toml b/Project.toml index e45b25f..7a45005 100644 --- a/Project.toml +++ b/Project.toml @@ -3,6 +3,16 @@ uuid = "873a7a81-90fd-4468-8feb-46a7ba58f008" authors = ["Avinash Subramanian"] version = "0.1.0" +[deps] +DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" +DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +GAMS = "1ca51c6a-1b4d-4546-9ae1-53e0a243ab12" +HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" +JuMP = "4076af6c-e467-56ae-b986-b466b2749572" +Kwonly = "18d08c8c-0732-55ee-a446-91a51d7b4206" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +XLSX = "fdbf4ff8-1666-58a4-91e7-1b58723a45e0" + [compat] julia = "1.6" diff --git a/src/CompHENS.jl b/src/CompHENS.jl index 60bf904..ccc5742 100644 --- a/src/CompHENS.jl +++ b/src/CompHENS.jl @@ -1,5 +1,74 @@ module CompHENS -# Write your package code here. +using DocStringExtensions +using Kwonly + + +""" +$(TYPEDEF) + +Umbrella for all problems where `CompHENS.jl` is relevant +""" +abstract type AbstractSynthesisProblem end + +""" +$(TYPEDEF) + +The classical Heat Exchanger Network Synthesis (HENS) problem. +""" +abstract type AbstractHENSProblem <: AbstractSynthesisProblem end + +""" +$(TYPEDEF) + +Subproblem formulated while solving an `AbstractSynthesisProblem` +""" +abstract type AbstractSubProblem end + + +""" +$(TYPEDEF) + +Type for technique to solve one or more `AbstractSynthesisProblem` types. +""" +abstract type AbstractSynthesisAlgorithm end + +""" +$(TYPEDEF) + +Algorithm to solve an `AbstractSubProblem` +""" +abstract type AbstractSubProblemAlgorithm end + +""" +$(TYPEDEF) + +Holds the solution of an `AbstractSynthesisProblem` +""" +abstract type AbstractSolution end + +""" +$(TYPEDEF) + +Holds the solution of an `AbstractSubProblem` +""" +abstract type AbstractSubProblemSolution end + +const smallest_value = 1e-4 + +# Holds structures for streams +export AbstractStream, HotStream, ColdStream, AbstractUtility, SimpleHotUtility, SimpleColdUtility +include("Streams/streams.jl") + +# Hold structures of problem types +export ClassicHENSProblem +include("ProblemConstructors/classic_hens_prob.jl") + +# Holds all kinds of temperature intervals +export TemperatureInterval, generate_heat_cascade_intervals, plot_hot_composite_curve, plot_cold_composite_curve, plot_composite_curve +include("TemperatureIntervals/temperature_intervals.jl") + +export solve_minimum_utilities_subproblem +include("SubProblems/minimum_utilities_subprob.jl") end diff --git a/src/ProblemConstructors/classic_hens_prob.jl b/src/ProblemConstructors/classic_hens_prob.jl new file mode 100644 index 0000000..f850ff5 --- /dev/null +++ b/src/ProblemConstructors/classic_hens_prob.jl @@ -0,0 +1,61 @@ +using XLSX +using DataFrames +""" +$(TYPEDEF) +$(TYPEDFIELDS) + +Holds the classical HENS problem with fixed stream data. +""" +struct ClassicHENSProblem <: AbstractSynthesisProblem + hot_streams_dict::Dict{String, HotStream} + cold_streams_dict::Dict{String, ColdStream} + hot_utilities_dict::Dict{String, SimpleHotUtility} + cold_utilities_dict::Dict{String, SimpleColdUtility} + ΔT_min::Float64 + @add_kwonly function ClassicHENSProblem(hot_streams_dict, cold_streams_dict, hot_utilities_dict = Dict{String, SimpleHotUtility}(), cold_utilities_dict = Dict{String, SimpleColdUtility}(); ΔT_min = 10) + new(hot_streams_dict, cold_streams_dict, hot_utilities_dict, cold_utilities_dict, ΔT_min) + end +end + +""" +$(TYPEDSIGNATURES) + +Reads data from an XSLX file in `file_path_xlsx` and constructs a `ClassicHENSProblem`. + +- **`file_path_xlsx`** needs to be a string that ends in .xlsx +""" +function ClassicHENSProblem(file_path_xlsx::String; ΔT_min) + # Column names of XLSX interface: + sheet_label, stream_label, type_label, t_in_label, t_out_label, heat_cap_label, heat_coeff_label, cost_label, forbidden_label, compulsory_label = "StreamData", "Stream", "Type [H, C, HU or CU]", "Supply Temperature T_in [C or K]", "Target Temperature T_out [C or K]", "Heat Capacity mCp [kW/C or kW/K]", "Heat transfer coefficient h [kW/m2C or kW/m2K]", "Cost [\$/kW]", "Forbidden Matches", "Compulsory Matches" + additional_user_fields = Set{String}(["Cost [\$/kW]", "Forbidden Matches", "Compulsory Matches", "Maximum Temperature", "Mininum Temperature"]) + + hot_streams_dict = Dict{String, HotStream}() + cold_streams_dict = Dict{String, ColdStream}() + hot_utilities_dict = Dict{String, SimpleHotUtility}() + cold_utilities_dict = Dict{String, SimpleColdUtility}() + + stream_data_df = XLSX.openxlsx(file_path_xlsx) do xf + sheet_label in XLSX.sheetnames(xf) || error("Sheet with name `$(sheet_label)` not found. ") + DataFrame(XLSX.gettable(xf[sheet_label]; infer_eltypes=true)) + end + + names_stream_df = names(stream_data_df) + + for row in eachrow(stream_data_df) + # add_user_data field in all stream types. + add_user_data = Dict{String, Any}(k => row[k] for k in additional_user_fields if (k in names_stream_df && !ismissing(row[k]))) + if row[type_label] == "H" # Hot stream + hot_streams_dict[row[stream_label]] = HotStream(row[stream_label], row[t_in_label], row[t_out_label], row[heat_cap_label], row[heat_coeff_label], add_user_data) + elseif row[type_label] == "C" # Cold stream + cold_streams_dict[row[stream_label]] = ColdStream(row[stream_label], row[t_in_label], row[t_out_label], row[heat_cap_label], row[heat_coeff_label], add_user_data) + elseif row[type_label] == "HU" # Hot utility stream + hot_utilities_dict[row[stream_label]] = SimpleHotUtility(row[stream_label], row[t_in_label], row[t_out_label], row[heat_coeff_label], add_user_data) + elseif row[type_label] == "CU" # Cold utility stream + cold_utilities_dict[row[stream_label]] = SimpleColdUtility(row[stream_label], row[t_in_label], row[t_out_label], row[heat_coeff_label], add_user_data) + end + end + + ## TODO: Logic to get `ΔT_min` from XLSX sheet. + return ClassicHENSProblem(hot_streams_dict, cold_streams_dict, hot_utilities_dict, cold_utilities_dict; ΔT_min) +end + diff --git a/src/Streams/streams.jl b/src/Streams/streams.jl new file mode 100644 index 0000000..14eadf5 --- /dev/null +++ b/src/Streams/streams.jl @@ -0,0 +1,89 @@ +abstract type AbstractStream end + + +""" +$(TYPEDEF) +$(TYPEDFIELDS) + +Single hot stream +""" +mutable struct HotStream <: AbstractStream + name::String # May eventually need parametric types when these can be variables. + T_in::Float64 + T_out::Float64 + mcp::Float64 + h::Float64 # Film heat transfer coefficient + add_user_data::Dict{String, Any} # Used to hold useful information moving forwards. `Any` here may ruin type inference? + calc::Dict{String, Float64} # New data calculated from input data + @add_kwonly function HotStream(name, T_in, T_out, mcp, h, add_user_data = Dict{String, Any}(), calc = Dict{String, Float64}()) + T_in isa Real && T_out isa Real && mcp isa Real && h isa Real || error("Input data contains a non-real number") + T_in >= T_out || error("Supply and Target temperature don't match stream type") + mcp > smallest_value && h > smallest_value || error("mcp or h values infeasible") + new(name, Float64(T_in), Float64(T_out), Float64(mcp), Float64(h), add_user_data, calc) + end +end + + +""" +$(TYPEDEF) +$(TYPEDFIELDS) + +Single cold stream +""" +mutable struct ColdStream <: AbstractStream + name::String + T_in::Float64 + T_out::Float64 + mcp::Float64 + h::Float64 + add_user_data::Dict{String, Any} + calc::Dict{String, Float64} + @add_kwonly function ColdStream(name, T_in, T_out, mcp, h, add_user_data = Dict{String, Any}(), calc = Dict{String, Float64}()) + T_in isa Real && T_out isa Real && mcp isa Real && h isa Real || error("Input data contains a non-real number") + T_in <= T_out || error("Supply and Target temperature don't match stream type") + mcp > smallest_value && h > smallest_value || error("mcp or h values infeasible") + new(name, Float64(T_in), Float64(T_out), Float64(mcp), Float64(h), add_user_data, calc) + end +end + +abstract type AbstractUtility end + +""" +$(TYPEDEF) +$(TYPEDFIELDS) + +Simple hot utility stream. Single stream, no configuration information. +""" +mutable struct SimpleHotUtility <: AbstractUtility + name::String + T_in::Float64 + T_out::Float64 + h::Float64 + add_user_data::Dict{String, Any} + calc::Dict{String, Float64} + @add_kwonly function SimpleHotUtility(name, T_in, T_out, h, add_user_data = Dict{String, Any}(), calc = Dict{String, Float64}()) + T_in isa Real && T_out isa Real && h isa Real || error("Input data contains a non-real number") + h > smallest_value || error("h value infeasible") + new(name, Float64(T_in), Float64(T_out), Float64(h), add_user_data, calc) + end +end + +""" +$(TYPEDEF) +$(TYPEDFIELDS) + +Simple cold utility stream. Single stream, no configuration information. +""" +mutable struct SimpleColdUtility <: AbstractUtility + name::String + T_in::Float64 + T_out::Float64 + h::Float64 # + add_user_data::Dict{String, Any} + calc::Dict{String, Float64} + @add_kwonly function SimpleColdUtility(name, T_in, T_out, h, add_user_data = Dict{String, Any}(), calc = Dict{String, Float64}()) + T_in isa Real && T_out isa Real && h isa Real || error("Input data contains a non-real number") + h > smallest_value || error("h value infeasible") + new(name, Float64(T_in), Float64(T_out), Float64(h), add_user_data, calc) + end +end diff --git a/src/SubProblems/minimum_utilities_subprob.jl b/src/SubProblems/minimum_utilities_subprob.jl new file mode 100644 index 0000000..7de2e85 --- /dev/null +++ b/src/SubProblems/minimum_utilities_subprob.jl @@ -0,0 +1,55 @@ +using JuMP +using HiGHS + +""" +$(TYPEDSIGNATURES) + +Constructs and solves a minimum utilities optimization problem. +Returns: +- sol: A dictionary mapping the `keys(prob.hot_utilities_dict)`, `keys(prob.cold_utilities_dict)` to their minimum utility requirements. +Supports multiple utilities if appropriately matched to interval. +[TODO: Utility costs] +""" +function solve_minimum_utilities_subproblem(prob::ClassicHENSProblem; time_limit = 60.0, presolve = true, optimizer = HiGHS.Optimizer, verbose = true) + @info "Solving the minimum utilities subproblem" + intervals = CompHENS.generate_heat_cascade_intervals(prob) + subprob = Model() + HU_set = keys(prob.hot_utilities_dict) + CU_set = keys(prob.cold_utilities_dict) + + @variable(subprob, 0 <= Q_in[HU_set]) + @variable(subprob, 0 <= Q_out[CU_set]) + @variable(subprob, 0 <= R[intervals]) # Notation: R[interval] is the residual heat exiting a given interval + JuMP.fix(R[last(intervals)], 0.0; force = true) + + # First interval: Entering == Leaving + @constraint(subprob, + sum(Q_in[hu] for hu in keys(first(intervals).hot_utilities_contribs)) + first(intervals).total_stream_heat_in == R[first(intervals)] + sum(Q_out[cu] for cu in keys(first(intervals).cold_utilities_contribs)) + first(intervals).total_stream_heat_out) + + # Remaining intervals + @constraint(subprob, [i in 2:length(intervals)], + R[intervals[i-1]] + sum(Q_in[hu] for hu in keys(intervals[i].hot_utilities_contribs)) + intervals[i].total_stream_heat_in == R[intervals[i]] + sum(Q_out[cu] for cu in keys(intervals[i].cold_utilities_contribs)) + intervals[i].total_stream_heat_out) + + # Objective: TODO: Add utility costs. + @objective(subprob, Min, sum(Q_in) + sum(Q_out)) + set_optimizer(subprob, optimizer) + presolve && set_optimizer_attribute(subprob, "presolve", "on") + set_optimizer_attribute(subprob, "time_limit", time_limit) + optimize!(subprob) + if verbose + @show termination_status(subprob) + @show primal_status(subprob) + @show dual_status(subprob) + end + + + # Post-processing + sol = Dict() + for hu in HU_set + push!(sol, hu => value.(Q_in[hu])) + end + for cu in CU_set + push!(sol, cu => value.(Q_out[cu])) + end + return sol +end \ No newline at end of file diff --git a/src/TemperatureIntervals/temperature_intervals.jl b/src/TemperatureIntervals/temperature_intervals.jl new file mode 100644 index 0000000..0b7860a --- /dev/null +++ b/src/TemperatureIntervals/temperature_intervals.jl @@ -0,0 +1,188 @@ +using Plots + +""" +$(TYPEDEF) +$(TYPEDFIELDS) + +Holds a single temperature interval. Each temperature interval is defined by a box containing the upper and lower temperatures on the hot and cold sides. The `*_contribs` contain only the streams that participate in the interval. + +QN: Make this immutable? +""" +mutable struct TemperatureInterval + index::Int64 + T_hot_upper::Float64 + T_hot_lower::Float64 + T_cold_upper::Float64 + T_cold_lower::Float64 + """The residual heat supplied to the interval by the adjacent hotter `TemperatureInterval`. Defined after solving optimization problem.""" + R_in::Float64 + """The residual heat supplied from the interval to the adjacent colder `TemperatureInterval`. Defined after solving optimization problem.""" + R_out::Float64 + """The heat contribution of the hot stream to the interval""" + hot_streams_contribs::Dict{String, Float64} + """The heat removed by cold stream from the interval""" + cold_streams_contribs::Dict{String, Float64} + """Hot utility entering system at interval. Note: Each HU can only enter at one interval""" + hot_utilities_contribs::Dict{String, Float64} + """Cold utility entering system at interval. Note: Each CU can only enter at one interval""" + cold_utilities_contribs::Dict{String, Float64} + @add_kwonly function TemperatureInterval(index, T_hot_upper, T_hot_lower, T_cold_upper, T_cold_lower, R_in = 0.0, R_out = 0.0, hot_streams_contribs = Dict{String, Float64}(), cold_streams_contribs = Dict{String, Float64}(), hot_utilities_contribs = Dict{String, Float64}(), cold_utilities_contribs = Dict{String, Float64}()) + R_in >= 0.0 && R_out >= 0.0 || error("Residuals to temperature interval negative") + new(index, T_hot_upper, T_hot_lower, T_cold_upper, T_cold_lower, R_in, R_out, hot_streams_contribs, cold_streams_contribs, hot_utilities_contribs, cold_utilities_contribs) + end +end + +Base.show(io::IO, interval::TemperatureInterval) = print(io, "itv_$(interval.index)") +function Base.show(io::IO, intervals::Vector{TemperatureInterval}) + for interval in intervals + println(io, "itv_", interval.index, ": Hot side: [", interval.T_hot_upper, ", ", interval.T_hot_lower, "] Cold side: [", interval.T_cold_upper, ", ", interval.T_cold_lower, "]") + end +end + +""" +$(TYPEDSIGNATURES) + +Generates the intervals necessary for the heat cascade diagram. Currently only works with one hot and one cold utility. +TODO: Multiple utilities. +""" +function generate_heat_cascade_intervals(prob::ClassicHENSProblem) + intervals = TemperatureInterval[] + hot_side_temps, cold_side_temps = Float64[], Float64[] + for (k,v) in prob.hot_streams_dict + push!(hot_side_temps, v.T_in, v.T_out) + end + + for (k,v) in prob.cold_streams_dict + push!(hot_side_temps, v.T_in + prob.ΔT_min, v.T_out + prob.ΔT_min) + end + + for (k,v) in prob.hot_streams_dict + push!(cold_side_temps, v.T_in - prob.ΔT_min, v.T_out - prob.ΔT_min) + end + + for (k,v) in prob.cold_streams_dict + push!(cold_side_temps, v.T_in, v.T_out) + end + sort!(unique!(hot_side_temps), rev = true) + sort!(unique!(cold_side_temps), rev = true) + + length(hot_side_temps) == length(cold_side_temps) && all(hot_side_temps .- cold_side_temps .== prob.ΔT_min) || error("Inconsistency in attaining sorted temperature intervals.") + + for i in 1:length(hot_side_temps)-1 + hot_streams_contribs, cold_streams_contribs, hot_utilities_contribs, cold_utilities_contribs = Dict{String, Float64}(), Dict{String, Float64}(), Dict{String, Float64}(), Dict{String, Float64}() + # hot_utilities_contribs, cold_utilities_contribs = Dict{String, Float64}(keys(prob.hot_utilities_dict) .=> 0.0), Dict{String, Float64}(keys(prob.cold_utilities_dict) .=> 0.0) + T_hot_upper = hot_side_temps[i] + T_hot_lower = hot_side_temps[i+1] + T_cold_upper = cold_side_temps[i] + T_cold_lower = cold_side_temps[i+1] + + # Filtering the participating streams. Would change for forbidden matches. No easy way to avoid inner for loop: + for (k,v) in prob.hot_streams_dict + if (v.T_in >= T_hot_upper) && (v.T_out <= T_hot_lower) + Q_contrib = (T_hot_upper - T_hot_lower)*v.mcp + push!(hot_streams_contribs, k => Q_contrib) + end + end + + for (k,v) in prob.cold_streams_dict + if (v.T_in <= T_cold_lower) && (v.T_out >= T_cold_upper) + Q_contrib = (T_hot_upper - T_hot_lower)*v.mcp + push!(cold_streams_contribs, k => Q_contrib) + end + end + + if i == 1 # TODO: Extend for multiple utilities. + for (k,v) in prob.hot_utilities_dict + push!(hot_utilities_contribs, k => Inf) + end + end + + if i == length(hot_side_temps)-1 + for (k,v) in prob.cold_utilities_dict + push!(cold_utilities_contribs, k => Inf) + end + end + + R_in, R_out = 0.0, 0.0 # Place holders. To be attained from optimizer. + push!(intervals, TemperatureInterval(i, T_hot_upper, T_hot_lower, T_cold_upper, T_cold_lower, R_in, R_out, hot_streams_contribs, cold_streams_contribs, hot_utilities_contribs, cold_utilities_contribs)) + end + return intervals +end + +""" +$(TYPEDSIGNATURES) + +Plots the hot-side composite curve. Assume intervals are already sorted e.g., attained from the `generate_heat_cascade_intervals` function. +""" +function plot_hot_composite_curve(sorted_intervals::Vector{TemperatureInterval}; ref_enthalpy = 0.0, ylabel = "T [°C or K]", xlabel = "Heat duty Q", verbose = false) + T_vals = Float64[last(sorted_intervals).T_hot_lower] + Q_vals = Float64[ref_enthalpy] + for interval in reverse(sorted_intervals) + !verbose || println(interval.T_hot_upper, " ", interval.T_hot_lower, " ", interval.total_stream_heat_in) + push!(T_vals, interval.T_hot_upper) + ref_enthalpy += interval.total_stream_heat_in + push!(Q_vals, ref_enthalpy) + end + plt = plot(Q_vals, T_vals, ylabel = ylabel, xlabel = xlabel, color = :red, shape = :circle, legend = false) + return plt, Q_vals, T_vals +end + +""" +$(TYPEDSIGNATURES) + +Plots the cold-side composite curve. Assume intervals are already sorted e.g., attained from the `generate_heat_cascade_intervals` function. +""" +function plot_cold_composite_curve(sorted_intervals::Vector{TemperatureInterval}; ref_enthalpy = 0.0, ylabel = "T [°C or K]", xlabel = "Heat duty Q", verbose = false) + T_vals = Float64[last(sorted_intervals).T_cold_lower] + Q_vals = Float64[ref_enthalpy] + for interval in reverse(sorted_intervals) + !verbose || println(interval.T_cold_upper, " ", interval.T_cold_lower, " ", interval.total_stream_heat_out) + push!(T_vals, interval.T_cold_upper) + ref_enthalpy += interval.total_stream_heat_out + push!(Q_vals, ref_enthalpy) + end + plt = plot(Q_vals, T_vals, ylabel = ylabel, xlabel = xlabel, color = :blue, shape = :circle, legend = false) + return plt, Q_vals, T_vals +end + +""" +$(TYPEDSIGNATURES) + +Plots the cold-side composite curve. Assume intervals are already sorted e.g., attained from the `generate_heat_cascade_intervals` function. +""" +function plot_composite_curve(sorted_intervals::Vector{TemperatureInterval}; hot_ref_enthalpy = 0.0, cold_ref_enthalpy = 0.0, ylabel = "T [°C or K]", xlabel = "Heat duty Q") + plt_hot, Q_hot_ints, T_hot_ints = CompHENS.plot_hot_composite_curve(sorted_intervals; ref_enthalpy = hot_ref_enthalpy); + plt_cold, Q_cold_int, T_cold_int = CompHENS.plot_cold_composite_curve(sorted_intervals; ref_enthalpy = cold_ref_enthalpy); + + # No point manipulating the plots. plot!(plt1, plt2) unpacks the data anyway. + x_limits = (floor(Int64, min(hot_ref_enthalpy, cold_ref_enthalpy, minimum(Q_cold_int), minimum(Q_hot_ints))), ceil(Int64, round(max(maximum(Q_hot_ints), maximum(Q_cold_int)), sigdigits = 2))) + y_limits = (floor(Int64, min(minimum(T_hot_ints), minimum(T_cold_int))), ceil(Int64, max(maximum(T_hot_ints), maximum(T_cold_int)))) + plot(Q_hot_ints, T_hot_ints, ylabel = ylabel, xlabel = xlabel, color = :red, shape = :circle, legend = false, xlims = x_limits, ylims = y_limits) + plot!(Q_cold_int, T_cold_int, color = :blue, shape = :circle, legend = false) +end + + +#= +""" +$(TYPEDSIGNATURES) + +Gets the total heat into interval from participating heat streams. +""" +function total_stream_heat_in(interval::TemperatureInterval) + return sum(values(hot_streams_contribs)) + + +end +=# + +function Base.getproperty(interval::TemperatureInterval, sym::Symbol) + if sym == :total_stream_heat_in + return sum(values(interval.hot_streams_contribs)) + elseif sym == :total_stream_heat_out + return sum(values(interval.cold_streams_contribs)) + else # fallback to getfield + return getfield(interval, sym) + end +end + + diff --git a/test/classic_hens_constructor.jl b/test/classic_hens_constructor.jl new file mode 100644 index 0000000..9192618 --- /dev/null +++ b/test/classic_hens_constructor.jl @@ -0,0 +1,5 @@ +@info "Colberg Morari 1990" +prob = ClassicHENSProblem(joinpath(@__DIR__,"../Examples/Colberg_Morari_1990/CompHENS_interface_ColbergMorari.xlsx")) +@test length(prob.hot_streams_dict) == 3 && length(prob.cold_streams_dict) == 4 && length(prob.hot_utilities_dict) == 1 && length(prob.cold_utilities_dict) == 1 +@test prob.cold_utilities_dict["CW"].add_user_data["Cost [\$/kW]"] == 20 && prob.hot_utilities_dict["ST"].add_user_data["Cost [\$/kW]"] == 200 + diff --git a/test/runtests.jl b/test/runtests.jl index 594b194..25f0aad 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,5 +2,7 @@ using CompHENS using Test @testset "CompHENS.jl" begin - # Write your tests here. + @testset "Problem Construction" begin + include("classic_hens_constructor.jl") + end end