0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

物質収支(Mass Balance)の一般化

0
Posted at

1. README(理論背景・数式の意味)

物質収支(Mass Balance)の一般化

希釈や混合の問題を解く際、最も確実な「正攻法」は、系(システム)に含まれる溶質の質量保存に注目することです。これを「物質収支」と呼びます。

支配方程式 (Governing Equations)

2つの溶液を混ぜる場合、以下の等式が成立します。

  1. 全質量の保存 (Total Mass Balance):

  2. 溶質の質量保存 (Solute Mass Balance):

ここで:

  • : 加える溶液1の質量 [g]
  • : 溶液1の濃度(比率)
  • : 用意した溶液2(または水)の質量 [g]
  • : 溶液2の濃度(比率、水なら0)
  • : 目標濃度

一般解の導出

上記2式から混合後の質量 を消去し、求める質量 について解くと、以下の「てんびんの公式」が導かれます。

ご提示の式 は、(水)の場合のこの一般式を簡略化したものです。

体積換算と工学的解釈

求まった質量 を、比体積(1gあたりの体積) を用いて体積 に変換します。

このモデルを用いることで、濃度誤差が最終的な体積決定に与える影響(感度)を解析することが可能になります。

# ==========================================================
# 2. Setup Code (import)
# ==========================================================
import numpy as np
import matplotlib.pyplot as plt
import ipywidgets as widgets
from IPython.display import display, clear_output

# Set plot style / プロットスタイルの設定
plt.style.use('seaborn-v0_8-muted')

# ==========================================================
# 3. TheoreticalModel
# ==========================================================
class TheoreticalModel:
    """
    Handles HCl dilution math using solute mass conservation.
    溶質の質量保存則に基づき塩酸希釈の計算を行うクラス。
    """
    def __init__(self):
        self.m_a = 0.0  # Mass of added acid [g] / 加える塩酸の質量
        self.V_a = 0.0  # Volume of added acid [cm^3] / 加える塩酸の体積

    def validate(self, C_a, C_t, m_w):
        """Validates physical constraints. / 物理的制約の検証"""
        if not (0 < C_t < C_a):
            return False, "Error: Target conc (C_t) must be between 0 and Source conc (C_a)."
        if m_w <= 0:
            return False, "Error: Water mass (m_w) must be positive."
        return True, ""

    def solve(self, m_w, C_a, C_t, v_s):
        """
        Step-by-step solution derivation.
        計算過程をステップバイステップで導出。
        """
        # Step 1: Calculate Mass m_a = (m_w * C_t) / (C_a - C_t)
        numerator = m_w * C_t
        denominator = C_a - C_t
        self.m_a = numerator / denominator
        
        # Step 2: Convert to Volume V_a = m_a * v_s
        self.V_a = self.m_a * v_s

        # Transparency Output / 計算の透明性表示
        print("--- Calculation Process (計算過程の透明性) ---")
        print(f"1. Solute Balance Equation (質量収支式): m_a * C_a = (m_a + m_w) * C_t")
        print(f"2. Rearranged for m_a (m_aの導出式): m_a = (m_w * C_t) / (C_a - C_t)")
        print(f"   Substitution (数値代入): m_a = ({m_w} * {C_t}) / ({C_a} - {C_t})")
        print(f"   => m_a = {numerator} / {denominator}")
        print(f"   => m_a = {self.m_a:.4f} g")
        print(f"3. Volume Conversion (体積換算): V_a = m_a * v_spec")
        print(f"   Substitution (数値代入): V_a = {self.m_a:.4f} * {v_s}")
        print(f"   => V_a = {self.V_a:.4f} cm^3")
        print("----------------------------------------------")
        
        return self.m_a, self.V_a

