Repository: tarao1006/pyheatintegration (language: python)

Issue:
Update ignore_validation
外部流体が存在しない場合に、

- 最小接近温度差の値
- 最小温度、最大温度の妥当性

の検証を行うかを指定できるように変更しました。 #49 
コストが小さい外部流体の交換熱量が大きくなるようにする
`GrandCompositeCurve` で外部流体の交換熱量を求める際に、コストが小さい流体から優先的に熱交換を行うようにする。


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index 8d11b83..7965a02 100644
--- a/README.md
+++ b/README.md
@@ -7,27 +7,27 @@
 
 <details>
   <summary>グランドコンポジットカーブ</summary>
-  <img src="https://raw.githubusercontent.com/tarao1006/pyheatintegration/main/docs/images/grand_composite_curve.png" width="400">
+  <img src="https://raw.githubusercontent.com/tarao1006/pyheatintegration/develop/examples/simple/grand_composite_curve.png" width="400">
 </details>
 
 <details>
   <summary>TQ線図</summary>
-  <img src="https://raw.githubusercontent.com/tarao1006/pyheatintegration/main/docs/images/tq_diagram.png" width="400">
+  <img src="https://raw.githubusercontent.com/tarao1006/pyheatintegration/develop/examples/simple/tq_diagram_with_vlines.png" width="400">
 </details>
 
 <details>
   <summary>TQ線図(流体ごとに分割)</summary>
-  <img src="https://raw.githubusercontent.com/tarao1006/pyheatintegration/main/docs/images/tq_diagram_separeted.png" width="400">
+  <img src="https://raw.githubusercontent.com/tarao1006/pyheatintegration/develop/examples/simple/tq_diagram_separated_with_vlines.png" width="400">
 </details>
 
 <details>
   <summary>TQ線図(流体ごとに分割。最小接近温度差を満たす。)</summary>
-  <img src="https://raw.githubusercontent.com/tarao1006/pyheatintegration/main/docs/images/tq_diagram_splitted.png" width="400">
+  <img src="https://raw.githubusercontent.com/tarao1006/pyheatintegration/develop/examples/simple/tq_diagram_split_with_vlines.png" width="400">
 </details>
 
 <details>
   <summary>TQ線図(結合可能な熱交換器を結合。)</summary>
-  <img src="https://raw.githubusercontent.com/tarao1006/pyheatintegration/main/docs/images/tq_diagram_merged.png" width="400">
+  <img src="https://raw.githubusercontent.com/tarao1006/pyheatintegration/develop/examples/simple/tq_diagram_merged_with_vlines.png" width="400">
 </details>
 
 ## Requirements
diff --git a/VERSION b/VERSION
index 9e11b32..1d0ba9e 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-0.3.1
+0.4.0
diff --git a/docs/analysis.rst b/docs/analysis.rst
new file mode 100644
index 0000000..3f92d8d
--- /dev/null
+++ b/docs/analysis.rst
@@ -0,0 +1,66 @@
+Step2. Analysis
+===============
+
+.. note::
+  流体の指定方法をまた読んでいない方は、 まずは :doc:`create_stream` を読んでください。
+
+流体を生成した後、 ``PinchAnalyzer`` を用いてグランドコンポジットカーブおよびTQ線図を書きま\
+す。 ``PinchAnalyzer`` のコンストラクタは以下のようになっています。
+
+.. py:class:: PinchAnalyzer(streams_: list[Stream], minimum_approach_temp_diff: float, ignore_maximum: bool = False)
+
+* ``streams_ (list[Stream])``: 流体のリスト。
+* ``minimum_approach_temp_diff (float)``: 最小接近温度差 [℃]。
+* ``ignore_maximum (bool, optional)``: 最小接近温度差を指定可能かを検証する際に、最大値
+  のチェックを無視するかどうか。
+
+.. note::
+  外部流体を指定せずに解析を行いたい場合などに ``ignore_maximum`` を指定すると、エラーが生じ\
+  ずに解析を行うことが可能となる可能性があります。
+
+**例**
+
+.. code-block:: python
+
+  streams = [
+      Stream(40.0, 90.0, 150.0, StreamType(1), StreamState.LIQUID, 0.0, False, 'cold1')
+      Stream(80.0, 110.0, 180.0, StreamType(1), StreamState.LIQUID, 0.0, False, 'cold2')
+      Stream(125.0, 80.0, 180.0, StreamType(2), StreamState.GAS, 0.0, False, 'hot1')
+      Stream(100.0, 60.0, 160.0, StreamType(2), StreamState.GAS, 0.0, False, 'hot2')
+      Stream(20.0, 20.0, 0.0, StreamType(3), StreamState.LIQUID_EVAPORATION, 100.0, False, 'external cold1')
+      Stream(150.0, 150.0, 0.0, StreamType(4), StreamState.GAS_CONDENSATION, 200.0, False, 'external hot1')
+  ]
+
+  analyzer = PinchAnalyzer(streams, 10.0)
+
+流体の検証
+-----------------------
+
+コンストラクタで受け取った流体のリストのバリデーションを行います。以下の場合に不正と判断します。\
+ただし、 ``ignore_maximum`` を ``True`` とした場合、3の条件は無視されます。
+
+1. 流体のidが重複している場合
+2. 与熱流体もしくは受熱流体が1つもない場合
+3. ``与熱流体の最高温度 < 受熱流体の最高温度`` または ``与熱流体の最低温度 > 受熱流体の最低温度`` である場合
+
+最小接近温度差の検証
+-----------------------
+
+最小接近温度差は、指定可能範囲が存在する。指定値がその範囲にない場合、不正と判断します。指定可能\
+な最大値を、 ``min(与熱流体の最高温度 - 受熱流体の最高温度, 与熱流体の最低温度 - 受熱流体の最低温度)``\
+とします。ただし、``ignore_maximum`` を ``True`` とした場合には、指定可能な最大値を
+``与熱流体の最高温度 - 受熱流体の最低温度`` とします。
+
+
+グラフ描画
+-----------------------
+
+``PinchAnalyzer`` のインスタンス生成時に、グランドコンポジットカーブおよびTQ線図の描画に必要\
+な計算が行われます。計算した結果は以下の ``create_*`` を呼ぶことで得ることができます。実際の\
+グラフ描画については :doc:`draw_graph` を参照して下さい。
+
+* ``create_grand_composite_curve``: グランドコンポジットカーブ
+* ``create_tq``: TQ線図
+* ``create_tq_separated``: 流体ごとに分割したTQ線図
+* ``create_tq_split``: 流体ごとに分割し、最初接近温度差を満たすように分割したTQ線図
+* ``create_tq_merged``: 結合可能な熱交換器を結合したTQ線図
diff --git a/docs/conf.py b/docs/conf.py
index 5a1f3ec..4966414 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.3.1'
+release = '0.4.0'
 
 
 # -- General configuration ---------------------------------------------------
