合金のat%とwt%を換算するPythonプログラム

python

金属材料の特性は合金組成に強く依存するため、金属に関わる仕事や研究に従事していると、合金組成を頻繁に目にします。合金組成の表現には、モル分率(at.%), \(x_i\), と重量分率(wt.%, mass%), \(w_i\)の2種類があります(ここで、\(i\)はFeやAlなどの成分に対応)。この2種類の内、原理的に特性と対応するのはモル分率であるため、学術的な研究ではモル分率がよく用いられています。一方で、実際のモノづくりでは取り回しのし易さから重量分率が用いられており、商用合金の多くは重量分率で組成が表現されています。このように、材料学の世界ではモル分率と重量分率という2つの組成表現が混在しており、先行研究などを整理するためにはこれらを相互に換算する必要があります。原子量を\(M_i\)とすると、\(x_i\), \(w_i\)の間には以下の関係式が成立します。

$$ x_i=\frac{1}{\sum_j \frac{w_j}{M_j}}\frac{w_i}{M_i} $$

$$ w_i=\frac{1}{\sum_j x_j M_j}x_i M_i $$

この二つの式を用いれば、\(x_i\)と\(w_i\)を相互に換算することができます。この式はExcelなどの表計算ソフトで容易に計算できますが、元素種や元素数は合金ごとに異なるため、合金に合わせて計算式の調整などが必要となり、組成の換算に少し手間がかかります。

本ページでは、合金の組成を汎用的に換算するためのPythonプログラムを紹介します。

計算コードとデータベース

計算には以下の二つのファイルを用います。”Alloy_Composition.py”がメインのソースコードで、”Atomic_Weight.dat”が原子量のデータを入力しておくインプットファイルです。”Alloy_Composition.py”から”Atomic_Weight.dat”を呼び出しますので、二つのファイルは同じディレクトリに格納してください(不足する元素があれば適宜追加してください。区切り文字はタブ(\t)です)。”Alloy_Composition.py”の”Alloy_Composition”クラスを用いることで組成計算を簡単に行うことができます。以降ではこのコードを用いた組成計算について解説します。

Alloy_Composition.py



import os
import re
import pandas as pd
import numpy as np