# ==========================================================
# 4. Visualizer
# ==========================================================
class Visualizer:
    """
    Plots the Area Diagram for concentration balance.
    濃度のつり合いを示す面積図を描画するクラス。
    """
    def __init__(self):
        self.prev_state = None # To store ghost lines / ゴースト線用の前回データ保存

    def plot_area(self, m_w, C_a, C_t, m_a):
        fig, ax = plt.subplots(figsize=(10, 6))
        
        # Current result: Area rectangles / 今回の結果:長方形
        # Rectangle 1: Water Base (m_w)
        ax.add_patch(plt.Rectangle((0, 0), m_w, 0, color='blue', alpha=0.1, label='Water Base (m_w)'))
        # Rectangle 2: Added Acid (m_a)
        ax.add_patch(plt.Rectangle((m_w, 0), m_a, C_a, color='red', alpha=0.4, label='Added Acid (m_a)'))
        
        # Target Concentration line (C_t)
        ax.axhline(y=C_t, color='green', linestyle='-', linewidth=2, label=f'Target Line C_t ({C_t}%)')

        # Ghost line for previous result / ゴースト線(前回の結果)
        if self.prev_state is not None:
            p_mw, p_ma, p_ca, p_ct = self.prev_state
            ax.plot([0, p_mw, p_mw, p_mw + p_ma, p_mw + p_ma], [0, 0, p_ca, p_ca, 0], 
                    color='gray', linestyle='--', alpha=0.4, label='Previous Result (Ghost)')
            ax.axhline(y=p_ct, color='gray', linestyle=':', alpha=0.4)

        # Plot Settings (English Only) / グラフ設定
        ax.set_title("Concentration Area Diagram: Solute Mass Conservation")
        ax.set_xlabel("Cumulative Solution Mass [g]")
        ax.set_ylabel("Concentration [%]")
        ax.set_xlim(0, (m_w + m_a) * 1.2)
        ax.set_ylim(0, C_a * 1.2)
        ax.grid(True, linestyle=':', alpha=0.6)
        ax.legend(loc='upper right')
        
        plt.show()
        
        # Update state for sensitivity analysis
        self.prev_state = (m_w, m_a, C_a, C_t)

# ==========================================================
# 5. UIBuilder
# ==========================================================
class UIBuilder:
    def __init__(self, model, viz):
        self.model = model
        self.viz = viz
        self.output = widgets.Output()

        # [Model Parameters: Solution Constants]
        self.C_a_box = widgets.BoundedFloatText(value=35.0, min=0.1, max=100.0, description='C_a [%]:')
        self.v_s_box = widgets.BoundedFloatText(value=0.85, min=0.01, max=5.0, description='v_spec [cm^3/g]:')

        # [Observed / Input Data: Experimental Setup]
        self.m_w_box = widgets.BoundedFloatText(value=125.0, min=0.1, max=10000.0, description='m_w [g]:')
        self.C_t_box = widgets.BoundedFloatText(value=10.0, min=0.1, max=99.0, description='C_t [%]:')

        # [Run Button]
        self.run_btn = widgets.Button(description='Run Simulation (実行)', button_style='primary', icon='play')
        self.run_btn.on_click(self._on_run)

        # Grouped Layout
        self.ui_layout = widgets.VBox([
            widgets.HTML("<b>[Model Parameters: Physical Properties]</b>"),
            widgets.HBox([self.C_a_box, self.v_s_box]),
            widgets.HTML("<b>[Input Data: Desired Setup]</b>"),
            widgets.HBox([self.m_w_box, self.C_t_box]),
            widgets.HTML("<br>"),
            self.run_btn
        ])

    def _on_run(self, b):
        with self.output:
            clear_output(wait=True)
            # 1. Validate / 検証
            is_valid, msg = self.model.validate(self.C_a_box.value, self.C_t_box.value, self.m_w_box.value)
            if not is_valid:
                print(f"{msg}")
                return
            
            # 2. Calculate / 計算
            ma, _ = self.model.solve(
                self.m_w_box.value, 
                self.C_a_box.value, 
                self.C_t_box.value, 
                self.v_s_box.value
            )
            
            # 3. Visualize / 描画
            self.viz.plot_area(self.m_w_box.value, self.C_a_box.value, self.C_t_box.value, ma)

    def display(self):
        display(self.ui_layout, self.output)

# ==========================================================
# 6. Initialization
# ==========================================================
model_inst = TheoreticalModel()
viz_inst = Visualizer()
ui_inst = UIBuilder(model_inst, viz_inst)
ui_inst.display()

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?