diff --git a/docs/create_stream.rst b/docs/create_stream.rst
new file mode 100644
index 0000000..ff3360b
--- /dev/null
+++ b/docs/create_stream.rst
@@ -0,0 +1,146 @@
+Step1. Create Stream
+====================
+
+熱交換を行いたい流体の情報を元に、 ``Stream`` のリストを生成します。 ``Stream`` のコンスト\
+ラクタは、以下のようになっています。
+
+.. py:class:: Stream(input_temperature: float, output_temperature: float, heat_flow: float, type_: StreamType = <StreamType.AUTO: 5>, state: StreamState = <StreamState.UNKNOWN: 5>, cost: float = 0.0, reboiler_or_reactor: bool = False, id_: str = '')
+
+* ``input_temperature (float)``: 入り口温度 [℃]
+* ``output_temperature (float)``: 出口温度 [℃]
+* ``heat_flow (float)``: 熱量 [W]
+* ``type_ (StreamType, optional)``: 流体の種類
+
+  * ``StreamType.COLD``: 受熱
+  * ``StreamType.HOT``: 与熱
+  * ``StreamType.EXTERNAL_COLD``: 外部受熱(冷却用ユーティリティ)
+  * ``StreamType.EXTERNAL_HOT``: 外部与熱(加熱用ユーティリティ)
+  * ``StreamType.AUTO``: 自動
+
+* ``state (StreamState, optional)``: 流体の状態
+
+  * ``StreamState.GAS``: ガス
+  * ``StreamState.LIQUID``: 液
+  * ``StreamState.GAS_CONDENSATION``: ガス(凝縮)
+  * ``StreamState.LIQUID_EVAPORATION``: 液(蒸発)
+  * ``StreamState.UNKNOWN``: 不明/指定しない。
+
+* ``cost (float)``: コスト [円/J] (外部流体の場合)
+* ``reboiler_or_reactor (bool, optional)``: リボイラーまたは反応器の熱交換に用いられる\
+  か。熱交換器のコストを計算する際の係数を決定するために必要な情報。
+* ``id_ (str, optional)``: ID
+
+**例**
+
+.. code-block:: python
+
+  Stream(40.0, 90.0, 150.0, StreamType(1), StreamState.LIQUID, 0.0, False, 'cold1')
+  Stream(80.0, 110.0, 180.0, StreamType(1), StreamState.LIQUID, 0.0, False, 'cold2')
+  Stream(125.0, 80.0, 180.0, StreamType(2), StreamState.GAS, 0.0, False, 'hot1')
+  Stream(100.0, 60.0, 160.0, StreamType(2), StreamState.GAS, 0.0, False, 'hot2')
+  Stream(20.0, 20.0, 0.0, StreamType(3), StreamState.LIQUID_EVAPORATION, 100.0, False, 'external cold1')
+  Stream(150.0, 150.0, 0.0, StreamType(4), StreamState.GAS_CONDENSATION, 200.0, False, 'external hot1')
+
+入り口温度/出口温度/熱量
+------------------------
+
+受熱流体は ``input_temperature ≤ output_temperature`` 、与熱流体は
+``input_temperature ≥ output_temperature`` である必要があります。等温流体の設定も可能で\
+す。外部流体の熱量は、グランドコンポジットカーブ作成時に決定するため、流体作成時には
+``heat_flow = 0`` を指定してください。
+
+流体の種類
+-----------
+
+流体の種類 ``type_`` は ``enum`` 型である ``StreamType`` を用いて指定します。指定可能な種\
+類は、 *受熱* 、*与熱* 、*外部受熱* 、*外部与熱* の4種類です。また、自動で種類を判断するよう\
+に指定する *自動* もありますが、明示的に指定する機会はないと思われます。
+
+* ``StreamType.COLD (= 1)``: 受熱
+* ``StreamType.HOT (= 2)``: 与熱
+* ``StreamType.EXTERNAL_COLD (= 3)``: 外部受熱(冷却用ユーティリティ)
+* ``StreamType.EXTERNAL_HOT (= 4)``: 外部与熱(加熱用ユーティリティ)
+* ``StreamType.AUTO (= 5)``: 自動
+
+以下の例のように流体を指定します。ただし、オプショナル引数は一部省略しています。
+
+.. code-block:: python
+
+  # 入り口温度: 40 度 出口温度 90 度 熱量 150.0 W の受熱流体
+  Stream(40.0, 90.0, 150.0, StreamType(1))
+
+  # 入り口温度: 125 度 出口温度 80 度 熱量 180.0 W の与熱流体
+  Stream(125.0, 80.0, 180.0, StreamType(2))
+
+  # 入り口温度: 20 度 出口温度 20 度 の外部受熱流体
+  Stream(40.0, 90.0, 0.0, StreamType(3))
+
+  # 入り口温度: 125 度 出口温度 80 度 の外部与熱流体
+  Stream(150.0, 150.0, 0.0, StreamType(4))
+
+ただし、外部流体でない場合、入り口温度と出口温度の関係から流体の種類を推測することができるため、\
+省略することができます。一方、外部流体は必ず指定する必要があります。
+
+.. code-block:: python
+
+  # 受熱流体と与熱流体はStreamTypeを指定しなくても良い。
+  Stream(40.0, 90.0, 150.0)
+  Stream(125.0, 80.0, 180.0)
+
+  # 外部受熱流体と外部与熱流体はStreamTypeを指定する必要があります。
+  Stream(40.0, 90.0, 0.0, StreamType(3))
+  Stream(150.0, 150.0, 0.0, StreamType(4))
+
+流体の状態
+------------
+
+流体の状態 ``state`` は ``enum`` 型である ``StreamState`` を用いて指定します。この値を用\
+いて総括伝熱係数の値を指定します。
+
+* ``StreamState.GAS (= 1)``: ガス
+* ``StreamState.LIQUID (= 2)``: 液
+* ``StreamState.GAS_CONDENSATION (= 3)``: ガス(凝縮)
+* ``StreamState.LIQUID_EVAPORATION (= 4)``: 液(蒸発)
+* ``StreamState.UNKNOWN (= 5)``: 不明/指定しない。
+
+.. code-block:: python
+
+  # 液体の流体
+  Stream(40.0, 90.0, 150.0, state=StreamState(2))
+
+総括伝熱係数の値は、 `プロセスデザインコンテスト <http://scejcontest.chem-eng.kyushu-u.ac.jp/2019/download/processsim2019_v1.pdf>`_
+を参考にして以下のように指定しています。
+
+.. _overall_heat_transfer_coefficient:
+
++--------------------+----------------------+------------------------------------------------------+
+| Hot                | Cold                 | U [:math:`\mathrm{W}/\mathrm{m}^2 \cdot \mathrm{K}`] |
++====================+======================+======================================================+
+| Gas                | Gas                  | 150                                                  |
++--------------------+----------------------+------------------------------------------------------+
+| Liquid             | Gas                  | 200                                                  |
++--------------------+----------------------+------------------------------------------------------+
+| Liquid             | Liquid               | 300                                                  |
++--------------------+----------------------+------------------------------------------------------+
+| Gas (Condensation) | Liquid (Evaporation) | 1,500                                                |
++--------------------+----------------------+------------------------------------------------------+
+| Gas                | Liquid               | 200                                                  |
++--------------------+----------------------+------------------------------------------------------+
+| Gas (Condensation) | Gas                  | 500                                                  |
++--------------------+----------------------+------------------------------------------------------+
+| Gas (Condensation) | Liquid               | 1,000                                                |
++--------------------+----------------------+------------------------------------------------------+
+| Gas                | Liquid (Evaporation) | 500                                                  |
++--------------------+----------------------+------------------------------------------------------+
+| Liquid             | Liquid (Evaporation) | 1,000                                                |
++--------------------+----------------------+------------------------------------------------------+
+
+.. note::
+  与熱流体には ``StreamState.LIQUID_EVAPORATION`` を、受熱流体には、
+  ``StreamState.GAS_CONDENSATION`` を指定することができません。
+
+ID
+---
+
+``id_`` は流体を区別するために指定します。複数の流体を作成する場合には、idを重複しないようにす\
+る必要があります。
diff --git a/docs/draw_graph.rst b/docs/draw_graph.rst
new file mode 100644
index 0000000..7b5c9a5
--- /dev/null
+++ b/docs/draw_graph.rst
@@ -0,0 +1,260 @@
+Step3. Draw Graph
+=================
+
+.. note::
+  流体の指定方法および ``PinchAnalyzer`` の説明をまた読んでいない方は、 まずは\
+  :doc:`create_stream` および :doc:`analysis` を読んでください。
+
+``PinchAnalyzer`` を用いた解析によって、グラフを描画するための情報を得ることができます。以下\
+の ``create_*`` を呼ぶことで得ることができます。
+
+* ``create_grand_composite_curve``: グランドコンポジットカーブ
+* ``create_tq``: TQ線図
+* ``create_tq_separated``: 流体ごとに分割したTQ線図
+* ``create_tq_split``: 流体ごとに分割し、最初接近温度差を満たすように分割したTQ線図
+* ``create_tq_merged``: 結合可能な熱交換器を結合したTQ線図
+
+例で用いる ``analyzer`` は以下のコードによって作成されたと仮定しています。
+
+.. code-block:: python
+
+  from pyheatintegration import PinchAnalyzer, Stream
+
+  streams = [
+      Stream(40.0, 90.0, 150.0),
+      Stream(80.0, 110.0, 180.0),
+      Stream(125.0, 80.0, 180.0),
+      Stream(100.0, 60.0, 160.0)
+  ]
+
+  minimum_approach_temperature_difference = 10.0
+  analyzer = PinchAnalyzer(streams, minimum_approach_temperature_difference)
+
+グランドコンポジットカーブ
+-----------------------------
+
+``analyzer.create_grand_composite_curve()`` を用いて、熱量と温度のリストを取得すること\
+ができます。
+
+.. code-block:: python
+
+  heats, temps = analyzer.create_grand_composite_curve()
+  fig, ax = plt.subplots(1, 1)
+  ax.set_xlabel("Q [kW]")
+  ax.set_ylabel("Shifted Temperature [℃]")
+  ax.plot(heats, temps)
+  fig.savefig("path/to/grand_composite_curve.png")
+
+.. image:: https://raw.githubusercontent.com/tarao1006/pyheatintegration/develop/examples/simple/grand_composite_curve.png
+  :width: 400
+  :align: center
+
+
+TQ線図
+-----------------------------
+
+``PinchAnalyzer`` は以下の4種類のTQ線図を描くためのデータを提供します。
+
+* :ref:`tq-diagram`
+* :ref:`tq-diagram-separated`
+* :ref:`tq-diagram-split`
+* :ref:`tq-diagram-merged`
+
+
+``create_tq()`` ``create_tq_separated()`` ``create_tq_split()`` ``create_tq_merged()``
+は、プロットに必要な直線を以下のような形式で返します。
+
+.. code-block:: python
+
+  # [((始点の座標), (終点の座標)), ((始点の座標), (終点の座標)), ...]
+  lines = [((0, 0), (1, 1)), ((1, 2), (2, 3))]
+
+タプルの第一成分が直線の始点の座標、第二成分が終点の座標を表します。また、与熱複合線と受熱複合線\
+をタプルで返します。それぞれを ``matplotlib.collections.LineCollection`` に変換後、\
+``ax.add_collection`` を行うことで直線をプロットすることができます。
+
+.. code-block:: python
+
+  # 複合線を表示
+  fig, ax = plt.subplots()
+  hot_lines, cold_lines = analyzer.create_tq()
+  ax.add_collection(LineCollection(hot_lines))
+  ax.add_collection(LineCollection(cold_lines))
+
+さらに、複合線において、直線が折れ曲がっている点を通る熱量の線をプロットしたい場合、 ``y_range``\
+と ``extract_x`` を呼ぶことで、必要な情報を得ることができます。
+
+.. code-block:: python
+
+  # たて線を表示
+  ymin, ymax = y_range(hot_lines + cold_lines)
+  heats = extract_x(hot_lines + cold_lines)
+  ax.vlines(heats, ymin=ymin, ymax=ymax, linestyles=':', colors='k')
+
+.. py:function:: extract_x(lines: list[Line]) -> list[float]
+
+**例**
+
+.. code-block:: python
+
+  >>> extract_x([
+          ((0, 0), (1, 1)),
+          ((1, 1), (2, 2)),
+          ((2, 2), (3, 5)),
+          ((3, 3), (5, 8))
+      ])
+  >>> [0, 1, 2, 3, 5]
+
+.. py:function:: y_range(hot_lines: list[Line], cold_lines: list[Line]) -> tuple[float, float]
+
+**例**
+
+.. code-block:: python
+
+  >>> y_range([
+          ((0, 0), (1, 1)),
+          ((1, 1), (2, 2)),
+          ((2, 2), (3, 5)),
+          ((3, 3), (5, 8))
+      ])
+  >>> (0, 8)
+
+.. _tq-diagram:
+
+通常のTQ線図
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: python
+
+  hot_lines, cold_lines = analyzer.create_tq()
+
+  # 与熱複合線と受熱複合線
+  fig, ax = plt.subplots(1, 1)
+  ax.set_xlabel("Q [kW]")
+  ax.set_ylabel("T [℃]")
+  ax.add_collection(LineCollection(hot_lines, colors="#ff7f0e"))
+  ax.add_collection(LineCollection(cold_lines, colors="#1f77b4"))
+  ax.autoscale()
+  fig.savefig("path/to/tq_diagram.png")
+
+  # 熱量の区間ごとのたて線も表示
+  ymin, ymax = y_range(hot_lines + cold_lines)
+  heats = extract_x(hot_lines + cold_lines)
+  fig, ax = plt.subplots(1, 1)
+  ax.set_xlabel("Q [kW]")
+  ax.set_ylabel("T [℃]")
+  ax.add_collection(LineCollection(hot_lines, colors="#ff7f0e"))
+  ax.add_collection(LineCollection(cold_lines, colors="#1f77b4"))
+  ax.vlines(heats, ymin=ymin, ymax=ymax, linestyles=':', colors='k')
+  ax.autoscale()
+  fig.savefig("path/to/tq_diagram_with_vlines.png")
+
+.. image:: https://raw.githubusercontent.com/tarao1006/pyheatintegration/develop/examples/simple/tq_diagram.png
+  :width: 45%
+.. image:: https://raw.githubusercontent.com/tarao1006/pyheatintegration/develop/examples/simple/tq_diagram_with_vlines.png
+  :width: 45%
+
+.. _tq-diagram-separated:
+
+流体ごとに分割したTQ線図
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: python
+
+  hot_lines_separated, cold_lines_separated = analyzer.create_tq_separated()
+
+  # 与熱複合線と受熱複合線
+  fig, ax = plt.subplots(1, 1)
+  ax.set_xlabel("Q [kW]")
+  ax.set_ylabel("T [℃]")
+  ax.add_collection(LineCollection(hot_lines_separated, colors="#ff7f0e"))
+  ax.add_collection(LineCollection(cold_lines_separated, colors="#1f77b4"))
+  ax.autoscale()
+  fig.savefig("path/to/tq_diagram_separated.png")
+
+  # 熱量の区間ごとのたて線も表示
+  ymin, ymax = y_range(hot_lines_separated + cold_lines_separated)
+  heats_separated = extract_x(hot_lines_separated + cold_lines_separated)
+  fig, ax = plt.subplots(1, 1)
+  ax.set_xlabel("Q [kW]")
+  ax.set_ylabel("T [℃]")
+  ax.add_collection(LineCollection(hot_lines_separated, colors="#ff7f0e"))
+  ax.add_collection(LineCollection(cold_lines_separated, colors="#1f77b4"))
+  ax.vlines(heats_separated, ymin=ymin, ymax=ymax, linestyles=':', colors='gray')
+  ax.autoscale()
+  fig.savefig("path/to/tq_diagram_separated_with_vlines.png")
+
+.. image:: https://raw.githubusercontent.com/tarao1006/pyheatintegration/develop/examples/simple/tq_diagram_separated.png
+  :width: 45%
+.. image:: https://raw.githubusercontent.com/tarao1006/pyheatintegration/develop/examples/simple/tq_diagram_separated_with_vlines.png
+  :width: 45%
+
+.. _tq-diagram-split:
+
+最小接近温度差を満たすように流体を分割したTQ線図
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: python
+
+  hot_lines_split, cold_lines_split = analyzer.create_tq_split()
+
+  # 与熱複合線と受熱複合線
+  fig, ax = plt.subplots(1, 1)
+  ax.set_xlabel("Q [kW]")
+  ax.set_ylabel("T [℃]")
+  ax.add_collection(LineCollection(hot_lines_split, colors="#ff7f0e"))
+  ax.add_collection(LineCollection(cold_lines_split, colors="#1f77b4"))
+  ax.autoscale()
+  fig.savefig("path/to/tq_diagram_split.png")
+
+  # 熱量の区間ごとのたて線も表示
+  ymin, ymax = y_range(hot_lines_split + cold_lines_split)
+  heats_split = extract_x(hot_lines_separated + cold_lines_separated)
+  fig, ax = plt.subplots(1, 1)
+  ax.set_xlabel("Q [kW]")
+  ax.set_ylabel("T [℃]")
+  ax.add_collection(LineCollection(hot_lines_split, colors="#ff7f0e"))
+  ax.add_collection(LineCollection(cold_lines_split, colors="#1f77b4"))
+  ax.vlines(heats_split, ymin=ymin, ymax=ymax, linestyles=':', colors='gray')
+  ax.autoscale()
+  fig.savefig("path/to/tq_diagram_split_with_vlines.png")
+
+.. image:: https://raw.githubusercontent.com/tarao1006/pyheatintegration/develop/examples/simple/tq_diagram_split.png
+  :width: 45%
+.. image:: https://raw.githubusercontent.com/tarao1006/pyheatintegration/develop/examples/simple/tq_diagram_split_with_vlines.png
+  :width: 45%
+
+.. _tq-diagram-merged:
+
+分割後の流体のうち、結合可能な熱交換器を結合したTQ線図
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: python
+
+  hot_lines_merged, cold_lines_merged = analyzer.create_tq_merged()
+
+  # 与熱複合線と受熱複合線
+  fig, ax = plt.subplots(1, 1)
+  ax.set_xlabel("Q [kW]")
+  ax.set_ylabel("T [℃]")
+  ax.add_collection(LineCollection(hot_lines_merged, colors="#ff7f0e"))
+  ax.add_collection(LineCollection(cold_lines_merged, colors="#1f77b4"))
+  ax.autoscale()
+  fig.savefig("path/to/tq_diagram_merged.png")
+
+  # 熱量の区間ごとのたて線も表示
+  ymin, ymax = y_range(hot_lines_merged + cold_lines_merged)
+  heats_merged = extract_x(hot_lines_merged + cold_lines_merged)
+  fig, ax = plt.subplots(1, 1)
+  ax.set_xlabel("Q [kW]")
+  ax.set_ylabel("T [℃]")
+  ax.add_collection(LineCollection(hot_lines_merged, colors="#ff7f0e"))
+  ax.add_collection(LineCollection(cold_lines_merged, colors="#1f77b4"))
+  ax.vlines(heats_merged, ymin=ymin, ymax=ymax, linestyles=':', colors='gray')
+  ax.autoscale()
+  fig.savefig("path/to/tq_diagram_merged_with_vlines.png")
+
+.. image:: https://raw.githubusercontent.com/tarao1006/pyheatintegration/develop/examples/simple/tq_diagram_merged.png
+  :width: 45%
+.. image:: https://raw.githubusercontent.com/tarao1006/pyheatintegration/develop/examples/simple/tq_diagram_merged_with_vlines.png
+  :width: 45%
diff --git a/docs/excel.rst b/docs/excel.rst
new file mode 100644
index 0000000..e08bc40
--- /dev/null
+++ b/docs/excel.rst
@@ -0,0 +1,44 @@
+Step4. Export to Excel
+======================
+
+.. note::
+  Matplotlibではなく、Excelを用いてグラフを作成したい方向けの説明です。
+
+グラフを作成する際にmatplotlibではなくExcelを用いたい場合、プロットに必要なデータを加工する必\
+要があります。例で用いる ``analyzer`` は以下のコードによって作成されたと仮定しています。
+
+.. code-block:: python
+
+  from pyheatintegration import PinchAnalyzer, Stream
+
+  streams = [
+      Stream(40.0, 90.0, 150.0),
+      Stream(80.0, 110.0, 180.0),
+      Stream(125.0, 80.0, 180.0),
+      Stream(100.0, 60.0, 160.0)
+  ]
+
+  minimum_approach_temperature_difference = 10.0
+  analyzer = PinchAnalyzer(streams, minimum_approach_temperature_difference)
+
+``convert_to_excel_data`` を用いることで、直線のリストをx座標のリストとy座標のリストに変換\
+することができます。その後、例えば ``numpy`` などを用いてCSVファイルに書き込んだ後に、Excel\
+で処理してください。
+
+.. code-block:: python
+
+  from pyheatintegration import convert_to_excel_data
+  import numpy as np
+
+  hot_lines, cold_lines = analyzer.create_tq()
+  hot_lines_excel = convert_to_excel_data(hot_lines)
+  cold_lines_excel = convert_to_excel_data(cold_lines)
+
+  print(hot_lines_excel)
+  # ([0.0, 40.0, 80.0, 160.0, 240.0, 250.0, 340.0], [60.0, 70.0, 80.0, 90.0, 100.0, 102.5, 125.0])
+
+  print(cold_lines_excel)
+  # ([40.0, 80.0, 160.0, 240.0, 250.0, 340.0, 370.0], [40.0, 53.33333333333333, 80.0, 88.88888888888889, 90.0, 105.0, 110.0])
+
+  np.savetxt("./tq_diagram_hot.csv", np.array(hot_lines_excel).T)
+  np.savetxt("./tq_diagram_cold.csv", np.array(cold_lines_excel).T)
diff --git a/docs/getting_started.rst b/docs/getting_started.rst
new file mode 100644
index 0000000..4611015
--- /dev/null
+++ b/docs/getting_started.rst
@@ -0,0 +1,14 @@
+Getting Started
+===============
+
+図を描くための手順を説明しています。最低限のPythonの知識があることを前提としています。また、実\
+際に動く例は `simple example <https://github.com/tarao1006/pyheatintegration/blob/19d6eda58a58e8c4ae0b3e15726297773e0a546c/examples/simple/main.py>`_
+を参照してください。
+
+.. toctree::
+  :maxdepth: 2
+
+  create_stream
+  analysis
+  draw_graph
+  excel
diff --git a/docs/heat_exchanger_cost.rst b/docs/heat_exchanger_cost.rst
new file mode 100644
index 0000000..3d67d13
--- /dev/null
+++ b/docs/heat_exchanger_cost.rst
@@ -0,0 +1,44 @@
+Heat Exchanger Cost
+===================
+
+.. note::
+  グランドコンポジットカーブやTQ線図の作成方法は :doc:`getting_started` を読んでください。
+
+熱交換器のコストを、以下の式によって推算しています。
+
+.. math::
+
+  \mathrm{Cost [yen]} = 1,500,000 \times A [\mathrm{m^2}]^{0.65} \times k
+
+ここで、 :math:`A` は熱交換器の面積、 :math:`k` は係数で、熱交換器がリボイラーまたは反応器を\
+流れる流体に対して用いる場合は ``2.0`` とし、それ以外の場合は ``1.0`` としています。
+
+熱交換器の面積は、以下の式を用いて求めています。
+
+.. math::
+
+  Q = U A \varDelta T_\mathrm{LMTD}
+
+* :math:`Q` [:math:`\mathrm{W}`]: 伝熱量
+* :math:`U` [:math:`\mathrm{W}/\mathrm{m}^2 \cdot \mathrm{K}`]: 総括伝熱係数
+* :math:`A` [:math:`\mathrm{m^2}`]: 熱交換器面積
+* :math:`T_\mathrm{LMTD}` [:math:`\mathrm{K}`]: 対数平均温度差
+
+総括伝熱係数の値は、 `プロセスデザインコンテスト <http://scejcontest.chem-eng.kyushu-u.ac.jp/2019/download/processsim2019_v1.pdf>`_
+を参考にして指定しています ( :ref:`総括伝熱係数の表 <overall_heat_transfer_coefficient>` )。\
+また、対数平均温度差は向流を仮定して求めています。係数 :math:`k` は ``Stream`` のコンストラ\
+クタの引数 ``reboiler_or_reactor`` の値を用いて決定しています。
+
+熱交換器コストの合計は、 ``PinchAnalyzer`` 経由で取得することが可能です。
+
+.. code-block:: python
+
+  analyzer.get_heat_exchanger_cost(ignore_unknown=True)
+  # or 
+  analyzer.get_heat_exchanger_cost(ignore_unknown=False)
+
+コスト計算の際に、流体の状態が ``StreamState.UNKNOWN`` に設定されている場合、総括伝熱係数が\
+設定されていないため、熱交換器の面積を求めることができず、コストを求めることができません。その際\
+に、エラーを生じさせるかどうかを ``ignore_unknown`` によって設定することが可能です。\
+``True`` の場合、エラーは生じず、 ``False``　の場合、エラーが生じ、プログラムが終了します。\
+デフォルトは ``True`` に設定されています。
diff --git a/docs/images/grand_composite_curve.png b/docs/images/grand_composite_curve.png
deleted file mode 100644
index b7ec5da..0000000
Binary files a/docs/images/grand_composite_curve.png and /dev/null differ
diff --git a/docs/images/tq_diagram.png b/docs/images/tq_diagram.png
deleted file mode 100644
index 89c131f..0000000
Binary files a/docs/images/tq_diagram.png and /dev/null differ
diff --git a/docs/images/tq_diagram_merged.png b/docs/images/tq_diagram_merged.png
deleted file mode 100644
index 10726ce..0000000
Binary files a/docs/images/tq_diagram_merged.png and /dev/null differ
diff --git a/docs/images/tq_diagram_separeted.png b/docs/images/tq_diagram_separeted.png
deleted file mode 100644
index 95c620a..0000000
Binary files a/docs/images/tq_diagram_separeted.png and /dev/null differ
diff --git a/docs/images/tq_diagram_splitted.png b/docs/images/tq_diagram_splitted.png
deleted file mode 100644
index 5a9ccec..0000000
Binary files a/docs/images/tq_diagram_splitted.png and /dev/null differ
diff --git a/docs/index.rst b/docs/index.rst
index e997d8f..cfd2771 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,7 +1,7 @@
 pyheatintegration reference documentation
 =========================================
 