class Alloy_Composition():
    # 変数の定義、原子量の読込
    def __init__(self,flag_per=True):
        self.alloy_wt=""
        self.alloy_at=""
        self.element=[]
        self.comp_wt=np.array([])
        self.comp_at=np.array([])
        self.atm_weight=np.array([])

        self.flag_per=flag_per
        self._load_atomic_weight()

    # 原子量のデータベースを読み込み
    def _load_atomic_weight(self):
        p=os.path.abspath(os.path.dirname(__file__))
        p=os.path.join(p,"Atomic_Weight.dat")
        df=pd.read_csv(p,delimiter="\t")
        ele = df["element"].values
        atm_weight = df["weight"].values

        # データベース内の原子量をdict型の変数に格納
        self.atm_weight_DB = { e:v for e,v in zip(ele,atm_weight)}

    # 元素種の設定
    def set_element(self,element:list):
        self.element=element
        self.atm_weight=np.ones(len(self.element))
        self.comp_at=np.zeros_like(self.atm_weight)
        self.comp_wt=np.zeros_like(self.atm_weight)

        # 原子量データの設定
        for i,e in enumerate(self.element):
            if e in self.atm_weight_DB.keys():
                self.atm_weight[i]=self.atm_weight_DB[e]
            else:
                print(f"[Error] The element [{e}] is not included in atomic weight database")

    # wt.%で組成を入力
    def set_comp_wt(self,comp:dict):
        self._set_comp(comp,True)
        self._calc_comp_at()
        self.get_alloy_str()

    # at.%で組成を入力
    def set_comp_at(self,comp:dict):
        self._set_comp(comp,False)
        self._calc_comp_wt()
        self.get_alloy_str()

    # 組成入力のメソッド
    def _set_comp(self, comp: dict, flag_wt):
        comp_temp = self.comp_wt
        for i, e in enumerate(self.element):
            if e in comp.keys():
                comp_temp[i] = comp[e]

        # 合金組成の合計を100 or 1に調整
        if self.flag_per:
            comp_temp[0] = 100 - np.sum(comp_temp[1:])
        else:
            comp_temp[0] = 1 - np.sum(comp_temp[1:])

        if self.element[0] in comp.keys():
            print(f"The composition of [{self.element[0]}] was overwritten")

        # 負の値が組成に含まれるときに警告
        if np.any(comp_temp < 0):
            print(f"[Attention] The alloy composition has negative value: {comp_temp}")

        if flag_wt:
            self.comp_wt = comp_temp
        else:
            self.comp_at = comp_temp

    # wt.%を計算
    def _calc_comp_wt(self):
        comp_temp=self.comp_at*self.atm_weight
        self.comp_wt=comp_temp/np.sum(comp_temp)
        if self.flag_per:
            self.comp_wt=self.comp_wt*100

    # at.%を計算
    def _calc_comp_at(self):
        comp_temp=self.comp_wt/self.atm_weight
        self.comp_at=comp_temp/np.sum(comp_temp)
        if self.flag_per:
            self.comp_at=self.comp_at*100

    # Ti-6Al-4Vのような合金表現を得る
    def get_alloy_str(self,l=10):
        self.alloy_at=self.element[0]
        self.alloy_wt=self.element[0]

        for i in range(1,len(self.element)):
            c=self.comp_at[i]
            w=self.comp_wt[i]
            e=self.element[i]

            if not(self.flag_per):
                c=c*100
                w=w*100

            c=round(c,l)
            w=round(w,l)

            if c!=0.0 or w!=0.0:
                self.alloy_at=self.alloy_at+f"-{c}{e}"
                self.alloy_wt=self.alloy_wt+f"-{w}{e}"

    # Ti-6Al-4Vなどの合金表現から合金組成を読み取る
    def set_alloy_by_str(self,alloy_str:str,flag_wt=True):
        alloy_str=alloy_str.replace(" ","")
        txt=alloy_str.replace(".","")
        txt = re.sub(r"[0-9]{0,}", "", txt)
        ele=txt.split("-")
        txts=alloy_str.split("-")

        comp={}
        for e, c in zip(ele[1:],txts[1:]):
            c=c.replace(e,"")
            if c=="":
                if self.flag_per:
                    c=1.0
                else:
                    c=0.01
            c=float(c)
            if not(self.flag_per):
                c=c*0.01
            comp[e]=c

        self.set_element(ele)
        if flag_wt:
            self.set_comp_wt(comp)
        else:
            self.set_comp_at(comp)

    # 組成を表示する
    def show_alloy_comp(self):
        print(f"Alloy (at.%): {self.alloy_at}")
        print(f"Alloy (wt.%): {self.alloy_wt}")
        print(f"Element: {self.element}")
        print(f"c: {self.comp_at}")
        print(f"w: {self.comp_wt}")
        print()

if __name__=="__main__":
    a=Alloy_Composition()
    a.set_element(["Ti","Al","V"])
    a.set_comp_wt({"Al":6,"V":4})
    a.get_alloy_str(1)
    a.show_alloy_comp()

    b=Alloy_Composition()
    b.set_alloy_by_str("Ti-6Al-4V")
    b.show_alloy_comp()

    a=Alloy_Composition()
    a.set_element(["Ti","Mo"])
    a.set_comp_at({"Mo":10})
    a.get_alloy_str(1)
    a.show_alloy_comp()

    b=Alloy_Composition()
    b.set_alloy_by_str("Ti-3.5Al-2V",flag_wt=False)
    b.show_alloy_comp()

Atomic_Weight.dat

