QTHEN: 5. The QTHEN Julia package for heat exchanger network design
The Julia package
The methodology described in the previous 4 posts (parts I, II, III, and IV) has been implemented in a Julia package, QTHEN. This post describes the basic contents of the package and illustrates its use with a case study.
The post is based on version 0.5.1 of the package and so this is to be considered fragile. There are likely to be some changes to the user visible interface for using the package and some of potential these changes will be indicated below. The core functionality should remain as it is, however.
Types
There are a number of fundamental types that need to be understood to use the package:
Streama process stream which needs either cooling or heating.1 An example stream would be:
Stream("H1", :hot, 40u"kW/K", 473u"K", 313u"K", 0.8u"kW/K/m^2")which is a stream that needs to be cooled from 473 K to 313 K with an mCp value of 40 kW K-1. The heat transfer coefficient for this stream's side of the exchange is 0.8 kW K-1 m-2. Note the use of units (of measure), based on the
Unitful.jlpackage. It is highly recommended that units always be included as they help identify inconsistencies in the problem definition (and it's always good practice in any case, as I constantly tried to remind my students when I used to teach…).ExternalUtilityThe process streams, described above, have cooling and heating requirements. Although the aim of the package is to identify an exchange network that best meets those requirements, most processes will need some external utilities. Any number of both hot and cold utilities can be defined. An example is
ExternalUtility("Steam", :hot, 493.15u"K", 492.150u"K", 1.6u"kW/K/m^2", Q -> 700*Q/u"kW")which represents an external utility, Steam, which can be used for heating. The last argument to the constructor for this type is the function that calculates the operating cost of the exchange, i.e. the cost of the actual utility. This is given an amount Q, the rate at which heat has to be delivered, and returns the cost in currency units per unit of time. The above example is for when the duty is in kW.
ProblemA heat exchanger network synthesis problem is defined by the process streams, the utilities available, a model for the capital cost, and a minimum temperature driving force for all exchanges. The following is an example, including the definition of the cost model as a function of the area of each exchanger:
areacost(A) = 8600 + 512*(A/u"m^2")^(0.83) problem = Problem(name, streams, areacost, utilities, 8.5u"K", QTHEN.Options(outernfmax, innernfmax))
The last argument is specific to the optimization methods used to solve the design problem and will be described below.
Optimization
The main entry point for the package is the optimize function. This is given an initial configuration and uses the Fresa nature inspired stochastic optimization method for both the outer and inner optimization problems (see the specification of the bi-level optimization problem in the previous blog post in this series).
A configuration consists of a series of operations to perform on streams in the problem. The choice of operations is to split or to cut a particular stream and where to split or cut it.
However, as Fresa needs only a single starting point in the search space, I have not gone into detail of how to create any specific design points. Instead, the default behaviour is to use the streams as they are without any cutting or splitting as the initial point for the search. This is the configuration that is defined by simply passing the Problem to the configuration constructor, as in
initial = QTHEN.Configuration(problem)
The one aspect that a user may wish to control is the amount of computation allocated to solving the problem. As the problem consists of an outer optimization problem (searching the space of different configurations) and an inner optimization problem (finding the best layout for a given configuration), the problem definition includes the maximum number of objective function evaluations that should be performed at both outer and inner levels.
Case study
The case study I present here comes from a paper by Morton (Morton 2002). It consists of three hot streams and three cold streams. The full Julia code to define and solve this problem is here:
using QTHEN using Unitful begin name = "morton" streams = [ QTHEN.Stream("H1", :hot, 40u"kW/K", 473u"K", 313u"K", 0.8u"kW/K/m^2") QTHEN.Stream("H2", :hot, 10u"kW/K", 393u"K", 333u"K", 0.8u"kW/K/m^2") QTHEN.Stream("H3", :hot, 5u"kW/K", 363u"K", 323u"K", 0.8u"kW/K/m^2") QTHEN.Stream("C1", :cold, 20u"kW/K", 298u"K", 453u"K", 1.6u"kW/K/m^2") QTHEN.Stream("C2", :cold, 25u"kW/K", 353u"K", 483u"K", 1.6u"kW/K/m^2") QTHEN.Stream("C3", :cold, 18u"kW/K", 308u"K", 433u"K", 1.6u"kW/K/m^2") ] utilities = [ QTHEN.ExternalUtility("Steam", :hot, 493.15u"K", 492.150u"K", 1.6u"kW/K/m^2", Q -> 700*Q/u"kW") QTHEN.ExternalUtility("Water", :cold, 303.15u"K", 313.15u"K", 0.8u"kW/K/m^2", Q -> 60*Q/u"kW") ] areacost(A) = 8600 + 512*(A/u"m^2")^(0.83) problem = QTHEN.Problem(name, streams, areacost, utilities, 8.5u"K", QTHEN.Options(1_000, 2_000)) best, population = QTHEN.optimize(QTHEN.Configuration(problem)) println("Best solution has objective function value $(best.z).") QTHEN.summary(best.x, orglevel = "**") end
The output from the final summary function consists of a table,
| hot | Th,in | Th,out | cold | Tc,in | Tc,out | Duty | Area | Capital cost | Operating cost |
|---|---|---|---|---|---|---|---|---|---|
| K | K | K | K | kW | m2 | ||||
| H1<2 | 384.3 | 313.0 | C1/1 | 298.0 | 372.5 | 1490.9 | 210.20 | 51956.25 | 0.00 |
| H1<2 | 473.0 | 384.3 | C2/2 | 374.1 | 448.3 | 1855.8 | 212.13 | 52287.25 | 0.00 |
| Steam | 493.1 | 492.1 | C2/2 | 448.3 | 483.0 | 867.8 | 47.35 | 21183.36 | 607482.76 |
| H2 | 361.5 | 333.0 | Water | 303.1 | 313.1 | 285.0 | 18.58 | 14387.55 | 17100.00 |
| H2 | 393.0 | 361.5 | C2/1 | 353.0 | 365.6 | 315.0 | 36.66 | 18776.16 | 0.00 |
| H1<1/1 | 422.4 | 411.4 | C2/1 | 365.6 | 374.1 | 211.4 | 8.42 | 11601.50 | 0.00 |
| H1<1/1 | 473.0 | 422.4 | C1/2 | 372.5 | 420.8 | 964.7 | 35.43 | 18490.87 | 0.00 |
| Steam | 493.1 | 492.1 | C1/2 | 420.8 | 453.0 | 644.4 | 14.84 | 13404.69 | 451084.64 |
| H1<1/2 | 321.9 | 313.0 | Water | 303.1 | 313.1 | 169.6 | 45.67 | 20811.53 | 10173.29 |
| H1<1/2 | 411.4 | 321.9 | C3 | 308.0 | 402.9 | 1707.7 | 291.82 | 65526.18 | 0.00 |
| Steam | 493.1 | 492.1 | C3 | 402.9 | 433.0 | 542.3 | 9.19 | 11827.48 | 379620.97 |
| H3 | 363.0 | 323.0 | Water | 303.1 | 313.1 | 200.0 | 15.36 | 13543.16 | 12000.00 |
| 945.65 | 313795.98 | 1477461.66 |
and a graphical representation of the streams and, implicitly, the exchange of heat from hot streams to cold streams:
Figure 1: Plot of best solution found for Morton's case study.
These results are based on a configuration in which the first hot stream, H1, is split into two streams, H1<1 and H1<2. The first of these streams, H1<1, is then cut into two pieces, H1<1/1 and H1<1/2. Two of the cold streams, C1 and C2, are also cut, resulting in four streams: C1/1, C1/2, C2/1, and C2/2.
Licensing
The package is available with an CC BY-NC-SA licence:
This license enables reusers to distribute, remix, adapt, and build upon the material in any medium or format for noncommercial purposes only, and only so long as attribution is given to the creator. If you remix, adapt, or build upon the material, you must license the modified material under identical terms. CC BY-NC-SA includes the following elements:
- BY
- credit must be given to the creator.
- NC
- Only noncommercial uses of the work are permitted.
- SA
- Adaptations must be shared under the same terms.
Commercial use is allowed under a different licence: please contact the author to discuss.
Blog navigation
| Previous post | Blog | Next post |
|---|---|---|
| QTHEN: 4. A representation for heat exchanger network configurations | Index | Emacs carnival: Ode to org Babel |
You can find me on Mastodon or you can email me should you wish to comment on this entry or the whole blog.
References
Footnotes:
This will likely change into two stream types, HotStream and ColdStream, in the near future.