-pyheatintegrationは `プロセス設計`_ で必要なヒートインテグレーションを支援するパッケージで
+pyheatintegrationは `プロセス設計`_ で必要なヒートインテグレーションを支援するパッケージで\
 す。グランドコンポジットカーブの作成、TQ線図の作成、必要な熱交換器数の導出を行うことができます。
 
 Installation
@@ -9,236 +9,97 @@ Installation
 
 ::
 
-   pip install pyheatintegration
+  pip install pyheatintegration
 
 Examples
 ========
 
 * `simple example <https://github.com/tarao1006/pyheatintegration/blob/19d6eda58a58e8c4ae0b3e15726297773e0a546c/examples/simple/main.py>`_
 
-Getting Started
-===============
+Quick Start
+===========
 
 .. code-block:: python
 
-   import matplotlib.pyplot as plt
-   from matplotlib.collections import LineCollection
-
-   from src.pyheatintegration import (PinchAnalyzer, Stream, StreamType,
-                                      extract_x, y_range)
-
-   # 熱交換を行う流体を準備
-   streams = [
-      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')
-   ]
-   minimum_approach_temperature_difference = 10.0
-   analyzer = PinchAnalyzer(streams, minimum_approach_temperature_difference)
-
-   # グランドコンポジットカーブ
-   heats, temps = analyzer.create_grand_composite_curve()
-   fig, ax = plt.subplots(1, 1)
-   ax.set_xlabel("Q [kW]")
-   ax.set_ylabel("Shifted Temperature [℃]")
-   ax.plot(heats, temps)
-   fig.savefig("path/to/grand_composite_curve.png")
-
-   # TQ線図
-   hot_lines, cold_lines = analyzer.create_tq()
-   ymin, ymax = y_range(hot_lines + cold_lines)
-   heats = extract_x(hot_lines + cold_lines)
-   fig, ax = plt.subplots(1, 1)
-   ax.set_xlabel("Q [kW]")
-   ax.set_ylabel("T [℃]")
-   ax.add_collection(LineCollection(hot_lines, colors="#ff7f0e"))
-   ax.add_collection(LineCollection(cold_lines, colors="#1f77b4"))
-   ax.vlines(heats, ymin=ymin, ymax=ymax, linestyles=':', colors='k')
-   ax.autoscale()
-   fig.savefig("path/to/tq_diagram.png")
-
-   # 分割したTQ線図
-   hot_lines_separated, cold_lines_separated = analyzer.create_tq_separated()
-   ymin, ymax = y_range(hot_lines_separated + cold_lines_separated)
-   heats_separated = extract_x(hot_lines_separated + cold_lines_separated)
-   fig, ax = plt.subplots(1, 1)
-   ax.set_xlabel("Q [kW]")
-   ax.set_ylabel("T [℃]")
-   ax.add_collection(LineCollection(hot_lines_separated, colors="#ff7f0e"))
-   ax.add_collection(LineCollection(cold_lines_separated, colors="#1f77b4"))
-   ax.vlines(heats_separated, ymin=ymin, ymax=ymax, linestyles=':', colors='gray')
-   ax.vlines(heats, ymin=ymin, ymax=ymax, linestyles=':', colors='k')
-   ax.autoscale()
-   fig.savefig("path/to/tq_diagram_separeted.png")
-
-   # 最小接近温度差を満たす分割したTQ線図
-   hot_lines_splitted, cold_lines_splitted = analyzer.create_tq_splitted()
-   ymin, ymax = y_range(hot_lines_splitted + cold_lines_splitted)
-   heats_splitted = extract_x(hot_lines_separated + cold_lines_separated)
-   fig, ax = plt.subplots(1, 1)
-   ax.set_xlabel("Q [kW]")
-   ax.set_ylabel("T [℃]")
-   ax.add_collection(LineCollection(hot_lines_splitted, colors="#ff7f0e"))
-   ax.add_collection(LineCollection(cold_lines_splitted, colors="#1f77b4"))
-   ax.vlines(heats_splitted, ymin=ymin, ymax=ymax, linestyles=':', colors='gray')
-   ax.vlines(heats, ymin=ymin, ymax=ymax, linestyles=':', colors='k')
-   ax.autoscale()
-   fig.savefig("path/to/tq_diagram_splitted.png")
-
-   # 熱交換器を結合したTQ線図
-   hot_lines_merged, cold_lines_merged = analyzer.create_tq_merged()
-   ymin, ymax = y_range(hot_lines_merged + cold_lines_merged)
-   heats_merged = extract_x(hot_lines_merged + cold_lines_merged)
-   fig, ax = plt.subplots(1, 1)
-   ax.set_xlabel("Q [kW]")
-   ax.set_ylabel("T [℃]")
-   ax.add_collection(LineCollection(hot_lines_merged, colors="#ff7f0e"))
-   ax.add_collection(LineCollection(cold_lines_merged, colors="#1f77b4"))
-   ax.vlines(heats_merged, ymin=ymin, ymax=ymax, linestyles=':', colors='gray')
-   ax.autoscale()
-   fig.savefig("path/to/tq_diagram_merged.png")
+  import matplotlib.pyplot as plt
+  from matplotlib.collections import LineCollection
+
+  from src.pyheatintegration import PinchAnalyzer, Stream
+
+  # 熱交換を行う流体を準備
+  streams = [
+    Stream(40.0, 90.0, 150.0),
+    Stream(80.0, 110.0, 180.0),
+    Stream(125.0, 80.0, 180.0),
+    Stream(100.0, 60.0, 160.0)
+  ]
+
+  # 最小接近温度差を指定し、作成した流体のリストとともにPinchAnalyzerのインスタンスを得る。
+  minimum_approach_temperature_difference = 10.0
+  analyzer = PinchAnalyzer(streams, minimum_approach_temperature_difference)
+
+  # グランドコンポジットカーブ
+  heats, temps = analyzer.create_grand_composite_curve()
+  fig, ax = plt.subplots(1, 1)
+  ax.set_xlabel("Q [kW]")
+  ax.set_ylabel("Shifted Temperature [℃]")
+  ax.plot(heats, temps)
+  fig.savefig("path/to/grand_composite_curve.png")
+
+  # TQ線図
+  hot_lines, cold_lines = analyzer.create_tq()
+  ymin, ymax = y_range(hot_lines + cold_lines)
+  heats = extract_x(hot_lines + cold_lines)
+  fig, ax = plt.subplots(1, 1)
+  ax.set_xlabel("Q [kW]")
+  ax.set_ylabel("T [℃]")
+  ax.add_collection(LineCollection(hot_lines, colors="#ff7f0e"))
+  ax.add_collection(LineCollection(cold_lines, colors="#1f77b4"))
+  ax.vlines(heats, ymin=ymin, ymax=ymax, linestyles=':', colors='k')
+  ax.autoscale()
+  fig.savefig("path/to/tq_diagram.png")
 
 * グランドコンポジットカーブ
 
-.. image:: images/grand_composite_curve.png
-   :width: 400
+.. image:: https://raw.githubusercontent.com/tarao1006/pyheatintegration/develop/examples/simple/grand_composite_curve.png
+  :width: 400
 
 * TQ線図
 
-.. image:: images/tq_diagram.png
-   :width: 400
+.. image:: https://raw.githubusercontent.com/tarao1006/pyheatintegration/develop/examples/simple/tq_diagram.png
+  :width: 400
 
-* TQ線図(流体ごとに分割)
+より詳しい説明は :doc:`getting_started` を確認してください。
 
-.. image:: images/tq_diagram_separeted.png
-   :width: 400
-
-* TQ線図(流体ごとに分割。最小接近温度差を満たす。)
-
-.. image:: images/tq_diagram_splitted.png
-   :width: 400
-
-* TQ線図(結合可能な熱交換器を結合。)
-
-.. image:: images/tq_diagram_merged.png
-    :width: 400
-
-Details
-=======
-
-熱交換を行いたい流体の情報を元に、 ``Stream`` のリストを生成します。
-
-.. code-block:: python
-
-   streams = [
-      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')
-   ]
-
-``Stream`` のコンストラクタ:
-
-.. py:class:: Stream(input_temperature: float, output_temperature: float, heat_flow: float, type_: StreamType, cost: float = 0.0)
-
-.. code-block:: python
-
-   # 受熱流体の例。StreamType(1)を設定。
-   Stream(40.0, 50.0, 150.0, StreamType(1))
-   Stream(50.0, 50.0, 150.0, StreamType(1))  # 等温
-
-   # 与熱流体の例。StreamType(2)を設定。
-   Stream(125.0, 80.0, 180.0, StreamType(2))
-   Stream(125.0, 125.0, 180.0, StreamType(2))
-
-   # 外部受熱流体の例。StreamType(3)を設定。
-   Stream(40.0, 50.0, 0.0, StreamType(3))
-   Stream(60.0, 60.0, 0.0, StreamType(3))
-
-   # 外部与熱流体の例。StreamType(4)を設定。
-   Stream(125.0, 80.0, 0.0, StreamType(4))
-   Stream(100.0, 100.0, 0.0, StreamType(4))
-
-受熱流体は ``入り口温度 ≤ 出口温度`` 、与熱流体は ``入り口温度 ≥ 出口温度`` である必要があり
-ます。等温流体の設定も可能です。外部流体の熱量は、グランドコンポジットカーブ作成時に決定するため、
-コンストラクタには ``0`` を渡してください。第五引数にはコストを設定することも可能ですが、現在は
-利用していません。将来利用するように変更予定です。
-
-流体のリストと最小接近温度差を引数に ``PinchAnalyzer`` のインスタンスを生成します。
-
-``PinchAnalyzer`` のコンストラクタ:
-
-.. py:class:: PinchAnalyzer(streams: list[Stream], minimum_approach_temperature_difference: float)
-
-.. code-block:: python
-
-   minimum_approach_temperature_difference = 10.0
-
-   # 流体のリストと最小接近温度差を渡してPinchAnalyzerのインスタンスを生成
-   analyzer = PinchAnalyzer(streams, minimum_approach_temperature_difference)
-
-   # create_*()を呼ぶことでプロットに必要な情報を取得
-   heats, temps = analyzer.create_grand_composite_curve()
-   hot_lines, cold_lines = analyzer.create_tq()
-   hot_lines_separated, cold_lines_separated = analyzer.create_tq_separated()
-   hot_lines_splitted, cold_lines_splitted = analyzer.create_tq_splitted()
-   hot_lines_merged, cold_lines_merged = analyzer.create_tq_merged()
-
-``create_tq()`` ``create_tq_separated()`` ``create_tq_splitted()``
-``create_tq_merged()`` は、プロットに必要な直線を以下のような形式で返します。
-
-.. code-block:: python
-
-   # [((始点の座標), (終点の座標))...]
-   lines = [((0, 0), (1, 1)), ((1, 2), (2, 3))]
-
-タプルの第一成分が直線の始点の座標、第二成分が終点の座標を表します。また、与熱複合線と受熱複合線
-をタプルで返します。それぞれを ``matplotlib.collections.LineCollection`` に変換後、
-``ax.add_collection`` を行うことで直線をプロットすることができます。
-
-.. code-block:: python
-
-   fig, ax = plt.subplots()
-   hot_lines, cold_lines = analyzer.create_tq()
-   ax.add_collection(LineCollection(hot_lines))
-   ax.add_collection(LineCollection(cold_lines))
-
-複合線において、直線が折れ曲がっている点を通る熱量の線をプロットしたい場合、 ``y_range`` と
-``extract_x`` を呼ぶことで、必要な情報を得ることができます。
-
-.. py:function:: y_range(hot_lines: list[Line], cold_lines: list[Line]) -> tuple[float, float]
-
-.. py:function:: extract_x(lines: list[Line]) -> list[float]
-
-.. code-block:: python
+.. toctree::
+  :maxdepth: 2
+  :caption: Getting Started
 
-   ymin, ymax = y_range(hot_lines + cold_lines)
-   heats = extract_x(hot_lines + cold_lines)
-   ax.vlines(heats, ymin=ymin, ymax=ymax, linestyles=':', colors='k')
+  getting_started
+  heat_exchanger_cost
 
 .. toctree::
-   :maxdepth: 2
-   :caption: API
+  :maxdepth: 2
+  :caption: API
 
-   pinch_analyzer
-   grand_composite_curve
-   tq_diagram
-   stream
-   heat_exchanger
-   enums
+  pinch_analyzer
+  grand_composite_curve
+  tq_diagram
+  stream
+  heat_exchanger
+  enums
 
 .. toctree::
-   :maxdepth: 2
-   :caption: ALL API
-
-   base_range
-   errors
-   heat_range
-   line
-   plot_segment
-   segment
-   temperature_range
+  :maxdepth: 2
+  :caption: ALL API
+
+  base_range
+  errors
+  heat_range
+  line
+  plot_segment
+  segment
+  temperature_range
 
 Indices and tables
 ==================
diff --git a/examples/simple/data.csv b/examples/simple/data.csv
index 59103fd..8d12fd1 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,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,
+A,125.0,80.0,180.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/grand_composite_curve.png b/examples/simple/grand_composite_curve.png
index b7ec5da..29c36f5 100644
Binary files a/examples/simple/grand_composite_curve.png and b/examples/simple/grand_composite_curve.png differ
diff --git a/examples/simple/main.py b/examples/simple/main.py
index 43c8e1c..917c465 100644
--- a/examples/simple/main.py
+++ b/examples/simple/main.py
@@ -3,7 +3,7 @@
 from matplotlib.collections import LineCollection
 
 from pyheatintegration import (PinchAnalyzer, Stream, StreamState, StreamType,
-                               extract_x, y_range)
+                               convert_to_excel_data, extract_x, y_range)
 
 
 def main():
@@ -28,7 +28,7 @@ def main():
         f" ~ {analyzer.minimum_approach_temp_diff_range.finish:.3f}"
     )
 
-    print(f'ピンチポイント [℃]: {analyzer.gcc.minimum_pinch_point_temp}')
+    print(f'ピンチポイント [℃]: {analyzer.gcc.pinch_point_temp()}')
     print(f'必要加熱量[W]: {analyzer.external_heating_demand:.3f}')
     print(f'必要冷却量[W]: {analyzer.external_cooling_demand:.3f}')
 
@@ -62,6 +62,17 @@ def main():
 
     # TQ線図
     hot_lines, cold_lines = analyzer.create_tq()
