├── LICENSE.txt ├── MANIFEST.in ├── README.rst ├── docs └── examples │ ├── DCModeling.rst │ ├── DCModeling_files │ ├── DCModeling_1.png │ ├── DCModeling_2_1.png │ ├── DCModeling_2_2.png │ ├── DCModeling_2_3.png │ ├── DCModeling_2_4.png │ ├── DCModeling_2_5.png │ └── DCModeling_3.png │ ├── DataSmooth.rst │ ├── DataSmooth_files │ ├── DataSmooth_1.png │ ├── DataSmooth_2_1.png │ ├── DataSmooth_2_2.png │ ├── DataSmooth_2_3.png │ ├── DataSmooth_2_4.png │ └── DataSmooth_3.png │ ├── DiffusionSimulation.rst │ ├── DiffusionSimulation_files │ ├── DiffusionSimulation_1.png │ ├── DiffusionSimulation_2.png │ └── DiffusionSimulation_3.png │ ├── ErrorAnalysis.rst │ ├── ErrorAnalysis_files │ ├── ErrorAnalysis_1.png │ ├── ErrorAnalysis_2.png │ └── ErrorAnalysis_3.png │ ├── FSA.rst │ └── FSA_files │ ├── FSA_1.png │ └── FSA_2.png ├── examples ├── DCModeling.py ├── DataSmooth.py ├── DiffusionSimulation.py ├── ErrorAnalysis.py ├── ForwardSimulationAnalysis.py └── data │ ├── NiMo.csv │ ├── NiMo_DC_init.csv │ ├── NiMo_exp.csv │ ├── NiMo_sm.csv │ ├── TiZr.csv │ └── TiZr_exp.csv ├── pydiffusion ├── Dtools.py ├── __init__.py ├── core.py ├── io.py ├── plot.py ├── simulation.py ├── smooth.py ├── tests │ ├── test_model.py │ ├── test_plot.py │ ├── test_simulation.py │ └── test_utils.py └── utils.py ├── setup.cfg └── setup.py /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2019 Zhangqi Chen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE.txt 2 | include README.rst -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | pyDiffusion 3 | =========== 4 | 5 | .. image:: https://img.shields.io/pypi/status/pydiffusion.svg 6 | :target: https://pypi.python.org/pypi/pydiffusion/ 7 | :alt: Development Status 8 | 9 | .. image:: https://img.shields.io/pypi/v/pydiffusion.svg 10 | :target: https://pypi.python.org/pypi/pydiffusion/ 11 | :alt: Latest version 12 | 13 | .. image:: https://img.shields.io/pypi/pyversions/pydiffusion.svg 14 | :target: https://pypi.python.org/pypi/pydiffusion/ 15 | :alt: Supported Python versions 16 | 17 | .. image:: https://img.shields.io/pypi/l/pydiffusion.svg 18 | :target: https://pypi.python.org/pypi/pydiffusion/ 19 | :alt: License 20 | 21 | **pyDiffusion** combines tools like **diffusion simulation**, **diffusion data smooth**, **forward simulation analysis (FSA)**, etc. to help people analyze diffusion data efficiently. 22 | 23 | Dependencies 24 | ------------ 25 | 26 | * Python 3.5+ 27 | * numpy, matplotlib, scipy, pandas 28 | 29 | Installation 30 | ------------ 31 | 32 | Via pip(recommend) 33 | 34 | .. code-block:: 35 | 36 | pip install pydiffusion 37 | 38 | Examples 39 | -------- 40 | 41 | Diffusion Simulation 42 | ~~~~~~~~~~~~~~~~~~~~ 43 | 44 | Based on Ni-Mo interdiffusion coefficients data at 1100C, simulate the diffusion process for 800 hours. See `Diffusion Simulation Example`_. 45 | 46 | .. image:: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/DiffusionSimulation_files/DiffusionSimulation_3.png 47 | 48 | Forward Simulation Analysis (FSA) 49 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 50 | 51 | Calculate interdiffusion coefficients of Ni-Mo at 1100C based on raw diffusion data (1000 hours). See `FSA Example`_. 52 | 53 | .. image:: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/FSA_files/FSA_2.png 54 | 55 | Error Analysis 56 | ~~~~~~~~~~~~~~ 57 | 58 | The interdiffusion coefficients in Ti-Zr system at 1000C are calculated using FSA. The error bounds of the diffusivity data are estimated using error analysis tool. See `Error Analysis Example`_. 59 | 60 | .. image:: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/ErrorAnalysis_files/ErrorAnalysis_3.png 61 | 62 | Citing 63 | ------ 64 | 65 | If you use pydiffusion in your research, please consider citing the following article published in JORS: 66 | 67 | Chen, Z., Zhang, Q. and Zhao, J.-C., 2019. pydiffusion: A Python Library for Diffusion Simulation and Data Analysis. Journal of Open Research Software, 7(1), p.13. DOI: http://doi.org/10.5334/jors.255 68 | 69 | .. _Diffusion Simulation Example: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/DiffusionSimulation.rst 70 | .. _FSA Example: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/FSA.rst 71 | .. _Error Analysis Example: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/ErrorAnalysis.rst 72 | -------------------------------------------------------------------------------- /docs/examples/DCModeling.rst: -------------------------------------------------------------------------------- 1 | ==================================== 2 | Diffusion Coefficients (DC) Modeling 3 | ==================================== 4 | 5 | Before **Forward Simulation Analysis (FSA)**, an initial modeling of diffusion coefficients is required. It is recommend to perform **Data Smoothing** (example_) before **DC modeling**. 6 | 7 | **DC modeling** can be implemented by ``pydiffusion.Dmodel.Dmodel``. Same with data smoothing, it requires many manually inputs during the interactive process, please do not close any plot window during the modeling process. (Currently doen't support ``matplotlib inline``) Here is an example for DC modeling of smoothed Ni-Mo 1100C 1000 hours data. 8 | 9 | .. code-block:: python 10 | 11 | from pydiffusion.io import read_csv, save_csv 12 | from pydiffusion.plot import profileplot, DCplot, SFplot 13 | from pydiffusion.Dtools import Dmodel 14 | 15 | Read smoothed profile data 16 | -------------------------- 17 | 18 | Read smoothed profile data of Ni-Mo 1100C 1000 hours. 19 | 20 | .. code-block:: python 21 | 22 | NiMo_sm, _ = read_csv('examples/data/NiMo_sm.csv') 23 | 24 | profileplot(NiMo_sm) 25 | 26 | .. image:: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/DCModeling_files/DCModeling_1.png 27 | 28 | DC modeling manually 29 | -------------------- 30 | 31 | DCmodeling is implemented by ``pydiffusion.Dmodel.Dmodel`` function. Time in seconds is required as input. 32 | 33 | .. code-block:: python 34 | 35 | time = 1000 * 3600 36 | diffsys_init = Dmodel(NiMo_sm, time, Xlim=[0, 1]) 37 | 38 | .. image:: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/DCModeling_files/DCModeling_2_1.png 39 | 40 | .. code-block:: 41 | 42 | Use Spline (y) or UnivariateSpline (n) to model diffusion coefficients? [y] 43 | 44 | At first, the function asks using whether **Spline** or **UnivariateSpline**: 45 | 46 | **Spline**: Need to select points (>0) as reference points for spline fitting. Can use both **Point Mode** and **Phase Mode** in the following **Forward Simulation Analysis (FSA)**. 47 | 48 | **UnivariateSpline**: Need to select range (2 points) for reliable data fitting. Can only use **Phase Mode** in the following **FSA**. 49 | 50 | (Please see definitions of **Point Mode** and **Phase Mode** in **FSA** example) 51 | 52 | In this example, we are using Spline method for all of 3 phases. 53 | 54 | Then program will ask you enter the number of points for **Spline** function of the current phase. Different number of points will create different **Spline** function. 55 | 56 | ================ =============== 57 | Number of points Spline function 58 | ================ =============== 59 | 1 Constant 60 | 2 linear 61 | \>2 Spline with order=2 62 | ================ =============== 63 | 64 | .. code-block:: 65 | 66 | # of spline points: 1 (constant), 2 (linear), >2 (spline) 67 | input # of spline points 68 | 2 69 | 70 | Then # of points must be selected on the figure: 71 | 72 | .. image:: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/DCModeling_files/DCModeling_2_2.png 73 | 74 | You can redo the modeling if you want. 75 | 76 | .. code-block:: 77 | 78 | Continue to next phase? [y] 79 | 80 | Spline for phase 1: 81 | 82 | .. image:: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/DCModeling_files/DCModeling_2_3.png 83 | 84 | .. code-block:: 85 | 86 | # of spline points: 1 (constant), 2 (linear), >2 (spline) 87 | input # of spline points 88 | 2 89 | Continue to next phase? [y] 90 | 91 | Spline for phase 2: 92 | 93 | .. image:: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/DCModeling_files/DCModeling_2_4.png 94 | 95 | .. code-block:: 96 | 97 | # of spline points: 1 (constant), 2 (linear), >2 (spline) 98 | input # of spline points 99 | 1 100 | Continue to next phase? [y] 101 | 102 | Spline for phase 3: 103 | 104 | .. image:: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/DCModeling_files/DCModeling_2_5.png 105 | 106 | .. code-block:: 107 | 108 | DC modeling finished, Xspl info: 109 | [[0.05759519125648957, 0.17265729768975802], [0.50242048811055617, 0.51836224278478515], [0.98405894820043416]] 110 | 111 | For **UnivariateSpline** option, only 2 points is required to select for each phase. 112 | 113 | Plot results: 114 | 115 | .. code-block:: python 116 | 117 | ax = SFplot(NiMo_sm, time, Xlim=[0, 1]) 118 | DCplot(diffsys_init, ax) 119 | 120 | .. image:: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/DCModeling_files/DCModeling_3.png 121 | 122 | DC modeling automatically 123 | ------------------------- 124 | 125 | `Dmodel` function can also automatically model the diffusion coefficients if `Xspl` is provided. You only need to choose from either **Spline** or **UnivariateSpline** during DC modeling. 126 | 127 | .. code-block:: python 128 | 129 | Xspl = [[.05, .2], 130 | [.5, .515], 131 | [.985]] 132 | diffsys_init_auto = Dmodel(NiMo_sm, time, Xspl=Xspl, Xlim=[0, 1]) 133 | 134 | Save both smoothed profile and initial DC settings 135 | -------------------------------------------------- 136 | 137 | Usually smoothed profile and initial DC settings are saved together preparing for FSA. 138 | 139 | .. code-block:: python 140 | 141 | save_csv('examples/data/NiMo_DC_init.csv', profile=NiMo_sm, diffsys=diffsys_init_auto) 142 | 143 | Make sure you remember the ``Xspl`` info if you are going to read data from .csv file before FSA! 144 | 145 | After **Data Smoothing** and **DC Modeling**, you can go ahead to perform **Forward Simulation Analysis**, see example__. 146 | 147 | .. _example: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/DataSmooth.rst 148 | .. __: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/FSA.rst -------------------------------------------------------------------------------- /docs/examples/DCModeling_files/DCModeling_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangqi-chen/pyDiffusion/98c500e07226219060e533dba48f5e7372d307fc/docs/examples/DCModeling_files/DCModeling_1.png -------------------------------------------------------------------------------- /docs/examples/DCModeling_files/DCModeling_2_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangqi-chen/pyDiffusion/98c500e07226219060e533dba48f5e7372d307fc/docs/examples/DCModeling_files/DCModeling_2_1.png -------------------------------------------------------------------------------- /docs/examples/DCModeling_files/DCModeling_2_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangqi-chen/pyDiffusion/98c500e07226219060e533dba48f5e7372d307fc/docs/examples/DCModeling_files/DCModeling_2_2.png -------------------------------------------------------------------------------- /docs/examples/DCModeling_files/DCModeling_2_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangqi-chen/pyDiffusion/98c500e07226219060e533dba48f5e7372d307fc/docs/examples/DCModeling_files/DCModeling_2_3.png -------------------------------------------------------------------------------- /docs/examples/DCModeling_files/DCModeling_2_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangqi-chen/pyDiffusion/98c500e07226219060e533dba48f5e7372d307fc/docs/examples/DCModeling_files/DCModeling_2_4.png -------------------------------------------------------------------------------- /docs/examples/DCModeling_files/DCModeling_2_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangqi-chen/pyDiffusion/98c500e07226219060e533dba48f5e7372d307fc/docs/examples/DCModeling_files/DCModeling_2_5.png -------------------------------------------------------------------------------- /docs/examples/DCModeling_files/DCModeling_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangqi-chen/pyDiffusion/98c500e07226219060e533dba48f5e7372d307fc/docs/examples/DCModeling_files/DCModeling_3.png -------------------------------------------------------------------------------- /docs/examples/DataSmooth.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | Data Smoothing 3 | ============== 4 | 5 | Before any diffusion coefficients calculation, the data smoothing is required for original experimental datasets. A good data smoothing will give a good guess of diffusion coefficients before **Forward Simulation Analysis (FSA)**. 6 | 7 | Data smoothing can be implemented by ``pydiffusion.smooth.datasmooth``. (Currently doen't support ``matplotlib inline``) The process requires many mannual inputs as smoothing goes on, so please do not close any window during the process. Here is an example for data smoothing of Ni-Mo 1100C 1000 hours diffusion data. 8 | 9 | .. code-block:: python 10 | 11 | import pandas as pd 12 | from pydiffusion.core import DiffProfile 13 | from pydiffusion.io import read_csv, save_csv 14 | from pydiffusion.plot import profileplot 15 | from pydiffusion.smooth import datasmooth 16 | 17 | Read experimental data 18 | ---------------------- 19 | 20 | Raw diffusion profile data can be represented by a ``DiffProfile`` object, which can be created easily. 21 | 22 | .. code-block:: python 23 | 24 | data = pd.read_csv('examples/data/NiMo_exp.csv') 25 | dis, X = data['dis'], data['X'] 26 | NiMo_exp = DiffProfile(dis=dis, X=X, name='NiMo_exp') 27 | 28 | As long as using `1d-array like` type, you can read ``dis`` and ``X`` data from any file type. 29 | 30 | Profile data can also be read from a saved .csv file. 31 | 32 | .. code-block:: python 33 | 34 | NiMo_exp, _ = read_csv('examples/data/NiMo_exp.csv') 35 | 36 | Plot the raw data 37 | 38 | .. code-block:: python 39 | 40 | ax = profileplot(NiMo_exp, ax, c='b', marker='o', ls='none', fillstyle='none') 41 | 42 | .. image:: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/DataSmooth_files/DataSmooth_1.png 43 | 44 | Data smoothing 45 | -------------- 46 | 47 | For multiple phases situation, interfaces locations are required as inputs. Ni-Mo has 3 phases at 1100C, 2 interfaces locations (311.5, 340.5) must be provided as the input for ``datasmooth``. ``n`` is the number of output interpolated profile, default ``n=2000``. 48 | 49 | .. code-block:: python 50 | 51 | NiMo_sm = datasmooth(NiMo_exp, [311.5, 340.5], n=500) 52 | 53 | The function will smooth 3 phases individually, each phase is smoothed in the following steps: 54 | 55 | 1. Ask if zoom in is required inside the phase. The zoom in range (start and end location in micron) should be entered. 56 | 2. Ask if the start and end composition need to be changed, since the default smoothing won't change the start and end data points. 57 | 3. Moving "radius" smoothing, input radius and times. For each data point at location d, its nearby data within [d-r, d+r] are averaged, in which r is the radius in micron. 58 | 4. If the smoothing is not good, redo the smoothing. 59 | 60 | Profile in each phase will be plotted at first. 61 | 62 | .. image:: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/DataSmooth_files/DataSmooth_2_1.png 63 | 64 | .. code-block:: 65 | 66 | Zoom in? [n]n 67 | 68 | No Change: Press ENTER (0 input) 69 | Constant: Enter the constant composition (1 input) 70 | Linear: Enter the start and end composition (2 inputs) 71 | Moving Radius: Start & end composition, smooth radius and times (4 inputs) 72 | (Unchanged end composition can be input by '-') 73 | Current ends: [0.0025311643355943733 0.2428008244781973] 74 | - - 20 2 75 | 76 | Redo this smooth? (y/[n])n 77 | 78 | Further smooth for this phase? (y/[n])n 79 | 80 | 81 | Phase 1 after smoothing: 82 | 83 | .. image:: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/DataSmooth_files/DataSmooth_2_2.png 84 | 85 | .. code-block:: 86 | 87 | Zoom in? [n]n 88 | 89 | No Change: Press ENTER (0 input) 90 | Constant: Enter the constant composition (1 input) 91 | Linear: Enter the start and end composition (2 inputs) 92 | Moving Radius: Start & end composition, smooth radius and times (4 inputs) 93 | (Unchanged end composition can be input by '-') 94 | Current ends: [0.4945196711802708 0.5223486142653296] 95 | .495 .525 10 2 96 | 97 | Redo this smooth? (y/[n])n 98 | 99 | Further smooth for this phase? (y/[n])n 100 | 101 | Phase 2 after smoothing: 102 | 103 | .. image:: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/DataSmooth_files/DataSmooth_2_3.png 104 | 105 | .. code-block:: 106 | 107 | Zoom in? [n]n 108 | 109 | No Change: Press ENTER (0 input) 110 | Constant: Enter the constant composition (1 input) 111 | Linear: Enter the start and end composition (2 inputs) 112 | Moving Radius: Start & end composition, smooth radius and times (4 inputs) 113 | (Unchanged end composition can be input by '-') 114 | Current ends: [0.9779640502935792 0.9933157889470364] 115 | .978 .9935 5 1 116 | 117 | Redo this smooth? (y/[n])n 118 | 119 | Further smooth for this phase? (y/[n])n 120 | Smooth finished 121 | 122 | Phase 3 after smoothing: 123 | 124 | .. image:: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/DataSmooth_files/DataSmooth_2_4.png 125 | 126 | Plot smoothed results 127 | --------------------- 128 | 129 | .. code-block:: python 130 | 131 | profileplot(NiMo_sm, ax, c='r') 132 | 133 | .. image:: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/DataSmooth_files/DataSmooth_3.png 134 | 135 | Save smoothed results 136 | --------------------- 137 | 138 | Smoothe profile can be saved as .csv, which can be read directly by ``read_csv`` later. 139 | 140 | .. code-block:: python 141 | 142 | save_csv('examples/data/NiMo_sm.csv', profile=NiMo_sm) 143 | 144 | After **Data Smoothing**, **Diffusion Coefficients Modeling** is required before **Forward Simulation Analysis**, see example_. 145 | 146 | .. _example: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/DCModeling.rst -------------------------------------------------------------------------------- /docs/examples/DataSmooth_files/DataSmooth_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangqi-chen/pyDiffusion/98c500e07226219060e533dba48f5e7372d307fc/docs/examples/DataSmooth_files/DataSmooth_1.png -------------------------------------------------------------------------------- /docs/examples/DataSmooth_files/DataSmooth_2_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangqi-chen/pyDiffusion/98c500e07226219060e533dba48f5e7372d307fc/docs/examples/DataSmooth_files/DataSmooth_2_1.png -------------------------------------------------------------------------------- /docs/examples/DataSmooth_files/DataSmooth_2_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangqi-chen/pyDiffusion/98c500e07226219060e533dba48f5e7372d307fc/docs/examples/DataSmooth_files/DataSmooth_2_2.png -------------------------------------------------------------------------------- /docs/examples/DataSmooth_files/DataSmooth_2_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangqi-chen/pyDiffusion/98c500e07226219060e533dba48f5e7372d307fc/docs/examples/DataSmooth_files/DataSmooth_2_3.png -------------------------------------------------------------------------------- /docs/examples/DataSmooth_files/DataSmooth_2_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangqi-chen/pyDiffusion/98c500e07226219060e533dba48f5e7372d307fc/docs/examples/DataSmooth_files/DataSmooth_2_4.png -------------------------------------------------------------------------------- /docs/examples/DataSmooth_files/DataSmooth_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangqi-chen/pyDiffusion/98c500e07226219060e533dba48f5e7372d307fc/docs/examples/DataSmooth_files/DataSmooth_3.png -------------------------------------------------------------------------------- /docs/examples/DiffusionSimulation.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | Diffusion Simulation 3 | ==================== 4 | 5 | Here are few examples of how to use ``pydiffusion.mphSim`` to perform diffusion simulations. 6 | 7 | .. code-block:: python 8 | 9 | import numpy as np 10 | import matplotlib.pyplot as plt 11 | import pandas as pd 12 | from pydiffusion.core import DiffSystem 13 | from pydiffusion.utils import step, mesh 14 | from pydiffusion.simulation import mphSim 15 | from pydiffusion.plot import profileplot, DCplot 16 | from pydiffusion.io import read_csv, save_csv 17 | 18 | Diffusion simulation by easy setups 19 | ----------------------------------- 20 | 21 | pyDiffusion can simulate simple diffusion systems by easy setups. 22 | 23 | Usually, a diffusion simulation requires two inputs: 24 | 25 | 1. A initial profile (``pydiffusion.core.DiffProfile``) 26 | 2. A diffusion system with well-defined diffusion coefficients (``pydiffusion.core.DiffSystem``) 27 | 28 | Define a diffusion system with constant diffusion coefficients 10\ :sup:`-14`\ m\ :sup:`2`\/s with concentration range from 0 to 1. To accomplish this, we should create a ``DiffSystem`` object. Input parameters include `Xr`: solubility range, ``X`` and ``DC``: concentration and corresponding diffusion coefficients data. 29 | 30 | .. code-block:: python 31 | 32 | diffsys = DiffSystem(Xr=[0, 1], X=[0, 1], DC=[1e-14, 1e-14], name='Constant D') 33 | 34 | Then create a initial step profile (step at 500 micron, length = 1000 micron) before simulation. To accomplish this, function ``mesh`` is used to mesh grids, and ``step`` is used to create a step profile upon meshed grids. 35 | 36 | .. code-block:: python 37 | 38 | dis = mesh(0, 1000, 501) 39 | profile_init = step(dis, 500, diffsys, name='Intitial step profile') 40 | 41 | fig = plt.figure(figsize=(16, 6)) 42 | ax1, ax2 = fig.add_subplot(121), fig.add_subplot(122) 43 | ax1.set_title('Diffusion Coefficients', fontsize=15) 44 | ax2.set_title('Initial Step Profile', fontsize=15) 45 | DCplot(diffsys, ax1) 46 | profileplot(profile_init, ax2) 47 | 48 | .. image:: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/DiffusionSimulation_files/DiffusionSimulation_1.png 49 | 50 | Simulate the diffusion process for 200 hours. 51 | 52 | .. code-block:: python 53 | 54 | time = 200 * 3600 55 | profile_final = mphSim(profile_init, diffsys, time) 56 | 57 | Plot the results 58 | 59 | .. code-block:: python 60 | 61 | ax = profileplot(profile_init, ax, ls='--') 62 | profileplot(profile_final, ax, c='r') 63 | 64 | .. image:: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/DiffusionSimulation_files/DiffusionSimulation_2.png 65 | 66 | Diffusion simulation from diffusivity data 67 | ------------------------------------------ 68 | 69 | The data required for diffusion simulation is the diffusion coefficients and solubility limits. Here we read concentration dependent diffusion coefficients for Ni-Mo system at 1100 degree C from datafile, and input solubility information mannually. 70 | 71 | .. code-block:: python 72 | 73 | data = pd.read_csv('examples/data/NiMo.csv') 74 | X, DC = data['X'], data['DC'] 75 | Xr = np.array([[0, 0.25], 76 | [0.49, 0.53], 77 | [0.97, 1]]) 78 | diffsys_NiMo = DiffSystem(Xr=Xr, X=X, DC=DC) 79 | 80 | As long as using `1d-array like` type, you can read ``X`` and ``DC`` data from any file type. 81 | 82 | You can also use `pydiffusion.io.read_csv` to read diffusion coefficients directly from saved data. 83 | 84 | .. code-block:: python 85 | 86 | _, diffsys_NiMo = read_csv('examples/data/NiMo.csv', [0, 1]) 87 | 88 | Then create initial step profile as above. 89 | 90 | .. code-block:: python 91 | 92 | dis = mesh(0, 400, 301) 93 | profile_NiMo_init = step(dis, 300, diffsys_NiMo) 94 | 95 | Perform diffusion simulation for 800 hours. 96 | 97 | .. code-block:: python 98 | 99 | profile_NiMo = mphSim(profile_NiMo_init, diffsys_NiMo, 800*3600) 100 | 101 | Plot results 102 | 103 | .. code-block:: python 104 | 105 | fig = plt.figure(figsize=(16, 6)) 106 | ax1, ax2 = fig.add_subplot(121), fig.add_subplot(122) 107 | ax1.set_title('Diffusion Coefficients of Ni-Mo at 1100C', fontsize=15) 108 | ax2.set_title('Diffusion Simulation for 800 hours', fontsize=15) 109 | DCplot(diffsys_NiMo, ax1) 110 | profileplot(profile_NiMo_init, ax2, ls='--') 111 | profileplot(profile_NiMo, ax2, c='r') 112 | 113 | .. image:: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/DiffusionSimulation_files/DiffusionSimulation_3.png 114 | 115 | Save simulation results 116 | ----------------------- 117 | 118 | Both diffusion profile and diffusion coefficient information can be saved into .csv format by pydiffusion. So that you can read it next time by ``read_csv``. It is recommended to save both profile and diffusion coefficients at the same time. 119 | 120 | .. code-block:: python 121 | 122 | save_csv('examples/data/NiMo_800h.csv', profile=profile_NiMo, diffsys=diffsys_NiMo) 123 | -------------------------------------------------------------------------------- /docs/examples/DiffusionSimulation_files/DiffusionSimulation_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangqi-chen/pyDiffusion/98c500e07226219060e533dba48f5e7372d307fc/docs/examples/DiffusionSimulation_files/DiffusionSimulation_1.png -------------------------------------------------------------------------------- /docs/examples/DiffusionSimulation_files/DiffusionSimulation_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangqi-chen/pyDiffusion/98c500e07226219060e533dba48f5e7372d307fc/docs/examples/DiffusionSimulation_files/DiffusionSimulation_2.png -------------------------------------------------------------------------------- /docs/examples/DiffusionSimulation_files/DiffusionSimulation_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangqi-chen/pyDiffusion/98c500e07226219060e533dba48f5e7372d307fc/docs/examples/DiffusionSimulation_files/DiffusionSimulation_3.png -------------------------------------------------------------------------------- /docs/examples/ErrorAnalysis.rst: -------------------------------------------------------------------------------- 1 | ============================================= 2 | Error Analysis of Diffusion Coefficients Data 3 | ============================================= 4 | 5 | Here is an example of how to analyze the uncertainty of diffusion coefficients data using ``pydiffusion.simulation.ErrorAnalysis`` 6 | 7 | .. code-block:: python 8 | 9 | import matplotlib.pyplot as plt 10 | from pydiffusion.io import read_csv 11 | from pydiffusion.utils import step, automesh, matanocalc, DCbias 12 | from pydiffusion.simulation import ErrorAnalysis 13 | from pydiffusion.plot import DCplot 14 | 15 | Definition of Error 16 | ------------------- 17 | 18 | The error is analyzed by creating a bias on diffusion coefficients, this can be done by ``DCbias`` function. Here is an example of creating bias at X = 0.2 in TiZr diffusion coefficients. 19 | 20 | .. code-block:: python 21 | 22 | profile_fsa, diffsys_TiZr = read_csv('examples/data/TiZr.csv', [0, 1]) 23 | profile_exp, _ = read_csv('examples/data/TiZr_exp.csv') 24 | diffsys_bias = DCbias(diffsys_TiZr, 0.2, 0.1) 25 | 26 | ax = DCplot(diffsys_TiZr, label='original') 27 | DCplot(diffsys_bias, ax, c='r', ls='--', label='bias') 28 | plt.pause(1.0) 29 | 30 | .. image:: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/ErrorAnalysis_files/ErrorAnalysis_1.png 31 | 32 | Error Analysis of Ti-Zr 1000C Diffusion Data 33 | -------------------------------------------- 34 | 35 | Here is an example showing error analysis of Ti-Zr 1000C data, calculating 3 positions at (0, 0.5, 1.0). Low accuracy this time. 36 | 37 | .. code-block:: python 38 | 39 | dism = automesh(profile_fsa, diffsys_TiZr, [300, 350]) 40 | mp = matanocalc(profile_fsa, [0, 1]) 41 | profile_init = step(dism, mp, diffsys_TiZr) 42 | time = 100*3600 43 | error_result = ErrorAnalysis(profile_exp, profile_init, diffsys_TiZr, time, loc=3, accuracy=1e-2) 44 | 45 | The program will ask an input of cap error. Higher the cap, higher the bias will be calculated. 46 | 47 | .. code-block:: 48 | 49 | Meshing Num=337, Minimum grid=5.653367 um 50 | Reference error= 0.001029. Input cap error: [ 0.001039].002 51 | Cap error = 0.002000 52 | At 0.000, simulation #1, deltaD = 0.500000, profile difference = 0.024898(0.002000) 53 | At 0.000, simulation #2, deltaD = 0.020338, profile difference = 0.001376(0.002000) 54 | At 0.000, simulation #3, deltaD = 0.033056, profile difference = 0.001741(0.002000) 55 | At 0.000, simulation #4, deltaD = 0.038286, profile difference = 0.001907(0.002000) 56 | At 0.000, simulation #5, deltaD = 0.040162, profile difference = 0.001972(0.002000) 57 | At 0.000, simulation #6, deltaD = 0.040722, profile difference = 0.001992(0.002000) 58 | Error (positive) at 0.000 = 0.040722, 6 simulations performed, profile difference = 0.001992 59 | At 0.000, simulation #1, deltaD = -0.040722, profile difference = 0.001682(0.002000) 60 | At 0.000, simulation #2, deltaD = -0.081444, profile difference = 0.003000(0.002000) 61 | At 0.000, simulation #3, deltaD = -0.050535, profile difference = 0.001984(0.002000) 62 | Error (negative) at 0.000 = -0.050535, 3 simulations performed, profile difference = 0.001984 63 | At 0.500, simulation #1, deltaD = 0.050535, profile difference = 0.003980(0.002000) 64 | At 0.500, simulation #2, deltaD = 0.016627, profile difference = 0.001299(0.002000) 65 | At 0.500, simulation #3, deltaD = 0.025496, profile difference = 0.001953(0.002000) 66 | At 0.500, simulation #4, deltaD = 0.026074, profile difference = 0.001998(0.002000) 67 | Error (positive) at 0.500 = 0.026074, 4 simulations performed, profile difference = 0.001998 68 | At 0.500, simulation #1, deltaD = -0.026074, profile difference = 0.002718(0.002000) 69 | At 0.500, simulation #2, deltaD = -0.014991, profile difference = 0.001955(0.002000) 70 | At 0.500, simulation #3, deltaD = -0.015648, profile difference = 0.002000(0.002000) 71 | Error (negative) at 0.500 = -0.015648, 3 simulations performed, profile difference = 0.002000 72 | At 1.000, simulation #1, deltaD = 0.015648, profile difference = 0.001144(0.002000) 73 | At 1.000, simulation #2, deltaD = 0.031295, profile difference = 0.001328(0.002000) 74 | At 1.000, simulation #3, deltaD = 0.062591, profile difference = 0.001778(0.002000) 75 | At 1.000, simulation #4, deltaD = 0.125181, profile difference = 0.002863(0.002000) 76 | At 1.000, simulation #5, deltaD = 0.075382, profile difference = 0.001973(0.002000) 77 | At 1.000, simulation #6, deltaD = 0.076904, profile difference = 0.001998(0.002000) 78 | Error (positive) at 1.000 = 0.076904, 6 simulations performed, profile difference = 0.001998 79 | At 1.000, simulation #1, deltaD = -0.076904, profile difference = 0.001654(0.002000) 80 | At 1.000, simulation #2, deltaD = -0.153807, profile difference = 0.002887(0.002000) 81 | At 1.000, simulation #3, deltaD = -0.098494, profile difference = 0.001991(0.002000) 82 | Error (negative) at 1.000 = -0.098494, 3 simulations performed, profile difference = 0.001991 83 | Error analysis complete 84 | 85 | Plot results 86 | 87 | .. code-block:: python 88 | 89 | fig = plt.figure(figsize=(16, 6)) 90 | ax1, ax2 = fig.add_subplot(121), fig.add_subplot(122) 91 | DCplot(diffsys_TiZr, ax1, error_result) 92 | profileplot(profile_fsa, ax2, error_result) 93 | profileplot(profile_exp, ax2, marker='o', ls='none', fillstyle='none') 94 | plt.pause(1.0) 95 | 96 | .. image:: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/ErrorAnalysis_files/ErrorAnalysis_2.png 97 | 98 | The error bar is marked out around the original diffusion coefficients data. Next example we try more calculation points and high accuracy. 99 | 100 | .. code-block:: python 101 | 102 | error_result2 = ErrorAnalysis(profile_exp, profile_init, diffsys_TiZr, time, loc=21, accuracy=1e-3, output=False) 103 | 104 | fig = plt.figure(figsize=(16, 6)) 105 | ax1, ax2 = fig.add_subplot(121), fig.add_subplot(122) 106 | DCplot(diffsys_TiZr, ax1, error_result2) 107 | profileplot(profile_fsa, ax2, error_result2) 108 | profileplot(profile_exp, ax2, marker='o', ls='none', fillstyle='none') 109 | 110 | .. code-block:: 111 | 112 | Reference error= 0.001029. Input cap error: [ 0.001039].002 113 | Cap error = 0.002000 114 | Error (positive) at 0.000 = 0.040939, 8 simulations performed, profile difference = 0.001999 115 | Error (negative) at 0.000 = -0.051030, 4 simulations performed, profile difference = 0.001999 116 | Error (positive) at 0.050 = 0.031481, 4 simulations performed, profile difference = 0.001999 117 | Error (negative) at 0.050 = -0.037261, 4 simulations performed, profile difference = 0.001999 118 | Error (positive) at 0.100 = 0.026793, 4 simulations performed, profile difference = 0.001999 119 | Error (negative) at 0.100 = -0.029648, 4 simulations performed, profile difference = 0.001999 120 | Error (positive) at 0.150 = 0.023722, 4 simulations performed, profile difference = 0.002000 121 | Error (negative) at 0.150 = -0.025324, 4 simulations performed, profile difference = 0.002000 122 | Error (positive) at 0.200 = 0.022054, 3 simulations performed, profile difference = 0.002000 123 | Error (negative) at 0.200 = -0.022245, 3 simulations performed, profile difference = 0.002000 124 | Error (positive) at 0.250 = 0.021606, 3 simulations performed, profile difference = 0.002000 125 | Error (negative) at 0.250 = -0.020141, 3 simulations performed, profile difference = 0.002000 126 | Error (positive) at 0.300 = 0.021791, 4 simulations performed, profile difference = 0.002000 127 | Error (negative) at 0.300 = -0.018476, 3 simulations performed, profile difference = 0.002000 128 | Error (positive) at 0.350 = 0.022895, 4 simulations performed, profile difference = 0.001999 129 | Error (negative) at 0.350 = -0.017589, 4 simulations performed, profile difference = 0.002000 130 | Error (positive) at 0.400 = 0.024581, 5 simulations performed, profile difference = 0.002000 131 | Error (negative) at 0.400 = -0.017005, 3 simulations performed, profile difference = 0.002000 132 | Error (positive) at 0.450 = 0.025304, 4 simulations performed, profile difference = 0.001999 133 | Error (negative) at 0.450 = -0.016211, 4 simulations performed, profile difference = 0.002000 134 | Error (positive) at 0.500 = 0.026098, 4 simulations performed, profile difference = 0.002000 135 | Error (negative) at 0.500 = -0.015648, 3 simulations performed, profile difference = 0.002000 136 | Error (positive) at 0.550 = 0.027747, 4 simulations performed, profile difference = 0.002000 137 | Error (negative) at 0.550 = -0.015897, 3 simulations performed, profile difference = 0.001999 138 | Error (positive) at 0.600 = 0.029100, 4 simulations performed, profile difference = 0.002000 139 | Error (negative) at 0.600 = -0.017100, 3 simulations performed, profile difference = 0.001998 140 | Error (positive) at 0.650 = 0.030079, 4 simulations performed, profile difference = 0.002000 141 | Error (negative) at 0.650 = -0.019340, 4 simulations performed, profile difference = 0.002000 142 | Error (positive) at 0.700 = 0.032644, 4 simulations performed, profile difference = 0.002000 143 | Error (negative) at 0.700 = -0.021295, 4 simulations performed, profile difference = 0.002000 144 | Error (positive) at 0.750 = 0.035065, 4 simulations performed, profile difference = 0.001999 145 | Error (negative) at 0.750 = -0.024158, 3 simulations performed, profile difference = 0.002001 146 | Error (positive) at 0.800 = 0.038058, 4 simulations performed, profile difference = 0.001999 147 | Error (negative) at 0.800 = -0.028804, 4 simulations performed, profile difference = 0.002000 148 | Error (positive) at 0.850 = 0.041325, 5 simulations performed, profile difference = 0.002000 149 | Error (negative) at 0.850 = -0.035934, 3 simulations performed, profile difference = 0.002000 150 | Error (positive) at 0.900 = 0.046970, 5 simulations performed, profile difference = 0.001999 151 | Error (negative) at 0.900 = -0.046637, 3 simulations performed, profile difference = 0.002000 152 | Error (positive) at 0.950 = 0.058243, 5 simulations performed, profile difference = 0.001999 153 | Error (negative) at 0.950 = -0.064287, 4 simulations performed, profile difference = 0.002000 154 | Error (positive) at 1.000 = 0.077021, 5 simulations performed, profile difference = 0.002000 155 | Error (negative) at 1.000 = -0.099025, 4 simulations performed, profile difference = 0.002000 156 | Error analysis complete 157 | 158 | .. image:: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/ErrorAnalysis_files/ErrorAnalysis_3.png 159 | 160 | With more points calculated and high accuracy, the uncertainty of diffusion coefficients are well calculated. 161 | -------------------------------------------------------------------------------- /docs/examples/ErrorAnalysis_files/ErrorAnalysis_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangqi-chen/pyDiffusion/98c500e07226219060e533dba48f5e7372d307fc/docs/examples/ErrorAnalysis_files/ErrorAnalysis_1.png -------------------------------------------------------------------------------- /docs/examples/ErrorAnalysis_files/ErrorAnalysis_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangqi-chen/pyDiffusion/98c500e07226219060e533dba48f5e7372d307fc/docs/examples/ErrorAnalysis_files/ErrorAnalysis_2.png -------------------------------------------------------------------------------- /docs/examples/ErrorAnalysis_files/ErrorAnalysis_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangqi-chen/pyDiffusion/98c500e07226219060e533dba48f5e7372d307fc/docs/examples/ErrorAnalysis_files/ErrorAnalysis_3.png -------------------------------------------------------------------------------- /docs/examples/FSA.rst: -------------------------------------------------------------------------------- 1 | ================================= 2 | Forward Simulation Analysis (FSA) 3 | ================================= 4 | 5 | After **Data Smoothing** (example_) and initial **DC Modeling** (example__) , we can perform **Forward Simulation Analysis (FSA)** to calculate accurate diffusion coefficients from diffusion profile. 6 | The process requires many mannual inputs as smoothing goes on, so please do not close any window during the process. 7 | 8 | .. code-block:: python 9 | 10 | import matplotlib.pyplot as plt 11 | from pydiffusion.io import read_csv, save_csv 12 | from pydiffusion.plot import profileplot, DCplot, SFplot 13 | from pydiffusion.Dtools import FSA 14 | 15 | Read data from previous data smoothing & DC modeling 16 | ---------------------------------------------------- 17 | 18 | Here we read previous results of Ni-Mo 1100C 1000 hours datasets, and plot them out. 19 | 20 | (The ``Xspl`` info required to input manually if initial DC modeling is read from file) 21 | 22 | .. code-block:: python 23 | 24 | NiMo_sm, diffsys_init = read_csv('examples/data/NiMo_DC_init.csv') 25 | NiMo_exp, _ = read_csv('examples/data/NiMo_exp.csv') 26 | Xp = [[.05, .2], 27 | [.5, .515], 28 | [.985]] 29 | diffsys_init.Xspl = Xp 30 | time = 3600*1000 31 | 32 | fig = plt.figure(figsize=(16, 6)) 33 | ax1, ax2 = fig.add_subplot(121), fig.add_subplot(122) 34 | profileplot(NiMo_exp, ax1, ls='none', c='b', marker='o', fillstyle='none') 35 | profileplot(NiMo_sm, ax1, c='g', lw=2, label='NiMo_exp_smoothed') 36 | SFplot(NiMo_sm, time, Xlim=[0, 1], ax=ax2, c='b', label='Sauer-Freise') 37 | DCplot(diffsys_init, ax2, c='r', lw=2) 38 | plt.pause(1.0) 39 | 40 | .. image:: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/FSA_files/FSA_1.png 41 | 42 | Left figure: smoothed profile vs. experimental profile Right figure: initial DC modeling vs. Sauer-Fraise calculation 43 | 44 | Forward simulation analysis (FSA) 45 | --------------------------------- 46 | 47 | **Forward simulation analysis (FSA)** is performed in the following steps: 48 | 49 | 1. Use DC modeling to simulate the diffusion process. 50 | 2. Compare the simulated profile vs. experimental profile, see if the difference between 2 profiles is smaller enough. 51 | 3. If not, compare the simulated profile vs. smoothed profile, adjust the DC modeling using **Point Mode** or **Phase Mode**. 52 | 4. Use the new DC modeling to simulate the diffusion process again... 53 | 54 | **Point Mode**: Can be applied only when reference points in **Spline** functions are provided, i.e. `DiffSystem.Xspl is not None`. Each diffusion coefficient value at reference point is adjusted at first, then **Spline** function is generated depending on adjusted diffusion coefficients. In the logD vs. X figure, **the shape of DC curve might change**. 55 | 56 | **Phase Mode**: Can be applied on both **Spline** and **UnivariateSpline** modeling. The diffusion coefficients inside each phase are adjusted by the phase width comparison or manually input. In the logD vs. X figure, **the shape of DC curve doesn't change**. 57 | 58 | In the `FSA` function, default error is defined by the difference between smoothed profile and experimental profile. The error of each simulation round is defined by the difference between simulated profile and experimental profile. 59 | 60 | The stop criteria of error is the cap for manually adjustment. One can compare the simulation result and experimental profile after each diffusion simulation to make decision of next adjustment. 61 | 62 | * If the error is larger than this cap, the function will adjust the system by **Point Mode** or **Phase Mode** automatically; 63 | * if the error is smaller than this cap, the function will ask to choose between further manually adjustment or exit. Of course, automatically adjustment can also be used when the criteria is reached. 64 | 65 | In this example, automatic **Phase Mode** will be used. 66 | 67 | .. code-block:: python 68 | 69 | NiMo_sim, diffsys_fsa = FSA(NiMo_exp, NiMo_sm, diffsys_init, time, Xlim=[0, 1], n=[250, 300]) 70 | 71 | Outputs: 72 | 73 | .. code-block:: 74 | 75 | Meshing Num=279, Minimum grid=0.525563 um 76 | 77 | Default error = 0.000995 78 | Input the stop criteria of error: [0.001991] 79 | .0016 80 | 81 | Use Phase Mode? [n] 82 | (The shape of diffusivity curve does not change) 83 | y 84 | 653.080/1000.000 hrs simulated 85 | Simulation Complete 86 | Simulation 1, error = 0.002112(0.001600) 87 | Simulation Complete 88 | Simulation 2, error = 0.002396(0.001600) 89 | Simulation Complete 90 | Simulation 3, error = 0.001930(0.001600) 91 | Simulation Complete 92 | Simulation 4, error = 0.001585(0.001600) 93 | 94 | Satisfied with FSA? [n] 95 | 96 | Use Point Mode (y) or Phase Mode (n)? [y]n 97 | 98 | Phase Mode 99 | Manually input for each phase? [n] 100 | 925.701/1000.000 hrs simulated 101 | Simulation Complete 102 | Simulation 5, error = 0.001418(0.001600) 103 | 104 | Satisfied with FSA? [n]y 105 | 106 | FSA results 107 | ----------- 108 | 109 | .. code-block:: python 110 | 111 | fig = plt.figure(figsize=(16, 6)) 112 | ax1, ax2 = fig.add_subplot(121), fig.add_subplot(122) 113 | profileplot(NiMo_exp, ax1, ls='none', c='b', marker='o', fillstyle='none') 114 | profileplot(NiMo_sm, ax1, c='g', lw=2, label='NiMo_exp_smoothed') 115 | profileplot(NiMo_sim, ax1, c='r', lw=2, label='FSA simulated') 116 | SFplot(NiMo_sm, time, Xlim=[0, 1], ax=ax2, c='b', label='Sauer-Freise') 117 | DCplot(diffsys_fsa, ax2, c='r', lw=2, label='FSA') 118 | 119 | .. image:: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/FSA_files/FSA_2.png 120 | 121 | Save FSA results 122 | ---------------- 123 | 124 | Usually FSA results are saved by combining DC data with simulated profile data. 125 | 126 | .. code-block:: python 127 | 128 | save_csv('examples/NiMo.csv', NiMo_sim, diffsys_fsa) 129 | 130 | Congratulations! Now you can perform forward simulation analysis based on raw diffusion data! 131 | 132 | .. _example: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/DataSmooth.rst 133 | .. __: https://github.com/zhangqi-chen/pyDiffusion/blob/master/docs/examples/DCModeling.rst -------------------------------------------------------------------------------- /docs/examples/FSA_files/FSA_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangqi-chen/pyDiffusion/98c500e07226219060e533dba48f5e7372d307fc/docs/examples/FSA_files/FSA_1.png -------------------------------------------------------------------------------- /docs/examples/FSA_files/FSA_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangqi-chen/pyDiffusion/98c500e07226219060e533dba48f5e7372d307fc/docs/examples/FSA_files/FSA_2.png -------------------------------------------------------------------------------- /examples/DCModeling.py: -------------------------------------------------------------------------------- 1 | from pydiffusion.io import read_csv, save_csv 2 | from pydiffusion.plot import profileplot, DCplot, SFplot 3 | from pydiffusion.Dtools import Dmodel 4 | 5 | # Read smoothed NiMo data 6 | NiMo_sm, _ = read_csv('examples/data/NiMo_sm.csv') 7 | profileplot(NiMo_sm) 8 | 9 | # DC modeling 10 | time = 1000 * 3600 11 | diffsys_init = Dmodel(NiMo_sm, time, Xlim=[0, 1]) 12 | 13 | # Plot results 14 | ax = SFplot(NiMo_sm, time, Xlim=[0, 1]) 15 | DCplot(diffsys_init, ax) 16 | 17 | # DC modeling automatically 18 | Xspl = [[.05, .2], 19 | [.5, .515], 20 | [.985]] 21 | diffsys_init_auto = Dmodel(NiMo_sm, time, Xspl=Xspl, Xlim=[0, 1]) 22 | 23 | # Save result 24 | save_csv('examples/data/NiMo_DC_init.csv', profile=NiMo_sm, diffsys=diffsys_init_auto) 25 | -------------------------------------------------------------------------------- /examples/DataSmooth.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from pydiffusion.core import DiffProfile 3 | from pydiffusion.io import read_csv, save_csv 4 | from pydiffusion.plot import profileplot 5 | from pydiffusion.smooth import datasmooth 6 | 7 | # Read raw data 8 | data = pd.read_csv('examples/data/NiMo_exp.csv') 9 | dis, X = data['dis'], data['X'] 10 | NiMo_exp = DiffProfile(dis=dis, X=X, name='NiMo_exp') 11 | 12 | # Another way to read profile data, .csv must be created by pydiffusion.io.save_csv 13 | NiMo_exp, _ = read_csv('examples/data/NiMo_exp.csv') 14 | 15 | ax = profileplot(NiMo_exp, c='b', marker='o', ls='none', fillstyle='none') 16 | 17 | # Data smoothing 18 | NiMo_sm = datasmooth(NiMo_exp, [311.5, 340.5], n=500) 19 | 20 | # Plot result 21 | profileplot(NiMo_sm, ax, c='r') 22 | 23 | # Save result 24 | save_csv('examples/data/NiMo_sm.csv', profile=NiMo_sm) 25 | -------------------------------------------------------------------------------- /examples/DiffusionSimulation.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | import pandas as pd 4 | from pydiffusion.core import DiffSystem 5 | from pydiffusion.utils import step, mesh 6 | from pydiffusion.simulation import mphSim 7 | from pydiffusion.plot import profileplot, DCplot 8 | from pydiffusion.io import read_csv, save_csv 9 | 10 | # Create diffusion system with constant DC 11 | diffsys = DiffSystem(Xr=[0, 1], X=[0, 1], DC=[1e-14, 1e-14], name='Constant D') 12 | 13 | # Create initial step profile 14 | dis = mesh(0, 1000, 501) 15 | profile_init = step(dis, 500, diffsys, name='Intitial step profile') 16 | 17 | fig = plt.figure(figsize=(16, 6)) 18 | ax1, ax2 = fig.add_subplot(121), fig.add_subplot(122) 19 | ax1.set_title('Diffusion Coefficients', fontsize=15) 20 | ax2.set_title('Initial Step Profile', fontsize=15) 21 | DCplot(diffsys, ax1) 22 | profileplot(profile_init, ax2) 23 | 24 | # Diffusion simulation using the setups 25 | time = 200 * 3600 26 | profile_final = mphSim(profile_init, diffsys, time) 27 | 28 | ax = profileplot(profile_init, ls='--') 29 | profileplot(profile_final, ax, c='r') 30 | 31 | # Read diffusion coefficients data of Ni-Mo system 32 | data = pd.read_csv('examples/data/NiMo.csv') 33 | X, DC = data['X'], data['DC'] 34 | Xr = np.array([[0, 0.25], 35 | [0.49, 0.53], 36 | [0.97, 1]]) 37 | diffsys_NiMo = DiffSystem(Xr=Xr, X=X, DC=DC) 38 | 39 | # Read diffusion coefficients from saved data file by pydiffusion.io.save_csv 40 | _, diffsys_NiMo = read_csv('examples/data/NiMo.csv', [0, 1]) 41 | 42 | # Create initial step profile 43 | dis = mesh(0, 400, 301) 44 | profile_NiMo_init = step(dis, 300, diffsys_NiMo) 45 | 46 | # Perform simulation 47 | profile_NiMo = mphSim(profile_NiMo_init, diffsys_NiMo, 800*3600) 48 | 49 | # Plot results 50 | fig = plt.figure(figsize=(16, 6)) 51 | ax1, ax2 = fig.add_subplot(121), fig.add_subplot(122) 52 | ax1.set_title('Diffusion Coefficients of Ni-Mo at 1100C', fontsize=15) 53 | ax2.set_title('Diffusion Simulation for 800 hours', fontsize=15) 54 | DCplot(diffsys_NiMo, ax1) 55 | profileplot(profile_NiMo_init, ax2, ls='--') 56 | profileplot(profile_NiMo, ax2, c='r') 57 | 58 | # Save results 59 | save_csv('examples/data/NiMo_800h.csv', profile=profile_NiMo, diffsys=diffsys_NiMo) 60 | -------------------------------------------------------------------------------- /examples/ErrorAnalysis.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | from pydiffusion.io import read_csv 3 | from pydiffusion.utils import step, automesh, matanocalc, DCbias 4 | from pydiffusion.simulation import ErrorAnalysis 5 | from pydiffusion.plot import DCplot, profileplot 6 | 7 | # Read data, create bias 8 | profile_fsa, diffsys_TiZr = read_csv('examples/data/TiZr.csv', [0, 1]) 9 | profile_exp, _ = read_csv('examples/data/TiZr_exp.csv') 10 | diffsys_bias = DCbias(diffsys_TiZr, 0.2, 0.1) 11 | 12 | ax = DCplot(diffsys_TiZr, label='original') 13 | DCplot(diffsys_bias, ax, c='r', ls='--', label='bias') 14 | plt.pause(1.0) 15 | 16 | # Error analysis with low accuracy 17 | dism = automesh(profile_fsa, diffsys_TiZr, [300, 350]) 18 | mp = matanocalc(profile_fsa, [0, 1]) 19 | profile_init = step(dism, mp, diffsys_TiZr) 20 | time = 100*3600 21 | error_result = ErrorAnalysis(profile_exp, profile_init, diffsys_TiZr, time, loc=3, accuracy=1e-2, output=True) 22 | 23 | fig = plt.figure(figsize=(16, 6)) 24 | ax1, ax2 = fig.add_subplot(121), fig.add_subplot(122) 25 | DCplot(diffsys_TiZr, ax1, error_result) 26 | profileplot(profile_fsa, ax2, error_result) 27 | profileplot(profile_exp, ax2, marker='o', ls='none', fillstyle='none') 28 | plt.pause(1.0) 29 | 30 | # Error analysis with high accuracy 31 | error_result2 = ErrorAnalysis(profile_exp, profile_init, diffsys_TiZr, time, loc=21, accuracy=1e-3, output=False) 32 | 33 | fig = plt.figure(figsize=(16, 6)) 34 | ax1, ax2 = fig.add_subplot(121), fig.add_subplot(122) 35 | DCplot(diffsys_TiZr, ax1, error_result2) 36 | profileplot(profile_fsa, ax2, error_result2) 37 | profileplot(profile_exp, ax2, marker='o', ls='none', fillstyle='none') 38 | -------------------------------------------------------------------------------- /examples/ForwardSimulationAnalysis.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | from pydiffusion.io import read_csv, save_csv 3 | from pydiffusion.plot import profileplot, DCplot, SFplot 4 | from pydiffusion.Dtools import FSA 5 | 6 | # Read required data for FSA 7 | NiMo_sm, diffsys_init = read_csv('examples/data/NiMo_DC_init.csv') 8 | NiMo_exp, _ = read_csv('examples/data/NiMo_exp.csv') 9 | Xp = [[.05, .2], 10 | [.5, .515], 11 | [.985]] 12 | diffsys_init.Xspl = Xp 13 | time = 3600*1000 14 | 15 | fig = plt.figure(figsize=(16, 6)) 16 | ax1, ax2 = fig.add_subplot(121), fig.add_subplot(122) 17 | profileplot(NiMo_exp, ax1, ls='none', c='b', marker='o', fillstyle='none') 18 | profileplot(NiMo_sm, ax1, c='g', lw=2, label='NiMo_exp_smoothed') 19 | SFplot(NiMo_sm, time, Xlim=[0, 1], ax=ax2, c='b', label='Sauer-Freise') 20 | DCplot(diffsys_init, ax2, c='r', lw=2) 21 | plt.pause(1.0) 22 | 23 | # FSA 24 | NiMo_sim, diffsys_fsa = FSA(NiMo_exp, NiMo_sm, diffsys_init, time, Xlim=[0, 1], n=[250, 300]) 25 | 26 | # Plot the results 27 | fig = plt.figure(figsize=(16, 6)) 28 | ax1, ax2 = fig.add_subplot(121), fig.add_subplot(122) 29 | profileplot(NiMo_exp, ax1, ls='none', c='b', marker='o', fillstyle='none') 30 | profileplot(NiMo_sm, ax1, c='g', lw=2, label='NiMo_exp_smoothed') 31 | profileplot(NiMo_sim, ax1, c='r', lw=2, label='FSA simulated') 32 | SFplot(NiMo_sm, time, Xlim=[0, 1], ax=ax2, c='b', label='Sauer-Freise') 33 | DCplot(diffsys_fsa, ax2, c='r', lw=2, label='FSA') 34 | 35 | # Save FSA result 36 | save_csv('examples/data/NiMo.csv', NiMo_sim, diffsys_fsa) 37 | -------------------------------------------------------------------------------- /examples/data/NiMo.csv: -------------------------------------------------------------------------------- 1 | DC,X,dis 2 | 2.0634219723113013e-15,0.005278752067613904,0.0 3 | 2.063415215335891e-15,0.005283541942207487,1.3874582983101846 4 | 2.0634016986724213e-15,0.005293123661418702,2.7749067646547885 5 | 2.0633814168008106e-15,0.0053075012325389526,4.162345399173163 6 | 2.0633543614180456e-15,0.005326680683031343,5.549772086207811 7 | 2.0633205216305913e-15,0.005350669924297089,6.937177450342355 8 | 2.063279883630434e-15,0.00537947898189069,8.324560801893828 9 | 2.0632324308410383e-15,0.005413119892392617,9.711920479722483 10 | 2.063178144018512e-15,0.005451606632331866,11.099252133519855 11 | 2.063117001006903e-15,0.005494955292407044,12.486555557657086 12 | 2.0630489769118135e-15,0.0055431839551108395,13.873829477835024 13 | 2.0629740441364672e-15,0.005596312670122612,15.261071534207856 14 | 2.062892172206241e-15,0.005654363579647684,16.6482817282393 15 | 2.0628033280271473e-15,0.005717360736284662,18.035457806151207 16 | 2.0627074758149474e-15,0.005785330154457137,19.42259719916898 17 | 2.0626045768935715e-15,0.0058582999546870984,20.809699918977618 18 | 2.0624945897523313e-15,0.00593630032443408,22.196767215884826 19 | 2.062377470164502e-15,0.006019363435495035,23.583800005516373 20 | 2.0622531712637115e-15,0.006107523391548056,24.97079828958917 21 | 2.062121643798545e-15,0.006200816049135376,26.35775894139087 22 | 2.0619828357265515e-15,0.006299279307539609,27.744680621820425 23 | 2.0618366922334153e-15,0.006402953097112359,29.131563333067852 24 | 2.0616831562305436e-15,0.0065118790283303345,30.518403878443365 25 | 2.0615221677862922e-15,0.0066261007974017195,31.90520153877917 26 | 2.0613536643438243e-15,0.006745664034031776,33.29195594897575 27 | 2.0611775811313292e-15,0.006870616012720548,34.67866402528322 28 | 2.0609938505618288e-15,0.007001006081043786,36.06532512266553 29 | 2.060802402518219e-15,0.007136885460070408,37.451938700852715 30 | 2.0606031647606744e-15,0.0072783069578425,38.838501687989876 31 | 2.0603960622248004e-15,0.007425325470389483,40.22501373161484 32 | 2.0601810174397473e-15,0.007577997687887656,41.6114742295321 33 | 2.059957950934547e-15,0.00773638180919279,42.99788005801554 34 | 2.0597267803817517e-15,0.007900538152995146,44.38423122137636 35 | 2.059487421310501e-15,0.008070528654811324,45.770526436195844 36 | 2.0592397873005724e-15,0.00824641673253359,47.15676243978488 37 | 2.05898378909275e-15,0.008428267921738623,48.542939217440015 38 | 2.058719335549802e-15,0.008616149196830165,49.92905470509629 39 | 2.058446333559131e-15,0.00881012904373526,51.315105756864895 40 | 2.058164687231985e-15,0.009010278032842385,52.70109236130059 41 | 2.0578742990512584e-15,0.009216668007376055,54.08701194970193 42 | 2.0575750694159446e-15,0.009429372411174244,55.47286201358889 43 | 2.0572668961236848e-15,0.009648466660696225,56.858642559936584 44 | 2.0569496756631465e-15,0.009874027230772077,58.24435036032836 45 | 2.0566233022888757e-15,0.010106132316498408,59.62998343634211 46 | 2.056287667880206e-15,0.010344861938498703,61.015541635882165 47 | 2.0559426632577395e-15,0.010590297011118667,62.401021337849116 48 | 2.055588176918906e-15,0.010842520246581283,63.78642095216956 49 | 2.055224095162406e-15,0.011101616071649348,65.17174035838343 50 | 2.054850303600402e-15,0.011367669556436422,66.55697534872762 51 | 2.0544666852853955e-15,0.011640767752581577,67.94212498794795 52 | 2.054073121339347e-15,0.01192099925107543,69.32718918743298 53 | 2.0536694926691884e-15,0.012208452965924252,70.71216294797277 54 | 2.053255677294695e-15,0.012503220042412775,72.09704595519229 55 | 2.052831551850441e-15,0.012805392793379987,73.48183733946534 56 | 2.0523969925131256e-15,0.013115064044084144,74.86653298167369 57 | 2.0519518729383067e-15,0.013432328608064316,76.25113239967195 58 | 2.0514960656719882e-15,0.013757282287483867,77.63563425324017 59 | 2.051029443077661e-15,0.014090021217894448,79.02003388177546 60 | 2.050551874810924e-15,0.01443064367500927,80.40433108021449 61 | 2.0500632299302803e-15,0.014779248576120289,81.78852385011325 62 | 2.0495633774184025e-15,0.015135935114014138,83.17260732766687 63 | 2.0490521834666536e-15,0.01550080470091274,84.5565815301332 64 | 2.0485295141832125e-15,0.01587395904301335,85.94044389420824 65 | 2.0479952352438703e-15,0.016255500395726617,87.32419060243711 66 | 2.04744920997777e-15,0.016645532937449577,88.70782167415041 67 | 2.046891302055659e-15,0.01704416085688529,90.09133412138122 68 | 2.046321374374729e-15,0.017451489155997,91.47472509368362 69 | 2.0457392879559558e-15,0.017867624445352684,92.85799461238761 70 | 2.0451449047750072e-15,0.01829267292787839,94.24113890864886 71 | 2.0445380856668894e-15,0.01872674190337557,95.62415568517476 72 | 2.0439186899440537e-15,0.019169940049698245,97.00704496547716 73 | 2.0432865786400175e-15,0.019622375109246238,98.38980198612575 74 | 2.0426416111319406e-15,0.020084156312427208,99.77242498620694 75 | 2.0419836458605915e-15,0.020555393870867685,101.15491356628975 76 | 2.0413125432724853e-15,0.021036196877420775,102.53726269952338 77 | 2.0406281620686668e-15,0.0215266759998854,103.91947082567893 78 | 2.0399303602405217e-15,0.022026942747859848,105.30153763621595 79 | 2.0392189986576627e-15,0.022537106907360053,106.6834571924811 80 | 2.0384939356751196e-15,0.023057280414578777,108.06522877778268 81 | 2.0377550296720913e-15,0.023587575544356785,109.44685185280778 82 | 2.037002142119132e-15,0.024128102715541073,110.82832060012201 83 | 2.0362351318327505e-15,0.024678974621999927,112.20963479850545 84 | 2.0354538586698895e-15,0.025240303589281202,113.59079298319004 85 | 2.034658184995544e-15,0.025812200526198113,114.9717901666285 86 | 2.033847971150391e-15,0.0263947781892142,116.35262594428801 87 | 2.0330230788799105e-15,0.026988148726397737,117.733298535315 88 | 2.0321833727804775e-15,0.027592422642954546,119.11380267681099 89 | 2.0313287150834643e-15,0.028207712561320306,120.49413840987572 90 | 2.0304589706207926e-15,0.028834129656103952,121.87430294088331 91 | 2.0295740066831957e-15,0.029471783760976315,123.25429122974906 92 | 2.0286736884056518e-15,0.0301207867006174,124.63410332147961 93 | 2.02775788491297e-15,0.030781247869791657,126.01373486405325 94 | 2.0268264671843043e-15,0.031453275776910844,127.39318073517846 95 | 2.0258793040693416e-15,0.03213698092608786,128.77244098482566 96 | 2.0249162698079617e-15,0.03283247039773541,130.15150985249912 97 | 2.0239372394002405e-15,0.033539851194444144,131.53038282797976 98 | 2.0229420862087397e-15,0.03425923198227253,132.90905996691336 99 | 2.0219306900268055e-15,0.034990717265769904,134.2875347330437 100 | 2.0209029302055973e-15,0.035734412360204706,135.66580378144678 101 | 2.01985868553285e-15,0.03649042348897096,137.0438669821008 102 | 2.0187978421277002e-15,0.03725885207364315,138.42171710629992 103 | 2.017720284702453e-15,0.038039801063726036,139.79935157286468 104 | 2.0166258984400514e-15,0.03883337358660445,141.17676985785704 105 | 2.0155145762735436e-15,0.039639667672792644,142.55396449183706 106 | 2.014386209160291e-15,0.040458783311659904,143.93093346995576 107 | 2.013240688666448e-15,0.04129082058667714,145.30767686615226 108 | 2.0120779173263324e-15,0.04213587215227849,146.68418460715654 109 | 2.0108977923019316e-15,0.0429940351056785,148.0604562378821 110 | 2.0097002149099825e-15,0.04386540407300292,149.4364894542719 111 | 2.008485100131088e-15,0.04475006428404766,150.81226964483866 112 | 2.0072523545260496e-15,0.04564810764333959,152.18779533770712 113 | 2.0060018860691427e-15,0.046559625587145013,153.5630666270203 114 | 2.004733605584527e-15,0.04748470804101148,154.93808360686913 115 | 2.0034474268293695e-15,0.0484234433611759,156.3128463712922 116 | 2.002143270605476e-15,0.04937591533246342,157.68735079761603 117 | 2.0008210708109248e-15,0.05034219873578791,159.0615826754875 118 | 1.999480751229203e-15,0.05132237630415501,160.435542112977 119 | 1.9981222427138217e-15,0.0523165261752807,161.8092253990072 120 | 1.9967454841165385e-15,0.05332472120202498,163.18262441932944 121 | 1.9953504097455477e-15,0.05434703813364217,164.55573929157558 122 | 1.9939369620289885e-15,0.0553835483451793,165.92856568582906 123 | 1.9925050893311523e-15,0.0564343194269846,167.30109726854806 124 | 1.9910547371918597e-15,0.05749942161207434,168.6733341421975 125 | 1.9895858603065813e-15,0.058578918983036916,170.04527139007217 126 | 1.9880984176728438e-15,0.05967287302504424,171.41690391054945 127 | 1.986592366687964e-15,0.0607813469662933,172.78823183897566 128 | 1.985067675819686e-15,0.06190439645239613,174.15924896786737 129 | 1.983524316055102e-15,0.06304207582710841,175.52995074817153 130 | 1.981962257554787e-15,0.06419444060011509,176.90033717037574 131 | 1.9803814824783475e-15,0.06536153798213372,178.27040150444043 132 | 1.9787819741299872e-15,0.06654341487855306,179.6401399983769 133 | 1.9771637157157908e-15,0.06774011881038447,181.0095528071455 134 | 1.975526704516477e-15,0.06895168742564574,182.37863203715534 135 | 1.9738709364110376e-15,0.07017815992937007,183.74737498339434 136 | 1.9721964086670103e-15,0.07141957502247145,185.11578161949365 137 | 1.9705031349272045e-15,0.0726759597768023,186.48384245718333 138 | 1.968791124960416e-15,0.07394734463063413,187.85155520860238 139 | 1.9670603915149052e-15,0.07523375830785363,189.21891905181843 140 | 1.965310962644925e-15,0.07653521863711533,190.58592501637193 141 | 1.963542862889067e-15,0.07785174652452925,191.95257093465946 142 | 1.9617561191128195e-15,0.07918336161107034,193.31885667685538 143 | 1.9599507778168295e-15,0.0805300693496423,194.68477105670198 144 | 1.958126876831828e-15,0.08189188208252131,196.05031352500666 145 | 1.95628446014022e-15,0.08326880799031504,197.41548273235966 146 | 1.9544235889894684e-15,0.08466084275373587,198.78026849427175 147 | 1.9525443174050547e-15,0.08606798783626886,200.14466995478045 148 | 1.950646706499998e-15,0.08749023977957425,201.50868491864665 149 | 1.948730832983088e-15,0.08892758379105935,202.87230391916992 150 | 1.9467967669590183e-15,0.09038001036485879,204.23552616693766 151 | 1.9448445859560626e-15,0.09184750475462633,205.59834941340358 152 | 1.942874386570812e-15,0.09333003817702155,206.96076202789897 153 | 1.9408862539573623e-15,0.09482759073020784,208.32276425782283 154 | 1.9388802849672102e-15,0.09634013397790588,209.68435206684975 155 | 1.936856593589827e-15,0.09786762679201108,211.04551370309036 156 | 1.934815282267814e-15,0.09941003696650193,212.40624909247424 157 | 1.932756466891176e-15,0.10096732234325033,213.7665528948167 158 | 1.930680279422242e-15,0.10253942876463794,215.12641303180598 159 | 1.9285868390925487e-15,0.10412631184777661,216.48582960583641 160 | 1.9264762810286683e-15,0.10572791529625097,217.84479603803925 161 | 1.9243487547840375e-15,0.10734417194400923,219.20330028483284 162 | 1.922204396436537e-15,0.10897502492003303,220.5613426605157 163 | 1.9200433600841767e-15,0.11062040370432574,221.9189156332162 164 | 1.91786581097624e-15,0.1122802292819407,223.2760083996982 165 | 1.9156719023097266e-15,0.11395443180304946,224.63262129959213 166 | 1.913461804874536e-15,0.1156429279652273,225.98874726136538 167 | 1.911235697027078e-15,0.11734562860026594,227.344378159466 168 | 1.9089937488949017e-15,0.11906245073334039,228.6995143552514 169 | 1.9067361474957526e-15,0.1207932983404517,230.05414935962446 170 | 1.904463084844981e-15,0.12253807139557774,231.4082771277538 171 | 1.902174747792706e-15,0.1242966736514942,232.7618980189163 172 | 1.8998713409676862e-15,0.12606899498797958,234.11500512869128 173 | 1.8975530719491874e-15,0.12785492274732838,235.4675931421653 174 | 1.8952201440014698e-15,0.129654347315496,236.8196624574884 175 | 1.892872779260467e-15,0.1314671442125951,238.17120567283732 176 | 1.890511200637387e-15,0.13329318799104647,239.5222181353267 177 | 1.888135627526005e-15,0.13513235554349573,240.87270026166746 178 | 1.885746299118343e-15,0.13698450802103876,242.22264422520783 179 | 1.883343453388712e-15,0.13884950704403465,243.57204594555998 180 | 1.880927325882602e-15,0.14072721563495388,244.92090562136346 181 | 1.8784981708336583e-15,0.14261748177266007,246.26921589052043 182 | 1.8760562409769504e-15,0.14452015402221627,247.61697312342753 183 | 1.8736017866552822e-15,0.14643508222408103,248.96417777375694 184 | 1.871135075113762e-15,0.14836210240750156,250.31082373005938 185 | 1.8686563713059972e-15,0.15030105171139344,251.65690885006472 186 | 1.866165940354526e-15,0.15225176645086616,253.0024334940691 187 | 1.8636640586169817e-15,0.1542140734143826,254.34739449144269 188 | 1.861151003001876e-15,0.15618779818449582,255.6917908905985 189 | 1.8586270523596768e-15,0.15817276402283784,257.03562312660637 190 | 1.856092492896198e-15,0.16016878757630149,258.3788900865716 191 | 1.8535476132148338e-15,0.16217568273232502,259.72159171313194 192 | 1.8509927051874216e-15,0.1641932598998289,261.0637284817369 193 | 1.8484280674320876e-15,0.1662213232214032,262.40529953041533 194 | 1.8458540007972925e-15,0.1682596740966767,263.74630499003405 195 | 1.843270809944359e-15,0.1703081098959213,265.08674509823567 196 | 1.8406788089262944e-15,0.17236641948709372,266.42661727166376 197 | 1.8380783118203624e-15,0.1744343906191466,267.76592092416996 198 | 1.835469634361232e-15,0.17651180860149052,269.1046565422509 199 | 1.832853105976097e-15,0.17859844667121255,270.44281947056527 200 | 1.8302290518182663e-15,0.18069408025479478,271.78040908632283 201 | 1.8275977988939215e-15,0.18279848206986712,273.11742556936264 202 | 1.824959686027512e-15,0.18491141410828385,274.4538647939959 203 | 1.8223150487920186e-15,0.1870326396447368,275.78972593544034 204 | 1.8196642234937893e-15,0.1891619200332999,277.1250093586423 205 | 1.8170075529274206e-15,0.19129901005167776,278.4597136565453 206 | 1.8143453796366724e-15,0.19344366327725462,279.7938389860052 207 | 1.8116780478519363e-15,0.19559563050570725,281.12738602045897 208 | 1.8090058968050437e-15,0.19775466512801462,282.46035921454376 209 | 1.8063292743698453e-15,0.19992051209651554,283.79275992146864 210 | 1.803648532074106e-15,0.20209291190055165,285.12458884102523 211 | 1.8009640239067086e-15,0.20427160149539847,286.45584655426586 212 | 1.7982761078308893e-15,0.2064563130388513,287.7865327121826 213 | 1.7955851440550386e-15,0.20864677525831193,289.11664683197785 214 | 1.792891487820226e-15,0.210842719304041,290.44619178855294 215 | 1.790195501528824e-15,0.21304386885282253,291.7751677512475 216 | 1.787497551308148e-15,0.21524994285946591,293.10357382548386 217 | 1.784798010898413e-15,0.21746065233127482,294.4314060780509 218 | 1.7820972385603186e-15,0.2196757192185711,295.7586688223698 219 | 1.7793955899100818e-15,0.22189486594459418,297.0853682145146 220 | 1.776693429792355e-15,0.22411780563260078,298.4115063582717 221 | 1.7739911166472004e-15,0.2263442549236739,299.73708891717257 222 | 1.7712890017481876e-15,0.22857393463222736,301.0621254143093 223 | 1.768587448753715e-15,0.23080655360782035,302.3866195805786 224 | 1.7658868674424048e-15,0.2330417807005809,303.710552756761 225 | 1.763187652511204e-15,0.23527929522852914,305.03391381629064 226 | 1.7604902176891206e-15,0.2375187587161383,306.3566823455533 227 | 1.7577950300007662e-15,0.239759786249284,307.6788117238022 228 | 1.7551025386098159e-15,0.24200200542500655,309.0002638784737 229 | 1.7524130121469426e-15,0.24424519223294144,310.3210890885087 230 | 1.7497271123432619e-15,0.24648879266229776,311.6411441905301 231 | 1.748804614925369e-15,0.2472601728729064,312.094977517592 232 | 2.9876975301753855e-15,0.4920843247025656,312.094977517592 233 | 2.9362239349125495e-15,0.4930789484402489,313.20084234165137 234 | 2.864760103961383e-15,0.49448914377420566,314.7373080329273 235 | 2.7939284847173502e-15,0.4959220136035264,316.2615078145163 236 | 2.7236057757912366e-15,0.49738098490696203,317.7762733689552 237 | 2.6538286354236304e-15,0.49886635645286326,319.28099890041784 238 | 2.5846542311306226e-15,0.5003779597262447,320.774634811123 239 | 2.5161171747322623e-15,0.5019160732973971,322.2566154793029 240 | 2.4482510031660444e-15,0.5034809823509221,323.7263962907317 241 | 2.3810543898556052e-15,0.5050737903356266,325.1841866957659 242 | 2.3145301066541452e-15,0.5066955716094407,326.63010927016876 243 | 2.2487068278661182e-15,0.5083468081681383,328.063723888056 244 | 2.1836167266118365e-15,0.510027884341096,329.484511103453 245 | 2.1192608312516997e-15,0.5117400051828637,330.8926293937315 246 | 2.0556275272593137e-15,0.5134848062582867,332.2885170163133 247 | 1.99274154166529e-15,0.5152630094570325,333.67181748398707 248 | 1.9306307585609263e-15,0.5170752525237526,335.0421015274261 249 | 1.8693343328383578e-15,0.518921823031706,336.39868553082437 250 | 1.8089096569272827e-15,0.5208023758943802,337.74047145891814 251 | 1.7493873992197908e-15,0.5227172947952288,339.06694162611154 252 | 1.6906434372000768e-15,0.5246721554136169,340.38103777695045 253 | 1.6521212011928014e-15,0.5259913157763974,341.24628597885334 254 | 5.507205215492017e-17,0.9726784717982,341.24628597885334 255 | 5.5072052154920563e-17,0.9740457906950037,341.68916769807873 256 | 5.507205215491978e-17,0.9755290490651299,342.2147308022542 257 | 5.507205215492017e-17,0.9769351419735458,342.7402939062276 258 | 5.507205215492017e-17,0.9782666033814531,343.26585701016796 259 | 5.507205215491978e-17,0.9795258326088901,343.7914201141121 260 | 5.507205215492017e-17,0.9807151189109254,344.31698321805806 261 | 5.507205215492017e-17,0.981836660182031,344.8425463220029 262 | 5.507205215492017e-17,0.9828925763461402,345.3681094259477 263 | 5.507205215492017e-17,0.9838849180765359,345.89367252989257 264 | 5.507205215491978e-17,0.9848156715493603,346.41923563383745 265 | 5.507205215492017e-17,0.9856867599603838,346.9447987377823 266 | 5.507205215492017e-17,0.9865000425251924,347.4703618417272 267 | 5.507205215492017e-17,0.9872573116406712,347.9959249456721 268 | 5.507205215492017e-17,0.9879602888166479,348.52148804961695 269 | 5.507205215492017e-17,0.9886106198988458,349.0470511535618 270 | 5.507205215492017e-17,0.9892098700066947,349.5726142575067 271 | 5.507205215492017e-17,0.9897595185104427,350.0981773614516 272 | 5.507205215492017e-17,0.9902609542785217,350.62374046539645 273 | 5.507205215492017e-17,0.9907154713434722,351.1493035693413 274 | 5.507205215492017e-17,0.9911242650662031,351.6748666732862 275 | 5.507205215492017e-17,0.9914884288252064,352.2004297772311 276 | 5.507205215492017e-17,0.9918089512192512,352.72599288117596 277 | 5.507205215492017e-17,0.9920867137474985,353.25155598512083 278 | 5.507205215492017e-17,0.9923224889176456,353.7771190890657 279 | 5.507205215492017e-17,0.9925169387280568,354.3026821930106 280 | 5.507205215492017e-17,0.9926706134713316,354.82824529695546 281 | 5.507205215492017e-17,0.9927839508122047,355.35380840090033 282 | 5.507205215492017e-17,0.9928572751002545,355.8793715048452 283 | 5.507205215492017e-17,0.9928907968863604,356.4049346087901 284 | 5.507205215492017e-17,0.9928928883049148,356.5255631039449 285 | -------------------------------------------------------------------------------- /examples/data/NiMo_exp.csv: -------------------------------------------------------------------------------- 1 | X,dis 2 | 0.0025311643355943737,0.0 3 | 0.002622412619610042,4.0 4 | 0.0026209959784718197,8.0 5 | 0.0027810846230029713,12.0 6 | 0.003733210561082531,16.0 7 | 0.0037430693168398086,20.0 8 | 0.003372158181235991,24.0 9 | 0.004613783302307893,28.0 10 | 0.004253445290685455,32.0 11 | 0.005131847465087431,36.0 12 | 0.0058549767302206865,40.0 13 | 0.006174198454949365,44.0 14 | 0.006275962164055853,48.0 15 | 0.007505704335294824,52.0 16 | 0.008594211163470101,56.0 17 | 0.008425476559763846,60.0 18 | 0.009814809256535703,64.0 19 | 0.010198668868538256,68.0 20 | 0.01187187187187187,72.0 21 | 0.012317143943487223,76.0 22 | 0.012862476602304235,80.0 23 | 0.014103257061637937,84.0 24 | 0.01585220619877305,88.0 25 | 0.01711249211924705,92.0 26 | 0.018537054089762584,96.0 27 | 0.019467008122924248,100.0 28 | 0.020335658457011897,104.0 29 | 0.0227252268021645,108.0 30 | 0.023603690140281363,112.0 31 | 0.02652432313449262,116.0 32 | 0.027265725802418504,120.0 33 | 0.028628322126160736,124.0 34 | 0.030850489166316546,128.0 35 | 0.03308727920142432,132.0 36 | 0.03530942018109961,136.0 37 | 0.037220098853380824,140.0 38 | 0.03943287106521782,144.0 39 | 0.040475380644644975,148.0 40 | 0.046927977756218556,156.0 41 | 0.04967887797362999,160.0 42 | 0.05310451793665883,164.0 43 | 0.05703159412165139,168.0 44 | 0.05850526547389264,172.0 45 | 0.06333900170051017,176.0 46 | 0.06555097631241998,180.0 47 | 0.06829639113387213,184.0 48 | 0.07228096147882884,188.0 49 | 0.07669218069938466,192.0 50 | 0.07982357000690118,196.0 51 | 0.08482999999999999,200.0 52 | 0.08898,204.0 53 | 0.09290065721688157,208.0 54 | 0.09693137362802288,212.0 55 | 0.1016589621980749,216.0 56 | 0.10477086063564793,220.0 57 | 0.11045528666753368,224.0 58 | 0.11708,228.0 59 | 0.1221267099640745,232.0 60 | 0.12721,236.0 61 | 0.13309186960958916,240.0 62 | 0.13912000000000005,244.0 63 | 0.14722000000000002,248.0 64 | 0.1537630752615052,252.0 65 | 0.16117805890294515,256.0 66 | 0.16520381509022308,260.0 67 | 0.17007999999999998,264.0 68 | 0.17404917672008482,268.0 69 | 0.1824722564117959,272.0 70 | 0.18792,276.0 71 | 0.19491499984991145,280.0 72 | 0.20324845478366968,284.0 73 | 0.21129845193807753,288.0 74 | 0.21635000000000001,292.0 75 | 0.22177766219216996,296.0 76 | 0.22252,297.0 77 | 0.22549823915479436,298.0 78 | 0.22514131359111603,299.0 79 | 0.2262662025924148,300.0 80 | 0.22773842398046984,301.0 81 | 0.22974730397551074,302.0 82 | 0.23079847528338324,303.0 83 | 0.23577715543108624,304.0 84 | 0.23542883430674455,305.0 85 | 0.23703874123495808,306.0 86 | 0.24071221366409923,307.0 87 | 0.23974116635162568,308.0 88 | 0.23958896159772675,309.0 89 | 0.24280082447819729,310.0 90 | 0.4945196711802708,313.0 91 | 0.4970180915786104,314.0 92 | 0.4965546210083109,315.0 93 | 0.49762791256305555,316.0 94 | 0.4991946053566248,317.0 95 | 0.49815,318.0 96 | 0.49971969166082686,319.0 97 | 0.49931484241375523,320.0 98 | 0.50224,321.0 99 | 0.5034272276980039,322.0 100 | 0.5043579198062702,323.0 101 | 0.5056135926993276,324.0 102 | 0.5046466263192116,325.0 103 | 0.5042653838845497,326.0 104 | 0.506181119308115,327.0 105 | 0.5091765600152025,328.0 106 | 0.5096022016512385,329.0 107 | 0.5095517817650531,330.0 108 | 0.5108529226335226,331.0 109 | 0.5148791351784194,332.0 110 | 0.5161458124924972,333.0 111 | 0.5200812454974786,334.0 112 | 0.5186386386386386,335.0 113 | 0.5183242929484999,336.0 114 | 0.5188509155014223,337.0 115 | 0.5266189789212149,338.0 116 | 0.5223486142653296,339.0 117 | 0.9779640502935792,342.0 118 | 0.9803546766477854,343.0 119 | 0.98128633902749,344.0 120 | 0.9816657988544878,345.0 121 | 0.9834999999999999,346.0 122 | 0.9857122818267441,347.0 123 | 0.9869151586086851,348.0 124 | 0.98803,349.0 125 | 0.9899945970824244,350.0 126 | 0.9898461415337827,351.0 127 | 0.9913380465041756,352.0 128 | 0.992396350248119,353.0 129 | 0.9915190595868671,354.0 130 | 0.9932848965213562,355.0 131 | 0.9933157889470364,356.0 132 | -------------------------------------------------------------------------------- /examples/data/NiMo_sm.csv: -------------------------------------------------------------------------------- 1 | dis,X 2 | 0.0,0.0025311643355943737 3 | 0.7160919540229885,0.0025489966903237244 4 | 1.432183908045977,0.002566829045053075 5 | 2.1482758620689655,0.002584661399782426 6 | 2.864367816091954,0.002602493754511777 7 | 3.5804597701149428,0.0026203261092411275 8 | 4.296551724137931,0.0026473087072628 9 | 5.01264367816092,0.00268723641691896 10 | 5.728735632183908,0.0027271641265751182 11 | 6.444827586206896,0.002767091836231278 12 | 7.1609195402298855,0.002807019545887437 13 | 7.877011494252874,0.002846947255543596 14 | 8.593103448275862,0.0028962196620472306 15 | 9.309195402298851,0.002947429825455284 16 | 10.02528735632184,0.0029986399888633376 17 | 10.741379310344827,0.0030498501522713906 18 | 11.457471264367816,0.003101060315679444 19 | 12.173563218390806,0.0031538695343610993 20 | 12.889655172413793,0.0032116771244940162 21 | 13.605747126436782,0.0032694847146269327 22 | 14.321839080459771,0.0033272923047598492 23 | 15.037931034482758,0.0033850998948927657 24 | 15.754022988505747,0.003442907485025682 25 | 16.470114942528735,0.0035064633738252538 26 | 17.186206896551724,0.003573026929897843 27 | 17.902298850574713,0.003639590485970432 28 | 18.618390804597702,0.003706154042043021 29 | 19.33448275862069,0.0037727175981156105 30 | 20.05057471264368,0.0038390027855851896 31 | 20.766666666666666,0.0039016248953015263 32 | 21.482758620689655,0.0039642470050178635 33 | 22.198850574712644,0.0040268691147342 34 | 22.914942528735633,0.004089491224450538 35 | 23.631034482758622,0.0041521133341668745 36 | 24.34712643678161,0.004218667849126998 37 | 25.0632183908046,0.004289402172309819 38 | 25.779310344827586,0.0043601364954926405 39 | 26.495402298850575,0.004430870818675462 40 | 27.211494252873564,0.0045016051418582835 41 | 27.927586206896553,0.004572339465041105 42 | 28.643678160919542,0.004649471116907735 43 | 29.35977011494253,0.004727322468251293 44 | 30.075862068965517,0.0048051738195948514 45 | 30.791954022988506,0.00488302517093841 46 | 31.508045977011495,0.004960876522281968 47 | 32.224137931034484,0.005041059189418359 48 | 32.94022988505747,0.0051263587958333785 49 | 33.65632183908046,0.005211658402248399 50 | 34.37241379310345,0.005296958008663417 51 | 35.08850574712644,0.0053822576150784375 52 | 35.804597701149426,0.005467557221493456 53 | 36.52068965517242,0.005558105221672164 54 | 37.236781609195404,0.0056506228177445285 55 | 37.95287356321839,0.005743140413816893 56 | 38.66896551724138,0.005835658009889258 57 | 39.38505747126437,0.005928175605961624 58 | 40.10114942528736,0.006021651087233243 59 | 40.817241379310346,0.0061209500751139515 60 | 41.53333333333333,0.006220249062994662 61 | 42.249425287356324,0.006319548050875375 62 | 42.96551724137931,0.006418847038756084 63 | 43.6816091954023,0.006518146026636797 64 | 44.39770114942529,0.006622060884995027 65 | 45.11379310344828,0.006729671107868497 66 | 45.829885057471266,0.006837281330741967 67 | 46.54597701149425,0.006944891553615436 68 | 47.262068965517244,0.007052501776488908 69 | 47.97816091954023,0.007160111999362378 70 | 48.69425287356322,0.007276950396612399 71 | 49.41034482758621,0.007394079084115986 72 | 50.1264367816092,0.0075112077716195744 73 | 50.842528735632186,0.007628336459123161 74 | 51.55862068965517,0.007745465146626748 75 | 52.274712643678164,0.007866143018550218 76 | 52.99080459770115,0.007992523345775927 77 | 53.70689655172414,0.008118903673001634 78 | 54.42298850574713,0.008245284000227341 79 | 55.13908045977011,0.008371664327453048 80 | 55.855172413793106,0.008498044654678757 81 | 56.57126436781609,0.008631998840311328 82 | 57.287356321839084,0.008767873159061133 83 | 58.00344827586207,0.008903747477810937 84 | 58.71954022988506,0.009039621796560742 85 | 59.43563218390805,0.009175496115310544 86 | 60.15172413793103,0.00931344694071445 87 | 60.867816091954026,0.009459121741627179 88 | 61.58390804597701,0.009604796542539908 89 | 62.300000000000004,0.009750471343452635 90 | 63.01609195402299,0.009896146144365363 91 | 63.73218390804598,0.010041820945278092 92 | 64.44827586206897,0.010193523602102818 93 | 65.16436781609195,0.010348827516433944 94 | 65.88045977011494,0.01050413143076507 95 | 66.59655172413794,0.010659435345096197 96 | 67.31264367816092,0.010814739259427322 97 | 68.02873563218391,0.010970440139930933 98 | 68.7448275862069,0.011135636451280427 99 | 69.46091954022988,0.011300832762629916 100 | 70.17701149425288,0.011466029073979412 101 | 70.89310344827587,0.011631225385328903 102 | 71.60919540229885,0.011796421696678395 103 | 72.32528735632184,0.011966173834400705 104 | 73.04137931034484,0.012141399403100968 105 | 73.75747126436782,0.012316624971801225 106 | 74.47356321839081,0.012491850540501483 107 | 75.1896551724138,0.01266707610920174 108 | 75.90574712643678,0.012842301677902 109 | 76.62183908045978,0.013027410436269652 110 | 77.33793103448276,0.013214017201204263 111 | 78.05402298850575,0.013400623966138872 112 | 78.77011494252874,0.013587230731073482 113 | 79.48620689655172,0.013773837496008093 114 | 80.20229885057472,0.013963992971663463 115 | 80.9183908045977,0.014163161366024388 116 | 81.63448275862069,0.014362329760385317 117 | 82.35057471264368,0.014561498154746243 118 | 83.06666666666666,0.014760666549107168 119 | 83.78275862068966,0.014959834943468099 120 | 84.49885057471265,0.01516715225424176 121 | 85.21494252873563,0.015378018286679028 122 | 85.93103448275862,0.015588884319116297 123 | 86.64712643678162,0.01579975035155357 124 | 87.3632183908046,0.016010616383990837 125 | 88.07931034482759,0.01622266992298967 126 | 88.79540229885058,0.016444257934961064 127 | 89.51149425287356,0.016665845946932455 128 | 90.22758620689656,0.016887433958903852 129 | 90.94367816091955,0.017109021970875243 130 | 91.65977011494253,0.017330609982846634 131 | 92.37586206896552,0.017558050910836162 132 | 93.0919540229885,0.017790789891245783 133 | 93.8080459770115,0.018023528871655405 134 | 94.52413793103449,0.018256267852065022 135 | 95.24022988505747,0.01848900683247464 136 | 95.95632183908046,0.018721745812884258 137 | 96.67241379310344,0.018965755122760483 138 | 97.38850574712644,0.019210496522414036 139 | 98.10459770114943,0.01945523792206758 140 | 98.82068965517242,0.01969997932172112 141 | 99.5367816091954,0.019944720721374667 142 | 100.2528735632184,0.02019402139689585 143 | 100.96896551724139,0.02045167383684728 144 | 101.68505747126437,0.02070932627679871 145 | 102.40114942528736,0.020966978716750143 146 | 103.11724137931034,0.021224631156701576 147 | 103.83333333333334,0.021482283596653013 148 | 104.54942528735633,0.021749828590088557 149 | 105.26551724137931,0.02202037446271907 150 | 105.9816091954023,0.022290920335349587 151 | 106.69770114942528,0.0225614662079801 152 | 107.41379310344828,0.022832012080610625 153 | 108.12988505747127,0.02310477298845922 154 | 108.84597701149426,0.023387530957911514 155 | 109.56206896551724,0.023670288927363815 156 | 110.27816091954023,0.023953046896816113 157 | 110.99425287356323,0.024235804866268418 158 | 111.71034482758621,0.024518562835720712 159 | 112.4264367816092,0.024808912027463447 160 | 113.14252873563218,0.025104417521139302 161 | 113.85862068965518,0.025399923014815168 162 | 114.57471264367817,0.025695428508491026 163 | 115.29080459770115,0.02599093400216688 164 | 116.00689655172414,0.026286565097935144 165 | 116.72298850574713,0.026595112275538943 166 | 117.43908045977012,0.02690365945314275 167 | 118.15517241379311,0.027212206630746548 168 | 118.8712643678161,0.027520753808350347 169 | 119.58735632183908,0.027829300985954143 170 | 120.30344827586207,0.028143997827254633 171 | 121.01954022988507,0.028467057279415408 172 | 121.73563218390805,0.028790116731576176 173 | 122.45172413793104,0.02911317618373694 174 | 123.16781609195402,0.029436235635897708 175 | 123.88390804597701,0.029759295088058475 176 | 124.60000000000001,0.030097058112998103 177 | 125.316091954023,0.03043766608209609 178 | 126.03218390804598,0.03077827405119407 179 | 126.74827586206897,0.031118882020292053 180 | 127.46436781609196,0.03145948998939004 181 | 128.18045977011494,0.031804897232934046 182 | 128.89655172413794,0.03216454945668093 183 | 129.61264367816094,0.032524201680427814 184 | 130.3287356321839,0.03288385390417469 185 | 131.0448275862069,0.03324350612792157 186 | 131.76091954022988,0.03360315835166844 187 | 132.47701149425288,0.03397504499923147 188 | 133.19310344827588,0.03435306359897222 189 | 133.90919540229885,0.03473108219871296 190 | 134.62528735632185,0.03510910079845372 191 | 135.34137931034482,0.035487119398194454 192 | 136.05747126436782,0.03586663286189636 193 | 136.77356321839082,0.03626327746659311 194 | 137.4896551724138,0.03665992207128983 195 | 138.2057471264368,0.03705656667598657 196 | 138.92183908045976,0.037453211280683296 197 | 139.63793103448276,0.037849855885380035 198 | 140.35402298850576,0.038255787834294 199 | 141.07011494252873,0.03867121820343011 200 | 141.78620689655173,0.039086648572566246 201 | 142.5022988505747,0.039502078941702365 202 | 143.2183908045977,0.0399175093108385 203 | 143.9344827586207,0.04033293967997462 204 | 144.65057471264367,0.04076625136306614 205 | 145.36666666666667,0.04120136381452422 206 | 146.08275862068967,0.04163647626598228 207 | 146.79885057471265,0.042071588717440336 208 | 147.51494252873565,0.0425067011688984 209 | 148.23103448275862,0.042952907117247814 210 | 148.94712643678162,0.04342240388991644 211 | 149.66321839080462,0.043891900662585054 212 | 150.3793103448276,0.044361397435253656 213 | 151.0954022988506,0.04483089420792227 214 | 151.81149425287356,0.045300390980590875 215 | 152.52758620689656,0.04576988775325949 216 | 153.24367816091956,0.04623938452592811 217 | 153.95977011494253,0.04670888129859671 218 | 154.67586206896553,0.047178378071265334 219 | 155.3919540229885,0.047647874843933936 220 | 156.1080459770115,0.04812253741137327 221 | 156.8241379310345,0.04862627131321376 222 | 157.54022988505747,0.04913000521505423 223 | 158.25632183908047,0.049633739116894726 224 | 158.97241379310344,0.05013747301873521 225 | 159.68850574712644,0.0506412069205757 226 | 160.40459770114944,0.05115753306484603 227 | 161.1206896551724,0.05168355380485068 228 | 161.8367816091954,0.05220957454485535 229 | 162.55287356321838,0.05273559528485999 230 | 163.26896551724138,0.053261616024864664 231 | 163.98505747126438,0.053787636764869334 232 | 164.70114942528735,0.05433347778890897 233 | 165.41724137931035,0.05487974121244447 234 | 166.13333333333333,0.055426004635979936 235 | 166.84942528735633,0.05597226805951543 236 | 167.56551724137933,0.05651853148305093 237 | 168.2816091954023,0.05707229089739099 238 | 168.9977011494253,0.057637615554686744 239 | 169.71379310344827,0.05820294021198248 240 | 170.42988505747127,0.05876826486927823 241 | 171.14597701149427,0.05933358952657399 242 | 171.86206896551724,0.05989891418386972 243 | 172.57816091954024,0.06048078379816303 244 | 173.29425287356324,0.06106660051949353 245 | 174.0103448275862,0.06165241724082401 246 | 174.7264367816092,0.062238233962154506 247 | 175.44252873563218,0.06282405068348498 248 | 176.15862068965518,0.06341437167452305 249 | 176.87471264367818,0.06402052288880876 250 | 177.59080459770115,0.06462667410309444 251 | 178.30689655172415,0.06523282531738014 252 | 179.02298850574712,0.06583897653166583 253 | 179.73908045977012,0.06644512774595154 254 | 180.45517241379312,0.06706420205938685 255 | 181.1712643678161,0.06769068431097101 256 | 181.8873563218391,0.06831716656255521 257 | 182.60344827586206,0.06894364881413939 258 | 183.31954022988506,0.06957013106572359 259 | 184.03563218390806,0.0701977556011875 260 | 184.75172413793103,0.07084719407396738 261 | 185.46781609195403,0.07149663254674728 262 | 186.183908045977,0.07214607101952715 263 | 186.9,0.07279550949230705 264 | 187.616091954023,0.07344494796508695 265 | 188.33218390804598,0.0741052201757157 266 | 189.04827586206898,0.07477801303839128 267 | 189.76436781609195,0.07545080590106681 268 | 190.48045977011495,0.07612359876374239 269 | 191.19655172413795,0.07679639162641796 270 | 191.91264367816092,0.07746918448909353 271 | 192.62873563218392,0.07816242278520422 272 | 193.3448275862069,0.07885850176310844 273 | 194.0609195402299,0.07955458074101268 274 | 194.7770114942529,0.08025065971891691 275 | 195.49310344827586,0.08094673869682112 276 | 196.20919540229886,0.08164994068910629 277 | 196.92528735632186,0.0823704022931606 278 | 197.64137931034483,0.08309086389721489 279 | 198.35747126436783,0.08381132550126921 280 | 199.0735632183908,0.0845317871053235 281 | 199.7896551724138,0.08525224870937781 282 | 200.5057471264368,0.08599009830802752 283 | 201.22183908045977,0.08673517973170213 284 | 201.93793103448277,0.08748026115537676 285 | 202.65402298850574,0.08822534257905135 286 | 203.37011494252874,0.08897042400272598 287 | 204.08620689655174,0.0897187521275662 288 | 204.80229885057472,0.09049080281558965 289 | 205.51839080459771,0.09126285350361311 290 | 206.2344827586207,0.09203490419163657 291 | 206.9505747126437,0.09280695487966004 292 | 207.66666666666669,0.0935790055676835 293 | 208.38275862068966,0.09436779761690889 294 | 209.09885057471266,0.09517116922994383 295 | 209.81494252873563,0.09597454084297873 296 | 210.53103448275863,0.09677791245601365 297 | 211.24712643678163,0.09758128406904859 298 | 211.9632183908046,0.09838465568208349 299 | 212.6793103448276,0.09922051154179931 300 | 213.39540229885057,0.10005812627781592 301 | 214.11149425287357,0.10089574101383257 302 | 214.82758620689657,0.10173335574984921 303 | 215.54367816091954,0.10257097048586583 304 | 216.25977011494254,0.10342176092646437 305 | 216.9758620689655,0.1042956962994479 306 | 217.6919540229885,0.10516963167243146 307 | 218.4080459770115,0.10604356704541502 308 | 219.12413793103448,0.10691750241839856 309 | 219.84022988505748,0.10779143779138212 310 | 220.55632183908045,0.1086932993415698 311 | 221.27241379310345,0.1096031810128967 312 | 221.98850574712645,0.11051306268422363 313 | 222.70459770114942,0.11142294435555052 314 | 223.42068965517242,0.11233282602687743 315 | 224.13678160919542,0.1132484685399146 316 | 224.8528735632184,0.11418850991195988 317 | 225.5689655172414,0.1151285512840052 318 | 226.28505747126437,0.11606859265605046 319 | 227.00114942528737,0.11700863402809578 320 | 227.71724137931037,0.11794867540014109 321 | 228.43333333333334,0.11890399012157644 322 | 229.14942528735634,0.11986927100733533 323 | 229.8655172413793,0.12083455189309417 324 | 230.5816091954023,0.12179983277885305 325 | 231.2977011494253,0.12276511366461193 326 | 232.01379310344828,0.12373087236710656 327 | 232.72988505747128,0.12472095990506515 328 | 233.44597701149425,0.12571104744302375 329 | 234.16206896551725,0.12670113498098234 330 | 234.87816091954025,0.12769122251894097 331 | 235.59425287356322,0.12868131005689953 332 | 236.31034482758622,0.12968205088492446 333 | 237.0264367816092,0.1306967199032953 334 | 237.7425287356322,0.13171138892166617 335 | 238.4586206896552,0.13272605794003706 336 | 239.17471264367816,0.1337407269584079 337 | 239.89080459770116,0.1347553959767788 338 | 240.60689655172413,0.13579076873769147 339 | 241.32298850574713,0.1368298666037964 340 | 242.03908045977013,0.13786896446990132 341 | 242.7551724137931,0.13890806233600617 342 | 243.4712643678161,0.1399471602021111 343 | 244.18735632183908,0.1409923431252594 344 | 244.90344827586208,0.14205469860202732 345 | 245.61954022988508,0.14311705407879524 346 | 246.33563218390805,0.14417940955556313 347 | 247.05172413793105,0.14524176503233102 348 | 247.76781609195402,0.14630412050909888 349 | 248.48390804597702,0.14737840474885514 350 | 249.20000000000002,0.14845841252809747 351 | 249.916091954023,0.14953842030733971 352 | 250.632183908046,0.15061842808658205 353 | 251.348275862069,0.15169843586582438 354 | 252.06436781609196,0.1527793397139332 355 | 252.78045977011496,0.1538693162593158 356 | 253.49655172413793,0.15495929280469833 357 | 254.21264367816093,0.15604926935008095 358 | 254.92873563218393,0.15713924589546352 359 | 255.6448275862069,0.15822922244084608 360 | 256.3609195402299,0.15932130054955246 361 | 257.0770114942529,0.16041544675719854 362 | 257.7931034482759,0.16150959296484463 363 | 258.5091954022989,0.16260373917249074 364 | 259.22528735632187,0.16369788538013683 365 | 259.9413793103448,0.16479203158778283 366 | 260.6574712643678,0.16588919010576436 367 | 261.3735632183908,0.16698661720386315 368 | 262.0896551724138,0.168084044301962 369 | 262.8057471264368,0.1691814714000608 370 | 263.52183908045976,0.17027889849815955 371 | 264.23793103448276,0.1713791556826821 372 | 264.95402298850576,0.17248510038388232 373 | 265.67011494252876,0.17359104508508252 374 | 266.38620689655176,0.17469698978628273 375 | 267.1022988505747,0.17580293448748285 376 | 267.8183908045977,0.17690887918868306 377 | 268.5344827586207,0.17802384898186777 378 | 269.2505747126437,0.1791418853654472 379 | 269.9666666666667,0.18025992174902664 380 | 270.68275862068964,0.181377958132606 381 | 271.39885057471264,0.18249599451618542 382 | 272.11494252873564,0.18361596424942464 383 | 272.83103448275864,0.18474604540138456 384 | 273.54712643678164,0.18587612655334448 385 | 274.2632183908046,0.1870062077053043 386 | 274.9793103448276,0.18813628885726422 387 | 275.6954022988506,0.18926637000922417 388 | 276.4114942528736,0.19039939064572636 389 | 277.1275862068966,0.19153458715765792 390 | 277.8436781609195,0.19266978366958937 391 | 278.5597701149425,0.19380498018152092 392 | 279.2758620689655,0.19494017669345245 393 | 279.9919540229885,0.196075373205384 394 | 280.7080459770115,0.19720187257031707 395 | 281.42413793103447,0.19832827310403417 396 | 282.14022988505747,0.19945467363775132 397 | 282.85632183908046,0.2005810741714685 398 | 283.57241379310346,0.20170747470518569 399 | 284.28850574712646,0.20283339632813463 400 | 285.0045977011494,0.2039586081709808 401 | 285.7206896551724,0.20508382001382705 402 | 286.4367816091954,0.20620903185667333 403 | 287.1528735632184,0.20733424369951958 404 | 287.8689655172414,0.20845945554236583 405 | 288.5850574712644,0.20957825353569096 406 | 289.30114942528735,0.2106956150283374 407 | 290.01724137931035,0.21181297652098388 408 | 290.73333333333335,0.21293033801363037 409 | 291.44942528735635,0.2140476995062769 410 | 292.16551724137935,0.215168463072862 411 | 292.8816091954023,0.21630054326039558 412 | 293.5977011494253,0.21743262344792924 413 | 294.3137931034483,0.21856470363546293 414 | 295.0298850574713,0.21969678382299654 415 | 295.7459770114943,0.2208288640105302 416 | 296.46206896551723,0.22196750677290655 417 | 297.17816091954023,0.22310999642988447 418 | 297.89425287356323,0.22425320804757334 419 | 298.61034482758623,0.22539254055574648 420 | 299.32643678160923,0.22652805098897033 421 | 300.0425287356322,0.2276593712873491 422 | 300.7586206896552,0.22878387951398377 423 | 301.4747126436782,0.2298981421764793 424 | 302.1908045977012,0.23100016448055422 425 | 302.9068965517242,0.23208283104644753 426 | 303.6229885057471,0.2331405543409043 427 | 304.3390804597701,0.23419311262224501 428 | 305.0551724137931,0.23524594432297366 429 | 305.7712643678161,0.23632119544072747 430 | 306.4873563218391,0.2374164507532448 431 | 307.20344827586206,0.23851759021852834 432 | 307.91954022988506,0.2396098988625065 433 | 308.63563218390806,0.24048846004733654 434 | 309.35172413793106,0.24158265263577644 435 | 310.06781609195406,0.2430024348653912 436 | 310.783908045977,0.24513130386914872 437 | 311.5,0.24726017287290636 438 | 311.5,0.49208432470256563 439 | 312.225,0.49349356776299225 440 | 312.95,0.49490281082341875 441 | 313.675,0.49581850977804465 442 | 314.4,0.4965016856656445 443 | 315.125,0.4970331334129435 444 | 315.85,0.49760054663985653 445 | 316.575,0.4981869300513958 446 | 317.3,0.49879139952520635 447 | 318.025,0.4994165443940334 448 | 318.75,0.5000994866912453 449 | 319.475,0.5008009787276807 450 | 320.2,0.5015204440794421 451 | 320.925,0.5022614614547437 452 | 321.65,0.5030311144961781 453 | 322.375,0.5038123213311838 454 | 323.1,0.5045963595212704 455 | 323.825,0.5053499705574986 456 | 324.55,0.5061183631278133 457 | 325.275,0.5069055255116318 458 | 326.0,0.5077157059650897 459 | 326.725,0.5085465295120208 460 | 327.45,0.5094058904509873 461 | 328.175,0.5102878085398577 462 | 328.9,0.5111858106167397 463 | 329.625,0.5120235152101098 464 | 330.35,0.5128721807386879 465 | 331.075,0.5137450224900703 466 | 331.8,0.5146360265604647 467 | 332.525,0.515552473463921 468 | 333.25,0.516493393301117 469 | 333.975,0.5174623959475351 470 | 334.7,0.5184683715736405 471 | 335.425,0.5195402122953527 472 | 336.15,0.5206672836286546 473 | 336.875,0.5218314221255508 474 | 337.6,0.5230453792536963 475 | 338.325,0.524136590613634 476 | 339.05,0.5250330438592132 477 | 339.775,0.5255121798178053 478 | 340.5,0.5259913157763974 479 | 340.5,0.9726784717982001 480 | 341.20454545454544,0.9751779774687422 481 | 341.90909090909093,0.9776774831392845 482 | 342.6136363636364,0.9792244068063765 483 | 343.3181818181818,0.9803186676599107 484 | 344.02272727272725,0.9810452648702828 485 | 344.72727272727275,0.9820908936737449 486 | 345.4318181818182,0.9831164532094916 487 | 346.1363636363636,0.9841049456607203 488 | 346.84090909090907,0.9849918057888642 489 | 347.54545454545456,0.9858866492035545 490 | 348.25,0.9867411559216028 491 | 348.95454545454544,0.9875180896301646 492 | 349.65909090909093,0.9882881983554803 493 | 350.3636363636364,0.9890534715555299 494 | 351.0681818181818,0.9898146138258179 495 | 351.77272727272725,0.9905753930733687 496 | 352.47727272727275,0.9912352088710902 497 | 353.1818181818182,0.9918192420229001 498 | 353.8863636363636,0.9923236238383145 499 | 354.59090909090907,0.9926935293102678 500 | 355.29545454545456,0.9930725502745092 501 | 356.0,0.9935000000000003 502 | -------------------------------------------------------------------------------- /examples/data/TiZr_exp.csv: -------------------------------------------------------------------------------- 1 | X,dis 2 | 0.0005888700765530074,0 3 | 0.0011024804399873745,50 4 | 0.0014138711538582527,100 5 | 0.002209697288193979,150 6 | 0.0029124586602689816,200 7 | 0.003732456849774337,250 8 | 0.004706918324483382,300 9 | 0.00630848690884136,350 10 | 0.0080795390165882,400 11 | 0.010042113313935487,450 12 | 0.012830887411878109,500 13 | 0.01568466418393328,550 14 | 0.019503387404126896,600 15 | 0.02361900129092021,650 16 | 0.02855602693885544,700 17 | 0.03466380693276136,750 18 | 0.041287837651078976,800 19 | 0.04846821527188609,850 20 | 0.05693336012473409,900 21 | 0.06746127976161603,950 22 | 0.07776436889425241,1000 23 | 0.09070281814056358,1050 24 | 0.10459640571733146,1100 25 | 0.1194157912368685,1150 26 | 0.13648146444301318,1200 27 | 0.15598193589161538,1250 28 | 0.17634822819723553,1300 29 | 0.19817280182719826,1350 30 | 0.22261399999999995,1400 31 | 0.25051199999999996,1450 32 | 0.276717,1500 33 | 0.3092364738917911,1550 34 | 0.3417882029168699,1600 35 | 0.37507762492237506,1650 36 | 0.408994,1700 37 | 0.44605444605444605,1750 38 | 0.4862869377644484,1800 39 | 0.524393,1850 40 | 0.5602171678180414,1900 41 | 0.60059,1950 42 | 0.6439489706929521,2000 43 | 0.6831196532966165,2050 44 | 0.724177,2100 45 | 0.766088,2150 46 | 0.804858,2200 47 | 0.8478270000000001,2250 48 | 0.8851829999999999,2300 49 | 0.9191996323201471,2350 50 | 0.9437791718885505,2400 51 | 0.9666173742881818,2450 52 | 0.9807074847670916,2500 53 | 0.9899661063491044,2550 54 | 0.9935881511672666,2600 55 | 0.9973734379468708,2650 56 | 0.9990047521056427,2700 57 | -------------------------------------------------------------------------------- /pydiffusion/Dtools.py: -------------------------------------------------------------------------------- 1 | """ 2 | The Dtools module provides tools to calculate diffusion coefficients based on 3 | a diffusion profile, like Sauer-Freise method, Hall method and Forward 4 | Simulation Analysis (FSA). This module also provides the construction of 5 | DiffSystem by fitting a smooth diffusion coefficient curve based on 6 | Sauer-Fraise calculation results and the tools to adjust it. 7 | """ 8 | 9 | import numpy as np 10 | import matplotlib.pyplot as plt 11 | from scipy.interpolate import splrep, splev, UnivariateSpline 12 | from scipy.special import erfinv 13 | from pydiffusion.core import DiffSystem 14 | from pydiffusion.utils import disfunc, matanocalc, error_profile, step, automesh, SF 15 | from pydiffusion.io import ita_start, ita_finish, ask_input 16 | from pydiffusion.plot import profileplot, DCplot, SFplot 17 | from pydiffusion.simulation import mphSim 18 | 19 | 20 | def SauerFreise(profile, time, Xlim=[]): 21 | """ 22 | Use Sauer-Freise method to calculate and virtualize diffusion coefficients. 23 | 24 | Parameters 25 | ---------- 26 | profile : DiffProfile 27 | Diffusion profile. 28 | time : float 29 | Diffusion time in seconds. 30 | Xlim : list (float), optional 31 | Indicates the left and right concentration limits for calculation. 32 | Default value = [profile.X[0], profile.X[-1]]. 33 | 34 | Returns 35 | ------- 36 | DC : numpy.array 37 | Diffusion coefficients. 38 | 39 | """ 40 | SFplot(profile=profile, time=time, Xlim=Xlim) 41 | DC = SF(profile=profile, time=time, Xlim=Xlim) 42 | return DC 43 | 44 | 45 | def Hall(profile, time, Xlim=[], a=0.25): 46 | """ 47 | Use Hall method to estimate diffusion coefficients nearby concentration limits. 48 | 49 | Parameters 50 | ---------- 51 | profile : DiffProfile 52 | Diffusion profile. 53 | time : float 54 | Diffusion time in seconds. 55 | Xlim : list (float), optional 56 | Indicates the left and right concentration limits for calculation. 57 | Default value = [profile.X[0], profile.X[-1]]. 58 | a : float, optional 59 | Potion of solubility range will be plotted to choose the linear fitting range 60 | of u vs. lambda. default a =0.25, 0 < a < 1. 61 | e.g. XL = 0, XR = 1, a = 0.25. [0, 0.25] and [0.75, 1] are the plotting range. 62 | 63 | Returns 64 | ------- 65 | DC_left, DC_right : numpy.array 66 | Diffusion coefficients by Hall method at left end and right end. 67 | Note: Only left part of DC_left and right part of DC_right are valid. 68 | 69 | """ 70 | dis, X = profile.dis, profile.X 71 | [XL, XR] = [X[0], X[-1]] if Xlim == [] else Xlim 72 | 73 | # Select range for plot 74 | X1, X2 = XL*(1-a)+XR*a, XL*a+XR*(1-a) 75 | id1, id2 = (np.abs(X-X1)).argmin(), (np.abs(X-X2)).argmin() 76 | 77 | # Calculate lambda & u 78 | Y = (X-XL)/(XR-XL) 79 | matano = matanocalc(profile, Xlim) 80 | lbd = (dis-matano)/np.sqrt(time)/1e6 81 | u = erfinv(2*Y-1) 82 | 83 | plt.figure('Hall') 84 | plt.cla() 85 | plt.title('LEFT side, select 2 points for linear fitting.') 86 | plt.plot(lbd[:id1], u[:id1], 'b.') 87 | plt.xlabel('lambda') 88 | plt.ylabel('u') 89 | plt.pause(0.1) 90 | lbd1 = np.array(plt.ginput(2))[:, 0] 91 | 92 | plt.cla() 93 | plt.title('RIGHT side, select 2 points for linear fitting.') 94 | plt.plot(lbd[id2:], u[id2:], 'b.') 95 | plt.xlabel('lambda') 96 | plt.ylabel('u') 97 | plt.pause(0.1) 98 | lbd2 = np.array(plt.ginput(2))[:, 0] 99 | 100 | sp = np.where((lbd < max(lbd1)) & (lbd > min(lbd1)))[0] 101 | h1, k1 = np.polyfit(lbd[sp], u[sp], 1) 102 | sp = np.where((lbd < max(lbd2)) & (lbd > min(lbd2)))[0] 103 | h2, k2 = np.polyfit(lbd[sp], u[sp], 1) 104 | 105 | DC_left = 1/4/h1**2*(1+2*k1/np.sqrt(np.pi)*np.exp(u**2)*Y) 106 | DC_right = 1/4/h2**2*(1-2*k2/np.sqrt(np.pi)*np.exp(u**2)*(1-Y)) 107 | DC = SF(profile, time, Xlim) 108 | 109 | plt.cla() 110 | plt.semilogy(X, DC, 'b.') 111 | plt.semilogy(X[:id1], DC_left[:id1], 'r--', lw=2) 112 | plt.semilogy(X[id2:], DC_right[id2:], 'r--', lw=2) 113 | plt.xlabel('Mole fraction', fontsize=15) 114 | plt.ylabel('Diffusion Coefficients '+'$(m^2/s)$', fontsize=15) 115 | plt.xlim(X.min(), X.max()) 116 | 117 | return DC_left, DC_right 118 | 119 | 120 | def Dpcalc(X, DC, Xp): 121 | """ 122 | Based on Sauer-Fraise calculated results, this function provides good 123 | estimation of diffusion coefficients at location picked. 124 | 125 | Parameters 126 | ---------- 127 | X, DC : 1d-array 128 | Sauer-Fraise calculated results. 129 | Xp : 1d-array 130 | Locations to estimate diffusion coefficients. 131 | 132 | Returns 133 | ------- 134 | Dp : 1d-array 135 | Estimated diffusion coefficients. 136 | 137 | """ 138 | if len(Xp) == 1: 139 | fD = splrep(X, np.log(DC), k=1) 140 | Dp = np.exp(splev(Xp, fD)) 141 | else: 142 | Dp = np.zeros(len(Xp)) 143 | for i in range(len(Xp)): 144 | mark = np.zeros(2) 145 | if i == 0: 146 | mark[0] = Xp[i] 147 | else: 148 | mark[0] = Xp[i-1] 149 | if i == len(Xp)-1: 150 | mark[1] = Xp[i] 151 | else: 152 | mark[1] = Xp[i+1] 153 | pid = np.where((X > mark[0]) & (X < mark[1]))[0] 154 | p = np.polyfit(X[pid], np.log(DC[pid]), 2) 155 | Dp[i] = np.exp(np.polyval(p, Xp[i])) 156 | return Dp 157 | 158 | 159 | def Dfunc_spl(Xp, Dp): 160 | """ 161 | Return a spline function to model diffusion coefficients. 162 | The function can be constant(1), linear(2) or quadratic(>2) depending on 163 | the length of Xp. 164 | 165 | Parameters 166 | ---------- 167 | Xp : 1d-array 168 | Composition list. 169 | Dp : 1d-array 170 | Corresponding diffusion coefficients at Xp. 171 | 172 | """ 173 | if len(Xp) == 1: 174 | fDC = splrep([Xp[0], Xp[0]*1.01], [np.log(Dp[0]), np.log(Dp[0])], k=1) 175 | elif len(Xp) == 2: 176 | fDC = splrep(Xp, np.log(Dp), k=1) 177 | else: 178 | fDC = splrep(Xp, np.log(Dp), k=2) 179 | return fDC 180 | 181 | 182 | def Dfunc_uspl(X, DC, Xp, Xr): 183 | """ 184 | Use UnivariateSpline to model diffusion coefficients. 185 | 186 | Parameters 187 | ---------- 188 | X, DC : 1d-array 189 | Diffusion coefficients data. 190 | Xp : 1d-array with shape (1, 2) 191 | UnivariateSpline range of X. 192 | Xr : 1d-array with shape (1, 2) 193 | Expanded range of UnivariateSpline, usually is the phase range. 194 | 195 | """ 196 | pid = np.where((X >= Xp[0]) & (X <= Xp[-1]))[0] 197 | fDC = UnivariateSpline(X[pid], np.log(DC[pid]), bbox=[Xr[0], Xr[1]], k=2) 198 | Xf = np.linspace(Xr[0], Xr[1], 30) 199 | return splrep(Xf, fDC(Xf), k=2) 200 | 201 | 202 | def Dadjust(profile_ref, profile_sim, diffsys, ph, pp=True, deltaD=None, r=0.02): 203 | """ 204 | Adjust diffusion coefficient fitting function by comparing simulated 205 | profile against reference profile. The purpose is to let simulated 206 | diffusion profile be similar to reference profile. 207 | 208 | Parameters 209 | ---------- 210 | profile_ref : DiffProfile 211 | Reference diffusion profile 212 | profile_sim : DiffProfile 213 | Simulated diffusion profile 214 | diffsys : DiffSystem 215 | Diffusion system 216 | ph : int 217 | Phase # to be adjusted, 0 <= ph <= diffsys.Np-1 218 | Xp : 1d-array 219 | Reference composition to adjust their corresponding diffusivities. 220 | If provided, spline function Dfunc must be determined by [Xp, Dp] 221 | alone, where Dp = exp(Dfunc(Xp)). 222 | pp : bool, optional 223 | Point Mode (True) or Phase Mode (False). Point Mode 224 | adjusts each Dp at Xp by itself. In Phase Mode, all Dp are 225 | adjusted by the same rate, i.e. the diffusivity curve shape won't 226 | change. 227 | deltaD: float, optional 228 | Only useful at Phase Mode. deltaD gives the rate to change 229 | diffusion coefficients DC. DC = DC * 10^deltaD 230 | r : float, optional 231 | Only useful at Phase Mode, default = 0.02, 0 < r < 1. r gives the 232 | range to calculate the concentration gradient around X, [X-r, X+r]. 233 | 234 | """ 235 | dref, Xref, Ifref = profile_ref.dis, profile_ref.X, profile_ref.If 236 | dsim, Xsim, Ifsim = profile_sim.dis, profile_sim.X, profile_sim.If 237 | 238 | if ph >= diffsys.Np: 239 | raise ValueError('Incorrect phase #, 0 <= ph <= %i' % diffsys.Np-1) 240 | if pp and 'Xspl' not in dir(diffsys): 241 | raise ValueError('diffsys must have Xspl properties in per-point mode') 242 | 243 | Dfunc, Xr, Np = diffsys.Dfunc[ph], diffsys.Xr[ph], diffsys.Np 244 | rate = 1 245 | 246 | # If there is phase consumed, increase adjustment rate 247 | if len(Ifref) != len(Ifsim): 248 | print('Phase consumed found, increase adjustment rate') 249 | rate = 2 250 | 251 | if Xr[1] > Xr[0]: 252 | idref = np.where((Xref >= Xr[0]) & (Xref <= Xr[1]))[0] 253 | idsim = np.where((Xsim >= Xr[0]) & (Xsim <= Xr[1]))[0] 254 | else: 255 | idref = np.where((Xref <= Xr[0]) & (Xref >= Xr[1]))[0] 256 | idsim = np.where((Xsim <= Xr[0]) & (Xsim >= Xr[1]))[0] 257 | 258 | if 'Xspl' in dir(diffsys): 259 | Xp = diffsys.Xspl[ph] 260 | else: 261 | Xp = np.linspace(Xr[0], Xr[1], 30) 262 | Dp = np.exp(splev(Xp, Dfunc)) 263 | 264 | # If this is consumed phase, increase DC by 2 or 10^deltaD 265 | if len(idsim) == 0: 266 | Dp = np.exp(splev(Xp, Dfunc)) 267 | if deltaD is None: 268 | return Dfunc_spl(Xp, Dp*2) 269 | else: 270 | return Dfunc_spl(Xp, Dp*10**deltaD) 271 | 272 | dref, Xref = dref[idref], Xref[idref] 273 | dsim, Xsim = dsim[idsim], Xsim[idsim] 274 | 275 | # Per phase adjustment 276 | if not pp: 277 | if deltaD is not None: 278 | return Dfunc_spl(Xp, Dp*10**deltaD) 279 | 280 | # Calculate deltaD by phase width 281 | # When it comes to first or last phase, data closed to end limits are not considered 282 | fdis_ref = disfunc(dref, Xref) 283 | fdis_sim = disfunc(dsim, Xsim) 284 | X1, X2 = Xr[0], Xr[1] 285 | if ph == 0: 286 | X1 = Xr[0]*0.9 + Xr[1]*0.1 287 | if ph == Np-1: 288 | X2 = Xr[0]*0.1 + Xr[1]*0.9 289 | ref = splev([X1, X2], fdis_ref) 290 | sim = splev([X1, X2], fdis_sim) 291 | wref = ref[1]-ref[0] 292 | wsim = sim[1]-sim[0] 293 | Dp *= np.sqrt(wref/wsim) 294 | return Dfunc_spl(Xp, Dp) 295 | 296 | # Point Mode adjustment 297 | for i in range(len(Xp)): 298 | # X1, X2 is the lower, upper bound to collect profile data 299 | # X1, X2 cannot exceed phase bound Xr 300 | if Xr[0] < Xr[1]: 301 | X1, X2 = max(Xp[i]-r, Xr[0]), min(Xp[i]+r, Xr[1]) 302 | else: 303 | X1, X2 = max(Xp[i]-r, Xr[1]), min(Xp[i]+r, Xr[0]) 304 | 305 | # Calculate the gradient inside [X1, X2] by linear fitting 306 | fdis_ref = disfunc(dref, Xref) 307 | fdis_sim = disfunc(dsim, Xsim) 308 | Xf = np.linspace(X1, X2, 10) 309 | pref = np.polyfit(splev(Xf, fdis_ref), Xf, 1)[0] 310 | psim = np.polyfit(splev(Xf, fdis_sim), Xf, 1)[0] 311 | 312 | # Adjust DC by gradient difference 313 | Dp[i] *= (psim/pref)**rate 314 | return Dfunc_spl(Xp, Dp) 315 | 316 | 317 | def Dmodel(profile, time, Xspl=None, Xlim=[], output=True, name=''): 318 | """ 319 | Given the diffusion profile and diffusion time, modeling the diffusion 320 | coefficients for each phase. Please do not close any plot window during 321 | the modeling process. 322 | 323 | Dmodel() is the second step of the forward simulation analysis (FSA). 324 | 325 | Parameters 326 | ---------- 327 | profile : DiffProfile 328 | Diffusion profile. Multiple phase profile must be after datasmooth to 329 | identify phase boundaries. 330 | time : float 331 | Diffusion time in seconds. 332 | Xspl : list, optional 333 | If Xspl is given, Dmodel will be done automatically. 334 | Xlim : list, optional 335 | Left and Right limit of diffusion coefficients. Xlim is also passed to 336 | SF function to calculate diffusion coefficients initially. 337 | output : bool, optional 338 | Plot Dmodel result or not. Can be False only if Xspl is given. 339 | name : str, optional 340 | Name of the output DiffSystem 341 | 342 | Examples 343 | -------- 344 | After datasmooth(), a initial diffusion coefficients will be established before 345 | FSA(): 346 | 347 | >>> ds = datasmooth(exp) 348 | >>> dsys = Dmodel(ds, time) 349 | 350 | """ 351 | if not isinstance(Xlim, list): 352 | raise TypeError('Xlim must be a list') 353 | if len(Xlim) != 2 and Xlim != []: 354 | raise ValueError('Xlim must be an empty list or a list with length = 2') 355 | 356 | dis, X = profile.dis, profile.X 357 | 358 | # If input Xlim doesn't follow trend of X, correct it 359 | if Xlim != [] and (X[-1]-X[0])*(Xlim[1]-Xlim[0]) < 0: 360 | Xlim = Xlim[::-1] 361 | 362 | # Initial set-up of Xr (phase boundaries) 363 | Xlim = [X[0], X[-1]] if Xlim == [] else Xlim 364 | DC = SF(profile, time, Xlim) 365 | Xr = np.array(Xlim, dtype=float) 366 | for i in range(len(dis)-1): 367 | if dis[i] == dis[i+1]: 368 | Xr = np.insert(Xr, -1, [X[i], X[i+1]]) 369 | Np = len(Xr)//2 370 | Xr.sort() 371 | Xr = Xr.reshape(Np, 2) 372 | fD = [0]*Np 373 | 374 | ita_start() 375 | 376 | # Choose Spline or UnivariateSpline 377 | if Xspl is None or output: 378 | ax = plt.figure().gca() 379 | ax.semilogy(X, DC, 'b.') 380 | ax.set_title('Sauer-Freise result') 381 | ax.set_xlabel('Mole fraction') 382 | ax.set_ylabel('Diffusion Coefficients '+'$(m^2/s)$') 383 | plt.tight_layout() 384 | 385 | ipt = ask_input('Use Spline (y) or UnivariateSpline (n) to model diffusion coefficients? [y]\n') 386 | choice = False if 'N' in ipt or 'n' in ipt else True 387 | 388 | # Xspl provided, no need for manually picking Xspl 389 | if Xspl is not None: 390 | if len(Xspl) != Np: 391 | raise ValueError('Xspl must has a length of phase number') 392 | 393 | for i in range(Np): 394 | if Xr[i, 1] > Xr[i, 0]: 395 | pid = np.where((X >= Xr[i, 0]) & (X <= Xr[i, 1]))[0] 396 | else: 397 | pid = np.where((X <= Xr[i, 0]) & (X >= Xr[i, 1]))[0] 398 | 399 | # Spline 400 | if choice: 401 | try: 402 | Dp = Dpcalc(X, DC, Xspl[i]) 403 | fD[i] = Dfunc_spl(Xspl[i], Dp) 404 | except (ValueError, TypeError) as error: 405 | ita_finish() 406 | raise error 407 | 408 | # UnivariateSpline 409 | else: 410 | try: 411 | fD[i] = Dfunc_uspl(X, DC, Xspl[i], Xr[i]) 412 | except (ValueError, TypeError) as error: 413 | ita_finish() 414 | raise error 415 | 416 | print('DC modeling finished, Xspl info:') 417 | print(Xspl) 418 | 419 | if output: 420 | ax.cla() 421 | ax.set_title('DC Modeling Result') 422 | ax.semilogy(X, DC, 'b.') 423 | for i in range(Np): 424 | Xf = np.linspace(Xr[i, 0], Xr[i, 1], 30) 425 | ax.semilogy(Xf, np.exp(splev(Xf, fD[i])), 'r-') 426 | ax.set_xlabel('Mole fraction') 427 | ax.set_ylabel('Diffusion Coefficients '+'$(m^2/s)$') 428 | plt.tight_layout() 429 | plt.pause(0.1) 430 | plt.show() 431 | 432 | ita_finish() 433 | 434 | return DiffSystem(Xr, Dfunc=fD, Xspl=Xspl) 435 | 436 | Xspl = [0] * Np if choice else None 437 | 438 | for i in range(Np): 439 | if Xr[i, 1] > Xr[i, 0]: 440 | pid = np.where((X >= Xr[i, 0]) & (X <= Xr[i, 1]))[0] 441 | else: 442 | pid = np.where((X <= Xr[i, 0]) & (X >= Xr[i, 1]))[0] 443 | 444 | # Spline 445 | if choice: 446 | while True: 447 | DC_real = [k for k in DC[pid] if not np.isnan(k) and not np.isinf(k)] 448 | DCmean = np.mean(DC_real) 449 | for k in pid: 450 | if np.isnan(DC[k]) or np.isinf(DC[k]) or abs(np.log10(DC[k]/DCmean)) > 5: 451 | DC[k] = DCmean 452 | ax.cla() 453 | ax.semilogy(X[pid], DC[pid], 'b.') 454 | ax.set_xlabel('Mole fraction') 455 | ax.set_ylabel('Diffusion Coefficients '+'$(m^2/s)$') 456 | ax.set_title('Phase %i' % (i+1)) 457 | plt.draw() 458 | msg = '# of spline points: 1 (constant), 2 (linear), >2 (spline)\n' 459 | ipt = ask_input(msg+'input # of spline points\n') 460 | ax.set_title('Phase %i: Select %i points of Spline' % (i+1, int(ipt))) 461 | plt.pause(0.1) 462 | Xp = np.array(plt.ginput(int(ipt)))[:, 0] 463 | try: 464 | Dp = Dpcalc(X, DC, Xp) 465 | fD[i] = Dfunc_spl(Xp, Dp) 466 | except (ValueError, TypeError) as error: 467 | ita_finish() 468 | raise error 469 | Xspl[i] = list(Xp) 470 | Xf = np.linspace(Xr[i, 0], Xr[i, 1], 30) 471 | ax.semilogy(Xf, np.exp(splev(Xf, fD[i])), 'r-', lw=2) 472 | plt.draw() 473 | ipt = ask_input('Continue to next phase? [y]') 474 | redo = False if 'N' in ipt or 'n' in ipt else True 475 | if redo: 476 | break 477 | 478 | # UnivariateSpline 479 | else: 480 | while True: 481 | ax.cla() 482 | ax.semilogy(X[pid], DC[pid], 'b.') 483 | ax.set_xlabel('Mole fraction') 484 | ax.set_ylabel('Diffusion Coefficients '+'$(m^2/s)$') 485 | #ax.set_title('Phase %i' % (i+1)) 486 | ax.set_title('Phase %i: Select 2 boundaries for UnivariateSpline' % (i+1)) 487 | plt.draw() 488 | plt.pause(0.1) 489 | Xp = np.array(plt.ginput(2))[:, 0] 490 | #ipt = ask_input('input 2 boundaries for UnivariateSpline\n') 491 | #Xp = np.array([float(x) for x in ipt.split()]) 492 | try: 493 | fD[i] = Dfunc_uspl(X, DC, Xp, Xr[i]) 494 | except (ValueError, TypeError) as error: 495 | ita_finish() 496 | raise error 497 | Xf = np.linspace(Xr[i, 0], Xr[i, 1], 30) 498 | ax.semilogy(Xf, np.exp(splev(Xf, fD[i])), 'r-', lw=2) 499 | plt.draw() 500 | ipt = ask_input('Continue to next phase? [y]') 501 | redo = False if 'N' in ipt or 'n' in ipt else True 502 | if redo: 503 | break 504 | 505 | ita_finish() 506 | 507 | print('DC modeling finished, Xspl info:') 508 | print(Xspl) 509 | 510 | ax.cla() 511 | ax.set_title('DC Modeling Result') 512 | ax.semilogy(X, DC, 'b.') 513 | for i in range(Np): 514 | Xf = np.linspace(Xr[i, 0], Xr[i, 1], 30) 515 | ax.semilogy(Xf, np.exp(splev(Xf, fD[i])), 'r-') 516 | ax.set_xlabel('Mole fraction') 517 | ax.set_ylabel('Diffusion Coefficients '+'$(m^2/s)$') 518 | plt.tight_layout() 519 | plt.pause(0.1) 520 | plt.show() 521 | 522 | if name == '': 523 | name = profile.name+'_%.1fh_Dmodeled' % (time/3600) 524 | 525 | return DiffSystem(Xr, Dfunc=fD, Xspl=Xspl, name=name) 526 | 527 | 528 | def FSA(profile_exp, profile_sm, diffsys, time, Xlim=[], n=[400, 500], w=None, f=None, alpha=0.3, name=''): 529 | """ 530 | Forward Simulation Analysis 531 | Extract diffusion coefficients based on a diffusion profile. 532 | Please do not close any plot window during the FSA process. 533 | 534 | This is the final step of FSA. 535 | 536 | Parameters 537 | ---------- 538 | profile_exp : DiffProfile 539 | Experimental diffusion profile, used for comparison with simulation 540 | results. 541 | profile_sm : DiffProfile 542 | Diffusion profile after data smooth on experimental profile. 543 | diffsys : DiffSystem 544 | Diffusion coefficients 545 | time : float 546 | Diffusion time in seconds 547 | Xlim : list (float), optional 548 | Passed to 'pydiffusion.Dtools.SF', 'pydiffusion.utils.step'. 549 | Indicates the left and right concentration limits for calculation. 550 | Default value = [profile.X[0], profile.X[-1]]. 551 | n : list. optional 552 | Passed to 'pydiffusion.utils.automesh'. 553 | Meshing number range, default = [400, 500]. 554 | w : list, optional 555 | Weights of each phase to calculate error. 556 | Passed to 'pydiffusion.utils.error_profile'. 557 | f : function of Meshing 558 | Keyword argument of automesh() 559 | alpha : float 560 | Keyword argument of automesh() 561 | name : str, optional 562 | Name the output DiffProfile 563 | 564 | Returns 565 | ------- 566 | profile_sim : DiffProfile 567 | Simulated diffusion profile after FSA. 568 | diffsys_sim : DiffSystem 569 | Calculated diffusion efficients by FSA. 570 | 571 | Examples 572 | -------- 573 | After datasmooth() and Dmodel(), FSA can be performed to calculate accurate diffusion coefficients: 574 | 575 | >>> ds = datasmooth(exp) 576 | >>> dsys = Dmodel(ds, time) 577 | >>> fsa = FSA(exp, ds, dsys, time) 578 | 579 | """ 580 | # Create step profile on meshed grids 581 | dism = automesh(profile=profile_sm, diffsys=diffsys, n=n, f=f, alpha=alpha) 582 | matano = matanocalc(profile_sm, Xlim) 583 | if Xlim == [] and profile_sm.X[-1] < profile_sm.X[0]: 584 | profile_init = step(dism, matano, diffsys, [diffsys.Xr[-1, 1], diffsys.Xr[0, 0]]) 585 | else: 586 | profile_init = step(dism, matano, diffsys, Xlim) 587 | 588 | # Determine the stop criteria of forward simulations 589 | error_sm = error_profile(profile_sm, profile_exp) 590 | ipt = input('Default error = %.6f\nInput the stop criteria of error: [%.6f]\n' 591 | % (error_sm, error_sm*2)) 592 | error_stop = error_sm*2 if ipt == '' else float(ipt) 593 | 594 | # If there is no Xspl info in diffsys, use Phase Mode 595 | # else: ask if use Phase or Point Mode 596 | if diffsys.Xspl is not None: 597 | ipt = input('Use Phase Mode? [n]\n(The shape of diffusivity curve does not change)\n') 598 | pp = False if 'y' in ipt or 'Y' in ipt else True 599 | else: 600 | pp = False 601 | 602 | if name == '': 603 | name = profile_exp.name+'_FSA' 604 | 605 | # Diffusion coefficients used for forward simulations 606 | diffsys_sim = DiffSystem(diffsys.Xr, diffsys.Dfunc, Xspl=diffsys.Xspl, name=name) 607 | 608 | # Plot FSA status 609 | fig = plt.figure('FSA', figsize=(16, 6)) 610 | ax1, ax2 = fig.add_subplot(121), fig.add_subplot(122) 611 | profileplot(profile_exp, ax1, ls='none', marker='o', c='b', fillstyle='none') 612 | profileplot(profile_sm, ax1, ls='-', c='g', lw=1) 613 | SFplot(profile_sm, time, Xlim, ax2, ls='none', c='b', marker='.') 614 | DCplot(diffsys_sim, ax2, ls='-', c='r', lw=2) 615 | plt.draw() 616 | plt.tight_layout() 617 | plt.pause(0.1) 618 | 619 | n_sim = 0 620 | while True: 621 | 622 | # Simulation 623 | n_sim += 1 624 | profile_sim = mphSim(profile_init, diffsys_sim, time, name=name) 625 | error_sim = error_profile(profile_sim, profile_exp, w) 626 | print('Simulation %i, error = %f(%f)' % (n_sim, error_sim, error_stop)) 627 | 628 | # Plot simulation results 629 | ax1.cla() 630 | ax2.cla() 631 | profileplot(profile_exp, ax1, ls='none', marker='o', c='b', fillstyle='none') 632 | profileplot(profile_sm, ax1, ls='-', c='g', lw=1) 633 | profileplot(profile_sim, ax1, ls='-', c='r', lw=2) 634 | SFplot(profile_sm, time, Xlim, ax2, ls='none', c='b', marker='.') 635 | DCplot(diffsys_sim, ax2, ls='-', c='r', lw=2) 636 | plt.draw() 637 | plt.tight_layout() 638 | 639 | # DC adjust 640 | Dfunc_adjust = [0] * diffsys_sim.Np 641 | 642 | # If error > stop criteria, continue simulation by auto DC adjustment 643 | if error_sim > error_stop: 644 | for ph in range(diffsys_sim.Np): 645 | try: 646 | Dfunc_adjust[ph] = Dadjust(profile_sm, profile_sim, diffsys_sim, ph, pp) 647 | except (ValueError, TypeError) as error: 648 | ita_finish() 649 | raise error 650 | diffsys_sim.Dfunc = Dfunc_adjust 651 | 652 | # If error < stop criteria or simulate too many times 653 | if error_sim <= error_stop or n_sim > 9: 654 | 655 | ita_start() 656 | 657 | # Ask if exit 658 | ipt = ask_input('Satisfied with FSA? [n]') 659 | if 'y' in ipt or 'Y' in ipt: 660 | ita_finish() 661 | break 662 | 663 | # If use Point Mode 664 | if diffsys_sim.Xspl is not None: 665 | ipt = ask_input('Use Point Mode (y) or Phase Mode (n)? [y]') 666 | pp = False if 'n' in ipt or 'N' in ipt else True 667 | if pp: 668 | for ph in range(diffsys_sim.Np): 669 | try: 670 | Dfunc_adjust[ph] = Dadjust(profile_sm, profile_sim, diffsys_sim, ph, pp) 671 | except (ValueError, TypeError) as error: 672 | ita_finish() 673 | raise error 674 | diffsys_sim.Dfunc = Dfunc_adjust 675 | 676 | DCplot(diffsys_sim, ax2, ls='-', c='m', lw=2) 677 | plt.draw() 678 | plt.pause(0.1) 679 | ita_finish() 680 | continue 681 | 682 | # Phase Mode, ask if use manual input for each phase 683 | pp = False 684 | ipt = input('Phase Mode\nManually input for each phase? [n]') 685 | manual = True if 'y' in ipt or 'Y' in ipt else False 686 | for ph in range(diffsys_sim.Np): 687 | if manual: 688 | ipt = input('Input deltaD for phase # %i:\n(DC = DC * 10^deltaD, default deltaD = auto)\n' % (ph+1)) 689 | deltaD = float(ipt) if ipt != '' else None 690 | else: 691 | deltaD = None 692 | try: 693 | Dfunc_adjust[ph] = Dadjust(profile_sm, profile_sim, diffsys_sim, ph, pp, deltaD) 694 | except (ValueError, TypeError) as error: 695 | ita_finish() 696 | raise error 697 | 698 | # Apply the adjustment to diffsys_sim 699 | diffsys_sim.Dfunc = Dfunc_adjust 700 | 701 | DCplot(diffsys_sim, ax2, ls='-', c='m', lw=2) 702 | plt.draw() 703 | plt.pause(0.1) 704 | ita_finish() 705 | 706 | return profile_sim, diffsys_sim 707 | -------------------------------------------------------------------------------- /pydiffusion/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | pyDiffusion combines tools like diffusion simulation, diffusion data smooth, 3 | forward simulation analysis (FSA), etc. to help people analyze diffusion data 4 | efficiently. 5 | """ 6 | __version__ = '0.2.3' 7 | 8 | 9 | from pydiffusion.core import DiffProfile, DiffSystem 10 | from pydiffusion.simulation import mphSim 11 | from pydiffusion.smooth import datasmooth 12 | from pydiffusion.plot import profileplot, DCplot, SFplot 13 | from pydiffusion.utils import matanocalc, mesh, step, SF 14 | from pydiffusion.Dtools import SauerFreise, Hall, Dmodel, FSA 15 | from pydiffusion.io import read_csv, save_csv 16 | -------------------------------------------------------------------------------- /pydiffusion/core.py: -------------------------------------------------------------------------------- 1 | """ 2 | The core module gives definition of main classes in pyDiffusion. 3 | """ 4 | 5 | import numpy as np 6 | from scipy.interpolate import splrep, splev 7 | 8 | 9 | class DiffProfile(object): 10 | """ 11 | Diffusion Profile 12 | 13 | Parameters 14 | ---------- 15 | dis : array-like 16 | Distance data in microns. 17 | X : array-like 18 | Concentration data in mole fraction (0~1). 19 | If : list, optional 20 | N-1 interfaces locations (in micron) for N phases system. 21 | Default value is [dis[0]-d1, dis[-1]+d2], d1 d2 are small values. 22 | name : str, optional 23 | Name of the current diffusion profile. 24 | 25 | Examples 26 | -------- 27 | Create a diffusion profile with 10 data points. Interfaces at [2.5, 3.5]: 28 | 29 | >>> dis = np.arange(10) 30 | >>> X = np.linspace(0, 1, 10) 31 | >>> If = [2.5, 3.5] 32 | >>> profile = DiffProfile(dis, X, If, name='test') 33 | 34 | """ 35 | 36 | def __init__(self, dis, X, If=[], name='Profile'): 37 | try: 38 | self.dis = np.array(dis, dtype=float) 39 | self.X = np.array(X, dtype=float) 40 | except TypeError: 41 | print('Can not convert input into 1d-array') 42 | 43 | try: 44 | self.name = str(name) 45 | except TypeError: 46 | print('Can not convert name to str type') 47 | 48 | if self.dis.ndim != 1 or self.X.ndim != 1: 49 | raise ValueError('1d data is required') 50 | 51 | if len(self.dis) != len(self.X): 52 | raise ValueError('length of dis and X is not equal') 53 | 54 | # dis must be ascending 55 | if not np.all(self.dis[1:] >= self.dis[:-1]): 56 | self.X = np.array([x for _, x in sorted(zip(self.dis, self.X))]) 57 | self.dis.sort() 58 | 59 | d1, d2 = 0.5*(self.dis[1]-self.dis[0]), 0.5*(self.dis[-1]-self.dis[-2]) 60 | 61 | try: 62 | If = np.array(If, dtype=float) 63 | except TypeError: 64 | print('If must be a list or 1d array') 65 | 66 | if If.ndim != 1: 67 | raise TypeError('If must be a list or 1d array') 68 | 69 | self.If = np.array([self.dis[0]-d1] + list(If) + [self.dis[-1]+d2]) 70 | self.Ip = np.zeros(len(self.If), dtype=int) 71 | for i in range(1, len(self.Ip)-1): 72 | self.Ip[i] = np.where(self.dis > self.If[i])[0][0] 73 | self.Ip[-1] = len(dis) 74 | 75 | def copy(self, dismax=None, Xmax=None): 76 | """ 77 | Method to copy DiffProfile 78 | Distance data or concentration data can be reversed 79 | 80 | Parameters 81 | ---------- 82 | dismax : if given, output distance data = dismax - dis 83 | Xmax : if given, output concentration data = Xmax - X 84 | 85 | Examples 86 | -------- 87 | Reverse a diffusion profile with distance: 88 | 89 | >>> profile_r = profile.copy(dismax=1000) 90 | 91 | For Fe-Ni diffusion couple, with known profile for Ni (profile_Ni), 92 | calculate profile for Fe: 93 | 94 | >>> profile_Fe = profile_Ni.copy(Xmax=1.0) 95 | 96 | """ 97 | if dismax is not None: 98 | try: 99 | dis = dismax-self.dis 100 | If = dismax-self.If 101 | except TypeError: 102 | print('dismax must be a number') 103 | else: 104 | dis = self.dis 105 | If = self.If 106 | if Xmax is not None: 107 | try: 108 | X = Xmax-self.X 109 | except TypeError: 110 | print('Xmax must be a number') 111 | else: 112 | X = self.X 113 | return DiffProfile(dis=dis, X=X, If=If[1:-1], name=self.name) 114 | 115 | 116 | class DiffSystem(object): 117 | """ 118 | Binary diffusion System with diffusion coefficients modeling 119 | 120 | Parameters 121 | ---------- 122 | Xr : List with length of 2 or array-like with shape (,2), optional 123 | Concentration range for each phase, default = [0,1]. 124 | Ascending list is recommended. 125 | Save Xr.shape[0] to phase number DiffSystem.Np. 126 | Dfunc : list of tck tuple, optional 127 | Np of tck tuple describe the diffusion coefficient function for each phase. 128 | X, DC : array-like, optional 129 | Use existing data to model Dfunc. 130 | Xspl : list of array, optional 131 | Xspl is the reference locations to create Dfunc, useful for forward 132 | simulation analysis. 133 | name : str, optional 134 | Name of the current diffusion system. 135 | 136 | 137 | Examples 138 | -------- 139 | Create a diffusion system with 3 phases, with known solubilities [0, 0.2], [0.4, 0.7] and [0.8, 1.0]: 140 | 141 | >>> X = np.linspace(0, 1, 10) 142 | >>> DC = np.linspace(1e-14, 1e-13, 10) 143 | >>> Xr = [[0, 0.2], [0.4, 0.7], [0.8, 1.0]] 144 | >>> dsys = DiffSystem(Xr=Xr, X=X, DC=DC, name='Dtest') 145 | 146 | """ 147 | 148 | def __init__(self, Xr=[0, 1], Dfunc=None, X=None, DC=None, Xspl=None, name='System'): 149 | try: 150 | self.Xr = np.array(Xr, dtype=float) 151 | except TypeError: 152 | print('Cannot convert Xr to array') 153 | if self.Xr.shape == (2,): 154 | self.Xr = np.array([Xr]) 155 | self.Np = 1 156 | elif self.Xr.shape[1] == 2: 157 | self.Np = self.Xr.shape[0] 158 | else: 159 | raise ValueError('Xr must has a shape (,2) or (2,)') 160 | 161 | try: 162 | self.name = str(name) 163 | except TypeError: 164 | print('name must be able to convert to str type') 165 | 166 | if Dfunc is not None: 167 | if len(Dfunc) != self.Np: 168 | raise ValueError('Length of Dfunc must be equal to phase number Np') 169 | else: 170 | self.Dfunc = Dfunc 171 | elif X is not None and DC is not None: 172 | try: 173 | X = np.array(X, dtype=float) 174 | DC = np.array(DC, dtype=float) 175 | except TypeError: 176 | print('Can not convert input into 1d-array') 177 | if X.ndim != 1 or DC.ndim != 1: 178 | raise ValueError('1d data is required') 179 | if len(X) != len(DC): 180 | raise ValueError('length of X and DC is not equal') 181 | fD = [0]*self.Np 182 | for i in range(self.Np): 183 | pid = np.where((X >= self.Xr[i, 0]) & (X <= self.Xr[i, 1]))[0] 184 | if len(pid) > 2: 185 | fD[i] = splrep(X[pid], np.log(DC[pid]), k=2) 186 | else: 187 | fD[i] = splrep(X[pid], np.log(DC[pid]), k=1) 188 | self.Dfunc = fD 189 | if Xspl is not None: 190 | if len(Xspl) != self.Np: 191 | raise ValueError('Length of Xspl must be equal to phase number Np') 192 | self.Xspl = Xspl 193 | 194 | def copy(self, Xmax=None): 195 | """ 196 | Method to copy DiffSystem 197 | Concentration can be reversed 198 | 199 | Parameters 200 | ---------- 201 | Xmax : if given, output concentration = Xmax - X 202 | 203 | Examples 204 | -------- 205 | For a Fe-Ni diffusion couple, with known diffusion coefficients as function 206 | of Ni (dsys_Ni), calculate diffusion coefficients as funcition of Fe: 207 | 208 | >>> dsys_Fe = dsys_Ni.copy(Xmax=1.0) 209 | 210 | """ 211 | if Xmax is None: 212 | return DiffSystem(Xr=self.Xr, Dfunc=self.Dfunc, Xspl=self.Xspl, name=self.name) 213 | else: 214 | Xr = Xmax-self.Xr.flatten()[::-1] 215 | Xr = Xr.reshape((self.Np, 2)) 216 | fD = [0]*self.Np 217 | for i in range(self.Np): 218 | k = self.Dfunc[-i-1][2] 219 | X = np.linspace(Xr[i, 0], Xr[i, 1], 30) 220 | DC = splev(Xmax-X, self.Dfunc[-i-1]) 221 | fD[i] = splrep(X, DC, k=k) 222 | Xspl = None if self.Xspl is None else Xmax-self.Xspl[::-1] 223 | return DiffSystem(Xr=Xr, Dfunc=fD, Xspl=Xspl, name=self.name) 224 | 225 | 226 | class DiffError(object): 227 | """ 228 | Error analysis result of binary diffusion system 229 | 230 | Parameters 231 | ---------- 232 | diffsys : DiffSystem 233 | Diffusion system object 234 | loc : array_like 235 | Locations performed error analysis 236 | errors : 2d-array 237 | Error calculated at loc. 238 | data : dict 239 | A dictionary contains all profiles data during error analysis. 240 | data['exp'] : Experimental collected data. 241 | data['ref'] : Reference simulated profile. 242 | data['error'] : Profiles that reaches error_cap during analysis. 243 | 244 | """ 245 | 246 | def __init__(self, diffsys, loc, errors, profiles=None): 247 | self.diffsys = diffsys 248 | self.loc = np.array(loc) 249 | self.errors = errors 250 | if profiles is not None: 251 | self.profiles = profiles 252 | 253 | 254 | class Profile1D(object): 255 | """ 256 | 1D diffusion profile for ternary system 257 | 258 | """ 259 | 260 | def __init__(self, dis, X1, X2, X3=None, name='1D Profile'): 261 | try: 262 | self.dis = np.array(dis, dtype=float) 263 | except TypeError: 264 | print('Can not convert dis input into 1d-array') 265 | 266 | self.n = self.dis.size 267 | 268 | if float(X1) is float: 269 | self.X1 = np.ones(self.n)*float(X1) 270 | elif X1.shape == (self.n, ): 271 | self.X1 = X1 272 | else: 273 | raise ValueError('X1 must be an 2D array of shape (%i, ) or a constant' % self.n) 274 | 275 | if float(X2) is float: 276 | self.X2 = np.ones(self.n)*float(X2) 277 | elif X2.shape == (self.n, ): 278 | self.X2 = X2 279 | else: 280 | raise ValueError('X2 must be an 2D array of shape (%i, ) or a constant' % self.n) 281 | 282 | self.X3 = np.ones(self.n)-self.X1-self.X2 283 | 284 | 285 | class Profile2D(object): 286 | """ 287 | 2D diffusion profile for ternary system 288 | 289 | """ 290 | 291 | def __init__(self, disx, disy, X1, X2, X3=None, name='2D Profile'): 292 | try: 293 | self.disx = np.array(disx, dtype=float) 294 | self.disy = np.array(disy, dtype=float) 295 | except TypeError: 296 | print('Can not convert dis input into 1d-array') 297 | 298 | self.nx = self.disx.size 299 | self.ny = self.disy.size 300 | 301 | if float(X1) is float: 302 | self.X1 = np.ones((self.ny, self.nx))*float(X1) 303 | elif X1.shape == (self.ny, self.nx): 304 | self.X1 = X1 305 | else: 306 | raise ValueError('X1 must be an 2D array of shape (%i, %i) or a float' % (self.ny, self.nx)) 307 | 308 | if float(X2) is float: 309 | self.X2 = np.ones((self.ny, self.nx))*float(X2) 310 | elif X2.shape == (self.ny, self.nx): 311 | self.X2 = X2 312 | else: 313 | raise ValueError('X2 must be an 2D array of shape (%i, %i) or a float' % (self.ny, self.nx)) 314 | 315 | self.X3 = np.ones((self.ny, self.nx))-self.X1-self.X2 316 | 317 | 318 | class TSystem(object): 319 | """ 320 | Ternary diffusion system 321 | 322 | """ 323 | 324 | def __init__(self, Dfunc, name='Ternary System'): 325 | self.fD = Dfunc 326 | -------------------------------------------------------------------------------- /pydiffusion/io.py: -------------------------------------------------------------------------------- 1 | """ 2 | The io module provides support for reading and writing diffusion profile data 3 | and diffusion coefficients data to csv files. 4 | """ 5 | 6 | import numpy as np 7 | import pandas as pd 8 | from scipy.interpolate import splev 9 | from pydiffusion.core import DiffProfile, DiffSystem 10 | import matplotlib.pyplot as plt 11 | import threading 12 | 13 | # To solve the problem when matplotlib figure freezes when input used 14 | # https://stackoverflow.com/questions/34938593/matplotlib-freezes-when-input-used-in-spyder 15 | prompt = False 16 | promptText = "" 17 | done = False 18 | waiting = False 19 | response = "" 20 | 21 | regular_input = input 22 | 23 | 24 | def threadfunc(): 25 | global prompt 26 | global done 27 | global waiting 28 | global response 29 | 30 | while not done: 31 | if prompt: 32 | prompt = False 33 | response = regular_input(promptText) 34 | waiting = True 35 | 36 | 37 | def ask_input(text): 38 | global waiting 39 | global prompt 40 | global promptText 41 | 42 | promptText = text 43 | prompt = True 44 | 45 | while not waiting: 46 | plt.pause(1.0) 47 | waiting = False 48 | 49 | return response 50 | 51 | 52 | def ita_start(): 53 | global done 54 | done = False 55 | thread = threading.Thread(target=threadfunc) 56 | thread.start() 57 | 58 | 59 | def ita_finish(): 60 | global done 61 | done = True 62 | 63 | 64 | def save_csv(name=None, profile=None, diffsys=None): 65 | """ 66 | Save diffusion data as csv file. 67 | 68 | Parameters 69 | ---------- 70 | name : str 71 | csv file name, default name is the profile name of diffsys name. 72 | profile : DiffProfile 73 | DiffProfile to save. 74 | diffsys : DiffSystem 75 | DiffSystem to save. diffsys can be saved by itself or with profile. 76 | 77 | Examples 78 | -------- 79 | 80 | >>> save_csv('data.csv', profile, dsys) 81 | 82 | """ 83 | if profile is None and diffsys is None: 84 | raise ValueError('No data entered') 85 | if name is not None and not name.endswith('.csv'): 86 | name += str(name)+'.csv' 87 | elif profile is None: 88 | Xr, fD = diffsys.Xr, diffsys.Dfunc 89 | X, DC = np.array([]), np.array([]) 90 | for i in range(diffsys.Np): 91 | Xnew = np.linspace(Xr[i, 0], Xr[i, 1], 30) 92 | Dnew = np.exp(splev(Xnew, fD[i])) 93 | X = np.append(X, Xnew) 94 | DC = np.append(DC, Dnew) 95 | data = pd.DataFrame({'X': X, 'DC': DC}) 96 | if name is None: 97 | name = diffsys.name+'.csv' 98 | data.to_csv(name, index=False) 99 | elif diffsys is None: 100 | data = pd.DataFrame({'dis': profile.dis, 'X': profile.X}) 101 | if name is None: 102 | name = profile.name+'.csv' 103 | data.to_csv(name, index=False) 104 | else: 105 | dis, X, Xr, fD = profile.dis, profile.X, diffsys.Xr, diffsys.Dfunc 106 | DC = np.zeros(len(dis)) 107 | for i in range(diffsys.Np): 108 | pid = np.where((X >= Xr[i, 0]) & (X <= Xr[i, 1]))[0] 109 | DC[pid] = np.exp(splev(X[pid], fD[i])) 110 | data = pd.DataFrame({'dis': dis, 'X': X, 'DC': DC}) 111 | if name is None: 112 | name = profile.name+'.csv' 113 | data.to_csv(name, index=False) 114 | 115 | 116 | def read_csv(filename, Xlim=None, name=''): 117 | """ 118 | Read diffusion data from csv file. 119 | 120 | Parameters 121 | ---------- 122 | filename : str 123 | csv file path. 124 | Xlim : list (length of 2), optional 125 | A list to determine the two ends solubilities. 126 | name : str, optional 127 | Name the output DiffProfile and DiffSystem 128 | 129 | Returns 130 | ------- 131 | profile : DiffProfile 132 | output DiffProfile object. 133 | diffsys : DiffSystem 134 | output DiffSystem object. 135 | 136 | Examples 137 | -------- 138 | Both profile and diffusivity data: 139 | 140 | >>> profile, dsys = read_csv('data.csv') 141 | 142 | Profile data only: 143 | 144 | >>> profile, _ = read_csv('data.csv') 145 | 146 | """ 147 | data = pd.read_csv(filename) 148 | if 'X' not in data.columns: 149 | raise ValueError('No column X in csv file') 150 | X = np.array(data['X']) 151 | 152 | # Auto rename 153 | if name == '': 154 | if '/' in filename: 155 | r = filename.rfind('/') 156 | elif '\\' in filename: 157 | r = filename.rfind('\\') 158 | else: 159 | r = -1 160 | if filename.endswith('.csv'): 161 | name = filename[r+1:-4] 162 | else: 163 | name = filename[r+1:] 164 | 165 | if 'dis' in data.columns: 166 | dis = np.array(data['dis']) 167 | If = [] 168 | XIf = [] 169 | for i in range(len(dis)-1): 170 | if dis[i] == dis[i+1]: 171 | If += [dis[i]] 172 | XIf += [X[i], X[i+1]] 173 | profile = DiffProfile(dis, X, If, name=name) 174 | if Xlim is None: 175 | XIf = np.array([X[0]] + XIf + [X[-1]]) 176 | else: 177 | XIf = np.array([Xlim[0]] + XIf + [Xlim[-1]]) 178 | Xr = XIf.reshape((len(XIf)//2, 2)) 179 | if 'DC' in data.columns: 180 | DC = np.array(data['DC']) 181 | else: 182 | X = DC = None 183 | if Xr is not None: 184 | diffsys = DiffSystem(Xr, X=X, DC=DC, name=name) 185 | return profile, diffsys 186 | -------------------------------------------------------------------------------- /pydiffusion/plot.py: -------------------------------------------------------------------------------- 1 | """ 2 | The plot module provides support for visualization of diffusion profile data 3 | and diffusion coefficients data using matplotlib. 4 | """ 5 | 6 | import itertools 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | from scipy.interpolate import splev 10 | from pydiffusion.utils import SF 11 | 12 | # Default label font size 13 | label_fontsize = 13 14 | # Default tick font size 15 | tick_fontsize = 11 16 | # Default legend font size 17 | leg_fontsize = 13 18 | 19 | 20 | def plot_lim(x1, x2, log=False): 21 | """ 22 | Automatically generate plot range for diffusion plots 23 | 24 | Parameters 25 | ---------- 26 | x1, x2 : float 27 | Plot range, can be concentration or diffusivity values. 28 | log : bool, optional 29 | Range in log scale or not. 30 | 31 | Returns 32 | ------- 33 | x1_lim, x2_lim : float 34 | Plot range output, used for plt.xlim or plt.set_ylim. 35 | 36 | """ 37 | x1, x2 = min(x1, x2), max(x1, x2) 38 | if not log: 39 | x1_lim = (x1*100//1)/100 40 | x2_lim = (x2*100//1+1)/100 41 | else: 42 | x1_lim = 10**np.floor(np.log10(x1)) 43 | x2_lim = 10**(np.floor(np.log10(x2))+1) 44 | return x1_lim, x2_lim 45 | 46 | 47 | def profileplot(profile, ax=None, err=None, **kwargs): 48 | """ 49 | Plot diffusion profiles 50 | 51 | Parameters 52 | ---------- 53 | profile : DiffProfile 54 | Diffusion profile object 55 | ax : matplotlib.Axes 56 | Default axes used if not specified 57 | kwargs : kwargs 58 | Passed to 'matplotlib.pyplot.plot', add label to name the profile. 59 | 60 | Returns 61 | ------- 62 | matplotlib AxesSubplot 63 | 64 | Examples 65 | -------- 66 | Plot two profiles on one axis: 67 | 68 | >>> ax = profileplot(profile1, c='b', label='profile1') 69 | >>> profileplot(profile2, ax, c='r', label='profile2') 70 | 71 | """ 72 | dis, X = profile.dis, profile.X 73 | clw = {'lw': 2, 'label': profile.name} 74 | args = {**clw, **kwargs} 75 | if ax is None: 76 | fig = plt.figure() 77 | ax = fig.add_subplot(111) 78 | ax.plot(dis, X, **args) 79 | 80 | # Error analysis result plot 81 | if err is not None: 82 | profiles = err.profiles['error'] 83 | disp = np.linspace(dis[0], dis[-1], 1e4) 84 | Xp = np.zeros((2, len(disp))) 85 | for i in range(2): 86 | Xp[i] = splev(disp, profiles[i]) 87 | p = ax.plot(disp, Xp[0], '--', lw=2, label='Error') 88 | ax.plot(disp, Xp[1], '--', c=p[0].get_color(), lw=2) 89 | 90 | ax.set_xlabel('Distance (micron)', fontsize=label_fontsize) 91 | ax.set_ylabel('Mole fraction', fontsize=label_fontsize) 92 | ax.set_xlim(dis.min(), dis.max()) 93 | ax.set_ylim(plot_lim(X.min(), X.max())) 94 | ax.tick_params(labelsize=tick_fontsize) 95 | leg = ax.legend(numpoints=1, fontsize=leg_fontsize) 96 | leg.get_frame().set_linewidth(0.0) 97 | leg.set_draggable(True) 98 | plt.tight_layout() 99 | 100 | return ax 101 | 102 | 103 | def SFplot(profile, time, Xlim=[], ax=None, **kwargs): 104 | """ 105 | Plot Sauer-Fraise calculated diffusion coefficients 106 | 107 | Parameters 108 | ---------- 109 | profile : DiffProfile 110 | Diffusion profile object, passed to 'pydiffusion.Dmodel.SF' 111 | time : float 112 | Passed to 'pydiffusion.Dmodel.SF' 113 | Xlim : list 114 | Passed to 'pydiffusion.Dmodel.SF' 115 | ax : matplotlib.Axes 116 | Default axes used if not specified 117 | kwargs : kwargs 118 | Passed to 'matplotlib.pyplot.semilogy', , add label to name the D curve. 119 | 120 | Returns 121 | ------- 122 | matplotlib AxesSubplot 123 | 124 | """ 125 | X = profile.X 126 | sf = SF(profile, time, Xlim) 127 | clw = {'marker': '.', 'ls': 'none'} 128 | if 'label' not in kwargs: 129 | clw['label'] = profile.name+'_%.1fh_SF' % (time/3600) 130 | args = {**clw, **kwargs} 131 | if ax is None: 132 | fig = plt.figure() 133 | ax = fig.add_subplot(111) 134 | ax.semilogy(X, sf, **args) 135 | ax.set_xlabel('Mole fraction', fontsize=label_fontsize) 136 | ax.set_ylabel('Diffusion Coefficients '+'$(m^2/s)$', fontsize=label_fontsize) 137 | ax.set_xlim(plot_lim(X.min(), X.max())) 138 | ax.tick_params(labelsize=tick_fontsize) 139 | leg = ax.legend(numpoints=1, fontsize=leg_fontsize) 140 | leg.get_frame().set_linewidth(0.0) 141 | leg.set_draggable(True) 142 | plt.tight_layout() 143 | 144 | return ax 145 | 146 | 147 | def DCplot(diffsys, ax=None, err=None, **kwargs): 148 | """ 149 | Plot diffusion coefficients 150 | 151 | Parameters 152 | ---------- 153 | diffsys : DiffProfile 154 | Diffusion system object 155 | ax : matplotlib.Axes 156 | Default axes used if not specified 157 | err : DiffError 158 | Error analysis result 159 | kwargs : kwargs 160 | Passed to 'matplotlib.pyplot.semilogy', add label to name the D curve. 161 | 162 | Returns 163 | ------- 164 | matplotlib AxesSubplot 165 | 166 | Compare two diffusion coefficients data on one axis: 167 | 168 | >>> ax = DCplot(dsys1, c='b', label='DC1') 169 | >>> DCplot(dsys2, ax, c='r', label='DC2') 170 | 171 | """ 172 | if ax is None: 173 | fig = plt.figure() 174 | ax = fig.add_subplot(111) 175 | Np, Xr, fD = diffsys.Np, diffsys.Xr, diffsys.Dfunc 176 | clw = {'lw': 2, 'label': diffsys.name} 177 | args = {**clw, **kwargs} 178 | # Diffusion Coefficients plot 179 | for i in range(Np): 180 | Xp = np.linspace(Xr[i, 0], Xr[i, 1], 51) 181 | Dp = np.exp(splev(Xp, fD[i])) 182 | if i == 0: 183 | Dmin, Dmax = min(Dp), max(Dp) 184 | p = ax.semilogy(Xp, Dp, **args) 185 | clw_nl = {'c': p[0].get_color(), 'label': '_nolegend_'} 186 | args_nl = {**args, **clw_nl} 187 | else: 188 | Dmin, Dmax = min(Dmin, min(Dp)), max(Dmax, max(Dp)) 189 | ax.semilogy(Xp, Dp, **args_nl) 190 | plt.tight_layout() 191 | 192 | # Error analysis result plot 193 | if err is not None: 194 | loc, errors = err.loc, err.errors 195 | for i in range(Np): 196 | pid = np.where((loc >= Xr[i, 0]) & (loc <= Xr[i, 1]))[0] 197 | Dloc = np.exp(splev(loc[pid], fD[i])) 198 | if i == 0: 199 | p = ax.semilogy(loc[pid], Dloc * 10**errors[pid, 0], '--', lw=2, label='Error') 200 | else: 201 | p = ax.semilogy(loc[pid], Dloc * 10**errors[pid, 0], '--', lw=2) 202 | ax.semilogy(loc[pid], Dloc * 10**errors[pid, 1], '--', c=p[0].get_color(), lw=2) 203 | 204 | ax.set_xlim(plot_lim(Xr[0, 0], Xr[-1, 1])) 205 | ax.set_ylim(plot_lim(Dmin, Dmax, True)) 206 | ax.set_xlabel('Mole fraction', fontsize=label_fontsize) 207 | ax.set_ylabel('Diffusion Coefficients '+'$(m^2/s)$', fontsize=label_fontsize) 208 | ax.tick_params(labelsize=tick_fontsize) 209 | leg = ax.legend(numpoints=1, fontsize=leg_fontsize) 210 | leg.get_frame().set_linewidth(0.0) 211 | leg.set_draggable(True) 212 | plt.tight_layout() 213 | 214 | return ax 215 | 216 | 217 | def colorcalc(X1, X2, r=1): 218 | """ 219 | Calculate RGB color based on ternary compositions. 220 | 221 | Parameters 222 | ---------- 223 | X1, X2 : array-like 224 | Composition 2D arrays. 225 | X1 = 1 corresponds to red. 226 | X2 = 1 corresponds to green. 227 | X1 = X2 = 0 corresponds to blue. 228 | r : float, >0 229 | r controls the color sensitivity to compositions. 230 | Higher r, higher sensitivity. 231 | 232 | """ 233 | X1[X1 < 0], X2[X2 < 0] = 0, 0 234 | X1[X1 > 1], X2[X2 > 1] = 1, 1 235 | X3 = 1-X1-X2 236 | c1, c2, c3 = np.copy(X1), np.copy(X2), np.copy(X3) 237 | for i, (x1, x2, x3) in enumerate(itertools.permutations([X1, X2, X3])): 238 | ip = (x1 <= x2) & (x2 <= x3) & (x3 < 1) 239 | x, y = x3[ip]+.5*x1[ip], x1[ip]*3**.5/2 240 | p = (x-.5)/.5 241 | q = (.5-p**r)/(1-x) 242 | xn, yn = 1-q*(1-x), y*q 243 | a, b, c = yn*2/3**.5, 1-xn-yn/3**.5, xn-yn/3**.5 244 | if i == 0: 245 | c1[ip], c2[ip], c3[ip] = a, b, c 246 | elif i == 1: 247 | c1[ip], c3[ip], c2[ip] = a, b, c 248 | elif i == 2: 249 | c2[ip], c1[ip], c3[ip] = a, b, c 250 | elif i == 3: 251 | c2[ip], c3[ip], c1[ip] = a, b, c 252 | elif i == 4: 253 | c3[ip], c1[ip], c2[ip] = a, b, c 254 | else: 255 | c3[ip], c2[ip], c1[ip] = a, b, c 256 | color = np.stack([c1, c2, c3], axis=2) 257 | e = 1/color.max(2) 258 | return color*np.stack([e, e, e], axis=2) 259 | -------------------------------------------------------------------------------- /pydiffusion/simulation.py: -------------------------------------------------------------------------------- 1 | """ 2 | The simulation module contains diffusion simulation function and 3 | simulation-based error analysis tools. 4 | """ 5 | 6 | import numpy as np 7 | from scipy.interpolate import splev, splrep 8 | from pydiffusion.core import DiffProfile, DiffError, Profile1D, Profile2D 9 | from pydiffusion.utils import error_profile, DCbias, profilefunc, polyval2d 10 | 11 | 12 | def sphSim(profile, diffsys, time, output=True, name=''): 13 | """ 14 | Single-Phase Diffusion Simulation 15 | 16 | Parameters 17 | ---------- 18 | profile : DiffProfile 19 | Initial diffusion profile before simulation. 20 | diffsys : DiffSystem 21 | Diffusion coefficients. 22 | time : float 23 | time in seconds. 24 | output : boolean, optional 25 | Print simulation progress, default = True. 26 | name : str, optional 27 | Name the output DiffProfile. 28 | 29 | Returns 30 | ------- 31 | profile : DiffProfile 32 | Simulated diffusion profile 33 | 34 | """ 35 | if name == '': 36 | name = diffsys.name+'_%.1fh' % (time/3600) 37 | 38 | dis, Xs = profile.dis.copy()/1e6, profile.X.copy() 39 | fD = diffsys.Dfunc 40 | d = dis[1:]-dis[:-1] 41 | dj = 0.5*(d[1:]+d[:-1]) 42 | t, m = 0.0, 0 43 | while t < time: 44 | Xm = 0.5*(Xs[1:]+Xs[:-1]) 45 | DCs = np.exp(splev(Xm, fD[0])) 46 | dt = min(d**2/DCs/2) 47 | dt = time-t if t+dt > time else dt*0.95 48 | t += dt 49 | m += 1 50 | Jf = -DCs*(Xs[1:]-Xs[:-1])/d 51 | Xs[1:-1] = Xs[1:-1]-dt*(Jf[1:]-Jf[:-1])/dj 52 | Xs[0] -= Jf[0]/d[0]*dt*2 53 | Xs[-1] += Jf[-1]/d[-1]*dt*2 54 | if output and np.mod(m, 3e4) == 0: 55 | print('%.3f/%.3f hrs simulated' % (t/3600, time/3600)) 56 | if output: 57 | print('Simulation Complete') 58 | return DiffProfile(dis*1e6, Xs, name=name) 59 | 60 | 61 | def mphSim(profile, diffsys, time, liquid=0, output=True, name=''): 62 | """ 63 | Single/Multiple-Phase Diffusion Simulation. Liquid phase can be attached 64 | at left or right end. For thin film simulation, please set up the 65 | interface nearby the end in the initial profile. 66 | 67 | Parameters 68 | ---------- 69 | profile : DiffProfile 70 | Initial profile before simulation. 71 | diffsys : DiffSystem 72 | Diffusion coefficients. 73 | time : float 74 | time in seconds. 75 | liquid : 1, 0 or -1, optional 76 | Liquid phase provide infinite mass flux during simulation, can only be 77 | attached at left or right end. 78 | 0 : No liquid phase attached. 79 | -1 : Liquid phase attached at left. 80 | 1 : Liquid phase attached at right. 81 | output : boolean, optional 82 | Print simulation progress, default = True. 83 | name : str, optional 84 | Name the output DiffProfile. 85 | 86 | Returns 87 | ------- 88 | profile : DiffProfile 89 | Simulated diffusion profile 90 | 91 | Examples 92 | -------- 93 | With known diffusion coefficients (dsys), simulate profile of 100 hours of diffusion 94 | for a diffusion couple experiment (initial profile is a step) on a 600 micron grids: 95 | 96 | >>> dis = mesh(0, 600) 97 | >>> init_profile = step(dis, 300, dsys) 98 | >>> final_profile = mphSim(init_profile, dsys, 3600*100) 99 | 100 | If liquid phase is attached to the left: 101 | 102 | >>> final_profile = mphSim(init_profile, dsys, 3600*100, liquid=-1) 103 | 104 | """ 105 | dis, Xs = profile.dis.copy()/1e6, profile.X.copy() 106 | Ip, If = profile.Ip.copy(), profile.If.copy()/1e6 107 | Np, Xr = diffsys.Np, diffsys.Xr.copy() 108 | fD = [f for f in diffsys.Dfunc] 109 | 110 | # Ascending or descending profile 111 | if (Xs[-1]-Xs[0]) * (Xr[-1, 1]-Xr[0, 0]) < 0: 112 | Xr = Xr.flatten()[::-1].reshape((Np, 2)) 113 | fD = fD[::-1] 114 | 115 | if name == '': 116 | name = diffsys.name+'_%.1fh' % (time/3600) 117 | 118 | if len(If) != Np+1: 119 | raise ValueError('Number of phases mismatches between profile and diffusion system') 120 | if liquid not in [-1, 0, 1]: 121 | raise ValueError('liquid can only be 0 1 or -1') 122 | try: 123 | time = float(time) 124 | except TypeError: 125 | print('Wrong Type of time') 126 | 127 | d = dis[1:]-dis[:-1] 128 | dj = 0.5*(d[1:]+d[:-1]) 129 | Jf, DCs = np.zeros(len(dis)-1), np.zeros(len(dis)-1) 130 | JIf = np.zeros([Np+1, 2]) 131 | vIf = np.zeros(Np+1) 132 | t, m = 0.0, 0 133 | dt0 = time/1e3 134 | 135 | while t < time: 136 | 137 | Xm = (Xs[:-1]+Xs[1:])/2 138 | dt = dt0 139 | 140 | # Jf JIf calculation 141 | # dt limited by simulation stability inside each phases 142 | for i in range(Np): 143 | if Ip[i+1] > Ip[i]+1: 144 | Ipr = np.arange(Ip[i], Ip[i+1]-1) 145 | DCs[Ipr] = np.exp(splev(Xm[Ipr], fD[i])) 146 | Jf[Ipr] = -DCs[Ipr]*(Xs[Ipr+1]-Xs[Ipr])/d[Ipr] 147 | dtn = np.abs(d[Ipr]**2/DCs[Ipr]/2).min() 148 | dt = dtn if dtn < dt else dt 149 | if Ip[i+1] == Ip[i]: 150 | X0 = np.mean(Xr[i]) 151 | JIf[i, 1] = JIf[i+1, 0] = -(Xr[i, 1]-Xr[i, 0])/(If[i+1]-If[i])*np.exp(splev(X0, fD[i])) 152 | else: 153 | if i < Np-1: 154 | X0 = 0.5*(Xr[i, 1]+Xs[Ip[i+1]-1]) 155 | 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])) 156 | if i > 0: 157 | X0 = 0.5*(Xr[i, 0]+Xs[Ip[i]]) 158 | JIf[i, 1] = -(Xs[Ip[i]]-Xr[i, 0])/(dis[Ip[i]]-If[i])*np.exp(splev(X0, fD[i])) 159 | 160 | # vIf calculation, dt limited by 'No interface passing by' 161 | for i in range(1, Np): 162 | vIf[i] = (JIf[i, 1]-JIf[i, 0])/(Xr[i, 0]-Xr[i-1, 1]) 163 | vid = [Ip[i]-2] if Ip[i] > 1 else [] 164 | vid += [Ip[i]] if Ip[i] < len(dis) else [] 165 | dtn = np.abs(d[vid]/vIf[i]).min() 166 | dt = dtn if dtn < dt else dt 167 | if i > 1 and vIf[i-1] > vIf[i]: 168 | dtn = (If[i]-If[i-1])/(vIf[i-1]-vIf[i])/2 169 | dt = dtn if dtn < dt else dt 170 | 171 | # dt limited by grid nearby interfaces cannot exceed solubility limit 172 | if Xr[0, 0] < Xr[-1, 1]: 173 | for i in range(Np): 174 | if Ip[i+1] == Ip[i]: 175 | continue 176 | elif Ip[i+1] == Ip[i]+1: 177 | if JIf[i, 1] > JIf[i+1, 0]: 178 | dtn = (Xr[i, 1]-Xs[Ip[i]])*dj[Ip[i]-1]/(JIf[i, 1]-JIf[i+1, 0]) 179 | dt = dtn if dtn < dt else dt 180 | elif JIf[i, 1] < JIf[i+1, 0]: 181 | dtn = (Xs[Ip[i]]-Xr[i, 0])*dj[Ip[i]-1]/(JIf[i+1, 0]-JIf[i, 1]) 182 | dt = dtn if dtn < dt else dt 183 | else: 184 | if i < Np-1 and JIf[i+1, 0] < Jf[Ip[i+1]-2]: 185 | dtn = -(Xr[i, 1]-Xs[Ip[i+1]-1])/(JIf[i+1, 0]-Jf[Ip[i+1]-2])*dj[Ip[i+1]-2] 186 | dt = dtn if dtn < dt else dt 187 | if i > 0 and Jf[Ip[i]] > JIf[i, 1]: 188 | dtn = (Xs[Ip[i]]-Xr[i, 0])/(Jf[Ip[i]]-JIf[i, 1])*dj[Ip[i]-1] 189 | dt = dtn if dtn < dt else dt 190 | else: 191 | for i in range(Np): 192 | if Ip[i+1] == Ip[i]: 193 | continue 194 | elif Ip[i+1] == Ip[i]+1: 195 | if JIf[i, 1] < JIf[i+1, 0]: 196 | dtn = (Xr[i, 1]-Xs[Ip[i]])*dj[Ip[i]-1]/(JIf[i, 1]-JIf[i+1, 0]) 197 | dt = dtn if dtn < dt else dt 198 | elif JIf[i, 1] > JIf[i+1, 0]: 199 | dtn = (Xs[Ip[i]]-Xr[i, 0])*dj[Ip[i]-1]/(JIf[i+1, 0]-JIf[i, 1]) 200 | dt = dtn if dtn < dt else dt 201 | else: 202 | if i < Np-1 and JIf[i+1, 0] > Jf[Ip[i+1]-2]: 203 | dtn = -(Xr[i, 1]-Xs[Ip[i+1]-1])/(JIf[i+1, 0]-Jf[Ip[i+1]-2])*dj[Ip[i+1]-2] 204 | dt = dtn if dtn < dt else dt 205 | if i > 0 and Jf[Ip[i]] < JIf[i, 1]: 206 | dtn = (Xs[Ip[i]]-Xr[i, 0])/(Jf[Ip[i]]-JIf[i, 1])*dj[Ip[i]-1] 207 | dt = dtn if dtn < dt else dt 208 | 209 | dt = time-t if t+dt > time else dt*0.95 210 | 211 | # If first or last phase will be consumed 212 | if If[1] < dis[1] and vIf[1] < 0: 213 | dtn = (dis[0]-If[1])/vIf[1] 214 | dt = dtn if dtn < dt else dt 215 | elif If[-2] > dis[-2] and vIf[-2] > 0: 216 | dtn = (dis[-1]-If[-2])/vIf[-2] 217 | dt = dtn if dtn < dt else dt 218 | 219 | t += dt 220 | m += 1 221 | 222 | # Ficks 2nd law inside each phase 223 | for i in range(Np): 224 | if Ip[i+1] == Ip[i]: 225 | continue 226 | elif Ip[i+1] == Ip[i]+1: 227 | Xs[Ip[i]] -= dt*(JIf[i+1, 0]-JIf[i, 1])/dj[Ip[i]-1] 228 | else: 229 | if i > 0: 230 | Xs[Ip[i]] -= dt*(Jf[Ip[i]]-JIf[i, 1])/dj[Ip[i]-1] 231 | if i < Np-1: 232 | Xs[Ip[i+1]-1] -= dt*(JIf[i+1, 0]-Jf[Ip[i+1]-2])/dj[Ip[i+1]-2] 233 | if Ip[i+1] > Ip[i]+2: 234 | Ipr = np.arange(Ip[i]+1, Ip[i+1]-1) 235 | Xs[Ipr] -= dt*(Jf[Ipr]-Jf[Ipr-1])/dj[Ipr-1] 236 | 237 | # Composition changes at first & last grid 238 | # If there is liquid phase attached, composition unchanged. 239 | if liquid != -1: 240 | Xs[0] -= Jf[0]/d[0]*dt 241 | if liquid != 1: 242 | Xs[-1] += Jf[-1]/d[-1]*dt 243 | 244 | # If one phase consumed, delete this phase 245 | if If[1]+vIf[1]*dt <= dis[0]: 246 | Xs[0] = Xr[1, 0] 247 | Np -= 1 248 | Xr, If, Ip, fD = Xr[1:], If[1:], Ip[1:], fD[1:] 249 | Ip[0] = 0 250 | JIf = np.zeros([Np+1, 2]) 251 | vIf = np.zeros(Np+1) 252 | if output: 253 | print('First phase consumed, %i phase(s) left, time = %.3f hrs' % (Np, t/3600)) 254 | elif If[-2]+vIf[-2]*dt >= dis[-1]: 255 | Xs[-1] = Xr[-2, 1] 256 | Np -= 1 257 | Xr, If, Ip, fD = Xr[:-1], If[:-1], Ip[:-1], fD[:-1] 258 | Ip[-1] = len(dis) 259 | JIf = np.zeros([Np+1, 2]) 260 | vIf = np.zeros(Np+1) 261 | if output: 262 | print('Last phase consumed, %i phase(s) left, time = %.3f hrs' % (Np, t/3600)) 263 | 264 | # Interface move across one grid, passed grid has value change 265 | for i in range(1, Np): 266 | If[i] += vIf[i]*dt 267 | if If[i] < dis[Ip[i]-1]: 268 | Ip[i] -= 1 269 | if If[i+1] < dis[Ip[i]+1]: 270 | Xs[Ip[i]] = splev(dis[Ip[i]], 271 | splrep([If[i], If[i+1]], Xr[i], k=1)) 272 | else: 273 | Xs[Ip[i]] = splev(dis[Ip[i]], 274 | splrep([If[i], dis[Ip[i]+1]], 275 | [Xr[i, 0], Xs[Ip[i]+1]], k=1)) 276 | elif If[i] > dis[Ip[i]]: 277 | Ip[i] += 1 278 | if If[i-1] > dis[Ip[i]-2]: 279 | Xs[Ip[i]-1] = splev(dis[Ip[i]-1], 280 | splrep([If[i-1], If[i]], Xr[i-1], k=1)) 281 | else: 282 | Xs[Ip[i]-1] = splev(dis[Ip[i]-1], 283 | splrep([dis[Ip[i]-2], If[i]], 284 | [Xs[Ip[i]-2], Xr[i-1, 1]], k=1)) 285 | 286 | if output and np.mod(m, 3e4) == 0: 287 | print('%.3f/%.3f hrs simulated' % (t/3600, time/3600)) 288 | if output: 289 | print('Simulation Complete') 290 | 291 | # Insert interface informations 292 | for i in list(range(Np-1, 0, -1)): 293 | dis = np.insert(dis, Ip[i], [If[i], If[i]]) 294 | Xs = np.insert(Xs, Ip[i], [Xr[i-1, 1], Xr[i, 0]]) 295 | 296 | return DiffProfile(dis*1e6, Xs, If[1:-1]*1e6, name=name) 297 | 298 | 299 | def ErrorAnalysis(profile_exp, profile_init, diffsys, time, loc=10, w=None, 300 | r=0.3, efunc=None, accuracy=1e-3, output=False): 301 | """ 302 | Error analysis of diffusion coefficients through comparison with experimental 303 | data. 304 | 305 | Parameters 306 | ---------- 307 | profile_exp : DiffProfile 308 | Experiment measured diffusion profile, which is used to compared against 309 | each simulation result. 310 | profile_init : DiffProfile 311 | The initial profile for diffusion simulation, usually a step profile. 312 | diffsys : DiffSystem 313 | Reference diffusion coefficients. Simulation based on this datasets will 314 | be the reference for the error analysis. 315 | Bias will then be applied to this diffusivity datasets before each 316 | simulation. 317 | time : float 318 | Diffusion time in seconds. 319 | loc : list or int 320 | loc indicates the locations to perform error analysis. If loc is an 321 | integer, loc points are selected inside each phase. Each point has 322 | both positive and negative error to be calculated. 323 | w : list, optional 324 | Weights of each phase to calculate error. 325 | Passed to 'pydiffusion.utils.error_profile'. 326 | r : float, optional 327 | The concentration range of bias. 328 | Passed to 'pydiffusion.utils.DCbias' 329 | efunc : function, optional 330 | Function to create bias. 331 | Passed to 'pydiffusion.utils.DCbias' 332 | accuracy : float 333 | Stop criterion of each simulation: within error_cap * (1+-accuracy). 334 | Low accuracy value may increase simulation times for each point. 335 | output : boolean, optional 336 | Print each simulation results, default = False. 337 | 338 | Returns 339 | ------- 340 | differror : DiffError 341 | Diffusion error object 342 | """ 343 | if len(profile_init.If) != diffsys.Np+1: 344 | raise ValueError('Number of phases mismatches between profile and diffusion system') 345 | try: 346 | time = float(time) 347 | except TypeError: 348 | print('Wrong type for time variable') 349 | 350 | profile_ref = mphSim(profile_init, diffsys, time, output=output) 351 | error_ref = error_profile(profile_ref, profile_exp, w) 352 | ipt = input('Reference error= % .6f. Input cap error: [% .6f]' % (error_ref, error_ref*1.01)) 353 | error_cap = error_ref*1.01 if ipt == '' else float(ipt) 354 | print('Cap error = % .6f' % error_cap) 355 | 356 | if isinstance(loc, int): 357 | n = loc 358 | loc = np.array([]) 359 | for i in range(diffsys.Np): 360 | loc = np.append(loc, np.linspace(diffsys.Xr[i, 0], diffsys.Xr[i, 1], n)) 361 | 362 | dis_compare = np.linspace(profile_init.dis[0], profile_init.dis[-1], 1e4) 363 | profile_compare = np.zeros((3, len(dis_compare))) 364 | profile_compare[0] = splev(dis_compare, profilefunc(profile_ref)) 365 | profile_compare[2] = splev(dis_compare, profilefunc(profile_ref)) 366 | 367 | errors = np.zeros((len(loc), 2)) 368 | deltaD_init = [0.5, -0.5] 369 | for i in range(len(loc)): 370 | X = loc[i] 371 | for p in range(2): 372 | n_sim = 0 373 | deltaD = deltaD_init[p]*1.1 374 | De, Xe = [0], [error_ref] 375 | while True: 376 | n_sim += 1 377 | diffsys_error = DCbias(diffsys, X, deltaD, r, efunc) 378 | profile_error = mphSim(profile_init, diffsys_error, time, output=output) 379 | error_sim = error_profile(profile_error, profile_exp, w) 380 | if output: 381 | print('At %.3f, simulation #%i, deltaD = %f, profile difference = %f(%f)' 382 | % (X, n_sim, deltaD, error_sim, error_cap)) 383 | 384 | if error_sim <= error_cap: 385 | profile_new = profilefunc(profile_error) 386 | profile_compare[1] = splev(dis_compare, profile_new) 387 | profile_compare[0] = profile_compare[:2].min(0) 388 | profile_compare[2] = profile_compare[1:].max(0) 389 | 390 | if error_sim > error_cap*(1-accuracy) and error_sim <= error_cap: 391 | break 392 | 393 | if len(De) == 1: 394 | if error_sim > error_cap: 395 | De += [deltaD] 396 | Xe += [error_sim] 397 | fe = splrep(Xe, De, k=1) 398 | deltaD = float(splev(error_cap, fe)) 399 | else: 400 | De, Xe = [deltaD], [error_sim] 401 | deltaD *= 2 402 | else: 403 | j = 1 if error_sim > error_cap else 0 404 | De[j], Xe[j] = deltaD, error_sim 405 | 406 | if (Xe[1]-Xe[0])/abs(De[1]-De[0]) > 10: 407 | deltaD, error_sim = De[0], Xe[0] 408 | if output: 409 | print('Jump between %f and %f' % (De[0], De[1])) 410 | break 411 | elif n_sim > 6: 412 | deltaD = np.mean(De) 413 | else: 414 | fe = splrep(Xe, De, k=1) 415 | deltaD = float(splev(error_cap, fe)) 416 | direction = 'positive' if p == 0 else 'negative' 417 | print('Error (%s) at %.3f = %f, %i simulations performed, profile difference = %f' 418 | % (direction, X, deltaD, n_sim, error_sim)) 419 | errors[i, p] = deltaD 420 | deltaD_init[p] = deltaD 421 | profiles = (splrep(dis_compare, profile_compare[0], k=1), 422 | splrep(dis_compare, profile_compare[2], k=1)) 423 | data = {} 424 | data['exp'] = profile_exp 425 | data['ref'] = profile_ref 426 | data['error'] = profiles 427 | 428 | print('Error analysis complete') 429 | differror = DiffError(diffsys, loc, errors, data) 430 | 431 | return differror 432 | 433 | 434 | def T1DsphSim(profile, tsys, time, name=''): 435 | """ 436 | 1D diffusion simulation of single-phase ternary system. 437 | 438 | Parameters 439 | ---------- 440 | profile : Profile1D 441 | Initial profile before simulation. 442 | tsys : TSystem 443 | Diffusion coefficients. 444 | time : float 445 | Diffusion time in seconds. 446 | name : str 447 | Name the output profile. 448 | 449 | Returns 450 | ------- 451 | Profile1D 452 | Final diffusion profile. 453 | 454 | """ 455 | if name == '': 456 | name = tsys.name+'_%.1fh' % (time/3600) 457 | 458 | dis = profile.dis 459 | X1, X2 = np.copy(profile.X1), np.copy(profile.X2) 460 | fD11, fD12, fD21, fD22 = tsys.fD 461 | t, m = 0, 0 462 | d = (dis[-1]-dis[0])/(dis.size-1) 463 | 464 | while t < time: 465 | X1m = (X1[1:]+X1[:-1])/2 466 | X2m = (X2[1:]+X2[:-1])/2 467 | 468 | D11 = np.exp(polyval2d(X1m, X2m, fD11)) 469 | D12 = np.exp(polyval2d(X1m, X2m, fD12)) 470 | D21 = np.exp(polyval2d(X1m, X2m, fD21)) 471 | D22 = np.exp(polyval2d(X1m, X2m, fD22)) 472 | 473 | dt = d**2/max(D11.max(), D12.max(), D21.max(), D22.max())/4 474 | 475 | dt = time-t if t+dt > time else dt*0.95 476 | t += dt 477 | m += 1 478 | 479 | g1 = (X1[1:]-X1[:-1])/d 480 | g2 = (X2[1:]-X2[:-1])/d 481 | 482 | J1 = -D11*g1-D12*g2 483 | J2 = -D21*g1-D22*g2 484 | 485 | X1[1:-1] -= dt*(J1[1:]-J1[:-1])/d 486 | X2[1:-1] -= dt*(J2[1:]-J2[:-1])/d 487 | 488 | X1[X1 < 0] = 0 489 | X2[X2 < 0] = 0 490 | Xsum = X1+X2 491 | X1[Xsum > 1] = X1[Xsum > 1]/Xsum[Xsum > 1] 492 | X2[Xsum > 1] = X2[Xsum > 1]/Xsum[Xsum > 1] 493 | 494 | X1[0] -= J1[0]/d*dt*2 495 | X1[-1] += J1[-1]/d*dt*2 496 | X2[0] -= J2[0]/d*dt*2 497 | X2[-1] += J2[-1]/d*dt*2 498 | 499 | if np.mod(m, 3e4) == 0: 500 | print('1D Simulation %.3f out of %.3f hrs complete' % (t/3600, time/3600)) 501 | 502 | return Profile1D(dis, X1, X2, name=name) 503 | 504 | 505 | def T2DsphSim(profile, tsys, time, name=''): 506 | """ 507 | 2D diffusion simulation of single-phase ternary system. 508 | 509 | Parameters 510 | ---------- 511 | profile : Profile2D 512 | Initial profile before simulation. 513 | tsys : TSystem 514 | Diffusion coefficients. 515 | time : float 516 | Diffusion time in seconds. 517 | name : str 518 | Name the output profile. 519 | 520 | Returns 521 | ------- 522 | Profile2D 523 | Final diffusion profile. 524 | 525 | """ 526 | if name == '': 527 | name = tsys.name+'_%.1fh' % (time/3600) 528 | 529 | disx, disy = profile.disx, profile.disy 530 | nx, ny = profile.nx, profile.ny 531 | X1, X2 = np.copy(profile.X1), np.copy(profile.X2) 532 | fD11, fD12, fD21, fD22 = tsys.fD 533 | t, m = 0, 0 534 | dx = (disx[-1]-disx[0])/(nx-1) 535 | dy = (disy[-1]-disy[0])/(ny-1) 536 | d = min(dx, dy) 537 | 538 | while t < time: 539 | X1xm = (X1[:, 1:]+X1[:, :-1])/2 540 | X1ym = (X1[1:, :]+X1[:-1, :])/2 541 | X2xm = (X2[:, 1:]+X2[:, :-1])/2 542 | X2ym = (X2[1:, :]+X2[:-1, :])/2 543 | 544 | g1x = (X1[:, 1:]-X1[:, :-1])/dx 545 | g1y = (X1[1:, :]-X1[:-1, :])/dy 546 | g2x = (X2[:, 1:]-X2[:, :-1])/dx 547 | g2y = (X2[1:, :]-X2[:-1, :])/dy 548 | 549 | D11x = np.exp(polyval2d(X1xm, X2xm, fD11)) 550 | D11y = np.exp(polyval2d(X1ym, X2ym, fD11)) 551 | D12x = np.exp(polyval2d(X1xm, X2xm, fD12)) 552 | D12y = np.exp(polyval2d(X1ym, X2ym, fD12)) 553 | D21x = np.exp(polyval2d(X1xm, X2xm, fD21)) 554 | D21y = np.exp(polyval2d(X1ym, X2ym, fD21)) 555 | D22x = np.exp(polyval2d(X1xm, X2xm, fD22)) 556 | D22y = np.exp(polyval2d(X1ym, X2ym, fD22)) 557 | 558 | dt = d**2/max(D11x.max(), D12x.max(), D21x.max(), D22x.max(), 559 | D11y.max(), D12y.max(), D21y.max(), D22y.max())/4 560 | 561 | dt = time-t if t+dt > time else dt*0.95 562 | t += dt 563 | m += 1 564 | 565 | J1x = -D11x*g1x-D12x*g2x 566 | J1y = -D11y*g1y-D12y*g2y 567 | J2x = -D21x*g1x-D22x*g2x 568 | J2y = -D21y*g1y-D22y*g2y 569 | 570 | X1[:, 1:-1] -= dt*(J1x[:, 1:]-J1x[:, :-1])/dx 571 | X1[1:-1, :] -= dt*(J1y[1:, :]-J1y[:-1, :])/dy 572 | X2[:, 1:-1] -= dt*(J2x[:, 1:]-J2x[:, :-1])/dx 573 | X2[1:-1, :] -= dt*(J2y[1:, :]-J2y[:-1, :])/dy 574 | 575 | X1[X1 < 0] = 0 576 | X2[X2 < 0] = 0 577 | Xsum = X1+X2 578 | X1[Xsum > 1] = X1[Xsum > 1]/Xsum[Xsum > 1] 579 | X2[Xsum > 1] = X2[Xsum > 1]/Xsum[Xsum > 1] 580 | 581 | X1[:, 0] -= J1x[:, 0]/dx*dt*2 582 | X1[:, -1] += J1x[:, -1]/dx*dt*2 583 | X1[0, :] -= J1y[0, :]/dy*dt*2 584 | X1[-1, :] += J1y[-1, :]/dy*dt*2 585 | X2[:, 0] -= J2x[:, 0]/dx*dt*2 586 | X2[:, -1] += J2x[:, -1]/dx*dt*2 587 | X2[0, :] -= J2y[0, :]/dy*dt*2 588 | X2[-1, :] += J2y[-1, :]/dy*dt*2 589 | 590 | if np.mod(m, 3e3) == 0: 591 | print('2D Simulation %.3f out of %.3f hrs complete' % (t/3600, time/3600)) 592 | 593 | return Profile2D(disx, disy, X1, X2, name=name) 594 | -------------------------------------------------------------------------------- /pydiffusion/smooth.py: -------------------------------------------------------------------------------- 1 | """ 2 | The smooth module provides tools for data smoothing of original diffusion 3 | profile data. 4 | """ 5 | 6 | import numpy as np 7 | import matplotlib.pyplot as plt 8 | from scipy.interpolate import splev, splrep 9 | from pydiffusion.core import DiffProfile 10 | from pydiffusion.io import ask_input, ita_start, ita_finish 11 | from pydiffusion.utils import check_mono 12 | 13 | 14 | def movingradius(dis, X, r): 15 | """ 16 | Data smooth using moving average within a radius range 17 | The first and last data point is unchanged, data at d is averaged by data 18 | in the range of (d-r, d+r) 19 | 20 | Parameters 21 | ---------- 22 | dis, X : numpy.array 23 | Diffusion profile required to be smoothed 24 | r : float 25 | Radius (micron) of moving average 26 | 27 | """ 28 | dmin, dmax = dis[0], dis[-1] 29 | n = np.size(dis) 30 | f = splrep(dis, X, k=1) 31 | Xnew = np.zeros(n) 32 | for i in range(n): 33 | h = min(abs(dmin-dis[i]), abs(dmax-dis[i]), r) 34 | Xnew[i] = np.mean(splev(np.linspace(dis[i]-h, dis[i]+h, 100), f)) 35 | return Xnew 36 | 37 | 38 | def phasesmooth(dis, X, ax, phn=1): 39 | """ 40 | Data smooth of a single phase, Using movingradius method. 41 | 42 | Parameters 43 | ---------- 44 | dis, X : numpy.array 45 | Diffusion profile within a single phase 46 | ax : matplotlib.axes.Axes 47 | Plot axes 48 | phn : int 49 | Phase # 50 | 51 | """ 52 | mr_msg = 'No Change: Press ENTER (0 input)\n'\ 53 | 'Constant: Enter the constant composition (1 input)\n'\ 54 | 'Linear: Enter the start and end composition (2 inputs)\n'\ 55 | 'Moving Radius: Start & end composition, smooth radius and times (4 inputs)\n'\ 56 | '(Unchanged end composition can be input by \'-\')\n' 57 | 58 | Xsm = X.copy() 59 | smoo = True 60 | while smoo: 61 | ax.cla() 62 | ax.plot(dis, Xsm, 'bo') 63 | ax.set_title('Phase #%i' % phn) 64 | plt.draw() 65 | 66 | # Zoom in or not 67 | ipt = ask_input('Zoom in? [n]\n') 68 | if 'y' in ipt or 'Y' in ipt: 69 | ax.set_title('Select 2 points to zoom in') 70 | plt.pause(0.1) 71 | zm = np.array(plt.ginput(2))[:, 0] 72 | else: 73 | zm = [dis[0], dis[-1]] 74 | zmid = np.where((dis >= zm[0]) & (dis <= zm[1]))[0] 75 | 76 | sm = True 77 | while sm: 78 | ax.cla() 79 | ax.plot(dis[zmid], Xsm[zmid], 'bo') 80 | ax.set_title('Phase #%i' % phn) 81 | plt.draw() 82 | Xsmn = np.copy(Xsm[zmid]) 83 | msg = mr_msg+'Current ends: ['+str(Xsmn[0])+' '+str(Xsmn[-1])+']\n' 84 | while True: 85 | ipt = ask_input(msg) 86 | if len(ipt.split()) in (0, 1, 2, 4): 87 | break 88 | else: 89 | print('Wrong inputs!') 90 | # No change 91 | if len(ipt.split()) == 0: 92 | Xsmn = np.copy(Xsm[zmid]) 93 | # Constant 94 | elif len(ipt.split()) == 1: 95 | Xsmn[:] = float(ipt) 96 | # Linear 97 | elif len(ipt.split()) == 2: 98 | dis_linear = [dis[zmid][0], dis[zmid][-1]] 99 | X_linear = [Xsmn[0], Xsmn[-1]] 100 | ipt = ipt.split() 101 | for i in range(2): 102 | if ipt[i] != '-': 103 | X_linear[i] = float(ipt[i]) 104 | f_linear = splrep(dis_linear, X_linear, k=1) 105 | Xsmn = splev(dis[zmid], f_linear) 106 | # Moving radius 107 | else: 108 | ipt = ipt.split() 109 | X_new = [Xsmn[0], Xsmn[-1]] 110 | for i in range(2): 111 | if ipt[i] != '-': 112 | X_new[i] = float(ipt[i]) 113 | Xsmn[0], Xsmn[-1] = X_new[0], X_new[1] 114 | r, t = float(ipt[2]), int(ipt[3]) 115 | for i in range(t): 116 | Xsmn = movingradius(dis[zmid], Xsmn, r) 117 | ax.cla() 118 | ax.plot(dis[zmid], Xsm[zmid], 'bo', dis[zmid], Xsmn, 'ro') 119 | ax.set_title('Phase #%i' % phn) 120 | plt.draw() 121 | ipt = ask_input('Redo this smooth? (y/[n])') 122 | sm = True if 'y' in ipt or 'Y' in ipt else False 123 | if not sm: 124 | Xsm[zmid] = Xsmn 125 | ax.cla() 126 | ax.plot(dis, X, 'bo', dis, Xsm, 'ro') 127 | ax.set_title('Phase #%i' % phn) 128 | plt.draw() 129 | check_mono(dis, Xsm) 130 | ipt = ask_input('Further smooth for this phase? (y/[n])') 131 | smoo = True if 'y' in ipt or 'Y' in ipt else False 132 | return Xsm 133 | 134 | 135 | def datasmooth(profile, interface=[], n=2000, name=''): 136 | """ 137 | Data smooth of diffusion profile. The functions use moving radius method 138 | on each phase. Please do not close any plot window during the process. 139 | 140 | datasmooth() is the first step of the forward simulation analysis (FSA). 141 | 142 | Parameters 143 | ---------- 144 | profile: DiffProfile 145 | Diffusion profile data. 146 | interface : list of float 147 | Np-1 locations of interfaces for a Np-phase system 148 | n : int 149 | Interpolation number of the smoothed profile 150 | name : string, optional 151 | Name the output DiffProfile 152 | 153 | Returns 154 | ------- 155 | profile : pydiffusion.diffusion.DiffProfile 156 | 157 | Examples 158 | -------- 159 | Smooth the experimental profile (exp) with known interfaces [200, 300]: 160 | 161 | >>> ds = datasmooth(exp, [200, 300]) 162 | 163 | """ 164 | dis, X = profile.dis, profile.X 165 | if len(dis) != len(X): 166 | raise ValueError('Nonequal length of distance and composition data') 167 | try: 168 | If = np.array(interface) 169 | except TypeError: 170 | print('interface must be array_like') 171 | if If.ndim != 1: 172 | raise ValueError('interface must be 1d-array') 173 | 174 | fig = plt.figure() 175 | ax = fig.add_subplot(111) 176 | 177 | If = [dis[0]-0.5] + list(If) + [dis[-1]+0.5] 178 | Np = len(If)-1 179 | Ip = [0]*(Np+1) 180 | disn, Xn = dis.copy(), X.copy() 181 | 182 | # Same distance values 183 | for i in range(len(disn)-1): 184 | for j in range(i+1, len(disn)): 185 | if disn[i] == disn[j]: 186 | disn[j] += 1e-5*(j-i) 187 | elif disn[j] > disn[i]: 188 | break 189 | 190 | ita_start() 191 | 192 | # Apply phasesmooth to each phase 193 | for i in range(Np): 194 | pid = np.where((disn > If[i]) & (disn < If[i+1]))[0] 195 | try: 196 | Xn[pid] = phasesmooth(disn[pid], Xn[pid], ax, i+1) 197 | except (ValueError, TypeError) as error: 198 | ita_finish() 199 | raise error 200 | plt.close() 201 | 202 | ita_finish() 203 | 204 | # Create a sharp interface at interface locations 205 | # Solubility of each phase will be extended a little 206 | for i in range(1, Np): 207 | pid = np.where(disn > If[i])[0][0] 208 | start = max(pid-5, np.where(disn > If[i-1])[0][0]) 209 | end = min(pid+5, np.where(disn < If[i+1])[0][-1]) 210 | if start+2 < pid: 211 | fX1 = splrep(disn[start:pid], Xn[start:pid], k=2) 212 | else: 213 | fX1 = splrep(disn[start:pid], Xn[start:pid], k=1) 214 | if pid+1 < end: 215 | fX2 = splrep(disn[pid:end+1], Xn[pid:end+1], k=2) 216 | else: 217 | fX2 = splrep(disn[pid:end+1], Xn[pid:end+1], k=1) 218 | disn = np.insert(disn, pid, [If[i], If[i]]) 219 | Xn = np.insert(Xn, pid, [splev(If[i], fX1), splev(If[i], fX2)]) 220 | Ip[i] = pid+1 221 | Ip[-1] = len(Xn) 222 | disni, Xni = disn.copy(), Xn.copy() 223 | 224 | # Interpolation 225 | if n > 0: 226 | ni = [int(n*(If[i]-If[0])//(If[-1]-If[0])) for i in range(Np)]+[n] 227 | disni, Xni = np.zeros(n), np.zeros(n) 228 | for i in range(Np): 229 | fX = splrep(disn[Ip[i]:Ip[i+1]], Xn[Ip[i]:Ip[i+1]], k=1) 230 | disni[ni[i]:ni[i+1]] = np.linspace(disn[Ip[i]], disn[Ip[i+1]-1], ni[i+1]-ni[i]) 231 | Xni[ni[i]:ni[i+1]] = splev(disni[ni[i]:ni[i+1]], fX) 232 | 233 | print('Smooth finished') 234 | 235 | if name == '': 236 | name = profile.name+'_smoothed' 237 | 238 | return DiffProfile(disni, Xni, If[1:-1], name=name) 239 | -------------------------------------------------------------------------------- /pydiffusion/tests/test_model.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2018-2019 Zhangqi Chen 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | The test_model module contains tests for DiffProfile and DiffSystem objects. 23 | """ 24 | 25 | import numpy as np 26 | from pydiffusion.core import DiffProfile, DiffSystem 27 | 28 | 29 | def test_profile(): 30 | """ 31 | DiffProfile constructor 32 | """ 33 | dis = np.linspace(100, 0, 101) 34 | X = np.linspace(0, 1, 101) 35 | If = [30.5, 60.5] 36 | profile = DiffProfile(dis=dis, X=X, If=If) 37 | 38 | assert len(profile.dis) == len(profile.X) 39 | assert isinstance(profile.name, str) 40 | assert np.all(profile.dis[1:] >= profile.dis[:-1]) 41 | assert len(profile.If) == len(If)+2 42 | assert len(profile.Ip) == len(If)+2 43 | assert profile.Ip[-1] == len(profile.dis) 44 | assert profile.If[0] < profile.dis[profile.Ip[0]] 45 | assert profile.If[-1] > profile.dis[profile.Ip[-1]-1] 46 | for i in range(1, len(If)): 47 | assert profile.If[i] > profile.dis[profile.Ip[i]-1] 48 | assert profile.If[i] < profile.dis[profile.Ip[i]] 49 | 50 | 51 | def test_system(): 52 | """ 53 | DiffSystem constructor 54 | """ 55 | # Construct with X, DC 56 | Xr = [[0, .4], [.6, 1]] 57 | X = np.linspace(0, 1, 101) 58 | DC = np.linspace(1, 10, 101)*1e-14 59 | diffsys = DiffSystem(Xr=Xr, X=X, DC=DC) 60 | 61 | assert diffsys.Xr.shape == (diffsys.Np, 2) 62 | assert isinstance(diffsys.name, str) 63 | assert len(diffsys.Dfunc) == diffsys.Np 64 | for i in range(diffsys.Np): 65 | assert len(diffsys.Dfunc[i][0]) == len(diffsys.Dfunc[i][1]) 66 | 67 | # Construct with Dfunc 68 | diffsys1 = DiffSystem(Xr=Xr, Dfunc=diffsys.Dfunc) 69 | 70 | assert diffsys1.Xr.shape == (diffsys1.Np, 2) 71 | assert len(diffsys1.Dfunc) == diffsys1.Np 72 | -------------------------------------------------------------------------------- /pydiffusion/tests/test_plot.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2018-2019 Zhangqi Chen 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | The test_plot verifies that the plot module produce plots without error. 23 | """ 24 | import numpy as np 25 | import matplotlib.pyplot as plt 26 | from matplotlib.axes import Axes 27 | from pydiffusion.core import DiffSystem, DiffProfile 28 | from pydiffusion.utils import mesh, step 29 | from pydiffusion.simulation import sphSim 30 | from pydiffusion.plot import profileplot, DCplot, SFplot 31 | plt.switch_backend('Agg') 32 | 33 | 34 | def test_profileplot(): 35 | """ 36 | profileplot should return an axes object when a DiffProfile is passed 37 | """ 38 | dis = np.linspace(0, 100, 101) 39 | X = np.linspace(0, 1, 101) 40 | If = [30.5, 60.5] 41 | profile = DiffProfile(dis=dis, X=X, If=If) 42 | ax = profileplot(profile, label='test') 43 | 44 | assert isinstance(ax, Axes) 45 | 46 | 47 | def test_DCplot(): 48 | """ 49 | DCplot should return an axes object when a DiffSystem is passed 50 | """ 51 | Xr = [[0, .4], [.6, 1]] 52 | X = np.linspace(0, 1, 101) 53 | DC = np.linspace(1, 10, 101)*1e-14 54 | diffsys = DiffSystem(Xr=Xr, X=X, DC=DC) 55 | ax = DCplot(diffsys, label='test') 56 | 57 | assert isinstance(ax, Axes) 58 | 59 | 60 | def test_SFplot(): 61 | """ 62 | SFplot should return an axes object when a DiffProfile and time is passed 63 | """ 64 | diffsys = DiffSystem(Xr=[0, 1], X=[0, 1], DC=[1e-14, 1e-13]) 65 | dis = mesh(0, 1000, 201) 66 | profile_init = step(dis, 500, diffsys) 67 | time = 200 * 3600 68 | profile = sphSim(profile_init, diffsys, time) 69 | ax = SFplot(profile, time, Xlim=[0, 1], label='test') 70 | 71 | assert isinstance(ax, Axes) 72 | -------------------------------------------------------------------------------- /pydiffusion/tests/test_simulation.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2018-2019 Zhangqi Chen 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | The test_simulation module contains tests for simulation in pydiffusion. 23 | """ 24 | 25 | import numpy as np 26 | from pydiffusion.core import DiffSystem, DiffProfile 27 | from pydiffusion.utils import mesh, step, matanocalc 28 | from pydiffusion.simulation import sphSim, mphSim 29 | 30 | 31 | def test_sphsim(): 32 | """ 33 | Single-phase system simulation. 34 | Offset of the simulated matano plane should be very small. 35 | """ 36 | diffsys = DiffSystem(Xr=[0, 1], X=[0, 1], DC=[1e-14, 1e-14]) 37 | dis = mesh(0, 1000, 201) 38 | profile_init = step(dis, 500, diffsys) 39 | time = 200 * 3600 40 | profile_final = sphSim(profile_init, diffsys, time) 41 | 42 | mpi = matanocalc(profile_init, [0, 1]) 43 | mpf = matanocalc(profile_final, [0, 1]) 44 | 45 | assert isinstance(profile_final, DiffProfile) 46 | assert len(profile_final.If) == diffsys.Np+1 47 | assert abs(mpi-mpf) < 1 48 | 49 | 50 | def test_mphsim(): 51 | """ 52 | Multiple-phase system simulation. 53 | Offset of the simulated matano plane should be very small. 54 | """ 55 | Xr = [[0, .4], [.6, 1]] 56 | X = np.linspace(0, 1, 101) 57 | DC = np.linspace(1, 2, 101)*1e-14 58 | diffsys = DiffSystem(Xr=Xr, X=X, DC=DC) 59 | dis = mesh(0, 1000, 201) 60 | profile_init = step(dis, 500, diffsys) 61 | time = 200 * 3600 62 | profile_final = mphSim(profile_init, diffsys, time) 63 | 64 | mpi = matanocalc(profile_init, [0, 1]) 65 | mpf = matanocalc(profile_final, [0, 1]) 66 | 67 | assert isinstance(profile_final, DiffProfile) 68 | assert len(profile_final.If) == diffsys.Np+1 69 | assert abs(mpi-mpf) < 1 70 | -------------------------------------------------------------------------------- /pydiffusion/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2018-2019 Zhangqi Chen 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | The test_utils module contains tests for pydiffusion utilities. 23 | """ 24 | 25 | import numpy as np 26 | from scipy.interpolate import splev 27 | from pydiffusion.core import DiffSystem 28 | from pydiffusion.simulation import sphSim 29 | from pydiffusion.utils import mesh, automesh, step, profilefunc, disfunc 30 | 31 | 32 | def test_mesh(): 33 | """ 34 | mesh should return a meshed array with increasing/decreasing grid size. 35 | """ 36 | dis = mesh(n=100, a=1) 37 | d = dis[1:]-dis[:-1] 38 | assert len(dis) == 100 39 | assert np.all(d[1:] > d[:-1]) 40 | 41 | dis = mesh(n=100, a=-1) 42 | d = dis[1:]-dis[:-1] 43 | assert np.all(d[1:] < d[:-1]) 44 | 45 | 46 | def test_automesh(): 47 | """ 48 | automesh should return a meshed array whose length is within its range. 49 | """ 50 | diffsys = DiffSystem(Xr=[0, 1], X=[0, 1], DC=[1e-14, 1e-13]) 51 | dis = mesh(0, 1000, 201) 52 | profile_init = step(dis, 500, diffsys) 53 | time = 200 * 3600 54 | profile = sphSim(profile_init, diffsys, time) 55 | dism = automesh(profile, diffsys, n=[300, 400]) 56 | 57 | assert len(dism) >= 300 and len(dism) <= 400 58 | 59 | 60 | def test_dispfunc(): 61 | """ 62 | disfunc and profilefunc should give functions to copy profile data. 63 | """ 64 | diffsys = DiffSystem(Xr=[0, 1], X=[0, 1], DC=[1e-14, 1e-13]) 65 | dis = mesh(0, 1000, 201) 66 | profile_init = step(dis, 500, diffsys) 67 | time = 200 * 3600 68 | profile = sphSim(profile_init, diffsys, time) 69 | 70 | fX = profilefunc(profile) 71 | fdis = disfunc(profile.dis, profile.X) 72 | 73 | assert np.all(abs(splev(dis, fX)-profile.X) < 0.01) 74 | assert np.all(abs(splev(profile.X, fdis)-dis) < 0.1) 75 | -------------------------------------------------------------------------------- /pydiffusion/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | The utils module include methods for basic diffusion data processing and analysis. 3 | """ 4 | 5 | import numpy as np 6 | import itertools 7 | from scipy.interpolate import splev, splrep 8 | from pydiffusion.core import DiffProfile, DiffSystem 9 | 10 | 11 | def mesh(start=0, end=500, n=200, a=0): 12 | """ 13 | Meshing for diffusion grids 14 | 15 | Parameters 16 | ---------- 17 | start, end : float 18 | Start & end location for diffusion grids. 19 | n : int 20 | Grid number of meshing. 21 | a : float 22 | Parameter for nonlinear meshing. 23 | = 0 : Linear meshing with equal grid size. 24 | > 0 : Nonlinear meshing with increasing grid size. 25 | < 0 : Nonlinear meshing with decreasing grid size. 26 | 27 | Returns 28 | ------- 29 | dis : numpy.array 30 | Meshed grids information. 31 | 32 | Examples 33 | -------- 34 | Create a meshed 400 grids in 1000 microns with increasing grid sizes: 35 | 36 | >>> dis = mesh(0, 1000, n=400, a=0.5) 37 | 38 | """ 39 | if a == 0: 40 | dis = np.linspace(start, end, n) 41 | elif a > 0: 42 | dis = np.array([start+(end-start)*np.sinh(i*a/(n-1))/np.sinh(a) for i in range(n)]) 43 | else: 44 | dis = np.array([end-(end-start)*np.sinh(i*a/(1-n))/np.sinh(-a) for i in range(n-1, -1, -1)]) 45 | d = dis[1:]-dis[:-1] 46 | print('Meshing completed. Grid min=%.3f, max=%.3f' % (min(d), max(d))) 47 | return dis 48 | 49 | 50 | def meshfunc_default(x, alpha=0.3): 51 | """ 52 | Default meshing size vs. DC function in automesh() 53 | 54 | """ 55 | return x**alpha 56 | 57 | 58 | def automesh(profile, diffsys, n=[400, 500], f=None, alpha=0.3): 59 | """ 60 | Meshing for fast simulation similar to existing profile. 61 | This is usually used for a fast and accurate simulation of difficult systems. 62 | 63 | Parameters 64 | ---------- 65 | profile : DiffProfile 66 | The profile meshing is based on. It should be similar to final 67 | simulated profile. e.g. Smoothed experimental profile. 68 | diffsys : DiffSystem 69 | The diffusion coefficients for simulation. 70 | n : list 71 | meshing number range, default = [400, 500] 72 | f : function of meshing 73 | Meshing grid size is proportional to f(DC), default = DC**alpha 74 | DC is diffusion coefficients. 75 | alpha : float 76 | argument of meshfunc_default 77 | 78 | Returns 79 | ------- 80 | dism: numpy.array 81 | Distance information after meshing. 82 | 83 | Examples 84 | -------- 85 | Create an efficient meshing grids with known diffusion profile (profile) 86 | and diffusion coefficients (dsys). Grid number is about 500~600: 87 | 88 | >>> dis = automesh(profile, dsys, [500, 600]) 89 | 90 | In FSA, profile is the smoothed profile from experimental data. 91 | If profile is unknown, you can simulate one with mphSim() before automesh(). 92 | 93 | To perform an accurate simulation on a 600 micron grid, with known diffusion 94 | coefficients (dsys) and simulation time (100 hours): 95 | 96 | >>> dis = mesh(0, 600, 300) 97 | >>> init_profile = step(dis, 300, dsys) 98 | >>> profile = mphSim(init_profile, dsys, time=3600*100) 99 | >>> dis = automesh(profile, dsys) 100 | 101 | Then use new grids to perform accurate simulation: 102 | 103 | >>> init_profile = step(dis, 300, dsys) 104 | >>> profile = mphSim(init_profile, dsys, time=3600*100) 105 | 106 | """ 107 | dis, X = profile.dis, profile.X 108 | Xr, fD = diffsys.Xr, diffsys.Dfunc 109 | nmin, nmax = n 110 | dmin = dis[-1]/nmax/2 111 | 112 | if f is None: 113 | def f(x): 114 | return meshfunc_default(x, alpha) 115 | 116 | # Create profile function fX 117 | fX = profilefunc(profile) 118 | 119 | # Create universal D function fDC 120 | Xf, Df = np.array([]), np.array([]) 121 | for i in range(len(fD)): 122 | Xnew = np.linspace(Xr[i, 0], Xr[i, 1], 20) 123 | Xf = np.append(Xf, Xnew) 124 | Df = np.append(Df, np.exp(splev(Xnew, fD[i]))) 125 | fDC = splrep(Xf, np.log(Df), k=1) 126 | 127 | # Meshing 128 | while True: 129 | dism = [dis[0]] 130 | dseed = dmin/min(f(np.exp(splev(X, fDC)))) 131 | while dism[-1] < dis[-1]: 132 | disDC = np.exp(splev(splev(dism[-1], fX), fDC)) 133 | dnew = dmin if disDC < 1e-17 else dseed*f(disDC) 134 | dism += [dism[-1]+dnew] 135 | dism += [dis[-1]+dnew] 136 | meshnum = len(dism) 137 | if meshnum < nmin: 138 | dmin /= 2 139 | elif meshnum < nmax: 140 | break 141 | else: 142 | dmin *= 1.1 143 | print('Meshing Num=%i, Minimum grid=%f um' % (meshnum, dmin)) 144 | return np.array(dism) 145 | 146 | 147 | def step(dis, matano, diffsys, Xlim=[], name=''): 148 | """ 149 | Create a step profile at the Matano Plane. 150 | 151 | Parameters 152 | ---------- 153 | dis : numpy.array 154 | Distance information. 155 | matano : float 156 | Matano plane location (micron). 157 | diffsys : DiffSystem 158 | The diffusion system information for initial setups before simulation. 159 | Xlim : list (float), optional 160 | Indicates the left and right concentration limits for step profile. 161 | Default value = [diffsys.Xr[0][0], diffsys.Xr[-1][1]]. 162 | For a decreasing step, Xlim must be provided. 163 | name : str, optional 164 | Name the output DiffProfile 165 | 166 | Returns 167 | ------- 168 | profile : DiffProfile 169 | Step profile can be used for input of pydiffusion.simulation.mphSim 170 | 171 | Examples 172 | -------- 173 | Create a step profile on a meshed grid (ascending): 174 | 175 | >>> dis = mesh() 176 | >>> init_profile = step(dis, 200, dsys, Xlim=[0, 1]) 177 | 178 | Create a reversed step profile (descending): 179 | 180 | >>> init_profile = step(dis, 200, dsys, Xlim=[1, 0]) 181 | 182 | """ 183 | Np = diffsys.Np 184 | if Xlim == []: 185 | XL, XR = diffsys.Xr[0][0], diffsys.Xr[-1][1] 186 | else: 187 | [XL, XR] = Xlim 188 | X = np.ones(len(dis))*XL 189 | X[dis > matano] = XR 190 | if name == '': 191 | name = diffsys.name+'_step' 192 | if Np == 1: 193 | return DiffProfile(dis, X, name=name) 194 | else: 195 | If = [0]*(Np-1) 196 | Ip = np.where(X == XR)[0][0] 197 | d = dis[Ip] - dis[Ip-1] 198 | for i in range(Np-1): 199 | If[i] = dis[Ip-1] + d/(Np+1)*(i+1) 200 | return DiffProfile(dis, X, If, name=name) 201 | 202 | 203 | def profilefunc(profile): 204 | """ 205 | Create a tck interpolation (k=1) of the diffusion profile 206 | 207 | """ 208 | disn, Xn = profile.dis.copy(), profile.X.copy() 209 | for i in range(len(disn)-1): 210 | if disn[i] == disn[i+1]: 211 | disn[i] -= (disn[i]-disn[i-1])/1e5 212 | disn[i+1] += (disn[i+2]-disn[i+1])/1e5 213 | return splrep(disn, Xn, k=1) 214 | 215 | 216 | def disfunc(dis, X): 217 | """ 218 | Create a tck interpolation (k=1) of the dis vs. X profile 219 | 220 | """ 221 | Xmin, Xmax = (X[0], X[-1]) if X[0] < X[-1] else (X[-1], X[0]) 222 | if list(X).count(Xmin) > 1 and list(X).count(Xmax) > 1: 223 | pid = np.where((X > Xmin) & (X < Xmax))[0] 224 | elif list(X).count(Xmin) > 1: 225 | pid = np.where((X > Xmin))[0] 226 | elif list(X).count(Xmax) > 1: 227 | pid = np.where((X < Xmax))[0] 228 | else: 229 | pid = np.arange(len(X)) 230 | if X[-1] < X[0]: 231 | pid = pid[::-1] 232 | return splrep(X[pid], dis[pid], k=1) 233 | 234 | 235 | def matanocalc(profile, Xlim=[]): 236 | """ 237 | Matano Plane calculation. 238 | 239 | Parameters 240 | ---------- 241 | profile : DiffProfile 242 | Diffusion Profile. 243 | Xlim : list 244 | The left and right end concentration of the profile. 245 | 246 | Returns 247 | ------- 248 | matano : float 249 | Matano Plane location. 250 | 251 | """ 252 | dis, X = profile.dis, profile.X 253 | if Xlim == []: 254 | XL, XR = X[0], X[-1] 255 | elif isinstance(Xlim, list) and len(Xlim) == 2: 256 | XL, XR = Xlim 257 | else: 258 | raise ValueError('Xlim is a list with length = 2') 259 | return (np.trapz(X, dis)-dis[-1]*XR+dis[0]*XL)/(XL-XR) 260 | 261 | 262 | def SF(profile, time, Xlim=[]): 263 | """ 264 | Use Sauer-Freise method to calculate diffusion coefficients from profile. 265 | 266 | Parameters 267 | ---------- 268 | profile : DiffProfile 269 | Diffusion profile. 270 | time : float 271 | Diffusion time in seconds. 272 | Xlim : list (float), optional 273 | Indicates the left and right concentration limits for calculation. 274 | Default value = [profile.X[0], profile.X[-1]]. 275 | 276 | Returns 277 | ------- 278 | DC : numpy.array 279 | Diffusion coefficients. 280 | 281 | """ 282 | try: 283 | time = float(time) 284 | except TypeError: 285 | print('Cannot convert time to float') 286 | 287 | dis, X = profile.dis, profile.X 288 | 289 | # Correct the wrong Xlim input 290 | if Xlim != [] and (X[-1]-X[0])*(Xlim[1]-Xlim[0]) < 0: 291 | Xlim = Xlim[::-1] 292 | 293 | [XL, XR] = [X[0], X[-1]] if Xlim == [] else Xlim 294 | Y1 = (X-XL)/(XR-XL) 295 | Y2 = 1-Y1 296 | dYds = (Y1[2:]-Y1[:-2])/(dis[2:]-dis[:-2]) 297 | dYds = np.append(dYds[0], np.append(dYds, dYds[-1])) 298 | intvalue = np.array([Y2[i]*np.trapz(Y1[:i+1], dis[:i+1])+Y1[i]*(np.trapz(Y2[i:], dis[i:])) for i in range(len(dis))]) 299 | DC = intvalue/dYds/2/time*1e-12 300 | DC[0], DC[-1] = DC[1], DC[-2] 301 | return DC 302 | 303 | 304 | def Jflux(profile, loc, time, Xlim=[]): 305 | """ 306 | Calculate the mass flux according to existing profile. 307 | 308 | Parameters 309 | ---------- 310 | profile : DiffProfile 311 | Existing diffusion profile. 312 | loc : float 313 | Location to calculate mass flux. 314 | time : float 315 | Diffusion time in seconds. 316 | Xlim : list, optional 317 | Indicates the left and right concentration limits for calculation. 318 | Default value = [profile.X[0], profile.X[-1]]. 319 | 320 | Returns 321 | ------- 322 | Jf : float 323 | Mass flux at loc. 324 | """ 325 | d, X = profile.dis.copy(), profile.X.copy() 326 | 327 | if loc > d.max() or loc < d.min(): 328 | raise ValueError('loc is out of range of distance data') 329 | 330 | if Xlim == []: 331 | Xlim = [X[0], X[-1]] 332 | 333 | XL, XR = Xlim[0], Xlim[1] 334 | Y1 = np.array((X-XR)/(XL-XR)) 335 | 336 | ip = np.where(d > loc)[0][0] 337 | fX = splrep(d, Y1, k=1) 338 | Y = float(splev(loc, fX)) 339 | 340 | d = np.insert(d, ip, loc) 341 | Y1 = np.insert(Y1, ip, Y) 342 | intvalue = Y1[ip]*np.trapz(1-Y1[:ip+1], d[:ip+1])+(1-Y1[ip])*np.trapz(Y1[ip:], d[ip:]) 343 | Jf = intvalue/2/time*(XL-XR)/1e6 344 | 345 | return Jf 346 | 347 | 348 | def check_mono(dis, X): 349 | """ 350 | Check the monotonicity of a profile. 351 | Return True if profile is monotonic. 352 | 353 | Parameters 354 | ---------- 355 | dis : Distance data 356 | X : Concentration data 357 | 358 | """ 359 | if X[-1] < X[0]: 360 | X = 1-X 361 | ip = np.where(X[1:] < X[:-1])[0] 362 | if len(ip) == 0: 363 | return True 364 | else: 365 | d1, d2 = dis[ip[0]], dis[ip[-1]+1] 366 | print('Non-monotonicity found between %.2f and %.2f micron!' % (d1, d2)) 367 | return False 368 | 369 | 370 | def error_profile(profilesim, profileexp, w=None): 371 | """ 372 | Calculate the difference (in mole fraction) between experimental profile 373 | and simulated profile. This function take profilesim as reference, i.e. 374 | compare profileexp against profilesim. 375 | 376 | Parameters 377 | ---------- 378 | profilesim : DiffProfile 379 | Simulated diffusion profile. 380 | profileexp : DiffProfile 381 | Experiemntal measured profile. 382 | w : list, optional 383 | Weights of each phase, default is equal weights. 384 | 385 | Returns 386 | ------- 387 | error : float 388 | Averaged difference in mole fraction. 389 | 390 | """ 391 | fX = profilefunc(profilesim) 392 | dissim, Xsim = profilesim.dis, profilesim.X 393 | disexp, Xexp = profileexp.dis, profileexp.X 394 | If = [] 395 | for i in range(len(dissim)-1): 396 | if dissim[i] == dissim[i+1]: 397 | If += [dissim[i]] 398 | Np = len(If)+1 399 | if w is not None and len(w) != Np: 400 | raise ValueError('Length of w must equal to number of phases.') 401 | if w is None: 402 | w = [1]*Np 403 | If += [disexp[-1]+1] 404 | error, n = 0, 0 405 | for i in range(len(disexp)): 406 | for j in range(Np): 407 | if disexp[i] < If[j]: 408 | wi = w[j] 409 | break 410 | n += wi 411 | if disexp[i] < dissim[0]: 412 | error += abs(Xexp[i]-Xsim[0])*wi 413 | elif disexp[i] > dissim[-1]: 414 | error += abs(Xexp[i]-Xsim[-1])*wi 415 | else: 416 | error += abs(Xexp[i]-splev(disexp[i], fX))*wi 417 | return error / n 418 | 419 | 420 | def efunc_default(X, Xf, r): 421 | """ 422 | Default efunc to create bias for diffusion coefficients 423 | 424 | """ 425 | if abs(Xf-X) <= r/2: 426 | deltae = 1-2*(X-Xf)**2/r**2 427 | elif Xf < X-r/2: 428 | deltae = 2*(Xf-X+r)**2/r**2 429 | else: 430 | deltae = 2*(Xf-X-r)**2/r**2 431 | return deltae 432 | 433 | 434 | def DCbias(diffsys, X, deltaD, r=0.3, efunc=None): 435 | """ 436 | This function creates bias for a diffusion coefficients profile 437 | 438 | Parameters 439 | ---------- 440 | diffsys : DiffSystem 441 | Original diffusion coefficients. 442 | X : float 443 | Concentration location which has largest bias. 444 | deltaD : float 445 | Scale of the bias. D *= 10**deltaD is the maximum of bias. 446 | r : float, optional 447 | Bias at X will create smaller bias in range of (X-r, X+r) 448 | efunc : function, optional 449 | Function to create bias on diffusion coefficients. 450 | Default = pydiffusion.utils.efunc 451 | efunc(X, Xf, r) 452 | efunc should return 1 as the maximum when X == Xf, 453 | and return 0 when abs(X-Xf) == r. 454 | 455 | Returns 456 | ------- 457 | fDbias : DiffSystem 458 | Diffusion coefficients with bias. 459 | 460 | """ 461 | Xr, Np, fD = diffsys.Xr, diffsys.Np, diffsys.Dfunc 462 | efunc = efunc_default if efunc is None else efunc 463 | fDbias = [] 464 | for i in range(Np): 465 | if X >= Xr[i, 0] and X <= Xr[i, 1]: 466 | Xf = np.linspace(Xr[i, 0], Xr[i, 1], 30) 467 | Df = np.exp(splev(Xf, fD[i])) 468 | eid = np.where((Xf >= X-r) & (Xf <= X+r))[0] 469 | for j in eid: 470 | Df[j] *= 10**(deltaD * efunc(X, Xf[j], r)) 471 | fDbias += [splrep(Xf, np.log(Df))] 472 | else: 473 | fDbias += [fD[i]] 474 | return DiffSystem(Xr, fDbias) 475 | 476 | 477 | def c2xy(x1, x2): 478 | """ 479 | Convert ternary compositions to (x, y) coordinates of a ternary 480 | phase diagram. 481 | 482 | Parameters 483 | ---------- 484 | x1, x2 : float or array-like, within [0, 1] 485 | Composition at the left-lower and right-lower corner component 486 | of a ternary phase diagram. 487 | 488 | Returns 489 | ------- 490 | x : float or array-like, within [0, 1] 491 | x coordinates of ternary phase diagram. 492 | y : float or array-like, within [0, 0.866] 493 | y coordinates of ternary phase diagram. 494 | 495 | """ 496 | if np.shape(x1) != np.shape(x2): 497 | raise ValueError('Shape of x1 and x2 must be the same') 498 | 499 | x3 = 1-x1-x2 500 | y = x3*np.sqrt(3)/2 501 | x = x2+x3/2 502 | return x, y 503 | 504 | 505 | def xy2c(x, y): 506 | """ 507 | Convert (x, y) coordinates of ternary phase diagram to a ternary 508 | compositions. 509 | 510 | Parameters 511 | ---------- 512 | x : float or array-like, within [0, 1] 513 | x coordinates of ternary phase diagram. 514 | y : float or array-like, within [0, 0.866] 515 | y coordinates of ternary phase diagram. 516 | 517 | Returns 518 | ------- 519 | x1, x2, x3 : float or array-like, within [0, 1] 520 | Composition of the left-lower, right-lower and top corner component 521 | of a ternary phase diagram. 522 | 523 | """ 524 | if np.shape(x) != np.shape(y): 525 | raise ValueError('Shape of x and y must be the same') 526 | 527 | x3 = y*2/np.sqrt(3) 528 | x2 = x-x3/2 529 | x1 = 1-x2-x3 530 | return x1, x2, x3 531 | 532 | 533 | def polyfit2d(x, y, z, order=2): 534 | """ 535 | Use polynominal function to fit a surface in 3D. 536 | Polynominal function = sum(x^i * y^j), where i+j <= order. 537 | 538 | Parameters 539 | ---------- 540 | x, y : 1d-array 541 | (x, y) coordinates data, can be any location at 2D scale. 542 | z : 1d-array 543 | z coordinates data coresponding to (x, y). 544 | order : int, optional 545 | Order of the polynominal function, default value = 2. 546 | 547 | Returns 548 | ------- 549 | m : list 550 | Coefficients of the polynominal function. 551 | 552 | """ 553 | ncols = (order + 1)**2 554 | G = np.zeros((x.size, ncols)) 555 | ij = itertools.product(range(order+1), range(order+1)) 556 | ij = [k for k in ij if sum(k) <= order] 557 | for k, (i, j) in enumerate(ij): 558 | G[:, k] = x**i * y**j 559 | m, _, _, _ = np.linalg.lstsq(G, z, rcond=-1) 560 | return m 561 | 562 | 563 | def polyval2d(x, y, m): 564 | """ 565 | Back-calculation of polyfit2d 566 | 567 | Parameters 568 | ---------- 569 | x, y : 1d-array 570 | (x, y) coordinates data, can be any location at 2D scale. 571 | m : list 572 | Coefficients of the polynominal function. 573 | z : 1d-array 574 | z coordinates data coresponding to (x, y). 575 | 576 | Returns 577 | ------- 578 | z : 1d-array 579 | z coordinates data coresponding to (x, y). 580 | 581 | """ 582 | order = int(np.sqrt(len(m))) - 1 583 | ij = itertools.product(range(order+1), range(order+1)) 584 | ij = [k for k in ij if sum(k) <= order] 585 | z = np.zeros_like(x) 586 | for a, (i, j) in zip(m, ij): 587 | z += a * x**i * y**j 588 | return z 589 | 590 | 591 | def cross(a, b, p): 592 | """ 593 | Find the cross coordinates (x, y) of vector a and p+b 594 | 595 | Parameters 596 | ---------- 597 | a, b, p : list with length of 2 598 | 2D vectors 599 | 600 | Returns 601 | ------- 602 | c : list with length of 2 603 | coordinates of the cross points. If no cross then return -1. 604 | 605 | """ 606 | A = np.array([[a[1], -a[0]], [b[1], -b[0]]]) 607 | B = np.array([0, b[1]*p[0]-b[0]*p[1]]) 608 | if np.linalg.det(A) == 0: 609 | return -1 610 | c = np.linalg.solve(A, B) 611 | if (c[0]-a[0])*c[0] <= 0 and (c[0]-p[0])*(c[0]-p[0]-b[0]) <= 0: 612 | return list(c) 613 | else: 614 | return -1 615 | 616 | 617 | def findcross(profile1, profile2): 618 | """ 619 | Find the cross composition of two ternary diffusion paths. 620 | 621 | Parameters 622 | ---------- 623 | profile1, profile2 : Profile1D 624 | Two 1D diffusion profiles of the same ternary system 625 | 626 | Returns 627 | ------- 628 | list of cross compositions 629 | 630 | """ 631 | l1x, l1y = profile1.X1, profile1.X2 632 | l2x, l2y = profile2.X1, profile2.X2 633 | result = [] 634 | 635 | for i in range(1, len(l1x)): 636 | for j in range(1, len(l2x)): 637 | if min(l1x[i-1:i+1]) > max(l2x[j-1:j+1]) or min(l2x[j-1:j+1]) > max(l1x[i-1:i+1]): 638 | continue 639 | elif min(l1y[i-1:i+1]) > max(l2y[j-1:j+1]) or min(l2y[j-1:j+1]) > max(l1y[i-1:i+1]): 640 | continue 641 | o = np.array([l1x[i-1], l1y[i-1]]) 642 | a = np.array([l1x[i], l1y[i]]) 643 | p = np.array([l2x[j-1], l2y[j-1]]) 644 | b = np.array([l2x[j], l2y[j]]) 645 | a, p, b = a-o, p-o, b-p 646 | r = cross(a, b, p) 647 | if r != -1: 648 | result += [[r[0]+o[0], r[1]+o[1]]] 649 | return result 650 | 651 | 652 | def DTcalc(profile1, profile2, time): 653 | """ 654 | Calculate the D matrix according to 2 1D ternary diffusion profiles. 655 | 656 | Parameters 657 | ---------- 658 | profile1, profile2 : Profile1D 659 | Two 1D diffusion profiles or a ternary system. 660 | time : float 661 | Diffusion time in seconds. 662 | 663 | Returns 664 | ------- 665 | cp : list of 2 666 | Cross of 2 diffusion paths. 667 | D : array of shape (2, 2) 668 | Calculated diffusion matrix at cp: [D11, D12; D21, D22] 669 | 670 | """ 671 | d1, x1, y1 = profile1.dis.copy(), profile1.X1.copy(), profile1.X2.copy() 672 | d2, x2, y2 = profile2.dis.copy(), profile2.X1.copy(), profile2.X2.copy() 673 | 674 | cp = findcross(x1, y1, x2, y2)[0] 675 | 676 | f1, f2 = disfunc(d1, x1), disfunc(d2, x2) 677 | 678 | loc1 = float(splev(cp[0], f1)) 679 | loc2 = float(splev(cp[0], f2)) 680 | 681 | # Calculate concentration gradients 682 | g1x = float(splev(loc1, splrep(d1, x1, k=1), 1)) 683 | g1y = float(splev(loc1, splrep(d1, y1, k=1), 1)) 684 | g2x = float(splev(loc2, splrep(d2, x2, k=1), 1)) 685 | g2y = float(splev(loc2, splrep(d2, y2, k=1), 1)) 686 | 687 | # Linear equations to solve 688 | A = np.array([[g1x, g1y, 0, 0], 689 | [0, 0, g1x, g1y], 690 | [g2x, g2y, 0, 0], 691 | [0, 0, g2x, g2y]])*-1e6 692 | B = np.array([Jflux(d1, x1, loc1, time), 693 | Jflux(d1, y1, loc1, time), 694 | Jflux(d2, x2, loc2, time), 695 | Jflux(d2, y2, loc2, time)]) 696 | 697 | D = np.linalg.solve(A, B) 698 | D = np.array([D[:2], D[2:]]) 699 | 700 | return cp, D 701 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | license_file = LICENSE.txt 3 | description-file = README.rst -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | from pydiffusion import __version__ as version 3 | 4 | long_description = """ 5 | =========== 6 | pyDiffusion 7 | =========== 8 | 9 | .. image:: https://img.shields.io/pypi/status/pydiffusion.svg 10 | :target: https://pypi.python.org/pypi/pydiffusion/ 11 | :alt: Development Status 12 | 13 | .. image:: https://img.shields.io/pypi/v/pydiffusion.svg 14 | :target: https://pypi.python.org/pypi/pydiffusion/ 15 | :alt: Latest version 16 | 17 | .. image:: https://img.shields.io/pypi/pyversions/pydiffusion.svg 18 | :target: https://pypi.python.org/pypi/pydiffusion/ 19 | :alt: Supported Python versions 20 | 21 | .. image:: https://img.shields.io/pypi/l/pydiffusion.svg 22 | :target: https://pypi.python.org/pypi/pydiffusion/ 23 | :alt: License 24 | 25 | **pyDiffusion** combines tools like **diffusion simulation**, **diffusion data smooth**, **forward simulation analysis (FSA)**, etc. to help people analyze diffusion data efficiently. 26 | 27 | Dependencies 28 | ------------ 29 | 30 | * Python 3.5+ 31 | * numpy, matplotlib, scipy, pandas 32 | 33 | Installation 34 | ------------ 35 | 36 | Via pip (recommend): 37 | 38 | .. code-block:: 39 | 40 | pip install pydiffusion 41 | 42 | """ 43 | 44 | setup( 45 | name='pydiffusion', 46 | version=version, 47 | keywords=['diffusion', 'simulation', 'python'], 48 | packages=['pydiffusion'], 49 | include_package_data=True, 50 | python_requires='>=3.5', 51 | install_requires=['numpy', 'matplotlib', 'scipy', 'pandas'], 52 | 53 | # metadata 54 | author='Zhangqi Chen', 55 | author_email='wshchzhq@gmail.com', 56 | description='A Python library for diffusion simulation and data analysis', 57 | long_description=long_description, 58 | license='MIT', 59 | url='https://github.com/zhangqi-chen/pyDiffusion', 60 | 61 | classifiers=[ 62 | # How mature is this project? Common values are 63 | # 3 - Alpha 64 | # 4 - Beta 65 | # 5 - Production/Stable 66 | 'Development Status :: 4 - Beta', 67 | 68 | # Indicate who your project is intended for 69 | 'Intended Audience :: Science/Research', 70 | 'Topic :: Scientific/Engineering :: Physics', 71 | 'Topic :: Scientific/Engineering :: Chemistry', 72 | 73 | # Pick your license as you wish (should match "license" above) 74 | 'License :: OSI Approved :: MIT License', 75 | 76 | # Specify the Python versions you support here. In particular, ensure 77 | # that you indicate whether you support Python 2, Python 3 or both. 78 | 'Programming Language :: Python :: 3.5', 79 | 'Programming Language :: Python :: 3.6', 80 | 'Programming Language :: Python :: 3.7' 81 | ], 82 | ) 83 | --------------------------------------------------------------------------------