```python
# %load /Users/facai/Study/book_notes/preconfig.py
%matplotlib inline

import matplotlib.pyplot as plt
import seaborn as sns
sns.set(color_codes=True)
sns.set(font='SimHei')
plt.rcParams['axes.grid'] = False

from IPython.display import SVG

def show_image(filename, figsize=None):
    if figsize:
        plt.figure(figsize=figsize)

    plt.imshow(plt.imread(filename))
```

xgboost的基本原理与实现简介
========================

xgboost是个很棒的工具，其优点很多：运行速度，支持正则，直接用损失函数指导树的生成等等，不一而足。相较spark自带的gbdt，xgboost更适合工程应用。

本文参照论文 Tianqi Chen and Carlos Guestrin. XGBoost: A Scalable Tree Boosting System，介绍下xgboost对原始gbdt的改进地方，并说明下具体的实现细节。

代码版本信息：

```bash
~/W/xgboost ❯❯❯ git log -n 1
commit 74db1e8867eb9ecfacf07311131c2724dbc3fdbd
Author: Nan Zhu <CodingCat@users.noreply.github.com>
Date:   Sun Aug 28 21:25:49 2016 -0400

    [jvm-packages] remove APIs with DMatrix from xgboost-spark  (#1519)

    * test consistency of prediction functions between DMatrix and RDD

    * remove APIs with DMatrix from xgboost-spark

    * fix compilation error in xgboost4j-example

    * fix test cases
```

### 0. 大纲

xgboost的贡献，既有理论上的拓展，也有工程上的性能优化。本文只关心在其在理论上的改进，主要是将正则（Regularized）引入损失函数，并且用损失函数直接指导树的生成。我们用传统GBDT做引，一步步走到xgboost。

这里假设读者已经了解决策树和GBDT的原理，不会过多铺垫。

### 1. GBDT框架

为了便于后续讲解，首先会引入公式描述决策树和GBDT模型，涉及的数学知识是比较简易的，不要恐慌。

对于一个$J$颗叶子的决策树（CART），可以表示为加法模型[1]：

\begin{equation}
    f(x) = h \left (x; \{b_j, R_j\}^J_1 \right ) = \displaystyle \sum_{j=1}^J b_j \mathbf{1}(x \in R_j)
\end{equation}

其中$R_j$是叶子，$b_j$是叶子对应的值。

对于一个tree ensemble model，就是$K$颗树的结果叠加：

\begin{equation}
    \hat{y}_i = \phi(x_i) = \displaystyle \sum_{k=1}^K f_k(x_i)
\end{equation}

具体到传统的GBDT，其可描述成最优问题：

\begin{align}
    f_m &= \displaystyle \operatorname{arg \, min}_f \sum_{i=1}^n L \left ( y_i, \hat{y}_i + f(x_i) \right ) \\
        &= \operatorname{arg \, min} \mathcal{L}(f)
\end{align}

传统的思路，便是借用最速下降的想法，认定损失函数中$f(x_i)$是关于梯度的一个函数。而xgboost正是在这里做了新的文章，引入正则，泰勒展开，再揉进树模型里。

[1]:  Friedman - Greedy function approximation: A gradient boosting machine

### 2. xgboost

#### 2.0 正则

xgboost将正则 $\Omega(f)$ 引入进损失函数 $\mathcal{L}(f)$，控制了树的叶子数$\|R_j\|$和叶子值$b_j$，表示如下：

\begin{align}
    \mathcal{L}(f) &= \displaystyle \sum_{i=1}^{n} L(y_i, \hat{y}_i + f(x_i)) + \Omega(f) \\
    \Omega(f) &= \gamma \|R_j\| + \frac{1}{2} \lambda \|b_j\|^2
\end{align}

但这样纯理论的公式是无法应用到工程上的，要用它指导树模型的生成，必须将它的这两个加法项$\sum L(f)$和$\Omega(f)$整合，才可以直接指导模型生成。要整合，重点有两个，一是打开$\sum L(f)$，放出$f$；二是将$\mathcal{L}(f)$转换成指导树生长的函数。

#### 2.1 泰勒展开

