-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Avi/initial plus sp1 general #26
Changes from 10 commits
9fb0e0f
eface71
3871f00
8cc6c2d
217839c
11fab07
b3db156
88f1401
6c06477
9beca82
94a9dc9
ab3f8db
685a4c4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
*.jl.*.cov | ||
*.jl.cov | ||
*.jl.mem | ||
Manifest.toml | ||
/Manifest.toml | ||
/tmp | ||
/.vscode |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
|
||
|
||
|
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
using XLSX | ||
using DataFrames | ||
""" | ||
$(TYPEDEF) | ||
$(TYPEDFIELDS) | ||
|
||
Holds the classical HENS problem with fixed stream data. | ||
""" | ||
mutable struct ClassicHENSProblem <: AbstractSynthesisProblem | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does this need to be mutable? Since it holds the dictionaries already it feels like there is little value? Perhaps one wants to adjust There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, made it immutable |
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do you have a good reason to make this an inner constructor? Usually inner constructors are used if you want to force some sort of sanity check that cannot be sidestepped. doesn't seem to be the case here. In that case it just reduces the extensibility but adds no value otherwise. Not sure if really critical here, though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
abstract type AbstractStream end | ||
|
||
|
||
""" | ||
$(TYPEDEF) | ||
$(TYPEDFIELDS) | ||
|
||
Single hot stream | ||
""" | ||
mutable struct HotStream <: AbstractStream | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the mutability of this I like. I could see adjusting stream data to be an important use case even after the problem has been defined. |
||
name::String # May eventually need parametric types when these can be variables. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps prefer the use of symbols here? There is no huge difference. If you end up comparing stream names a lot then Symbols are faster. I doubt this is gonna be critical here. Perhaps actually a little relevant is that I think they are easier to type - : instead of " ". If you manipulate the stream names a lot (which I doubt?) then Strings should be preferred. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good qn. The major reason to use strings is once again this is constructed from the users XLSX sheet, and the user may want to give long names with spaces e.g., "Reactor effluent hot stream". Easier with strings than symbols. |
||
T_in::Float64 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is it necessary to restrict to Float64? What if someone uses Float32 for whatever reason or inputs integer data for temperatures. Maybe make the type parametric, and handle promotion to the same number type for all inputs? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point to use parametric types. #28 I do accept any type in the constructor and promote to Float64 as seen in the constructor code. |
||
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}()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same issue regarding the inner constructor as before. |
||
T_in isa Real && T_out isa Real && mcp isa Real && h isa Real || error("Input data contains a non-real number") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not use dispatch for that? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, this I don't understand. The above is a sanity check of user data. I want to error if the user gives in garbage in the XLSX sheet. |
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see HotStream comments |
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see comments for other ColdStream |
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see SimpleHotUtility comments. |
||
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The use of macros to create the problem is very slow. Avoiding that is a lot of work and takes a lot of learning about JuMP (or ideally MathOptInterface) though. Depending on how big the problems become, the inefficiency of macro usage can actually become very noticeable (especially when you start thinking about the nonlinear problems). For now I think this is fine but something to keep in mind. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, will make an issue #27 |
||
@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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are you sure you wanna go with Julia 1.6? Is that necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will update at a later stage. Would rather be conservative on this.