+
+    # 与熱複合線と受熱複合線
+    fig, ax = plt.subplots(1, 1)
+    ax.set_xlabel("Q [kW]")
+    ax.set_ylabel("T [℃]")
+    ax.add_collection(LineCollection(hot_lines, colors="#ff7f0e"))
+    ax.add_collection(LineCollection(cold_lines, colors="#1f77b4"))
+    ax.autoscale()
+    fig.savefig("./tq_diagram.png")
+
+    # 熱量の区間ごとのたて線も表示
     ymin, ymax = y_range(hot_lines + cold_lines)
     heats = extract_x(hot_lines + cold_lines)
     fig, ax = plt.subplots(1, 1)
@@ -71,10 +82,21 @@ def main():
     ax.add_collection(LineCollection(cold_lines, colors="#1f77b4"))
     ax.vlines(heats, ymin=ymin, ymax=ymax, linestyles=':', colors='k')
     ax.autoscale()
-    fig.savefig("./tq_diagram.png")
+    fig.savefig("./tq_diagram_with_vlines.png")
 
     # 分割したTQ線図
     hot_lines_separated, cold_lines_separated = analyzer.create_tq_separated()
+
+    # 与熱複合線と受熱複合線
+    fig, ax = plt.subplots(1, 1)
+    ax.set_xlabel("Q [kW]")
+    ax.set_ylabel("T [℃]")
+    ax.add_collection(LineCollection(hot_lines_separated, colors="#ff7f0e"))
+    ax.add_collection(LineCollection(cold_lines_separated, colors="#1f77b4"))
+    ax.autoscale()
+    fig.savefig("./tq_diagram_separated.png")
+
+    # 熱量の区間ごとのたて線も表示
     ymin, ymax = y_range(hot_lines_separated + cold_lines_separated)
     heats_separated = extract_x(hot_lines_separated + cold_lines_separated)
     fig, ax = plt.subplots(1, 1)
@@ -83,26 +105,46 @@ def main():
     ax.add_collection(LineCollection(hot_lines_separated, colors="#ff7f0e"))
     ax.add_collection(LineCollection(cold_lines_separated, colors="#1f77b4"))
     ax.vlines(heats_separated, ymin=ymin, ymax=ymax, linestyles=':', colors='gray')
-    ax.vlines(heats, ymin=ymin, ymax=ymax, linestyles=':', colors='k')
     ax.autoscale()
-    fig.savefig("./tq_diagram_separeted.png")
+    fig.savefig("./tq_diagram_separated_with_vlines.png")
 
     # 最小接近温度差を満たす分割したTQ線図
-    hot_lines_splitted, cold_lines_splitted = analyzer.create_tq_splitted()
-    ymin, ymax = y_range(hot_lines_splitted + cold_lines_splitted)
-    heats_splitted = extract_x(hot_lines_separated + cold_lines_separated)
+    hot_lines_split, cold_lines_split = analyzer.create_tq_split()
+
+    # 与熱複合線と受熱複合線
     fig, ax = plt.subplots(1, 1)
     ax.set_xlabel("Q [kW]")
     ax.set_ylabel("T [℃]")
-    ax.add_collection(LineCollection(hot_lines_splitted, colors="#ff7f0e"))
-    ax.add_collection(LineCollection(cold_lines_splitted, colors="#1f77b4"))
-    ax.vlines(heats_splitted, ymin=ymin, ymax=ymax, linestyles=':', colors='gray')
-    ax.vlines(heats, ymin=ymin, ymax=ymax, linestyles=':', colors='k')
+    ax.add_collection(LineCollection(hot_lines_split, colors="#ff7f0e"))
+    ax.add_collection(LineCollection(cold_lines_split, colors="#1f77b4"))
     ax.autoscale()
-    fig.savefig("./tq_diagram_splitted.png")
+    fig.savefig("./tq_diagram_split.png")
+
+    # 熱量の区間ごとのたて線も表示
+    ymin, ymax = y_range(hot_lines_split + cold_lines_split)
+    heats_split = extract_x(hot_lines_separated + cold_lines_separated)
+    fig, ax = plt.subplots(1, 1)
+    ax.set_xlabel("Q [kW]")
+    ax.set_ylabel("T [℃]")
+    ax.add_collection(LineCollection(hot_lines_split, colors="#ff7f0e"))
+    ax.add_collection(LineCollection(cold_lines_split, colors="#1f77b4"))
+    ax.vlines(heats_split, ymin=ymin, ymax=ymax, linestyles=':', colors='gray')
+    ax.autoscale()
+    fig.savefig("./tq_diagram_split_with_vlines.png")
 
     # 熱交換器を結合したTQ線図
     hot_lines_merged, cold_lines_merged = analyzer.create_tq_merged()
+
+    # 与熱複合線と受熱複合線
+    fig, ax = plt.subplots(1, 1)
+    ax.set_xlabel("Q [kW]")
+    ax.set_ylabel("T [℃]")
+    ax.add_collection(LineCollection(hot_lines_merged, colors="#ff7f0e"))
+    ax.add_collection(LineCollection(cold_lines_merged, colors="#1f77b4"))
+    ax.autoscale()
+    fig.savefig("./tq_diagram_merged.png")
+
+    # 熱量の区間ごとのたて線も表示
     ymin, ymax = y_range(hot_lines_merged + cold_lines_merged)
     heats_merged = extract_x(hot_lines_merged + cold_lines_merged)
     fig, ax = plt.subplots(1, 1)
@@ -112,7 +154,16 @@ def main():
     ax.add_collection(LineCollection(cold_lines_merged, colors="#1f77b4"))
     ax.vlines(heats_merged, ymin=ymin, ymax=ymax, linestyles=':', colors='gray')
     ax.autoscale()
-    fig.savefig("./tq_diagram_merged.png")
+    fig.savefig("./tq_diagram_merged_with_vlines.png")
+
+    # TQ線図(Excel風)
+    hot_lines, cold_lines = analyzer.create_tq()
+    fig, ax = plt.subplots(1, 1)
+    ax.set_xlabel("Q [kW]")
+    ax.set_ylabel("T [℃]")
+    ax.plot(*convert_to_excel_data(hot_lines), color="#ff7f0e")
+    ax.plot(*convert_to_excel_data(cold_lines), color="#1f77b4")
+    fig.savefig("./tq_diagram_excel.png")
 
 
 if __name__ == '__main__':
diff --git a/examples/simple/tq_diagram.png b/examples/simple/tq_diagram.png
index 89c131f..5cc07fd 100644
Binary files a/examples/simple/tq_diagram.png and b/examples/simple/tq_diagram.png differ
diff --git a/examples/simple/tq_diagram_excel.png b/examples/simple/tq_diagram_excel.png
new file mode 100644
index 0000000..9d5b040
Binary files /dev/null and b/examples/simple/tq_diagram_excel.png differ
diff --git a/examples/simple/tq_diagram_merged.png b/examples/simple/tq_diagram_merged.png
index 10726ce..9489c1c 100644
Binary files a/examples/simple/tq_diagram_merged.png and b/examples/simple/tq_diagram_merged.png differ
diff --git a/examples/simple/tq_diagram_merged_with_vlines.png b/examples/simple/tq_diagram_merged_with_vlines.png
new file mode 100644
index 0000000..04edc79
Binary files /dev/null and b/examples/simple/tq_diagram_merged_with_vlines.png differ
diff --git a/examples/simple/tq_diagram_separated.png b/examples/simple/tq_diagram_separated.png
new file mode 100644
index 0000000..bc0701f
Binary files /dev/null and b/examples/simple/tq_diagram_separated.png differ
diff --git a/examples/simple/tq_diagram_separated_with_vlines.png b/examples/simple/tq_diagram_separated_with_vlines.png
new file mode 100644
index 0000000..5f47507
Binary files /dev/null and b/examples/simple/tq_diagram_separated_with_vlines.png differ
diff --git a/examples/simple/tq_diagram_separeted.png b/examples/simple/tq_diagram_separeted.png
deleted file mode 100644
index 95c620a..0000000
Binary files a/examples/simple/tq_diagram_separeted.png and /dev/null differ
diff --git a/examples/simple/tq_diagram_split.png b/examples/simple/tq_diagram_split.png
new file mode 100644
index 0000000..5d2a748
Binary files /dev/null and b/examples/simple/tq_diagram_split.png differ
diff --git a/examples/simple/tq_diagram_split_with_vlines.png b/examples/simple/tq_diagram_split_with_vlines.png
new file mode 100644
index 0000000..e3ad4a5
Binary files /dev/null and b/examples/simple/tq_diagram_split_with_vlines.png differ
diff --git a/examples/simple/tq_diagram_splitted.png b/examples/simple/tq_diagram_splitted.png
deleted file mode 100644
index 5a9ccec..0000000
Binary files a/examples/simple/tq_diagram_splitted.png and /dev/null differ
diff --git a/examples/simple/tq_diagram_with_vlines.png b/examples/simple/tq_diagram_with_vlines.png
new file mode 100644
index 0000000..a7e1e2d
Binary files /dev/null and b/examples/simple/tq_diagram_with_vlines.png differ
diff --git a/examples/simple_with_external/data.csv b/examples/simple_with_external/data.csv
new file mode 100644
index 0000000..d5c57c2
--- /dev/null
+++ b/examples/simple_with_external/data.csv
@@ -0,0 +1,7 @@
+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,180.0,2,1,0,
+B,100.0,60.0,160.0,2,1,0,
+c,20.0,20.0,0.0,3,1,0,
+C,150.0,150.0,0.0,4,1,0,
\ No newline at end of file
diff --git a/examples/simple_with_external/grand_composite_curve.png b/examples/simple_with_external/grand_composite_curve.png
new file mode 100644
index 0000000..29c36f5
Binary files /dev/null and b/examples/simple_with_external/grand_composite_curve.png differ
diff --git a/examples/simple_with_external/main.py b/examples/simple_with_external/main.py
new file mode 100644
index 0000000..917c465
--- /dev/null
+++ b/examples/simple_with_external/main.py
@@ -0,0 +1,170 @@
+import matplotlib.pyplot as plt
+import pandas as pd
+from matplotlib.collections import LineCollection
+
+from pyheatintegration import (PinchAnalyzer, Stream, StreamState, StreamType,
+                               convert_to_excel_data, extract_x, y_range)
+
+
+def main():
+    df = pd.read_csv("./data.csv").fillna({'reboiler_or_reactor': ''})
+    streams = [
+        Stream(
+            row.input_temperature,
+            row.output_temperature,
+            row.heat_flow,
+            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()]
+
+    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.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)
+    ax.set_xlabel("Q [kW]")
+    ax.set_ylabel("Shifted Temperature [℃]")
+    ax.plot(heats, temps)
+    fig.savefig("./grand_composite_curve.png")
+
+    # TQ線図
+    hot_lines, cold_lines = analyzer.create_tq()
+
+    # 与熱複合線と受熱複合線
+    fig, ax = plt.subplots(1, 1)
+    ax.set_xlabel("Q [kW]")
+    ax.set_ylabel("T [℃]")
+    ax.add_collection(LineCollection(hot_lines, colors="#ff7f0e"))
+    ax.add_collection(LineCollection(cold_lines, colors="#1f77b4"))
+    ax.autoscale()
+    fig.savefig("./tq_diagram.png")
+
+    # 熱量の区間ごとのたて線も表示
+    ymin, ymax = y_range(hot_lines + cold_lines)
+    heats = extract_x(hot_lines + cold_lines)
+    fig, ax = plt.subplots(1, 1)
+    ax.set_xlabel("Q [kW]")
+    ax.set_ylabel("T [℃]")
+    ax.add_collection(LineCollection(hot_lines, colors="#ff7f0e"))
+    ax.add_collection(LineCollection(cold_lines, colors="#1f77b4"))
+    ax.vlines(heats, ymin=ymin, ymax=ymax, linestyles=':', colors='k')
+    ax.autoscale()
+    fig.savefig("./tq_diagram_with_vlines.png")
+
+    # 分割したTQ線図
+    hot_lines_separated, cold_lines_separated = analyzer.create_tq_separated()
+
+    # 与熱複合線と受熱複合線
+    fig, ax = plt.subplots(1, 1)
+    ax.set_xlabel("Q [kW]")
+    ax.set_ylabel("T [℃]")
+    ax.add_collection(LineCollection(hot_lines_separated, colors="#ff7f0e"))
+    ax.add_collection(LineCollection(cold_lines_separated, colors="#1f77b4"))
+    ax.autoscale()
+    fig.savefig("./tq_diagram_separated.png")
+
+    # 熱量の区間ごとのたて線も表示
+    ymin, ymax = y_range(hot_lines_separated + cold_lines_separated)
+    heats_separated = extract_x(hot_lines_separated + cold_lines_separated)
+    fig, ax = plt.subplots(1, 1)
+    ax.set_xlabel("Q [kW]")
+    ax.set_ylabel("T [℃]")
+    ax.add_collection(LineCollection(hot_lines_separated, colors="#ff7f0e"))
+    ax.add_collection(LineCollection(cold_lines_separated, colors="#1f77b4"))
+    ax.vlines(heats_separated, ymin=ymin, ymax=ymax, linestyles=':', colors='gray')
+    ax.autoscale()
+    fig.savefig("./tq_diagram_separated_with_vlines.png")
+
+    # 最小接近温度差を満たす分割したTQ線図
+    hot_lines_split, cold_lines_split = analyzer.create_tq_split()
+
+    # 与熱複合線と受熱複合線
+    fig, ax = plt.subplots(1, 1)
+    ax.set_xlabel("Q [kW]")
+    ax.set_ylabel("T [℃]")
+    ax.add_collection(LineCollection(hot_lines_split, colors="#ff7f0e"))
+    ax.add_collection(LineCollection(cold_lines_split, colors="#1f77b4"))
+    ax.autoscale()
+    fig.savefig("./tq_diagram_split.png")
+
+    # 熱量の区間ごとのたて線も表示
+    ymin, ymax = y_range(hot_lines_split + cold_lines_split)
+    heats_split = extract_x(hot_lines_separated + cold_lines_separated)
+    fig, ax = plt.subplots(1, 1)
+    ax.set_xlabel("Q [kW]")
+    ax.set_ylabel("T [℃]")
+    ax.add_collection(LineCollection(hot_lines_split, colors="#ff7f0e"))
+    ax.add_collection(LineCollection(cold_lines_split, colors="#1f77b4"))
+    ax.vlines(heats_split, ymin=ymin, ymax=ymax, linestyles=':', colors='gray')
+    ax.autoscale()
+    fig.savefig("./tq_diagram_split_with_vlines.png")
+
+    # 熱交換器を結合したTQ線図
+    hot_lines_merged, cold_lines_merged = analyzer.create_tq_merged()
+
+    # 与熱複合線と受熱複合線
+    fig, ax = plt.subplots(1, 1)
+    ax.set_xlabel("Q [kW]")
+    ax.set_ylabel("T [℃]")
+    ax.add_collection(LineCollection(hot_lines_merged, colors="#ff7f0e"))
+    ax.add_collection(LineCollection(cold_lines_merged, colors="#1f77b4"))
+    ax.autoscale()
+    fig.savefig("./tq_diagram_merged.png")
+
+    # 熱量の区間ごとのたて線も表示
+    ymin, ymax = y_range(hot_lines_merged + cold_lines_merged)
+    heats_merged = extract_x(hot_lines_merged + cold_lines_merged)
+    fig, ax = plt.subplots(1, 1)
+    ax.set_xlabel("Q [kW]")
+    ax.set_ylabel("T [℃]")
+    ax.add_collection(LineCollection(hot_lines_merged, colors="#ff7f0e"))
+    ax.add_collection(LineCollection(cold_lines_merged, colors="#1f77b4"))
+    ax.vlines(heats_merged, ymin=ymin, ymax=ymax, linestyles=':', colors='gray')
+    ax.autoscale()
+    fig.savefig("./tq_diagram_merged_with_vlines.png")
+
+    # TQ線図(Excel風)
+    hot_lines, cold_lines = analyzer.create_tq()
+    fig, ax = plt.subplots(1, 1)
+    ax.set_xlabel("Q [kW]")
+    ax.set_ylabel("T [℃]")
+    ax.plot(*convert_to_excel_data(hot_lines), color="#ff7f0e")
+    ax.plot(*convert_to_excel_data(cold_lines), color="#1f77b4")
+    fig.savefig("./tq_diagram_excel.png")
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/simple_with_external/tq_diagram.png b/examples/simple_with_external/tq_diagram.png
new file mode 100644
index 0000000..dc40375
Binary files /dev/null and b/examples/simple_with_external/tq_diagram.png differ
diff --git a/examples/simple_with_external/tq_diagram_excel.png b/examples/simple_with_external/tq_diagram_excel.png
new file mode 100644
index 0000000..cc0d6e3
Binary files /dev/null and b/examples/simple_with_external/tq_diagram_excel.png differ
diff --git a/examples/simple_with_external/tq_diagram_merged.png b/examples/simple_with_external/tq_diagram_merged.png
new file mode 100644
index 0000000..20594f2
Binary files /dev/null and b/examples/simple_with_external/tq_diagram_merged.png differ
diff --git a/examples/simple_with_external/tq_diagram_merged_with_vlines.png b/examples/simple_with_external/tq_diagram_merged_with_vlines.png
new file mode 100644
index 0000000..83a6226
Binary files /dev/null and b/examples/simple_with_external/tq_diagram_merged_with_vlines.png differ
diff --git a/examples/simple_with_external/tq_diagram_separated.png b/examples/simple_with_external/tq_diagram_separated.png
new file mode 100644
index 0000000..4af9c2d
Binary files /dev/null and b/examples/simple_with_external/tq_diagram_separated.png differ
diff --git a/examples/simple_with_external/tq_diagram_separated_with_vlines.png b/examples/simple_with_external/tq_diagram_separated_with_vlines.png
new file mode 100644
index 0000000..55b31e1
Binary files /dev/null and b/examples/simple_with_external/tq_diagram_separated_with_vlines.png differ
diff --git a/examples/simple_with_external/tq_diagram_split.png b/examples/simple_with_external/tq_diagram_split.png
new file mode 100644
index 0000000..af7045f
Binary files /dev/null and b/examples/simple_with_external/tq_diagram_split.png differ
diff --git a/examples/simple_with_external/tq_diagram_split_with_vlines.png b/examples/simple_with_external/tq_diagram_split_with_vlines.png
new file mode 100644
index 0000000..1273a04
Binary files /dev/null and b/examples/simple_with_external/tq_diagram_split_with_vlines.png differ
diff --git a/examples/simple_with_external/tq_diagram_with_vlines.png b/examples/simple_with_external/tq_diagram_with_vlines.png
new file mode 100644
index 0000000..2f056eb
Binary files /dev/null and b/examples/simple_with_external/tq_diagram_with_vlines.png differ
diff --git a/src/pyheatintegration/__init__.py b/src/pyheatintegration/__init__.py
index e4cee5d..8f173f6 100644
--- a/src/pyheatintegration/__init__.py
+++ b/src/pyheatintegration/__init__.py
@@ -1,6 +1,6 @@
 from .enums import StreamState, StreamType  # noqa
 from .grand_composite_curve import GrandCompositeCurve  # noqa