对于第一个问题，打开 $\sum L(f)$，xgboost的方法是利用[泰勒展开](https://zh.wikipedia.org/wiki/泰勒公式)成二阶多项式:

\begin{align}
    \mathcal{L} &\approx \sum_{i=1}^n \left [ L(y_i, \hat{y}_i) + g_i f(x_i) + \frac{1}{2} h_i f^2(x_i) \right ] + \Omega(f) \\
    g_i &= \frac{\partial \, L(y_i, \hat{y}_i)}{\partial \hat{y}_i} \\
    h_i &= \frac{\partial^2 \, L(y_i, \hat{y}_i)}{\partial \hat{y}^2_i} \\
\end{align}

为了便于理解，我将推导过程细致说下：

利用泰勒公式，我们在单点可将一个函数$f(x)$展开为高阶导数的叠加：
\begin{equation}
    \sum _{n=0}^{\infty }{\frac {f^{(n)}(a)}{n!}}(x-a)^{n}
\end{equation}

具体到二阶导数为
\begin{equation}
f(x) \approx f(a)+{\frac  {f'(a)}{1!}}(x-a)+{\frac  {f''(a)}{2!}}(x-a)^{2}
\end{equation}

我们做点变形以利于理解，令$t = \hat{y}_i + f(x_i)$，则
\begin{align}
    L(y_i, \hat{y}_i + f(x_i)) &= L(y_i, t)\\
                               &= L(t) \quad \text{给定$x_i$，则$y_i$是定值，即常数}
\end{align}

接着利用泰勒将$L(t)$在$\hat{y}_i$点展开：

\begin{align}
L(t) &\approx L(\hat{y}_i) + {\frac  {L'(\hat{y}_i)}{1!}}(t-\hat{y}_i)+{\frac  {f''(\hat{y}_i)}{2!}}(t-\hat{y}_i)^{2} \\
     &= L(\hat{y}_i) + L'(\hat{y}_i) f(x_i) + \frac{1}{2}f''(\hat{y}_i) f^{2}(x_i) \\
     &= L(y_i, \hat{y}_i) + L'(\hat{y}_i) f(x_i) + \frac{1}{2}f''(\hat{y}_i) f^{2}(x_i) \\
     &= L(y_i, \hat{y}_i) + g_i f(x_i) + \frac{1}{2} h_i f^2(x_i)
\end{align}

#### 2.2 引入树模型

泰勒解决了第一个问题，打开$L(f)$，得到：

\begin{equation}
    \mathcal{L} \approx \sum_{i=1}^n \left [ L(y_i, \hat{y}_i) + g_i f(x_i) + \frac{1}{2} h_i f^2(x_i) \right ] + \Omega(f)
\end{equation}

接着需要回答第二个问题，在这之前，我们先做点预处理。

对于损失函数$\mathcal{L}$，常数项$L(y_i, \hat{y}_i)$是可以直接舍掉的，记为：

\begin{equation}
    \mathcal{L} = \sum_{i=1}^n \left [ g_i f(x_i) + \frac{1}{2} h_i f^2(x_i) \right ] + \Omega(f)
\end{equation}

如何用这个外部的损失函数指导树模型呢？

在论文Friedman - Greedy function approximation: A gradient boosting machine里推导TreeBoost方法时给了个套路：

1. 将决策树的数学模型$f(x)$代入外部损失函数$\mathcal{L}$。
2. 固定$J$，得到最优的$b_j$解析式。
3. 反代$b_j$回$\mathcal{L}$，将它作为树生成的评价函数（内部损失函数）。

这个套路的思想是，对于树模型来说，主要有两个参数：叶子数、叶子值。通过固定叶子数，就可以利用外部损失函数$\mathcal{L}(f)$寻优到最佳的叶子值计算解析式。将参数反代，外部损失函数就变成只和$x$相关$\mathcal{L}(x)$，这时它就可以作为树生长的评价函数。这个思路挺巧妙的，可能有点绕。

##### 2.2.0 代入树模型

我们就将树模型$f(x)$的数学代入，

\begin{align}
    \mathcal{L} &= \sum_{i=1}^n \left [ g_i f(x_i) + \frac{1}{2} h_i f^2(x_i) \right ] + \Omega(f) \\
                &= \sum_{i=1}^n \left [ g_i \sum_{j=1}^J b_j \mathbf{1}(x_i \in R_j) + \frac{1}{2} h_i (\sum_{j=1}^J \color{red}{b_j} \mathbf{1}(x_i \in R_j))^{\color{red}{2}} \right ] + \Omega(f) \\
                &= \sum_{i=1}^n \left [ g_i \sum_{j=1}^J b_j \mathbf{1}(x_i \in R_j) + \frac{1}{2} h_i \sum_{j=1}^J \color{red}{b_j^2} \mathbf{1}(x_i \in R_j) \right ] + \Omega(f) \quad \text{因为$x_i$只属于一个$R_j$} \\
                &= \sum_{j=1}^J \color{blue}{\sum_{i=1}^n \mathbf{1}(x_i \in R_j)} g_i b_j + \frac{1}{2} \color{blue}{\sum_{j=1}^J \sum_{i=1}^n \mathbf{1}(x_i \in R_j)} h_i b_j^2 + \Omega(f) \quad \text{乘法交换} \\
                &\text{令} I_j = \{ i \, | \, x_i \in R_j \} \quad \text{即属于$R_j$的全部下标$i$} \\
                &= \sum_{j=1}^J \color{blue}{\sum_{i \in I_j}} g_i b_j + \frac{1}{2} \sum_{j=1}^J \color{blue}{\sum_{i \in I_j}} h_i b_j^2 + \Omega(f) \\
                &= \sum_{j=1}^J ( \sum_{i \in I_j} g_i ) b_j + \frac{1}{2} \sum_{j=1}^J (\sum_{i \in I_j} h_i) b_j^2 + \Omega(f) \quad \text{乘法分配律}\\
                &= \sum_{j=1}^J ( \sum_{i \in I_j} g_i ) b_j + \frac{1}{2} \sum_{j=1}^J (\sum_{i \in I_j} h_i) b_j^2 + \gamma \|R_j\| + \frac{1}{2} \lambda \|b_j\|^2 \quad \text{代入正则$\Omega(f)$}\\
                &= \sum_{j=1}^J ( \sum_{i \in I_j} g_i ) b_j + \frac{1}{2} \sum_{j=1}^J (\sum_{i \in I_j} h_i) b_j^2 + \gamma \|R_j\| + \frac{1}{2} \lambda \sum_{j=1}^J b_j^2 \\
                &= \sum_{j=1}^J \left ( ( \sum_{i \in I_j} g_i ) b_j + \frac{1}{2} (\sum_{i \in I_j} h_i) b_j^2 + \frac{1}{2} \lambda b_j^2 \right ) + \gamma \|R_j\|  \\
                &= \sum_{j=1}^J \left ( ( \sum_{i \in I_j} g_i ) b_j + \frac{1}{2} (\sum_{i \in I_j} h_i + \lambda) b_j^2 \right ) + \gamma \|R_j\|  \\
\end{align}

##### 2.2.1 $b_j$最优解析式

\begin{align}
    b_j &= \operatorname{arg \, min}_{b_j} \mathcal{L} \\
        &= \operatorname{arg \, min}_{b_j} \sum_{j=1}^J \left ( ( \sum_{i \in I_j} g_i ) b_j + \frac{1}{2} (\sum_{i \in I_j} h_i + \lambda) b_j^2 \right ) + \gamma \|R_j\|  \\
        &\approx \sum_{j=1}^J \operatorname{arg \, min}_{b_j} \left ( ( \sum_{i \in I_j} g_i ) \color{red}{b_j} + \frac{1}{2} (\sum_{i \in I_j} h_i + \lambda) \color{red}{b_j^2} \right ) + \gamma \|R_j\|
\end{align}

全局最优不好求，我们转而求局部最优。注意到每个局部项是一个二项式，它的最小点可用公式直接套出：

\begin{align}
    b^*_j &= - \frac{\sum_{i \in I_j} g_i}{\sum_{i \in I_j} h_i + \lambda} \\
\end{align}

##### 2.2.2 树生长的评价函数

将$b_j$重代回$\mathcal{L}$得到评价函数：

\begin{align}
    \mathcal{L} &= - \frac{1}{2} \sum_{j=1}^J \frac{(\sum_{i \in I_j} g_i)^2}{\sum_{i \in I_j} h_i + \lambda} + \gamma \|R_j\| \\
                &= - \frac{1}{2} H + \gamma T
\end{align}

于是得到树生成的分割依据：

\begin{align}
    \mathcal{L}_{\text{split}} &= \mathcal{L} - \mathcal{L}_L - \mathcal{L}_R \\
                              &= \frac{1}{2} (H_L + H_R - H) + \gamma (T - T_L - T_R) \\
                              &= \frac{1}{2} (H_L + H_R - H) - \gamma \\
\end{align}

#### 2.3 小结

至此，xgboost对GBDT框架的主要改进就说明完了。我们梳理下，对于一个损失函数 $L$，用它解出老模型的输出 $\hat{y}_i$ 的一阶导 $g_i$ 和二阶导 $h_i$。有了这两阶导数，就可以做为评价函数直接指导决策树的生长，同时算出叶子的值，于是就得到了新树，再加回老模型得到新模型。

我们可以看到，原始的GBDT只是将Gradient Boost框架中的学习模型指定是决策树，这时还是一个通用框架。而TreeBoost和xbgoost，则更进一步，将决策树的数学模型代入Gradeint Boost，从而解出直接指导决策树生长的解析式。也就说，把外部的损失函数，引入到了决策树的建立过程中，这其实就是从通用到定制的过程。所以，可以说，TreeBoost和xgboost的本质，就是对树模型进行定制优化的Gradient Boost。

### 3 工程实现

#### 3.0 训练

正如前面小结所言，xgboost的训练主要是三步：

1. 老模型的预测值$\hat{y}_i$；
2. 计算出两阶导数$g_i$和$h_i$；
3. 将导数信息传入，指导决策树生长。

主体代码位于 `src/learner.cc`，如下：

```C++
288   void UpdateOneIter(int iter, DMatrix* train) override {
289     CHECK(ModelInitialized())
290         << "Always call InitModel or LoadModel before update";
291     if (tparam.seed_per_iteration || rabit::IsDistributed()) {
292       common::GlobalRandom().seed(tparam.seed * kRandSeedMagic + iter);
293     }
294     this->LazyInitDMatrix(train);
295     this->PredictRaw(train, &preds_);
296     obj_->GetGradient(preds_, train->info(), iter, &gpair_);
297     gbm_->DoBoost(train, this->FindBufferOffset(train), &gpair_);
298   }
```

理论比工程好讲，工程实现会有大量细节，相当繁琐。现在时间不多，没有心力面面俱到，不再准备细写了。

我画了简版的UML图，可能不准确，感兴趣的朋友可以参考它，自行从这个入口去追一遍。

```python
SVG("./res/Main.svg")
```

```python

```


```python
import sys
import numpy as np
```

```python
sys.path.insert(0, '../')
sys.path.insert(0, '../detmodel/')
```

```python
import elements
```

```python
import si_mu_late
```

```python
det = si_mu_late.setup_detector('../cards/atlas_mm.yml')
```

```python
det
```

    <detector.Detector at 0x7fa757624c88>

```python
class conf():
    def __init__(self, nevs,ismu,muxmin,muxmax,muymin,muymax,muamin,muamax,bkgr):
        self.nevs=nevs
        self.ismu=ismu
        self.muxmin=muxmin
        self.muxmax=muxmax
        self.muymin=muymin
        self.muymax=muymax
        self.muamin=muamin
        self.muamax=muamax
        self.bkgr=bkgr
```

```python
myconf = conf(1,1,0,0,0,0,0,0,100000)
```

```python
signals = si_mu_late.event(det,myconf)
```

    1
    1
    1
    1
    1
    1
    1
    1

```python
signals
```

```python
import sympy
```

```python
l1 = sympy.Line3D( sympy.Point3D(-1/2, 0, 1), sympy.Point3D(-1/2, 5, 1) )
l2 = sympy.Line3D(sympy.Point3D(5, -5, 1), sympy.Point3D(5, 5, 1))
```

```python
l1.intersection(l2)
```

```python
det.get_signals()
```

```python
det.planes[1].z
```

```python
mu = elements.Muon(0,0,0,0)
```

```python
det.planes[3].pass_muon(mu)
```

```python

```

```python

```

```python

```

```python

```


# snapReactors

Copyright (c) Dan Kotlyar and CoRE group

# Materials Container

* This container stores materials information such as isotopic definition, abundances, uncertainties and material properties.
* The container also stores, modifies or creates new h5 files for data storage.

## Code:

```python
import numpy as np
import sympy as sp
from snapReactors.containers.materials import Material
from snapReactors.containers.property import Constant, Table, Correlation
```

## Defining a new material

### Define with Materials container

1. Give name of material
2. Give type of uncertainty, must be in ``Enum.UTYPE`` which are:
    - ABSOLUTE
    - RELATIVE
    - PERCENTAGE
    - NONE
3. Give the composition type, must be in ``Enum.CTYPE`` which are:
    - RELATIVE
    - WEIGHT
    - ATOMIC
4. Give the isotopes that define a material as a np.array
5. Give the abundances for each isotope as a np.array
6. The uncertainty value, reference, description, and properties are left as optional parameters.
    1. Note that properties must be under ``ALLOWED_PROPERTIES``

```python
mat1 = Material("mat1", 'NONE', 'WEIGHT', np.array([]), np.array([]), None, reference=None, description='This is an example', _properties=None)
print(mat1)
```

    {'matName': ['mat1'], 'utype': [<UTYPE.NONE: 4>], 'ctype': [<CTYPE.WEIGHT: 2>], 'abundances': [array([], dtype=float64)], 'isotopes': [array([], dtype=float64)], 'unc': [None], 'reference': [None], 'description': ['This is an example'], '_properties': [None]}

#### Properties may be inputted as a list of properties or a single property

```python
p1 = Constant(id='cv',  value=1, unit= "J/kg/K", unc=None, ref=None, description=None)
mat2 = Material("mat2", 'NONE', 'WEIGHT', np.array([]), np.array([]), None, reference=None, description='This is an example', _properties=p1)
print(mat2)

p2 = Table('h',  np.array([1, 2, 3, 4]), 'W/K/m^2', np.array([100, 200, 300, 400]), 'K',  unc = np.array([.01, .01, .01, .01]), dependency2=None, dependencyUnit2=None, ref=None, description=None)
corr1 = "T**2 + P + 1/2"
syms1 = "T, P"
p3 = Correlation('h', corr1, syms1, 'W/K/m^2', np.array([300, 600]), 'K', np.array([10, 20]), 'Pa', unc=None, ref=None, description=None)
properties = [p2, p3]
mat3 = Material("mat3", 'NONE', 'WEIGHT', np.array([]), np.array([]), None, reference=None, description='This is an example', _properties=properties)
print(mat3)
```

    {'matName': ['mat2'], 'utype': [<UTYPE.NONE: 4>], 'ctype': [<CTYPE.WEIGHT: 2>], 'abundances': [array([], dtype=float64)], 'isotopes': [array([], dtype=float64)], 'unc': [None], 'reference': [None], 'description': ['This is an example'], '_properties': [<snapReactors.containers.property.Constant object at 0x000001B2DE2BF430>]}
    {'matName': ['mat3'], 'utype': [<UTYPE.NONE: 4>], 'ctype': [<CTYPE.WEIGHT: 2>], 'abundances': [array([], dtype=float64)], 'isotopes': [array([], dtype=float64)], 'unc': [None], 'reference': [None], 'description': ['This is an example'], '_properties': [<snapReactors.containers.property.Table object at 0x000001B2DE2BF400>, <snapReactors.containers.property.Correlation object at 0x000001B2DE2BF640>]}

### Reading in isotopic defintion through a text file

The ``Materials.readData(filename)`` method is used to read in all relevant materials information through a text file. There are several rules to keep in mind for the structure of the text file:
1. The ``Material Name`` must be indicated at the beginning of every material data section with the following format:
    - Material Name: Example Name
2. The ``ctype``, ``utype``, and ``Number of isotopes`` must be indicated before the beginning of ``Isotopic Definition`` although their order doesn't matter.
3. The ``Isotopic Definition`` must have a line between itself and where the definition begins. In the example below a dashed line is used to indicate this seperation.
4. For each input a colon is used to seperate the keyword and input, for example:
    - utype: NONE
5. The location of ``Reference`` and ``Description`` for a specific material must be placed before the beginning of the next ``Material Name`` if present.

Material Property data can be read in by adding a ``Properties`` section.
1. The location of ``Properties`` must be before the beginning of the next ``Material Name`` and is indicated with curly brackets:
   ```
    Properties: {

    }
    ```
2. The formatting for the ``Properties`` information only requires there to be a colon in between the keyword and value, and that each keyword be on its own line
    ```
        type = const, table, corr
        id = property id
        unit = SI or imperial
        must have a ":" between keyword and value i.e "keyword: value"
        each keyword must on its own line i.e
         keyword1: val1
         keyword: val2

        array type inputs are denoted using "[]" i.e [1, 2] or [1 2]
        multidimensional arrays can be denoted using the ";" matlab style i.e
         [1 2; 3 4] or [1, 2;
                        3, 4]
         or by using a newline i.e
           [1 2
            3 4]
        Supports comments by preceeding a line with "%"
        Examples are included below
    }
    ```
3. Structure of ``Properties`` input is outlined in Property Container documentation.

Optional parameters such as reference or uncertainty values can be left out, however, warnings will be highlighted to the user. Two examples for the Material Property data are shown below.

####  Example text file shown below

```python
text_file = open('test.txt')
file_content = text_file.read()
print(file_content)
text_file.close()
```

    Material Name: hasteC
    ctype: RELATIVE
    utype: NONE
    Number of isotopes: 33
    Isotopic Definition:
    -------------------
    6000.03c      0.0007
    27059.03c     0.0125
    24050.03c     0.006952
    24052.03c     0.1340624
    24053.03c     0.0152016
    24054.03c     0.003784
    42092.03c     0.0249033
    42094.03c     0.0156179
    42095.03c     0.0269841
    42096.03c     0.0283441
    42097.03c     0.0162894
    42098.03c     0.0412964
    42100.03c     0.0165648
    23050.03c     0.0000075
    23051.03c     0.0029925
    74180.03c     0.00048
    74182.03c     0.106
    74183.03c     0.05724
    74184.03c     0.12256
    74186.03c     0.11372
    26054.03c     0.003360875
    26056.03c     0.05275855
    26057.03c     0.001218425
    26058.03c     0.00016215
    25055.03c     0.01
    14028.03c     0.00645561
    14029.03c     0.00032795
    14030.03c     0.00032795
    28058.03c     0.1220600887
    28060.03c     0.0470180183
    28061.03c     0.0020438407
    28062.03c     0.0065166585
    28064.03c     0.0016596008

    Properties: {
    %property values for material
    %type = const, table, corr
    %id = property id
    %unit = SI or imperial
    %must have a ":" between keyword and value i.e "keyword: value"
    %each keyword must on its own line i.e
    % keyword1: val1
    % keyword: val2
    %array type inputs are denoted using "[]" i.e [1, 2] or [1 2]
    %multidimensional arrays can be denoted using the ";" matlab style i.e
    % [1 2; 3 4] or [1, 2;
    %                3, 4]
    % or by using a newline i.e
    %   [1 2
         3 4]
    %Supports comments by preceeding a line with "%"
    %Examples are included below

    type:const
    id:cp
    unit:SI
    value:[1]
    unc:[.01]

    type:table
    id:h
    unit:imperial
    ref:NAA-SR-6160
    dep1unit:K
    dep1values: [1 2]
    dep2unit:Pa
    dep2values: [.1 .2]
    value: [1.1 2.1
            3.1 4.1]
    unc: [1 1
            1 1]

    type:corr
    id:r
    unit:SI
    ref:NAA-SR-3120
    corr:T+P**2
    deps:T,P
    dep1unit:K
    dep2unit:Pa
    dep1range: [300,900]
    dep2range: [16,48]
            }
    Reference: NA-Examples
    Description: This is an example input file

    Material Name: hasteB
    ctype: RELATIVE
    utype: NONE
    Number of isotopes: 33
    Isotopic Definition:
    --------------------
    6000.03c     0.0007
    27059.03c    0.0125
    24050.03c    0.006952
    24052.03c    0.1340624
    24053.03c    0.0152016
    24054.03c    0.003784
    42092.03c    0.0249033
    42094.03c    0.0156179
    42095.03c    0.0269841
    42096.03c    0.0283441
    42097.03c    0.0162894
    42098.03c    0.0412964
    42100.03c    0.0165648
    23050.03c    0.0000075
    23051.03c    0.0029925
    74180.03c    0.00048
    74182.03c    0.106
    74183.03c    0.05724
    74184.03c    0.12256
    74186.03c    0.11372
    26054.03c    0.003360875
    26056.03c    0.05275855
    26057.03c    0.001218425
    26058.03c    0.00016215
    25055.03c    0.01
    14028.03c    0.00645561
    14029.03c    0.00032795
    14030.03c    0.00032795
    28058.03c    0.1220600887
    28060.03c    0.0470180183
    28061.03c    0.0020438407
    28062.03c    0.0065166585
    28064.03c    0.0016596008

    Properties: {
    %property values for material
    %type = const, table, corr
    %id = property id
    %unit = SI or imperial
    %must have a ":" between keyword and value i.e "keyword: value"
    %each keyword must on its own line i.e
    % keyword1: val1
    % keyword: val2
    %array type inputs are denoted using "[]" i.e [1, 2] or [1 2]
    %multidimensional arrays can be denoted using the ";" matlab style i.e
    % [1 2; 3 4] or [1, 2;
    %                3, 4]
    % or by using a newline i.e
    %   [1 2
         3 4]
    %Supports comments by preceeding a line with "%"
    %Examples are included below

    type:const
    id:cp
    unit:SI
    value:[1]
    unc:[.01]

    type:table
    id:h
    unit:imperial
    ref:NAA-SR-6160
    dep1unit:K
    dep1values: [1 2]
    dep2unit:Pa
    dep2values: [.1 .2]
    value: [1.1 2.1
            3.1 4.1]
    unc: [1 1
            1 1]

    type:corr
    id:r
    unit:SI
    ref:NAA-SR-3120
    corr:T+P**2
    deps:T,P
    dep1unit:K
    dep2unit:Pa
    dep1range: [300,900]
    dep2range: [16,48]
    }
    Reference: NA-Examples
    Description: This is an example input file

#### Materials definition returned by readData

```python
mats = Material.readData('test.txt')
print(mats)
```

    [<snapReactors.containers.materials.Material object at 0x000001B2DE418FA0>, <snapReactors.containers.materials.Material object at 0x000001B2DE4DF6A0>]

    c:\Users\iaguirre6\Documents\GitHub\docTEST\snapReactors\containers\property.py:669: InputFileSyntaxWarning: reference not given for cp const property @ line: 19
      warnings.warn("reference not given for {} {} property @"
    c:\Users\iaguirre6\Documents\GitHub\docTEST\snapReactors\containers\property.py:868: InputFileSyntaxWarning: uncertainty not given for r corr property @ line: 38
      warnings.warn("uncertainty not given for {} {} property @"

## Updating properties to materials

1. The properties must be from the following list: ['cp', 'cv', 'g', 'h', 'my', 'pr', 'r', 's', 'tc', 'v']

```python
p4 = Constant(id='cv',  value=1, unit= "J/kg/K", unc=None, ref=None, description=None)
print(p4)

mat3.addproperty([p4])
print(mat3)

```

    {'id': 'cv', 'dtype': <DTYPE.NUMBER: 1>, 'vtype': <VTYPE.CONSTANT: 1>, 'value': array([1]), 'valueUnit': 'J/kg/K', 'unc': None, 'dependents': None, 'dependentsUnit': None, 'description': None, 'ref': None}
    {'matName': ['mat3'], 'utype': [<UTYPE.NONE: 4>], 'ctype': [<CTYPE.WEIGHT: 2>], 'abundances': [array([], dtype=float64)], 'isotopes': [array([], dtype=float64)], 'unc': [None], 'reference': [None], 'description': ['This is an example'], '_properties': [<snapReactors.containers.property.Table object at 0x0000020E2EA260A0>, <snapReactors.containers.property.Correlation object at 0x0000020E2EA26250>, <snapReactors.containers.property.Constant object at 0x0000020E2EB83FD0>]}