element	no	weight
Mg	12	24.31
Al	13	26.98
Ca	20	40.08
Ti	22	47.88
V	23	50.94
Cr	24	52.00
Mn	25	54.94
Fe	26	55.85
Ni	28	58.69
Zn	30	65.39
Y	39	88.91
Nb	41	92.91
Mo	42	95.94
Ce	58	140.1
Nd	60	144.2
Gd	64	157.3
Bi	83	209.0
O	8	16.00
N	7	14.01

組成計算

組成の計算は以下のよう行うことができます。

# インスタンス化 
a=Alloy_Composition()
# 合金元素種の設定
a.set_element(["Ti","Al","V"])
# 組成の設定
a.set_comp_wt({"Al":6,"V":4})
# 合金表現の取得
a.get_alloy_str(1)
# 組成の表示
a.show_alloy_comp()

まず、インスタンスを生成します。デフォルトでは、組成は100分率(%)で表現するように設定しています。分率(総和が1)で表現する場合は、引数に"flag_per=False"を与えてください。

a=Alloy_Composition()
# 組成を分率(総和が1)で計算する場合 
a=Alloy_Composition(flag_per=False)

次に、合金を構成する元素と組成を入力します。合金元素の指定には"set_element()"メソッドを用います。元素はリストで与えてください。合金組成の入力には"set_comp_wt()", "set_comp_at()"を用います。前者は重量分率、後者はモル分率で組成を与える場合に用います。組成情報は元素記号をkey、組成をvalueとしたdict型で引数に渡します。keyに入力されていない元素については、直前の値が保持されます。"set_element()"メソッドを実行するとすべての成分組成が0に初期化されます。また、"set_element()"の引数として渡したリスト中の一番最初の元素組成については、合金組成の総和が100あるいは1になるように調整され入力れます。下記の例では"Ti"の組成は90に自動的に調整されます。"set_comp_wt({"Ti":80})"などとして組成を入力しても自動的に修正されます。

a.set_element(["Ti","Al","V"])
# 組成の設定(重量分率の場合)
a.set_comp_wt({"Al":6,"V":4})
# 組成の設定(モル分率の場合)
a.set_comp_at({"Al":6,"V":4})

# リストなどを用いて組成を入力する方法(for loopの内包表記)
e=["Al","V"]
c=[6,4]
a.set_comp_wt({k:v for k,v zip(e,c)})

"set_comp_wt()", "set_comp_at()"を実行すると自動的にモル分率、重量分率も計算されます。合金は組成に基づいてTi-6Al-4V (wt.%)のように表現されることがよくあります。このように、組成と元素をまとめて表現すると合金組成の確認が容易になります。このような表現の取得は"get_alloy_str()"メソッドで行うことができます。引数では組成を丸める桁数を指定できます(1であれば小数第一位までで組成が表現されます)。

# 合金表現の取得
a.get_alloy_str()
# 合金表現の取得(小数第一位で丸める)
a.get_alloy_str(1)

計算結果(組成情報)を出力して表示します。

a.show_alloy_comp()
# Alloy (at.%): Ti-10.2Al-3.6V
# Alloy (wt.%): Ti-6.0Al-4.0V
# Element: ['Ti', 'Al', 'V']
# c: [86.20061758 10.19838293  3.60099949]
# w: [90.  6.  4.]

Ti-6Al-4Vのような合金表現から組成を読み取る

材料系の世界では合金組成をTi-6Al-4V (wt.%)のように一つの文字列で表現することがよくあります。上記の組成計算では、合金元素を指定し組成を入力しましたが、このような合金組成の文字列表現から元素種と組成を読み取れるようにすると便利です。"Alloy_Composition"クラスでは"set_alloy_by_str()"でTi-6Al-4Vのような表現から組成情報を取得することができます。デフォルトでは重量分率で読み取る設定になっています。モル分率で読み取る場合は"flag_wt=False"を引数に与えてください。

b=Alloy_Composition()
# wt.%で組成情報を取得
b.set_alloy_by_str("Ti-6Al-4V")
# at.%で組成情報を取得
b.set_alloy_by_str("Ti-6Al-4V",flag_wt=False)
b.show_alloy_comp()

タイトルとURLをコピーしました