-from .line import extract_x, y_range  # noqa
+from .line import convert_to_excel_data, extract_x, y_range  # noqa
 from .pinch_analyzer import PinchAnalyzer  # noqa
 from .stream import Stream  # noqa
 from .tq_diagram import TQDiagram, get_possible_minimum_temp_diff_range  # noqa
diff --git a/src/pyheatintegration/base_range.py b/src/pyheatintegration/base_range.py
index cfd924f..d2e4ac4 100644
--- a/src/pyheatintegration/base_range.py
+++ b/src/pyheatintegration/base_range.py
@@ -1,6 +1,7 @@
 from __future__ import annotations
 
 from abc import ABC
+from typing import Optional, TypeVar
 
 
 def minmax(a: float, b: float) -> tuple[float, float]:
@@ -26,6 +27,18 @@ def __init__(self, start: float, finish: float):
         self.start, self.finish = minmax(start, finish)
         self.delta = self.finish - self.start
 
+    def __repr__(self) -> str:
+        return f"{self.__class__.__name__}({self.start}, {self.finish})"
+
+    def __str__(self) -> str:
+        return f"{self.start}->{self.finish}"
+
+    def __format__(self, format_spec: str) -> str:
+        return (
+            f"{self.start.__format__(format_spec)}->"
+            f"{self.finish.__format__(format_spec)}"
+        )
+
     def __eq__(self, other: object) -> bool:
         if not isinstance(other, BaseRange):
             return NotImplemented
@@ -59,18 +72,6 @@ def __ge__(self, other: object) -> bool:
     def __hash__(self) -> int:
         return hash((self.start, self.finish))
 
-    def __repr__(self) -> str:
-        return f"BaseRange({self.start}, {self.finish})"
-
-    def __str__(self) -> str:
-        return f"{self.start}->{self.finish}"
-
-    def __format__(self, format_spec: str) -> str:
-        return (
-            f"{self.start.__format__(format_spec)}->"
-            f"{self.finish.__format__(format_spec)}"
-        )
-
     def __call__(self) -> tuple[float, float]:
         return self.start, self.finish
 
@@ -81,15 +82,30 @@ def __contains__(self, value: float) -> bool:
             value (float): 範囲内にあるかを検証したい値。
 
         Returns:
-            bool: 範囲内にあるかどうか。
+            bool: 範囲内にある場合はTrue、ない場合はFalseを返します。
+
+        Examples:
+            >>> base_range = BaseRange(0, 10)
+            >>> 10 in base_range
+            True
+            >>> 11 in base_range
+            False
         """
         return self.start <= value <= self.finish
 
     def shift(self, delta: float) -> None:
-        """範囲をずらす。
+        """範囲をずらします。
 
         Args:
-            delta (float): ずらす値
+            delta (float): ずらす値。
+
+        Examples:
+            >>> base_range = BaseRange(0, 10)
+            >>> base_range.shift(10)
+            >>> base_range.start
+            10
+            >>> base_range.finish
+            20
         """
         self.start += delta
         self.finish += delta
@@ -101,6 +117,117 @@ def mergeable(self, other: BaseRange) -> bool:
             other (BaseRange): 結合対象。
 
         Returns:
