From 2ccb93713928a74550f32499b7ab15a35ac71ead Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Mon, 12 Jul 2021 14:00:31 +0900 Subject: [PATCH 01/43] Update VERSION --- VERSION | 2 +- docs/conf.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index abd4105..0d91a54 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.4 +0.3.0 diff --git a/docs/conf.py b/docs/conf.py index aab5e9d..622e4e3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -22,7 +22,7 @@ author = 'tarao1006' # The full version, including alpha/beta/rc tags -release = '0.2.4' +release = '0.3.0' # -- General configuration --------------------------------------------------- From da6aa3121cd7f644505fee3ef7b4cc096d5e0413 Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Mon, 12 Jul 2021 14:03:46 +0900 Subject: [PATCH 02/43] Replace 'cool' to 'cold' --- docs/index.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index e7edcbd..3aa5931 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -29,8 +29,8 @@ Getting Started # 熱交換を行う流体を準備 streams = [ - Stream(40.0, 90.0, 150.0, StreamType(1), 'cool1'), - Stream(80.0, 110.0, 180.0, StreamType(1), 'cool2'), + Stream(40.0, 90.0, 150.0, StreamType(1), 'cold1'), + Stream(80.0, 110.0, 180.0, StreamType(1), 'cold2'), Stream(125.0, 80.0, 180.0, StreamType(2), 'hot1'), Stream(100.0, 60.0, 160.0, StreamType(2), 'hot2') ] @@ -132,8 +132,8 @@ Details .. code-block:: python streams = [ - Stream(40.0, 90.0, 150.0, StreamType(1), 'cool1'), - Stream(80.0, 110.0, 180.0, StreamType(1), 'cool2'), + Stream(40.0, 90.0, 150.0, StreamType(1), 'cold1'), + Stream(80.0, 110.0, 180.0, StreamType(1), 'cold2'), Stream(125.0, 80.0, 180.0, StreamType(2), 'hot1'), Stream(100.0, 60.0, 160.0, StreamType(2), 'hot2') ] From 22f4b70c5e04ce9c67fd9271617c97a88b802afc Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Mon, 12 Jul 2021 14:17:10 +0900 Subject: [PATCH 03/43] Fix type --- src/pyheatintegration/grand_composite_curve.py | 2 +- src/pyheatintegration/stream.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pyheatintegration/grand_composite_curve.py b/src/pyheatintegration/grand_composite_curve.py index 0b32569..1b851cc 100644 --- a/src/pyheatintegration/grand_composite_curve.py +++ b/src/pyheatintegration/grand_composite_curve.py @@ -62,7 +62,7 @@ def __init__( self.hot_utility_target = self.heats[-1] self.cold_utility_target = self.heats[0] - def solve_external_heat(self) -> dict[int, float]: + def solve_external_heat(self) -> dict[str, float]: """外部流体による熱交換量を求めます. Returns: diff --git a/src/pyheatintegration/stream.py b/src/pyheatintegration/stream.py index 51f66f9..2612a03 100644 --- a/src/pyheatintegration/stream.py +++ b/src/pyheatintegration/stream.py @@ -1,6 +1,6 @@ import math from collections import defaultdict -from collections.abc import Iterable, Callable +from collections.abc import Iterable from copy import copy import uuid from .enums import StreamType @@ -110,7 +110,7 @@ def __format__(self, format_spec: str) -> str: f"heat flow [W]: {self.heat_flow.__format__(format_spec)}" ) - def sort_key(self) -> Callable[[], float]: + def sort_key(self) -> float: """ソートの際に用いるキーを返します。 """ if self.is_hot(): From 4697e0a9ff97c4380d1b14235a07f7446683c1f6 Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Mon, 12 Jul 2021 14:18:26 +0900 Subject: [PATCH 04/43] Fix Callable package --- src/pyheatintegration/tq_diagram.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pyheatintegration/tq_diagram.py b/src/pyheatintegration/tq_diagram.py index 8c23686..53d51f9 100644 --- a/src/pyheatintegration/tq_diagram.py +++ b/src/pyheatintegration/tq_diagram.py @@ -1,6 +1,7 @@ import math +from collections.abc import Callable from copy import copy, deepcopy -from typing import Callable, Optional +from typing import Optional from .heat_exchanger import HeatExchanger from .heat_range import (REL_TOL_DIGIT, HeatRange, get_detailed_heat_ranges, From f7022c50b454bebd6a358c55f30416663a2dbb89 Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Mon, 12 Jul 2021 14:18:36 +0900 Subject: [PATCH 05/43] Fix import order --- src/pyheatintegration/stream.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pyheatintegration/stream.py b/src/pyheatintegration/stream.py index 2612a03..d54f080 100644 --- a/src/pyheatintegration/stream.py +++ b/src/pyheatintegration/stream.py @@ -1,8 +1,9 @@ import math +import uuid from collections import defaultdict from collections.abc import Iterable from copy import copy -import uuid + from .enums import StreamType from .errors import InvalidStreamError from .temperature_range import (TemperatureRange, get_temperature_ranges, From e708c280e4cd5f1910c0044ad20d6d274f30a96b Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Mon, 12 Jul 2021 17:53:55 +0900 Subject: [PATCH 06/43] Add Examples --- src/pyheatintegration/line.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/pyheatintegration/line.py b/src/pyheatintegration/line.py index b53aa0e..c1f2b02 100644 --- a/src/pyheatintegration/line.py +++ b/src/pyheatintegration/line.py @@ -8,12 +8,23 @@ def y_range(lines: list[Line]) -> tuple[float, float]: """xy座標系における複数の直線からyの最小値と最大値を返します。 + 直線は広義単調増加であることを期待しています。 + Args: lines list[Line]: 直線。 Returns: tuple[float, float]: 最小値と最大値。 + + Examples: + >>> y_range([ + ((0, 0), (1, 1)), + ((1, 1), (2, 2)), + ((2, 2), (3, 5)), + ((3, 3), (5, 8)) + ]) + >>> (0, 8) """ return ( min(line[0][1] for line in lines), @@ -29,5 +40,14 @@ def extract_x(lines: list[Line]) -> list[float]: Returns: list[float]: x座標の値。 + + Examples: + >>> extract_x([ + ((0, 0), (1, 1)), + ((1, 1), (2, 2)), + ((2, 2), (3, 5)), + ((3, 3), (5, 8)) + ]) + >>> [0, 1, 2, 3, 5] """ return sorted(list(set(point[0] for point in chain(*lines)))) From 7dbb918235cd39ee30ec44555c9f25b559be600e Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Mon, 12 Jul 2021 17:54:06 +0900 Subject: [PATCH 07/43] Add line_test --- tests/line_test.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 tests/line_test.py diff --git a/tests/line_test.py b/tests/line_test.py new file mode 100644 index 0000000..e868cbb --- /dev/null +++ b/tests/line_test.py @@ -0,0 +1,32 @@ +import unittest + +from src.pyheatintegration.line import y_range, extract_x + + +class TestLine(unittest.TestCase): + + def test_y_range(self): + self.assertEqual( + y_range([ + ((0, 0), (1, 1)), + ((1, 1), (2, 2)), + ((2, 2), (3, 5)), + ((3, 3), (5, 8)) + ]), + (0, 8) + ) + + def test_extract_x(self): + self.assertEqual( + extract_x([ + ((0, 0), (1, 1)), + ((1, 1), (2, 2)), + ((2, 2), (3, 5)), + ((3, 3), (5, 8)) + ]), + [0, 1, 2, 3, 5] + ) + + +if __name__ == '__main__': + unittest.main() From db865f748519cf9865a87248e927dc0f999437a3 Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Mon, 12 Jul 2021 19:33:18 +0900 Subject: [PATCH 08/43] Add max --- src/pyheatintegration/tq_diagram.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pyheatintegration/tq_diagram.py b/src/pyheatintegration/tq_diagram.py index 53d51f9..c6541c6 100644 --- a/src/pyheatintegration/tq_diagram.py +++ b/src/pyheatintegration/tq_diagram.py @@ -391,6 +391,8 @@ def get_possible_minimum_temp_diff_range( hot_finish_temp - cold_finish_temp ) + minimum_minimum_approch_temp_diff = max(0, minimum_minimum_approch_temp_diff) + return TemperatureRange( maximum_minimum_approch_temp_diff, minimum_minimum_approch_temp_diff From ac245e97978720ae2c29d6cab587aa7072352dd5 Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Mon, 12 Jul 2021 19:45:49 +0900 Subject: [PATCH 09/43] Add temp range validation #25 --- src/pyheatintegration/pinch_analyzer.py | 8 +++- src/pyheatintegration/tq_diagram.py | 53 +++++++++++++++++-------- 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/src/pyheatintegration/pinch_analyzer.py b/src/pyheatintegration/pinch_analyzer.py index 448110a..5bc6c0b 100644 --- a/src/pyheatintegration/pinch_analyzer.py +++ b/src/pyheatintegration/pinch_analyzer.py @@ -13,7 +13,8 @@ class PinchAnalyzer: def __init__( self, streams_: list[Stream], - minimum_approach_temp_diff: float + minimum_approach_temp_diff: float, + ignore_maximum: bool = False ): streams = deepcopy(streams_) @@ -37,7 +38,10 @@ def __init__( if not cold_streams: raise RuntimeError('受熱流体は少なくとも1つは指定する必要があります。') - minimum_approach_temp_diff_range = get_possible_minimum_temp_diff_range(streams) + minimum_approach_temp_diff_range = get_possible_minimum_temp_diff_range( + streams, + ignore_maximum + ) if minimum_approach_temp_diff not in minimum_approach_temp_diff_range: raise ValueError( diff --git a/src/pyheatintegration/tq_diagram.py b/src/pyheatintegration/tq_diagram.py index c6541c6..1393678 100644 --- a/src/pyheatintegration/tq_diagram.py +++ b/src/pyheatintegration/tq_diagram.py @@ -287,12 +287,14 @@ def _merge_segments( def get_possible_minimum_temp_diff_range( - streams: list[Stream] + streams: list[Stream], + ignore_maximum: bool = False ) -> TemperatureRange: """設定可能な最小接近温度差を返します。 Args: streams (list[Stream]): 流体のリスト。 + ignore_maximum (bool): 最大値のチェックを無視するか。 Returns: float: 可能な最小接近温度差[℃]。 @@ -311,26 +313,43 @@ def get_possible_minimum_temp_diff_range( if not cold_streams: raise RuntimeError('受熱流体は少なくとも1つは指定する必要があります。') - hot_maximum_temp = max( - stream.input_temperature() for stream in streams if stream.is_hot() - ) + if ignore_maximum: + maximum_minimum_approch_temp_diff = math.inf + else: + hot_maximum_temp = max( + stream.input_temperature() for stream in streams if stream.is_hot() + ) - hot_minimum_temp = min( - stream.output_temperature() for stream in streams if stream.is_hot() - ) + hot_minimum_temp = min( + stream.output_temperature() for stream in streams if stream.is_hot() + ) - cold_maximum_temp = max( - stream.output_temperature() for stream in streams if stream.is_cold() - ) + cold_maximum_temp = max( + stream.output_temperature() for stream in streams if stream.is_cold() + ) - cold_minimum_temp = min( - stream.input_temperature() for stream in streams if stream.is_cold() - ) + cold_minimum_temp = min( + stream.input_temperature() for stream in streams if stream.is_cold() + ) - maximum_minimum_approch_temp_diff = min( - hot_maximum_temp - cold_maximum_temp, - hot_minimum_temp - cold_minimum_temp - ) + if hot_minimum_temp - cold_minimum_temp < 0: + raise ValueError( + '与熱流体の最低温度が受熱流体の最低温度を下回っています。' + f'与熱流体最低温度: {hot_minimum_temp:.3f} ℃ ' + f'受熱流体最低温度: {cold_minimum_temp:.3f} ℃' + ) + + if hot_maximum_temp - cold_maximum_temp < 0: + raise ValueError( + '与熱流体の最高温度が受熱流体の最高温度を下回っています。' + f'与熱流体最高温度: {hot_maximum_temp:.3f} ℃ ' + f'受熱流体最高温度: {cold_maximum_temp:.3f} ℃' + ) + + maximum_minimum_approch_temp_diff = min( + hot_maximum_temp - cold_maximum_temp, + hot_minimum_temp - cold_minimum_temp + ) # 与熱流体と受熱流体のセグメントを得る。 initial_hcc = _create_composite_curve(hot_streams) From c5c7300ce1b252cd1152ce1197db2929288f4656 Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Tue, 13 Jul 2021 11:25:55 +0900 Subject: [PATCH 10/43] Update data.csv --- examples/simple/data.csv | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/simple/data.csv b/examples/simple/data.csv index 1929640..dff4e36 100644 --- a/examples/simple/data.csv +++ b/examples/simple/data.csv @@ -1,5 +1,5 @@ -input_temperature,output_temperature,heat_flow,type,cost,id -40.0,90.0,150.0,1,0,'a' -80.0,110.0,180.0,1,0,'b' -125.0,80.0,160.0,2,0,'A' -100.0,60.0,160.0,2,0,'B' \ No newline at end of file +id,input_temperature,output_temperature,heat_flow,type,cost +a,40.0,90.0,150.0,1,0 +b,80.0,110.0,180.0,1,0 +A,125.0,80.0,160.0,2,0 +B,100.0,60.0,160.0,2,0 \ No newline at end of file From a689707cafd8e470b7c2372a368c35189b4541db Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Tue, 13 Jul 2021 11:26:37 +0900 Subject: [PATCH 11/43] Update docstring --- src/pyheatintegration/stream.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pyheatintegration/stream.py b/src/pyheatintegration/stream.py index d54f080..7710aaa 100644 --- a/src/pyheatintegration/stream.py +++ b/src/pyheatintegration/stream.py @@ -24,17 +24,17 @@ def __init__( """流体を表すクラス。 Args: - input_temperature (float): 入り口温度 - output_temperature (float): 出口温度 - heat_flow (float): 熱量 - type_ (StreamType): 流体種 + input_temperature (float): 入口温度。 + output_temperature (float): 出口温度。 + heat_flow (float): 熱量。 + type_ (StreamType): 流体種。 cost (float, optional): 流体のコスト。外部流体の場合のみ設定できる。 id_ (str): 流体を区別する識別子。 Raises: InvalidStreamError: - 入り口温度と出口温度の大小関係と流体種の関係が不正である場合。また、外部流体 - の熱量が0以外の場合、および外部流体以外の流体の熱量が0である場合。 + 入口温度と出口温度の大小関係と流体種の関係が不正である場合。また、外部流体の + 熱量が0以外の場合、および外部流体以外の流体の熱量が0である場合。 """ if id_: self.id_ = id_ From 2edc20a8bca95fc36ab7eb12c99a057499228f89 Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Tue, 13 Jul 2021 11:32:42 +0900 Subject: [PATCH 12/43] Add StreamState enum #27 --- examples/simple/data.csv | 10 +-- examples/simple/main.py | 7 ++- src/pyheatintegration/__init__.py | 2 +- src/pyheatintegration/enums.py | 22 +++++++ src/pyheatintegration/heat_exchanger.py | 82 +++++++++++++++++++++++++ src/pyheatintegration/plot_segment.py | 9 ++- src/pyheatintegration/segment.py | 15 +++-- src/pyheatintegration/stream.py | 14 ++++- src/pyheatintegration/tq_diagram.py | 6 +- tests/heat_exchanger_test.py | 29 +++++++++ 10 files changed, 177 insertions(+), 19 deletions(-) create mode 100644 tests/heat_exchanger_test.py diff --git a/examples/simple/data.csv b/examples/simple/data.csv index dff4e36..6ed871b 100644 --- a/examples/simple/data.csv +++ b/examples/simple/data.csv @@ -1,5 +1,5 @@ -id,input_temperature,output_temperature,heat_flow,type,cost -a,40.0,90.0,150.0,1,0 -b,80.0,110.0,180.0,1,0 -A,125.0,80.0,160.0,2,0 -B,100.0,60.0,160.0,2,0 \ No newline at end of file +id,input_temperature,output_temperature,heat_flow,type,state,cost +a,40.0,90.0,150.0,1,1,0 +b,80.0,110.0,180.0,1,1,0 +A,125.0,80.0,160.0,2,1,0 +B,100.0,60.0,160.0,2,1,0 \ No newline at end of file diff --git a/examples/simple/main.py b/examples/simple/main.py index 5e10dce..9883a51 100644 --- a/examples/simple/main.py +++ b/examples/simple/main.py @@ -2,7 +2,7 @@ import pandas as pd from matplotlib.collections import LineCollection -from pyheatintegration import (PinchAnalyzer, Stream, StreamType, +from pyheatintegration import (PinchAnalyzer, Stream, StreamState, StreamType, extract_x, y_range) @@ -14,8 +14,9 @@ def main(): row.output_temperature, row.heat_flow, StreamType(row.type), - row.cost, - row.id + StreamState(row.state), + cost=row.cost, + id_=row.id ) for _, row in df.iterrows()] minimum_approach_temperature_difference = 10.0 diff --git a/src/pyheatintegration/__init__.py b/src/pyheatintegration/__init__.py index 6dabc6a..e4cee5d 100644 --- a/src/pyheatintegration/__init__.py +++ b/src/pyheatintegration/__init__.py @@ -1,4 +1,4 @@ -from .enums import StreamType # noqa +from .enums import StreamState, StreamType # noqa from .grand_composite_curve import GrandCompositeCurve # noqa from .line import extract_x, y_range # noqa from .pinch_analyzer import PinchAnalyzer # noqa diff --git a/src/pyheatintegration/enums.py b/src/pyheatintegration/enums.py index e9eeaa1..5833523 100644 --- a/src/pyheatintegration/enums.py +++ b/src/pyheatintegration/enums.py @@ -19,3 +19,25 @@ def describe(self) -> str: 'EXTERNAL_COLD': 'external cold', 'EXTERNAL_HOT': 'external hot', } + + +class StreamState(Enum): + """流体の状態""" + + LIQUID = 1 + GAS = 2 + LIQUID_EVAPORATION = 3 + GAS_CONDENSATION = 4 + UNKNOWN = 5 + + def describe(self) -> str: + return STREAMSTATE_STR[self.name] + + +STREAMSTATE_STR = { + 'LIQUID': 'liquid', + 'GAS': 'gas', + 'LIQUID_EVAPORATION': 'liquid (evaporation)', + 'GAS_CONDENSATION': 'gas (condensation)', + 'UNKNOWN': 'unknown' +} diff --git a/src/pyheatintegration/heat_exchanger.py b/src/pyheatintegration/heat_exchanger.py index a5c8293..a842539 100644 --- a/src/pyheatintegration/heat_exchanger.py +++ b/src/pyheatintegration/heat_exchanger.py @@ -1,10 +1,74 @@ import math from typing import Optional +from .enums import StreamState from .heat_range import HeatRange from .plot_segment import PlotSegment +OVERALL_HEAT_TRANSFER_COEFFICIENT = { + StreamState.LIQUID: { + StreamState.LIQUID: 300.0, + StreamState.GAS: 200.0, + StreamState.LIQUID_EVAPORATION: 1000.0 + }, + StreamState.GAS: { + StreamState.LIQUID: 200.0, + StreamState.GAS: 150.0, + StreamState.LIQUID_EVAPORATION: 500.0 + }, + StreamState.GAS_CONDENSATION: { + StreamState.LIQUID: 1000.0, + StreamState.GAS: 500.0, + StreamState.LIQUID_EVAPORATION: 1500.0 + }, +} + + +def get_overall_heat_transfer_coefficient( + hot_stream_state: StreamState, + cold_stream_state: StreamState +) -> float: + """対応する総括伝熱係数を返します。 + + Args: + hot_stream_state (StreamState): 与熱流体の状態。 + cold_stream_state (StreamState): 受熱流体の状態。 + + Returns: + float: 総括伝熱係数 [W/m2.K] + + Exapmles: + >>> get_overall_heat_transfer_coefficient(StreamState.LIQUID, StreamState.LIQUID) + 300.0 + """ + if hot_stream_state not in [ + StreamState.LIQUID, StreamState.GAS, StreamState.GAS_CONDENSATION + ]: + raise ValueError( + '与熱流体の状態が不正です。対応する総括伝熱係数が設定されていません。' + f'状態: {hot_stream_state} ' + '設定可能な状態: ' + f'{StreamState.LIQUID}, ' + f'{StreamState.GAS}, ' + f'{StreamState.GAS_CONDENSATION}' + ) + + if cold_stream_state not in [ + StreamState.LIQUID, StreamState.GAS, StreamState.LIQUID_EVAPORATION + ]: + raise ValueError( + '受熱流体の状態が不正です。対応する総括伝熱係数が設定されていません。' + f'状態: {cold_stream_state} ' + '設定可能な状態: ' + f'{StreamState.LIQUID}, ' + f'{StreamState.GAS}, ' + f'{StreamState.LIQUID_EVAPORATION}' + ) + + return OVERALL_HEAT_TRANSFER_COEFFICIENT[hot_stream_state][cold_stream_state] + + class HeatExchanger: """熱交換器を表すクラス。 @@ -12,10 +76,14 @@ class HeatExchanger: heat_range (HeatRange): 熱交換の範囲。 hot_stream_uuid (str): 与熱流体のid。 cold_stream_uuid (str): 受熱流体のid。 + hot_stream_state (StreamState): 与熱流体の状態。 + cold_stream_state (StreamState): 受熱流体の状態。 hot_temperature_range (TemperatureRange): 与熱流体の温度領域。 cold_temperature_range (TemperatureRange): 受熱流体の温度領域。 lmtd_parallel_flow (Optional[float]): 並流の場合の対数平均温度差。 lmtd_counterflow (float): 向流の場合の対数平均温度差。 + area_parallel_flow (Optional[float]): 並流の場合の必要面積 [m2]。 + area_counterflow (float): 向流の場合の必要面積 [m2]。 hot_plot_segment (PlotSegment): 与熱流体のプロットセグメント。 cold_plot_segment (PlotSegment): 受熱流体のプロットセグメント。 """ @@ -33,10 +101,24 @@ def __init__( self.cold_temperature_range = self.cold_plot_segment.temperature_range self.hot_stream_uuid = self.hot_plot_segment.uuid self.cold_stream_uuid = self.cold_plot_segment.uuid + self.hot_stream_state = self.hot_plot_segment.state + self.cold_stream_state = self.cold_plot_segment.state + + self.overall_heat_transfer_coefficient = get_overall_heat_transfer_coefficient( + self.hot_stream_state, + self.cold_stream_state + ) self.lmtd_parallel_flow = self.init_lmtd_pararell_flow() self.lmtd_counterflow = self.init_lmtd_counterflow() + if self.lmtd_parallel_flow is not None: + self.area_parallel_flow = self.heat_range.delta / self.lmtd_parallel_flow / self.overall_heat_transfer_coefficient + else: + self.area_parallel_flow = None + + self.area_counterflow = self.heat_range.delta / self.lmtd_counterflow / self.overall_heat_transfer_coefficient + def __repr__(self) -> str: return ( "HeatExchanger(" diff --git a/src/pyheatintegration/plot_segment.py b/src/pyheatintegration/plot_segment.py index 492970d..994329a 100644 --- a/src/pyheatintegration/plot_segment.py +++ b/src/pyheatintegration/plot_segment.py @@ -4,6 +4,7 @@ from collections.abc import Iterable from typing import Optional +from .enums import StreamState from .heat_range import HeatRange from .heat_range import is_continuous as is_continuous_heat_ranges from .temperature_range import TemperatureRange @@ -20,7 +21,8 @@ def __init__( finish_heat: float = 0.0, start_temperature: float = 0.0, finish_temperature: float = 0.0, - uuid_: Optional[str] = None + uuid_: Optional[str] = None, + state: StreamState = StreamState.UNKNOWN ): self.heat_range = HeatRange(start_heat, finish_heat) self.temperature_range = TemperatureRange(start_temperature, finish_temperature) @@ -29,6 +31,8 @@ def __init__( else: self.uuid = uuid_ + self.state = state + def __str__(self) -> str: return ( f"heat: ({self.heat_range.start}; " @@ -194,7 +198,8 @@ def get_plot_segments( PlotSegment( *heat_range(), *plot_segment.temperatures_at_heats(heat_range()), - plot_segment.uuid + plot_segment.uuid, + plot_segment.state ) ) return res diff --git a/src/pyheatintegration/segment.py b/src/pyheatintegration/segment.py index c8b8bcc..de125a9 100644 --- a/src/pyheatintegration/segment.py +++ b/src/pyheatintegration/segment.py @@ -131,7 +131,8 @@ def init_plot_segments_separated_streams( start_heat, finish_heat, *temperature_range(), - str(streams[i].id_) + str(streams[i].id_), + streams[i].state )) start_heat += heat return res @@ -176,12 +177,14 @@ def split(self, minimum_approach_temp_diff: float) -> None: hot_heat_range_plot_segment[heat_range] = PlotSegment( *heat_range(), *self.hot_temperature_range(), - hot_plot_segment.uuid + hot_plot_segment.uuid, + hot_plot_segment.state ) cold_heat_range_plot_segment[heat_range] = PlotSegment( *heat_range(), *self.cold_temperature_range(), - cold_plot_segment.uuid + cold_plot_segment.uuid, + cold_plot_segment.state ) for heat_range_ in self.heat_ranges: @@ -200,14 +203,16 @@ def split(self, minimum_approach_temp_diff: float) -> None: hot_heat_range_plot_segment[heat_range_] = PlotSegment( *heat_range_(), *self.hot_temperature_range(), - hot_plot_segment.uuid + hot_plot_segment.uuid, + hot_plot_segment.state ) if cold_plot_segment_ is not None and cold_plot_segment_.uuid == cold_plot_segment.uuid: cold_heat_range_plot_segment[heat_range_] = PlotSegment( *heat_range_(), *self.cold_temperature_range(), - cold_plot_segment.uuid + cold_plot_segment.uuid, + cold_plot_segment.state ) self.hot_plot_segments_splitted = sorted(list(hot_heat_range_plot_segment.values())) diff --git a/src/pyheatintegration/stream.py b/src/pyheatintegration/stream.py index 7710aaa..edce867 100644 --- a/src/pyheatintegration/stream.py +++ b/src/pyheatintegration/stream.py @@ -4,7 +4,7 @@ from collections.abc import Iterable from copy import copy -from .enums import StreamType +from .enums import StreamState, StreamType from .errors import InvalidStreamError from .temperature_range import (TemperatureRange, get_temperature_ranges, get_temperature_transition) @@ -18,6 +18,7 @@ def __init__( output_temperature: float, heat_flow: float, type_: StreamType, + state: StreamState = StreamState.UNKNOWN, cost: float = 0.0, id_: str = '' ): @@ -85,6 +86,17 @@ def __init__( ) self.heat_flow = heat_flow + self.state = state + + if self.state in [StreamState.LIQUID_EVAPORATION, StreamState.GAS_CONDENSATION] \ + and not self.is_isothermal(): + raise InvalidStreamError( + '相変化によって熱交換を行う流体は等温である必要があります。' + f'流体の状態: {self.state.describe()} ' + f'入口温度: {input_temperature} ' + f'出口温度: {output_temperature}' + ) + def __repr__(self) -> str: return ( f"Stream({self.input_temperature()}, " diff --git a/src/pyheatintegration/tq_diagram.py b/src/pyheatintegration/tq_diagram.py index 1393678..d1defee 100644 --- a/src/pyheatintegration/tq_diagram.py +++ b/src/pyheatintegration/tq_diagram.py @@ -268,12 +268,14 @@ def _merge_segments( merged_hot_plot_segments.append(PlotSegment( *merged_heat_range(), *merged_hot_temp_range(), - hot_plot_segment.uuid + hot_plot_segment.uuid, + hot_plot_segment.state )) merged_cold_plot_segments.append(PlotSegment( *merged_heat_range(), *merged_cold_temp_range(), - cold_plot_segment.uuid + cold_plot_segment.uuid, + cold_plot_segment.state )) merged_heat_ranges.extend([heat_ranges[i], heat_ranges[i + 1]]) diff --git a/tests/heat_exchanger_test.py b/tests/heat_exchanger_test.py new file mode 100644 index 0000000..e477e70 --- /dev/null +++ b/tests/heat_exchanger_test.py @@ -0,0 +1,29 @@ +import unittest + +from src.pyheatintegration.enums import StreamState +from src.pyheatintegration.heat_exchanger import get_overall_heat_transfer_coefficient + + +class TestGetOverallHeatTransferCoefficient(unittest.TestCase): + + def test_success(self): + self.assertEqual( + get_overall_heat_transfer_coefficient( + StreamState.LIQUID, StreamState.LIQUID + ), + 300.0 + ) + + def test_failure(self): + with self.assertRaises(ValueError): + get_overall_heat_transfer_coefficient( + StreamState.LIQUID, StreamState.GAS_CONDENSATION + ) + + get_overall_heat_transfer_coefficient( + StreamState.LIQUID_EVAPORATION, StreamState.LIQUID + ) + + +if __name__ == '__main__': + unittest.main() From 89ae3106207cf964127c3e32d7c907a955ff1e8f Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Tue, 13 Jul 2021 17:00:40 +0900 Subject: [PATCH 13/43] Move heat_exchangers --- src/pyheatintegration/pinch_analyzer.py | 28 ++++++++++++++ src/pyheatintegration/tq_diagram.py | 51 +++++++------------------ 2 files changed, 41 insertions(+), 38 deletions(-) diff --git a/src/pyheatintegration/pinch_analyzer.py b/src/pyheatintegration/pinch_analyzer.py index 5bc6c0b..e919d12 100644 --- a/src/pyheatintegration/pinch_analyzer.py +++ b/src/pyheatintegration/pinch_analyzer.py @@ -1,9 +1,12 @@ from copy import deepcopy from .grand_composite_curve import GrandCompositeCurve +from .heat_range import HeatRange, get_detailed_heat_ranges from .line import Line from .stream import Stream from .tq_diagram import TQDiagram, get_possible_minimum_temp_diff_range +from .plot_segment import PlotSegment, get_plot_segments +from .heat_exchanger import HeatExchanger class PinchAnalyzer: @@ -66,6 +69,31 @@ def __init__( self.pinch_point_temp ) + all_heat_ranges = get_detailed_heat_ranges( + [ + [plot_segment.heat_range for plot_segment in self.tq.hcc_merged], + [plot_segment.heat_range for plot_segment in self.tq.ccc_merged] + ] + ) + hot_heat_range_plot_segment: dict[HeatRange, PlotSegment] = { + s.heat_range: s for s in get_plot_segments(all_heat_ranges, self.tq.hcc_merged) + } + cold_heat_range_plot_segment: dict[HeatRange, PlotSegment] = { + s.heat_range: s for s in get_plot_segments(all_heat_ranges, self.tq.ccc_merged) + } + + self.heat_exchangers: list[HeatExchanger] = [] + for heat_range in all_heat_ranges: + hot_plot_segment = hot_heat_range_plot_segment.get(heat_range, None) + cold_plot_segment = cold_heat_range_plot_segment.get(heat_range, None) + + if hot_plot_segment is None or cold_plot_segment is None: + continue + + self.heat_exchangers.append( + HeatExchanger(heat_range, hot_plot_segment, cold_plot_segment) + ) + def create_grand_composite_curve(self) -> tuple[list[float], list[float]]: """グランドコンポジットカーブを描くために必要な熱量と温度を返します。 """ diff --git a/src/pyheatintegration/tq_diagram.py b/src/pyheatintegration/tq_diagram.py index d1defee..f1b038d 100644 --- a/src/pyheatintegration/tq_diagram.py +++ b/src/pyheatintegration/tq_diagram.py @@ -3,7 +3,6 @@ from copy import copy, deepcopy from typing import Optional -from .heat_exchanger import HeatExchanger from .heat_range import (REL_TOL_DIGIT, HeatRange, get_detailed_heat_ranges, get_heat_ranges, get_heats) from .plot_segment import PlotSegment, get_plot_segments, is_continuous @@ -429,15 +428,16 @@ class TQDiagram: pinch_point_temp (float): ピンチポイントの温度[℃]。 Attributes: - heat_exchangers (list[HeatExchanger]): 熱交換器のリスト。 - hot_lines (list[Line]): TQ線図の与熱複合線。 - cold_linse (list[Line]): TQ線図の受熱複合線。 - hot_lines_separated (list[Line]): 流体ごとに分割した与熱複合線。 - cold_lines_separated (list[Line]): 流体ごとに分割した受熱複合線。 - hot_lines_splitted (list[Line]): 流体ごとに分割し、最小接近温度差を満たした与熱複合線。 - cold_lines_splitted (list[Line]): 流体ごとに分割し、最小接近温度差を満たした受熱複合線。 - hot_lines_merged (list[Line]): 熱交換器を結合した与熱複合線。 - cold_lines_merged (list[Line]): 熱交換器を結合した受熱複合線。 + hot_lines (list[Line]): TQ線図の与熱複合線(プロット用の直線のリスト)。 + cold_linse (list[Line]): TQ線図の受熱複合線(プロット用の直線のリスト)。 + hot_lines_separated (list[Line]): 流体ごとに分割した与熱複合線(プロット用の直線のリスト)。 + cold_lines_separated (list[Line]): 流体ごとに分割した受熱複合線(プロット用の直線のリスト)。 + hot_lines_splitted (list[Line]): 流体ごとに分割し、最小接近温度差を満たした与熱複合線(プロット用の直線のリスト)。 + cold_lines_splitted (list[Line]): 流体ごとに分割し、最小接近温度差を満たした受熱複合線(プロット用の直線のリスト)。 + hot_lines_merged (list[Line]): 熱交換器を結合した与熱複合線(プロット用の直線のリスト)。 + cold_lines_merged (list[Line]): 熱交換器を結合した受熱複合線(プロット用の直線のリスト)。 + hcc_merged (list[PlotSegment]): 熱交換器を結合した与熱複合線。 + ccc_merged (list[PlotSegment]): 熱交換器を結合した受熱複合線。 """ def __init__( @@ -480,31 +480,6 @@ def __init__( self.cold_lines_splitted = segments.cold_lines_splitted() # 結合可能なセグメント同士を結合する。 - hcc_merged, ccc_merged = _merge_segments(segments) - self.hot_lines_merged = [plot_segment.line() for plot_segment in hcc_merged] - self.cold_lines_merged = [plot_segment.line() for plot_segment in ccc_merged] - - all_heat_ranges = get_detailed_heat_ranges( - [ - [plot_segment.heat_range for plot_segment in hcc_merged], - [plot_segment.heat_range for plot_segment in ccc_merged] - ] - ) - hot_heat_range_plot_segment: dict[HeatRange, PlotSegment] = { - s.heat_range: s for s in get_plot_segments(all_heat_ranges, hcc_merged) - } - cold_heat_range_plot_segment: dict[HeatRange, PlotSegment] = { - s.heat_range: s for s in get_plot_segments(all_heat_ranges, ccc_merged) - } - - self.heat_exchangers: list[HeatExchanger] = [] - for heat_range in all_heat_ranges: - hot_plot_segment = hot_heat_range_plot_segment.get(heat_range, None) - cold_plot_segment = cold_heat_range_plot_segment.get(heat_range, None) - - if hot_plot_segment is None or cold_plot_segment is None: - continue - - self.heat_exchangers.append( - HeatExchanger(heat_range, hot_plot_segment, cold_plot_segment) - ) + self.hcc_merged, self.ccc_merged = _merge_segments(segments) + self.hot_lines_merged = [plot_segment.line() for plot_segment in self.hcc_merged] + self.cold_lines_merged = [plot_segment.line() for plot_segment in self.ccc_merged] From b8630185896e82e1feaac093568a5e2c8f252668 Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Tue, 13 Jul 2021 17:01:18 +0900 Subject: [PATCH 14/43] Fix import order --- src/pyheatintegration/heat_exchanger.py | 1 - src/pyheatintegration/pinch_analyzer.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pyheatintegration/heat_exchanger.py b/src/pyheatintegration/heat_exchanger.py index a842539..67dde6b 100644 --- a/src/pyheatintegration/heat_exchanger.py +++ b/src/pyheatintegration/heat_exchanger.py @@ -5,7 +5,6 @@ from .heat_range import HeatRange from .plot_segment import PlotSegment - OVERALL_HEAT_TRANSFER_COEFFICIENT = { StreamState.LIQUID: { StreamState.LIQUID: 300.0, diff --git a/src/pyheatintegration/pinch_analyzer.py b/src/pyheatintegration/pinch_analyzer.py index e919d12..33cb1b0 100644 --- a/src/pyheatintegration/pinch_analyzer.py +++ b/src/pyheatintegration/pinch_analyzer.py @@ -1,12 +1,12 @@ from copy import deepcopy from .grand_composite_curve import GrandCompositeCurve +from .heat_exchanger import HeatExchanger from .heat_range import HeatRange, get_detailed_heat_ranges from .line import Line +from .plot_segment import PlotSegment, get_plot_segments from .stream import Stream from .tq_diagram import TQDiagram, get_possible_minimum_temp_diff_range -from .plot_segment import PlotSegment, get_plot_segments -from .heat_exchanger import HeatExchanger class PinchAnalyzer: From bfd820badc7fe09b67484a5649962b0531729621 Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Tue, 13 Jul 2021 21:21:09 +0900 Subject: [PATCH 15/43] Add white line --- src/pyheatintegration/heat_exchanger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pyheatintegration/heat_exchanger.py b/src/pyheatintegration/heat_exchanger.py index 67dde6b..a842539 100644 --- a/src/pyheatintegration/heat_exchanger.py +++ b/src/pyheatintegration/heat_exchanger.py @@ -5,6 +5,7 @@ from .heat_range import HeatRange from .plot_segment import PlotSegment + OVERALL_HEAT_TRANSFER_COEFFICIENT = { StreamState.LIQUID: { StreamState.LIQUID: 300.0, From 4add67528704452f7538de5ad8f475059d5833ea Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Tue, 13 Jul 2021 21:42:20 +0900 Subject: [PATCH 16/43] Add calculate_heat_exchanger_cost #14 --- src/pyheatintegration/pinch_analyzer.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/pyheatintegration/pinch_analyzer.py b/src/pyheatintegration/pinch_analyzer.py index 33cb1b0..f735c2d 100644 --- a/src/pyheatintegration/pinch_analyzer.py +++ b/src/pyheatintegration/pinch_analyzer.py @@ -1,4 +1,5 @@ from copy import deepcopy +import math from .grand_composite_curve import GrandCompositeCurve from .heat_exchanger import HeatExchanger @@ -94,6 +95,11 @@ def __init__( HeatExchanger(heat_range, hot_plot_segment, cold_plot_segment) ) + self.heat_exchanger_cost = sum( + self.calculate_heat_exchanger_cost(heat_exchanger.area_counterflow) + for heat_exchanger in self.heat_exchangers + ) + def create_grand_composite_curve(self) -> tuple[list[float], list[float]]: """グランドコンポジットカーブを描くために必要な熱量と温度を返します。 """ @@ -130,3 +136,19 @@ def create_tq_merged(self) -> tuple[list[Line], list[Line]]: self.tq.hot_lines_merged, self.tq.cold_lines_merged ) + + def calculate_heat_exchanger_cost( + self, + area: HeatExchanger, + k: float = 1.0 + ) -> float: + """熱交換器にかかるコストを返します。 + + Args: + heat_exchanger (HeatExchanger): 熱交換器。 + k (float): 係数。リボイラーまたは反応器の場合は2 + + Returns: + float: コスト[円]。 + """ + return 1_500_000 * math.pow(area, 0.65) * k From e15ed3f4b5fa4792814961cf6ae27f1dc8863703 Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Tue, 13 Jul 2021 21:55:31 +0900 Subject: [PATCH 17/43] Add reboiler_or_reactor --- examples/simple/data.csv | 10 +++++----- examples/simple/main.py | 3 ++- src/pyheatintegration/heat_exchanger.py | 2 ++ src/pyheatintegration/pinch_analyzer.py | 14 ++++++++++++-- src/pyheatintegration/plot_segment.py | 11 ++++++++--- src/pyheatintegration/segment.py | 15 ++++++++++----- src/pyheatintegration/stream.py | 4 ++++ src/pyheatintegration/tq_diagram.py | 6 ++++-- 8 files changed, 47 insertions(+), 18 deletions(-) diff --git a/examples/simple/data.csv b/examples/simple/data.csv index 6ed871b..59103fd 100644 --- a/examples/simple/data.csv +++ b/examples/simple/data.csv @@ -1,5 +1,5 @@ -id,input_temperature,output_temperature,heat_flow,type,state,cost -a,40.0,90.0,150.0,1,1,0 -b,80.0,110.0,180.0,1,1,0 -A,125.0,80.0,160.0,2,1,0 -B,100.0,60.0,160.0,2,1,0 \ No newline at end of file +id,input_temperature,output_temperature,heat_flow,type,state,cost,reboiler_or_reactor +a,40.0,90.0,150.0,1,1,0, +b,80.0,110.0,180.0,1,1,0, +A,125.0,80.0,160.0,2,1,0, +B,100.0,60.0,160.0,2,1,0, \ No newline at end of file diff --git a/examples/simple/main.py b/examples/simple/main.py index 9883a51..9423141 100644 --- a/examples/simple/main.py +++ b/examples/simple/main.py @@ -7,7 +7,7 @@ def main(): - df = pd.read_csv("./data.csv") + df = pd.read_csv("./data.csv").fillna({'reboiler_or_reactor': ''}) streams = [ Stream( row.input_temperature, @@ -16,6 +16,7 @@ def main(): StreamType(row.type), StreamState(row.state), cost=row.cost, + reboiler_or_reactor=bool(row.reboiler_or_reactor), id_=row.id ) for _, row in df.iterrows()] diff --git a/src/pyheatintegration/heat_exchanger.py b/src/pyheatintegration/heat_exchanger.py index a842539..51bb9d9 100644 --- a/src/pyheatintegration/heat_exchanger.py +++ b/src/pyheatintegration/heat_exchanger.py @@ -86,6 +86,7 @@ class HeatExchanger: area_counterflow (float): 向流の場合の必要面積 [m2]。 hot_plot_segment (PlotSegment): 与熱流体のプロットセグメント。 cold_plot_segment (PlotSegment): 受熱流体のプロットセグメント。 + reboiler_or_reactor (bool): リボイラーもしくは反応器で用いるか。 """ def __init__( @@ -103,6 +104,7 @@ def __init__( self.cold_stream_uuid = self.cold_plot_segment.uuid self.hot_stream_state = self.hot_plot_segment.state self.cold_stream_state = self.cold_plot_segment.state + self.reboiler_or_reactor = self.hot_plot_segment.reboiler_or_reactor | self.cold_plot_segment.reboiler_or_reactor self.overall_heat_transfer_coefficient = get_overall_heat_transfer_coefficient( self.hot_stream_state, diff --git a/src/pyheatintegration/pinch_analyzer.py b/src/pyheatintegration/pinch_analyzer.py index f735c2d..f5f0546 100644 --- a/src/pyheatintegration/pinch_analyzer.py +++ b/src/pyheatintegration/pinch_analyzer.py @@ -95,8 +95,14 @@ def __init__( HeatExchanger(heat_range, hot_plot_segment, cold_plot_segment) ) + for heat_exchanger in self.heat_exchangers: + print(heat_exchanger.reboiler_or_reactor) + self.heat_exchanger_cost = sum( - self.calculate_heat_exchanger_cost(heat_exchanger.area_counterflow) + self.calculate_heat_exchanger_cost( + heat_exchanger.area_counterflow, + heat_exchanger.reboiler_or_reactor + ) for heat_exchanger in self.heat_exchangers ) @@ -140,7 +146,7 @@ def create_tq_merged(self) -> tuple[list[Line], list[Line]]: def calculate_heat_exchanger_cost( self, area: HeatExchanger, - k: float = 1.0 + reboiler_or_reactor: bool = False ) -> float: """熱交換器にかかるコストを返します。 @@ -151,4 +157,8 @@ def calculate_heat_exchanger_cost( Returns: float: コスト[円]。 """ + if reboiler_or_reactor: + k = 2.0 + else: + k = 1.0 return 1_500_000 * math.pow(area, 0.65) * k diff --git a/src/pyheatintegration/plot_segment.py b/src/pyheatintegration/plot_segment.py index 994329a..a5168fc 100644 --- a/src/pyheatintegration/plot_segment.py +++ b/src/pyheatintegration/plot_segment.py @@ -22,7 +22,8 @@ def __init__( start_temperature: float = 0.0, finish_temperature: float = 0.0, uuid_: Optional[str] = None, - state: StreamState = StreamState.UNKNOWN + state: StreamState = StreamState.UNKNOWN, + reboiler_or_reactor: bool = False ): self.heat_range = HeatRange(start_heat, finish_heat) self.temperature_range = TemperatureRange(start_temperature, finish_temperature) @@ -32,6 +33,7 @@ def __init__( self.uuid = uuid_ self.state = state + self.reboiler_or_reactor = reboiler_or_reactor def __str__(self) -> str: return ( @@ -162,7 +164,9 @@ def merge(self, other: PlotSegment) -> PlotSegment: other.finish_heat(), self.start_temperature(), other.finish_temperature(), - self.uuid + self.uuid, + self.state, + self.reboiler_or_reactor ) @@ -199,7 +203,8 @@ def get_plot_segments( *heat_range(), *plot_segment.temperatures_at_heats(heat_range()), plot_segment.uuid, - plot_segment.state + plot_segment.state, + plot_segment.reboiler_or_reactor ) ) return res diff --git a/src/pyheatintegration/segment.py b/src/pyheatintegration/segment.py index de125a9..0af00fc 100644 --- a/src/pyheatintegration/segment.py +++ b/src/pyheatintegration/segment.py @@ -132,7 +132,8 @@ def init_plot_segments_separated_streams( finish_heat, *temperature_range(), str(streams[i].id_), - streams[i].state + streams[i].state, + streams[i].reboiler_or_reactor )) start_heat += heat return res @@ -178,13 +179,15 @@ def split(self, minimum_approach_temp_diff: float) -> None: *heat_range(), *self.hot_temperature_range(), hot_plot_segment.uuid, - hot_plot_segment.state + hot_plot_segment.state, + hot_plot_segment.reboiler_or_reactor ) cold_heat_range_plot_segment[heat_range] = PlotSegment( *heat_range(), *self.cold_temperature_range(), cold_plot_segment.uuid, - cold_plot_segment.state + cold_plot_segment.state, + cold_plot_segment.reboiler_or_reactor ) for heat_range_ in self.heat_ranges: @@ -204,7 +207,8 @@ def split(self, minimum_approach_temp_diff: float) -> None: *heat_range_(), *self.hot_temperature_range(), hot_plot_segment.uuid, - hot_plot_segment.state + hot_plot_segment.state, + hot_plot_segment.reboiler_or_reactor ) if cold_plot_segment_ is not None and cold_plot_segment_.uuid == cold_plot_segment.uuid: @@ -212,7 +216,8 @@ def split(self, minimum_approach_temp_diff: float) -> None: *heat_range_(), *self.cold_temperature_range(), cold_plot_segment.uuid, - cold_plot_segment.state + cold_plot_segment.state, + cold_plot_segment.reboiler_or_reactor ) self.hot_plot_segments_splitted = sorted(list(hot_heat_range_plot_segment.values())) diff --git a/src/pyheatintegration/stream.py b/src/pyheatintegration/stream.py index edce867..861c407 100644 --- a/src/pyheatintegration/stream.py +++ b/src/pyheatintegration/stream.py @@ -20,6 +20,7 @@ def __init__( type_: StreamType, state: StreamState = StreamState.UNKNOWN, cost: float = 0.0, + reboiler_or_reactor: bool = False, id_: str = '' ): """流体を表すクラス。 @@ -30,6 +31,7 @@ def __init__( heat_flow (float): 熱量。 type_ (StreamType): 流体種。 cost (float, optional): 流体のコスト。外部流体の場合のみ設定できる。 + reboiler_or_reactor (bool): 流体がリボイラーまたは反応器で用いられるか。 id_ (str): 流体を区別する識別子。 Raises: @@ -97,6 +99,8 @@ def __init__( f'出口温度: {output_temperature}' ) + self.reboiler_or_reactor = reboiler_or_reactor + def __repr__(self) -> str: return ( f"Stream({self.input_temperature()}, " diff --git a/src/pyheatintegration/tq_diagram.py b/src/pyheatintegration/tq_diagram.py index f1b038d..ab4d20a 100644 --- a/src/pyheatintegration/tq_diagram.py +++ b/src/pyheatintegration/tq_diagram.py @@ -268,13 +268,15 @@ def _merge_segments( *merged_heat_range(), *merged_hot_temp_range(), hot_plot_segment.uuid, - hot_plot_segment.state + hot_plot_segment.state, + hot_plot_segment.reboiler_or_reactor )) merged_cold_plot_segments.append(PlotSegment( *merged_heat_range(), *merged_cold_temp_range(), cold_plot_segment.uuid, - cold_plot_segment.state + cold_plot_segment.state, + cold_plot_segment.reboiler_or_reactor )) merged_heat_ranges.extend([heat_ranges[i], heat_ranges[i + 1]]) From cb6c5db3af9bf222a3008a66c337e6fc7500275a Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Tue, 13 Jul 2021 22:17:45 +0900 Subject: [PATCH 18/43] Add StreamType.AUTO --- src/pyheatintegration/enums.py | 2 ++ src/pyheatintegration/stream.py | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/pyheatintegration/enums.py b/src/pyheatintegration/enums.py index 5833523..b051377 100644 --- a/src/pyheatintegration/enums.py +++ b/src/pyheatintegration/enums.py @@ -8,6 +8,7 @@ class StreamType(Enum): HOT = 2 EXTERNAL_COLD = 3 EXTERNAL_HOT = 4 + AUTO = 5 def describe(self) -> str: return STREAMTYPE_STR[self.name] @@ -18,6 +19,7 @@ def describe(self) -> str: 'HOT': 'hot', 'EXTERNAL_COLD': 'external cold', 'EXTERNAL_HOT': 'external hot', + 'AUTO': 'auto' } diff --git a/src/pyheatintegration/stream.py b/src/pyheatintegration/stream.py index 861c407..6423589 100644 --- a/src/pyheatintegration/stream.py +++ b/src/pyheatintegration/stream.py @@ -17,7 +17,7 @@ def __init__( input_temperature: float, output_temperature: float, heat_flow: float, - type_: StreamType, + type_: StreamType = StreamType.AUTO, state: StreamState = StreamState.UNKNOWN, cost: float = 0.0, reboiler_or_reactor: bool = False, @@ -44,7 +44,17 @@ def __init__( else: self.id_ = str(uuid.uuid4()) - self.type_ = type_ + if type_ == StreamType.AUTO: + if input_temperature < output_temperature: + self.type_ = StreamType.COLD + elif input_temperature > output_temperature: + self.type_ = StreamType.HOT + else: + raise InvalidStreamError( + '入り口温度と出口温度が同じ流体の種類は明示的に指定する必要があります。' + ) + else: + self.type_ = type_ if self.is_internal() and heat_flow == 0: raise InvalidStreamError( From 65e1613bdc3f4f887e54e2eef547e652294d6aa5 Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Tue, 13 Jul 2021 22:47:21 +0900 Subject: [PATCH 19/43] Remove print --- src/pyheatintegration/pinch_analyzer.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/pyheatintegration/pinch_analyzer.py b/src/pyheatintegration/pinch_analyzer.py index f5f0546..e4f45ac 100644 --- a/src/pyheatintegration/pinch_analyzer.py +++ b/src/pyheatintegration/pinch_analyzer.py @@ -95,9 +95,6 @@ def __init__( HeatExchanger(heat_range, hot_plot_segment, cold_plot_segment) ) - for heat_exchanger in self.heat_exchangers: - print(heat_exchanger.reboiler_or_reactor) - self.heat_exchanger_cost = sum( self.calculate_heat_exchanger_cost( heat_exchanger.area_counterflow, From 7eaa9df46510520591bfd7dd2b4e1e5c54f6aeb3 Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Tue, 13 Jul 2021 22:48:40 +0900 Subject: [PATCH 20/43] Update type --- src/pyheatintegration/pinch_analyzer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyheatintegration/pinch_analyzer.py b/src/pyheatintegration/pinch_analyzer.py index e4f45ac..6f2713d 100644 --- a/src/pyheatintegration/pinch_analyzer.py +++ b/src/pyheatintegration/pinch_analyzer.py @@ -142,13 +142,13 @@ def create_tq_merged(self) -> tuple[list[Line], list[Line]]: def calculate_heat_exchanger_cost( self, - area: HeatExchanger, + area: float, reboiler_or_reactor: bool = False ) -> float: """熱交換器にかかるコストを返します。 Args: - heat_exchanger (HeatExchanger): 熱交換器。 + area (float): 熱交換器の面積。 k (float): 係数。リボイラーまたは反応器の場合は2 Returns: From d5477b40ea7a24b2b716e360d07ea808e67947b3 Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Tue, 13 Jul 2021 23:06:21 +0900 Subject: [PATCH 21/43] Update Stream --- src/pyheatintegration/stream.py | 141 +++++++++++++++++++++++--------- 1 file changed, 104 insertions(+), 37 deletions(-) diff --git a/src/pyheatintegration/stream.py b/src/pyheatintegration/stream.py index 6423589..de9eef9 100644 --- a/src/pyheatintegration/stream.py +++ b/src/pyheatintegration/stream.py @@ -11,6 +11,43 @@ class Stream: + r"""熱交換を行う流体を表すクラス。 + + Args: + input_temperature (float): 入り口温度 [℃]。 + output_temperature (float): 出口温度 [℃]。 + heat_flow (float): 熱量 [W]。 + type\_ (StreamType): 流体の種類。 + state (StreamState): 流体の状態。 + cost (float): 流体のコスト [円/J]。外部流体の場合のみ設定可能。 + reboiler_or_reactor (bool): 流体がリボイラーまたは反応器で用いられるかどうか。 + id\_ (str): 流体を区別する識別子。 + + Attributes: + id_ (str): 流体を区別する識別子。 + temperature_range (TemperatureRange): 温度範囲。 + heat_flow (float): 熱量 [W]。 + cost (float): コスト [円/J]。 + type_ (StreamType): 流体の種類。 + state (StreamState): 流体の状態。 + reboiler_or_reactor (bool): 流体がリボイラーまたは反応器で用いられるかどうか。 + + Raises: + InvalidStreamError: + 入り口温度と出口温度の大小関係と流体種の関係が不正である場合。また、外部流体の熱 + 量が0以外の場合および外部流体以外の流体の熱量が0である場合。外部流体以外にコスト + を設定した場合。 + + Examples: + >>> Stream(0, 20, 300) + Stream(0, 20, 300, type_=StreamType.COLD, state=StreamState.UNKNOWN, cost=0.0, reboiler_or_reactor=False, id_="e0c1facb-538b-417f-862c-5cf8043ec075") + >>> Stream(20, 0, 300) + Stream(20, 0, 300, type_=StreamType.HOT, state=StreamState.UNKNOWN, cost=0.0, reboiler_or_reactor=False, id_="1a193fc7-9f34-4e6a-8e99-615d40be1b20") + >>> Stream(0, 0, 0) + Traceback (most recent call last): + ... + InvalidStreamError: 入り口温度と出口温度が同じ流体の種類は明示的に指定する必要があります。 + """ def __init__( self, @@ -23,22 +60,6 @@ def __init__( reboiler_or_reactor: bool = False, id_: str = '' ): - """流体を表すクラス。 - - Args: - input_temperature (float): 入口温度。 - output_temperature (float): 出口温度。 - heat_flow (float): 熱量。 - type_ (StreamType): 流体種。 - cost (float, optional): 流体のコスト。外部流体の場合のみ設定できる。 - reboiler_or_reactor (bool): 流体がリボイラーまたは反応器で用いられるか。 - id_ (str): 流体を区別する識別子。 - - Raises: - InvalidStreamError: - 入口温度と出口温度の大小関係と流体種の関係が不正である場合。また、外部流体の - 熱量が0以外の場合、および外部流体以外の流体の熱量が0である場合。 - """ if id_: self.id_ = id_ else: @@ -72,23 +93,23 @@ def __init__( raise InvalidStreamError( "受熱流体は入り口温度が出口温度より低い必要があります。" f"流体の種類: {self.type_.describe()} " - f"入り口温度: {input_temperature} " - f"出口温度: {output_temperature}" + f"入り口温度 [℃]: {input_temperature} " + f"出口温度 [℃]: {output_temperature}" ) if self.is_hot() and input_temperature < output_temperature: raise InvalidStreamError( "与熱流体は入り口温度が出口温度より高い必要があります。" f"流体の種類: {self.type_.describe()} " - f"入り口温度: {input_temperature} " - f"出口温度: {output_temperature}" + f"入り口温度 [℃]: {input_temperature} " + f"出口温度 [℃]: {output_temperature}" ) if self.is_internal() and cost != 0: raise InvalidStreamError( "外部流体以外にはコストを設定できません。" f"流体の種類: {self.type_.describe()} " - f"コスト: {cost} " + f"コスト [円]: {cost} " ) self.cost = cost @@ -113,32 +134,42 @@ def __init__( def __repr__(self) -> str: return ( - f"Stream({self.input_temperature()}, " - f"{self.output_temperature()}, " - f"{self.heat_flow}, " - f"{self.type_}, " - f"{self.cost})" + f'Stream(' + f'{self.input_temperature()}, ' + f'{self.output_temperature()}, ' + f'{self.heat_flow}, ' + f'type_={self.type_}, ' + f'state={self.state}, ' + f'cost={self.cost}, ' + f'reboiler_or_reactor={self.reboiler_or_reactor}, ' + f'id_="{self.id_}"' + ')' ) def __str__(self) -> str: return ( - f"{self.type_.describe()}, " - f"input [℃]: {self.input_temperature()}, " - f"output [℃]: {self.output_temperature()}, " - f"heat flow [W]: {self.heat_flow}" + f'{self.type_.describe()}, ' + f'input [℃]: {self.input_temperature()}, ' + f'output [℃]: {self.output_temperature()}, ' + f'heat flow [W]: {self.heat_flow}' ) def __format__(self, format_spec: str) -> str: description = self.type_.describe() return ( - f"{description},{'':{14 - len(description)}s}" - f"input [℃]: {self.input_temperature().__format__(format_spec)}, " - f"output [℃]: {self.output_temperature().__format__(format_spec)}, " - f"heat flow [W]: {self.heat_flow.__format__(format_spec)}" + f'{description},{"":{14 - len(description)}s}' + f'input [℃]: {self.input_temperature().__format__(format_spec)}, ' + f'output [℃]: {self.output_temperature().__format__(format_spec)}, ' + f'heat flow [W]: {self.heat_flow.__format__(format_spec)}' ) def sort_key(self) -> float: """ソートの際に用いるキーを返します。 + + 与熱流体は出口温度、受熱温度は入口温度を返します。 + + Returns: + float: ソート時にキーとなる値。 """ if self.is_hot(): return self.output_temperature() @@ -146,31 +177,49 @@ def sort_key(self) -> float: def is_external(self) -> bool: """外部流体であるかを返します。 + + Returns: + bool: 外部流体であるかどうか。 """ return self.type_ in [StreamType.EXTERNAL_HOT, StreamType.EXTERNAL_COLD] def is_internal(self) -> bool: """外部流体以外であるかを返します。 + + Returns: + bool: 外部流体以外であるかどうか。 """ return not self.is_external() def is_hot(self) -> bool: """与熱流体であるかを返します。 + + Returns: + bool: 与熱流体であるかどうか。 """ return self.type_ in [StreamType.HOT, StreamType.EXTERNAL_HOT] def is_cold(self) -> bool: """受熱流体であるかを返します。 + + Returns: + bool: 受熱流体であるかどうか。 """ return not self.is_hot() def is_isothermal(self) -> bool: - """温度変化がない流体かを返します。 + """等温流体かを返します。 + + Returns: + bool: 等温流体であるかどうか。 """ return math.isclose(self.temperature_range.delta, 0.0) def input_temperature(self) -> float: """入り口温度を返します。 + + Returns: + float: 入り口温度。 """ if self.is_hot(): return self.temperature_range.finish @@ -178,6 +227,9 @@ def input_temperature(self) -> float: def output_temperature(self) -> float: """出口温度を返します。 + + Returns: + float: 出口温度。 """ if self.is_hot(): return self.temperature_range.start @@ -185,16 +237,25 @@ def output_temperature(self) -> float: def temperature(self) -> float: """温度変化を返します。 + + Returns: + float: 温度変化 [℃]。 """ return self.temperature_range.delta def heat(self) -> float: """熱量を返します。 + + Returns: + float: 熱量 [W]。 """ return self.heat_flow def temperatures(self) -> tuple[float, float]: """入り口温度と出口温度を返します。 + + Returns: + tuple[float, float]: 温度範囲。 """ return self.temperature_range() @@ -211,6 +272,9 @@ def contain_temperature(self, temperature: float) -> bool: Args: temperature (float): 検証したい温度。 + + Returns: + bool: 与えられた温度をとるかどうか。 """ return temperature in self.temperature_range @@ -221,6 +285,9 @@ def contain_temperatures(self, temperatures: Iterable[float]) -> bool: Args: temperatures (list[float]): 検証したい温度のリスト。 + + Returns: + bool: 与えられた複数の温度をとるかどうか。 """ return all(map(lambda t: t in self.temperature_range, temperatures)) @@ -235,8 +302,8 @@ def update_temperature( 度変化の比に従って更新します。 Args: - input_temperature (float): 更新する入り口温度。 - output_temperature (float): 更新する出口温度。 + input_temperature (float): 更新する入り口温度 [℃]。 + output_temperature (float): 更新する出口温度 [℃]。 Raises: ValueError: 等温流体に対して温度を更新しようとした場合。 From 30d024f5247c26a57cb49bf9b1fc8b7436b67425 Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Tue, 13 Jul 2021 23:14:07 +0900 Subject: [PATCH 22/43] Update Stream --- src/pyheatintegration/stream.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/pyheatintegration/stream.py b/src/pyheatintegration/stream.py index de9eef9..944228d 100644 --- a/src/pyheatintegration/stream.py +++ b/src/pyheatintegration/stream.py @@ -339,10 +339,10 @@ def get_temperature_range_streams( list[TemperatureRange], defaultdict[TemperatureRange, set[Stream]] ]: - """温度領域と、温度領域に属する流体を返します。 + """温度領域に属する流体を返します。 Args: - streams (list[Stream]): 流体。 + streams (list[Stream]): 流体のリスト。 Returns: list[list[TemperatureRange], defaultdict[TemperatureRange, set[Stream]]: @@ -379,6 +379,15 @@ def get_temperature_range_streams( def get_temperature_range_heats( streams: list[Stream] ) -> tuple[list[TemperatureRange], dict[TemperatureRange, float]]: + """温度領域ごとに必要な熱量を返します。 + + Args: + streams (list[Stream]): 流体のリスト。 + + Returns: + tuple[list[TemperatureRange], dict[TemperatureRange, float]]: + 温度領域、温度領域ごとの必要熱量。 + """ temp_ranges, temp_range_streams = get_temperature_range_streams(streams) tem_range_heats = { temp_range: sum(stream.heat() for stream in temp_range_streams[temp_range]) From b60c2a9b06585a141b941351dd542804592fb083 Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Tue, 13 Jul 2021 23:17:11 +0900 Subject: [PATCH 23/43] Update temperature_range --- src/pyheatintegration/temperature_range.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pyheatintegration/temperature_range.py b/src/pyheatintegration/temperature_range.py index a8a8477..812e097 100644 --- a/src/pyheatintegration/temperature_range.py +++ b/src/pyheatintegration/temperature_range.py @@ -56,6 +56,11 @@ def is_continuous(temp_ranges_: list[TemperatureRange]) -> Optional[tuple[float, Returns: Optional[tuple[float, float]]: 領域が連続であるか。 + + Examples: + >>> is_continuous([TemperatureRange(0, 10), TemperatureRange(10, 20)]) + >>> is_continuous([TemperatureRange(0, 10), TemperatureRange(20, 40)]) + (10, 20) """ temp_ranges = sorted(temp_ranges_) for i in range(len(temp_ranges)): From 4f08cb94f4f3c3aab96e91ee3f9c5690a0538efc Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Tue, 13 Jul 2021 23:24:05 +0900 Subject: [PATCH 24/43] Update temperature_range --- src/pyheatintegration/temperature_range.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/pyheatintegration/temperature_range.py b/src/pyheatintegration/temperature_range.py index 812e097..8d571de 100644 --- a/src/pyheatintegration/temperature_range.py +++ b/src/pyheatintegration/temperature_range.py @@ -79,27 +79,27 @@ def get_temperatures(temp_ranges_: list[TemperatureRange]) -> list[float]: Returns: list[float]: 温度のリスト。 + Raises: + ValueError: 温度領域が連続でない場合。 + Examples: - >>> get_heats([TemperatureRange(0, 10), TemperatureRange(10, 20)]) + >>> get_temperatures([TemperatureRange(0, 10), TemperatureRange(10, 20)]) [0, 10, 20] + >>> get_temperatures([TemperatureRange(0, 10), TemperatureRange(30, 40)]) + Traceback (most recent call last): + ... + ValueError: 終了値と開始値が異なります。終了値: 10.000 開始値: 30.000 """ temp_ranges = sorted(temp_ranges_) if (values := is_continuous(temp_ranges)) is not None: raise ValueError( f'終了値と開始値が異なります。' - f'finish: {values[0]}, ' - f'start: {values[1]}' + f'終了値: {values[0]:.3f} ' + f'開始値: {values[1]:.3f}' ) + res: list[float] = [] for i in range(len(temp_ranges)): - if i != len(temp_ranges) - 1: - if temp_ranges[i].finish != temp_ranges[i + 1].start: - raise ValueError( - f'終了値と開始値が異なります。' - f'finish: {temp_ranges[i].finish}, ' - f'start: {temp_ranges[i + 1].start}' - ) - res.append(temp_ranges[i].start) if i == len(temp_ranges) - 1: res.append(temp_ranges[i].finish) From 0573362af6fbe3ffefd0cd8968661f03feb3afc2 Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Tue, 13 Jul 2021 23:32:27 +0900 Subject: [PATCH 25/43] Update pinch_analyzer --- src/pyheatintegration/pinch_analyzer.py | 67 ++++++++++++++++--------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/src/pyheatintegration/pinch_analyzer.py b/src/pyheatintegration/pinch_analyzer.py index 6f2713d..fc04ea8 100644 --- a/src/pyheatintegration/pinch_analyzer.py +++ b/src/pyheatintegration/pinch_analyzer.py @@ -10,8 +10,45 @@ from .tq_diagram import TQDiagram, get_possible_minimum_temp_diff_range +def calculate_heat_exchanger_cost( + area: float, + reboiler_or_reactor: bool = False +) -> float: + """熱交換器にかかるコストを返します。 + + Args: + area (float): 熱交換器の面積。 + k (float): 係数。リボイラーまたは反応器の場合は2 + + Returns: + float: コスト[円]。 + """ + if reboiler_or_reactor: + k = 2.0 + else: + k = 1.0 + return 1_500_000 * math.pow(area, 0.65) * k + + class PinchAnalyzer: """エントリーポイント。 + + 解析を行う場合はこのクラス経由で扱う。 + + Args: + streams_ (list[Stream]): 流体のリスト。 + minimum_approach_temp_diff (float): 最小接近温度差 [℃]。 + ignore_maximum (bool): 最小接近温度差の最大値のチェックを無視するかどうか。 + + Attributes: + gcc (GrandCompositeCurve): グランドコンポジットカーブ。 + tq (TQDiagram): TQ線図 + streams (list[Stream]): 流体のリスト。 + pinch_point_temp (float): ピンチポイントの温度 [℃]。 + heat_exchangers (list[HeatExchanger]): 熱交換器のリスト。 + heat_exchanger_cost (float): 熱交換器のコスト。 + + """ def __init__( @@ -96,7 +133,7 @@ def __init__( ) self.heat_exchanger_cost = sum( - self.calculate_heat_exchanger_cost( + calculate_heat_exchanger_cost( heat_exchanger.area_counterflow, heat_exchanger.reboiler_or_reactor ) @@ -109,7 +146,7 @@ def create_grand_composite_curve(self) -> tuple[list[float], list[float]]: return self.gcc.heats, self.gcc.temps def create_tq(self) -> tuple[list[Line], list[Line]]: - """tq線図をを描くために必要な直線と熱量変化帯を返します。 + """tq線図をを描くために必要な与熱複合線および受熱複合線を返します。 """ return ( self.tq.hot_lines, @@ -117,7 +154,7 @@ def create_tq(self) -> tuple[list[Line], list[Line]]: ) def create_tq_separated(self) -> tuple[list[Line], list[Line]]: - """流体ごとに分割したtq線図をを描くために必要な直線と熱量変化帯を返します。 + """流体ごとに分割したtq線図をを描くために必要な与熱複合線および受熱複合線を返します。 """ return ( self.tq.hot_lines_separated, @@ -125,7 +162,7 @@ def create_tq_separated(self) -> tuple[list[Line], list[Line]]: ) def create_tq_splitted(self) -> tuple[list[Line], list[Line]]: - """流体ごとに分割し、最小接近温度差の条件を満たしたtq線図をを描くために必要な直線を返します。 + """流体ごとに分割し、最小接近温度差の条件を満たしたtq線図をを描くために必要な与熱複合線および受熱複合線を返します。 """ return ( self.tq.hot_lines_splitted, @@ -133,29 +170,9 @@ def create_tq_splitted(self) -> tuple[list[Line], list[Line]]: ) def create_tq_merged(self) -> tuple[list[Line], list[Line]]: - """流体ごとに分割し、最小接近温度差の条件を満たしたtq線図をを描くために必要な直線を返します。 + """結合可能な熱交換器を結合したtq線図をを描くために必要な与熱複合線および受熱複合線を返します。 """ return ( self.tq.hot_lines_merged, self.tq.cold_lines_merged ) - - def calculate_heat_exchanger_cost( - self, - area: float, - reboiler_or_reactor: bool = False - ) -> float: - """熱交換器にかかるコストを返します。 - - Args: - area (float): 熱交換器の面積。 - k (float): 係数。リボイラーまたは反応器の場合は2 - - Returns: - float: コスト[円]。 - """ - if reboiler_or_reactor: - k = 2.0 - else: - k = 1.0 - return 1_500_000 * math.pow(area, 0.65) * k From 6e10a2087a0a8b6e4bcfda595768d67375928051 Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Tue, 13 Jul 2021 23:35:58 +0900 Subject: [PATCH 26/43] Add Raises to pinch_analyzer --- src/pyheatintegration/pinch_analyzer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pyheatintegration/pinch_analyzer.py b/src/pyheatintegration/pinch_analyzer.py index fc04ea8..03cb39b 100644 --- a/src/pyheatintegration/pinch_analyzer.py +++ b/src/pyheatintegration/pinch_analyzer.py @@ -48,7 +48,9 @@ class PinchAnalyzer: heat_exchangers (list[HeatExchanger]): 熱交換器のリスト。 heat_exchanger_cost (float): 熱交換器のコスト。 - + Raises: + ValueError: 流体のidが重複している場合。また、最小接近温度差の値が不正な場合。 + RuntimeError: 受熱流体、与熱流体が一つも指定されていない場合。 """ def __init__( From 9f8fd709294ea5de4d5e7e3a5a9ef0d560d732f5 Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Tue, 13 Jul 2021 23:43:16 +0900 Subject: [PATCH 27/43] Update heat_exchanger docs --- src/pyheatintegration/heat_exchanger.py | 34 ++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/pyheatintegration/heat_exchanger.py b/src/pyheatintegration/heat_exchanger.py index 51bb9d9..a5dca68 100644 --- a/src/pyheatintegration/heat_exchanger.py +++ b/src/pyheatintegration/heat_exchanger.py @@ -72,6 +72,11 @@ def get_overall_heat_transfer_coefficient( class HeatExchanger: """熱交換器を表すクラス。 + Args: + heat_range (HeatRange): 熱量領域。 + hot_plot_segment (PlotSegment): 与熱流体のプロットセグメント。 + cold_plot_segment (PlotSegment): 受熱流体のプロットセグメント。 + Attributes: heat_range (HeatRange): 熱交換の範囲。 hot_stream_uuid (str): 与熱流体のid。 @@ -123,10 +128,10 @@ def __init__( def __repr__(self) -> str: return ( - "HeatExchanger(" - f"{self.heat_range}, " - f"{self.hot_plot_segment}, " - f"{self.cold_plot_segment})" + 'HeatExchanger(' + f'{self.heat_range}, ' + f'{self.hot_plot_segment}, ' + f'{self.cold_plot_segment})' ) def __str__(self) -> str: @@ -141,6 +146,13 @@ def __lt__(self, other) -> bool: return self.heat_range < other.heat_range def init_lmtd_pararell_flow(self) -> Optional[float]: + """並流の場合の対数平均温度差を返します。 + + 並流が不可能な場合はNoneを返します。 + + Returns: + Optional[float]: 並流の場合の対数平均温度差。並流が不可能な場合はNone。 + """ hot_low_temp, hot_high_temp = self.hot_temperature_range() cold_low_temp, cold_high_temp = self.cold_temperature_range() @@ -156,6 +168,11 @@ def init_lmtd_pararell_flow(self) -> Optional[float]: return (start_temp_diff - finish_temp_diff) / math.log(start_temp_diff / finish_temp_diff) def init_lmtd_counterflow(self) -> float: + """向流の場合の対数平均温度差を返します。 + + Returns: + float: 向流の場合の対数平均温度差。 + """ hot_low_temp, hot_high_temp = self.hot_temperature_range() cold_low_temp, cold_high_temp = self.cold_temperature_range() @@ -169,6 +186,15 @@ def init_lmtd_counterflow(self) -> float: def merge_heat_exchangers(heat_exchanger: HeatExchanger, other: HeatExchanger) -> HeatExchanger: + """熱交換器を結合します。 + + Args: + heat_exchanger (HeatExchange): 熱交換器。 + other (HeatExchanger): 熱交換器(結合対象)。 + + Returns: + HeatExchanger: 結合後の熱交換器。 + """ heat_range = heat_exchanger.heat_range.merge(other.heat_range) hot_plot_segment = heat_exchanger.hot_plot_segment.merge(other.hot_plot_segment) cold_plot_segment = heat_exchanger.cold_plot_segment.merge(other.cold_plot_segment) From f22ec1f7cfd9d98f5db8d20ea0ad07758e6cb60a Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Wed, 14 Jul 2021 00:31:24 +0900 Subject: [PATCH 28/43] Update PlotSegment docs --- src/pyheatintegration/plot_segment.py | 32 +++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/pyheatintegration/plot_segment.py b/src/pyheatintegration/plot_segment.py index a5168fc..eaade9b 100644 --- a/src/pyheatintegration/plot_segment.py +++ b/src/pyheatintegration/plot_segment.py @@ -11,9 +11,24 @@ class PlotSegment: - uuid: str - heat_range: HeatRange - temperature_range: TemperatureRange + """プロットの一部を表すクラスです。 + + Args: + start_heat (float): 熱量の開始値。 + finish_heat (float): 熱量の終了値。 + start_temperature (float): 温度の開始値。 + finish_temperature (float): 温度の終了値。 + uuid_ (Optional[str]): uuid。対応する流体がある場合はそのid。 + state (StreamState): 対応する流体の状態。 + reboiler_or_reactor (bool): 対応する流体がリボイラーもしくは反応器で用いられるか。 + + Attributes: + heat_range (HeatRange): 熱量領域。 + temperature_range (TemperatureRange): 温度領域。 + uuid (str): uuid。対応する流体がある場合はそのid。 + state (StreamState): 対応する流体の状態。 + reboiler_or_reactor (bool): 対応する流体がリボイラーもしくは反応器で用いられるか。 + """ def __init__( self, @@ -33,7 +48,7 @@ def __init__( self.uuid = uuid_ self.state = state - self.reboiler_or_reactor = reboiler_or_reactor + self.state = reboiler_or_reactor def __str__(self) -> str: return ( @@ -171,6 +186,15 @@ def merge(self, other: PlotSegment) -> PlotSegment: def temp_diff(segment: PlotSegment, other: PlotSegment) -> tuple[float, float]: + """同じ熱量領域のプロットセグメントの入り口温度の差と出口温度の差を求める。 + + Args: + segment (PlotSegment): プロットセグメント。 + ohter (PlotSegment): プロットセグメント。 + + Returns: + tuple[float, float]: 入り口温度差、出口温度差。 + """ hot_temp_start, hot_temp_finish = segment.temperatures() cold_temp_start, cold_temp_finish = other.temperatures() From d72b81956a9df17bc30da682f181bffc425cd3b3 Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Wed, 14 Jul 2021 00:43:21 +0900 Subject: [PATCH 29/43] Update PlotSegment docs --- src/pyheatintegration/plot_segment.py | 113 +++++++++++++++++++++++++- 1 file changed, 109 insertions(+), 4 deletions(-) diff --git a/src/pyheatintegration/plot_segment.py b/src/pyheatintegration/plot_segment.py index eaade9b..67a6fe4 100644 --- a/src/pyheatintegration/plot_segment.py +++ b/src/pyheatintegration/plot_segment.py @@ -8,6 +8,7 @@ from .heat_range import HeatRange from .heat_range import is_continuous as is_continuous_heat_ranges from .temperature_range import TemperatureRange +from .line import Line class PlotSegment: @@ -48,7 +49,7 @@ def __init__( self.uuid = uuid_ self.state = state - self.state = reboiler_or_reactor + self.reboiler_or_reactor = reboiler_or_reactor def __str__(self) -> str: return ( @@ -102,43 +103,118 @@ def __ge__(self, other: object) -> bool: return NotImplemented return self.heat_range >= other.heat_range - def line(self) -> tuple[tuple[float, float], tuple[float, float]]: + def line(self) -> Line: + """直線の始点と終点を返します。 + + Returns: + Line: 直線。 + """ return ( (self.heats()[0], self.temperatures()[0]), (self.heats()[1], self.temperatures()[1]) ) def heats(self) -> tuple[float, float]: + """熱量の開始値と終了値を返します。 + + Returns: + tuple[float, float]: 熱量の開始値と終了値。 + """ return self.heat_range() def start_heat(self) -> float: + """熱量の開始値を返します。 + + Returns: + float: 熱量の開始値。 + """ return self.heat_range.start def finish_heat(self) -> float: + """熱量の終了値を返します。 + + Returns: + float: 熱量の終了値。 + """ return self.heat_range.finish def temperatures(self) -> tuple[float, float]: + """温度の開始値と終了値を返します。 + + Returns: + tuple[float, float]: 温度の開始値と終了値。 + """ return self.temperature_range() def start_temperature(self) -> float: + """温度の開始値を返します。 + + Returns: + float: 温度の開始値。 + """ return self.temperature_range.start def finish_temperature(self) -> float: + """温度の終了値を返します。 + + Returns: + float: 温度の終了値。 + """ return self.temperature_range.finish def contain_heat(self, heat: float) -> bool: + """熱量を含むかを返します。 + + Args: + heat (float): 含むかを検証する熱量。 + + Returns: + bool: 熱量を含むかどうか。 + """ return heat in self.heat_range def contain_heats(self, heats: Iterable[float]) -> bool: + """複数の熱量を含むかを返します。 + + Args: + heats (Iterable[float]): 含むかを検証する熱量。 + + Returns: + bool: 複数の熱量を含むかどうか。 + """ return all(self.contain_heat(heat) for heat in heats) def contain_temperature(self, temperature: float) -> bool: + """温度を含むかを返します。 + + Args: + temperature (float): 含むかを検証する温度。 + + Returns: + bool: 温度を含むかどうか。 + """ return temperature in self.temperature_range def temperatures_at_heats(self, heats: tuple[float, float]) -> tuple[float, float]: + """ある複数の熱量をとる温度を返します。 + + Args: + heats (tuple[float, float]): 温度を求めたい熱量。 + + Returns: + tuple[float, float]: ある熱量をとる温度。 + """ return self.temperature_at_heat(heats[0]), self.temperature_at_heat(heats[1]) def temperature_at_heat(self, heat: float) -> float: + """ある熱量をとる温度を返します。 + + Args: + heat (float): 温度を求めたい熱量。 + + Returns: + float: ある熱量をとる温度。 + """ if not self.contain_heat(heat): raise ValueError('heatを含んでいる必要があります。') @@ -149,8 +225,16 @@ def temperature_at_heat(self, heat: float) -> float: return slope * (heat - heat_left) + temp_left def heat_at_temperature(self, temperature: float) -> float: + """ある温度をとる熱量を返します。 + + Args: + temperature (float): 熱量を求めたい温度。 + + Returns: + float: ある温度をとる熱量。 + """ if not self.contain_temperature(temperature): - raise ValueError('heatを含んでいる必要があります。') + raise ValueError('temperatureを含んでいる必要があります。') heat_left, heat_right = self.heat_range() temp_left, temp_right = self.temperature_range() @@ -159,14 +243,35 @@ def heat_at_temperature(self, temperature: float) -> float: return 1 / slope * (temperature - temp_left) + heat_left def shift_heat(self, delta: float) -> None: + """熱量をずらします。 + + Args: + delta (float): ずらす値。 + """ self.heat_range.shift(delta) - def mergiable(self, other) -> bool: + def mergiable(self, other: PlotSegment) -> bool: + """プロットセグメントを結合可能かを返します。 + + Args: + other (PlotSegment): 結合できるかを検証したいプロットセグメント。 + + Returns: + bool: 結合可能かどうか。 + """ return (self.uuid == other.uuid) \ and (self.finish_heat() == other.start_heat()) \ and (self.finish_temperature() == other.start_temperature()) def merge(self, other: PlotSegment) -> PlotSegment: + """プロットセグメントを結合します。 + + Args: + other (PlotSegment): 結合したいプロットセグメント。 + + Returns: + PlotSegment: 結合後のプロットセグメント。 + """ if self.finish_heat() != other.start_heat(): raise ValueError("other's start_heat must be self's finish_heat.") if self.finish_temperature() != other.start_temperature(): From d221b695c743b3278cff345853f314b5c1dfb6cf Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Wed, 14 Jul 2021 00:58:45 +0900 Subject: [PATCH 30/43] Solve conflict --- src/pyheatintegration/pinch_analyzer.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/pyheatintegration/pinch_analyzer.py b/src/pyheatintegration/pinch_analyzer.py index 112c274..67b2137 100644 --- a/src/pyheatintegration/pinch_analyzer.py +++ b/src/pyheatintegration/pinch_analyzer.py @@ -59,6 +59,14 @@ def __init__( minimum_approach_temp_diff: float, ignore_maximum: bool = False ): + streams = deepcopy(streams_) + + id_set: set[str] = set() + for stream in streams: + if stream.id_ in id_set: + raise ValueError('流体のidは一意である必要があります。') + id_set.add(stream.id_) + hot_streams = sorted( [stream for stream in streams_ if stream.is_hot()], key=lambda s: s.output_temperature() @@ -73,20 +81,10 @@ def __init__( if not cold_streams: raise RuntimeError('受熱流体は少なくとも1つは指定する必要があります。') -<<<<<<< HEAD minimum_approach_temp_diff_range = get_possible_minimum_temp_diff_range( streams, ignore_maximum ) -======= - streams = hot_streams + cold_streams - for i, s in enumerate(streams): - s.set_id(i + 1) - - streams = deepcopy(streams) - - minimum_approach_temp_diff_range = get_possible_minimum_temp_diff_range(streams) ->>>>>>> main if minimum_approach_temp_diff not in minimum_approach_temp_diff_range: raise ValueError( From 408385ebdc89ea4d65afa493391ba597c58b6df3 Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Wed, 14 Jul 2021 01:26:38 +0900 Subject: [PATCH 31/43] Update import order --- src/pyheatintegration/heat_exchanger.py | 1 - src/pyheatintegration/pinch_analyzer.py | 2 +- src/pyheatintegration/plot_segment.py | 2 +- tests/heat_exchanger_test.py | 3 ++- tests/heat_range_test.py | 6 ++++-- tests/line_test.py | 2 +- tests/segment_test.py | 4 ++-- tests/temperature_range_test.py | 4 ++-- 8 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/pyheatintegration/heat_exchanger.py b/src/pyheatintegration/heat_exchanger.py index a5dca68..6e5a902 100644 --- a/src/pyheatintegration/heat_exchanger.py +++ b/src/pyheatintegration/heat_exchanger.py @@ -5,7 +5,6 @@ from .heat_range import HeatRange from .plot_segment import PlotSegment - OVERALL_HEAT_TRANSFER_COEFFICIENT = { StreamState.LIQUID: { StreamState.LIQUID: 300.0, diff --git a/src/pyheatintegration/pinch_analyzer.py b/src/pyheatintegration/pinch_analyzer.py index 67b2137..65e4285 100644 --- a/src/pyheatintegration/pinch_analyzer.py +++ b/src/pyheatintegration/pinch_analyzer.py @@ -1,5 +1,5 @@ -from copy import deepcopy import math +from copy import deepcopy from .grand_composite_curve import GrandCompositeCurve from .heat_exchanger import HeatExchanger diff --git a/src/pyheatintegration/plot_segment.py b/src/pyheatintegration/plot_segment.py index 67a6fe4..bb48ce2 100644 --- a/src/pyheatintegration/plot_segment.py +++ b/src/pyheatintegration/plot_segment.py @@ -7,8 +7,8 @@ from .enums import StreamState from .heat_range import HeatRange from .heat_range import is_continuous as is_continuous_heat_ranges -from .temperature_range import TemperatureRange from .line import Line +from .temperature_range import TemperatureRange class PlotSegment: diff --git a/tests/heat_exchanger_test.py b/tests/heat_exchanger_test.py index e477e70..fba245a 100644 --- a/tests/heat_exchanger_test.py +++ b/tests/heat_exchanger_test.py @@ -1,7 +1,8 @@ import unittest from src.pyheatintegration.enums import StreamState -from src.pyheatintegration.heat_exchanger import get_overall_heat_transfer_coefficient +from src.pyheatintegration.heat_exchanger import \ + get_overall_heat_transfer_coefficient class TestGetOverallHeatTransferCoefficient(unittest.TestCase): diff --git a/tests/heat_range_test.py b/tests/heat_range_test.py index 0ad5584..c365a6e 100644 --- a/tests/heat_range_test.py +++ b/tests/heat_range_test.py @@ -1,7 +1,9 @@ import unittest -from src.pyheatintegration.heat_range import (HeatRange, get_heat_ranges, - get_heats, is_continuous, get_detailed_heat_ranges) +from src.pyheatintegration.heat_range import (HeatRange, + get_detailed_heat_ranges, + get_heat_ranges, get_heats, + is_continuous) class TestHeatRange(unittest.TestCase): diff --git a/tests/line_test.py b/tests/line_test.py index e868cbb..1ae3e2f 100644 --- a/tests/line_test.py +++ b/tests/line_test.py @@ -1,6 +1,6 @@ import unittest -from src.pyheatintegration.line import y_range, extract_x +from src.pyheatintegration.line import extract_x, y_range class TestLine(unittest.TestCase): diff --git a/tests/segment_test.py b/tests/segment_test.py index 454a8b2..318f9ef 100644 --- a/tests/segment_test.py +++ b/tests/segment_test.py @@ -1,10 +1,10 @@ import unittest +from src.pyheatintegration.enums import StreamType from src.pyheatintegration.heat_range import HeatRange +from src.pyheatintegration.plot_segment import PlotSegment from src.pyheatintegration.segment import Segment from src.pyheatintegration.stream import Stream -from src.pyheatintegration.enums import StreamType -from src.pyheatintegration.plot_segment import PlotSegment class TeseSegment(unittest.TestCase): diff --git a/tests/temperature_range_test.py b/tests/temperature_range_test.py index 84fb5b9..aad1d9d 100644 --- a/tests/temperature_range_test.py +++ b/tests/temperature_range_test.py @@ -1,8 +1,8 @@ import unittest from src.pyheatintegration.temperature_range import ( - TemperatureRange, get_temperature_ranges, get_temperature_transition, - is_continuous, accumulate_heats) + TemperatureRange, accumulate_heats, get_temperature_ranges, + get_temperature_transition, is_continuous) class TestGetTemperatureRange(unittest.TestCase): From c62efbabd01d5a29f004d1feb49e46d974e5a03a Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Wed, 14 Jul 2021 01:44:20 +0900 Subject: [PATCH 32/43] Update hot_streams and cold_streams --- src/pyheatintegration/pinch_analyzer.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/pyheatintegration/pinch_analyzer.py b/src/pyheatintegration/pinch_analyzer.py index 65e4285..c913ee4 100644 --- a/src/pyheatintegration/pinch_analyzer.py +++ b/src/pyheatintegration/pinch_analyzer.py @@ -67,14 +67,8 @@ def __init__( raise ValueError('流体のidは一意である必要があります。') id_set.add(stream.id_) - hot_streams = sorted( - [stream for stream in streams_ if stream.is_hot()], - key=lambda s: s.output_temperature() - ) - cold_streams = sorted( - [stream for stream in streams_ if stream.is_cold()], - key=lambda s: s.input_temperature() - ) + hot_streams = [stream for stream in streams if stream.is_hot()] + cold_streams = [stream for stream in streams if stream.is_cold()] if not hot_streams: raise RuntimeError('与熱流体は少なくとも1つは指定する必要があります。') From 5442a0e51d099f6a05603465b807450d5c85203c Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Wed, 14 Jul 2021 01:45:57 +0900 Subject: [PATCH 33/43] Update docstring --- src/pyheatintegration/pinch_analyzer.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/pyheatintegration/pinch_analyzer.py b/src/pyheatintegration/pinch_analyzer.py index c913ee4..cb1f58d 100644 --- a/src/pyheatintegration/pinch_analyzer.py +++ b/src/pyheatintegration/pinch_analyzer.py @@ -31,9 +31,11 @@ def calculate_heat_exchanger_cost( class PinchAnalyzer: - """エントリーポイント。 + """流体のリストと最小接近温度差を設定し、グランドコンポジットカーブおよびTQ線図を作成します。 - 解析を行う場合はこのクラス経由で扱う。 + 解析を行う場合はこのクラス経由で扱います。このクラスを経由することで、流体のidが重複してい + ないことや、最小接近温度差が指定可能な値であるかを検証したのちに図を作成するため、予想外の + エラーが生じることを回避することができます。 Args: streams_ (list[Stream]): 流体のリスト。 @@ -42,7 +44,7 @@ class PinchAnalyzer: Attributes: gcc (GrandCompositeCurve): グランドコンポジットカーブ。 - tq (TQDiagram): TQ線図 + tq (TQDiagram): TQ線図。 streams (list[Stream]): 流体のリスト。 pinch_point_temp (float): ピンチポイントの温度 [℃]。 heat_exchangers (list[HeatExchanger]): 熱交換器のリスト。 From eb3a8697c8d819ad015eefa2c23f52787f9ff42a Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Wed, 14 Jul 2021 02:02:33 +0900 Subject: [PATCH 34/43] Add is_valid_streams --- src/pyheatintegration/stream.py | 32 ++++++++++++++++++++++++++++++++ tests/stream_test.py | 18 +++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/pyheatintegration/stream.py b/src/pyheatintegration/stream.py index 944228d..66b3c55 100644 --- a/src/pyheatintegration/stream.py +++ b/src/pyheatintegration/stream.py @@ -333,6 +333,38 @@ def update_heat(self, heat_flow: float) -> None: self.heat_flow = heat_flow +def is_valid_streams(streams: list[Stream]) -> bool: + """与熱流体および受熱流体がそれぞれ1つ以上含まれるかどうかを返します。 + + Args: + streams (list[Stream]): 流体のリスト。 + + Returns: + bool: 流体のリストが不正かどうか。 + + Example: + >>> is_valid_streams([ + Stream(40, 90, 150), + Stream(80, 110, 180), + Stream(125, 80, 160), + Stream(100, 60, 160) + ]) + True + >>> is_valid_streams([ + Stream(40, 90, 150), + Stream(80, 110, 180) + ]) + False + """ + + hot_streams = [stream for stream in streams if stream.is_hot()] + cold_streams = [stream for stream in streams if stream.is_cold()] + + if not hot_streams or not cold_streams: + return False + return True + + def get_temperature_range_streams( streams: list[Stream] ) -> tuple[ diff --git a/tests/stream_test.py b/tests/stream_test.py index 9cedb8e..ff335a2 100644 --- a/tests/stream_test.py +++ b/tests/stream_test.py @@ -3,7 +3,7 @@ from src.pyheatintegration.enums import StreamType from src.pyheatintegration.errors import InvalidStreamError -from src.pyheatintegration.stream import Stream, get_temperature_range_streams +from src.pyheatintegration.stream import Stream, get_temperature_range_streams, is_valid_streams from src.pyheatintegration.temperature_range import TemperatureRange @@ -67,5 +67,21 @@ def test_get_temperature_range_streams(self): self.assertEqual(result_stream.type_, expected_stream.type_) +class TestIsValidStreams(unittest.TestCase): + + def test_is_valid_streams(self): + self.assertTrue(is_valid_streams([ + Stream(40, 90, 150), + Stream(80, 110, 180), + Stream(125, 80, 160), + Stream(100, 60, 160) + ])) + + self.assertFalse(is_valid_streams([ + Stream(40, 90, 150), + Stream(80, 110, 180) + ])) + + if __name__ == '__main__': unittest.main() From b9d98b587a0dca50621d6d9e8c0ad0513a69a51a Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Wed, 14 Jul 2021 02:03:30 +0900 Subject: [PATCH 35/43] Update import order --- tests/stream_test.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/stream_test.py b/tests/stream_test.py index ff335a2..20908ca 100644 --- a/tests/stream_test.py +++ b/tests/stream_test.py @@ -3,7 +3,9 @@ from src.pyheatintegration.enums import StreamType from src.pyheatintegration.errors import InvalidStreamError -from src.pyheatintegration.stream import Stream, get_temperature_range_streams, is_valid_streams +from src.pyheatintegration.stream import (Stream, + get_temperature_range_streams, + is_valid_streams) from src.pyheatintegration.temperature_range import TemperatureRange From 1cae3bc425635ce0636dd5d2cb05b1e2ef47643c Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Wed, 14 Jul 2021 13:43:06 +0900 Subject: [PATCH 36/43] Update PinchAnalyzer --- src/pyheatintegration/pinch_analyzer.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/pyheatintegration/pinch_analyzer.py b/src/pyheatintegration/pinch_analyzer.py index cb1f58d..50ab68b 100644 --- a/src/pyheatintegration/pinch_analyzer.py +++ b/src/pyheatintegration/pinch_analyzer.py @@ -6,7 +6,7 @@ from .heat_range import HeatRange, get_detailed_heat_ranges from .line import Line from .plot_segment import PlotSegment, get_plot_segments -from .stream import Stream +from .stream import Stream, is_valid_streams from .tq_diagram import TQDiagram, get_possible_minimum_temp_diff_range @@ -66,16 +66,14 @@ def __init__( id_set: set[str] = set() for stream in streams: if stream.id_ in id_set: - raise ValueError('流体のidは一意である必要があります。') + raise ValueError( + '流体のidは一意である必要があります。' + f'重複しているid: {stream.id_}' + ) id_set.add(stream.id_) - hot_streams = [stream for stream in streams if stream.is_hot()] - cold_streams = [stream for stream in streams if stream.is_cold()] - - if not hot_streams: - raise RuntimeError('与熱流体は少なくとも1つは指定する必要があります。') - if not cold_streams: - raise RuntimeError('受熱流体は少なくとも1つは指定する必要があります。') + if not is_valid_streams(streams): + raise ValueError('与熱流体および受熱流体は少なくとも1つは指定する必要があります。') minimum_approach_temp_diff_range = get_possible_minimum_temp_diff_range( streams, @@ -87,7 +85,7 @@ def __init__( "最小接近温度差が不正です。" f"指定最小接近温度差 [℃]: {minimum_approach_temp_diff}, " f"設定可能最小接近温度差 [℃]: {minimum_approach_temp_diff_range.start:.3f}" - f" - {minimum_approach_temp_diff_range.finish:.3f}" + f" ~ {minimum_approach_temp_diff_range.finish:.3f}" ) self.gcc = GrandCompositeCurve(streams, minimum_approach_temp_diff) From f65b6b50b158215d2c4ba5b63bfd6aaede32e83f Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Wed, 14 Jul 2021 14:35:49 +0900 Subject: [PATCH 37/43] Update test --- tests/base_range_test.py | 8 ++++++++ tests/pinch_analyzer_test.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/base_range_test.py b/tests/base_range_test.py index b9c05b6..09aae0c 100644 --- a/tests/base_range_test.py +++ b/tests/base_range_test.py @@ -20,9 +20,17 @@ class TestBaseRange(unittest.TestCase): def test_base_range(self): base_range = BaseRange(0.0, 100.0) + other = BaseRange(200.0, 300.0) self.assertEqual(base_range.__repr__(), "BaseRange(0.0, 100.0)") self.assertEqual(base_range.__str__(), "0.0->100.0") + self.assertEqual(f"{base_range:.2f}", "0.00->100.00") + + self.assertTrue(base_range != other) + self.assertTrue(base_range < other) + self.assertTrue(base_range <= other) + self.assertTrue(other > base_range) + self.assertTrue(other >= base_range) self.assertEqual(base_range.delta, 100) self.assertEqual(base_range.start, 0) diff --git a/tests/pinch_analyzer_test.py b/tests/pinch_analyzer_test.py index d0c8883..0f4a500 100644 --- a/tests/pinch_analyzer_test.py +++ b/tests/pinch_analyzer_test.py @@ -6,7 +6,7 @@ class TestPinchAnalyzer(unittest.TestCase): def test(self): - with self.assertRaises(RuntimeError): + with self.assertRaises(ValueError): PinchAnalyzer([], 10.0) From 68eb4dcefae557cc0ce3338a313031af552366fc Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Wed, 14 Jul 2021 14:45:31 +0900 Subject: [PATCH 38/43] Update gcc --- .../grand_composite_curve.py | 130 ++++++++---------- 1 file changed, 60 insertions(+), 70 deletions(-) diff --git a/src/pyheatintegration/grand_composite_curve.py b/src/pyheatintegration/grand_composite_curve.py index 1b851cc..3bdda89 100644 --- a/src/pyheatintegration/grand_composite_curve.py +++ b/src/pyheatintegration/grand_composite_curve.py @@ -5,6 +5,49 @@ from .temperature_range import TemperatureRange, get_temperatures +def _get_heats( + temp_ranges: list[TemperatureRange], + temp_range_streams: defaultdict[TemperatureRange, set[Stream]] +) -> list[float]: + """温度変化領域ごとの熱量変化を求めます。 + + Args: + temp_ranges: list[TemperatureRange]: 温度領域のリスト。 + temp_range_lacking_heat: dict[TemperatureRange, float]: + 温度領域ごとの過不足熱量。 + + Returns: + list[float]: 熱量のリスト。 + """ + temp_range_lacking_heat = _get_lacking_heats(temp_range_streams) + + temp_ranges.sort() + heats = [0.0] * (len(temp_ranges) + 1) + for i, temp_range in enumerate(temp_ranges): + heats[i + 1] = heats[i] - temp_range_lacking_heat[temp_range] + min_heat = min(heats) + + return [heat - min_heat for heat in heats] + + +def _get_lacking_heats( + temp_range_streams: defaultdict[TemperatureRange, set[Stream]] +) -> defaultdict[TemperatureRange, float]: + """温度領域ごとの不足熱量を求めます. + + Args: + temp_range_streams defaultdict[float, set[Stream]]: + 温度領域ごとの流体のセット。 + + Returns: + defaultdict[TemperatureRange, float]: 温度領域ごとの過不足熱量。 + """ + return defaultdict(int, { + temp_range: sum([s.heat() for s in streams if s.is_hot()]) - sum([s.heat() for s in streams if s.is_cold()]) + for temp_range, streams in temp_range_streams.items() + }) + + class GrandCompositeCurve: """グランドコンポジットカーブを作成するために必要な情報を得るためのクラス。 @@ -48,12 +91,9 @@ def __init__( stream for stream in streams if stream.is_internal() ]) self.temps = get_temperatures(temp_ranges) - self.heats = self._get_heats( - temp_ranges, - self._lacking_heats(temp_range_streams) - ) + self.heats = _get_heats(temp_ranges, temp_range_streams) - pinch_point_info = self._set_pinch_point() + pinch_point_info = self._get_pinch_point() self.maximum_pinch_point_temp = pinch_point_info[0] self.maximum_pinch_point_index = pinch_point_info[1] self.minimum_pinch_point_temp = pinch_point_info[2] @@ -62,6 +102,21 @@ def __init__( self.hot_utility_target = self.heats[-1] self.cold_utility_target = self.heats[0] + def _get_pinch_point(self) -> tuple[float, int, float, int]: + """ピンチポイントとピンチポイントのインデックスを求めます。 + """ + pinch_point_indexes = [ + i for i, heat in enumerate(self.heats) if heat == 0 + ] + pinch_points = [self.temps[i] for i in pinch_point_indexes] + + return ( + pinch_points[-1], + pinch_point_indexes[-1], + pinch_points[0], + pinch_point_indexes[0] + ) + def solve_external_heat(self) -> dict[str, float]: """外部流体による熱交換量を求めます. @@ -93,71 +148,6 @@ def solve_external_heat(self) -> dict[str, float]: for stream in updated_external_streams } - @classmethod - def _lacking_heats( - cls, - temp_range_streams: defaultdict[TemperatureRange, set[Stream]] - ) -> defaultdict[TemperatureRange, float]: - """温度領域ごとの不足熱量を求めます. - - Args: - temp_range_streams defaultdict[float, set[Stream]]: - 温度領域ごとの流体のセット。 - - Returns: - defaultdict[TemperatureRange, float]: 温度領域ごとの過不足熱量。 - """ - return defaultdict(int, { - temp_range: sum([s.heat() for s in streams if s.is_hot()]) - sum([s.heat() for s in streams if s.is_cold()]) - for temp_range, streams in temp_range_streams.items() - }) - - @classmethod - def _get_heats( - cls, - temp_ranges: list[TemperatureRange], - temp_range_lacking_heat: dict[TemperatureRange, float] - ) -> list[float]: - """温度変化領域ごとの熱量変化を求めます。 - - Args: - temp_ranges: list[TemperatureRange]: 温度領域のリスト。 - temp_range_lacking_heat: dict[TemperatureRange, float]: - 温度領域ごとの過不足熱量。 - - Returns: - list[float]: 熱量のリスト。 - """ - temp_ranges.sort() - heats = [0.0] * (len(temp_ranges) + 1) - for i, temp_range in enumerate(temp_ranges): - heats[i + 1] = heats[i] - temp_range_lacking_heat[temp_range] - min_heat = min(heats) - - return [heat - min_heat for heat in heats] - - def _set_pinch_point(self) -> tuple[float, int, float, int]: - """ピンチポイントとピンチポイントのインデックスを求めます。 - """ - # heat == 0は必ず存在する。 - if 0 not in self.heats: - raise ValueError("heatに0が存在しません。") - - pinch_point_indexes = [ - i for i, heat in enumerate(self.heats) if heat == 0 - ] - pinch_points = [self.temps[i] for i in pinch_point_indexes] - - if any(pinch_point_indexes) in [0, len(self.heats) - 1]: - print("ピンチポイントの値が不正である可能性があります。") - - return ( - pinch_points[-1], - pinch_point_indexes[-1], - pinch_points[0], - pinch_point_indexes[0] - ) - def _update_external_streams( self, external_cold_streams: list[Stream], From 66c688484ddd8d68102608f26137cbe2418902d5 Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Wed, 14 Jul 2021 14:54:32 +0900 Subject: [PATCH 39/43] Update heat_exchanger lmtd and area --- src/pyheatintegration/heat_exchanger.py | 28 ++++++++++++------------- src/pyheatintegration/pinch_analyzer.py | 2 +- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/pyheatintegration/heat_exchanger.py b/src/pyheatintegration/heat_exchanger.py index 6e5a902..95114d6 100644 --- a/src/pyheatintegration/heat_exchanger.py +++ b/src/pyheatintegration/heat_exchanger.py @@ -1,5 +1,4 @@ import math -from typing import Optional from .enums import StreamState from .heat_range import HeatRange @@ -84,10 +83,8 @@ class HeatExchanger: cold_stream_state (StreamState): 受熱流体の状態。 hot_temperature_range (TemperatureRange): 与熱流体の温度領域。 cold_temperature_range (TemperatureRange): 受熱流体の温度領域。 - lmtd_parallel_flow (Optional[float]): 並流の場合の対数平均温度差。 - lmtd_counterflow (float): 向流の場合の対数平均温度差。 - area_parallel_flow (Optional[float]): 並流の場合の必要面積 [m2]。 - area_counterflow (float): 向流の場合の必要面積 [m2]。 + lmtd (float): 対数平均温度差。 + area (float): 向流の場合の必要面積 [m2]。 hot_plot_segment (PlotSegment): 与熱流体のプロットセグメント。 cold_plot_segment (PlotSegment): 受熱流体のプロットセグメント。 reboiler_or_reactor (bool): リボイラーもしくは反応器で用いるか。 @@ -97,7 +94,8 @@ def __init__( self, heat_range: HeatRange, hot_plot_segment: PlotSegment, - cold_plot_segment: PlotSegment + cold_plot_segment: PlotSegment, + counterflow: bool = True ): self.heat_range = heat_range self.hot_plot_segment = hot_plot_segment @@ -115,15 +113,12 @@ def __init__( self.cold_stream_state ) - self.lmtd_parallel_flow = self.init_lmtd_pararell_flow() - self.lmtd_counterflow = self.init_lmtd_counterflow() - - if self.lmtd_parallel_flow is not None: - self.area_parallel_flow = self.heat_range.delta / self.lmtd_parallel_flow / self.overall_heat_transfer_coefficient + if counterflow: + self.lmtd = self.init_lmtd_counterflow() else: - self.area_parallel_flow = None + self.lmtd = self.init_lmtd_pararell_flow() - self.area_counterflow = self.heat_range.delta / self.lmtd_counterflow / self.overall_heat_transfer_coefficient + self.area = self.heat_range.delta / self.lmtd / self.overall_heat_transfer_coefficient def __repr__(self) -> str: return ( @@ -144,7 +139,7 @@ def __str__(self) -> str: def __lt__(self, other) -> bool: return self.heat_range < other.heat_range - def init_lmtd_pararell_flow(self) -> Optional[float]: + def init_lmtd_pararell_flow(self) -> float: """並流の場合の対数平均温度差を返します。 並流が不可能な場合はNoneを返します。 @@ -159,7 +154,10 @@ def init_lmtd_pararell_flow(self) -> Optional[float]: finish_temp_diff = hot_low_temp - cold_high_temp if finish_temp_diff <= 0: - return None + raise RuntimeError( + '出口温度差が0以下となるため、並流にすることができません。' + f'出口温度差: {finish_temp_diff} ℃' + ) if start_temp_diff == finish_temp_diff: return start_temp_diff diff --git a/src/pyheatintegration/pinch_analyzer.py b/src/pyheatintegration/pinch_analyzer.py index 50ab68b..da19a20 100644 --- a/src/pyheatintegration/pinch_analyzer.py +++ b/src/pyheatintegration/pinch_analyzer.py @@ -130,7 +130,7 @@ def __init__( self.heat_exchanger_cost = sum( calculate_heat_exchanger_cost( - heat_exchanger.area_counterflow, + heat_exchanger.area, heat_exchanger.reboiler_or_reactor ) for heat_exchanger in self.heat_exchangers From 7453a61bca549826517b52c1b47e78fd76c2d3fb Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Wed, 14 Jul 2021 15:05:00 +0900 Subject: [PATCH 40/43] Update get_possible_minimum_temp_diff_range #34 --- src/pyheatintegration/tq_diagram.py | 48 ++++++++++++----------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/src/pyheatintegration/tq_diagram.py b/src/pyheatintegration/tq_diagram.py index ab4d20a..9f169ad 100644 --- a/src/pyheatintegration/tq_diagram.py +++ b/src/pyheatintegration/tq_diagram.py @@ -302,39 +302,25 @@ def get_possible_minimum_temp_diff_range( Returns: float: 可能な最小接近温度差[℃]。 """ - cold_streams = sorted( - [stream for stream in streams if stream.is_internal() and stream.is_cold()], - key=lambda stream: stream.input_temperature() + hot_maximum_temp = max( + stream.input_temperature() for stream in streams if stream.is_hot() ) - hot_streams = sorted( - [stream for stream in streams if stream.is_internal() and stream.is_hot()], - key=lambda stream: stream.output_temperature() + + hot_minimum_temp = min( + stream.output_temperature() for stream in streams if stream.is_hot() + ) + + cold_maximum_temp = max( + stream.output_temperature() for stream in streams if stream.is_cold() ) - if not hot_streams: - raise RuntimeError('与熱流体は少なくとも1つは指定する必要があります。') - if not cold_streams: - raise RuntimeError('受熱流体は少なくとも1つは指定する必要があります。') + cold_minimum_temp = min( + stream.input_temperature() for stream in streams if stream.is_cold() + ) if ignore_maximum: - maximum_minimum_approch_temp_diff = math.inf + maximum_minimum_approch_temp_diff = hot_maximum_temp - cold_minimum_temp else: - hot_maximum_temp = max( - stream.input_temperature() for stream in streams if stream.is_hot() - ) - - hot_minimum_temp = min( - stream.output_temperature() for stream in streams if stream.is_hot() - ) - - cold_maximum_temp = max( - stream.output_temperature() for stream in streams if stream.is_cold() - ) - - cold_minimum_temp = min( - stream.input_temperature() for stream in streams if stream.is_cold() - ) - if hot_minimum_temp - cold_minimum_temp < 0: raise ValueError( '与熱流体の最低温度が受熱流体の最低温度を下回っています。' @@ -355,8 +341,12 @@ def get_possible_minimum_temp_diff_range( ) # 与熱流体と受熱流体のセグメントを得る。 - initial_hcc = _create_composite_curve(hot_streams) - initial_ccc = _create_composite_curve(cold_streams) + initial_hcc = _create_composite_curve([ + stream for stream in streams if stream.is_internal() and stream.is_hot() + ]) + initial_ccc = _create_composite_curve([ + stream for stream in streams if stream.is_internal() and stream.is_cold() + ]) initial_heat_ranges = get_detailed_heat_ranges( [ From e08f456fca36a924e11c256fb7e673d4688b5900 Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Wed, 14 Jul 2021 15:40:21 +0900 Subject: [PATCH 41/43] Move heating demand and cooling demand to PinchAnalyzer --- src/pyheatintegration/grand_composite_curve.py | 5 ----- src/pyheatintegration/pinch_analyzer.py | 5 +++++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pyheatintegration/grand_composite_curve.py b/src/pyheatintegration/grand_composite_curve.py index 3bdda89..3f4b593 100644 --- a/src/pyheatintegration/grand_composite_curve.py +++ b/src/pyheatintegration/grand_composite_curve.py @@ -64,8 +64,6 @@ class GrandCompositeCurve: minimum_pinch_point_index (int): 最も温度が低いピンチポイントのインデックス。 temps (list[float]): 温度のリスト[℃]。 heats (list[float]): 熱量のリスト[W]。 - hot_utility_target (float): 必要加熱量[W]。 - cold_utility_target (float): 必要冷却熱量[W]。 """ def __init__( @@ -99,9 +97,6 @@ def __init__( self.minimum_pinch_point_temp = pinch_point_info[2] self.minimum_pinch_point_index = pinch_point_info[3] - self.hot_utility_target = self.heats[-1] - self.cold_utility_target = self.heats[0] - def _get_pinch_point(self) -> tuple[float, int, float, int]: """ピンチポイントとピンチポイントのインデックスを求めます。 """ diff --git a/src/pyheatintegration/pinch_analyzer.py b/src/pyheatintegration/pinch_analyzer.py index da19a20..ea6037a 100644 --- a/src/pyheatintegration/pinch_analyzer.py +++ b/src/pyheatintegration/pinch_analyzer.py @@ -49,6 +49,8 @@ class PinchAnalyzer: pinch_point_temp (float): ピンチポイントの温度 [℃]。 heat_exchangers (list[HeatExchanger]): 熱交換器のリスト。 heat_exchanger_cost (float): 熱交換器のコスト。 + external_heating_demand (float): 必要加熱量[W]。 + external_cooling_demand (float): 必要冷却熱量[W]。 Raises: ValueError: 流体のidが重複している場合。また、最小接近温度差の値が不正な場合。 @@ -89,6 +91,9 @@ def __init__( ) self.gcc = GrandCompositeCurve(streams, minimum_approach_temp_diff) + self.external_heating_demand = self.gcc.heats[-1] + self.external_cooling_demand = self.gcc.heats[0] + id_heats = self.gcc.solve_external_heat() for stream in streams: for id_, heat in id_heats.items(): From 56042c14e23103d99cc1f6d05ec89f474dbdcf20 Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Wed, 14 Jul 2021 15:51:19 +0900 Subject: [PATCH 42/43] Update PinchAnalyzer --- src/pyheatintegration/pinch_analyzer.py | 29 +++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/pyheatintegration/pinch_analyzer.py b/src/pyheatintegration/pinch_analyzer.py index ea6037a..d3e00c5 100644 --- a/src/pyheatintegration/pinch_analyzer.py +++ b/src/pyheatintegration/pinch_analyzer.py @@ -90,16 +90,45 @@ def __init__( f" ~ {minimum_approach_temp_diff_range.finish:.3f}" ) + print( + f"設定可能最小接近温度差 [℃]: {minimum_approach_temp_diff_range.start:.3f}" + f" ~ {minimum_approach_temp_diff_range.finish:.3f}" + ) + self.gcc = GrandCompositeCurve(streams, minimum_approach_temp_diff) self.external_heating_demand = self.gcc.heats[-1] self.external_cooling_demand = self.gcc.heats[0] + print(f'ピンチポイント [℃]: {self.gcc.minimum_pinch_point_temp}') + print(f'必要加熱量[W]: {self.external_heating_demand:.3f}') + print(f'必要冷却量[W]: {self.external_cooling_demand:.3f}') + id_heats = self.gcc.solve_external_heat() for stream in streams: for id_, heat in id_heats.items(): if stream.id_ == id_: stream.update_heat(heat) + for stream in streams: + if stream.is_external() and stream.is_hot(): + print( + f'外部与熱流体 id: {stream.id_} ' + '\033[1m加熱量\033[0m [W]: ' + f'{stream.heat():.3f} ' + '\033[1mコスト\033[0m [円/s]: ' + f'{stream.heat() * stream.cost:.3f}' + ) + + for stream in streams: + if stream.is_external() and stream.is_cold(): + print( + f'外部受熱流体 id: {stream.id_} ' + '\033[1m冷却量\033[0m [W]: ' + f'{stream.heat():.3f} ' + '\033[1mコスト\033[0m [円/s]: ' + f'{stream.heat() * stream.cost:.3f}' + ) + self.streams = [stream for stream in streams if stream.heat() != 0] self.pinch_point_temp = self.gcc.maximum_pinch_point_temp self.tq = TQDiagram( From c412e38b79d9030c453571a4845bae0891ad79e4 Mon Sep 17 00:00:00 2001 From: tarao1006 Date: Wed, 14 Jul 2021 16:03:45 +0900 Subject: [PATCH 43/43] Move print --- examples/simple/main.py | 29 +++++++++++++++++++ src/pyheatintegration/pinch_analyzer.py | 38 ++++--------------------- 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/examples/simple/main.py b/examples/simple/main.py index 9423141..43c8e1c 100644 --- a/examples/simple/main.py +++ b/examples/simple/main.py @@ -23,6 +23,35 @@ def main(): minimum_approach_temperature_difference = 10.0 analyzer = PinchAnalyzer(streams, minimum_approach_temperature_difference) + print( + f"設定可能最小接近温度差 [℃]: {analyzer.minimum_approach_temp_diff_range.start:.3f}" + f" ~ {analyzer.minimum_approach_temp_diff_range.finish:.3f}" + ) + + print(f'ピンチポイント [℃]: {analyzer.gcc.minimum_pinch_point_temp}') + print(f'必要加熱量[W]: {analyzer.external_heating_demand:.3f}') + print(f'必要冷却量[W]: {analyzer.external_cooling_demand:.3f}') + + for stream in analyzer.streams: + if stream.is_external() and stream.is_hot(): + print( + f'外部与熱流体 id: {stream.id_} ' + '\033[1m加熱量\033[0m [W]: ' + f'{stream.heat():.3f} ' + '\033[1mコスト\033[0m [円/s]: ' + f'{stream.heat() * stream.cost:.3f}' + ) + + for stream in analyzer.streams: + if stream.is_external() and stream.is_cold(): + print( + f'外部受熱流体 id: {stream.id_} ' + '\033[1m冷却量\033[0m [W]: ' + f'{stream.heat():.3f} ' + '\033[1mコスト\033[0m [円/s]: ' + f'{stream.heat() * stream.cost:.3f}' + ) + # グランドコンポジットカーブ heats, temps = analyzer.create_grand_composite_curve() fig, ax = plt.subplots(1, 1) diff --git a/src/pyheatintegration/pinch_analyzer.py b/src/pyheatintegration/pinch_analyzer.py index d3e00c5..298972f 100644 --- a/src/pyheatintegration/pinch_analyzer.py +++ b/src/pyheatintegration/pinch_analyzer.py @@ -46,6 +46,7 @@ class PinchAnalyzer: gcc (GrandCompositeCurve): グランドコンポジットカーブ。 tq (TQDiagram): TQ線図。 streams (list[Stream]): 流体のリスト。 + minimum_approach_temp_diff_range (TemperatureRange): 最小接近温度差の指定可能範囲。 pinch_point_temp (float): ピンチポイントの温度 [℃]。 heat_exchangers (list[HeatExchanger]): 熱交換器のリスト。 heat_exchanger_cost (float): 熱交換器のコスト。 @@ -77,58 +78,29 @@ def __init__( if not is_valid_streams(streams): raise ValueError('与熱流体および受熱流体は少なくとも1つは指定する必要があります。') - minimum_approach_temp_diff_range = get_possible_minimum_temp_diff_range( + self.minimum_approach_temp_diff_range = get_possible_minimum_temp_diff_range( streams, ignore_maximum ) - if minimum_approach_temp_diff not in minimum_approach_temp_diff_range: + if minimum_approach_temp_diff not in self.minimum_approach_temp_diff_range: raise ValueError( "最小接近温度差が不正です。" f"指定最小接近温度差 [℃]: {minimum_approach_temp_diff}, " - f"設定可能最小接近温度差 [℃]: {minimum_approach_temp_diff_range.start:.3f}" - f" ~ {minimum_approach_temp_diff_range.finish:.3f}" + f"設定可能最小接近温度差 [℃]: {self.minimum_approach_temp_diff_range.start:.3f}" + f" ~ {self.minimum_approach_temp_diff_range.finish:.3f}" ) - print( - f"設定可能最小接近温度差 [℃]: {minimum_approach_temp_diff_range.start:.3f}" - f" ~ {minimum_approach_temp_diff_range.finish:.3f}" - ) - self.gcc = GrandCompositeCurve(streams, minimum_approach_temp_diff) self.external_heating_demand = self.gcc.heats[-1] self.external_cooling_demand = self.gcc.heats[0] - print(f'ピンチポイント [℃]: {self.gcc.minimum_pinch_point_temp}') - print(f'必要加熱量[W]: {self.external_heating_demand:.3f}') - print(f'必要冷却量[W]: {self.external_cooling_demand:.3f}') - id_heats = self.gcc.solve_external_heat() for stream in streams: for id_, heat in id_heats.items(): if stream.id_ == id_: stream.update_heat(heat) - for stream in streams: - if stream.is_external() and stream.is_hot(): - print( - f'外部与熱流体 id: {stream.id_} ' - '\033[1m加熱量\033[0m [W]: ' - f'{stream.heat():.3f} ' - '\033[1mコスト\033[0m [円/s]: ' - f'{stream.heat() * stream.cost:.3f}' - ) - - for stream in streams: - if stream.is_external() and stream.is_cold(): - print( - f'外部受熱流体 id: {stream.id_} ' - '\033[1m冷却量\033[0m [W]: ' - f'{stream.heat():.3f} ' - '\033[1mコスト\033[0m [円/s]: ' - f'{stream.heat() * stream.cost:.3f}' - ) - self.streams = [stream for stream in streams if stream.heat() != 0] self.pinch_point_temp = self.gcc.maximum_pinch_point_temp self.tq = TQDiagram(