def step(dis, matano, diffsys, Xlim=[], name=''): """ Create a step profile at the Matano Plane. Parameters ---------- dis : numpy.array Distance information. matano : float Matano plane location (micron). diffsys : DiffSystem The diffusion system information for initial setups before simulation. Xlim : list (float), optional Indicates the left and right concentration limits for step profile. Default value = [diffsys.Xr[0][0], diffsys.Xr[-1][1]]. For a decreasing step, Xlim must be provided. name : str, optional Name the output DiffProfile Returns ------- profile : DiffProfile Step profile can be used for input of pydiffusion.simulation.mphSim Examples -------- Create a step profile on a meshed grid (ascending): >>> dis = mesh() >>> init_profile = step(dis, 200, dsys, Xlim=[0, 1]) Create a reversed step profile (descending): >>> init_profile = step(dis, 200, dsys, Xlim=[1, 0]) """ Np = diffsys.Np if Xlim == []: XL, XR = diffsys.Xr[0][0], diffsys.Xr[-1][1] else: [XL, XR] = Xlim X = np.ones(len(dis)) * XL X[dis > matano] = XR if name == '': name = diffsys.name + '_step' if Np == 1: return DiffProfile(dis, X, name=name) else: If = [0] * (Np - 1) Ip = np.where(X == XR)[0][0] d = dis[Ip] - dis[Ip - 1] for i in range(Np - 1): If[i] = dis[Ip - 1] + d / (Np + 1) * (i + 1) return DiffProfile(dis, X, If, name=name)
def test_profileplot(): """ profileplot should return an axes object when a DiffProfile is passed """ dis = np.linspace(0, 100, 101) X = np.linspace(0, 1, 101) If = [30.5, 60.5] profile = DiffProfile(dis=dis, X=X, If=If) ax = profileplot(profile, label='test') assert isinstance(ax, Axes)
def sphSim(profile, diffsys, time, output=True, name=''): """ Single-Phase Diffusion Simulation Parameters ---------- profile : DiffProfile Initial diffusion profile before simulation. diffsys : DiffSystem Diffusion coefficients. time : float time in seconds. output : boolean, optional Print simulation progress, default = True. name : str, optional Name the output DiffProfile. Returns ------- profile : DiffProfile Simulated diffusion profile """ if name == '': name = diffsys.name + '_%.1fh' % (time / 3600) dis, Xs = profile.dis.copy() / 1e6, profile.X.copy() fD = diffsys.Dfunc d = dis[1:] - dis[:-1] dj = 0.5 * (d[1:] + d[:-1]) t, m = 0.0, 0 while t < time: Xm = 0.5 * (Xs[1:] + Xs[:-1]) DCs = np.exp(splev(Xm, fD[0])) dt = min(d**2 / DCs / 2) dt = time - t if t + dt > time else dt * 0.95 t += dt m += 1 Jf = -DCs * (Xs[1:] - Xs[:-1]) / d Xs[1:-1] = Xs[1:-1] - dt * (Jf[1:] - Jf[:-1]) / dj Xs[0] -= Jf[0] / d[0] * dt * 2 Xs[-1] += Jf[-1] / d[-1] * dt * 2 if output and np.mod(m, 3e4) == 0: print('%.3f/%.3f hrs simulated' % (t / 3600, time / 3600)) if output: print('Simulation Complete') return DiffProfile(dis * 1e6, Xs, name=name)
def test_profile(): """ DiffProfile constructor """ dis = np.linspace(100, 0, 101) X = np.linspace(0, 1, 101) If = [30.5, 60.5] profile = DiffProfile(dis=dis, X=X, If=If) assert len(profile.dis) == len(profile.X) assert isinstance(profile.name, str) assert np.all(profile.dis[1:] >= profile.dis[:-1]) assert len(profile.If) == len(If) + 2 assert len(profile.Ip) == len(If) + 2 assert profile.Ip[-1] == len(profile.dis) assert profile.If[0] < profile.dis[profile.Ip[0]] assert profile.If[-1] > profile.dis[profile.Ip[-1] - 1] for i in range(1, len(If)): assert profile.If[i] > profile.dis[profile.Ip[i] - 1] assert profile.If[i] < profile.dis[profile.Ip[i]]
def read_csv(filename, Xlim=None, name=''): """ Read diffusion data from csv file. Parameters ---------- filename : str csv file path. Xlim : list (length of 2), optional A list to determine the two ends solubilities. name : str, optional Name the output DiffProfile and DiffSystem Returns ------- profile : DiffProfile output DiffProfile object. diffsys : DiffSystem output DiffSystem object. Examples -------- Both profile and diffusivity data: >>> profile, dsys = read_csv('data.csv') Profile data only: >>> profile, _ = read_csv('data.csv') """ data = pd.read_csv(filename) if 'X' not in data.columns: raise ValueError('No column X in csv file') X = np.array(data['X']) # Auto rename if name == '': if '/' in filename: r = filename.rfind('/') elif '\\' in filename: r = filename.rfind('\\') else: r = -1 if filename.endswith('.csv'): name = filename[r + 1:-4] else: name = filename[r + 1:] if 'dis' in data.columns: dis = np.array(data['dis']) If = [] XIf = [] for i in range(len(dis) - 1): if dis[i] == dis[i + 1]: If += [dis[i]] XIf += [X[i], X[i + 1]] profile = DiffProfile(dis, X, If, name=name) if Xlim is None: XIf = np.array([X[0]] + XIf + [X[-1]]) else: XIf = np.array([Xlim[0]] + XIf + [Xlim[-1]]) Xr = XIf.reshape((len(XIf) // 2, 2)) if 'DC' in data.columns: DC = np.array(data['DC']) else: X = DC = None if Xr is not None: diffsys = DiffSystem(Xr, X=X, DC=DC, name=name) return profile, diffsys
import pandas as pd from pydiffusion.core import DiffProfile from pydiffusion.io import read_csv, save_csv from pydiffusion.plot import profileplot from pydiffusion.smooth import datasmooth # Read raw data data = pd.read_csv('examples/data/NiMo_exp.csv') dis, X = data['dis'], data['X'] NiMo_exp = DiffProfile(dis=dis, X=X, name='NiMo_exp') # Another way to read profile data, .csv must be created by pydiffusion.io.save_csv NiMo_exp, _ = read_csv('examples/data/NiMo_exp.csv') ax = profileplot(NiMo_exp, c='b', marker='o', ls='none', fillstyle='none') # Data smoothing NiMo_sm = datasmooth(NiMo_exp, [311.5, 340.5], n=500) # Plot result profileplot(NiMo_sm, ax, c='r') # Save result save_csv('examples/data/NiMo_sm.csv', profile=NiMo_sm)
def datasmooth(profile, interface=[], n=2000, name=''): """ Data smooth of diffusion profile. The functions use moving radius method on each phase. Please do not close any plot window during the process. datasmooth() is the first step of the forward simulation analysis (FSA). Parameters ---------- profile: DiffProfile Diffusion profile data. interface : list of float Np-1 locations of interfaces for a Np-phase system n : int Interpolation number of the smoothed profile name : string, optional Name the output DiffProfile Returns ------- profile : pydiffusion.diffusion.DiffProfile Examples -------- Smooth the experimental profile (exp) with known interfaces [200, 300]: >>> ds = datasmooth(exp, [200, 300]) """ dis, X = profile.dis, profile.X if len(dis) != len(X): raise ValueError('Nonequal length of distance and composition data') try: If = np.array(interface) except TypeError: print('interface must be array_like') if If.ndim != 1: raise ValueError('interface must be 1d-array') fig = plt.figure() ax = fig.add_subplot(111) If = [dis[0]-0.5] + list(If) + [dis[-1]+0.5] Np = len(If)-1 Ip = [0]*(Np+1) disn, Xn = dis.copy(), X.copy() # Same distance values for i in range(len(disn)-1): for j in range(i+1, len(disn)): if disn[i] == disn[j]: disn[j] += 1e-5*(j-i) elif disn[j] > disn[i]: break ita_start() # Apply phasesmooth to each phase for i in range(Np): pid = np.where((disn > If[i]) & (disn < If[i+1]))[0] try: Xn[pid] = phasesmooth(disn[pid], Xn[pid], ax, i+1) except (ValueError, TypeError) as error: ita_finish() raise error plt.close() ita_finish() # Create a sharp interface at interface locations # Solubility of each phase will be extended a little for i in range(1, Np): pid = np.where(disn > If[i])[0][0] start = max(pid-5, np.where(disn > If[i-1])[0][0]) end = min(pid+5, np.where(disn < If[i+1])[0][-1]) if start+2 < pid: fX1 = splrep(disn[start:pid], Xn[start:pid], k=2) else: fX1 = splrep(disn[start:pid], Xn[start:pid], k=1) if pid+1 < end: fX2 = splrep(disn[pid:end+1], Xn[pid:end+1], k=2) else: fX2 = splrep(disn[pid:end+1], Xn[pid:end+1], k=1) disn = np.insert(disn, pid, [If[i], If[i]]) Xn = np.insert(Xn, pid, [splev(If[i], fX1), splev(If[i], fX2)]) Ip[i] = pid+1 Ip[-1] = len(Xn) disni, Xni = disn.copy(), Xn.copy() # Interpolation if n > 0: ni = [int(n*(If[i]-If[0])//(If[-1]-If[0])) for i in range(Np)]+[n] disni, Xni = np.zeros(n), np.zeros(n) for i in range(Np): fX = splrep(disn[Ip[i]:Ip[i+1]], Xn[Ip[i]:Ip[i+1]], k=1) disni[ni[i]:ni[i+1]] = np.linspace(disn[Ip[i]], disn[Ip[i+1]-1], ni[i+1]-ni[i]) Xni[ni[i]:ni[i+1]] = splev(disni[ni[i]:ni[i+1]], fX) print('Smooth finished') if name == '': name = profile.name+'_smoothed' return DiffProfile(disni, Xni, If[1:-1], name=name)
def mphSim(profile, diffsys, time, liquid=0, output=True, name=''): """ Single/Multiple-Phase Diffusion Simulation. Liquid phase can be attached at left or right end. For thin film simulation, please set up the interface nearby the end in the initial profile. Parameters ---------- profile : DiffProfile Initial profile before simulation. diffsys : DiffSystem Diffusion coefficients. time : float time in seconds. liquid : 1, 0 or -1, optional Liquid phase provide infinite mass flux during simulation, can only be attached at left or right end. 0 : No liquid phase attached. -1 : Liquid phase attached at left. 1 : Liquid phase attached at right. output : boolean, optional Print simulation progress, default = True. name : str, optional Name the output DiffProfile. Returns ------- profile : DiffProfile Simulated diffusion profile Examples -------- With known diffusion coefficients (dsys), simulate profile of 100 hours of diffusion for a diffusion couple experiment (initial profile is a step) on a 600 micron grids: >>> dis = mesh(0, 600) >>> init_profile = step(dis, 300, dsys) >>> final_profile = mphSim(init_profile, dsys, 3600*100) If liquid phase is attached to the left: >>> final_profile = mphSim(init_profile, dsys, 3600*100, liquid=-1) """ dis, Xs = profile.dis.copy() / 1e6, profile.X.copy() Ip, If = profile.Ip.copy(), profile.If.copy() / 1e6 Np, Xr = diffsys.Np, diffsys.Xr.copy() fD = [f for f in diffsys.Dfunc] # Ascending or descending profile if (Xs[-1] - Xs[0]) * (Xr[-1, 1] - Xr[0, 0]) < 0: Xr = Xr.flatten()[::-1].reshape((Np, 2)) fD = fD[::-1] if name == '': name = diffsys.name + '_%.1fh' % (time / 3600) if len(If) != Np + 1: raise ValueError( 'Number of phases mismatches between profile and diffusion system') if liquid not in [-1, 0, 1]: raise ValueError('liquid can only be 0 1 or -1') try: time = float(time) except TypeError: print('Wrong Type of time') d = dis[1:] - dis[:-1] dj = 0.5 * (d[1:] + d[:-1]) Jf, DCs = np.zeros(len(dis) - 1), np.zeros(len(dis) - 1) JIf = np.zeros([Np + 1, 2]) vIf = np.zeros(Np + 1) t, m = 0.0, 0 dt0 = time / 1e3 while t < time: Xm = (Xs[:-1] + Xs[1:]) / 2 dt = dt0 # Jf JIf calculation # dt limited by simulation stability inside each phases for i in range(Np): if Ip[i + 1] > Ip[i] + 1: Ipr = np.arange(Ip[i], Ip[i + 1] - 1) DCs[Ipr] = np.exp(splev(Xm[Ipr], fD[i])) Jf[Ipr] = -DCs[Ipr] * (Xs[Ipr + 1] - Xs[Ipr]) / d[Ipr] dtn = np.abs(d[Ipr]**2 / DCs[Ipr] / 2).min() dt = dtn if dtn < dt else dt if Ip[i + 1] == Ip[i]: X0 = np.mean(Xr[i]) JIf[i, 1] = JIf[i + 1, 0] = -(Xr[i, 1] - Xr[i, 0]) / ( If[i + 1] - If[i]) * np.exp(splev(X0, fD[i])) else: if i < Np - 1: X0 = 0.5 * (Xr[i, 1] + Xs[Ip[i + 1] - 1]) JIf[i + 1, 0] = -(Xr[i, 1] - Xs[Ip[i + 1] - 1]) / ( If[i + 1] - dis[Ip[i + 1] - 1]) * np.exp( splev(X0, fD[i])) if i > 0: X0 = 0.5 * (Xr[i, 0] + Xs[Ip[i]]) JIf[i, 1] = -(Xs[Ip[i]] - Xr[i, 0]) / ( dis[Ip[i]] - If[i]) * np.exp(splev(X0, fD[i])) # vIf calculation, dt limited by 'No interface passing by' for i in range(1, Np): vIf[i] = (JIf[i, 1] - JIf[i, 0]) / (Xr[i, 0] - Xr[i - 1, 1]) vid = [Ip[i] - 2] if Ip[i] > 1 else [] vid += [Ip[i]] if Ip[i] < len(dis) else [] dtn = np.abs(d[vid] / vIf[i]).min() dt = dtn if dtn < dt else dt if i > 1 and vIf[i - 1] > vIf[i]: dtn = (If[i] - If[i - 1]) / (vIf[i - 1] - vIf[i]) / 2 dt = dtn if dtn < dt else dt # dt limited by grid nearby interfaces cannot exceed solubility limit if Xr[0, 0] < Xr[-1, 1]: for i in range(Np): if Ip[i + 1] == Ip[i]: continue elif Ip[i + 1] == Ip[i] + 1: if JIf[i, 1] > JIf[i + 1, 0]: dtn = (Xr[i, 1] - Xs[Ip[i]]) * dj[Ip[i] - 1] / ( JIf[i, 1] - JIf[i + 1, 0]) dt = dtn if dtn < dt else dt elif JIf[i, 1] < JIf[i + 1, 0]: dtn = (Xs[Ip[i]] - Xr[i, 0]) * dj[Ip[i] - 1] / ( JIf[i + 1, 0] - JIf[i, 1]) dt = dtn if dtn < dt else dt else: if i < Np - 1 and JIf[i + 1, 0] < Jf[Ip[i + 1] - 2]: dtn = -(Xr[i, 1] - Xs[Ip[i + 1] - 1]) / ( JIf[i + 1, 0] - Jf[Ip[i + 1] - 2]) * dj[Ip[i + 1] - 2] dt = dtn if dtn < dt else dt if i > 0 and Jf[Ip[i]] > JIf[i, 1]: dtn = (Xs[Ip[i]] - Xr[i, 0]) / ( Jf[Ip[i]] - JIf[i, 1]) * dj[Ip[i] - 1] dt = dtn if dtn < dt else dt else: for i in range(Np): if Ip[i + 1] == Ip[i]: continue elif Ip[i + 1] == Ip[i] + 1: if JIf[i, 1] < JIf[i + 1, 0]: dtn = (Xr[i, 1] - Xs[Ip[i]]) * dj[Ip[i] - 1] / ( JIf[i, 1] - JIf[i + 1, 0]) dt = dtn if dtn < dt else dt elif JIf[i, 1] > JIf[i + 1, 0]: dtn = (Xs[Ip[i]] - Xr[i, 0]) * dj[Ip[i] - 1] / ( JIf[i + 1, 0] - JIf[i, 1]) dt = dtn if dtn < dt else dt else: if i < Np - 1 and JIf[i + 1, 0] > Jf[Ip[i + 1] - 2]: dtn = -(Xr[i, 1] - Xs[Ip[i + 1] - 1]) / ( JIf[i + 1, 0] - Jf[Ip[i + 1] - 2]) * dj[Ip[i + 1] - 2] dt = dtn if dtn < dt else dt if i > 0 and Jf[Ip[i]] < JIf[i, 1]: dtn = (Xs[Ip[i]] - Xr[i, 0]) / ( Jf[Ip[i]] - JIf[i, 1]) * dj[Ip[i] - 1] dt = dtn if dtn < dt else dt dt = time - t if t + dt > time else dt * 0.95 # If first or last phase will be consumed if If[1] < dis[1] and vIf[1] < 0: dtn = (dis[0] - If[1]) / vIf[1] dt = dtn if dtn < dt else dt elif If[-2] > dis[-2] and vIf[-2] > 0: dtn = (dis[-1] - If[-2]) / vIf[-2] dt = dtn if dtn < dt else dt t += dt m += 1 # Ficks 2nd law inside each phase for i in range(Np): if Ip[i + 1] == Ip[i]: continue elif Ip[i + 1] == Ip[i] + 1: Xs[Ip[i]] -= dt * (JIf[i + 1, 0] - JIf[i, 1]) / dj[Ip[i] - 1] else: if i > 0: Xs[Ip[i]] -= dt * (Jf[Ip[i]] - JIf[i, 1]) / dj[Ip[i] - 1] if i < Np - 1: Xs[Ip[i + 1] - 1] -= dt * (JIf[i + 1, 0] - Jf[Ip[i + 1] - 2]) / dj[Ip[i + 1] - 2] if Ip[i + 1] > Ip[i] + 2: Ipr = np.arange(Ip[i] + 1, Ip[i + 1] - 1) Xs[Ipr] -= dt * (Jf[Ipr] - Jf[Ipr - 1]) / dj[Ipr - 1] # Composition changes at first & last grid # If there is liquid phase attached, composition unchanged. if liquid != -1: Xs[0] -= Jf[0] / d[0] * dt if liquid != 1: Xs[-1] += Jf[-1] / d[-1] * dt # If one phase consumed, delete this phase if If[1] + vIf[1] * dt <= dis[0]: Xs[0] = Xr[1, 0] Np -= 1 Xr, If, Ip, fD = Xr[1:], If[1:], Ip[1:], fD[1:] Ip[0] = 0 JIf = np.zeros([Np + 1, 2]) vIf = np.zeros(Np + 1) if output: print( 'First phase consumed, %i phase(s) left, time = %.3f hrs' % (Np, t / 3600)) elif If[-2] + vIf[-2] * dt >= dis[-1]: Xs[-1] = Xr[-2, 1] Np -= 1 Xr, If, Ip, fD = Xr[:-1], If[:-1], Ip[:-1], fD[:-1] Ip[-1] = len(dis) JIf = np.zeros([Np + 1, 2]) vIf = np.zeros(Np + 1) if output: print( 'Last phase consumed, %i phase(s) left, time = %.3f hrs' % (Np, t / 3600)) # Interface move across one grid, passed grid has value change for i in range(1, Np): If[i] += vIf[i] * dt if If[i] < dis[Ip[i] - 1]: Ip[i] -= 1 if If[i + 1] < dis[Ip[i] + 1]: Xs[Ip[i]] = splev(dis[Ip[i]], splrep([If[i], If[i + 1]], Xr[i], k=1)) else: Xs[Ip[i]] = splev( dis[Ip[i]], splrep([If[i], dis[Ip[i] + 1]], [Xr[i, 0], Xs[Ip[i] + 1]], k=1)) elif If[i] > dis[Ip[i]]: Ip[i] += 1 if If[i - 1] > dis[Ip[i] - 2]: Xs[Ip[i] - 1] = splev( dis[Ip[i] - 1], splrep([If[i - 1], If[i]], Xr[i - 1], k=1)) else: Xs[Ip[i] - 1] = splev( dis[Ip[i] - 1], splrep([dis[Ip[i] - 2], If[i]], [Xs[Ip[i] - 2], Xr[i - 1, 1]], k=1)) if output and np.mod(m, 3e4) == 0: print('%.3f/%.3f hrs simulated' % (t / 3600, time / 3600)) if output: print('Simulation Complete') # Insert interface informations for i in list(range(Np - 1, 0, -1)): dis = np.insert(dis, Ip[i], [If[i], If[i]]) Xs = np.insert(Xs, Ip[i], [Xr[i - 1, 1], Xr[i, 0]]) return DiffProfile(dis * 1e6, Xs, If[1:-1] * 1e6, name=name)