-            bool: 結合可能かどうか。
+            結合可能であるかどうかを表すbool値。
+
+        Examples:
+            >>> base_range = BaseRange(0, 10)
+            >>> base_range.mergeable(BaseRange(10, 20))
+            True
+            >>> base_range.mergeable(BaseRange(5, 20))
+            False
         """
         return self.start == other.finish or self.finish == other.start
+
+
+T = TypeVar('T', bound=BaseRange)
+
+
+def merge(range_: T, other: T) -> T:
+    """範囲を結合します。
+
+    Args:
+        range_ (T): 結合元。
+        other (T): 結合対象。
+
+    Returns:
+        T: 結合後の範囲。
+
+    Raises:
+        TypeError: 結合元と結合対象の型が一致しない場合。
+        ValueError: 結合可能ではない範囲が渡された場合。
+    """
+    if type(range_) != type(other):
+        raise TypeError(
+            f'{repr(range_)}と{repr(other)}は、'
+            '型が異なるため、結合することができません。'
+        )
+
+    if not range_.mergeable(other):
+        raise ValueError(
+            f"{repr(range_)}と{repr(other)}は結合することができません。"
+            "終了値と結合対象の開始値が同じか、"
+            "開始値と結合対象の終了値が同じである必要があります。"
+        )
+
+    cls = type(range_)
+
+    if range_.start == other.finish:
+        return cls(other.start, range_.finish)
+    return cls(range_.start, other.finish)
+
+
+def is_continuous(
+    ranges_: list[T]
+) -> Optional[tuple[float, float]]:
+    """領域のリストが連続であるかを検証します。
+
+    Args:
+        ranges_ (list[T]): 領域のリスト。
+
+    Returns:
+        Optional[tuple[float, float]]:
+            領域が連続である場合はNoneを返し、連続でない場合は、連続でないと判断された箇所の
+            値をタプルで返します。
+    """
+    ranges = sorted(ranges_)
+    for i in range(len(ranges)):
+        if i != len(ranges) - 1:
+            if ranges[i].finish != ranges[i + 1].start:
+                return ranges[i].finish, ranges[i + 1].start
+    return None
+
+
+def flatten(ranges_: list[T]) -> list[float]:
+    """領域を平坦化したリストを返します。
+
+    Args:
+        heat_ranges_ (list[T]): 領域のリスト。
+
+    Returns:
+        list[float]: 平坦化されたリスト。
+
+    Raises:
+        ValueError: 領域が連続でない場合。
+    """
+    ranges = sorted(ranges_)
+    if (values := is_continuous(ranges)) is not None:
+        raise ValueError(
+            f'終了値と開始値が異なります。'
+            f'終了値: {values[0]:.3f} '
+            f'開始値: {values[1]:.3f}'
+        )
+
+    res: list[float] = []
+    for i in range(len(ranges)):
+        res.append(ranges[i].start)
+        if i == len(ranges) - 1:
+            res.append(ranges[i].finish)
+
+    return res
+
+
+def get_ranges(
+    values: list[float],
+    cls: type[T]
+) -> list[T]:
+    """領域のリストを返します。
+
+    Args:
+        values (list[float]): 領域の開始値と終了値を構成する値のリスト。
+        cls (type[T]): 領域のクラス。HeatRange/TemperatureRange
+
+    Returns:
+        list[T]: 領域のリスト。
+    """
+    values = sorted(values)
+    return [cls(values[i], values[i + 1]) for i in range(len(values) - 1)]
diff --git a/src/pyheatintegration/grand_composite_curve.py b/src/pyheatintegration/grand_composite_curve.py
index 3f4b593..8c4542d 100644
--- a/src/pyheatintegration/grand_composite_curve.py
+++ b/src/pyheatintegration/grand_composite_curve.py
@@ -2,50 +2,7 @@
 from copy import deepcopy
 
 from .stream import Stream, get_temperature_range_streams
-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()
-    })
+from .temperature_range import TemperatureRange, flatten_temperature_ranges
 
 
 class GrandCompositeCurve:
@@ -58,12 +15,10 @@ class GrandCompositeCurve:
     Attributes:
         extarnal_streams (list[Stream]): 外部流体。
         minimum_approach_temp_diff (float): 最小接近温度差[℃]。
-        maximum_pinch_point_temp (float): 最高温であるピンチポイントの温度[℃]。
-        maximum_pinch_point_index (int): 最も温度が高いピンチポイントのインデックス。
-        minimum_pinch_point_temp (float): 最低音であるピンチポイントの温度[℃]。
-        minimum_pinch_point_index (int): 最も温度が低いピンチポイントのインデックス。
         temps (list[float]): 温度のリスト[℃]。
         heats (list[float]): 熱量のリスト[W]。
+        pinch_points (list[tuple[int, float]]):
+            ピンチポイントとなるインデックスと温度のtupleのリスト。
     """
 
     def __init__(
@@ -88,110 +43,76 @@ def __init__(
         temp_ranges, temp_range_streams = get_temperature_range_streams([
             stream for stream in streams if stream.is_internal()
         ])
-        self.temps = get_temperatures(temp_ranges)
+        temp_ranges.sort()
+
+        self.temps = flatten_temperature_ranges(temp_ranges)
         self.heats = _get_heats(temp_ranges, temp_range_streams)
 
-        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]
-        self.minimum_pinch_point_index = pinch_point_info[3]
+        self.pinch_points = [
+            (i, self.temps[i]) for i, heat in enumerate(self.heats) if heat == 0
+        ]
+
+    def pinch_point_temp(self) -> float:
+        """ピンチポイントの温度を返します。
+
+        ピンチポイントが複数ある場合、最小値を返します。
 
-    def _get_pinch_point(self) -> tuple[float, int, float, int]:
-        """ピンチポイントとピンチポイントのインデックスを求めます。
+        Raises:
+            RuntimeError: ピンチポイントが求まっていないかピンチポイントが存在しない場合。
         """
-        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 not self.pinch_points:
+            raise RuntimeError('ピンチポイントが求まっていません。')
 
-        return (
-            pinch_points[-1],
-            pinch_point_indexes[-1],
-            pinch_points[0],
-            pinch_point_indexes[0]
-        )
+        return self.pinch_points[0][1]
 
     def solve_external_heat(self) -> dict[str, float]:
         """外部流体による熱交換量を求めます.
 
         Returns:
-            dict[int, float]:
-                流体のidごとの交換熱量。
+            dict[int, float]: 流体のidごとの交換熱量。
 
         Raises:
             RuntimeError: ピンチポイントを求める前に呼び出した場合。
         """
-        if not hasattr(self, 'maximum_pinch_point_temp') \
-           or not hasattr(self, 'minimum_pinch_point_temp'):
+        if not self.pinch_points:
             raise RuntimeError('ピンチポイントが求まっていません。')
 
-        external_cold_streams = [
-            stream for stream in self.external_streams if stream.is_cold()
-        ]
-        external_hot_streams = [
-            stream for stream in self.external_streams if stream.is_hot()
-        ]
+        hot_streams = self._update_external_streams_heat(
+            [stream for stream in self.external_streams if stream.is_hot()],
+            self.heats[self.pinch_points[-1][0]:],
+            self.temps[self.pinch_points[-1][0]:],
+            self.pinch_points[-1][1]
+        )
 
-        updated_external_streams = self._update_external_streams(
-            external_cold_streams,
-            external_hot_streams
+        cold_streams = self._update_external_streams_heat(
+            [stream for stream in self.external_streams if stream.is_cold()],
+            self.heats[self.pinch_points[0][0]::-1],
+            self.temps[self.pinch_points[0][0]::-1],
+            self.pinch_points[0][1]
         )
 
         return {
-            stream.id_: stream.heat()
-            for stream in updated_external_streams
+            stream.id_: stream.heat() for stream in hot_streams + cold_streams
         }
 
-    def _update_external_streams(
-            self,
-            external_cold_streams: list[Stream],
-            external_hot_streams: list[Stream]
-    ) -> list[Stream]:
-        """外部流体によって交換される熱量を求めます。
-
-        Args:
-            external_cold_streams (list[Stream]): 外部受熱流体。
-            external_hot_streams (list[Stream]): 外部与熱流体。
-
-        Returns:
-            tuple[list[Stream]]:
-                交換熱量の情報を追加した外部流体。
-        """
-        # ピンチポイントの上下に分割し、ピンチポイントに近い順に優先的に熱交換を行わせる。
-        heats_heating = self.heats[self.maximum_pinch_point_index:]
-        temps_heating = self.temps[self.maximum_pinch_point_index:]
-        heats_cooling = self.heats[self.minimum_pinch_point_index::-1]
-        temps_cooling = self.temps[self.minimum_pinch_point_index::-1]
-
-        return (
-            self._update_external_streams_heat(
-                external_hot_streams,
-                heats_heating,
-                temps_heating,
-                self.maximum_pinch_point_temp
-            )
-            + self._update_external_streams_heat(
-                external_cold_streams,
-                heats_cooling,
-                temps_cooling,
-                self.minimum_pinch_point_temp
-            )
-        )
-
     def _update_external_streams_heat(
         self,
         streams: list[Stream],
         heats: list[float],
         temps: list[float],
-        pinch_point: float
+        pinch_point_temp: float
     ) -> list[Stream]:
         """外部流体の交換熱量を決定します。
 
+        ピンチポイントが複数ある場合、pinch_point_tempは与熱流体と受熱流体で変わります。与
+        熱流体の場合は、ピンチポイントのうち最大温度の点、受熱流体の場合は、ピンチポイントのう
+        ち最小温度の点となります。
+
         Args:
             streams list[Stream]: 流体。
             heats list[float]: 熱量変化。
             temps list[float]: 温度変化。
+            pinch_point_temp (float): ピンチポイントの温度。
 
         Returns:
             list[Stream]: 情報を更新した流体のリスト。
@@ -199,28 +120,28 @@ def _update_external_streams_heat(
         heated = 0.0
         not_heated = heats[-1]
 
-        streams.sort(
-            key=lambda stream: abs(stream.output_temperature() - pinch_point)
-        )
+        streams.sort(key=lambda stream: stream.cost)
         for stream in streams:
             # すでに交換熱量が設定されている場合にはスキップする。
             if stream.heat() != 0:
                 continue
 
-            if (stream.is_hot() and stream.output_temperature() < pinch_point) \
-               or (stream.is_cold() and stream.output_temperature() > pinch_point):
+            target_temperature = stream.output_temperature()
+
+            # 与熱流体の場合は、出口温度がピンチポイントの温度より低い時、受熱流体の場合は、
+            # 出口温度がピンチポイントの温度より高い時、外部流体として用いることができない。
+            if (stream.is_hot() and target_temperature < pinch_point_temp) \
+               or (stream.is_cold() and target_temperature > pinch_point_temp):
                 continue
 
-            target_temperature = stream.output_temperature()
             for i in range(len(heats)):
                 if i == len(heats) - 1:
+                    # 与熱流体の場合は、流体温度がtemps[i]よりも大きい時、受熱流体の場合は、
+                    # 流体温度がtemps[i]より小さい時、外部流体として用いることができる。
                     if (stream.is_hot() and target_temperature >= temps[i]) \
                        or (stream.is_cold() and target_temperature <= temps[i]):
-                        heat = not_heated
-                        stream.update_heat(heat)
-                        heated += heat
-                        not_heated -= heat
-                    break
+                        stream.update_heat(not_heated)
+                        break
                 start_temp = temps[i]
                 finish_temp = temps[i + 1]
                 temp_range = TemperatureRange(start_temp, finish_temp)
@@ -241,3 +162,46 @@ def _update_external_streams_heat(
                     break
 
         return streams
+
+
+def _get_heats(
+    temp_ranges: list[TemperatureRange],
+    temp_range_streams: defaultdict[TemperatureRange, set[Stream]]
+) -> list[float]:
+    """温度変化領域ごとの熱量変化を求めます。
+
+    Args:
+        temp_ranges: list[TemperatureRange]: 温度領域のリスト。
+        temp_range_streams defaultdict[TemperatureRange, set[Stream]]:
+            温度領域ごとの流体のセット。
+
+    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[TemperatureRange, 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()
+    })
diff --git a/src/pyheatintegration/heat_exchanger.py b/src/pyheatintegration/heat_exchanger.py
index 4f0b5f7..f8375b2 100644
--- a/src/pyheatintegration/heat_exchanger.py
+++ b/src/pyheatintegration/heat_exchanger.py
@@ -1,7 +1,7 @@
 import math
 
 from .enums import StreamState
-from .heat_range import HeatRange
+from .heat_range import HeatRange, merge_heat_range
 from .plot_segment import PlotSegment
 
 OVERALL_HEAT_TRANSFER_COEFFICIENT = {
@@ -196,7 +196,7 @@ def merge_heat_exchangers(heat_exchanger: HeatExchanger, other: HeatExchanger) -
     Returns:
         HeatExchanger: 結合後の熱交換器。
     """
-    heat_range = heat_exchanger.heat_range.merge(other.heat_range)
+    heat_range = merge_heat_range(heat_exchanger.heat_range, 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)
 
diff --git a/src/pyheatintegration/heat_range.py b/src/pyheatintegration/heat_range.py
index 65277a8..ac03917 100644
--- a/src/pyheatintegration/heat_range.py
+++ b/src/pyheatintegration/heat_range.py
@@ -1,146 +1,52 @@
 from __future__ import annotations
 
-from typing import Optional
-
-from .base_range import BaseRange
+from .base_range import BaseRange, flatten, get_ranges, merge
 
 REL_TOL_DIGIT = 9
 
 
 class HeatRange(BaseRange):
-    def __init__(self, start: float, finish: float):
-        super().__init__(start, finish)
-
-    def __repr__(self):
-        return f"HeatRange({self.start}, {self.finish})"
+    """熱量範囲を表すクラス。"""
 
     def __contains__(self, temp: float, eps: float = 1e-6) -> bool:
         return self.start - eps <= temp <= self.finish + eps
 
-    def merge(self, other: HeatRange) -> HeatRange:
-        """範囲を結合します。
-
-        Args:
-            other (HeatRange): 結合対象。
-
-        Returns:
-            HeatRange: 結合した範囲。
-
-        Example:
-            >>> a = HeatRange(0, 10)
-            >>> b = HeatRange(10, 20)
-            >>> a.merge(b)
-            HeatRange(0, 20)
-            >>> a = HeatRange(10, 20)
-            >>> b = HeatRange(0, 10)
-            >>> a.merge(b)
-            HeatRange(0, 20)
-
-        Raises:
-            ValueError: 結合可能ではない範囲が渡された場合。
-        """
-        if not self.mergeable(other):
-            raise ValueError(
-                "終了値と結合対象の開始値が同じか、"
-                "開始値と結合対象の終了値が同じである必要があります。"
-            )
-
-        if self.start == other.finish:
-            return HeatRange(other.start, self.finish)
-        return HeatRange(self.start, other.finish)
-
 
 BaseRange.register(HeatRange)
 
 
-def get_heat_ranges(heats_: list[float]) -> list[HeatRange]:
-    """熱量領域のリストを返します。
+def merge_heat_range(range_: HeatRange, other: HeatRange) -> HeatRange:
+    return merge(range_, other)
 
-    Args:
-        heats_ (list[float]): 熱量のリスト。
 
-    Returns:
-        list[HeatRange]: 熱量領域のリスト。
-
-    Examples:
-        >>> get_heat_ranges([0, 10, 20])
-        [HeatRange(0, 10), HeatRange(10, 20)]
-    """
-    heats = sorted(heats_)
-    return [
-        HeatRange(heats[i], heats[i + 1])
-        for i in range(len(heats) - 1)
-    ]
+def get_heat_ranges(heats: list[float]) -> list[HeatRange]:
+    return get_ranges(heats, HeatRange)
 
 
-def is_continuous(heat_ranges_: list[HeatRange]) -> Optional[tuple[float, float]]:
-    """範囲が連続であるかを検証します。
+def flatten_heat_ranges(heat_ranges: list[HeatRange]) -> list[float]:
+    return flatten(heat_ranges)
 
-    Args:
-        heat_ranges_ (list[HeatRange]): 熱量領域のリスト。
-
-    Returns:
-        Optional[tuple[float, float]]: 領域が連続であるか。
 
-    Examples:
-        >>> is_continuous([HeatRange(0, 10), HeatRange(10, 20)])
-        None
-        >>> is_continuous([HeatRange(0, 10), HeatRange(15, 20)])
-        (10, 15)
-        >>> is_continuous([HeatRange(0, 15), HeatRange(10, 20)])
-        (15, 10)
-    """
-    heat_ranges = sorted(heat_ranges_)
-    for i in range(len(heat_ranges)):
-        if i != len(heat_ranges) - 1:
-            if heat_ranges[i].finish != heat_ranges[i + 1].start:
-                return heat_ranges[i].finish, heat_ranges[i + 1].start
-    return None
-
-
-def get_heats(heat_ranges_: list[HeatRange]) -> list[float]:
-    """熱量のリストを返します。
-
-    Args:
-        heat_ranges_ (list[HeatRange]): 熱量領域のリスト。
-
-    Returns:
-        list[float]: 熱量のリスト。
-
-    Examples:
-        >>> get_heats([HeatRange(0, 10), HeatRange(10, 20)])
-        [0, 10, 20]
-    """
-    heat_ranges = sorted(heat_ranges_)
-    if (values := is_continuous(heat_ranges)) is not None:
-        raise ValueError(
-            f'終了値と開始値が異なります。'
-            f'finish: {values[0]}, '
-            f'start: {values[1]}'
-        )
-
-    res: list[float] = []
-    for i in range(len(heat_ranges)):
-        res.append(heat_ranges[i].start)
-        if i == len(heat_ranges) - 1:
-            res.append(heat_ranges[i].finish)
-
-    return res
-
-
-def get_detailed_heat_ranges(
+def get_merged_heat_ranges(
     heat_ranges_list: list[list[HeatRange]]
 ) -> list[HeatRange]:
-    """与熱流体と受熱流体を合わせた熱量領域を返します。
+    """複数の熱量領域のリストを合わせた熱量領域を返します。
 
     Args:
-        plot_segments_list (list[list[PlotSegment]]): 複合線のリスト。
+        plot_segments_list (list[list[PlotSegment]]):
+            熱量領域のリストのリスト。
 
     Returns:
-        list[HeatRange]: 熱量領域のリスト。
+        list[HeatRange]: 結合後の熱量領域のリスト。
+
+    Examples:
+        >>> l1 = [HeatRange(0, 10), HeatRange(10, 30)]
+        >>> l2 = [HeatRange(5, 15), HeatRange(15, 40)]
+        >>> get_merged_heat_ranges([l1, l2])
+        [HeatRange(0, 5), HeatRange(5, 10), HeatRange(10, 15), HeatRange(15, 30), HeatRange(30, 40)]
     """
     heats: set[float] = set()
     for heat_ranges in heat_ranges_list:
-        heats |= set(get_heats(heat_ranges))
+        heats |= set(flatten_heat_ranges(heat_ranges))
 
     return get_heat_ranges(sorted(list(heats)))
diff --git a/src/pyheatintegration/line.py b/src/pyheatintegration/line.py
index c1f2b02..831143a 100644
--- a/src/pyheatintegration/line.py
+++ b/src/pyheatintegration/line.py
@@ -18,13 +18,8 @@ def y_range(lines: list[Line]) -> tuple[float, float]:
             最小値と最大値。
 
     Examples:
-        >>> y_range([
-                ((0, 0), (1, 1)),
-                ((1, 1), (2, 2)),
-                ((2, 2), (3, 5)),
-                ((3, 3), (5, 8))
-            ])
-        >>> (0, 8)
+        >>> 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),
@@ -42,12 +37,39 @@ def extract_x(lines: list[Line]) -> list[float]:
         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]
+        >>> 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))))
+
+
+def convert_to_excel_data(lines_: list[Line]) -> tuple[list[float], list[float]]:
+    """直線のリストをx座標のリストとy座標のリストに変換します。
+
+    Args:
+        lines_ (list[Line]): 直線のリスト
+
+    Returns:
+        typle[list[float], list[float]]:
+            x座標のリストとy座標のリスト
+
+    Examples:
+        >>> convert_to_excel_data([((0, 0), (1, 2)), ((1, 2), (3, 3)), ((3, 3), (4, 5))])
+        ([0, 1, 3, 4], [0, 2, 3, 5])
+        >>> convert_to_excel_data([((0, 0), (1, 2)), ((1, 0), (2, 2))])
+        ([0, 1, 1, 2], [0, 2, 0, 2])
+    """
+    lines = sorted(lines_, key=lambda line: line[0][0])
+    x_list = []
+    y_list = []
+
+    for i in range(len(lines)):
+        x_list.append(lines[i][0][0])
+        y_list.append(lines[i][0][1])
+
+        if i != len(lines) - 1 and lines[i][1] == lines[i + 1][0]:
+            continue
+        x_list.append(lines[i][1][0])
+        y_list.append(lines[i][1][1])
+
+    return x_list, y_list
diff --git a/src/pyheatintegration/pinch_analyzer.py b/src/pyheatintegration/pinch_analyzer.py
index 8fa0c3f..c8c59aa 100644
--- a/src/pyheatintegration/pinch_analyzer.py
+++ b/src/pyheatintegration/pinch_analyzer.py
@@ -3,7 +3,7 @@
 
 from .grand_composite_curve import GrandCompositeCurve
 from .heat_exchanger import HeatExchanger
-from .heat_range import HeatRange, get_detailed_heat_ranges
+from .heat_range import HeatRange, get_merged_heat_ranges
 from .line import Line
 from .plot_segment import PlotSegment, get_plot_segments
 from .stream import Stream, is_valid_streams
@@ -61,7 +61,7 @@ def __init__(
         self,
         streams_: list[Stream],
         minimum_approach_temp_diff: float,
-        ignore_maximum: bool = False
+        force_validation: bool = False
     ):
         streams = deepcopy(streams_)
 
@@ -77,9 +77,19 @@ def __init__(
         if not is_valid_streams(streams):
             raise ValueError('与熱流体および受熱流体は少なくとも1つは指定する必要があります。')
 
+        # 外部流体が存在する場合は検証を行う。
+        ignore_validation = True
+        for stream in streams:
+            if stream.is_external():
+                ignore_validation = False
+                break
+
+        if force_validation:
+            ignore_validation = False
+
         self.minimum_approach_temp_diff_range = get_possible_minimum_temp_diff_range(
             streams,
-            ignore_maximum
+            ignore_validation
         )
 
         if minimum_approach_temp_diff not in self.minimum_approach_temp_diff_range:
@@ -101,14 +111,14 @@ def __init__(
                     stream.update_heat(heat)
 
         self.streams = [stream for stream in streams if stream.heat() != 0]
-        self.pinch_point_temp = self.gcc.maximum_pinch_point_temp
+        self.pinch_point_temp = self.gcc.pinch_point_temp()
         self.tq = TQDiagram(
             self.streams,
             minimum_approach_temp_diff,
             self.pinch_point_temp
         )
 
-        all_heat_ranges = get_detailed_heat_ranges(
+        all_heat_ranges = get_merged_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]
@@ -154,12 +164,12 @@ def create_tq_separated(self) -> tuple[list[Line], list[Line]]:
             self.tq.cold_lines_separated
         )
 
-    def create_tq_splitted(self) -> tuple[list[Line], list[Line]]:
+    def create_tq_split(self) -> tuple[list[Line], list[Line]]:
         """流体ごとに分割し、最小接近温度差の条件を満たしたtq線図をを描くために必要な与熱複合線および受熱複合線を返します。
         """
         return (
-            self.tq.hot_lines_splitted,
-            self.tq.cold_lines_splitted
+            self.tq.hot_lines_split,
+            self.tq.cold_lines_split
         )
 
     def create_tq_merged(self) -> tuple[list[Line], list[Line]]:
diff --git a/src/pyheatintegration/plot_segment.py b/src/pyheatintegration/plot_segment.py
index bb48ce2..039b030 100644
--- a/src/pyheatintegration/plot_segment.py
+++ b/src/pyheatintegration/plot_segment.py
@@ -4,9 +4,9 @@
 from collections.abc import Iterable
 from typing import Optional
 
+from .base_range import is_continuous as is_continuous_ranges
 from .enums import StreamState
 from .heat_range import HeatRange
-from .heat_range import is_continuous as is_continuous_heat_ranges
 from .line import Line
 from .temperature_range import TemperatureRange
 
@@ -353,6 +353,6 @@ def is_continuous(
     Returns:
         Optional[tuple[float, float]]: 領域が連続であるか。
     """
-    return is_continuous_heat_ranges(
+    return is_continuous_ranges(
         sorted([plot_segment.heat_range for plot_segment in plot_segments])
     )
diff --git a/src/pyheatintegration/segment.py b/src/pyheatintegration/segment.py
index e18bf6d..d7b0a1e 100644
--- a/src/pyheatintegration/segment.py
+++ b/src/pyheatintegration/segment.py
@@ -2,7 +2,8 @@
 from copy import deepcopy
 from typing import Optional, cast
 
-from .heat_range import REL_TOL_DIGIT, HeatRange, get_heat_ranges, get_heats
+from .heat_range import (REL_TOL_DIGIT, HeatRange, flatten_heat_ranges,
+                         get_heat_ranges)
 from .line import Line
 from .plot_segment import PlotSegment, get_plot_segments, temp_diff
 from .stream import Stream
@@ -21,8 +22,8 @@ class Segment:
     cold_plot_segments: list[PlotSegment]
     hot_plot_segments_separated: list[PlotSegment]
     cold_plot_segments_separated: list[PlotSegment]
-    hot_plot_segments_splitted: list[PlotSegment]
-    cold_plot_segments_splitted: list[PlotSegment]
+    hot_plot_segments_split: list[PlotSegment]
+    cold_plot_segments_split: list[PlotSegment]
 
     def __init__(
         self,
@@ -64,11 +65,11 @@ def __init__(
                 self.cold_temperature_range
             )
 
-        hot_heats = get_heats([
+        hot_heats = flatten_heat_ranges([
             plot_segment.heat_range
             for plot_segment in self.hot_plot_segments_separated_streams
         ])
-        cold_heats = get_heats([
+        cold_heats = flatten_heat_ranges([
             plot_segment.heat_range
             for plot_segment in self.cold_plot_segments_separated_streams
         ])
@@ -219,8 +220,8 @@ def split(self, minimum_approach_temp_diff: float) -> None:
                             cold_plot_segment.reboiler_or_reactor
                         )
 
-        self.hot_plot_segments_splitted = sorted(list(hot_heat_range_plot_segment.values()))
-        self.cold_plot_segments_splitted = sorted(list(cold_heat_range_plot_segment.values()))
+        self.hot_plot_segments_split = sorted(list(hot_heat_range_plot_segment.values()))
+        self.cold_plot_segments_split = sorted(list(cold_heat_range_plot_segment.values()))
 
 
 class Segments(UserList[Segment]):
@@ -243,11 +244,11 @@ def hot_lines_separated(self) -> list[Line]:
     def cold_lines_separated(self) -> list[Line]:
         return [line for segment in self.data for line in self.get_lines(segment.cold_plot_segments_separated)]
 
-    def hot_lines_splitted(self) -> list[Line]:
-        return [line for segment in self.data for line in self.get_lines(segment.hot_plot_segments_splitted)]
+    def hot_lines_split(self) -> list[Line]:
+        return [line for segment in self.data for line in self.get_lines(segment.hot_plot_segments_split)]
 
-    def cold_lines_splitted(self) -> list[Line]:
-        return [line for segment in self.data for line in self.get_lines(segment.cold_plot_segments_splitted)]
+    def cold_lines_split(self) -> list[Line]:
+        return [line for segment in self.data for line in self.get_lines(segment.cold_plot_segments_split)]
 
     def split(self, minimum_approach_temp_diff: float) -> None:
         for segment in self.data:
diff --git a/src/pyheatintegration/stream.py b/src/pyheatintegration/stream.py
index 66b3c55..9e3841e 100644
--- a/src/pyheatintegration/stream.py
+++ b/src/pyheatintegration/stream.py
@@ -284,7 +284,7 @@ def contain_temperatures(self, temperatures: Iterable[float]) -> bool:
         全ての温度をとる場合のみTrueを返します。
 
         Args:
-            temperatures (list[float]): 検証したい温度のリスト。
+            temperatures (Iterable[float]): 検証したい温度のリスト。
 
         Returns:
             bool: 与えられた複数の温度をとるかどうか。
diff --git a/src/pyheatintegration/temperature_range.py b/src/pyheatintegration/temperature_range.py
index 8d571de..303cebb 100644
--- a/src/pyheatintegration/temperature_range.py
+++ b/src/pyheatintegration/temperature_range.py
@@ -1,130 +1,28 @@
 from __future__ import annotations
 
-from typing import Optional
-
-from .base_range import BaseRange
+from .base_range import BaseRange, flatten, get_ranges, merge
 
 
 class TemperatureRange(BaseRange):
-    def __init__(self, start: float, finish: float):
-        super().__init__(start, finish)
-
-    def __repr__(self) -> str:
-        return f"TemperatureRange({self.start}, {self.finish})"
-
-    def merge(self, other: TemperatureRange) -> TemperatureRange:
-        """範囲を結合します。
-
-        Args:
-            other (TemperatureRange): 結合対象。
-
-        Returns:
-            TemperatureRange: 結合した範囲。
-
-        Example:
-            >>> a = TemperatureRange(0, 10)
-            >>> b = TemperatureRange(10, 20)
-            >>> a.merge(b)
-            TemperatureRange(0, 20)
-            >>> a = TemperatureRange(10, 20)
-            >>> b = TemperatureRange(0, 10)
-            >>> a.merge(b)
-            TemperatureRange(0, 20)
-
-        Raises:
-            ValueError: 結合可能ではない範囲が渡された場合。
-        """
-        if not self.mergeable(other):
-            raise ValueError(
-                "終了値と結合対象の開始値が同じか、"
-                "開始値と結合対象の終了値が同じである必要があります。"
-            )
-
-        if self.start == other.finish:
-            return TemperatureRange(other.start, self.finish)
-        return TemperatureRange(self.start, other.finish)
+    """温度範囲を表すクラス。"""
 
 
 BaseRange.register(TemperatureRange)
 
 
-def is_continuous(temp_ranges_: list[TemperatureRange]) -> Optional[tuple[float, float]]:
-    """範囲が連続であるかを検証します。
-
-    Args:
-        temp_ranges_ (list[HeatRange]): 温度領域のリスト。
-
-    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)):
-        if i != len(temp_ranges) - 1:
-            if temp_ranges[i].finish != temp_ranges[i + 1].start:
-                return temp_ranges[i].finish, temp_ranges[i + 1].start
-    return None
-
-
-def get_temperatures(temp_ranges_: list[TemperatureRange]) -> list[float]:
-    """温度のリストを返します。
+def merge_temperature_range(
+    range_: TemperatureRange,
+    other: TemperatureRange
+) -> TemperatureRange:
+    return merge(range_, other)
 
-    Args:
-        heat_ranges_ (list[TemperatureRange]): 温度領域のリスト。
-
-    Returns:
-        list[float]: 温度のリスト。
-
-    Raises:
-        ValueError: 温度領域が連続でない場合。
-
-    Examples:
-        >>> 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'終了値: {values[0]:.3f} '
-            f'開始値: {values[1]:.3f}'
-        )
 
-    res: list[float] = []
-    for i in range(len(temp_ranges)):
-        res.append(temp_ranges[i].start)
-        if i == len(temp_ranges) - 1:
-            res.append(temp_ranges[i].finish)
+def get_temperature_ranges(temperatures: list[float]) -> list[TemperatureRange]:
+    return get_ranges(temperatures, TemperatureRange)
 
-    return res
 
-
-def get_temperature_ranges(temperatures_: list[float]) -> list[TemperatureRange]:
-    """温度領域のリストを返します。
-
-    Args:
-        temperatures_ (list[float]): 温度のリスト。
-
-    Returns:
-        list[HeatRange]: 温度領域のリスト。
-
-    Examples:
-        >>> get_temperature_ranges([0, 10, 20])
-        [TemperatureRange(0, 10), TemperatureRange(10, 20)]
-    """
-    temperatures = sorted(temperatures_)
-    return [
-        TemperatureRange(temperatures[i], temperatures[i + 1])
-        for i in range(len(temperatures) - 1)
-    ]
+def flatten_temperature_ranges(temperature_ranges: list[TemperatureRange]) -> list[float]:
+    return flatten(temperature_ranges)
 
 
 def get_temperature_transition(
diff --git a/src/pyheatintegration/tq_diagram.py b/src/pyheatintegration/tq_diagram.py
index 9f169ad..33e169e 100644
--- a/src/pyheatintegration/tq_diagram.py
+++ b/src/pyheatintegration/tq_diagram.py
@@ -3,13 +3,79 @@
 from copy import copy, deepcopy
 from typing import Optional
 
-from .heat_range import (REL_TOL_DIGIT, HeatRange, get_detailed_heat_ranges,
-                         get_heat_ranges, get_heats)
+from .heat_range import (REL_TOL_DIGIT, HeatRange, flatten_heat_ranges,
+                         get_heat_ranges, get_merged_heat_ranges, merge_heat_range)
 from .plot_segment import PlotSegment, get_plot_segments, is_continuous
 from .segment import Segment, Segments
 from .stream import Stream, get_temperature_range_heats
 from .temperature_range import (TemperatureRange, accumulate_heats,
-                                get_temperatures)
+                                flatten_temperature_ranges, merge_temperature_range)
+
+
+class TQDiagram:
+    """TQ線図を描くために必要な情報を得るためのクラス。
+
+    Args:
+        streams (list[Stream]): 熱交換を行いたい流体。
+        minimum_approach_temp_diff (float): 最小接近温度差[℃]。
+        pinch_point_temp (float): ピンチポイントの温度[℃]。
+
+    Attributes:
+        hot_lines (list[Line]): TQ線図の与熱複合線(プロット用の直線のリスト)。
+        cold_linse (list[Line]): TQ線図の受熱複合線(プロット用の直線のリスト)。
+        hot_lines_separated (list[Line]): 流体ごとに分割した与熱複合線(プロット用の直線のリスト)。
+        cold_lines_separated (list[Line]): 流体ごとに分割した受熱複合線(プロット用の直線のリスト)。
+        hot_lines_split (list[Line]): 流体ごとに分割し、最小接近温度差を満たした与熱複合線(プロット用の直線のリスト)。
+        cold_lines_split (list[Line]): 流体ごとに分割し、最小接近温度差を満たした受熱複合線(プロット用の直線のリスト)。
+        hot_lines_merged (list[Line]): 熱交換器を結合した与熱複合線(プロット用の直線のリスト)。
+        cold_lines_merged (list[Line]): 熱交換器を結合した受熱複合線(プロット用の直線のリスト)。
+        hcc_merged (list[PlotSegment]): 熱交換器を結合した与熱複合線。
+        ccc_merged (list[PlotSegment]): 熱交換器を結合した受熱複合線。
+    """
+
+    def __init__(
+        self,
+        streams: list[Stream],
+        minimum_approach_temp_diff: float,
+        pinch_point_temp: float
+    ):
+        hot_streams = sorted(
+            [stream for stream in streams if stream.is_hot()],
+            key=lambda stream: stream.output_temperature()
+        )
+
+        cold_streams = sorted(
+            [stream for stream in streams if stream.is_cold()],
+            key=lambda stream: stream.input_temperature()
+        )
+
+        # 与熱複合線と受熱複合線を得た後に、最小接近温度差を満たすようにずらす。
+        hcc, ccc = _shift_curve(
+            _create_composite_curve(hot_streams),
+            _create_composite_curve(cold_streams),
+            minimum_approach_temp_diff,
+            pinch_point_temp
+        )
+
+        # セグメントを得る。
+        segments = _get_segments(hcc, ccc, hot_streams, cold_streams)
+
+        # 最小接近温度差を満たすように分割を行う
+        segments.split(minimum_approach_temp_diff)
+
+        self.hot_lines = segments.hot_lines()
+        self.cold_lines = segments.cold_lines()
+
+        self.hot_lines_separated = segments.hot_lines_separated()
+        self.cold_lines_separated = segments.cold_lines_separated()
+
+        self.hot_lines_split = segments.hot_lines_split()
+        self.cold_lines_split = segments.cold_lines_split()
+
+        # 結合可能なセグメント同士を結合する。
+        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]
 
 
 def _create_composite_curve(streams: list[Stream]) -> list[PlotSegment]:
@@ -22,7 +88,7 @@ def _create_composite_curve(streams: list[Stream]) -> list[PlotSegment]:
         list[PlotSegment]: 複合線。
     """
     t_ranges, t_range_heats = get_temperature_range_heats(streams)
-    t = get_temperatures(t_ranges)
+    t = flatten_temperature_ranges(t_ranges)
     h = accumulate_heats(t_ranges, t_range_heats)
     return [
         PlotSegment(h[i], h[i + 1], t[i], t[i + 1]) for i in range(len(h) - 1)
@@ -137,7 +203,7 @@ def _get_segments(
     Returns:
         Segments: セグメントのリスト。
     """
-    heat_ranges = get_detailed_heat_ranges(
+    heat_ranges = get_merged_heat_ranges(
         [
             [plot_segment.heat_range for plot_segment in hot_plot_segments],
             [plot_segment.heat_range for plot_segment in cold_plot_segments]
@@ -225,13 +291,13 @@ def _merge_segments(
     """
     segments = deepcopy(segments_)
     hot_plot_segments = sorted([
-        plot_segment for segment in segments for plot_segment in segment.hot_plot_segments_splitted
+        plot_segment for segment in segments for plot_segment in segment.hot_plot_segments_split
     ])
     cold_plot_segments = sorted([
-        plot_segment for segment in segments for plot_segment in segment.cold_plot_segments_splitted
+        plot_segment for segment in segments for plot_segment in segment.cold_plot_segments_split
     ])
     heat_ranges = get_heat_ranges(
-        get_heats(
+        flatten_heat_ranges(
             sorted([
                 heat_range for segment in segments for heat_range in segment.heat_ranges
             ])
@@ -261,9 +327,9 @@ def _merge_segments(
 
         if hot_plot_segment.mergiable(next_hot_plot_segment) \
            and cold_plot_segment.mergiable(next_cold_plot_segment):
-            merged_heat_range = hot_plot_segment.heat_range.merge(next_hot_plot_segment.heat_range)
-            merged_hot_temp_range = hot_plot_segment.temperature_range.merge(next_hot_plot_segment.temperature_range)
-            merged_cold_temp_range = cold_plot_segment.temperature_range.merge(next_cold_plot_segment.temperature_range)
+            merged_heat_range = merge_heat_range(hot_plot_segment.heat_range, next_hot_plot_segment.heat_range)
+            merged_hot_temp_range = merge_temperature_range(hot_plot_segment.temperature_range, next_hot_plot_segment.temperature_range)
+            merged_cold_temp_range = merge_temperature_range(cold_plot_segment.temperature_range, next_cold_plot_segment.temperature_range)
             merged_hot_plot_segments.append(PlotSegment(
                 *merged_heat_range(),
                 *merged_hot_temp_range(),
@@ -291,13 +357,13 @@ def _merge_segments(
 
 def get_possible_minimum_temp_diff_range(
     streams: list[Stream],
-    ignore_maximum: bool = False
+    ignore_validation: bool = False
 ) -> TemperatureRange:
     """設定可能な最小接近温度差を返します。
 
     Args:
         streams (list[Stream]): 流体のリスト。
-        ignore_maximum (bool): 最大値のチェックを無視するか。
+        ignore_validation (bool): 最大値のチェックを無視するか。
 
     Returns:
         float: 可能な最小接近温度差[℃]。
@@ -318,7 +384,7 @@ def get_possible_minimum_temp_diff_range(
         stream.input_temperature() for stream in streams if stream.is_cold()
     )
 
-    if ignore_maximum:
+    if ignore_validation:
         maximum_minimum_approch_temp_diff = hot_maximum_temp - cold_minimum_temp
     else:
         if hot_minimum_temp - cold_minimum_temp < 0:
@@ -348,7 +414,7 @@ def get_possible_minimum_temp_diff_range(
         stream for stream in streams if stream.is_internal() and stream.is_cold()
     ])
 
-    initial_heat_ranges = get_detailed_heat_ranges(
+    initial_heat_ranges = get_merged_heat_ranges(
         [
             [plot_segment.heat_range for plot_segment in initial_hcc],
             [plot_segment.heat_range for plot_segment in initial_ccc]
@@ -369,7 +435,7 @@ def get_possible_minimum_temp_diff_range(
             plot_segment.shift_heat(gap)
 
     # ずらした複合線の与熱流体と受熱流体を合わせた熱量領域を得る。
-    heat_ranges = get_detailed_heat_ranges(
+    heat_ranges = get_merged_heat_ranges(
         [
             [plot_segment.heat_range for plot_segment in hcc],
             [plot_segment.heat_range for plot_segment in ccc]
@@ -409,69 +475,3 @@ def get_possible_minimum_temp_diff_range(
         maximum_minimum_approch_temp_diff,
         minimum_minimum_approch_temp_diff
     )
-
-
-class TQDiagram:
-    """TQ線図を描くために必要な情報を得るためのクラス。
-
-    Args:
-        streams (list[Stream]): 熱交換を行いたい流体。
-        minimum_approach_temp_diff (float): 最小接近温度差[℃]。
-        pinch_point_temp (float): ピンチポイントの温度[℃]。
-
-    Attributes:
-        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__(
-        self,
-        streams: list[Stream],
-        minimum_approach_temp_diff: float,
-        pinch_point_temp: float
-    ):
-        hot_streams = sorted(
-            [stream for stream in streams if stream.is_hot()],
-            key=lambda stream: stream.output_temperature()
-        )
-
-        cold_streams = sorted(
-            [stream for stream in streams if stream.is_cold()],
-            key=lambda stream: stream.input_temperature()
-        )
-
-        # 与熱複合線と受熱複合線を得た後に、最小接近温度差を満たすようにずらす。
-        hcc, ccc = _shift_curve(
-            _create_composite_curve(hot_streams),
-            _create_composite_curve(cold_streams),
-            minimum_approach_temp_diff,
-            pinch_point_temp
-        )
-
-        # セグメントを得る。
-        segments = _get_segments(hcc, ccc, hot_streams, cold_streams)
-
-        # 最小接近温度差を満たすように分割を行う
-        segments.split(minimum_approach_temp_diff)
-
-        self.hot_lines = segments.hot_lines()
-        self.cold_lines = segments.cold_lines()
-
-        self.hot_lines_separated = segments.hot_lines_separated()
-        self.cold_lines_separated = segments.cold_lines_separated()
-
-        self.hot_lines_splitted = segments.hot_lines_splitted()
-        self.cold_lines_splitted = segments.cold_lines_splitted()
-
-        # 結合可能なセグメント同士を結合する。
-        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]


Test patch:
diff --git a/tests/base_range_test.py b/tests/base_range_test.py
index 09aae0c..4e9e0f2 100644
--- a/tests/base_range_test.py
+++ b/tests/base_range_test.py
@@ -1,6 +1,7 @@
 import unittest
 
-from src.pyheatintegration.base_range import BaseRange, minmax
+from src.pyheatintegration.base_range import (BaseRange, flatten, get_ranges,
+                                              is_continuous, merge, minmax)
 
 
 class TestMinMax(unittest.TestCase):
@@ -26,6 +27,7 @@ def test_base_range(self):
         self.assertEqual(base_range.__str__(), "0.0->100.0")
         self.assertEqual(f"{base_range:.2f}", "0.00->100.00")
 
+        self.assertEqual(base_range, BaseRange(0.0, 100.0))
         self.assertTrue(base_range != other)
         self.assertTrue(base_range < other)
         self.assertTrue(base_range <= other)
@@ -47,5 +49,64 @@ def test_base_range(self):
         self.assertEqual(base_range.finish, 110.0)
 
 
+class TestMerger(unittest.TestCase):
+
+    def test_should_success(self):
+        self.assertEqual(
+            merge(BaseRange(0, 10), BaseRange(10, 20)),
+            BaseRange(0, 20),
+        )
+
+    def test_should_raise_value_error(self):
+        with self.assertRaises(ValueError):
+            merge(BaseRange(0, 10), BaseRange(5, 20))
+
+
+class TestIsContinuous(unittest.TestCase):
+
+    def test_should_return_none(self):
+        self.assertIsNone(is_continuous([
+            BaseRange(0, 10),
+            BaseRange(10, 20),
+        ]))
+
+    def test_should_return_tuple(self):
+        self.assertEqual(
+            is_continuous([
+                BaseRange(0, 10),
+                BaseRange(5, 20),
+            ]),
+            (10, 5)
+        )
+
+
+class TestFlatten(unittest.TestCase):
+
+    def test_should_success(self):
+        self.assertEqual(
+            flatten([
+                BaseRange(0, 10),
+                BaseRange(10, 20),
+            ]),
+            [0, 10, 20]
+        )
+
+    def test_should_raise_value_error(self):
+        with self.assertRaises(ValueError):
+            flatten([
+                BaseRange(0, 10),
+                BaseRange(5, 20),
+            ])
+
+
+class TestGetRanges(unittest.TestCase):
+
+    def test_should_success(self):
+        self.assertEqual(
+            get_ranges([0, 10, 20], BaseRange),
+            [BaseRange(0, 10), BaseRange(10, 20)]
+        )
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/grand_composite_curve_test.py b/tests/grand_composite_curve_test.py
index c88ac38..aea038b 100644
--- a/tests/grand_composite_curve_test.py
+++ b/tests/grand_composite_curve_test.py
@@ -1,30 +1,73 @@
 import unittest
+from collections import defaultdict
 
-from src.pyheatintegration.enums import StreamType
-from src.pyheatintegration.grand_composite_curve import GrandCompositeCurve
-from src.pyheatintegration.stream import Stream
+from src.pyheatintegration.grand_composite_curve import GrandCompositeCurve, _get_lacking_heats, _get_heats
+from src.pyheatintegration.stream import Stream, get_temperature_range_streams
+from src.pyheatintegration.temperature_range import TemperatureRange
 
 
 class GrandCompositeCurveTest(unittest.TestCase):
 
-    def setUp(self):
+    def test_gcc(self):
         streams = [
-            Stream(40.0, 90.0, 150.0, StreamType(1)),
-            Stream(80.0, 110.0, 180.0, StreamType(1)),
-            Stream(125.0, 80.0, 180.0, StreamType(2)),
-            Stream(100.0, 60.0, 160.0, StreamType(2)),
+            Stream(40.0, 90.0, 150.0),
+            Stream(80.0, 110.0, 180.0),
+            Stream(125.0, 80.0, 180.0),
+            Stream(100.0, 60.0, 160.0),
         ]
         minimum_approach_temp_diff = 10.0
         self.gcc = GrandCompositeCurve(streams, minimum_approach_temp_diff)
 
-    def test_lackgin_heats(self):
-        pass
-
-    def test_gcc(self):
         self.assertEqual(self.gcc.temps, [50.0, 60.0, 80.0, 90.0, 100.0, 120.0, 125.0])
         self.assertEqual(self.gcc.heats, [40.0, 70.0, 50.0, 0.0, 10.0, 50.0, 30.0])
-        self.assertEqual(self.gcc.minimum_pinch_point_temp, 90.0)
-        self.assertEqual(self.gcc.maximum_pinch_point_temp, 90.0)
+        self.assertEqual(self.gcc.pinch_points, [(3, 90.0)])
+
+
+class TestGetLackingHeats(unittest.TestCase):
+
+    def test_should_success(self):
+        streams = [
+            Stream(50.0, 100.0, 150.0),
+            Stream(90.0, 120.0, 180.0),
+            Stream(125.0, 80.0, 180.0),
+            Stream(100.0, 60.0, 160.0),
+        ]
+
+        _, temp_range_streams = get_temperature_range_streams(
+            streams
+        )
+
+        self.assertEqual(
+            _get_lacking_heats(temp_range_streams),
+            defaultdict(int, {
+                TemperatureRange(50, 60): -30,
+                TemperatureRange(60, 80): 20,
+                TemperatureRange(80, 90): 50,
+                TemperatureRange(90, 100): -10,
+                TemperatureRange(100, 120): -40,
+                TemperatureRange(120, 125): 20,
+            })
+        )
+
+
+class TestGetHeats(unittest.TestCase):
+
+    def test_should_success(self):
+        streams = [
+            Stream(50.0, 100.0, 150.0),
+            Stream(90.0, 120.0, 180.0),
+            Stream(125.0, 80.0, 180.0),
+            Stream(100.0, 60.0, 160.0),
+        ]
+
+        temp_range, temp_range_streams = get_temperature_range_streams(
+            streams
+        )
+
+        self.assertEqual(
+            [40, 70, 50, 0, 10, 50, 30],
+            _get_heats(temp_range, temp_range_streams)
+        )
 
 
 if __name__ == '__main__':
diff --git a/tests/heat_range_test.py b/tests/heat_range_test.py
index c365a6e..a2bdfa4 100644
--- a/tests/heat_range_test.py
+++ b/tests/heat_range_test.py
@@ -1,100 +1,34 @@
 import unittest
 
-from src.pyheatintegration.heat_range import (HeatRange,
-                                              get_detailed_heat_ranges,
-                                              get_heat_ranges, get_heats,
-                                              is_continuous)
+from src.pyheatintegration.heat_range import HeatRange, get_merged_heat_ranges
 
 
-class TestHeatRange(unittest.TestCase):
+class TestHeatRnage(unittest.TestCase):
 
-    def test_merge(self):
+    def test_heat_range(self):
         self.assertEqual(
-            HeatRange(0, 20),
-            HeatRange(0, 10).merge(HeatRange(10, 20))
+            HeatRange(0, 10).__repr__(),
+            "HeatRange(0, 10)"
         )
 
-        self.assertEqual(
-            HeatRange(0, 20),
-            HeatRange(10, 20).merge(HeatRange(0, 10))
-        )
-
-
-class TestGetHeatRanges(unittest.TestCase):
-
-    def test_get_heat_range(self):
-        self.assertEqual(
-            [
-                HeatRange(0, 10),
-                HeatRange(10, 20)
-            ],
-            get_heat_ranges([0, 10, 20])
-        )
-
-
-class TestGetHeats(unittest.TestCase):
-
-    def test_get_heats(self):
-        self.assertEqual(
-            [0, 10, 20],
-            get_heats([
-                HeatRange(0, 10),
-                HeatRange(10, 20)
-            ])
-        )
-
-    def test_get_heats_error(self):
-        with self.assertRaises(ValueError):
-            get_heats([
-                HeatRange(0, 10),
-                HeatRange(20, 30)
-            ])
-
 
-class TestIsContinuous(unittest.TestCase):
+class TestGetMergedHeatRanges(unittest.TestCase):
 
-    def test_is_continuous(self):
-        self.assertIsNone(is_continuous([
-            HeatRange(0, 10),
-            HeatRange(10, 20),
-        ]))
-
-    def test_is_continuous_erorr(self):
+    def test_should_success(self):
         self.assertEqual(
-            is_continuous([
-                HeatRange(0, 10),
-                HeatRange(5, 20),
+            get_merged_heat_ranges([
+                [HeatRange(0, 10), HeatRange(10, 30)],
+                [HeatRange(5, 15), HeatRange(15, 40)]
             ]),
-            (10, 5)
+            [HeatRange(0, 5), HeatRange(5, 10), HeatRange(10, 15), HeatRange(15, 30), HeatRange(30, 40)]
         )
 
-
-class TestGetDetailedHeatRanges(unittest.TestCase):
-
-    def test_get_detailed_heat_ranges(self):
-        self.assertEqual(get_detailed_heat_ranges([[
-            HeatRange(0, 15),
-            HeatRange(15, 30),
-        ], [
-            HeatRange(10, 20),
-            HeatRange(20, 31),
-        ]]), [
-            HeatRange(0, 10),
-            HeatRange(10, 15),
-            HeatRange(15, 20),
-            HeatRange(20, 30),
-            HeatRange(30, 31),
-        ])
-
-    def test_get_detailed_heat_ranges_error(self):
+    def test_should_raise_value_error(self):
         with self.assertRaises(ValueError):
-            get_detailed_heat_ranges([[
-                HeatRange(0, 15),
-                HeatRange(10, 30),
-            ], [
-                HeatRange(10, 20),
-                HeatRange(20, 31),
-            ]])
+            get_merged_heat_ranges([
+                [HeatRange(0, 10), HeatRange(5, 30)],
+                [HeatRange(5, 15), HeatRange(15, 40)]
+            ])
 
 
 if __name__ == '__main__':
diff --git a/tests/line_test.py b/tests/line_test.py
index 1ae3e2f..7d3de01 100644
--- a/tests/line_test.py
+++ b/tests/line_test.py
@@ -1,6 +1,6 @@
 import unittest
 
-from src.pyheatintegration.line import extract_x, y_range
+from src.pyheatintegration.line import convert_to_excel_data, extract_x, y_range
 
 
 class TestLine(unittest.TestCase):
@@ -28,5 +28,18 @@ def test_extract_x(self):
         )
 
 
+class TestConvertToExcelData(unittest.TestCase):
+
+    def test_should_success(self):
+        self.assertEqual(
+            ([0, 1, 3, 4], [0, 2, 3, 5]),
+            convert_to_excel_data([((0, 0), (1, 2)), ((1, 2), (3, 3)), ((3, 3), (4, 5))])
+        )
+        self.assertEqual(
+            ([0, 1, 1, 2], [0, 2, 0, 2]),
+            convert_to_excel_data([((0, 0), (1, 2)), ((1, 0), (2, 2))])
+        )
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/segment_test.py b/tests/segment_test.py
index 318f9ef..04e6d47 100644
--- a/tests/segment_test.py
+++ b/tests/segment_test.py
@@ -81,7 +81,7 @@ def test_split(self):
         segment.split(200)
 
         self.assertEqual(
-            segment.hot_plot_segments_splitted,
+            segment.hot_plot_segments_split,
             [
                 PlotSegment(0, 70, 300, 400),
                 PlotSegment(70, 90, 300, 400),
@@ -90,7 +90,7 @@ def test_split(self):
         )
 
         self.assertEqual(
-            segment.cold_plot_segments_splitted,
+            segment.cold_plot_segments_split,
             [
                 PlotSegment(0, 70, 100, 200),
                 PlotSegment(70, 90, 100, 200),
diff --git a/tests/temperature_range_test.py b/tests/temperature_range_test.py
index aad1d9d..8e0fd58 100644
--- a/tests/temperature_range_test.py
+++ b/tests/temperature_range_test.py
@@ -1,38 +1,21 @@
 import unittest
 
 from src.pyheatintegration.temperature_range import (
-    TemperatureRange, accumulate_heats, get_temperature_ranges,
-    get_temperature_transition, is_continuous)
+    TemperatureRange, accumulate_heats, get_temperature_transition)
 
 
-class TestGetTemperatureRange(unittest.TestCase):
+class TestTemperatureRange(unittest.TestCase):
 
-    def test_sorted(self):
+    def test_temperature_range(self):
         self.assertEqual(
-            get_temperature_ranges([
-                0.0, 10.0, 20.0
-            ]),
-            [
-                TemperatureRange(0.0, 10.0),
-                TemperatureRange(10.0, 20.0)
-            ]
-        )
-
-    def test_unsorted(self):
-        self.assertEqual(
-            get_temperature_ranges([
-                0.0, 20.0, 10.0
-            ]),
-            [
-                TemperatureRange(0.0, 10.0),
-                TemperatureRange(10.0, 20.0)
-            ]
+            TemperatureRange(0, 10).__repr__(),
+            "TemperatureRange(0, 10)"
         )
 
 
 class TestGetTemperatureTransition(unittest.TestCase):
 
-    def test_get_temperature_transition(self):
+    def test_should_success(self):
         self.assertEqual(
             [0, 10, 20, 30, 30, 40, 50, 70, 70],
             sorted(get_temperature_transition([
@@ -45,27 +28,9 @@ def test_get_temperature_transition(self):
         )
 
 
-class TestIsContinuous(unittest.TestCase):
-
-    def test_is_continuous(self):
-        self.assertIsNone(is_continuous([
-            TemperatureRange(0, 10),
-            TemperatureRange(10, 20),
-        ]))
-
-    def test_is_continuous_erorr(self):
-        self.assertEqual(
-            is_continuous([
-                TemperatureRange(0, 10),
-                TemperatureRange(5, 20),
-            ]),
-            (10, 5)
-        )
-
-
 class TestAccumulateHeats(unittest.TestCase):
 
-    def test_accumulate_heats(self):
+    def test_should_success(self):
         self.assertEqual(accumulate_heats([
             TemperatureRange(0, 10),
             TemperatureRange(10, 20),
@@ -78,7 +43,7 @@ def test_accumulate_heats(self):
             0.0, 10.0, 30.0, 60.0
         ])
 
-    def test_accumulate_heats_error(self):
+    def test_should_raise_value_error(self):
         with self.assertRaises(ValueError):
             accumulate_heats([
                 TemperatureRange(0, 10),