diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index da8d1f79..7d68d51c 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -6,9 +6,9 @@ name: CI on: # Triggers the workflow on push or pull request events but only for the develop branch push: - branches: [ develop ] + branches: [ develop, v1.0 ] pull_request: - branches: [ develop ] + branches: [ develop, v1.0 ] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: diff --git a/librwfortra.QHHCXIBOJA55O3WWC5YSNGHAWGL2BYRO.gfortran-win_amd64.dll b/librwfortra.QHHCXIBOJA55O3WWC5YSNGHAWGL2BYRO.gfortran-win_amd64.dll new file mode 100644 index 00000000..2fce9bf7 Binary files /dev/null and b/librwfortra.QHHCXIBOJA55O3WWC5YSNGHAWGL2BYRO.gfortran-win_amd64.dll differ diff --git a/notebooks/notebook_shapiro.ipynb b/notebooks/notebook_shapiro.ipynb new file mode 100644 index 00000000..563cbba1 --- /dev/null +++ b/notebooks/notebook_shapiro.ipynb @@ -0,0 +1,263 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "cfc7bba9", + "metadata": {}, + "source": [ + "# Shapiro-Wilk test for normal and lognormal distributions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8e456eaf", + "metadata": {}, + "outputs": [], + "source": [ + "import sandy" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35fae75e", + "metadata": {}, + "outputs": [], + "source": [ + "import logging\n", + "import pandas as pd\n", + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8e08433c", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import seaborn as sns" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c57f6407", + "metadata": {}, + "outputs": [], + "source": [ + "logging.getLogger().setLevel(logging.WARN)" + ] + }, + { + "cell_type": "markdown", + "id": "8d1ddd57", + "metadata": {}, + "source": [ + "Generate 5000 xs samples normally and log-normally distributed" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fa73bbbc", + "metadata": {}, + "outputs": [], + "source": [ + "tape = sandy.get_endf6_file(\"jeff_33\", \"xs\", 10010)\n", + "njoy_kws = dict(err=1, errorr33_kws=dict(mt=102))\n", + "nsmp = 5000\n", + "seed = 5\n", + "\n", + "smp_norm = tape.get_perturbations(nsmp, njoy_kws=njoy_kws, smp_kws=dict(seed33=seed, pdf=\"normal\"))[33]\n", + "smp_lognorm = tape.get_perturbations(nsmp, njoy_kws=njoy_kws, smp_kws=dict(seed33=seed, pdf=\"lognormal\"))[33]\n", + "smp_uniform = tape.get_perturbations(nsmp, njoy_kws=njoy_kws, smp_kws=dict(seed33=seed, pdf=\"uniform\"))[33]" + ] + }, + { + "cell_type": "markdown", + "id": "08f12802", + "metadata": {}, + "source": [ + "## Shapiro-Wilk test normal samples and normal distribution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4cf664f6", + "metadata": {}, + "outputs": [], + "source": [ + "stat_norm = []\n", + "stat_lognorm = []\n", + "for n in [10, 50, 100, 500, 1000, 5000]:\n", + " df = smp_norm.test_shapiro(pdf=\"normal\", size=n)\n", + " idx = df.statistic.idxmin()\n", + " stat_norm.append(df.loc[idx].rename(n))\n", + "\n", + " df = smp_norm.test_shapiro(pdf=\"lognormal\", size=n)\n", + " idx = df.statistic.idxmin()\n", + " stat_lognorm.append(df.loc[idx].rename(n))\n", + "\n", + "opts = dict(left_index=True, right_index=True, suffixes=(\"_norm\", \"_lognorm\"))\n", + "pd.DataFrame(stat_norm).merge(pd.DataFrame(stat_lognorm), **opts).rename_axis(\"# SMP\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "449bdf7d", + "metadata": {}, + "outputs": [], + "source": [ + "test = smp_norm.test_shapiro(pdf=\"normal\", size=5000)\n", + "\n", + "fig, ax = plt.subplots(figsize=(7, 4), dpi=100)\n", + "\n", + "idx = test.statistic.idxmin()\n", + "w = test.loc[idx]\n", + "sns.histplot(data=smp_norm.data.loc[idx], label=f\"W:stat={w.statistic:.2e}, p-value={w.pvalue:.2e}\", color=\"dodgerblue\")\n", + "\n", + "idx = test.statistic.idxmax()\n", + "w = test.loc[idx]\n", + "sns.histplot(data=smp_norm.data.loc[idx], label=f\"W:stat={w.statistic:.2e}, p-value={w.pvalue:.2e}\", color=\"tomato\")\n", + "\n", + "ax.set(xlabel=\"W\")\n", + "ax.legend()\n", + "fig.tight_layout()" + ] + }, + { + "cell_type": "markdown", + "id": "78c3e97d", + "metadata": {}, + "source": [ + "## Shapiro-Wilk test for lognormal samples and lognormal distribution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "be16d4f1", + "metadata": {}, + "outputs": [], + "source": [ + "stat_norm = []\n", + "stat_lognorm = []\n", + "for n in [10, 50, 100, 500, 1000, 5000]:\n", + " df = smp_lognorm.test_shapiro(pdf=\"normal\", size=n)\n", + " idx = df.statistic.idxmin()\n", + " stat_norm.append(df.loc[idx].rename(n))\n", + "\n", + " df = smp_lognorm.test_shapiro(pdf=\"lognormal\", size=n)\n", + " idx = df.statistic.idxmin()\n", + " stat_lognorm.append(df.loc[idx].rename(n))\n", + "\n", + "opts = dict(left_index=True, right_index=True, suffixes=(\"_norm\", \"_lognorm\"))\n", + "pd.DataFrame(stat_norm).merge(pd.DataFrame(stat_lognorm), **opts).rename_axis(\"# SMP\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8fa36199", + "metadata": {}, + "outputs": [], + "source": [ + "test = smp_lognorm.test_shapiro(pdf=\"lognormal\", size=5000)\n", + "\n", + "fig, ax = plt.subplots(figsize=(7, 4), dpi=100)\n", + "\n", + "idx = test.statistic.idxmin()\n", + "w = test.loc[idx]\n", + "sns.histplot(data=smp_lognorm.data.loc[idx], label=f\"W:stat={w.statistic:.2e}, p-value={w.pvalue:.2e}\", color=\"dodgerblue\")\n", + "\n", + "idx = test.statistic.idxmax()\n", + "w = test.loc[idx]\n", + "sns.histplot(data=smp_lognorm.data.loc[idx], label=f\"W:stat={w.statistic:.2e}, p-value={w.pvalue:.2e}\", color=\"tomato\")\n", + "\n", + "ax.set(xlabel=\"W\")\n", + "ax.legend()\n", + "fig.tight_layout()" + ] + }, + { + "cell_type": "markdown", + "id": "975dbaa1", + "metadata": {}, + "source": [ + "## Shapiro-Wilk test for uniform samples and normal distribution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bded2114", + "metadata": {}, + "outputs": [], + "source": [ + "stat_norm = []\n", + "stat_lognorm = []\n", + "for n in [10, 50, 100, 500, 1000, 5000]:\n", + " df = smp_uniform.test_shapiro(pdf=\"normal\", size=n)\n", + " idx = df.statistic.idxmin()\n", + " stat_norm.append(df.loc[idx].rename(n))\n", + "\n", + " df = smp_uniform.test_shapiro(pdf=\"lognormal\", size=n)\n", + " idx = df.statistic.idxmin()\n", + " stat_lognorm.append(df.loc[idx].rename(n))\n", + "\n", + "opts = dict(left_index=True, right_index=True, suffixes=(\"_norm\", \"_lognorm\"))\n", + "pd.DataFrame(stat_norm).merge(pd.DataFrame(stat_lognorm), **opts).rename_axis(\"# SMP\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b6e1d4ee", + "metadata": {}, + "outputs": [], + "source": [ + "test = smp_uniform.test_shapiro(pdf=\"uniform\", size=5000)\n", + "\n", + "fig, ax = plt.subplots(figsize=(7, 4), dpi=100)\n", + "\n", + "idx = test.statistic.idxmin()\n", + "w = test.loc[idx]\n", + "sns.histplot(data=smp_uniform.data.loc[idx], label=f\"W:stat={w.statistic:.2e}, p-value={w.pvalue:.2e}\", color=\"dodgerblue\")\n", + "\n", + "idx = test.statistic.idxmax()\n", + "w = test.loc[idx]\n", + "sns.histplot(data=smp_uniform.data.loc[idx], label=f\"W:stat={w.statistic:.2e}, p-value={w.pvalue:.2e}\", color=\"tomato\")\n", + "\n", + "ax.set(xlabel=\"W\")\n", + "ax.legend()\n", + "fig.tight_layout()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python3 (sandy-devel)", + "language": "python", + "name": "sandy-devel" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/requirements_conda.txt b/requirements_conda.txt index c712a5e4..14bd012a 100644 --- a/requirements_conda.txt +++ b/requirements_conda.txt @@ -11,4 +11,4 @@ scipy sphinx seaborn >= 0.9 statsmodels -pytables \ No newline at end of file +pytables diff --git a/rwf.cp310-win_amd64.pyd b/rwf.cp310-win_amd64.pyd new file mode 100644 index 00000000..a870aa6c Binary files /dev/null and b/rwf.cp310-win_amd64.pyd differ diff --git a/sandy/__init__.py b/sandy/__init__.py index 9421da0e..80c7d4fd 100755 --- a/sandy/__init__.py +++ b/sandy/__init__.py @@ -7,7 +7,7 @@ from .decay import * from .energy_grids import * from .errorr import * -from .groupr import * +from .gendf import * from .fy import * from .tsl import * from .gls import * @@ -23,6 +23,7 @@ from .utils import * from .core import * from .sampling import * +from .spectra import * import sandy.mcnp import sandy.aleph2 import sandy.tools diff --git a/sandy/core/cov.py b/sandy/core/cov.py index 2d80d14d..fb1cd76b 100644 --- a/sandy/core/cov.py +++ b/sandy/core/cov.py @@ -4,7 +4,6 @@ import scipy import scipy.linalg import scipy.sparse as sps -import scipy.sparse.linalg as spsl import pandas as pd import matplotlib.pyplot as plt import seaborn as sns @@ -25,7 +24,6 @@ "corr2cov", "random_corr", "random_cov", - "sample_distribution", ] S = np.array([[1, 1, 1], @@ -247,7 +245,7 @@ class CategoryCov(): from_stack create a covariance matrix from a stacked `pd.DataFrame` from_stdev - construct a covariance matrix from a stdev vector + construct a covariance matrix from a standard deviation vector from_var construct a covariance matrix from a variance vector get_corr @@ -269,7 +267,7 @@ def __repr__(self): return self.data.__repr__() def __init__(self, *args, **kwargs): - self.data = pd.DataFrame(*args, **kwargs) + self.data = pd.DataFrame(*args, dtype=float, **kwargs) @property def data(self): @@ -305,11 +303,14 @@ def data(self): @data.setter def data(self, data): - self._data = pd.DataFrame(data, dtype=float) + self._data = data + if not len(data.shape) == 2 and data.shape[0] == data.shape[1]: raise TypeError("Covariance matrix must have two dimensions") + if not (np.diag(data) >= 0).all(): raise TypeError("Covariance matrix must have positive variance") + sym_limit = 10 # Round to avoid numerical fluctuations if not (data.values.round(sym_limit) == data.values.T.round(sym_limit)).all(): @@ -319,6 +320,233 @@ def data(self, data): def size(self): return self.data.values.shape[0] + @classmethod + def random_corr(cls, size, correlations=True, seed=None, **kwargs): + """ + >>> sandy.CategoryCov.random_corr(2, seed=1) + 0 1 + 0 1.00000e+00 4.40649e-01 + 1 4.40649e-01 1.00000e+00 + + >>> sandy.CategoryCov.random_corr(2, correlations=False, seed=1) + 0 1 + 0 1.00000e+00 0.00000e+00 + 1 0.00000e+00 1.00000e+00 + """ + np.random.seed(seed=seed) + corr = np.eye(size) + if correlations: + offdiag = np.random.uniform(-1, 1, size**2).reshape(size, size) + up = np.triu(offdiag, 1) + else: + up = np.zeros([size, size]) + corr += up + up.T + return cls(corr, **kwargs) + + @classmethod + def random_cov(cls, size, stdmin=0.0, stdmax=1.0, correlations=True, + seed=None, **kwargs): + """ + Construct a covariance matrix with random values + + Parameters + ---------- + size : `int` + Dimension of the original matrix + stdmin : `float`, default is 0 + minimum value of the uniform standard deviation vector + stdmax : `float`, default is 1 + maximum value of the uniform standard deviation vector + correlation : `bool`, default is True + flag to insert the random correlations in the covariance matrix + seed : `int`, optional, default is `None` + seed for the random number generator (by default use `numpy` + dafault pseudo-random number generator) + + Returns + ------- + `CategoryCov` + object containing covariance matrix + + Examples + -------- + >>> sandy.CategoryCov.random_cov(2, seed=1) + 0 1 + 0 2.15373e-02 5.97134e-03 + 1 5.97134e-03 8.52642e-03 + """ + corr = random_corr(size, correlations=correlations, seed=seed) + std = np.random.uniform(stdmin, stdmax, size) + return CategoryCov(corr).corr2cov(std) + + @classmethod + def from_stdev(cls, std): + """ + Construct the covariance matrix from the standard deviation vector. + + Parameters + ---------- + var : 1D iterable + Standar deviation vector. + + Returns + ------- + `CategoryCov` + Object containing the covariance matrix. + + Example + ------- + Create covariance from stdev in `pd.Series`. + >>> var = pd.Series(np.array([0, 2, 3]), index=pd.Index(["A", "B", "C"])) + >>> std = np.sqrt(var) + >>> cov = sandy.CategoryCov.from_stdev(std) + >>> cov + A B C + A 0.00000e+00 0.00000e+00 0.00000e+00 + B 0.00000e+00 2.00000e+00 0.00000e+00 + C 0.00000e+00 0.00000e+00 3.00000e+00 + """ + std_ = pd.Series(std) + return cls.from_var(std_ * std_) + + @classmethod + def from_var(cls, var): + """ + Construct the covariance matrix from the variance vector. + + Parameters + ---------- + var : 1D iterable + Variance vector. + + Returns + ------- + `CategoryCov` + Object containing the covariance matrix. + + Example + ------- + Create covariance from variance in `pd.Series`. + >>> var = pd.Series(np.array([0, 2, 3]), index=pd.Index(["A", "B", "C"])) + >>> cov = sandy.CategoryCov.from_var(var) + >>> cov + A B C + A 0.00000e+00 0.00000e+00 0.00000e+00 + B 0.00000e+00 2.00000e+00 0.00000e+00 + C 0.00000e+00 0.00000e+00 3.00000e+00 + + Create covariance from variance in list. + >>> sandy.CategoryCov.from_var([1, 2, 3]) + 0 1 2 + 0 1.00000e+00 0.00000e+00 0.00000e+00 + 1 0.00000e+00 2.00000e+00 0.00000e+00 + 2 0.00000e+00 0.00000e+00 3.00000e+00 + """ + var_ = pd.Series(var) + values = np.diag(var_) + cov = pd.DataFrame(values, index=var_.index, columns=var_.index) + return cls(cov) + + @classmethod + def from_stack(cls, data_stack, index, columns, values, rows=10000000, + kind='upper'): + """ + Create a covariance matrix from a stacked dataframe. + + Parameters + ---------- + data_stack : `pd.Dataframe` + Stacked dataframe. + index : 1D iterable, optional + Index of the final covariance matrix. + columns : 1D iterable, optional + Columns of the final covariance matrix. + values : `str`, optional + Name of the column where the values are located. + rows : `int`, optional + Number of rows to take into account into each loop. The default + is 10000000. + kind : `str`, optional + Select if the stack data represents upper or lower triangular + matrix. The default is 'upper. + + Returns + ------- + `sandy.CategoryCov` + Covarinace matrix. + + Examples + -------- + If the stack data represents the covariance matrix: + >>> S = pd.DataFrame(np.array([[1, 1, 1], [1, 2, 1], [1, 1, 1]])) + >>> S = S.stack().reset_index().rename(columns = {'level_0': 'dim1', 'level_1': 'dim2', 0: 'cov'}) + >>> S = S[S['cov'] != 0] + >>> sandy.CategoryCov.from_stack(S, index=['dim1'], columns=['dim2'], values='cov', kind='all') + dim2 0 1 2 + dim1 + 0 1.00000e+00 1.00000e+00 1.00000e+00 + 1 1.00000e+00 2.00000e+00 1.00000e+00 + 2 1.00000e+00 1.00000e+00 1.00000e+00 + + If the stack data represents only the upper triangular part of the + covariance matrix: + >>> test_1 = sandy.CategoryCov.from_stack(minimal_covtest, index=["MAT", "MT", "E"], columns=["MAT1", "MT1", "E1"], values='VAL').data + >>> test_1 + MAT1 9437 + MT1 2 102 + E1 1.00000e-02 2.00000e+05 1.00000e-02 2.00000e+05 + MAT MT E + 9437 2 1.00000e-02 2.00000e-02 0.00000e+00 4.00000e-02 0.00000e+00 + 2.00000e+05 0.00000e+00 9.00000e-02 0.00000e+00 5.00000e-02 + 102 1.00000e-02 4.00000e-02 0.00000e+00 1.00000e-02 0.00000e+00 + 2.00000e+05 0.00000e+00 5.00000e-02 0.00000e+00 1.00000e-02 + + >>> test_2 = sandy.CategoryCov.from_stack(minimal_covtest, index=["MAT", "MT", "E"], columns=["MAT1", "MT1", "E1"], values='VAL', rows=1).data + >>> test_2 + MAT1 9437 + MT1 2 102 + E1 1.00000e-02 2.00000e+05 1.00000e-02 2.00000e+05 + MAT MT E + 9437 2 1.00000e-02 2.00000e-02 0.00000e+00 4.00000e-02 0.00000e+00 + 2.00000e+05 0.00000e+00 9.00000e-02 0.00000e+00 5.00000e-02 + 102 1.00000e-02 4.00000e-02 0.00000e+00 1.00000e-02 0.00000e+00 + 2.00000e+05 0.00000e+00 5.00000e-02 0.00000e+00 1.00000e-02 + + >>> assert (test_1 == test_2).all().all() + + If the stack data represents only the lower triangular part of the + covariance matrix: + >>> test_1 = sandy.CategoryCov.from_stack(minimal_covtest, index=["MAT1", "MT1", "E1"], columns=["MAT", "MT", "E"], values='VAL', kind="lower").data + >>> test_1 + MAT 9437 + MT 2 102 + E 1.00000e-02 2.00000e+05 1.00000e-02 2.00000e+05 + MAT1 MT1 E1 + 9437 2 1.00000e-02 2.00000e-02 0.00000e+00 4.00000e-02 0.00000e+00 + 2.00000e+05 0.00000e+00 9.00000e-02 0.00000e+00 5.00000e-02 + 102 1.00000e-02 4.00000e-02 0.00000e+00 1.00000e-02 0.00000e+00 + 2.00000e+05 0.00000e+00 5.00000e-02 0.00000e+00 1.00000e-02 + + >>> test_2 = sandy.CategoryCov.from_stack(minimal_covtest, index=["MAT1", "MT1", "E1"], columns=["MAT", "MT", "E"], values='VAL', kind="lower", rows=1).data + >>> test_2 + MAT 9437 + MT 2 102 + E 1.00000e-02 2.00000e+05 1.00000e-02 2.00000e+05 + MAT1 MT1 E1 + 9437 2 1.00000e-02 2.00000e-02 0.00000e+00 4.00000e-02 0.00000e+00 + 2.00000e+05 0.00000e+00 9.00000e-02 0.00000e+00 5.00000e-02 + 102 1.00000e-02 4.00000e-02 0.00000e+00 1.00000e-02 0.00000e+00 + 2.00000e+05 0.00000e+00 5.00000e-02 0.00000e+00 1.00000e-02 + + >>> assert (test_1 == test_2).all().all() + """ + cov = segmented_pivot_table(data_stack, rows=rows, index=index, + columns=columns, values=values) + if kind == 'all': + return cls(cov) + else: + return triu_matrix(cov, kind=kind) + def get_std(self): """ Extract standard deviations. @@ -423,7 +651,7 @@ def get_eig(self, tolerance=None): Real test on H1 file >>> endf6 = sandy.get_endf6_file("jeff_33", "xs", 10010) >>> ek = sandy.energy_grids.CASMO12 - >>> err = endf6.get_errorr(ek_errorr=ek, err=1) + >>> err = endf6.get_errorr(errorr_kws=dict(ek=ek), err=1)["errorr33"] >>> cov = err.get_cov() >>> cov.get_eig()[0].sort_values(ascending=False).head(7) 0 3.66411e-01 @@ -511,7 +739,7 @@ def invert(self): >>> np.testing.assert_array_equal(sandy.CategoryCov(c.invert()).invert(), c.data) Test on real ND covariance. With previous implementation this test failed. - >>> c = sandy.get_endf6_file("jeff_33", "xs", 10010).get_errorr(err=1, mt=[102]).get_cov(multigroup=False) + >>> c = sandy.get_endf6_file("jeff_33", "xs", 10010).get_errorr(err=1, errorr_kws=dict(mt=102))["errorr33"].get_cov(multigroup=False) >>> cinv = c.invert() >>> np.testing.assert_array_almost_equal(c.data, np.linalg.pinv(cinv, hermitian=True)) >>> assert (cinv.index == c.data.index).all() @@ -526,7 +754,7 @@ def invert(self): def _double_invert(self): """ Test method get a covariance matrix without negative eigs. - BEcause of the properties of pinv, this should be equivalent to + Because of the properties of pinv, this should be equivalent to reconstructing the matrix as `V @ E @ V^T`, where `V` are the eigenvectors and `E` are truncated eigenvalues. @@ -536,7 +764,7 @@ def _double_invert(self): inverse of the inverted matrix Test on real ND covariance. - >>> c = sandy.get_endf6_file("jeff_33", "xs", 10010).get_errorr(err=1, mt=[102]).get_cov(multigroup=False) + >>> c = sandy.get_endf6_file("jeff_33", "xs", 10010).get_errorr(err=1, errorr_kws=dict(mt=102))["errorr33"].get_cov(multigroup=False) >>> c2 = c._double_invert() >>> np.testing.assert_array_almost_equal(c.data, c2.data) >>> assert (c2.data.index == c.data.index).all() @@ -549,114 +777,7 @@ def _double_invert(self): columns=self.data.columns, ) - def log2norm_cov(self, mu): - """ - Transform covariance matrix to the one of the underlying normal - distribution. - - Parameters - ---------- - mu : iterable - The desired mean values of the target lognormal distribution. - - Returns - ------- - `CategoryCov` of the underlying normal covariance matrix - - Examples - -------- - >>> cov = CategoryCov(pd.DataFrame([[8, 2, 3], [2, 10, 4], [3, 4, 7]], index=['A', 'B', 'C'], columns=['A', 'B', 'C'])) - >>> cov.log2norm_cov(pd.Series(np.ones(cov.data.shape[0]), index=cov.data.index)) - A B C - A 2.19722e+00 1.09861e+00 1.38629e+00 - B 1.09861e+00 2.39790e+00 1.60944e+00 - C 1.38629e+00 1.60944e+00 2.07944e+00 - - >>> cov = CategoryCov(pd.DataFrame([[8, 2, 3], [2, 10, 4], [3, 4, 7]], index=['A', 'B', 'C'], columns=['A', 'B', 'C'])) - >>> mu = pd.Series([1, 2, .5], index=["A", "B", "C"]) - >>> cov.log2norm_cov(mu) - A B C - A 2.19722e+00 6.93147e-01 1.94591e+00 - B 6.93147e-01 1.25276e+00 1.60944e+00 - C 1.94591e+00 1.60944e+00 3.36730e+00 - - >>> cov = CategoryCov(pd.DataFrame([[8, 2, 3], [2, 10, 4], [3, 4, 7]], index=['A', 'B', 'C'], columns=['A', 'B', 'C'])) - >>> mu = [1, 2, .5] - >>> cov.log2norm_cov(mu) - A B C - A 2.19722e+00 6.93147e-01 1.94591e+00 - B 6.93147e-01 1.25276e+00 1.60944e+00 - C 1.94591e+00 1.60944e+00 3.36730e+00 - - >>> cov = CategoryCov(pd.DataFrame([[8, 2, 3], [2, 10, 4], [3, 4, 7]], index=['A', 'B', 'C'], columns=['A', 'B', 'C'])) - >>> mu = np.array([1, 2, .5]) - >>> cov.log2norm_cov(mu) - A B C - A 2.19722e+00 6.93147e-01 1.94591e+00 - B 6.93147e-01 1.25276e+00 1.60944e+00 - C 1.94591e+00 1.60944e+00 3.36730e+00 - - Notes - ----- - ..notes:: Reference for the equation is 10.1016/j.nima.2012.06.036 - .. math:: - $$ - cov(lnx_i, lnx_j) = \ln\left(\frac{cov(x_i,x_j)}{\cdot}+1\right) - $$ - """ - mu_ = np.diag(1 / pd.Series(mu)) - mu_ = pd.DataFrame(mu_, index=self.data.index, columns=self.data.index) - return self.__class__(np.log(self.sandwich(mu_).data + 1)) - - - def log2norm_mean(self, mu): - """ - Transform mean values to the mean values of the undelying normal - distribution. - - Parameters - ---------- - mu : iterable - The target mean values. - - Returns - ------- - `pd.Series` of the underlyig normal distribution mean values - - Examples - -------- - >>> cov = CategoryCov(pd.DataFrame([[8, 2, 3], [2, 10, 4], [3, 4, 7]], index=['A', 'B', 'C'], columns=['A', 'B', 'C'])) - >>> mu = pd.Series(np.ones(cov.data.shape[0]), index=cov.data.index) - >>> cov.log2norm_mean(mu) - A -1.09861e+00 - B -1.19895e+00 - C -1.03972e+00 - dtype: float64 - - >>> cov = CategoryCov(pd.DataFrame([[8, 2, 3], [2, 10, 4], [3, 4, 7]], index=['A', 'B', 'C'], columns=['A', 'B', 'C'])) - >>> cov.log2norm_mean([1, 1, 1]) - A -1.09861e+00 - B -1.19895e+00 - C -1.03972e+00 - dtype: float64 - - >>> cov = CategoryCov(pd.DataFrame([[8, 2, 3], [2, 10, 4], [3, 4, 7]], index=['A', 'B', 'C'], columns=['A', 'B', 'C'])) - >>> mu = np.ones(cov.data.shape[0]) - >>> cov.log2norm_mean(mu) - A -1.09861e+00 - B -1.19895e+00 - C -1.03972e+00 - dtype: float64 - - Reindexing example - - """ - mu_ = pd.Series(mu) - mu_.index = self.data.index - return np.log(mu_**2 / np.sqrt(np.diag(self.data) + mu_**2)) - - def sampling(self, nsmp, seed=None, rows=None, pdf='normal', - tolerance=None, relative=True): + def sampling(self, nsmp, seed=None, pdf='normal', tolerance=0, relative=True, **kwargs): """ Extract perturbation coefficients according to chosen distribution with covariance from given covariance matrix. See note for non-normal @@ -670,9 +791,6 @@ def sampling(self, nsmp, seed=None, rows=None, pdf='normal', seed : `int`, optional, default is `None` seed for the random number generator (by default use `numpy` dafault pseudo-random number generator). - rows : `int`, optional, default is `None` - option to use row calculation for matrix calculations. This option - defines the number of lines to be taken into account in each loop. pdf : `str`, optional, default is 'normal' random numbers distribution. Available distributions are: @@ -692,346 +810,165 @@ def sampling(self, nsmp, seed=None, rows=None, pdf='normal', `sandy.Samples` object containing samples - Notes - ----- - .. note:: sampling with uniform distribution is performed on - diagonal covariance matrix, neglecting all correlations. - - .. note:: sampling with relative covariance matrix is performed - setting all the negative perturbation coefficients equal to 0 - and the ones larger than 2 equal to 2 for normal distribution, or - adjusting the standard deviations in such a way that negative - samples are avoided for uniform distribution. - - .. note:: sampling with lognormal distribution gives a set of samples - with mean=1 as lognormal distribution can not have mean=0. - Therefore, `relative` parameter does not apply to it. - - Examples - -------- - Draw 3 sets of samples using custom seed: - >>> sandy.CategoryCov([[1, 0.4],[0.4, 1]]).sampling(3, seed=11, relative=False).data + 1 - 0 1 - 0 -7.49455e-01 -2.13159e+00 - 1 1.28607e+00 1.10684e+00 - 2 1.48457e+00 9.00879e-01 - - >>> sandy.CategoryCov([[1, 0.4],[0.4, 1]]).sampling(3, seed=11, rows=1, relative=False).data + 1 - 0 1 - 0 -7.49455e-01 -2.13159e+00 - 1 1.28607e+00 1.10684e+00 - 2 1.48457e+00 9.00879e-01 - - >>> sample = sandy.CategoryCov([[1, 0.4],[0.4, 1]]).sampling(1000000, seed=11, relative=False) - >>> sample.data.cov() - 0 1 - 0 9.98662e-01 3.99417e-01 - 1 3.99417e-01 9.98156e-01 - - Small negative eigenvalue: - >>> sandy.CategoryCov([[1, -.2],[-.2, 3]]).sampling(3, seed=11, tolerance=0, relative=False).data + 1 - 0 1 - 0 2.74945e+00 5.21505e+00 - 1 7.13927e-01 1.07147e+00 - 2 5.15435e-01 1.64683e+00 - - >>> sandy.CategoryCov([[1, -.2],[-.2, 3]]).sampling(1000000, seed=11, tolerance=0, relative=False).data.cov() - 0 1 - 0 9.98662e-01 -1.99822e-01 - 1 -1.99822e-01 2.99437e+00 - - Sampling with different `pdf`: - >>> sandy.CategoryCov([[1, -.2],[-.2, 3]]).sampling(3, seed=11, pdf='uniform', tolerance=0, relative=False).data + 1 - 0 1 - 0 -1.07578e-01 2.34960e+00 - 1 -6.64587e-01 5.21222e-01 - 2 8.72585e-01 9.12563e-01 - - >>> sandy.CategoryCov([[1, .2],[.2, 3]]).sampling(3, seed=11, pdf='lognormal', tolerance=0) - 0 1 - 0 3.03419e+00 1.57919e+01 - 1 5.57248e-01 4.74160e-01 - 2 4.72366e-01 6.50840e-01 - - >>> sandy.CategoryCov([[1, -.2],[-.2, 3]]).sampling(1000000, seed=11, pdf='uniform', tolerance=0, relative=False).data.cov() - 0 1 - 0 1.00042e+00 -1.58806e-03 - 1 -1.58806e-03 3.00327e+00 - - >>> sandy.CategoryCov([[1, .2],[.2, 3]]).sampling(1000000, seed=11, pdf='lognormal', tolerance=0).data.cov() - 0 1 - 0 1.00219e+00 1.99199e-01 - 1 1.99199e-01 3.02605e+00 - - `relative` kwarg usage: - >>> sandy.CategoryCov([[1, -.2],[-.2, 3]]).sampling(1000000, seed=11, pdf='normal', tolerance=0, relative=False).data.mean(axis=0) + 1 - 0 1.00014e+00 - 1 9.99350e-01 - dtype: float64 - - >>> sandy.CategoryCov([[1, -.2],[-.2, 3]]).sampling(1000000, seed=11, pdf='normal', tolerance=0, relative=False).data.mean(axis=0) - 0 1.41735e-04 - 1 -6.49679e-04 - dtype: float64 - - >>> sandy.CategoryCov([[1, -.2],[-.2, 3]]).sampling(1000000, seed=11, pdf='uniform', tolerance=0, relative=False).data.mean(axis=0) + 1 - 0 9.98106e-01 - 1 9.99284e-01 - dtype: float64 - - >>> sandy.CategoryCov([[1, -.2],[-.2, 3]]).sampling(1000000, seed=11, pdf='uniform', tolerance=0, relative=False).data.mean(axis=0) - 0 -1.89367e-03 - 1 -7.15929e-04 - dtype: float64 - - Lognormal distribution sampling independency from `relative` kwarg - >>> sandy.CategoryCov([[1, .2],[.2, 3]]).sampling(1000000, seed=11, pdf='lognormal', tolerance=0, relative=True).data.mean(axis=0) - 0 9.99902e-01 - 1 9.99284e-01 - dtype: float64 - - >>> sandy.CategoryCov([[1, .2],[.2, 3]]).sampling(1000000, seed=11, pdf='lognormal', tolerance=0, relative=False).data.mean(axis=0) - 0 9.99902e-01 - 1 9.99284e-01 - dtype: float64 - - >>> sandy.CategoryCov([[1, 0.4],[0.4, 1]]).sampling(3, seed=11, pdf='normal', relative=True) - 0 1 - 0 0.00000e+00 0.00000e+00 - 1 1.28607e+00 1.10684e+00 - 2 1.48457e+00 9.00879e-01 - - >>> sandy.CategoryCov([[1, 0.4],[0.4, 1]]).sampling(3, seed=11, pdf='uniform', relative=True) - 0 1 - 0 3.60539e-01 1.44987e+00 - 1 3.89505e-02 8.40407e-01 - 2 9.26437e-01 9.70854e-01 - - >>> sandy.CategoryCov([[1, -.2],[-.2, 3]]).sampling(3, seed=11, tolerance=0, relative=True) - 0 1 - 0 2.00000e+00 2.00000e+00 - 1 7.13927e-01 1.07147e+00 - 2 5.15435e-01 1.64683e+00 - """ - dim = self.data.shape[0] - pdf_ = pdf if pdf != 'lognormal' else 'normal' - y = sample_distribution(dim, nsmp, seed=seed, pdf=pdf_) - 1 - y = sps.csc_matrix(y) - # the covariance matrix to decompose is created depending on the chosen - # pdf - if pdf == 'uniform': - to_decompose = self.__class__(np.diag(np.diag(self.data))) - if relative: - a = np.sqrt(12) / 2 # upper bound of the distribution y - std = np.sqrt(np.diag(to_decompose.data)) - std_modified = np.where(std < 1 / a, std, 1 / a) - cov = np.diag(std_modified**2) - to_decompose = self.__class__(cov, index=to_decompose.data.index, - columns=to_decompose.data.columns) - elif pdf == 'lognormal': - ones = np.ones(self.data.shape[0]) - to_decompose = self.log2norm_cov(ones) - else: - to_decompose = self - L = sps.csr_matrix(to_decompose.get_L(rows=rows, - tolerance=tolerance)) - samples = pd.DataFrame(L.dot(y).toarray(), index=self.data.index, - columns=list(range(nsmp))) - if pdf == 'lognormal': - # mean value of lognormally sampled distributions will be one by - # defaul - samples = np.exp(samples.add(self.log2norm_mean(ones), axis=0)) - elif relative: - samples += 1 - lower_bound = samples > 0 - upper_bound = samples < 2 - samples = samples.where(lower_bound, 0) - samples = samples.where(upper_bound, 2) - return sandy.Samples(samples.T) - - @classmethod - def from_var(cls, var): - """ - Construct the covariance matrix from the variance vector. - - Parameters - ---------- - var : 1D iterable - Variance vector. - - Returns - ------- - `CategoryCov` - Object containing the covariance matrix. + Notes + ----- + .. note:: sampling with uniform distribution is performed on + diagonal covariance matrix, neglecting all correlations. - Example - ------- - >>> S = pd.Series(np.array([0, 2, 3]), index=pd.Index([1, 2, 3])) - >>> cov = sandy.CategoryCov.from_var(S) - >>> cov - 1 2 3 - 1 0.00000e+00 0.00000e+00 0.00000e+00 - 2 0.00000e+00 2.00000e+00 0.00000e+00 - 3 0.00000e+00 0.00000e+00 3.00000e+00 + .. note:: sampling with relative covariance matrix is performed + setting all the negative perturbation coefficients equal to 0 + and the ones larger than 2 equal to 2 for normal distribution, or + adjusting the standard deviations in such a way that negative + samples are avoided for uniform distribution. - >>> assert type(cov) is sandy.CategoryCov + .. note:: sampling with lognormal distribution gives a set of samples + with mean=1 since a lognormal distribution cannot have mean=0. + In this case the `relative` parameter does not apply to it. - >>> S = sandy.CategoryCov.from_var((1, 2, 3)) - >>> S - 0 1 2 - 0 1.00000e+00 0.00000e+00 0.00000e+00 - 1 0.00000e+00 2.00000e+00 0.00000e+00 - 2 0.00000e+00 0.00000e+00 3.00000e+00 + Examples + -------- + Common parameters. + >>> seed = 11 + >>> nsmp = 1e5 + + Create Positive-Definite covariance matrix with small stdev. + >>> index = columns = ["A", "B"] + >>> c = pd.DataFrame([[1, 0.4],[0.4, 1]], index=index, columns=index) / 10 + >>> cov = sandy.CategoryCov(c) - >>> assert type(S) is sandy.CategoryCov - >>> assert type(sandy.CategoryCov.from_var([1, 2, 3])) is sandy.CategoryCov - """ - var_ = pd.Series(var) - cov_values = sps.diags(var_.values).toarray() - cov = pd.DataFrame(cov_values, - index=var_.index, columns=var_.index) - return cls(cov) + Draw relative samples using different distributions. + >>> smp_n = cov.sampling(nsmp, seed=seed, pdf='normal') + >>> smp_ln = cov.sampling(nsmp, seed=seed, pdf='lognormal') + >>> smp_u = cov.sampling(nsmp, seed=seed, pdf='uniform') - @classmethod - def from_stdev(cls, std): - """ - Construct the covariance matrix from the standard deviation vector. + The sample mean converges to a unit vector. + >>> np.testing.assert_array_almost_equal(smp_n.get_mean(), [1, 1], decimal=2) + >>> np.testing.assert_array_almost_equal(smp_ln.get_mean(), [1, 1], decimal=2) + >>> np.testing.assert_array_almost_equal(smp_u.get_mean(), [1, 1], decimal=2) - Parameters - ---------- - std : `pandas.Series` - Standard deviations vector. + The sample covariance converges to the original one. + >>> np.testing.assert_array_almost_equal(smp_n.get_cov(), c, decimal=3) + >>> np.testing.assert_array_almost_equal(smp_ln.get_cov(), c, decimal=3) + >>> np.testing.assert_array_almost_equal(np.diag(smp_u.get_cov()), np.diag(c), decimal=3) - Returns - ------- - `CategoryCov` - Object containing the covariance matrix. + Samples are reproducible by setting a seed. + assert cov.sampling(nsmp, seed=seed, pdf='normal').data.equals(smp_n.data) - Example - ------- - >>> S = pd.Series(np.array([0, 2, 3]), index=pd.Index([1, 2, 3])) - >>> cov = sandy.CategoryCov.from_stdev(S) - >>> cov - 1 2 3 - 1 0.00000e+00 0.00000e+00 0.00000e+00 - 2 0.00000e+00 4.00000e+00 0.00000e+00 - 3 0.00000e+00 0.00000e+00 9.00000e+00 - >>> assert type(cov) is sandy.CategoryCov - >>> S = sandy.CategoryCov.from_stdev((1, 2, 3)) - >>> S - 0 1 2 - 0 1.00000e+00 0.00000e+00 0.00000e+00 - 1 0.00000e+00 4.00000e+00 0.00000e+00 - 2 0.00000e+00 0.00000e+00 9.00000e+00 + Create Positive-Definite covariance matrix with small stdev (small negative eig). + >>> c = pd.DataFrame([[1, 1.2],[1.2, 1]], index=index, columns=index) / 10 + >>> cov = sandy.CategoryCov(c) - >>> assert type(S) is sandy.CategoryCov - >>> assert type(sandy.CategoryCov.from_stdev([1, 2, 3])) is sandy.CategoryCov - """ - std_ = pd.Series(std) - var = std_ * std_ - return cls.from_var(var) + >>> smp_0 = cov.sampling(nsmp, seed=seed, pdf='normal', tolerance=0) + >>> np.testing.assert_array_almost_equal(np.diag(smp_0.get_cov()), np.diag(c), decimal=2) + >>> smp_inf = cov.sampling(nsmp, seed=seed, pdf='normal', tolerance=np.inf) + >>> with pytest.raises(Exception): + ... raise np.testing.assert_array_almost_equal(np.diag(smp_0.get_cov()), np.diag(c), decimal=1) - @classmethod - def from_stack(cls, data_stack, index, columns, values, rows=10000000, - kind='upper'): - """ - Create a covariance matrix from a stacked dataframe. - Parameters - ---------- - data_stack : `pd.Dataframe` - Stacked dataframe. - index : 1D iterable, optional - Index of the final covariance matrix. - columns : 1D iterable, optional - Columns of the final covariance matrix. - values : `str`, optional - Name of the column where the values are located. - rows : `int`, optional - Number of rows to take into account into each loop. The default - is 10000000. - kind : `str`, optional - Select if the stack data represents upper or lower triangular - matrix. The default is 'upper. - Returns - ------- - `sandy.CategoryCov` - Covarinace matrix. + Create Positive-Definite covariance matrix with small stdev (large negative eig). + >>> c = pd.DataFrame([[1, 4],[4, 1]], index=index, columns=index) / 10 + >>> cov = sandy.CategoryCov(c) + + Samples kind of converge only if we set a low tolerance + >>> smp_0 = cov.sampling(nsmp, seed=seed, pdf='normal', tolerance=0) + >>> with pytest.raises(Exception): + ... raise np.testing.assert_array_almost_equal(np.diag(smp_0.get_cov()), np.diag(c), decimal=1) + >>> smp_inf = cov.sampling(nsmp, seed=seed, pdf='normal', tolerance=np.inf) + >>> with pytest.raises(Exception): + ... raise np.testing.assert_array_almost_equal(np.diag(smp_0.get_cov()), np.diag(c), decimal=1) + + + + Create Positive-Definite covariance matrix with large stdev. + >>> index = columns = ["A", "B"] + >>> c = pd.DataFrame([[1, 0.4],[0.4, 1]], index=index, columns=index) / 10 + >>> cov = sandy.CategoryCov(c) + + Need to increase the amount of samples + >>> nsmp = 1e6 + + The sample mean still converges to a unit vector. + >>> np.testing.assert_array_almost_equal(smp_n.get_mean(), [1, 1], decimal=2) + >>> np.testing.assert_array_almost_equal(smp_ln.get_mean(), [1, 1], decimal=2) + >>> np.testing.assert_array_almost_equal(smp_u.get_mean(), [1, 1], decimal=2) + + Only the lognormal covariance still converges. + >>> with pytest.raises(Exception): + ... raise np.testing.assert_array_almost_equal(smp_n.get_cov(), c, decimal=1) + >>> np.testing.assert_array_almost_equal(smp_ln.get_cov(), c, decimal=2) + >>> with pytest.raises(Exception): + ... raise np.testing.assert_array_almost_equal(np.diag(smp_u.get_cov()), np.diag(c), decimal=1) + """ + allowed_pdf = [ + "normal", + "lognormal", + "uniform", + ] + if pdf not in allowed_pdf: + raise ValueError("`pdf='lognormal'` not allowed") + + if not relative and pdf=='lognormal': + raise ValueError("`pdf='lognormal'` and `relative=False` is not a valid combination") + + nsmp_ = int(nsmp) - Examples - -------- - If the stack data represents the covariance matrix: - >>> S = pd.DataFrame(np.array([[1, 1, 1], [1, 2, 1], [1, 1, 1]])) - >>> S = S.stack().reset_index().rename(columns = {'level_0': 'dim1', 'level_1': 'dim2', 0: 'cov'}) - >>> S = S[S['cov'] != 0] - >>> sandy.CategoryCov.from_stack(S, index=['dim1'], columns=['dim2'], values='cov', kind='all') - dim2 0 1 2 - dim1 - 0 1.00000e+00 1.00000e+00 1.00000e+00 - 1 1.00000e+00 2.00000e+00 1.00000e+00 - 2 1.00000e+00 1.00000e+00 1.00000e+00 + # -- Draw IID samples with mu=0 and std=1 + np.random.seed(seed=seed) + if pdf == 'uniform': + a = np.sqrt(12) / 2 + y = np.random.uniform(-a, a, (self.size, nsmp_)) + else: + y = np.random.randn(self.size, nsmp_) - If the stack data represents only the upper triangular part of the - covariance matrix: - >>> test_1 = sandy.CategoryCov.from_stack(minimal_covtest, index=["MAT", "MT", "E"], columns=["MAT1", "MT1", "E1"], values='VAL').data - >>> test_1 - MAT1 9437 - MT1 2 102 - E1 1.00000e-02 2.00000e+05 1.00000e-02 2.00000e+05 - MAT MT E - 9437 2 1.00000e-02 2.00000e-02 0.00000e+00 4.00000e-02 0.00000e+00 - 2.00000e+05 0.00000e+00 9.00000e-02 0.00000e+00 5.00000e-02 - 102 1.00000e-02 4.00000e-02 0.00000e+00 1.00000e-02 0.00000e+00 - 2.00000e+05 0.00000e+00 5.00000e-02 0.00000e+00 1.00000e-02 + # -- Fix covariance matrix according to distribution + if pdf == 'uniform': + # no cross-correlation term is considered + if relative: + a = np.sqrt(12) / 2 # upper bound of the distribution y + std = np.sqrt(np.diag(self.data)) + std_modified = np.where(std < 1 / a, std, 1 / a) + cov = np.diag(std_modified**2) + else: + cov = np.diag(np.diag(self.data)) + to_decompose = self.__class__(cov, index=self.data.index, columns=self.data.columns) + elif pdf == 'lognormal': + ucov = np.log(self.sandwich(np.eye(self.size)).data + 1).values # covariance matrix of underlying normal distribution + to_decompose = self.__class__(ucov, index=self.data.index, columns=self.data.columns) + else: + to_decompose = self - >>> test_2 = sandy.CategoryCov.from_stack(minimal_covtest, index=["MAT", "MT", "E"], columns=["MAT1", "MT1", "E1"], values='VAL', rows=1).data - >>> test_2 - MAT1 9437 - MT1 2 102 - E1 1.00000e-02 2.00000e+05 1.00000e-02 2.00000e+05 - MAT MT E - 9437 2 1.00000e-02 2.00000e-02 0.00000e+00 4.00000e-02 0.00000e+00 - 2.00000e+05 0.00000e+00 9.00000e-02 0.00000e+00 5.00000e-02 - 102 1.00000e-02 4.00000e-02 0.00000e+00 1.00000e-02 0.00000e+00 - 2.00000e+05 0.00000e+00 5.00000e-02 0.00000e+00 1.00000e-02 + # -- Decompose covariance into lower triangular + L = to_decompose.get_L(tolerance=tolerance) - >>> assert (test_1 == test_2).all().all() + # -- Apply covariance to samples + sparse = False # it seems to me that a sparse inner product is only marginally faster + if sparse: + y = sps.csc_matrix(y) + L = scipy.sparse.csr_matrix(L) + inner = (L @ y).toarray() + else: + inner = L @ y - If the stack data represents only the lower triangular part of the - covariance matrix: - >>> test_1 = sandy.CategoryCov.from_stack(minimal_covtest, index=["MAT1", "MT1", "E1"], columns=["MAT", "MT", "E"], values='VAL', kind="lower").data - >>> test_1 - MAT 9437 - MT 2 102 - E 1.00000e-02 2.00000e+05 1.00000e-02 2.00000e+05 - MAT1 MT1 E1 - 9437 2 1.00000e-02 2.00000e-02 0.00000e+00 4.00000e-02 0.00000e+00 - 2.00000e+05 0.00000e+00 9.00000e-02 0.00000e+00 5.00000e-02 - 102 1.00000e-02 4.00000e-02 0.00000e+00 1.00000e-02 0.00000e+00 - 2.00000e+05 0.00000e+00 5.00000e-02 0.00000e+00 1.00000e-02 + index = self.data.index + columns = list(range(nsmp_)) + samples = pd.DataFrame(inner, index=index, columns=columns) - >>> test_2 = sandy.CategoryCov.from_stack(minimal_covtest, index=["MAT1", "MT1", "E1"], columns=["MAT", "MT", "E"], values='VAL', kind="lower", rows=1).data - >>> test_2 - MAT 9437 - MT 2 102 - E 1.00000e-02 2.00000e+05 1.00000e-02 2.00000e+05 - MAT1 MT1 E1 - 9437 2 1.00000e-02 2.00000e-02 0.00000e+00 4.00000e-02 0.00000e+00 - 2.00000e+05 0.00000e+00 9.00000e-02 0.00000e+00 5.00000e-02 - 102 1.00000e-02 4.00000e-02 0.00000e+00 1.00000e-02 0.00000e+00 - 2.00000e+05 0.00000e+00 5.00000e-02 0.00000e+00 1.00000e-02 + # -- Fix sample (and sample mean) according to distribution + if pdf == 'lognormal': + mu = np.ones(self.size) + umu = np.log(mu**2 / np.sqrt(np.diag(self.data) + mu**2)) # mean of the underlying normal distribution + samples = np.exp(samples.add(umu, axis=0)) + elif relative: + samples += 1 + lower_bound = samples > 0 + upper_bound = samples < 2 + samples = samples.where(lower_bound, 0) + samples = samples.where(upper_bound, 2) - >>> assert (test_1 == test_2).all().all() - """ - cov = segmented_pivot_table(data_stack, rows=rows, index=index, - columns=columns, values=values) - if kind == 'all': - return cls(cov) - else: - return triu_matrix(cov, kind=kind) + return sandy.Samples(samples) def gls_cov_update(self, S, Vy_extra=None): """ @@ -1250,65 +1187,6 @@ def from_csv(cls, file, **kwargs): df = pd.read_csv(file, **kwargs) return cls(df) - @classmethod - def random_corr(cls, size, correlations=True, seed=None, **kwargs): - """ - >>> sandy.CategoryCov.random_corr(2, seed=1) - 0 1 - 0 1.00000e+00 4.40649e-01 - 1 4.40649e-01 1.00000e+00 - - >>> sandy.CategoryCov.random_corr(2, correlations=False, seed=1) - 0 1 - 0 1.00000e+00 0.00000e+00 - 1 0.00000e+00 1.00000e+00 - """ - np.random.seed(seed=seed) - corr = np.eye(size) - if correlations: - offdiag = np.random.uniform(-1, 1, size**2).reshape(size, size) - up = np.triu(offdiag, 1) - else: - up = np.zeros([size, size]) - corr += up + up.T - return cls(corr, **kwargs) - - @classmethod - def random_cov(cls, size, stdmin=0.0, stdmax=1.0, correlations=True, - seed=None, **kwargs): - """ - Construct a covariance matrix with random values - - Parameters - ---------- - size : `int` - Dimension of the original matrix - stdmin : `float`, default is 0 - minimum value of the uniform standard deviation vector - stdmax : `float`, default is 1 - maximum value of the uniform standard deviation vector - correlation : `bool`, default is True - flag to insert the random correlations in the covariance matrix - seed : `int`, optional, default is `None` - seed for the random number generator (by default use `numpy` - dafault pseudo-random number generator) - - Returns - ------- - `CategoryCov` - object containing covariance matrix - - Examples - -------- - >>> sandy.CategoryCov.random_cov(2, seed=1) - 0 1 - 0 2.15373e-02 5.97134e-03 - 1 5.97134e-03 8.52642e-03 - """ - corr = random_corr(size, correlations=correlations, seed=seed) - std = np.random.uniform(stdmin, stdmax, size) - return CategoryCov(corr).corr2cov(std) - def to_sparse(self, method='csr_matrix'): """ Method to extract `CategoryCov` values into a sparse matrix @@ -1798,62 +1676,6 @@ def from_lb6(cls, evalues_r, evalues_c, fvalues): return cls(cov, index=evalues_r, columns=evalues_c) -class GlobalCov(CategoryCov): - - @classmethod - def from_list(cls, iterable): - """ - Extract global cross section/nubar covariance matrix from iterables - of `EnergyCovs`. - - Parameters - ---------- - iterable : iterable - list of tuples/lists/iterables with content `[mat, mt, mat1, mt1, EnergyCov]` - - Returns - ------- - `XsCov` or `pandas.DataFrame` - global cross section/nubar covariance matrix (empty dataframe if no covariance matrix was found) - """ - columns = ["KEYS_ROWS", "KEYS_COLS", "COV"] - # Reindex the cross-reaction matrices - covs = pd.DataFrame.from_records(iterable).set_axis(columns, axis=1).set_index(columns[:-1]).COV - for (keys_rows,keys_cols), cov in covs.iteritems(): - if keys_rows == keys_cols: # diagonal terms - if cov.data.shape[0] != cov.data.shape[1]: - raise SandyError("non-symmetric covariance matrix for ({}, {})".format(keys_rows, keys_cols)) - if not np.allclose(cov.data, cov.data.T): - raise SandyError("non-symmetric covariance matrix for ({}, {})".format(keys_rows, keys_cols)) - else: # off-diagonal terms - condition1 = (keys_rows,keys_rows) in covs.index - condition2 = (keys_cols,keys_cols) in covs.index - if not (condition1 and condition2): - covs[keys_rows,keys_cols] = np.nan - logging.warn("skip covariance matrix for ({}, {})".format(keys_rows, keys_cols)) - continue - ex = covs[keys_rows,keys_rows].data.index.values - ey = covs[keys_cols,keys_cols].data.columns.values - covs[keys_rows,keys_cols] = cov.change_grid(ex, ey) - covs.dropna(inplace=True) - if covs.empty: - logging.warn("covariance matrix is empty") - return pd.DataFrame() - # Create index for global matrix - rows_levels = covs.index.levels[0] - indexlist = [(*keys,e) for keys in rows_levels for e in covs[(keys,keys)].data.index.values] - index = pd.MultiIndex.from_tuples(indexlist, names=cls.labels) - # Create global matrix - matrix = np.zeros((len(index),len(index))) - for (keys_rows,keys_cols), cov in covs.iteritems(): - ix = index.get_loc(keys_rows) - ix1 = index.get_loc(keys_cols) - matrix[ix.start:ix.stop,ix1.start:ix1.stop] = cov.data - if keys_rows != keys_cols: - matrix[ix1.start:ix1.stop,ix.start:ix.stop] = cov.data.T - return cls(matrix, index=index, columns=index) - - def corr2cov(corr, s): """ Produce covariance matrix given correlation matrix and standard @@ -2165,102 +1987,6 @@ def restore_size(nonzero_idxs, mat_reduced, dim): return pd.DataFrame(mat) -def sample_distribution(dim, nsmp, seed=None, pdf='normal'): - """ - Extract random independent and identically distributed samples according to - the chosen distribution with standard deviation=1 and mean=1. - - Parameters - ---------- - dim : `int` - Dimension of the matrix from where we obtain the samples. - nsmp : `int` - number of samples. - seed : `int`, optional, default is `None` - seed for the random number generator (by default use `numpy` - dafault pseudo-random number generator). - pdf : `str`, optional - Random numbers distribution. The default is 'normal' - Available distributions are: - * `'normal'` - * `'uniform'` - * `'lognormal'` - - Returns - ------- - y : `np.array` - Numpy array with the random numbers. - - Notes - ----- - .. note:: the implementation of the lognormal distribution sampling is - performed with the following equations: - ..math:: - $$ - \mu_{LN} = \exp(\mu_N + \frac{\sigma_N^2}{2})\\ - \sigma_{LN} = \sqrt{[\exp(2\mu_N + 2\sigma_N^2) - \exp(2\mu_N + \sigma_N^2)]} - $$ - More details can be found in reference: https://linkinghub.elsevier.com/retrieve/pii/S0168900213008450 - DOI: 10.1016/J.NIMA.2013.06.025 - - Examples - -------- - >>> sandy.cov.sample_distribution(2, 3, seed=11) - array([[ 2.74945474, 0.713927 , 0.51543487], - [-1.65331856, 0.99171537, 0.68036864]]) - - >>> sandy.cov.sample_distribution(2, 3, seed=11, pdf='uniform') - array([[-0.10757829, -0.66458659, 0.87258524], - [ 1.77919399, 0.72357718, 0.94951799]]) - - >>> sandy.cov.sample_distribution(2, 3, seed=11, pdf='lognormal') - array([[3.03418551, 0.55724795, 0.4723663 ], - [0.07764515, 0.70224636, 0.54189439]]) - - >>> sandy.cov.sample_distribution(2, 1000000, seed=11).mean().round(5) - 1.00025 - - >>> sandy.cov.sample_distribution(2, 1000000, seed=11, pdf='uniform').mean().round(5) - 0.99885 - - >>> sandy.cov.sample_distribution(2, 1000000, seed=11, pdf='lognormal').mean().round(5) - 0.99953 - - >>> sandy.cov.sample_distribution(2, 1000000, seed=11).std().round(5) - 0.99919 - - >>> sandy.cov.sample_distribution(2, 1000000, seed=11, pdf='uniform').std().round(5) - 1.00038 - - >>> sandy.cov.sample_distribution(2, 1000000, seed=11, pdf='lognormal').std().round(5) - 0.99756 - - >>> np.corrcoef(sandy.cov.sample_distribution(2, 1000000, seed=11)).round(5) - array([[1.e+00, 5.e-05], - [5.e-05, 1.e+00]]) - - >>> np.corrcoef(sandy.cov.sample_distribution(2, 1000000, seed=11, pdf='uniform')).round(5) - array([[ 1.0e+00, -9.2e-04], - [-9.2e-04, 1.0e+00]]) - - >>> np.corrcoef(sandy.cov.sample_distribution(2, 1000000, seed=11, pdf='lognormal')).round(5) - array([[ 1.0e+00, -2.6e-04], - [-2.6e-04, 1.0e+00]]) - """ - np.random.seed(seed=seed) - if pdf == 'normal': - y = np.random.randn(dim, nsmp) + 1 - elif pdf == 'uniform': - a = np.sqrt(12) / 2 - y = np.random.uniform(-a, a, (dim, nsmp)) + 1 - elif pdf == 'lognormal': - sl = ml = 1 # target mean and standard deviation of the lognormal distribution - mn = 2 * np.log(ml) - .5 * np.log(sl**2 + np.exp(2 * np.log(ml))) # required mean of the corresponding normal distibution (note reference in the docstring) - sn = np.sqrt(2 * (np.log(ml) - mn)) # required standard deviation of the corresponding normal distibution (note reference in the docstring) - y = np.random.lognormal(mn, sn, (dim, nsmp)) - return y - - def random_corr(size, correlations=True, seed=None): np.random.seed(seed=seed) corr = np.eye(size) @@ -2281,16 +2007,3 @@ def random_cov(size, stdmin=0.0, stdmax=1.0, correlations=True, seed=None): def random_ctg_cov(index, stdmin=0.0, stdmax=1.0, correlations=True, seed=None): cov = random_cov(len(index), stdmin=stdmin, stdmax=stdmax, correlations=correlations, seed=seed) return pd.DataFrame(cov, index=index, columns=index) - - -def print_matrix(size, triu_matrices): - coords = list(zip(*np.triu_indices(size))) - kwargs = {"cbar": False, "vmin": -1, "vmax": 1, "cmap": "RdBu"} - fig,ax = plt.subplots(size, size, sharex="col", sharey="row") - for (i,j), m in zip(coords, MM): - if m is None: - continue - ax[i,j] = sns.heatmap(m, ax=ax[i,j], **kwargs) - if i != j: - ax[j,i] = sns.heatmap(m.T, ax=ax[j,i], **kwargs) - return fig, ax diff --git a/sandy/core/endf6.py b/sandy/core/endf6.py index 620ee1cf..b6ea0a4b 100644 --- a/sandy/core/endf6.py +++ b/sandy/core/endf6.py @@ -5,7 +5,6 @@ @author: lfiorito """ import io -import shutil import os from functools import reduce from tempfile import TemporaryDirectory @@ -13,8 +12,10 @@ from urllib.request import urlopen, Request, urlretrieve from zipfile import ZipFile import re +import types import pytest +import multiprocessing as mp import numpy as np import pandas as pd import numpy as np @@ -166,6 +167,14 @@ def get_tsl_index(library): return +nsubs = { + 4: "decay", + 10: "neutron", + 11: "nfpy", + 10010: "proton", + } + + def get_endf6_file(library, kind, zam, to_file=False): """ Given a library and a nuclide import the corresponding ENDF-6 nuclear @@ -363,7 +372,7 @@ def get_endf6_file(library, kind, zam, to_file=False): Available libraries are: {available_libs} """ ) - if kind == 'dxs': + elif kind == 'dxs': available_libs = ( "jeff_33".upper(), "proton".upper(), @@ -467,6 +476,8 @@ def get_endf6_file(library, kind, zam, to_file=False): Available libraries are: {available_libs} """ ) + else: + raise ValueError(f"option 'kind={kind}' is not supported") if str(zam).lower() == 'all': if kind.lower() == 'xs' or kind.lower() == 'dxs': @@ -522,6 +533,9 @@ class _FormattedFile(): Create dataframe from endf6 text in string. to_series Covert content into `pandas.Series`. + to_file + Given a filename write the content of the instance to disk in + ASCII format. Notes ----- @@ -600,13 +614,16 @@ def kind(self): Examples -------- - >>> file = os.path.join(sandy.data.__path__[0], "h1.endf") - >>> _FormattedFile.from_file(file).kind - 'endf6' - - >>> file = os.path.join(sandy.data.__path__[0], "h1.pendf") - >>> _FormattedFile.from_file(file).kind - 'pendf' + >>> assert sandy.get_endf6_file("jeff_33", "decay", 10010).kind == "endf6" + >>> assert sandy.get_endf6_file("jeff_33", "nfpy", 922350).kind == "endf6" + >>> assert sandy.get_endf6_file("jeff_33", "xs", 10010).kind == "endf6" + >>> assert sandy.get_endf6_file("jeff_33", "xs", 10010).get_pendf(err=1).kind == "pendf" + >>> assert sandy.get_endf6_file("jeff_33", "xs", 10010).get_gendf(err=1).kind == "gendf" + >>> outs = sandy.get_endf6_file("jeff_33", "xs", 942410).get_errorr(err=1, errorr_kws=dict(mt=18)) + >>> assert outs["errorr31"].kind == "errorr" + >>> assert outs["errorr33"].kind == "errorr" + >>> assert outs["errorr34"].kind == "errorr" + >>> assert outs["errorr35"].kind == "errorr" """ if len(self.mat) > 1: msg = "Attribute 'kind' does not work if more than 1 MAT number is" @@ -623,10 +640,12 @@ def kind(self): else: if lrp == 2: kind = "pendf" - elif lrp == 1 or lrp == 0: + elif lrp in [-1, 0, 1]: + # -1 for decay and nfpy + # 0 for endf6 kind = "endf6" else: - kind == "unkwown" + kind = "unkwown" return kind @classmethod @@ -691,11 +710,11 @@ def read_url(filename, rooturl): Examples -------- - - >>> filename = "n-1-H-001.jeff32" - >>> rooturl = "https://www.oecd-nea.org/dbforms/data/eva/evatapes/jeff_32/" - >>> file = sandy.Endf6.read_url(filename, rooturl) - >>> print(file[0:890]) + Removed because website stopped working + #>>> filename = "n-1-H-001.jeff32" + #>>> rooturl = "https://www.oecd-nea.org/dbforms/data/eva/evatapes/jeff_32/" + #>>> file = sandy.Endf6.read_url(filename, rooturl) + #>>> print(file[0:890]) JEFF-3.2 Release - Neutron File March 2014 0 0 0 1.001000+3 9.991673-1 0 0 2 5 125 1451 1 0.000000+0 0.000000+0 0 0 0 6 125 1451 2 @@ -1050,7 +1069,7 @@ def merge(self, *iterable): Returns ------- - merged : TYPE + merged : :func:`_FormattedFile` a ENDF6 file containing the MAT/MF/MT sections of `self` and of the passed ENDF6 files. @@ -1065,100 +1084,26 @@ def merge(self, *iterable): Merge two files. >>> h1 = sandy.get_endf6_file("jeff_33", 'xs', 10010) >>> h2 = sandy.get_endf6_file("endfb_71", 'xs', 10020) - >>> h1.merge(h2) - MAT MF MT - 125 1 451 1.001000+3 9.991673-1 0 0 ... - 2 151 1.001000+3 9.991673-1 0 0 ... - 3 1 1.001000+3 9.991673-1 0 0 ... - 2 1.001000+3 9.991673-1 0 0 ... - 102 1.001000+3 9.991673-1 0 0 ... - 4 2 1.001000+3 9.991673-1 0 1 ... - 6 102 1.001000+3 9.991673-1 0 2 ... - 33 1 1.001000+3 9.991673-1 0 0 ... - 2 1.001000+3 9.991673-1 0 0 ... - 102 1.001000+3 9.991673-1 0 0 ... - 128 1 451 1.002000+3 1.996800+0 0 0 ... - 2 151 1.002000+3 1.996800+0 0 0 ... - 3 1 1.002000+3 1.996800+0 0 0 ... - 2 1.002000+3 1.996800+0 0 0 ... - 3 1.002000+3 1.996800+0 0 0 ... - 16 1.002000+3 1.996800+0 0 0 ... - 102 1.002000+3 1.996800+0 0 0 ... - 4 2 1.002000+3 1.996800+0 0 2 ... - 6 16 1.002000+3 1.996800+0 0 1 ... - 8 102 1.002000+3 1.996800+0 0 0 ... - 9 102 1.002000+3 1.996800+0 0 0 ... - 12 102 1.002000+3 1.996800+0 1 0 ... - 14 102 1.002000+3 1.996800+0 1 0 ... - 33 1 1.002000+3 1.996800+0 0 0 ... - 2 1.002000+3 1.996800+0 0 0 ... - 16 1.002000+3 1.996800+0 0 0 ... - 102 1.002000+3 1.996800+0 0 0 ... - dtype: object + >>> h = h1.merge(h2) + >>> assert h.to_series()[h1.to_series().index].equals(h1.to_series()) + >>> assert h.to_series()[h2.to_series().index].equals(h2.to_series()) Merge three files from different libraries. >>> h3 = sandy.get_endf6_file("endfb_71", 'xs', 10030) - >>> h1.merge(h2, h3) - MAT MF MT - 125 1 451 1.001000+3 9.991673-1 0 0 ... - 2 151 1.001000+3 9.991673-1 0 0 ... - 3 1 1.001000+3 9.991673-1 0 0 ... - 2 1.001000+3 9.991673-1 0 0 ... - 102 1.001000+3 9.991673-1 0 0 ... - 4 2 1.001000+3 9.991673-1 0 1 ... - 6 102 1.001000+3 9.991673-1 0 2 ... - 33 1 1.001000+3 9.991673-1 0 0 ... - 2 1.001000+3 9.991673-1 0 0 ... - 102 1.001000+3 9.991673-1 0 0 ... - 128 1 451 1.002000+3 1.996800+0 0 0 ... - 2 151 1.002000+3 1.996800+0 0 0 ... - 3 1 1.002000+3 1.996800+0 0 0 ... - 2 1.002000+3 1.996800+0 0 0 ... - 3 1.002000+3 1.996800+0 0 0 ... - 16 1.002000+3 1.996800+0 0 0 ... - 102 1.002000+3 1.996800+0 0 0 ... - 4 2 1.002000+3 1.996800+0 0 2 ... - 6 16 1.002000+3 1.996800+0 0 1 ... - 8 102 1.002000+3 1.996800+0 0 0 ... - 9 102 1.002000+3 1.996800+0 0 0 ... - 12 102 1.002000+3 1.996800+0 1 0 ... - 14 102 1.002000+3 1.996800+0 1 0 ... - 33 1 1.002000+3 1.996800+0 0 0 ... - 2 1.002000+3 1.996800+0 0 0 ... - 16 1.002000+3 1.996800+0 0 0 ... - 102 1.002000+3 1.996800+0 0 0 ... - 131 1 451 1.003000+3 2.989596+0 0 0 ... - 2 151 1.003000+3 2.989596+0 0 0 ... - 3 1 1.003000+3 2.989596+0 0 0 ... - 2 1.003000+3 2.989596+0 0 0 ... - 16 1.003000+3 2.989596+0 0 0 ... - 4 2 1.003000+3 2.989596+0 0 1 ... - 16 1.003000+3 2.989596+0 0 2 ... - 5 16 1.003000+3 2.989596+0 0 0 ... - dtype: object + >>> h_ = h1.merge(h2, h3).to_series() + >>> h__ = h.merge(h3).to_series() + >>> h___ = h1.merge(h2).merge(h3).to_series() + >>> assert h_.equals(h__) and h_.equals(h___) Merge two evaluations for the same nuclide. - >>> h2_2 = sandy.get_endf6_file("jeff_32", 'xs', 10020) - >>> h2.merge(h2_2) - MAT MF MT - 128 1 451 1.002000+3 1.995712+0 0 0 ... - 2 151 1.002000+3 1.995712+0 0 0 ... - 3 1 1.002000+3 1.995712+0 0 0 ... - 2 1.002000+3 1.995712+0 0 0 ... - 3 1.002000+3 1.995712+0 0 0 ... - 16 1.002000+3 1.995712+0 0 0 ... - 102 1.002000+3 1.995712+0 0 0 ... - 4 2 1.002000+3 1.995712+0 0 1 ... - 6 16 1.002000+3 1.995712+0 0 1 ... - 8 102 1.002000+3 1.996800+0 0 0 ... - 9 102 1.002000+3 1.996800+0 0 0 ... - 12 102 1.002000+3 1.995712+0 1 0 ... - 14 102 1.002000+3 1.995712+0 1 0 ... - 33 1 1.002000+3 1.996800+0 0 0 ... - 2 1.002000+3 1.996800+0 0 0 ... - 16 1.002000+3 1.996800+0 0 0 ... - 102 1.002000+3 1.996800+0 0 0 ... - dtype: object + >>> bi_71 = sandy.get_endf6_file("endfb_71", 'xs', 832090) + >>> bi_33 = sandy.get_endf6_file("jeff_33", 'xs', 832090) + >>> bi = bi_71.merge(bi_33) + >>> assert not bi.to_series()[bi_71.to_series().index].equals(bi_71.to_series()) + >>> assert bi.to_series()[bi_33.to_series().index].equals(bi_33.to_series()) + >>> bi = bi_33.merge(bi_71) + >>> assert bi.to_series()[bi_71.to_series().index].equals(bi_71.to_series()) + >>> assert not bi.to_series()[bi_33.to_series().index].equals(bi_33.to_series()) """ tape = reduce(lambda x, y: x.add_sections(y.data), iterable) merged = self.add_sections(tape.data) @@ -1242,8 +1187,7 @@ def write_string(self, title=""): Examples -------- - >>> file = os.path.join(sandy.data.__path__[0], "h1.endf") - >>> string = sandy.Endf6.from_file(file).write_string() + >>> string = sandy.get_endf6_file("jeff_33", "xs", 10010).write_string() >>> print(string[:81 * 4 - 1]) 1 0 0 0 1.001000+3 9.991673-1 0 0 2 5 125 1451 1 @@ -1252,11 +1196,10 @@ def write_string(self, title=""): if no modification is applied to the `_FormattedFile` content, the `write_string` returns an output identical to the file ASCII content. - >>> assert string == open(file).read() Test with `sandy.Errorr` object and title option: >>> endf6 = sandy.get_endf6_file("jeff_33", "xs", 10010) - >>> err = endf6.get_errorr(ek=[1e-2, 1e1, 2e7], err=1) + >>> err = endf6.get_errorr(ek=[1e-2, 1e1, 2e7], err=1)["errorr33"] >>> err.to_file("out.err", title="H with ERRORR") >>> err_2 = sandy.Errorr.from_file("out.err") >>> os.remove("out.err") @@ -1327,32 +1270,232 @@ class Endf6(_FormattedFile): Process `Endf6` instance into a Errorr file using NJOY. get_id Extract ID for a given MAT for a ENDF-6 file. + get_nsub + Determine ENDF-6 sub-library type. + get_records + Extract tabulated MAT, MF and MT numbers. read_section Parse MAT/MF/MT section. - to_file - Given a filename write the content of a `Endf6` instance to disk in - ASCII format. - to_string - Write `Endf6.data` content to string according to the ENDF-6 file - rules. - write_string - Write ENDF-6 content to string. + update_intro + Update MF1/MT451. """ - def _get_nsub(self): + def update_intro(self, **kwargs): """ - Determine ENDF-6 sub-library type by reading flag "NSUB" of first MAT - in file: + Method to update MF1/MT451 of each MAT based on the file content + (concistency is enforced) and user-given keyword arguments. + + Parameters + ---------- + **kwargs : `dict` + dictionary of elements to be modified in section MF1/MT451 (it + applies to all MAT numbers). + + Returns + ------- + :func:`~sandy.Endf6` + :func:`~sandy.Endf6` with updated MF1/MT451. - * `NSUB = 10` : Incident-Neutron Data - * `NSUB = 11` : Neutron-Induced Fission Product Yields + + Examples + -------- + Check how many lines of description and how many sections are recorded + in a file. + >>> tape = sandy.get_endf6_file("jeff_33", "xs", 10010) + >>> intro = tape.read_section(125, 1, 451) + >>> assert len(intro["DESCRIPTION"]) == 87 + >>> assert len(intro["SECTIONS"]) == 10 + + By removing sections in the `Endf6` instance, the recorded number of + sections does not change. + >>> tape2 = tape.delete_sections([(125, 33, 1), (125, 33, 2)]) + >>> intro = tape2.read_section(125, 1, 451) + >>> assert len(intro["DESCRIPTION"]) == 87 + >>> assert len(intro["SECTIONS"]) == 10 + + Running `updated intro` updates the recorded number of sections. + >>> tape2 = tape.delete_sections([(125, 33, 1), (125, 33, 2)]).update_intro() + >>> intro = tape2.read_section(125, 1, 451) + >>> assert len(intro["DESCRIPTION"]) == 87 + >>> assert len(intro["SECTIONS"]) == 8 + + It can also be used to update the lines of description. + >>> intro = tape2.update_intro(**dict(DESCRIPTION=[" new description"])).read_section(125, 1, 451) + >>> print(sandy.write_mf1(intro)) + 1001.00000 9.991673-1 0 0 2 5 125 1451 1 + 0.00000000 0.00000000 0 0 0 6 125 1451 2 + 1.00000000 20000000.0 3 0 10 3 125 1451 3 + 0.00000000 0.00000000 0 0 1 8 125 1451 4 + new description 125 1451 5 + 1 451 13 0 125 1451 6 + 2 151 4 0 125 1451 7 + 3 1 35 0 125 1451 8 + 3 2 35 0 125 1451 9 + 3 102 35 0 125 1451 10 + 4 2 196 0 125 1451 11 + 6 102 201 0 125 1451 12 + 33 102 21 0 125 1451 13 + """ + tape = self.data.copy() + for mat, g in self.to_series().groupby("MAT"): + intro = self.read_section(mat, 1, 451) + intro.update(**kwargs) + new_records = [(mf, mt, sec.count('\n') + 1, 0) for (mat, mf, mt), sec in g.items()] + NWD, NXC = len(intro["DESCRIPTION"]), g.shape[0] + new_records[0] = (1, 451, NWD+NXC+4, 0) + intro["SECTIONS"] = new_records + tape[(mat, 1, 451)] = sandy.write_mf1(intro) + return self.__class__(tape) + + def get_nsub(self): + """ + Determine ENDF-6 sub-library type by reading flag "NSUB" of first MAT + in file. Returns ------- `int` NSUB value + + Examples + -------- + assert sandy.get_endf6_file("jeff_33", "xs", 10010).get_nsub() == "neutron" + assert sandy.get_endf6_file("jeff_33", "xs", 10010).get_nsub().get_pendf(err=1).get_nsub() == "neutron" + assert sandy.get_endf6_file("jeff_33", "nfpy", 942410).get_nsub() == "nfpy" + assert sandy.get_endf6_file("jeff_33", "decay", 942410).get_nsub() == "decay" + assert sandy.get_endf6_file("jeff_33", "dxs", 26000).get_nsub() == "neutron" + assert sandy.get_endf6_file("proton", "dxs", 26000).get_nsub() == "proton" """ - return self.read_section(self.mat[0], 1, 451)["NSUB"] + nsub = self.read_section(self.mat[0], 1, 451)["NSUB"] + return nsubs(nsub) + + + def _handle_njoy_inputs(method): + """ + Decorator to handle keyword arguments for NJOY before running + the executable. + + Examples + -------- + Test that `minimal_processing` filters unwanted modules. + >>> g = sandy.get_endf6_file("jeff_33", "xs", 10010).get_gendf(err=1, minimal_processing=True, temperature=300, dryrun=True) + >>> assert "broadr" in g and "reconr" in g + >>> assert "thermr" not in g and "purr" not in g and "heatr" not in g and "unresr" not in g and "gaspr" not in g + + Test `minimal_processing=False`. + >>> g = sandy.get_endf6_file("jeff_33", "xs", 10010).get_gendf(err=1, temperature=300, dryrun=True) + >>> assert "broadr" in g and "reconr" in g + >>> assert "thermr" in g and "purr" in g and "heatr" in g and "gaspr" in g + + Check that for `temperature=0` the calculation stops after RECONR. + >>> g = sandy.get_endf6_file("jeff_33", "xs", 10010).get_gendf(err=1, dryrun=True) + >>> assert "reconr" in g + >>> assert "broadr" not in g and "thermr" not in g and "purr" not in g and "heatr" not in g and "unresr" not in g and "gaspr" not in g + """ + def inner( + self, + temperature=0, + err=0.001, + minimal_processing=False, + verbose=False, + **kwargs, + ): + """ + Parameters + ---------- + err : TYPE, optional + reconstruction tolerance for RECONR, BROADR and THERMR. + The default is 0.001. + minimal_processing: `bool`, optional + deactivate modules THERMR, GASPR, HEATR, PURR and UNRESR. + The default is False. + temperature : `float`, optional + temperature of the cross sections in K. If not given, stop + the processing after RECONR (before BROADR). The default is 0. + verbose : `bool`, optional + flag to print NJOY input file to screen before running the + executable. The default is False. + """ + kwds_njoy = kwargs.copy() + + # Handle 'minimal' processing options + if minimal_processing or float(temperature) == 0: + kwds_njoy["thermr"] = False + kwds_njoy["gaspr"] = False + kwds_njoy["heatr"] = False + kwds_njoy["purr"] = False + kwds_njoy["unresr"] = False + # deactivate modules if temperature is 0 + if temperature == 0: + kwds_njoy["broadr"] = False + msg = """Zero or no temperature was requested, NJOY processing will stop after RECONR. + If you want to process 0K cross sections use `temperature=0.1`. + """ + logging.info(msg) + + # handle err + reconr_kws = kwds_njoy.get("reconr_kws", {}) + broadr_kws = kwds_njoy.get("broadr_kws", {}) + thermr_kws = kwds_njoy.get("thermr_kws", {}) + reconr_kws["err"] = broadr_kws["err"] = thermr_kws["err"] = float(err) + kwds_njoy["reconr_kws"] = reconr_kws + kwds_njoy["broadr_kws"] = broadr_kws + kwds_njoy["thermr_kws"] = thermr_kws + + kwds_njoy.update(dict(temperatures=[temperature], verbose=verbose)) + + return method(self, **kwds_njoy) + return inner + + def _handle_groupr_inputs(method): + """ + Decorator to handle keyword arguments for NJOY before running + the executable. + + Examples + -------- + Test that `minimal_processing` filters unwanted modules. + >>> g = sandy.get_endf6_file("jeff_33", "xs", 10010).get_gendf(err=1, minimal_processing=True, temperature=300, dryrun=True) + >>> assert "broadr" in g and "reconr" in g + >>> assert "thermr" not in g and "purr" not in g and "heatr" not in g and "unresr" not in g and "gaspr" not in g + + Test `minimal_processing=False`. + >>> g = sandy.get_endf6_file("jeff_33", "xs", 10010).get_gendf(err=1, temperature=300, dryrun=True) + >>> assert "broadr" in g and "reconr" in g + >>> assert "thermr" in g and "purr" in g and "heatr" in g and "gaspr" in g + + Check that for `temperature=0` the calculation stops after RECONR. + >>> g = sandy.get_endf6_file("jeff_33", "xs", 10010).get_gendf(err=1, dryrun=True) + >>> assert "reconr" in g + >>> assert "broadr" not in g and "thermr" not in g and "purr" not in g and "heatr" not in g and "unresr" not in g and "gaspr" not in g + """ + def inner( + self, + groupr_kws={}, + **kwargs, + ): + """ + Parameters + ---------- + err : TYPE, optional + reconstruction tolerance for RECONR, BROADR and THERMR. + The default is 0.001. + minimal_processing: `bool`, optional + deactivate modules THERMR, GASPR, HEATR, PURR and UNRESR. + The default is False. + temperature : `float`, optional + temperature of the cross sections in K. If not given, stop + the processing after RECONR (before BROADR). The default is 0. + verbose : `bool`, optional + flag to print NJOY input file to screen before running the + executable. The default is False. + """ + fission = 18 in self.get_records().query("MF==3").MT.values + groupr_kws["nubar"] = fission + groupr_kws["chi"] = fission + groupr_kws["mubar"] = True + return method(self, groupr_kws=groupr_kws, **kwargs) + return inner def read_section(self, mat, mf, mt, raise_error=True): """ @@ -1407,20 +1550,6 @@ def _update_info(self, descr=None): tape.loc[mat,1,451].TEXT = text return Endf6(tape) - def parse(self): - mats = self.index.get_level_values("MAT").unique() - if len(mats) > 1: - raise NotImplementedError("file contains more than 1 MAT") - self.mat = self.endf = mats[0] - if hasattr(self, "tape"): - self.filename = os.path.basename(self.tape) - INFO = self.read_section(mats[0], 1 ,451) - del INFO["TEXT"], INFO["RECORDS"] - self.__dict__.update(**INFO) - self.SECTIONS = self.loc[INFO["MAT"]].reset_index()["MF"].unique() - self.EHRES = 0 - self.THNMAX = - self.EHRES if self.EHRES != 0 else 1.0E6 - def get_id(self, method="nndc"): """ Extract ID for a given MAT for a ENDF-6 file. @@ -1469,10 +1598,9 @@ def get_id(self, method="nndc"): ID = zam if method.lower() == "aleph" else za_new return ID + @_handle_njoy_inputs def get_ace(self, - temperature, - njoy=None, - verbose=False, + suffix=None, pendf=None, **kwargs, ): @@ -1481,219 +1609,205 @@ def get_ace(self, Parameters ---------- - temperature : `float` - temperature of the cross sections in K. - njoy : `str`, optional, default is `None` - NJOY executable, if `None` search in the system path. - verbose : TYPE, optional, default is `False` - flag to print NJOY input file to screen before running the - executable. - **kwargs : TYPE - keyword argument to pass to `sandy.njoy.process`. + dryrun : `bool`, optional + Do not run NJOY and return NJOY input. Default is False. + pendf : :func:`~sandy.Endf6`, optional + provide manually PENDF object and add it to the processing + sequence after RECONR and before BROADR. Default is None. + suffix : `str`, optional + suffix in the form `".[0-9][0-9]"` to assign to the ACE data. + If not given, generate automatic suffix according to ALEPH rules. + Default is None. + **kwargs : `dict` + keyword argument to pass to :func:`~sandy.njoy.process_neutron`. Returns ------- - outs : `dict` - output with `'ace'` and `'xsdir'` as keys pointing to the - filenames of the corresponding ACE and xsdir files generated in - the run. + `dict` of `str` + output with `'ace'` and `'xsdir'` as keys. Examples -------- - >>> sandy.get_endf6_file("jeff_33", "xs", 10010).get_ace(700) - {'ace': '1001.07c', 'xsdir': '1001.07c.xsd'} + Check that output is a ace file. + >>> e6 = sandy.get_endf6_file("jeff_33", "xs", 10010) + >>> ace = e6.get_ace(temperature=700, err=1, minimal_processing=True)["ace"] + >>> assert "1001.07c" in ace + >>> assert "sandy runs acer" in ace + >>> assert "mat 125" in ace + + Check that ace is processed at a different temperature. + >>> ace = e6.get_ace(temperature=800, err=1, minimal_processing=True)["ace"] + >>> assert "1001.08c" in ace + Check xsdir. + >>> print(outs[xsdir]) + 1001.08c 0.999167 filename 0 1 1 3297 0 0 6.894E-08 + + Check that using option `pendf` results in the same output. + >>> pendf = e6.get_pendf(temperature=0, err=1) + >>> ace2 = e6.get_ace(temperature=800, err=1, , minimal_processing=True, pendf=pendf)["ace"] + >>> assert ace == ace2 + + Check that the option suffix is used correctly. + >>> ace = e6.get_ace(temperature=800, suffix=".85", err=1) + >>> assert "1001.85c" in ace + + Check input pendf file + >>> with pytest.raises(Exception) as e_info: + >>> e6.get_ace(pendf=e6) """ - outs = {} - pendftape = None + if suffix: + kwargs["suffixes"] = [suffix] + with TemporaryDirectory() as td: endf6file = os.path.join(td, "endf6_file") self.to_file(endf6file) + # we don not call to_pendf because we might want to pass a pendf in input if pendf: + if pendf.kind != 'pendf': + raise TypeError(f"kw argument 'pendf' does not contain a PENDF file") pendftape = os.path.join(td, "pendf_file") pendf.to_file(pendftape) - text, inputs, outputs = sandy.njoy.process( + else: + pendftape = None + outputs = sandy.njoy.process_neutron( endf6file, pendftape=pendftape, - wdir=".", - keep_pendf=False, - exe=njoy, - temperatures=[temperature], - verbose=verbose, **kwargs, ) - acefile = outputs["tape50"] - basename = os.path.split(acefile)[1] - dest = os.path.join(os.getcwd(), basename) - outs["ace"] = basename - shutil.move(acefile, dest) - xsdfile = outputs["tape70"] - basename = os.path.split(xsdfile)[1] - dest = os.path.join(os.getcwd(), basename) - outs["xsdir"] = basename - shutil.move(xsdfile, dest) - return outs + if kwargs.get("dryrun", False): + return outputs # this contains the NJOY input + return {k: outputs[k] for k in ["ace", "xsdir"]} - def get_pendf(self, - temperature=0, - njoy=None, - to_file=False, - verbose=False, - **kwargs, - ): + @_handle_njoy_inputs + def get_pendf(self, **kwargs,): """ Process `Endf6` instance into an PENDF file using NJOY. Parameters ---------- - temperature : `float`, optional, default is `0`. - temperature of the cross sections in K. - If not given, stop the processing after RECONR (before BROADR). - njoy : `str`, optional, default is `None` - NJOY executable, if `None` search in the system path. - to_file : `str`, optional, default is `None` - if not `None` write processed ERRORR data to file. - The name of the PENDF file is the keyword argument. - verbose : `bool`, optional, default is `False` - flag to print NJOY input file to screen before running the - executable. **kwargs : `dict` - keyword argument to pass to `sandy.njoy.process`. + keyword argument to pass to :func:`~sandy.njoy.process_neutron`. Returns ------- - pendf : `sandy.Endf6` - `Endf6` instance constaining the nuclear data of the PENDF file. + pendf : :func:`~sandy.Endf6` + Pendf object Examples -------- - Process H1 file from ENDF/B-VII.1 into PENDF - >>> pendf =sandy.get_endf6_file("endfb_71", "xs", 10010).get_pendf(verbose=True) - moder - 20 -21 / - reconr - -21 -22 / - 'sandy runs njoy'/ - 125 0 0 / - 0.001 0. / - 0/ - moder - -22 30 / - stop - - >>> pendf - MAT MF MT - 125 1 451 1.001000+3 9.991673-1 2 0 ... - 2 151 1.001000+3 9.991673-1 0 0 ... - 3 1 1.001000+3 9.991673-1 0 99 ... - 2 1.001000+3 9.991673-1 0 0 ... - 102 1.001000+3 9.991673-1 0 0 ... - dtype: object - - >>> pendf.kind - 'pendf' - - Test `to_file` + Default run. >>> endf6 = sandy.get_endf6_file("jeff_33", "xs", 10010) - >>> out = endf6.get_pendf(to_file="out.pendf") - >>> assert os.path.isfile('out.pendf') - + >>> out = endf6.get_pendf(verbose=True, temperature=293.6, err=1, minimal_processing=True) + >>> assert isinstance(out, sandy.Endf6) """ - if float(temperature) == 0: - kwargs["broadr"] = False - kwargs["thermr"] = False - kwargs["gaspr"] = False - kwargs["heatr"] = False - kwargs["purr"] = False - kwargs["unresr"] = False - msg = """Zero or no temperature was requested, NJOY processing will stop after RECONR. -If you want to process 0K cross sections use `temperature=0.1`. -""" - logging.info(msg) + # always deactivate acer + kwargs["acer"] = False + with TemporaryDirectory() as td: endf6file = os.path.join(td, "endf6_file") self.to_file(endf6file) - text, inputs, outputs = sandy.njoy.process( + outputs = sandy.njoy.process_neutron( endf6file, - acer=False, - keep_pendf=True, - exe=njoy, - temperatures=[temperature], suffixes=[0], - verbose=verbose, **kwargs, ) - pendffile = outputs["tape30"] - pendf = Endf6.from_file(pendffile) - if to_file: - shutil.move(pendffile, to_file) + if kwargs.get("dryrun", False): + return outputs # this contains the NJOY input + pendf = Endf6.from_text(outputs["pendf"]) return pendf - def merge_pendf(self, pendf): + @_handle_njoy_inputs + @_handle_groupr_inputs + def get_gendf(self, **kwargs,): """ - Merge endf-6 file content with that of a pendf file. + Process `Endf6` instance into a Gendf file using NJOY. Parameters ---------- - pendf : `sandy.Endf6` - `Endf6` object containing pendf tape. + **kwargs : `dict` + keyword argument to pass to :func:`~sandy.njoy.process_neutron`. Returns ------- - `sandy.Endf6` - `Endf6` object with MF3 and MF1MT451 from PENDF - - Notes - ----- - .. note:: the `Endf6` object in output has attribute `kind='pendf'`. - - .. note:: the `Endf6` object in output contains all sections from the - original endf-6 file, but for all MF=3 and MF=1 MT=451. + gendf : :func:`~sandy.Gendf` + Gendf object Examples -------- - >>> file = os.path.join(sandy.data.__path__[0], "h1.endf") - >>> endf6 = sandy.Endf6.from_file(file) - >>> file = os.path.join(sandy.data.__path__[0], "h1.pendf") - >>> pendf = sandy.Endf6.from_file(file) - >>> merged = endf6.merge_pendf(pendf) - >>> merged - MAT MF MT - 125 1 451 1.001000+3 9.991673-1 2 0 ... - 2 151 1.001000+3 9.991673-1 0 0 ... - 3 1 1.001000+3 9.991673-1 0 99 ... - 2 1.001000+3 9.991673-1 0 0 ... - 102 1.001000+3 9.991673-1 0 0 ... - 4 2 1.001000+3 9.991673-1 0 1 ... - 6 102 1.001000+3 9.991673-1 0 2 ... - 33 1 1.001000+3 9.991673-1 0 0 ... - 2 1.001000+3 9.991673-1 0 0 ... - 102 1.001000+3 9.991673-1 0 0 ... - dtype: object + Default run. + >>> endf6 = sandy.get_endf6_file("jeff_33", "xs", 10010) + >>> out = endf6.get_gendf(temperature=293.6, minimal_processing=True) + >>> assert isinstance(out, sandy.Gendf) - The cross section in the merged file come from the pendf. - >>> assert merged.data[125, 3, 1] == pendf.data[125, 3, 1] - >>> assert merged.data[125, 3, 1] != endf6.data[125, 3, 1] + Test keyword `sigz` + >>> out = endf6.get_gendf(groupr_kws=dict(sigz=[1e10, 1e2])) + >>> assert 1e10 in sandy.gendf.read_mf1(out, 125)[sigz] + >>> assert 1e10 in sandy.gendf.read_mf1(out, 125)[sigz] + + Test keyword `iwt` + >>> g = endf6.get_gendf(groupr_kws=dict(iwt=3), dryrun=True) + >>> found = re.search('groupr(.*)moder', g, flags=re.DOTALL).group().splitlines() + assert "125 2 0 3 0 1 1 0 /" == found[2] + + Test keyword `ign` + >>> g = endf6.get_gendf(groupr_kws=dict(ign=3), dryrun=True) + >>> found = re.search('groupr(.*)moder', g, flags=re.DOTALL).group().splitlines() + assert "125 3 0 2 0 1 1 0 /" == found[2] + + Test keyword `ek` + >>> g = endf6.get_gendf(groupr_kws=dict(ek=sandy.energy_grids.CASMO12), dryrun=True) + >>> found = re.search('groupr(.*)moder', g, flags=re.DOTALL).group().splitlines() + >>> ek = np.array(list(map(float, found[7].replace("/", "").split()))) + >>> assert np.testing.array_allclose(ek, sandy.energy_grids.CASMO12, rtol=1e-14, atol=1e-14) + + Test groupr MFs and MTs for fissile and non-fissile nuclides + >>> g = endf6.get_gendf(dryrun=True) + >>> found = re.search('groupr(.*)moder', g, flags=re.DOTALL).group().splitlines() + assert " ".join(found[6:10]) == '3/ 3 251 / 0/ 0/' + U-238 test because it contains mubar, xs, chi and nubar + >>> endf6 = sandy.get_endf6_file('jeff_33','xs', 922380) + >>> g = endf6.get_gendf(dryrun=True) + >>> found = re.search('groupr(.*)moder', g, flags=re.DOTALL).group().splitlines() + >>> assert " ".join(found[6:15]) == '3/ 3 452 / 3 455 / 3 456 / 3 251 / 5/ 5 18 / 0/ 0/' - The new file is also a pendf. - >>> merged.kind - 'pendf' - """ - if pendf.kind != "pendf": - raise sandy.Error("given file is not a pendf") - section_3_endf6 = self.filter_by(listmf=[3]).data - section_3_pendf = pendf.filter_by(listmf=[3]).data - section_1451_pendf = pendf.filter_by(listmf=[1], listmt=[451]).data - return self.delete_sections(section_3_endf6) \ - .add_sections(section_3_pendf) \ - .add_sections(section_1451_pendf) + Test custom MTs + >>> endf6 = sandy.get_endf6_file("jeff_33", "xs", 10010) + >>> g = endf6.get_gendf(dryrun=True, groupr_kws=dict(mt=4)) + >>> found = re.search('groupr(.*)moder', g, flags=re.DOTALL).group().splitlines() + assert " ".join(found[6:10]) == '3 4 / 3 251 / 0/ 0/' + >>> g = endf6.get_gendf(dryrun=True, groupr_kws=dict(mt=4)) + >>> found = re.search('groupr(.*)moder', g, flags=re.DOTALL).group().splitlines() + assert " ".join(found[6:11]) == '3 4 / 3 102 / 3 251 / 0/ 0/' + """ + groupr_kws = kwargs.get("groupr_kws", {}) + fission = 18 in self.get_records().query("MF==3").MT.values + groupr_kws["nubar"] = fission + groupr_kws["chi"] = fission + groupr_kws["mubar"] = True + kwargs["groupr_kws"] = groupr_kws + + # always activate groupr + kwargs["groupr"] = True + + # always deactivate acer + kwargs["acer"] = False + + with TemporaryDirectory() as td: + endf6file = os.path.join(td, "endf6_file") + self.to_file(endf6file) + outputs = sandy.njoy.process_neutron( + endf6file, + suffixes=[0], + **kwargs, + ) + if kwargs.get("dryrun", False): + return outputs # this contains the NJOY input + gendf = sandy.Gendf.from_text(outputs["gendf"]) + return gendf + @_handle_njoy_inputs + @_handle_groupr_inputs def get_errorr(self, - temperature=0, - njoy=None, - to_file=None, - verbose=False, - groupr=False, - err=0.005, nubar=True, mubar=True, chi=True, @@ -1707,73 +1821,17 @@ def get_errorr(self, ---------- chi : `bool`, optional Process the chi covariance (default is `True`) - groupr : `bool`, optional, default is `False` - option to generate covariances from a multigroup cross section - ..note:: this option is activated by default if `nubar=True` or - `chi=True` mubar : `bool`, optional Process the mubar covariance (default is `True`) - njoy : `str`, optional, default is `None` - NJOY executable, if `None` search in the system path. nubar : `bool`, optional Process the nubar covariance (default is `True`) - temperature : `float`, optional, default is `0`. - temperature of the cross sections in K. - If not given, stop the processing after RECONR (before BROADR). - to_file : `str`, optional, default is `None` - if not `None` write processed ERRORR data to file. - The name of the ERRORR file is the keyword argument. - verbose : `bool`, optional, default is `False` - flag to print NJOY input file to screen before running the - executable. xs : `bool`, optional Process the xs covariance (default is `True`) **kwargs : `dict` keyword argument to pass to `sandy.njoy.process`. - Parameters for RECONR - --------------------- - err : `float`, optional - reconstruction tolerance (default is 0.005) - - Parameters for GROUPR - --------------------- - ign_groupr : `int`, optional - neutron group option (default is 2, csewg 239-group structure) - ek_groupr : iterable, optional - derived cross section energy bounds - (default is `[1e-5, 2e7]` if `ign_groupr==1`) - sigz : iterable of `float` - sigma zero values. The default is 1.0e10. - iwt_groupr : `int`, optional - weight function option (default is 2, constant) - spectrum_groupr : iterable, optional - Weight function as a iterable (default is None) - - Parameters for ERRORR - --------------------- - ign_errorr : `int`, optional - neutron group option (default is 2, csewg 239-group structure) - ek_errorr : iterable, optional - derived cross section energy bounds - (default is `[1e-5, 2e7]` if `ign_errorr==1`) - iwt_errorr : `int`, optional - weight function option (default is 2, constant) spectrum_errorr : iterable, optional weight function as a iterable (default is None) - irespr: `int`, optional - processing for resonance parameter covariances - (default is 1, 1% sensitivity method) - mt: `int` or iterable of `int`, optional - list of MT reactions to be processed - - .. note:: this list will be used for all covariance types, i.e., - MF31, MF33, MF34, MF35. - If this is not the expected behavior, use keyword - arguments `nubar`, `xs`, `mubar` and `chi`. - - .. note:: keyword `mt` is currently incompatible with keyword - `groupr`. Returns ------- @@ -1788,178 +1846,71 @@ def get_errorr(self, Examples -------- - Test verbose keyword + Default run + >>> endf6 = sandy.get_endf6_file("jeff_33", "xs", 942410) + >>> out = endf6.get_errorr(temperature=300, minimal_processing=True, err=1, errorr_kws=dict(ign=3, mt=18)) + Check `ign` and `ek` + This test check also the type of each output + >>> assert out["errorr33"].get_xs().data.shape[0] == 30 + >>> assert out["errorr31"].get_xs().data.shape[0] == 30 + >>> assert out["errorr34"].get_xs().data.shape[0] == 30 + >>> assert out["errorr33"].get_xs().data.shape[0] == 30 + Check `ign` and `ek` >>> endf6 = sandy.get_endf6_file("jeff_33", "xs", 10010) - >>> out = endf6.get_errorr(ek_errorr=sandy.energy_grids.CASMO12, verbose=True) - moder - 20 -21 / - reconr - -21 -22 / - 'sandy runs njoy'/ - 125 0 0 / - 0.005 0. / - 0/ - errorr - -21 -22 0 33 0 / - 125 1 2 0 1 / - 0 0.0 / - 0 33 1/ - 12 / - 1.00000e-05 3.00000e-02 5.80000e-02 1.40000e-01 2.80000e-01 3.50000e-01 6.25000e-01 4.00000e+00 4.80520e+01 5.53000e+03 8.21000e+05 2.23100e+06 1.00000e+07 / - stop - - Test output type - >>> assert isinstance(out, sandy.Errorr) - - Test `ign` and `ek` - >>> assert out.get_xs().data[(125, 1)].size == 12 - - Test `to_file` - >>> out = endf6.get_errorr(to_file="out.err") - >>> assert os.path.isfile('out.err') - - Test groupr and errorr: - >>> out = endf6.get_errorr(verbose=True, groupr=True) - moder - 20 -21 / - reconr - -21 -22 / - 'sandy runs njoy'/ - 125 0 0 / - 0.005 0. / - 0/ - groupr - -21 -22 0 -23 / - 125 2 0 2 0 1 1 0 / - 'sandy runs groupr' / - 0.0/ - 10000000000.0/ - 3/ - 0/ - 0/ - errorr - -21 0 -23 33 0 / - 125 2 2 0 1 / - 0 0.0 / - 0 33 1/ - stop - - Test groupr and errorr for neutron energy grids: - >>> out = endf6.get_errorr(ek_errorr=sandy.energy_grids.CASMO12, ek_groupr=sandy.energy_grids.CASMO12, verbose=True, groupr=True) - moder - 20 -21 / - reconr - -21 -22 / - 'sandy runs njoy'/ - 125 0 0 / - 0.005 0. / - 0/ - groupr - -21 -22 0 -23 / - 125 1 0 2 0 1 1 0 / - 'sandy runs groupr' / - 0.0/ - 10000000000.0/ - 12 / - 1.00000e-05 3.00000e-02 5.80000e-02 1.40000e-01 2.80000e-01 3.50000e-01 6.25000e-01 4.00000e+00 4.80520e+01 5.53000e+03 8.21000e+05 2.23100e+06 1.00000e+07 / - 3/ - 0/ - 0/ - errorr - -21 0 -23 33 0 / - 125 1 2 0 1 / - 0 0.0 / - 0 33 1/ - 12 / - 1.00000e-05 3.00000e-02 5.80000e-02 1.40000e-01 2.80000e-01 3.50000e-01 6.25000e-01 4.00000e+00 4.80520e+01 5.53000e+03 8.21000e+05 2.23100e+06 1.00000e+07 / - stop - - Test groupr and errorr for neutron and photons energy grids: - >>> out = endf6.get_errorr(ek_groupr=sandy.energy_grids.CASMO12, ek_errorr=sandy.energy_grids.CASMO12, ep=sandy.energy_grids.CASMO12, verbose=True, groupr=True) - moder - 20 -21 / - reconr - -21 -22 / - 'sandy runs njoy'/ - 125 0 0 / - 0.005 0. / - 0/ - groupr - -21 -22 0 -23 / - 125 1 1 2 0 1 1 0 / - 'sandy runs groupr' / - 0.0/ - 10000000000.0/ - 12 / - 1.00000e-05 3.00000e-02 5.80000e-02 1.40000e-01 2.80000e-01 3.50000e-01 6.25000e-01 4.00000e+00 4.80520e+01 5.53000e+03 8.21000e+05 2.23100e+06 1.00000e+07 / - 12 / - 1.00000e-05 3.00000e-02 5.80000e-02 1.40000e-01 2.80000e-01 3.50000e-01 6.25000e-01 4.00000e+00 4.80520e+01 5.53000e+03 8.21000e+05 2.23100e+06 1.00000e+07 / - 3/ - 0/ - 0/ - errorr - -21 0 -23 33 0 / - 125 1 2 0 1 / - 0 0.0 / - 0 33 1/ - 12 / - 1.00000e-05 3.00000e-02 5.80000e-02 1.40000e-01 2.80000e-01 3.50000e-01 6.25000e-01 4.00000e+00 4.80520e+01 5.53000e+03 8.21000e+05 2.23100e+06 1.00000e+07 / - stop - - U-238 test because it contains mubar, xs, chi and nubar: - >>> endf6 = sandy.get_endf6_file('jeff_33','xs', 922380) - >>> out = endf6.get_errorr(ek_errorr=sandy.energy_grids.CASMO12, ek_groupr=sandy.energy_grids.CASMO12, verbose=True, err=1) - moder - 20 -21 / - reconr - -21 -22 / - 'sandy runs njoy'/ - 9237 0 0 / - 1 0. / - 0/ - groupr - -21 -22 0 -23 / - 9237 1 0 2 0 1 1 0 / - 'sandy runs groupr' / - 0.0/ - 10000000000.0/ - 12 / - 1.00000e-05 3.00000e-02 5.80000e-02 1.40000e-01 2.80000e-01 3.50000e-01 6.25000e-01 4.00000e+00 4.80520e+01 5.53000e+03 8.21000e+05 2.23100e+06 1.00000e+07 / - 3/ - 3 251 'mubar' / - 5/ - 5 18 'chi' / - 0/ - 0/ - errorr - -21 0 -23 31 0 / - 9237 1 2 0 1 / - 0 0.0 / - 0 31 1/ - 12 / - 1.00000e-05 3.00000e-02 5.80000e-02 1.40000e-01 2.80000e-01 3.50000e-01 6.25000e-01 4.00000e+00 4.80520e+01 5.53000e+03 8.21000e+05 2.23100e+06 1.00000e+07 / - errorr - -21 0 -23 33 0 / - 9237 1 2 0 1 / - 0 0.0 / - 0 33 1/ - 12 / - 1.00000e-05 3.00000e-02 5.80000e-02 1.40000e-01 2.80000e-01 3.50000e-01 6.25000e-01 4.00000e+00 4.80520e+01 5.53000e+03 8.21000e+05 2.23100e+06 1.00000e+07 / - errorr - -21 0 -23 35 0 / - 9237 1 2 0 1 / - 0 0.0 / - 0 35 1/ - 12 / - 1.00000e-05 3.00000e-02 5.80000e-02 1.40000e-01 2.80000e-01 3.50000e-01 6.25000e-01 4.00000e+00 4.80520e+01 5.53000e+03 8.21000e+05 2.23100e+06 1.00000e+07 / - errorr - -21 0 -23 34 0 / - 9237 1 2 0 1 / - 0 0.0 / - 0 34 1/ - 12 / - 1.00000e-05 3.00000e-02 5.80000e-02 1.40000e-01 2.80000e-01 3.50000e-01 6.25000e-01 4.00000e+00 4.80520e+01 5.53000e+03 8.21000e+05 2.23100e+06 1.00000e+07 / - stop + >>> out = endf6.get_errorr(errorr_kws=dict(ek=sandy.energy_grids.CASMO12)) + Check `mt` + assert out["errorr33"].get_xs().data.squeeze().name == (9443, 2) + assert out["errorr34"].get_xs().data.squeeze().name == (9443, 251) + columns = out["errorr31"].get_xs().data.columns + assert (9443, 452) in columns and (9443, 455) in columns and (9443, 456) in columns + + Check consistency between keywords errorr_kws and errorr33_kws + >>> ekws = dict(irespr=0, iwt=5, ek=[1e-5, 2e7], mt=(16, 18, 102)) + >>> e6 = sandy.get_endf6_file("jeff_33", "xs", 942410) + >>> inp1 = e6.get_errorr(temperature=300, dryrun=True, xs=True, chi=False, nubar=False, mubar=False, errorr_kws=ekws) + >>> inp2 = e6.get_errorr(temperature=300, dryrun=True, xs=True, chi=False, nubar=False, mubar=False, errorr33_kws=ekws) + >>> inp3 = e6.get_errorr(temperature=300, dryrun=True, xs=True, chi=False, nubar=False, mubar=False) + >>> assert "groupr" not in inp1 and "groupr" not in inp2 and "groupr" not in inp3 + >>> assert inp1 == inp2 and inp1 != inp3 + Check consistency between keywords errorr_kws and errorr35_kws + >>> inp1 = e6.get_errorr(temperature=300, dryrun=True, xs=False, chi=True, nubar=False, mubar=False, errorr_kws=ekws) + >>> inp2 = e6.get_errorr(temperature=300, dryrun=True, xs=False, chi=True, nubar=False, mubar=False, errorr35_kws=ekws) + >>> inp3 = e6.get_errorr(temperature=300, dryrun=True, xs=False, chi=True, nubar=False, mubar=False) + >>> assert "groupr" in inp1 and "groupr" in inp2 and "groupr" in inp3 + >>> assert inp1 == inp2 and inp1 != inp3 + Check consistency between keywords errorr_kws and errorr31_kws + >>> inp1 = e6.get_errorr(temperature=300, dryrun=True, xs=False, chi=False, nubar=True, mubar=False, errorr_kws=ekws) + >>> inp2 = e6.get_errorr(temperature=300, dryrun=True, xs=False, chi=False, nubar=True, mubar=False, errorr31_kws=ekws) + >>> inp3 = e6.get_errorr(temperature=300, dryrun=True, xs=False, chi=False, nubar=True, mubar=False) + >>> assert inp1 == inp2 and inp1 != inp3 + >>> assert "groupr" in inp1 and "groupr" in inp2 and "groupr" in inp3 + Check consistency between keywords errorr_kws and errorr34_kws + >>> inp1 = e6.get_errorr(temperature=300, dryrun=True, xs=False, chi=False, nubar=False, mubar=True, errorr_kws=ekws) + >>> inp2 = e6.get_errorr(temperature=300, dryrun=True, xs=False, chi=False, nubar=False, mubar=True, errorr34_kws=ekws) + >>> inp3 = e6.get_errorr(temperature=300, dryrun=True, xs=False, chi=False, nubar=False, mubar=True) + >>> assert inp1 == inp2 and inp1 != inp3 + >>> assert "groupr" in inp1 and "groupr" in inp2 and "groupr" in inp3 + >>> inp1 = e6.get_errorr(temperature=300, dryrun=True, errorr_kws=ekws) + >>> inp2 = e6.get_errorr(temperature=300, dryrun=True, errorr33_kws=ekws, errorr31_kws=ekws, errorr34_kws=ekws, errorr35_kws=ekws) + >>> assert inp1 == inp2 + >>> assert "groupr" in inp1 and "groupr" in inp2 + + Check default options + >>> g = sandy.get_endf6_file("jeff_33", "xs", 10010).get_errorr(temperature=300, dryrun=True) + >>> found = re.search('errorr(.*)', g, flags=re.DOTALL).group().splitlines() + Check ign(2), iwt (2), iprint (0) and relative (1) options + >>> assert found[2] == '125 2 2 0 1 /' + Check temperature (300) option + >>> assert found[3] == '0 300.0 /' + Check irespr (1) option + >>> assert found[4] = '0 33 1/' + Check options changes + >>> ekws = dict(ign=3, iwt=5, iprint=True, relative=False) + >>> g = sandy.get_endf6_file("jeff_33", "xs", 10010).get_errorr(temperature=400, dryrun=True) + >>> found = re.search('errorr(.*)', g, flags=re.DOTALL).group().splitlines() + >>> assert found[2] == '125 3 5 1 0 /' + >>> assert found[3] == '0 400.0 /' + >>> assert found[4] = '0 33 0/' Test spectrum: >>> spect = [1.000000e-5, 2.00000000, 3.000000e-2, 2.00000000, 5.800000e-2, 4.00000000, 3, 1] @@ -1985,551 +1936,417 @@ def get_errorr(self, 3.00000000 1.00000000 / stop - - - >>> spect_g = [1.000000e-5, 1.00000000, 3.000000e-2, 2.00000000, 5.800000e-2, 3.00000000, 3, 2] - >>> out = endf6.get_errorr(spectrum_errorr=spect, spectrum_groupr=spect_g, ek_errorr=[1.000000e-5, 3.000000e-2, 5.800000e-2, 3], ek_groupr=[1.000000e-5, 3.000000e-2, 5.800000e-2, 3], verbose=True, nubar=False, chi=False, mubar=False, groupr=True) - moder - 20 -21 / - reconr - -21 -22 / - 'sandy runs njoy'/ - 9237 0 0 / - 0.005 0. / - 0/ - groupr - -21 -22 0 -23 / - 9237 1 0 1 0 1 1 0 / - 'sandy runs groupr' / - 0.0/ - 10000000000.0/ - 3 / - 1.00000e-05 3.00000e-02 5.80000e-02 3.00000e+00 / - 0.00000000 0.00000000 0 0 1 4 - 4 1 - 1.000000-5 1.00000000 3.000000-2 2.00000000 5.800000-2 3.00000000 - 3.00000000 2.00000000 - / - 3/ - 0/ - 0/ - errorr - -21 0 -23 33 0 / - 9237 1 1 0 1 / - 0 0.0 / - 0 33 1/ - 3 / - 1.00000e-05 3.00000e-02 5.80000e-02 3.00000e+00 / - 0.00000000 0.00000000 0 0 1 4 - 4 1 - 1.000000-5 2.00000000 3.000000-2 2.00000000 5.800000-2 4.00000000 - 3.00000000 1.00000000 - / - stop - - Test irespr: - out = endf6.get_errorr(spectrum_errorr=spect, ek_errorr=[1.000000e-5, 3.000000e-2, 5.800000e-2, 3], verbose=True, nubar=False, chi=False, mubar=False, irespr=0) - moder - 20 -21 / - reconr - -21 -22 / - 'sandy runs njoy'/ - 125 0 0 / - 0.005 0. / - 0/ - errorr - -21 -22 0 33 0 / - 125 1 1 0 1 / - 0 0.0 / - 0 33 0/ - 3 / - 1.00000e-05 3.00000e-02 5.80000e-02 3.00000e+00 / - 1.000000-5 2.00000000 3.000000-2 2.00000000 5.800000-2 4.00000000 3.00000000 1.00000000 - / - stop - - Test for MT: - >>> endf6 = sandy.get_endf6_file("jeff_33", "xs", 10010) - >>> out = endf6.get_errorr(verbose=True, mt=[1, 2], ek_errorr=sandy.energy_grids.CASMO12) - moder - 20 -21 / - reconr - -21 -22 / - 'sandy runs njoy'/ - 125 0 0 / - 0.005 0. / - 0/ - errorr - -21 -22 0 33 0 / - 125 1 2 0 1 / - 0 0.0 / - 1 33 1/ - 2 0 / - 1 2 / - 12 / - 1.00000e-05 3.00000e-02 5.80000e-02 1.40000e-01 2.80000e-01 3.50000e-01 6.25000e-01 4.00000e+00 4.80520e+01 5.53000e+03 8.21000e+05 2.23100e+06 1.00000e+07 / - stop - - Keywords `mt` and `groupr` are incompatible - >>> with pytest.raises(sandy.SandyError): - ... sandy.get_endf6_file("jeff_33", "xs", 10010).get_errorr(err=1, mt=1, groupr=True) - - Test content of output `Errorr` file - >>> out = sandy.get_endf6_file('jeff_33', "xs", 922350).get_errorr(err=1., irespr=0, mubar=False, chi=False) - >>> keys = [(9228, 1, 451), (9228, 3, 456), (9228, 33, 456), (9228, 3, 1), (9228, 3, 2), (9228, 3, 4), (9228, 3, 16), (9228, 3, 17), (9228, 3, 18), (9228, 3, 37), (9228, 3, 102), (9228, 33, 1), (9228, 33, 2), (9228, 33, 4), (9228, 33, 16), (9228, 33, 17), (9228, 33, 18), (9228, 33, 37), (9228, 33, 102)] - >>> for key in keys: assert key in out.data """ - kwds_njoy = kwargs.copy() - if float(temperature) == 0: - kwds_njoy["broadr"] = False - kwds_njoy["thermr"] = False - kwds_njoy["gaspr"] = False - kwds_njoy["heatr"] = False - kwds_njoy["purr"] = False - kwds_njoy["unresr"] = False - kwds_njoy['keep_pendf'] = False - else: - kwds_njoy["broadr"] = True - kwds_njoy["thermr"] = kwds_njoy.get("thermr", False) - kwds_njoy["gaspr"] = kwds_njoy.get("gaspr", False) - kwds_njoy["heatr"] = kwds_njoy.get("heatr", False) - kwds_njoy["purr"] = kwds_njoy.get("purr", False) - kwds_njoy["unresr"] = kwds_njoy.get("unresr", False) - kwds_njoy['keep_pendf'] = kwds_njoy.get('keep_pendf', False) - - cov_info = self.covariance_info(nubar=nubar, xs=xs, - mubar=mubar, chi=chi) - if not np.any(list(cov_info.values())): - return # no covariance found or wanted - kwds_njoy.update(cov_info) - - # Mandatory groupr module activation - groupr_ = True if (kwds_njoy["nubar"] or kwds_njoy["chi"] or "ek_groupr" in kwds_njoy or "spectrum_groupr" in kwds_njoy) else groupr + # Activate specific errorr module according to covariance info and input options + mf31 = self.get_records().query("MF==31") + errorr31 = False if mf31.empty else nubar + mf33 = self.get_records().query("MF==33") + errorr33 = False if mf33.empty else xs + mf34 = self.get_records().query("MF==34") + errorr34 = False if mf34.empty else mubar + mf35 = self.get_records().query("MF==35") + errorr35 = False if mf35.empty else chi + kwargs.update(dict( + errorr31=errorr31, + errorr33=errorr33, + errorr34=errorr34, + errorr35=errorr35, + )) + + # Always deactivate acer + kwargs["acer"] = False + + # keyword arguments in error_kws, if any, overwrite the others + errorr_kws = kwargs.get("errorr_kws", {}) + errorr31_kws = kwargs.get("errorr31_kws", {}) + errorr31_kws.update(**errorr_kws) + errorr33_kws = kwargs.get("errorr33_kws", {}) + errorr33_kws.update(**errorr_kws) + errorr34_kws = kwargs.get("errorr34_kws", {}) + errorr34_kws.update(**errorr_kws) + errorr35_kws = kwargs.get("errorr35_kws", {}) + errorr35_kws.update(**errorr_kws) + kwargs.update(dict( + errorr31_kws=errorr31_kws, + errorr33_kws=errorr33_kws, + errorr34_kws=errorr34_kws, + errorr35_kws=errorr35_kws, + )) with TemporaryDirectory() as td: endf6file = os.path.join(td, "endf6_file") self.to_file(endf6file) - outputs = sandy.njoy.process( + # update kwargs, or else error because multiple keyword argument + outputs = sandy.njoy.process_neutron( endf6file, - errorr=True, - acer=False, - verbose=verbose, - temperatures=[temperature], suffixes=[0], - err=err, - groupr=groupr_, - **kwds_njoy, - )[2] - seq = map(sandy.Errorr.from_file, outputs.values()) - errorr = reduce(lambda x, y: x.merge(y), seq) - if to_file: - errorr.to_file(to_file) - return errorr - - def get_gendf(self, - temperature=293.6, - njoy=None, - to_file=None, - verbose=False, - err=0.005, - nubar=False, - xs=True, - mubar=False, - chi=False, - **kwargs): - """ - Process `Endf6` instance into a Gendf file using NJOY. - - Parameters - ---------- - temperature : `float`, optional, default is `293.6`. - temperature of the cross sections in K. - If not given, stop the processing after RECONR (before BROADR). - njoy : `str`, optional, default is `None` - NJOY executable, if `None` search in the system path. - to_file : `str`, optional, default is `None` - if not `None` write processed GENDF data to file. - The name of the GENDF file is the keyword argument. - verbose : `bool`, optional, default is `False` - flag to print NJOY input file to screen before running the - executable. - broadr : `bool`, optional, default is `True` - option to generate gendf file with Doppler-broadened cross sections - **kwargs : `dict` - keyword argument to pass to `sandy.njoy.process`. - - Parameters for RECONR and BROADR - -------------------------------- - err : `float`, optional - reconstruction tolerance (default is 0.005) + **kwargs, + ) + if kwargs.get("dryrun", False): + return outputs # this contains the NJOY input + outputs = {k: sandy.Errorr.from_text(v) for k, v in outputs.items() if "errorr" in k} + return outputs - Parameters for GROUPR - --------------------- - chi : `bool`, optional - Proccess the chi covariance(default is `False`) - ign : `int`, optional - neutron group option (default is 2, csewg 239-group structure) - iwt_groupr : `int`, optional - weight function option (default is 2, constant) - mubar : `bool`, optional - Proccess multigroup mubar (default is `False`) - mt: `int` or iterable of `int`, optional - run groupr only for the selected MT numbers - nubar : `bool`, optional - Proccess multigroup nubar (default is `False`) - nuclide_production : `bool`, optional - process multigroup activation yields (default is `False`) - spectrum_groupr : iterable, optional - Weight function as a iterable (default is None) - sigz : iterable of `float` - sigma zero values. The default is 1.0e10. - xs : `bool`, optional - Proccess multigroup xs (default is `True`) + def get_records(self): + """ + Extract MAT, MF and MT combinations avaialbel in the file and + report it in tabulated format. Returns ------- - gendf : `sandy.Gendf` - Gendf object + df : `pd.DataFrame` + Dataframe with MAT, MF and MT as columns. Examples -------- - Default test - >>> endf6 = sandy.get_endf6_file("jeff_33", "xs", 10010) - >>> out = endf6.get_gendf(verbose=True) - moder - 20 -21 / - reconr - -21 -22 / - 'sandy runs njoy'/ - 125 0 0 / - 0.005 0. / - 0/ - broadr - -21 -22 -23 / - 125 1 0 0 0. / - 0.005 / - 293.6 / - 0 / - groupr - -21 -23 0 -24 / - 125 2 0 2 0 1 1 0 / - 'sandy runs groupr' / - 293.6/ - 10000000000.0/ - 3/ - 0/ - 0/ - moder - -24 32 / - stop - - Test keyword `sigz` - >>> out = endf6.get_gendf(verbose=True, sigz=[1.0e10, 1e2]) - moder - 20 -21 / - reconr - -21 -22 / - 'sandy runs njoy'/ - 125 0 0 / - 0.005 0. / - 0/ - broadr - -21 -22 -23 / - 125 1 0 0 0. / - 0.005 / - 293.6 / - 0 / - groupr - -21 -23 0 -24 / - 125 2 0 2 0 1 2 0 / - 'sandy runs groupr' / - 293.6/ - 10000000000.0 100.0/ - 3/ - 0/ - 0/ - moder - -24 32 / - stop - - Test keyword `iwt_groupr` - >>> out = endf6.get_gendf(verbose=True, iwt_groupr=3) - moder - 20 -21 / - reconr - -21 -22 / - 'sandy runs njoy'/ - 125 0 0 / - 0.005 0. / - 0/ - broadr - -21 -22 -23 / - 125 1 0 0 0. / - 0.005 / - 293.6 / - 0 / - groupr - -21 -23 0 -24 / - 125 2 0 3 0 1 1 0 / - 'sandy runs groupr' / - 293.6/ - 10000000000.0/ - 3/ - 0/ - 0/ - moder - -24 32 / - stop + Short test for hydrogen + >>> sandy.get_endf6_file("jeff_33", "xs", 10010).get_records() + MAT MF MT + 0 125 1 451 + 1 125 2 151 + 2 125 3 1 + 3 125 3 2 + 4 125 3 102 + 5 125 4 2 + 6 125 6 102 + 7 125 33 1 + 8 125 33 2 + 9 125 33 102 + """ + df = self.to_series().rename("TEXT").reset_index().drop("TEXT", axis=1) + return df - Test keyword `ign_groupr` - >>> out = endf6.get_gendf(verbose=True, ign_groupr=3) - moder - 20 -21 / - reconr - -21 -22 / - 'sandy runs njoy'/ - 125 0 0 / - 0.005 0. / - 0/ - broadr - -21 -22 -23 / - 125 1 0 0 0. / - 0.005 / - 293.6 / - 0 / - groupr - -21 -23 0 -24 / - 125 3 0 2 0 1 1 0 / - 'sandy runs groupr' / - 293.6/ - 10000000000.0/ - 3/ - 0/ - 0/ - moder - -24 32 / - stop + def get_perturbations( + self, + nsmp, + to_excel=None, + njoy_kws={}, + smp_kws={}, + **kwargs, + ): + """ + Construct multivariate distributions with a unit vector for + mean and with relative covariances taken from the evaluated files + processed with the NJOY module ERRORR. - Test keyword `to_file` - >>> endf6 = sandy.get_endf6_file("jeff_33", "xs", 10010) - >>> out = endf6.get_gendf(to_file="out.gendf") - >>> assert os.path.isfile('out.gendf') + Perturbation factors are sampled with the same multigroup structure of + the covariance matrix and are returned by nuclear datatype as a `dict` + of `pd.Dataframe` instances . - Test keyword `ek_groupr` - >>> out = endf6.get_gendf(verbose=True, ek_groupr=sandy.energy_grids.CASMO12) - moder - 20 -21 / - reconr - -21 -22 / - 'sandy runs njoy'/ - 125 0 0 / - 0.005 0. / - 0/ - broadr - -21 -22 -23 / - 125 1 0 0 0. / - 0.005 / - 293.6 / - 0 / - groupr - -21 -23 0 -24 / - 125 1 0 2 0 1 1 0 / - 'sandy runs groupr' / - 293.6/ - 10000000000.0/ - 12 / - 1.00000e-05 3.00000e-02 5.80000e-02 1.40000e-01 2.80000e-01 3.50000e-01 6.25000e-01 4.00000e+00 4.80520e+01 5.53000e+03 8.21000e+05 2.23100e+06 1.00000e+07 / - 3/ - 0/ - 0/ - moder - -24 32 / - stop + Parameters + ---------- + nsmp : TYPE + DESCRIPTION. + to_excel : TYPE, optional + DESCRIPTION. The default is None. + njoy_kws : TYPE, optional + DESCRIPTION. The default is {}. + smp_kws : TYPE, optional + DESCRIPTION. The default is {}. + **kwargs : TYPE + DESCRIPTION. - U-238 test because it contains mubar, xs, chi and nubar: - >>> endf6 = sandy.get_endf6_file('jeff_33','xs', 922380) - >>> out = endf6.get_gendf(ek_groupr=sandy.energy_grids.CASMO12, verbose=True, err=1, nubar=True, mubar=True, chi=True) - moder - 20 -21 / - reconr - -21 -22 / - 'sandy runs njoy'/ - 9237 0 0 / - 1 0. / - 0/ - broadr - -21 -22 -23 / - 9237 1 0 0 0. / - 1 / - 293.6 / - 0 / - groupr - -21 -23 0 -24 / - 9237 1 0 2 0 1 1 0 / - 'sandy runs groupr' / - 293.6/ - 10000000000.0/ - 12 / - 1.00000e-05 3.00000e-02 5.80000e-02 1.40000e-01 2.80000e-01 3.50000e-01 6.25000e-01 4.00000e+00 4.80520e+01 5.53000e+03 8.21000e+05 2.23100e+06 1.00000e+07 / - 3/ - 3 251 'mubar' / - 5/ - 5 18 'chi' / - 0/ - 0/ - moder - -24 32 / - stop + Returns + ------- + smp : TYPE + DESCRIPTION. - Test keyword `spectrum_groupr` - >>> endf6 = sandy.get_endf6_file('jeff_33','xs', 10010) - >>> spect = [1.000000e-5, 2.00000000, 3.000000e-2, 2.00000000, 5.800000e-2, 4.00000000, 3, 1] - >>> out = endf6.get_gendf(spectrum_groupr=spect, ek_groupr=[1.000000e-5, 3.000000e-2, 5.800000e-2, 3], verbose=True, nubar=False, chi=False, mubar=False) - moder - 20 -21 / - reconr - -21 -22 / - 'sandy runs njoy'/ - 125 0 0 / - 0.005 0. / - 0/ - broadr - -21 -22 -23 / - 125 1 0 0 0. / - 0.005 / - 293.6 / - 0 / - groupr - -21 -23 0 -24 / - 125 1 0 1 0 1 1 0 / - 'sandy runs groupr' / - 293.6/ - 10000000000.0/ - 3 / - 1.00000e-05 3.00000e-02 5.80000e-02 3.00000e+00 / - 0.00000000 0.00000000 0 0 1 4 - 4 1 - 1.000000-5 2.00000000 3.000000-2 2.00000000 5.800000-2 4.00000000 - 3.00000000 1.00000000 - / - 3/ - 0/ - 0/ - moder - -24 32 / - stop + Examples + -------- + Generate a couple of samples from the H1 file of JEFF-3.3. + >>> njoy_kws = dict(err=1, errorr_kws=dict(mt=102)) + >>> tape = sandy.get_endf6_file("jeff_33", "xs", 10010) + >>> smps = tape.get_perturbations(nsmp=2, njoy_kws=njoy_kws) + >>> assert len(smps) == 1 + >>> assert isinstance(smps[33], sandy.Samples) + >>> assert (smps[33].data.index.get_level_values("MT") == 102).all() """ - kwds_njoy = kwargs.copy() - if float(temperature) == 0: - kwds_njoy["broadr"] = False - kwds_njoy["thermr"] = False - kwds_njoy["gaspr"] = False - kwds_njoy["heatr"] = False - kwds_njoy["purr"] = False - kwds_njoy["unresr"] = False - kwds_njoy['keep_pendf'] = False - else: - kwds_njoy["broadr"] = True - kwds_njoy["thermr"] = kwds_njoy.get("thermr", False) - kwds_njoy["gaspr"] = kwds_njoy.get("gaspr", False) - kwds_njoy["heatr"] = kwds_njoy.get("heatr", False) - kwds_njoy["purr"] = kwds_njoy.get("purr", False) - kwds_njoy["unresr"] = kwds_njoy.get("unresr", False) - kwds_njoy['keep_pendf'] = kwds_njoy.get('keep_pendf', False) - - kwds_njoy["acer"] = False - kwds_njoy["keep_pendf"] = False - - kwds_njoy["nubar"] = nubar - kwds_njoy["xs"] = xs - kwds_njoy["chi"] = chi - kwds_njoy["mubar"] = mubar + smp = {} + + outs = self.get_errorr(**njoy_kws) + + if "errorr31" in outs: + smp_kws["seed"] = smp_kws.get("seed31", None) + smp[31] = outs["errorr31"].get_cov().sampling(nsmp, **smp_kws) + if "errorr33" in outs: + smp_kws["seed"] = smp_kws.get("seed33", None) + smp[33] = outs["errorr33"].get_cov().sampling(nsmp, **smp_kws) + if to_excel and smp: + with pd.ExcelWriter(to_excel) as writer: + for k, v in smp.items(): + v.to_excel(writer, sheet_name=f'MF{k}') + return smp + + def _handle_pert_to_file(method): + """ + Decorator . + """ + def inner(self, *args, to_file=None, verbose=False, **kwargs): + """ + - with TemporaryDirectory() as td: - endf6file = os.path.join(td, "endf6_file") - self.to_file(endf6file) - outputs = sandy.njoy.process( - endf6file, - groupr=True, - verbose=verbose, - temperatures=[temperature], - suffixes=[0], - err=err, - **kwds_njoy, - )[2] # keep only gendf filename - gendf_file = outputs["tape32"] - groupr = sandy.Groupr.from_file(gendf_file) + Parameters + ---------- + *args : TYPE + DESCRIPTION. + to_file : TYPE, optional + DESCRIPTION. The default is None. + verbose : TYPE, optional + DESCRIPTION. The default is False. + **kwargs : TYPE + DESCRIPTION. + + Returns + ------- + TYPE + DESCRIPTION. + """ if to_file: - groupr.to_file(to_file) - return groupr + for n, e6 in method(self, *args, verbose=False, **kwargs): + filename = to_file.format(SMP=n) + if verbose: + print(f"creating file '{filename}'...") + e6.to_file(filename) + else: + return method(self, *args, verbose=verbose, **kwargs) - def covariance_info(self, nubar=True, xs=True, mubar=True, chi=True): + return inner + + @_handle_pert_to_file + def apply_perturbations(self, smp, processes=1, **kwargs): """ - Check the covariance information in the formatted file. + Parameters ---------- - nubar : `bool`, optional - default parameter for MF31 (default is `True`) - it will overwrite what found in the file - xs : `bool`, optional - default parameter for MF33 (default is `True`) - it will overwrite what found in the file - mubar : `bool`, optional - default parameter for MF34 (default is `True`) - it will overwrite what found in the file - chi : `bool`, optional - default parameter for MF35 (default is `True`) - it will overwrite what found in the file + smp : TYPE + DESCRIPTION. + processes : TYPE, optional + DESCRIPTION. The default is 1. + **kwargs : TYPE + DESCRIPTION. Returns ------- - cov_info : `dict` - dictionary reporting if covariances were found. - - Notes - ----- - .. note:: this method only works with MF31, MF33, MF34 and MF35 + TYPE + DESCRIPTION. Examples -------- - Check file contatining MF31, MF33, MF34 and MF35 - >>> endf6 = sandy.get_endf6_file('jeff_33', 'xs', 922380) - >>> endf6.covariance_info() - {'nubar': True, 'xs': True, 'mubar': True, 'chi': True} - - Set all values to `False` - >>> endf6.covariance_info(xs=False, mubar=False, chi=False, nubar=False) - {'nubar': False, 'xs': False, 'mubar': False, 'chi': False} - - 2nd example without MF34 - >>> endf6 = sandy.get_endf6_file('jeff_33', 'xs', 922350) - >>> endf6.covariance_info() - {'nubar': True, 'xs': True, 'mubar': False, 'chi': True} - - If MF34 is not found, setting `mubar=True` won't change anything' - >>> endf6 = sandy.get_endf6_file('jeff_33', 'xs', 922350) - >>> endf6.covariance_info(mubar=True) - {'nubar': True, 'xs': True, 'mubar': False, 'chi': True} - - All infos are `False` if no covariance is found - >>> endf6 = sandy.get_endf6_file('jeff_33', 'xs', 10030) - >>> endf6.covariance_info() - {'nubar': False, 'xs': False, 'mubar': False, 'chi': False} + Get a couple of samples from H1 file of JEFF-3.3. + >>> njoy_kws = dict(err=1, errorr_kws=dict(mt=102)) + >>> nsmp = 2 + >>> smps = sandy.get_endf6_file("jeff_33", "xs", 10010).get_perturbations(nsmp=nsmp, njoy_kws=njoy_kws) + + Process PENDF file. + >>> pendf = sandy.get_endf6_file("jeff_33", "xs", 10010).get_pendf(minimal_processing=True) + + Apply samples to xs in PENDF file. + >>> outs = pendf.apply_perturbations(smps[33], verbose=True) + + The output is a generator. + >>> assert isinstance(outs, types.GeneratorType) + >>> outs = dict(outs) + Processing xs sample 0... + Processing xs sample 1... + + We have as amany files as samples. + >>> assert list(outs.keys()) == list(range(nsmp)) + + Assert that only perturbed xs sections are different in perturbed files. + >>> for mat, mf, mt in outs[0].keys: + ... if mt != 102: + ... assert outs[0].data[(mat, mf, mt)] == outs[1].data[(mat, mf, mt)] + >>> assert outs[0].data[(125, 3, 102)] != outs[1].data[(125, 3, 102)] + + .. note:: the total cross section is not perturbed + + Apply samples to xs in PENDF file using multiprocessing. + >>> outs_mp = pendf.apply_perturbations(smps[33], processes=2) + + Assert that multiprocessing and singleprocessing results are identical. + >>> for n in outs: + ... for key in outs[n].data: + ... assert outs_mp[n].data[key] == outs[n].data[key] """ - supported_mf = [31, 33, 34, 35] - endf6_cov_mf = self.to_series().index.get_level_values("MF")\ - .intersection(supported_mf) - - run_nubar = True if 31 in endf6_cov_mf else False - run_xs = True if 33 in endf6_cov_mf else False - run_mubar = True if 34 in endf6_cov_mf else False - run_chi = True if 35 in endf6_cov_mf else False - - cov_info = { - 'nubar': run_nubar if nubar else False, - 'xs': run_xs if xs else False, - 'mubar': run_mubar if mubar else False, - 'chi': run_chi if chi else False, - } - return cov_info + if smp.data.index.names == [*sandy.Xs._columnsnames] + [sandy.Xs._indexname]: + if processes == 1: + return self._apply_xs_perturbations(smp, **kwargs) + else: + return self._mp_apply_perturbations(smp, processes=processes, **kwargs) + + + def _mp_apply_xs_perturbations(self, smp, processes, **kwargs): + # need to pass xs.data (dataframe), because sandy.Xs instance cannot be pickled + xs = sandy.Xs.from_endf6(self).data + seq = smp.iterate_xs_samples() + + pool = mp.Pool(processes=processes) + outs = {n: pool.apply_async(pendf_xs_perturb_worker, (self, xs, n, p), kwargs) for n, p in seq} + outs = {n: out.get() for n, out in outs.items()} + pool.close() + pool.join() + return outs + + def _mp_apply_perturbations(self, smps, processes, + pendf_kws={}, + **kwargs): + # Passed NEDF-6 and PENDF as dictionaries because class instances cannot be pickled + endf6 = self.data + if 33 in smps: + pendf = self.get_pendf(**pendf_kws).data + kwargs["process_xs"] = True + + # Samples passed as generator in apply_async + def null_gen(): + "generator mimicking dictionary and returning only None" + while True: + yield None, None + + def get_key(*lst): + return np.array([x for x in lst if x is not None]).item() + + seq_xs = smps[33].iterate_xs_samples() if 33 in smps else null_gen() + seq_nu = smps[31].iterate_xs_samples() if 31 in smps else null_gen() + seqs = zip(seq_xs, seq_nu) + + + pool = mp.Pool(processes=processes) + outs = {get_key(nxs, nnu): pool.apply_async(endf6_perturb_worker, (endf6, pendf, nxs, pxs), kwargs) + for (nxs, pxs), (nnu, pnu) in seqs} + outs = {n: out.get() for n, out in outs.items()} + pool.close() + pool.join() + return outs + + def _apply_xs_perturbations(self, smp, **kwargs): + xs = sandy.Xs.from_endf6(self) + for n, x in xs.perturb(smp, **kwargs): + # instead of defining the function twice, just call the worker also here + yield n, pendf_perturb_worker(self, n, x, **kwargs) + + +def pendf_perturb_worker(e6, n, xs, **kwargs): + # This is the function that needs to be changed for any concatenation + # in the pendf reconstruction pipeline + out = xs.reconstruct_sums(drop=True).to_endf6(e6).update_intro() + return out + + +def endf6_perturb_worker(e6, pendf, nxs, pxs, + verbose=False, + process_xs=False, + process_nu=False, + process_lpc=False, + process_chi=False, + to_ace=False, + to_file=False, + filename="{ZA}_{SMP}", + ace_kws={}, + **kwargs): + """ + + + Parameters + ---------- + e6 : TYPE + DESCRIPTION. + pendf : TYPE + DESCRIPTION. + nxs : TYPE + DESCRIPTION. + pxs : TYPE + DESCRIPTION. + verbose : TYPE, optional + DESCRIPTION. The default is False. + process_xs : TYPE, optional + DESCRIPTION. The default is False. + process_nu : TYPE, optional + DESCRIPTION. The default is False. + process_lpc : TYPE, optional + DESCRIPTION. The default is False. + process_chi : TYPE, optional + DESCRIPTION. The default is False. + to_ace : TYPE, optional + DESCRIPTION. The default is False. + to_file : TYPE, optional + DESCRIPTION. The default is False. + filename : TYPE, optional + DESCRIPTION. The default is "{ZA}_{SMP:d}". + ace_kws : TYPE, optional + DESCRIPTION. The default is {}. + **kwargs : TYPE + DESCRIPTION. + + Returns + ------- + TYPE + DESCRIPTION. + + """ + # default initialization + endf6_pert = sandy.Endf6(e6.copy()) + pendf_pert = None + + # filename options, in case we erite to file + ismp = np.array([x for x in [nxs] if x is not None]).item() + mat = endf6_pert.mat[0] + intro = endf6_pert.read_section(mat, 1, 451) + za = int(intro["ZA"]) + meta = int(intro["LISO"]) + zam = sandy.zam.za2zam(za, meta=meta) + temperature = ace_kws["temperature"] = ace_kws.get("temperature", 0) + params = dict( + MAT=mat, + ZAM=zam, + ZA=za, + META=meta, + SMP=ismp, + ) + fn = filename.format(**params) + + # apply xs perturbation + if process_xs: + pendf_ = sandy.Endf6(pendf) + xs = sandy.Xs.from_endf6(pendf_) + xs_pert = sandy.core.xs.xs_perturb_worker(xs, nxs, pxs, verbose=verbose) + pendf_pert = xs_pert.reconstruct_sums(drop=True).to_endf6(pendf_).update_intro() + + # Run NJOY and convert to ace + if to_ace: + suffix = ace_kws.get("suffix", sandy.njoy.get_temperature_suffix(temperature)) + ace_kws["suffix"] = "." + suffix + ace = endf6_pert.get_ace(pendf=pendf_pert, **ace_kws) + + if to_file: + file = f"{fn}.{suffix}c" + with open(file, "w") as f: + if verbose: + print(f"writing to file '{file}'") + f.write(ace["ace"]) + file = f"{file}.xsd" + with open(file, "w") as f: + if verbose: + print(f"writing to file '{file}'") + f.write(ace["xsdir"]) + return + + return ace + + out = {"endf6": endf6_pert} + if pendf_pert: + out["pendf"] = pendf_pert + + if to_file: + file = f"{fn}.endf6" + if verbose: + print(f"writing to file '{file}'") + endf6_pert.to_file(file) + if pendf_pert: + file = f"{fn}.pendf" + if verbose: + print(f"writing to file '{file}'") + pendf_pert.to_file(file) + return + + return out diff --git a/sandy/core/records.py b/sandy/core/records.py index 5668e8e4..95282ec3 100644 --- a/sandy/core/records.py +++ b/sandy/core/records.py @@ -25,6 +25,7 @@ "line_numbers", "write_line", "write_eol", + "write_text", ] @@ -120,6 +121,20 @@ def read_text(df, ipos): return TEXT(HL), ipos +def write_text(text): + """ + Write ENDF-6 `TEXT` record in formatted fortran. + + Returns + ------- + `str` + list of 66-characters-long ENDF-6 formatted string + + """ + line = f"{text[:66]:66}" + return [line] + + def write_integer_list(lst): """ Write list of integers into ENDF-6 format. diff --git a/sandy/core/samples.py b/sandy/core/samples.py index a7021b50..654ffdce 100644 --- a/sandy/core/samples.py +++ b/sandy/core/samples.py @@ -1,11 +1,6 @@ -import logging -import io - import numpy as np import pandas as pd -import matplotlib.pyplot as plt -import seaborn as sns -import statsmodels.api as sm +import scipy import sandy @@ -14,53 +9,38 @@ "Samples", ] -np.random.seed(1) -minimal_testcase = np.random.randn(4, 3) - - -def cov33csv(func): - def inner(*args, **kwargs): - key = "cov33csv" - kw = kwargs.copy() - if key in kw: - if kw[key]: - print(f"found argument '{key}', ignore oher arguments") - out = func( - *args, - index_col=[0, 1, 2], - ) - out.data.index.names = ["MAT", "MT", "E"] - return out - else: - del kw[key] - out = func(*args, **kw) - return out - return inner - class Samples(): """ + Container for samples. + Attributes ---------- - condition_number - data - + Dataframe of samples. Methods ------- - filter_by - - from_csv - - regression_coefficients - - sm_ols - + get_condition_number + Return condition number of samples. + get_cov + Return covariance matrix of samples. + get_mean + Return mean vector of samples. + get_std + Return standard deviation vector of samples. + get_rstd + Return relative standard deviation vector of samples. + iterate_xs_samples + Generator that iterates over each sample (in the form of :func:`sandy.Xs`). + test_shapiro + Perform the Shapiro-Wilk test for normality on the samples. """ - def __init__(self, df): - self.data = pd.DataFrame(df, dtype=float) + _columnsname = "SMP" + + def __init__(self, df, *args, **kwargs): + self.data = pd.DataFrame(df, *args, dtype=float, **kwargs) def __repr__(self): return self.data.__repr__() @@ -88,10 +68,9 @@ def data(self): @data.setter def data(self, data): - self._data = data + self._data = data.rename_axis(self.__class__._columnsname, axis=1) - @property - def condition_number(self): + def get_condition_number(self): """ Return condition number of samples. @@ -106,55 +85,29 @@ def condition_number(self): for i, name in enumerate(X): norm_x[:, i] = X[name] / np.linalg.norm(X[name]) norm_xtx = np.dot(norm_x.T, norm_x) + # Then, we take the square root of the ratio of the biggest to the # smallest eigen values eigs = np.linalg.eigvals(norm_xtx) return np.sqrt(eigs.max() / eigs.min()) - @property - def mean(self): + def get_mean(self): return self.data.mean(axis=1).rename("MEAN") - @property - def rstd(self): - return (self.std / self.mean).rename("RSTD") + def get_cov(self): + return self.data.T.cov() - @property - def std(self): + def get_std(self): return self.data.std(axis=1).rename("STD") - def filter_by(self, key, value): - """ - Apply condition to source data and return filtered results. - - Parameters - ---------- - `key` : `str` - any label present in the columns of `data` - `value` : `int` or `float` - value used as filtering condition + def get_rstd(self): + return (self.get_std() / self.get_mean()).rename("RSTD") - Returns - ------- - `sandy.Samples` - filtered dataframe of samples - - Raises - ------ - `sandy.Error` - if applied filter returned empty dataframe - - Notes - ----- - .. note:: The primary function of this method is to make sure that - the filtered dataframe is still returned as a `Samples` - object. - """ - condition = self.data.index.get_level_values(key) == value - out = self.data.copy()[condition] - if out.empty: - raise sandy.Error("applied filter returned empty dataframe") - return self.__class__(out) + def iterate_xs_samples(self): + levels = sandy.Xs._columnsnames + df = self.data.unstack(level=levels) + for n, p in df.groupby(axis=1, level=self._columnsname): + yield n, p.droplevel(self._columnsname, axis=1) def _std_convergence(self): smp = self.data @@ -167,98 +120,99 @@ def _mean_convergence(self): rng = range(1, smp.shape[0]) foo = lambda x: smp.loc[:x].mean() return pd.DataFrame(map(foo, rng), index=rng) - - def _heatmap(self, vmin=-1, vmax=1, cmap="bwr", **kwargs): - corr = np.corrcoef(self.data) - return sns.heatmap(corr, vmin=vmin, vmax=vmax, cmap=cmap, **kwargs) - - def sm_ols(self, Y, normalized=False, intercept=False): - X = self.data.T.copy() - NX, MX = X.shape - NY = Y.size - N = min(NX, NY) - if NX != NY: - print(f"X and Y have different size, fit only first {N} samples") - if normalized: - X = X.divide(X.mean()).fillna(0) - Y = Y.divide(Y.mean()).fillna(0) - if intercept: - X = sm.add_constant(X) - model = sm.OLS(Y.iloc[:N].values, X[:N].values) - out = model.fit() - return out - - def regression_coefficients(self, Y, **kwargs): + + def test_shapiro(self, size=None, pdf="normal"): """ - Calculate regression coefficients from OLS model given an output - population. - - Parameters - ---------- - Y : `pandas.Series` - tabulated output population - kwargs : keyword arguments, optional - arguments to pass to method `sm_ols` - - Returns - ------- - `pandas.DataFrame` - Dataframe with regression coefficients and standard errors. - """ - X = self.data - MX, NX = X.shape - index = X.index - res = self.sm_ols(Y, **kwargs) - params = res.params - bse = res.bse - start_at = 0 if params.size == MX else 1 - coeff = pd.DataFrame({ - "coeff": params[start_at:], - "stderr": bse[start_at:], - }, index=index) - return coeff - - @classmethod - @cov33csv - def from_csv(cls, file, **kwargs): - """ - Read samples from csv file, + Perform the Shapiro-Wilk test for normality on the samples. + The test can be performed also for a lognormal distribution by testing + for normality the logarithm of the samples. + + The Shapiro-Wilk test tests the null hypothesis that the data was + drawn from a normal distribution. Parameters ---------- - file : `str` - csv file. - **kwargs : `dict` - keyword options for `pandas.read_csv`. + size : `int`, optional + number of samples (starting from the first) that need to be + considered for the test. The default is `None`, i.e., all samples. + pdf : `str`, optional + the pdf used to test the samples. Either `"normal"` or + `"lognormal"`. The default is "normal". Returns ------- - `sandy.Samples` - samples into a sandy object. + pd.DataFrame + Dataframe with Shapriro-Wilk results (statistic and pvalue) for + each variable considered in the :func:`~Samples` instance. Examples -------- - >>> csv = minimal_testcase.to_string() - >>> sandy.Samples.from_csv(io.StringIO(csv), sep="\s+") - 0 1 2 - 0 1.62435e+00 -6.11756e-01 -5.28172e-01 - 1 -1.07297e+00 8.65408e-01 -2.30154e+00 - 2 1.74481e+00 -7.61207e-01 3.19039e-01 - 3 -2.49370e-01 1.46211e+00 -2.06014e+00 - - >>> index = pd.MultiIndex.from_product( - ... [[9437], [102], [1e-5, 1e-1, 1e1, 1e6]] - ... ) - >>> df = minimal_testcase.copy() - >>> df.index = index - >>> csv = df.to_csv() - >>> sandy.Samples.from_csv(io.StringIO(csv), sep="\s+", cov33csv=True) - 0 1 2 - MAT MT E - 9437 102 1.00000e-05 1.62435e+00 -6.11756e-01 -5.28172e-01 - 1.00000e-01 -1.07297e+00 8.65408e-01 -2.30154e+00 - 1.00000e+01 1.74481e+00 -7.61207e-01 3.19039e-01 - 1.00000e+06 -2.49370e-01 1.46211e+00 -2.06014e+00 + Generate 5000 xs samples normally, log-normally and uniform distributed + >>> tape = sandy.get_endf6_file("jeff_33", "xs", 10010) + >>> njoy_kws = dict(err=1, errorr33_kws=dict(mt=102)) + >>> nsmp = 5000 + >>> seed = 5 + >>> + >>> smp_norm = tape.get_perturbations(nsmp, njoy_kws=njoy_kws, smp_kws=dict(seed33=seed, pdf="normal"))[33] + >>> smp_lognorm = tape.get_perturbations(nsmp, njoy_kws=njoy_kws, smp_kws=dict(seed33=seed, pdf="lognormal"))[33] + >>> smp_uniform = tape.get_perturbations(nsmp, njoy_kws=njoy_kws, smp_kws=dict(seed33=seed, pdf="uniform"))[33] + + In this example we defined the following arbitrary convergence criteria: + - if the p value is larger than 0.05 we fail to reject the null-hypothesis and we accept the results + - if the first condition is accepted, we confirm the pdf if the statistics is larger than 0.95 + >>> threshold = 0.95 + >>> pthreshold = 0.05 + >>> def test(smps): + ... data = [] + ... for n in [10, 50, 100, 500, 1000, 5000]: + ... for pdf in ("normal", "lognormal"): + ... df = smps.test_shapiro(pdf=pdf, size=n) + ... idx = df.statistic.idxmin() + ... w = df.loc[idx] + ... t = "reject" if w.pvalue < pthreshold else (pdf if w.statistic > threshold else "reject") + ... data.append({"PDF": pdf, "test":t, "# SMP": n}) + ... df = pd.DataFrame(data).pivot_table(index="# SMP", columns="PDF", values="test", aggfunc=lambda x: ' '.join(x)) + ... return df + + The Shapiro-Wilks test proves wrong the normal samples because of the tail truncation. + >>> print(test(smp_norm)) + PDF lognormal normal + # SMP + 10 reject reject + 50 reject reject + 100 reject reject + 500 reject reject + 1000 reject reject + 5000 reject reject + + The Shapiro-Wilks test proves right for the lognormal samples and the lognormal distribution. + >>> print(test(smp_lognorm)) + PDF lognormal normal + # SMP + 10 lognormal reject + 50 lognormal reject + 100 lognormal reject + 500 lognormal reject + 1000 lognormal reject + 5000 lognormal reject + + The Shapiro-Wilks gives too low p-values for the uniform samples. + >>> print(test(smp_uniform)) + PDF lognormal normal + # SMP + 10 reject reject + 50 reject reject + 100 reject reject + 500 reject reject + 1000 reject reject + 5000 reject reject """ - df = pd.read_csv(file, **kwargs) - return cls(df) + size_ = size or self.data.shape[1] + names = ["statistic", "pvalue"] + + data = self.data.iloc[:, :size_] + if pdf.lower() == "lognormal": + data = np.log(self.data) + + df = pd.DataFrame({idx: scipy.stats.shapiro(row) for idx, row in data.iterrows()}, index=names).T + return df.rename_axis(data.index.names) diff --git a/sandy/core/xs.py b/sandy/core/xs.py index 0373268a..8d25cbd4 100644 --- a/sandy/core/xs.py +++ b/sandy/core/xs.py @@ -7,9 +7,11 @@ class `Xs` that acts as a container for energy-dependent tabulated cross import os import logging import functools +import types import numpy as np import pandas as pd +import multiprocessing as mp import sandy @@ -48,14 +50,16 @@ class Xs(): Methods ------- - reshape - Interpolate cross sections over new grid structure custom_perturbation Apply a custom perturbation to a given cross section - to_endf6 - Update cross sections in `Endf6` instance from_endf6 Extract cross sections/nubar from `Endf6` instance + perturb + + reshape + Interpolate cross sections over new grid structure + to_endf6 + Update cross sections in `Endf6` instance """ redundant_xs = { @@ -123,9 +127,8 @@ def data(self): def data(self, data): self._data = data.rename_axis(self.__class__._indexname, axis=0)\ .rename_axis(self.__class__._columnsnames, axis=1) - self._data.index = self._data.index if not data.index.is_monotonic_increasing: - raise sandy.Error("energy grid is not monotonically increasing") + raise ValueError("energy grid is not monotonically increasing") def reshape(self, eg): """ @@ -189,11 +192,6 @@ def custom_perturbation(self, mat, mt, pert): u_xs.data[(mat, mt)] = u_xs.data[(mat, mt)] * u_pert.right.values return self.__class__(u_xs.data) - def filter_energies(self, energies): - mask = self.data.index.isin(energies) - data = self.data.loc[mask] - return self.__class__(data) - def to_endf6(self, endf6): """ Update cross sections in `Endf6` instance with those available in a @@ -271,6 +269,9 @@ def from_endf6(cls, endf6): .. note:: missing points are linearly interpolated if inside the energy domain, else zero is assigned. + .. note:: Duplicate energy points will be removed, only the first one + is kept. + Parameters ---------- `endf6` : `sandy.Endf6` @@ -285,23 +286,28 @@ def from_endf6(cls, endf6): ------ `sandy.Error` if interpolation scheme is not lin-lin - `sandy.Error` - if requested cross section was not found Warns ----- `logging.warning` if duplicate energy points are found - Notes - ----- - .. note:: Cross sections are linearized on a unique grid. - - .. note:: Missing points are linearly interpolated if inside the energy - domain, else zero is assigned. - - .. note:: Duplicate energy points will be removed, only the first one - is kept. + Examples + -------- + Get H1 file and process it to PENDF. + >>> tape = sandy.get_endf6_file("jeff_33", "xs", 10010) + >>> pendf = tape.get_pendf(minimal_processing=True) + + Show content of `sandy.Xs` instance. + >>> sandy.Xs.from_endf6(pendf).data.head() + MAT 125 + MT 1 2 102 + E + 1.00000e-05 3.71363e+01 2.04363e+01 1.66999e+01 + 1.03125e-05 3.68813e+01 2.04363e+01 1.64450e+01 + 1.06250e-05 3.66377e+01 2.04363e+01 1.62013e+01 + 1.09375e-05 3.64045e+01 2.04363e+01 1.59682e+01 + 1.12500e-05 3.61812e+01 2.04363e+01 1.57448e+01 """ data = [] # read cross sections @@ -359,80 +365,227 @@ def foo(l, r): .fillna(0) return cls(df) - def _reconstruct_sums(self, drop=True, inplace=False): + def reconstruct_sums(self, drop=True): """ - Reconstruct redundant xs. + Reconstruct redundant xs according to ENDF-6 rules in Appendix B. + Redundant cross sections are available in `dict` + :func:`~sandy.redundant_xs`. + + Parameters + ---------- + drop : `bool`, optional + keep in output only the MT number originally present. + The default is True. + + Returns + ------- + :func:`~sandy.Xs` + Cross section instance where reconstruction rules are enforced. + + Examples + -------- + Get ENDF-6 file for H1, process it in PENDF and extract xs. + >>> tape = sandy.get_endf6_file("jeff_33", "xs", 10010) + >>> pendf = tape.get_pendf(minimal_processing=True) + >>> xs = sandy.Xs.from_endf6(pendf) + + We introduce a perturbation to the elastic scattering xs + >>> xs.data[(125, 2)] *= 2 + >>> assert not xs.data[(125, 1)].equals(xs.data[(125, 2)] + xs.data[(125, 102)]) + + Reconstruciting xs enforces consistency. + >>> xs1 = xs.reconstruct_sums(drop=True).data + >>> assert xs1.columns.equals(xs.data.columns) + >>> assert xs1[(125, 1)].equals(xs1[(125, 2)] + xs1[(125, 102)]) + + We can keep all redundant xs with keyword `drop=True` + >>> xs2 = xs.reconstruct_sums(drop=False).data + >>> assert not xs2.columns.equals(xs.data.columns) + >>> assert xs2[xs1.columns].equals(xs1) + + >>> assert xs2[(125, 101)].equals(xs2[(125, 102)]) + >>> assert xs2[(125, 27)].equals(xs2[(125, 101)]) + >>> assert xs2[(125, 3)].equals(xs2[(125, 27)]) """ df = self.data.copy() - for mat in self.data.columns.get_level_values("MAT").unique(): - for parent, daughters in sorted(redundant_xs.items(), reverse=True): - daughters = [x for x in daughters if x in df[mat]] - if daughters: - df[mat,parent] = df[mat][daughters].sum(axis=1) + for mat, group in df.groupby("MAT", axis=1): + + # starting from the lat redundant cross section, find daughters and sum them + for parent, daughters in sorted(sandy.redundant_xs.items(), reverse=True): + # it must be df, not group, because df is updated + x = df[mat].T.query("MT in @daughters").T # need to transpose to query on columns + if not x.empty: + df[(mat, parent)] = x.sum(axis=1) + # keep only mts present in the original file if drop: - todrop = [x for x in df[mat].columns if x not in self.data[mat].columns] - cols_to_drop = pd.MultiIndex.from_product([[mat], todrop]) - df.drop(cols_to_drop, axis=1, inplace=True) - if inplace: - self.data = df - else: - return Xs(df) -# frame = self.copy() -# for mat in frame.columns.get_level_values("MAT").unique(): -# for parent, daughters in sorted(Xs.redundant_xs.items(), reverse=True): -# daughters = [ x for x in daughters if x in frame[mat]] -# if daughters: -# frame[mat,parent] = frame[mat][daughters].sum(axis=1) -# # keep only mts present in the original file -# if drop: -# todrop = [ x for x in frame[mat].columns if x not in self.columns.get_level_values("MT") ] -# frame.drop(pd.MultiIndex.from_product([[mat], todrop]), axis=1, inplace=True) -# return Xs(frame) - - def _perturb(self, pert, method=2, **kwargs): - """Perturb cross sections/nubar given a set of perturbations. - + keep = group[mat].columns + # same filtering method as above + todrop = df[mat].T.query("MT not in @keep").index + df.drop( + pd.MultiIndex.from_product([[mat], todrop]), + axis=1, + inplace=True, + ) + + return self.__class__(df) + + def _mp_multi_perturb(self, smp, processes, verbose=False): + pool = mp.Pool(processes=processes) + seq = dict(smp.iterate_xs_samples()).items() + kw = dict(verbose=verbose) + outs = {n: pool.apply_async(xs_perturb_worker, (self, n, p), kw) for n, p in seq} + outs = {n: out.get() for n, out in outs.items()} + pool.close() + pool.join() + return outs + + def _multi_perturb(self, smp, verbose=False): + """ + Decorator to handle :func:`~sandy.Samples` instance as input of method + :func:`~sandy.Xs.perturb`. + Multiple perturbed :func:`~sandy.Xs` instances are returned as a + generator as they mimic dict comprehension. + """ + for n, p in smp.iterate_xs_samples(): + if verbose: + print(f"Processing xs sample {n}...") + # instead of defining the function twice, just call the worker also here + yield n, xs_perturb_worker(self, n, p) + + def _perturb(self, s): + """ + Apply perturbations to cross sections. + Parameters ---------- - pert : pandas.Series - multigroup perturbations from sandy.XsSamples - method : int - * 1 : samples outside the range [0, 2*_mean_] are set to _mean_. - * 2 : samples outside the range [0, 2*_mean_] are set to 0 or 2*_mean_ respectively if they fall below or above the defined range. + s : `pandas.DataFrame` or :func:`~sandy.Samples` + input perturbations or samples. + If `s` is a `pandas.DataFrame`, its index and columns must have the + same names and structure as in `self.data`. + + .. note:: the energy grid of `s` must be multigroup, i.e., + rendered by a (right-closed) `pd.IntervalIndex`. + + If `s` is a :func:`~sandy.Samples` instance, see + :func:`~sandy.Xs._multi_pert`. + + Returns + ------- + xs : :func:`~Xs` or `dict` of :func:`~Xs` + perturbed cross section object if `s` is a `pandas.DataFrame`, + otherwise dictionary of perturbed cross section objects with + sample numbers as key. + + Examples + -------- + Get plutonium cross sections + >>> pendf = sandy.get_endf6_file("jeff_33", "xs", 942390).get_pendf(err=1, minimal_processing=True) + >>> xs = sandy.Xs.from_endf6(pendf) + + Apply multiplication coefficient equal to 1 to elastic and inelastic + scattering cross sections up to 3e7 eV (upper xs energy limit) + >>> index = pd.IntervalIndex.from_breaks([1e-5, 3e7], name="E", closed="right") + >>> columns = pd.MultiIndex.from_product([[9437], [2, 4]], names=["MAT", "MT"]) + >>> s = pd.DataFrame(1, index=index, columns=columns) + >>> xp = xs.perturb(s) + >>> assert xp.data.equals(xs.data) + + Apply multiplication coefficients equal to 1 and to 2 respectively to + elastic and inelastic scattering cross sections up to 3e7 eV (upper xs energy limit) + >>> s = pd.DataFrame([[1, 2]], index=index, columns=columns) + >>> xp = xs.perturb(s) + >>> assert not xp.data.equals(xs.data) + >>> assert xp.data.loc[:, xp.data.columns != (9437, 4)].equals(xp.data.loc[:, xs.data.columns != (9437, 4)]) + >>> assert xp.data[(9437, 4)].equals(xs.data[(9437, 4)] * 2) + Apply multiplication coefficients equal to 1 and to 2 respectively to + elastic and inelastic scattering cross sections up to 2e7 eV + >>> index = pd.IntervalIndex.from_breaks([1e-5, 2e7], name="E", closed="right") + >>> columns = pd.MultiIndex.from_product([[9437], [2, 4]], names=["MAT", "MT"]) + >>> s = pd.DataFrame([[1, 2]], index=index, columns=columns) + >>> xp = xs.perturb(s) + >>> assert not xp.data.equals(xs.data) + >>> assert xp.data.loc[:, xp.data.columns != (9437, 4)].equals(xp.data.loc[:, xs.data.columns != (9437, 4)]) + >>> assert xp.data.loc[:2e7, (9437, 4)].equals(xs.data.loc[:2e7, (9437, 4)] * 2) + >>> assert xp.data.loc[2e7:, (9437, 4)].iloc[1:].equals(xs.data.loc[2e7:, (9437, 4)].iloc[1:]) + """ + x = self.data + + # reshape indices (energy) + idx = s.index.get_indexer(x.index) + # need to copy, or else it returns a view + # seed https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy + s_ = s.iloc[idx].copy() + s_.index = x.index + s_.loc[idx < 0, :] = 1. # idx = -1 indicates out of range lines + + # reshape columns (MAT and MT) + idx = s_.columns.get_indexer(x.columns) + s_ = s_.iloc[:, idx] + s_.columns = x.columns + s_.loc[:, idx < 0] = 1. # idx = -1 indicates out of range lines + + xs = self.__class__(s_ * x) + return xs + + def perturb(self, smp, processes=1, **kwargs): + """ + + + Parameters + ---------- + smp : TYPE + DESCRIPTION. + processes : TYPE, optional + DESCRIPTION. The default is 1. + **kwargs : TYPE + DESCRIPTION. + Returns ------- - `sandy.formats.utils.Xs` + TYPE + DESCRIPTION. + + Examples + -------- + Create two H1 samples. + >>> njoy_kws = dict(err=1, errorr_kws=dict(mt=102)) + >>> nsmp = 2 + >>> tape = sandy.get_endf6_file("jeff_33", "xs", 10010) + >>> smps = tape.get_perturbations(nsmp=nsmp, njoy_kws=njoy_kws) + + Extract H1 xs. + >>> pendf = tape.get_pendf(minimal_processing=True) + >>> xs = sandy.Xs.from_endf6(pendf) + + Apply perturbations to xs without multiprocessing. + >>> outs = xs.perturb(smps[33], processes=1, verbose=True) + + The output is a generator. + >>> assert isinstance(outs, types.GeneratorType) + >>> outs = dict(outs) + Processing xs sample 0... + Processing xs sample 1... + + Apply perturbations to xs with multiprocessing. + >>> outs_mp = xs.perturb(smps[33], processes=2, verbose=True) + + The output is a dict. + >>> assert isinstance(outs_mp, dict) + + The two outputs are identical. + >>> for k in outs: + ... assert outs[k].data.equals(outs_mp[k].data) """ - frame = self.copy() - for mat in frame.columns.get_level_values("MAT").unique(): - if mat not in pert.index.get_level_values("MAT"): - continue - for mt in frame[mat].columns.get_level_values("MT").unique(): - lmtp = pert.loc[mat].index.get_level_values("MT").unique() - mtPert = None - if lmtp.max() == 3 and mt >= 3: - mtPert = 3 - elif mt in lmtp: - mtPert = mt - else: - for parent, daughters in sorted(self.__class__.redundant_xs.items(), reverse=True): - if mt in daughters and not list(filter(lambda x: x in lmtp, daughters)) and parent in lmtp: - mtPert = parent - break - if not mtPert: - continue - P = pert.loc[mat,mtPert] - P = P.reindex(P.index.union(frame[mat,mt].index)).ffill().fillna(1).reindex(frame[mat,mt].index) - if method == 2: - P = P.where(P>0, 0.0) - P = P.where(P<2, 2.0) - elif method == 1: - P = P.where((P>0) & (P<2), 1.0) - xs = frame[mat,mt].multiply(P, axis="index") - frame[mat,mt] = xs - return Xs(frame).reconstruct_sums() + if not isinstance(smp, sandy.Samples): + return self._perturb(smp) + + if processes==1: + return self._multi_perturb(smp, **kwargs) + else: + return self._mp_multi_perturb(smp, processes=processes, **kwargs) + @classmethod def _from_errorr(cls, errorr): @@ -468,62 +621,8 @@ def _from_errorr(cls, errorr): frame = pd.concat(listxs, axis=1).reindex(eg, method="ffill") return Xs(frame) - @classmethod - def from_file(cls, file, kind="endf6"): - """ - Read cross sections directly from file. - - Parameters - ---------- - file : `str` - file name with relative or absolute path - kind : `str`, optional, default is `'endf6'` - type of file - - Returns - ------- - `sandy.Xs` - cross sections tabulated data - - Examples - -------- - >>> file = os.path.join(sandy.data.__path__[0], "h1.pendf") - >>> sandy.Xs.from_file(file).data.head() - MAT 125 - MT 1 2 102 - E - 1.00000e-05 3.71363e+01 2.04363e+01 1.66999e+01 - 1.03125e-05 3.68813e+01 2.04363e+01 1.64450e+01 - 1.06250e-05 3.66377e+01 2.04363e+01 1.62013e+01 - 1.09375e-05 3.64045e+01 2.04363e+01 1.59682e+01 - 1.12500e-05 3.61812e+01 2.04363e+01 1.57448e+01 - """ - if kind != "endf6": - raise ValueError("sandy can only read cross sections from 'endf6' " - "files") - tape = sandy.Endf6.from_file(file) - return cls.from_endf6(tape) - def eV2MeV(self): - """ - Produce dataframe of cross sections with index in MeV instead of eV. - - Returns - ------- - `pandas.DataFrame` - dataframe of cross sections with enery index in MeV - - Examples - -------- - >>> index = [1e-5, 2e7] - >>> columns = pd.MultiIndex.from_tuples([(9437, 1)]) - >>> sandy.Xs([1, 2], index=index, columns=columns).eV2MeV() - MAT 9437 - MT 1 - E - 1.00000e-11 1.00000e+00 - 2.00000e+01 2.00000e+00 - """ - df = self.data.copy() - df.index = df.index * 1e-6 - return df +def xs_perturb_worker(xs, n, s, verbose=False): + if verbose: + print(f"Processing xs sample {n}...") + return xs._perturb(s) diff --git a/sandy/errorr.py b/sandy/errorr.py index dbd6e23e..e069575a 100644 --- a/sandy/errorr.py +++ b/sandy/errorr.py @@ -15,11 +15,21 @@ class Errorr(_FormattedFile): """ Container for ERRORR file text grouped by MAT, MF and MT numbers. + + Methods + ------- + get_cov + Extract mulitgroup covariance matrix. + get_energy_grid + Extract breaks of multi-group energy grid from ERRORR output file. + get_xs + Extract multigroup xs values. """ def get_energy_grid(self, **kwargs): """ - Obtaining the energy grid. + Extract breaks of multi-group energy grid from ERRORR + output file. Parameters ---------- @@ -33,17 +43,11 @@ def get_energy_grid(self, **kwargs): Examples -------- - >>> endf6_2 = sandy.get_endf6_file("jeff_33", "xs", 942410) - >>> err = endf6_2.get_errorr(ek_errorr=sandy.energy_grids.CASMO12, err=1, ek_groupr=sandy.energy_grids.CASMO12) - >>> err.get_energy_grid() - array([1.0000e-05, 3.0000e-02, 5.8000e-02, 1.4000e-01, 2.8000e-01, - 3.5000e-01, 6.2500e-01, 4.0000e+00, 4.8052e+01, 5.5300e+03, - 8.2100e+05, 2.2310e+06, 1.0000e+07]) - - >>> err.get_energy_grid(mat=9443) - array([1.0000e-05, 3.0000e-02, 5.8000e-02, 1.4000e-01, 2.8000e-01, - 3.5000e-01, 6.2500e-01, 4.0000e+00, 4.8052e+01, 5.5300e+03, - 8.2100e+05, 2.2310e+06, 1.0000e+07]) + >>> e6 = sandy.get_endf6_file("jeff_33", "xs", 10010) + >>> ek = sandy.energy_grids.CASMO12 + >>> err = e6.get_errorr(errorr_kws=dict(ek=ek), err=1)['errorr33'] + >>> np.testing.assert_allclose(err.get_energy_grid(), ek, atol=1e-14, rtol=1e-14) + >>> np.testing.assert_allclose(err.get_energy_grid(mat=125), ek, atol=1e-14, rtol=1e-14) """ mat_ = kwargs.get('mat', self.mat[0]) mf1 = read_mf1(self, mat_) @@ -51,7 +55,7 @@ def get_energy_grid(self, **kwargs): def get_xs(self, **kwargs): """ - Obtain the xs values across the energy grid. + Extract multigroup xs values. Returns ------- @@ -60,8 +64,10 @@ def get_xs(self, **kwargs): Examples -------- - >>> endf6 = sandy.get_endf6_file("jeff_33", "xs", 10010) - >>> err = endf6.get_errorr(ek_errorr=sandy.energy_grids.CASMO12, err=1) + Test for xs + >>> e6 = sandy.get_endf6_file("jeff_33", "xs", 10010) + >>> ek = sandy.energy_grids.CASMO12 + >>> err = e6.get_errorr(err=1, errorr_kws=dict(ek=ek))['errorr33'] >>> err.get_xs() MAT 125 MT 1 2 102 @@ -112,18 +118,36 @@ def get_xs(self, **kwargs): (5530.0, 821000.0] 8.05810e+00 (821000.0, 2231000.0] 3.48867e+00 (2231000.0, 10000000.0] 1.52409e+00 + + Test for chi + >>> e6 = sandy.get_endf6_file("jeff_33", "xs", 922350) + >>> ek = [2e-5, 0.058, 0.28, 0.625, 4.0, 48.052, 5530, 8.21e5, 2.23e6, 1e7] + >>> err = e6.get_errorr(err=1, errorr_kws=dict(ek=ek))['errorr35'] + >>> err.get_xs() + MAT 9228 + MT 18 + E + (2e-05, 0.058] 1.10121e-10 + (0.058, 0.28] 1.10121e-10 + (0.28, 0.625] 1.05356e-10 + (0.625, 4.0] 3.35715e-10 + (4.0, 48.052] 1.55325e-08 + (48.052, 5530.0] 7.49925e-06 + (5530.0, 821000.0] 5.85238e-03 + (821000.0, 2230000.0] 1.72394e-02 + (2230000.0, 10000000.0] 6.29781e-03 """ data = [] listmt_ = kwargs.get('mt', range(1, 10000)) listmt_ = [listmt_] if isinstance(listmt_, int) else listmt_ listmat_ = kwargs.get('mat', range(1, 10000)) listmat_ = [listmat_] if isinstance(listmat_, int) else listmat_ - for mat, mf, mt in self.filter_by(listmf=[3], + for mat, mf, mt in self.filter_by(listmf=[3, 5], listmt=listmt_, listmat=listmat_).data: mf1 = sandy.errorr.read_mf1(self, mat) egn = pd.IntervalIndex.from_breaks(mf1["EG"]) - mf3 = sandy.errorr.read_mf3(self, mat, mt) + mf3 = sandy.errorr.read_mf3(self, mat, mf, mt) columns = pd.MultiIndex.from_tuples([(mat, mt)], names=["MAT", "MT"]) index = pd.Index(egn, name="E") @@ -131,7 +155,7 @@ def get_xs(self, **kwargs): data = pd.concat(data, axis=1).fillna(0) return sandy.Xs(data) - def get_cov(self, multigroup=True): + def get_cov(self, multigroup=True, mt=None): """ Extract cross section/nubar covariance from `Errorr` instance. @@ -143,9 +167,11 @@ def get_cov(self, multigroup=True): Examples -------- - >>> endf6 = sandy.get_endf6_file("jeff_33", "xs", 10010) - >>> err = endf6.get_errorr(ek_errorr=[1e-2, 1e1, 2e7], err=1) - >>> err.get_cov().data + Test for xs covariance matrix + >>> e6 = sandy.get_endf6_file("jeff_33", "xs", 10010) + >>> err = e6.get_errorr(errorr_kws=dict(ek=[1e-2, 1e1, 2e7]), err=1)['errorr33'] + >>> datamg = err.get_cov().data + >>> datamg MAT1 125 MT1 1 2 102 E1 (0.01, 10.0] (10.0, 20000000.0] (0.01, 10.0] (10.0, 20000000.0] (0.01, 10.0] (10.0, 20000000.0] @@ -157,39 +183,54 @@ def get_cov(self, multigroup=True): 102 (0.01, 10.0] 1.07035e-06 7.58742e-09 0.00000e+00 0.00000e+00 6.51764e-04 3.40163e-04 (10.0, 20000000.0] 5.58627e-07 1.49541e-06 0.00000e+00 0.00000e+00 3.40163e-04 6.70431e-02 - >>> err.get_cov(multigroup=False).data - MAT1 125 - MT1 1 2 102 - E1 1.00000e-02 1.00000e+01 2.00000e+07 1.00000e-02 1.00000e+01 2.00000e+07 1.00000e-02 1.00000e+01 2.00000e+07 - MAT MT E - 125 1 1.00000e-02 8.74838e-06 4.62556e-05 0.00000e+00 8.76101e-06 4.62566e-05 0.00000e+00 1.07035e-06 5.58627e-07 0.00000e+00 - 1.00000e+01 4.62556e-05 2.47644e-04 0.00000e+00 4.63317e-05 2.47650e-04 0.00000e+00 7.58742e-09 1.49541e-06 0.00000e+00 - 2.00000e+07 0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 - 2 1.00000e-02 8.76101e-06 4.63317e-05 0.00000e+00 8.77542e-06 4.63327e-05 0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 - 1.00000e+01 4.62566e-05 2.47650e-04 0.00000e+00 4.63327e-05 2.47655e-04 0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 - 2.00000e+07 0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 - 102 1.00000e-02 1.07035e-06 7.58742e-09 0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 6.51764e-04 3.40163e-04 0.00000e+00 - 1.00000e+01 5.58627e-07 1.49541e-06 0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 3.40163e-04 6.70431e-02 0.00000e+00 - 2.00000e+07 0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 0.00000e+00 + Test for chi covariance matrix + >>> e6 = sandy.get_endf6_file("jeff_33", "xs", 922350) + >>> err = e6.get_errorr(errorr_kws=dict(ek=[1e-2, 1e1, 1e3, 1e5, 1.5e7]), err=1)['errorr35'] + >>> datamg = err.get_cov().data + >>> datamg + MAT1 9228 + MT1 18 + E1 (0.01, 10.0] (10.0, 1000.0] (1000.0, 100000.0] (100000.0, 15000000.0] + MAT MT E + 9228 18 (0.01, 10.0] 1.40033e-01 1.22734e-01 2.53846e-01 -3.65421e-02 + (10.0, 1000.0] 1.22734e-01 1.07572e-01 2.22490e-01 -3.20279e-02 + (1000.0, 100000.0] 2.53846e-01 2.22490e-01 4.60196e-01 -6.62479e-02 + (100000.0, 15000000.0] -3.65421e-02 -3.20279e-02 -6.62479e-02 9.54612e-03 + + >>> data = err.get_cov(multigroup=False).data + >>> np.testing.assert_array_equal( + ... data.index.get_level_values("E").unique()[:-1], + ... datamg.index.get_level_values("E").left.unique(), + ... ) + >>> np.testing.assert_array_equal( + ... data.index.get_level_values("E").unique()[1:], + ... datamg.index.get_level_values("E").right.unique(), + ... ) """ eg = self.get_energy_grid() if multigroup: eg = pd.IntervalIndex.from_breaks(eg) + data = [] - for mat, mf, mt in self.filter_by(listmf=[31, 33]).data: - mf33 = sandy.errorr.read_mf33(self, mat, mt) + for mat_, mf_, mt_ in self.filter_by(listmf=[31, 33, 35]).data: + if mt and mt_ not in mt: + continue + mf33 = sandy.errorr.read_mf33(self, mat_, mf_, mt_) + for mt1, cov in mf33["COVS"].items(): + if mt and mt1 not in mt: + continue if not multigroup: # add zero row and column at the end of the matrix # (this must be done for ERRORR covariance matrices) cov = np.insert(cov, cov.shape[0], [0]*cov.shape[1], axis=0) cov = np.insert(cov, cov.shape[1], [0]*cov.shape[0], axis=1) idx = pd.MultiIndex.from_product( - [[mat], [mt], eg], + [[mat_], [mt_], eg], names=["MAT", "MT", "E"], ) idx1 = pd.MultiIndex.from_product( - [[mat], [mt1], eg], + [[mat_], [mt1], eg], names=["MAT1", "MT1", "E1"], ) df = pd.DataFrame(cov, index=idx, columns=idx1) \ @@ -198,23 +239,28 @@ def get_cov(self, multigroup=True): .reset_index() data.append(df) data = pd.concat(data) - return sandy.CategoryCov.from_stack(data, index=["MAT", "MT", "E"], - columns=["MAT1", "MT1", "E1"], - values='VAL') + + out = sandy.CategoryCov.from_stack( + data, + index=["MAT", "MT", "E"], + columns=["MAT1", "MT1", "E1"], + values='VAL', + ) + return out def read_mf1(tape, mat): """ Parse MAT/MF=1/MT=451 section from `sandy.Errorr` object and return structured content in nested dcitionaries. + Parameters ---------- tape : `sandy.Errorr` endf6 object containing requested section mat : `int` MAT number - mt : `int` - MT number + Returns ------- out : `dict` @@ -244,10 +290,11 @@ def read_mf1(tape, mat): return out -def read_mf3(tape, mat, mt): +def read_mf3(tape, mat, mf, mt): """ Parse MAT/MF=33/MT section from `sandy.Errorr` object and return structured content in nested dcitionaries. + Parameters ---------- tape : `sandy.Errorr` @@ -256,12 +303,12 @@ def read_mf3(tape, mat, mt): MAT number mt : `int` MT number + Returns ------- out : `dict` Content of the ENDF-6 tape structured as nested `dict`. """ - mf = 3 df = tape._get_section_df(mat, mf, mt) out = { "MAT": mat, @@ -277,10 +324,11 @@ def read_mf3(tape, mat, mt): return out -def read_mf33(tape, mat, mt): +def read_mf33(tape, mat, mf, mt): """ Parse MAT/MF=33/MT section from `sandy.Errorr` object and return structured content in nested dcitionaries. + Parameters ---------- tape : `sandy.Errorr` @@ -289,12 +337,12 @@ def read_mf33(tape, mat, mt): MAT number mt : `int` MT number + Returns ------- out : `dict` Content of the ENDF-6 tape structured as nested `dict`. """ - mf = 33 df = tape._get_section_df(mat, mf, mt) out = { "MAT": mat, @@ -311,16 +359,16 @@ def read_mf33(tape, mat, mt): reaction_pairs = {} for rp in range(C.N2): # number of reaction pairs C, i = sandy.read_cont(df, i) - MT1 = C.L2 - NG = C.N2 + MT1 = C.L2 # Second reaction MT + NG = C.N2 # Number of rows (first reaction) in matrix M M = np.zeros((NG, NG)) while True: L, i = sandy.read_list(df, i) - NGCOL = L.L1 - GROW = L.N2 - GCOL = L.L2 - M[GROW-1, GCOL-1:GCOL+NGCOL-1] = L.B - if GCOL+NGCOL >= NG and GROW >= NG: + NGCOL = L.L1 # Number of columns (second reaction) in matrix M with non zero-values + GROW = L.N2 # Starting point for rows in matrix M + GCOL = L.L2 # Starting point for columns in matrix M + M[GROW-1, GCOL-1:GCOL+NGCOL-1] = L.B # List values added to NGROW row + if GCOL+NGCOL >= NG and GROW >= NG: # if both 1st and 2nd reaction contain data for all the groups (or it could not work) break reaction_pairs[MT1] = M out["COVS"] = reaction_pairs diff --git a/sandy/groupr.py b/sandy/gendf.py similarity index 81% rename from sandy/groupr.py rename to sandy/gendf.py index b3b64817..2ca5c94e 100644 --- a/sandy/groupr.py +++ b/sandy/gendf.py @@ -6,15 +6,15 @@ __author__ = "Luca Fiorito" __all__ = [ - "Groupr", + "Gendf", ] pd.options.display.float_format = '{:.5e}'.format -class Groupr(_FormattedFile): +class Gendf(_FormattedFile): """ - Container for groupr information grouped by MAT, MF and MT numbers. + Container for gendf information grouped by MAT, MF and MT numbers. """ def get_n_energy_grid(self, **kwargs): @@ -24,21 +24,18 @@ def get_n_energy_grid(self, **kwargs): Returns ------- `np.array` - The energy grid of the `sandy.Groupr` object. + The energy grid of the :func:`~sandy.Gendf` object. Examples -------- >>> endf6 = sandy.get_endf6_file("jeff_33", "xs", 10010) - >>> groupr = endf6.get_gendf(verborse=True) - >>> len(groupr.get_n_energy_grid()) - 241 + >>> gendf = endf6.get_gendf(verborse=True) + >>> assert len(gendf.get_n_energy_grid()) == 241 >>> endf6 = sandy.get_endf6_file("jeff_33", "xs", 10010) - >>> groupr = endf6.get_gendf(verborse=True, ek_groupr=sandy.energy_grids.CASMO12) - >>> groupr.get_n_energy_grid() - array([1.0000e-05, 3.0000e-02, 5.8000e-02, 1.4000e-01, 2.8000e-01, - 3.5000e-01, 6.2500e-01, 4.0000e+00, 4.8052e+01, 5.5300e+03, - 8.2100e+05, 2.2310e+06, 1.0000e+07]) + >>> gendf = endf6.get_gendf(groupr_kws=dict(ek=sandy.energy_grids.CASMO12)) + >>> np.testing.assert_allclose(gendf.get_n_energy_grid(), sandy.energy_grids.CASMO12, atol=1e-14, rtol=1e-14) + >>> np.testing.assert_allclose(gendf.get_n_energy_grid(mat=125), sandy.energy_grids.CASMO12, atol=1e-14, rtol=1e-14) """ mat_ = kwargs.get('mat', self.mat[0]) mf1 = read_mf1(self, mat_) @@ -51,21 +48,14 @@ def get_g_energy_grid(self, **kwargs): Returns ------- `np.array` - The energy grid of the `sandy.Gendf` object. + The energy grid of the :func:`~sandy.Gendf` object. Examples -------- >>> endf6 = sandy.get_endf6_file("jeff_33", "xs", 10010) - >>> groupr = endf6.get_gendf(verborse=True, ep=sandy.energy_grids.CASMO12) - >>> groupr.get_g_energy_grid() - array([1.0000e-05, 3.0000e-02, 5.8000e-02, 1.4000e-01, 2.8000e-01, - 3.5000e-01, 6.2500e-01, 4.0000e+00, 4.8052e+01, 5.5300e+03, - 8.2100e+05, 2.2310e+06, 1.0000e+07]) - - >>> groupr.get_g_energy_grid(mat=125) - array([1.0000e-05, 3.0000e-02, 5.8000e-02, 1.4000e-01, 2.8000e-01, - 3.5000e-01, 6.2500e-01, 4.0000e+00, 4.8052e+01, 5.5300e+03, - 8.2100e+05, 2.2310e+06, 1.0000e+07]) + >>> gendf = endf6.get_gendf(groupr_kws=dict(ep=sandy.energy_grids.CASMO12)) + >>> np.testing.assert_allclose(gendf.get_g_energy_grid(), sandy.energy_grids.CASMO12, atol=1e-14, rtol=1e-14) + >>> np.testing.assert_allclose(gendf.get_g_energy_grid(mat=125), sandy.energy_grids.CASMO12, atol=1e-14, rtol=1e-14) """ mat_ = kwargs.get('mat', self.mat[0]) mf1 = read_mf1(self, mat_) @@ -77,31 +67,31 @@ def get_xs(self, **kwargs): Returns ------- - xs : `sandy.Xs` + xs : :func:`~sandy.Xs` multigroup cross sections Examples -------- >>> endf6 = sandy.get_endf6_file("jeff_33", "xs", 10010) - >>> groupr = endf6.get_gendf(verborse=True, ek_groupr=sandy.energy_grids.CASMO12) - >>> groupr.get_xs() - MAT 125 - MT 1 2 102 - E - (1e-05, 0.03] 4.74500e+01 4.68507e+01 5.99276e-01 - (0.03, 0.058] 2.66592e+01 2.64039e+01 2.55277e-01 - (0.058, 0.14] 2.33852e+01 2.32133e+01 1.71860e-01 - (0.14, 0.28] 2.18356e+01 2.17186e+01 1.17013e-01 - (0.28, 0.35] 2.13559e+01 2.12616e+01 9.43025e-02 - (0.35, 0.625] 2.10611e+01 2.09845e+01 7.66054e-02 - (0.625, 4.0] 2.06169e+01 2.05790e+01 3.79424e-02 - (4.0, 48.052] 2.04594e+01 2.04475e+01 1.18527e-02 - (48.052, 5530.0] 2.00729e+01 2.00716e+01 1.28270e-03 - (5530.0, 821000.0] 8.05819e+00 8.05812e+00 6.41591e-05 - (821000.0, 2231000.0] 3.48869e+00 3.48866e+00 3.54245e-05 - (2231000.0, 10000000.0] 1.52409e+00 1.52406e+00 3.44005e-05 - - >>> groupr.get_xs(mt=1) + >>> gendf = endf6.get_gendf(minimal_processing=True, err=0.005, temperature=293.6, groupr_kws=dict(ek=sandy.energy_grids.CASMO12)) + >>> gendf.get_xs() + MAT 125 + MT 1 2 102 251 + E + (1e-05, 0.03] 4.74500e+01 4.68507e+01 5.99276e-01 6.67348e-01 + (0.03, 0.058] 2.66592e+01 2.64039e+01 2.55277e-01 6.67289e-01 + (0.058, 0.14] 2.33852e+01 2.32133e+01 1.71860e-01 6.67323e-01 + (0.14, 0.28] 2.18356e+01 2.17186e+01 1.17013e-01 6.67259e-01 + (0.28, 0.35] 2.13559e+01 2.12616e+01 9.43025e-02 6.67231e-01 + (0.35, 0.625] 2.10611e+01 2.09845e+01 7.66054e-02 6.67220e-01 + (0.625, 4.0] 2.06169e+01 2.05790e+01 3.79424e-02 6.67215e-01 + (4.0, 48.052] 2.04594e+01 2.04475e+01 1.18527e-02 6.67220e-01 + (48.052, 5530.0] 2.00729e+01 2.00716e+01 1.28270e-03 6.67237e-01 + (5530.0, 821000.0] 8.05819e+00 8.05812e+00 6.41591e-05 6.67120e-01 + (821000.0, 2231000.0] 3.48869e+00 3.48866e+00 3.54245e-05 6.66838e-01 + (2231000.0, 10000000.0] 1.52409e+00 1.52406e+00 3.44005e-05 6.65044e-01 + + >>> gendf.get_xs(mt=1) MAT 125 MT 1 E @@ -118,7 +108,7 @@ def get_xs(self, **kwargs): (821000.0, 2231000.0] 3.48869e+00 (2231000.0, 10000000.0] 1.52409e+00 - >>> groupr.get_xs(mt=[1, 2]) + >>> gendf.get_xs(mt=[1, 2]) MAT 125 MT 1 2 E @@ -135,9 +125,10 @@ def get_xs(self, **kwargs): (821000.0, 2231000.0] 3.48869e+00 3.48866e+00 (2231000.0, 10000000.0] 1.52409e+00 1.52406e+00 + `err=1` or else it takes too long >>> endf6 = sandy.get_endf6_file('jeff_33','xs', 922350) - >>> groupr = endf6.get_gendf(ek_groupr=sandy.energy_grids.CASMO12) - >>> groupr.get_xs(mt=[4, 5]) + >>> gendf = endf6.get_gendf(minimal_processing=True, err=1, groupr_kws=dict(ek=sandy.energy_grids.CASMO12)) + >>> gendf.get_xs(mt=[4, 5]) MAT 9228 MT 4 5 E @@ -165,7 +156,7 @@ def get_xs(self, **kwargs): for mat, mf, mt in self.filter_by(listmf=[3], listmt=listmt_, listmat=listmat_).data: - mf3 = sandy.groupr.read_mf3(self, mat, mt) + mf3 = sandy.gendf.read_mf3(self, mat, mt) lowest_range = mf3["GROUPS"][0]["IG"] - 1 xs = np.array([x["DATA"][1].tolist() for x in mf3["GROUPS"]]) xs = np.insert(xs, [0]*lowest_range, 0) if lowest_range != 0 else xs @@ -188,8 +179,8 @@ def get_flux(self, **kwargs): Examples -------- >>> endf6 = sandy.get_endf6_file("jeff_33", "xs", 10010) - >>> groupr = endf6.get_gendf(verborse=True, ek_groupr=sandy.energy_grids.CASMO12) - >>> groupr.get_flux() + >>> gendf = endf6.get_gendf(minimal_processing=True, err=1, temperature=293.6, groupr_kws=dict(ek=sandy.energy_grids.CASMO12)) + >>> gendf.get_flux() (1e-05, 0.03] 2.99900e-02 (0.03, 0.058] 2.80000e-02 (0.058, 0.14] 8.20000e-02 @@ -204,7 +195,7 @@ def get_flux(self, **kwargs): (2231000.0, 10000000.0] 7.76900e+06 Name: iwt, dtype: float64 - >>> groupr.get_flux(mat=125, mt=2) + >>> gendf.get_flux(mat=125, mt=2) (1e-05, 0.03] 2.99900e-02 (0.03, 0.058] 2.80000e-02 (0.058, 0.14] 8.20000e-02 @@ -238,7 +229,7 @@ def read_mf1(tape, mat): Parameters ---------- - tape : `sandy.Errorr` + tape : :func:`~sandy.Gendf` endf6 object containing requested section mat : `int` MAT number @@ -253,8 +244,8 @@ def read_mf1(tape, mat): Examples -------- >>> endf6 = sandy.get_endf6_file("jeff_33", "xs", 10010) - >>> groupr = endf6.get_gendf(verborse=True, ek_groupr=sandy.energy_grids.CASMO12) - >>> mf1 = sandy.groupr.read_mf1(groupr, 125) + >>> gendf = endf6.get_gendf(groupr_kws=dict(ek=sandy.energy_grids.CASMO12)) + >>> mf1 = sandy.gendf.read_mf1(gendf, 125) >>> mf1['AWR'] = round(mf1['AWR'], 3) >>> mf1 {'MAT': 125, @@ -263,7 +254,7 @@ def read_mf1(tape, mat): 'ZA': 1001.0, 'AWR': 0.999, 'LRP': -1, - 'TEMPIN': 293.6, + 'TEMPIN': 0.0, 'TITLE': [0.0], 'SIGZ': [10000000000.0], 'EGN': array([1.0000e-05, 3.0000e-02, 5.8000e-02, 1.4000e-01, 2.8000e-01, @@ -310,7 +301,7 @@ def read_mf3(tape, mat, mt): Parameters ---------- - tape : `sandy.Errorr` + tape : :func:`~sandy.Gendf` endf6 object containing requested section mat : `int` MAT number @@ -325,8 +316,8 @@ def read_mf3(tape, mat, mt): Examples -------- >>> endf6 = sandy.get_endf6_file("jeff_33", "xs", 10010) - >>> groupr = endf6.get_gendf(verborse=True, ek_groupr=sandy.energy_grids.CASMO12) - >>> sandy.groupr.read_mf3(groupr, 125, 1)['GROUPS'][0] + >>> gendf = endf6.get_gendf(temperature=293.6, err=0.005, minimal_processing=True, groupr_kws=dict(ek=sandy.energy_grids.CASMO12)) + >>> sandy.gendf.read_mf3(gendf, 125, 1)['GROUPS'][0] {'TEMPIN': 293.6, 'NG2': 2, 'IG2LO': 1, diff --git a/sandy/mcnp/output_file.py b/sandy/mcnp/output_file.py index 4c00c81e..8526c63c 100644 --- a/sandy/mcnp/output_file.py +++ b/sandy/mcnp/output_file.py @@ -8,6 +8,7 @@ __all__ = [ "get_keff", + "get_table126", "get_table140", ] @@ -22,14 +23,45 @@ def get_keff(file): return {"KEFF": keff, "ERR": std} +def get_table126(file): + print(f"reading file '{file}'...") + with open(file, 'r') as f: + text = f.read() + + PATTERN = "(?:^1neutron.*table 126\n)(?P(?:.*\n)+?)(?P^\s{11}total\s{2})" + match = re.search(PATTERN, text, re.MULTILINE).group("table") + widths=[9, 10, 12, 14, 13, 14, 13, 13, 13, 13] + dtypes = [int] * 5 + [float] * 5 + + names = pd.read_fwf( + StringIO(match), + widths=widths, + skip_blank_lines=True, + nrows=3, + header=None, + ).fillna("").apply(lambda col: " ".join(col).strip()) + names[0] = "cell index" + + df = pd.read_fwf( + StringIO(match), + widths=widths, + skip_blank_lines=True, + skiprows=4, + names=names, + ).ffill() + return df.astype(dict(zip(names, dtypes))) + + def get_table140(file): print(f"reading file '{file}'...") with open(file, 'r') as f: text = f.read() + PATTERN = "(?:^1neutron.*table 140\n)(?P
(?:.*\n)+?)(?P^\s+total\s{20})" match = re.search(PATTERN, text, re.MULTILINE).group("table") widths=[10, 9, 11, 9,] + [12] * 6 dtypes = [int, int, str, float, int, float, float, float, float, int] + names = pd.read_fwf( StringIO(match), widths=widths, @@ -37,6 +69,7 @@ def get_table140(file): nrows=2, header=None, ).fillna("").apply(lambda col: " ".join(col).strip()) + df = pd.read_fwf( StringIO(match), widths=widths, diff --git a/sandy/njoy.py b/sandy/njoy.py index e21ea0a4..34b9ec9e 100644 --- a/sandy/njoy.py +++ b/sandy/njoy.py @@ -1,77 +1,3 @@ -# -*- coding: utf-8 -*- -""" -Outline -======= -1. Summary_ -2. Examples_ -3. Routines_ - -.. _Summary: - -Summary -======= -This module contains template inputs for NJOY routines and functions to run them. - -Two major functions `process` and `process_protons` are provided to process nuclear data -files with NJOY into ACE format, respectively for fast neutron-induced and proton-induced -nuclear data. - -Given any nuclear data evaluation file for incident neutrons (fast, not SAB) function `process` -generates the correspoding ACE filea for a given set of temperatures (one file per temperature). -If no keyword argument is provided, function `process` runs with default options, which include -NJOY routines RECONR, BROADR, THERMR, HEATR, GASPR, PURR, ACER. -Keyword arguments can be changed to add/remove NJOY routines using `True/False` flags, or to change -a routine's input parameters. - -Major default parmameters: - -+------------------+-----------------------------------------------------------+------------------------------+ -| Parameter | Value | Description | -+==================+===========================================================+==============================+ -| err | `0.001` | xs reconstruction tolerance | -+------------------+-----------------------------------------------------------+------------------------------+ -| temperatures | `[293.6]` | `list` of temperatures (K) | -+------------------+-----------------------------------------------------------+------------------------------+ -| bins | `20` | # probability bins (PURR) | -+------------------+-----------------------------------------------------------+------------------------------+ -| ladders | `32` | # resonance ladders (PURR) | -+------------------+-----------------------------------------------------------+------------------------------+ -| iprint | `False` | output verbosity | -+------------------+-----------------------------------------------------------+------------------------------+ -| kermas | `[302, 303, 304, 318, 402, 442, 443, 444, 445, 446, 447]` | `list` of KERMA factors (MT) | -+------------------+-----------------------------------------------------------+------------------------------+ - -.. _Examples: - -Examples -======== - -Extract njoy executable ------------------------ - -#>>> import sandy -#>>> exe = sandy.get_njoy() - -It raises an error if the system environment variable `NJOY` is not set. - -Default njoy processing of a neutron file ------------------------------------------ -Process a ENDF-6 neutron file "my_file.endf6" using NJOY with default options - -#>>> import sandy.njoy -#>>> endftape = "my_file.endf6" -#>>> input, inputs, outputs = sandy.njoy.process(endftape) - - -.. _Routines: - -Routines -======== - -* get_njoy -* process -* process_proton -""" import os from os.path import join @@ -79,17 +5,20 @@ import re import logging import pdb -import tempfile +from tempfile import TemporaryDirectory import subprocess as sp import pandas as pd import numpy as np import sandy -from sandy.settings import SandyError __author__ = "Luca Fiorito" -__all__ = ["process", "process_proton", "get_njoy"] +__all__ = [ + "process_neutron", + "process_proton", + "get_njoy", + ] sab = pd.DataFrame.from_records([[48,9237,1,1,241,'uuo2'], [42,125,0,8,221,'tol'], @@ -139,16 +68,9 @@ 1300: "13", 1400: "14", 1500: "15", - 1600: "16", - 1700: "17", 1800: "18", - 1900: "19", - 2000: "20", 2100: "21", - 2200: "22", - 2300: "23", 2400: "24", - 2500: "25", } tmp2ext_meta = { @@ -175,10 +97,7 @@ 1500: "52", 1800: "53", 2100: "54", - 2200: "56", - 2300: "57", - 2400: "58", - 2500: "59", + 2400: "55", } @@ -196,51 +115,117 @@ def get_njoy(): ------- `string` njoy executable - - Raises - ------ - `SandyError` - if environment variable `NJOY` is not assigned """ - if "NJOY" in os.environ: - exe = os.environ["NJOY"] - else: - raise ValueError("environment variable 'NJOY' is not assigned") - return exe + return os.environ["NJOY"] -def get_suffix(temp, meta, method=None): +def get_temperature_suffix(temperature, meta=False): """ Determine suffix saccording to temperature value. - + The following table is used, in line with the ALEPH manual. + + | | EXT | META | + |:-----------------|------:|-------:| + | [275.0, 325.0) | 03 | 31 | + | [325.0, 375.0) | 35 | 32 | + | [375.0, 425.0) | 04 | 33 | + | [425.0, 475.0) | 45 | 34 | + | [475.0, 525.0) | 05 | 36 | + | [525.0, 575.0) | 55 | 37 | + | [575.0, 625.0) | 06 | 38 | + | [625.0, 675.0) | 65 | 39 | + | [675.0, 725.0) | 07 | 40 | + | [725.0, 775.0) | 75 | 41 | + | [775.0, 825.0) | 08 | 42 | + | [825.0, 875.0) | 85 | 43 | + | [875.0, 925.0) | 09 | 44 | + | [925.0, 975.0) | 95 | 46 | + | [975.0, 1050.0) | 10 | 47 | + | [1050.0, 1150.0) | 11 | 48 | + | [1150.0, 1250.0) | 12 | 49 | + | [1250.0, 1350.0) | 13 | 50 | + | [1350.0, 1450.0) | 14 | 51 | + | [1450.0, 1650.0) | 15 | 52 | + | [1650.0, 1950.0) | 18 | 53 | + | [1950.0, 2250.0) | 21 | 54 | + + If temperature is outside the interval range given above, suffix `'00'` is + returned. + If temperature is 293.6 K, suffix `'02'` and `'30'` are returned + respectively for ground and meta states. + Parameters ---------- - temp : `float` + temperature : `float` processing temperature - meta : `int` - metastate number - method : `str`, optional, default `None` - use `method="aleph"` to treat metastate extensions using ALEPH rules - - Raise - ----- - `ValueError` - if extension was not found for given temperature + meta : `bool`, optional, default is `True` + `True` if metastable (any), `False` if ground level Returns ------- `str` suffix + + Examples + -------- + Test temperatures outside range + >>> assert get_temperature_suffix(3000, True) == get_temperature_suffix(3000, False) + >>> assert get_temperature_suffix(3000, True) == get_temperature_suffix(0, True) + >>> assert get_temperature_suffix(3000, True) == get_temperature_suffix(0, False) + + Test reference ALEPH temperatures for ground and meta states + >>> for k, v in sandy.njoy.tmp2ext.items(): + ... assert v == get_temperature_suffix(k) + ... assert v == get_temperature_suffix(k, 0) + + >>> for k, v in sandy.njoy.tmp2ext_meta.items(): + ... assert v == get_temperature_suffix(k, meta=True) + ... assert v == get_temperature_suffix(k, 1) + ... assert v == get_temperature_suffix(k, 2) """ - dct = tmp2ext_meta if meta and method == "aleph" else tmp2ext - if temp in dct: - temp_in_dict = temp + closed = "left" + + if temperature == 293.6: + return "30" if meta else "02" + + # Up to 1000 K temperatures are split every 50 degrees. + splitter = 50 + idx = pd.IntervalIndex.from_breaks(np.arange(300-splitter/2, 1000+splitter/2, splitter).tolist() + [(1100+1000)/2], closed=closed) + suffix1 = pd.DataFrame({ + "EXT": ["03", "35", "04", "45", "05", "55", "06", "65", "07", "75", "08", "85", "09", "95", "10"], + "META": ["31", "32", "33", "34", "36", "37", "38", "39", "40", "41", "42", "43", "44", "46", "47"], + }, index=idx) + + # Between 1000 K and 1500 K temperatures are split every 100 degrees. + splitter = 100 + idx = pd.IntervalIndex.from_breaks(np.arange(1100-splitter/2, 1500+splitter/2, splitter).tolist() + [(1800+1500)/2], closed=closed) + suffix2 = pd.DataFrame({ + "EXT": ["11", "12", "13", "14", "15"], + "META": ["48", "49", "50", "51", "52"], + }, index=idx) + suffix = pd.concat([suffix1, suffix2]) + + # Between 1500 K and 2400 K temperatures are split every 300 degrees. + splitter = 300 + idx = pd.IntervalIndex.from_breaks(np.arange(1800-splitter/2, 2400+splitter/2, splitter).tolist() + [2400+splitter/2], closed=closed) + suffix3 = pd.DataFrame({ + "EXT": ["18", "21", "24"], + "META": ["53", "54", "55"], + }, index=idx) + + suffix = pd.concat([suffix1, suffix2, suffix3]) + + mask = suffix.index.contains(temperature) + if suffix[mask].empty: + suff = "00" + msg = f"extension '{suff}' will be used for temperature '{temperature}'" + logging.warning(msg) else: - splitter = 50 if temp < 1000 else 100 - temp_in_dict = int(round(temp / splitter) * splitter) - if temp_in_dict not in dct: - raise ValueError(f"extension was not found for temperature '{temp}'") - return dct[temp_in_dict] + if meta: + suff = suffix[mask].META.squeeze() + else: + suff = suffix[mask].EXT.squeeze() + return suff def _moder_input(nin, nout, **kwargs): @@ -279,10 +264,10 @@ def _reconr_input(endfin, pendfout, mat, tape number for output PENDF file mat : `int` MAT number - header : `str` - file header (default is "sandy runs njoy") err : `float` tolerance (default is 0.001) + header : `str` + file header (default is "sandy runs njoy") Returns ------- @@ -315,10 +300,10 @@ def _broadr_input(endfin, pendfin, pendfout, mat, tape number for output PENDF file mat : `int` MAT number - temperatures : iterable of `float` - iterable of temperature values in K (default is 293.6 K) err : `float` tolerance (default is 0.001) + temperatures : iterable of `float` + iterable of temperature values in K (default is 293.6 K) Returns ------- @@ -535,10 +520,10 @@ def _heatr_input(endfin, pendfin, pendfout, mat, pks, def _acer_input(endfin, pendfin, aceout, dirout, mat, - temp=NJOY_TEMPERATURES[0], + temperature=NJOY_TEMPERATURES[0], iprint=False, itype=1, - suff=".00", + suffix=".00", header="sandy runs acer", photons=True, **kwargs): @@ -557,21 +542,19 @@ def _acer_input(endfin, pendfin, aceout, dirout, mat, tape number for output ACE file mat : `int` MAT number - temp : `float` - temperature in K (default is 293.6 K) - local : `bool` - option to deposit gamma rays locally (default is `False`) + header : `str` + descriptive character string of max. 70 characters + (default is "sandy runs acer") iprint : `bool` print option (default is `False`) itype : `int` ace output type: 1, 2, or 3 (default is 1) - suff : `str` - id suffix for zaid (default is ".00") - header : `str` - descriptive character string of max. 70 characters - (default is "sandy runs acer") photons : `bool` detailed photons (default is `True`) + suffix : `str` + id suffix for zaid (default is ".00") + temperature : `float` + temperature in K (default is 293.6 K) Returns ------- @@ -581,9 +564,9 @@ def _acer_input(endfin, pendfin, aceout, dirout, mat, text = ["acer"] text += [f"{endfin:d} {pendfin:d} 0 {aceout:d} {dirout:d} /"] printflag = int(iprint) - text += [f"1 {printflag:d} {itype:d} {suff} 0 /"] + text += [f"1 {printflag:d} {itype:d} {suffix} 0 /"] text += [f"'{header}'/"] - text += [f"{mat:d} {temp:.1f} /"] + text += [f"{mat:d} {temperature:.1f} /"] photonsflag = int(photons) text += [f"1 {photonsflag:d} /"] text += ["/"] @@ -591,10 +574,10 @@ def _acer_input(endfin, pendfin, aceout, dirout, mat, def _errorr_input(endfin, pendfin, gendfin, errorrout, mat, - ign_errorr=2, ek_errorr=None, spectrum_errorr=None, - iwt_errorr=2, relative=True, - mt=None, irespr=1, - temp=NJOY_TEMPERATURES[0], mfcov=33, + ign=2, ek=None, spectrum=None, + iwt=2, relative=True, + mt=None, irespr=1, ifissp=-1, efmean=None, + temperature=NJOY_TEMPERATURES[0], mfcov=33, iprint=False, **kwargs): """ @@ -612,32 +595,49 @@ def _errorr_input(endfin, pendfin, gendfin, errorrout, mat, tape number for output ERRORR file mat : `int` MAT number - ek_errorr : iterable, optional + ek : iterable, optional derived cross section energy bounds (default is None) - ign_errorr : `int`, optional + ign : `int`, optional neutron group option (default is 2, csewg 239-group structure) + + .. note:: this parameter will not be used if keyword argument + `ek` is provided. + iprint : `bool`, optional print option (default is `False`) irespr: `int`, optional processing for resonance parameter covariances (default is 1, 1% sensitivity method) - iwt_errorr : `int`, optional + ifissp: `int`, subsection of the fission spectrum covariance + matrix to process. + efmean: incident neutron energy (eV). Process the covar- + iance matrix subsection whose energy interval in- + cludes efmean. But if there is only + one subsection, process it and if the input efmean + was not within this subsection’s energy range then + redefine efmean to equal the average energy for + this subsection. + iwt : `int`, optional weight function option (default is 2, constant) - + .. note:: this parameter will not be used if keyword argument - `spect` is provided + `spect` is provided. relative: `bool` use relative covariance form (default is `True`) - temp : `float`, optional - temperature in K (default is 293.6 K) mfcov : `int` endf covariance file to be processed (default is 33) - mt: `int` or iterable of `int`, optional + mt : `int` or iterable of `int`, optional run errorr only for the selected mt numbers (default is `None`, i.e., process all MT) - spectrum_errorr : iterable, optional + + .. note:: this parameter will not be used if keyword argument + `mfcov!=33`. + + spectrum : iterable, optional weight function (default is `None`) + temperature : `float`, optional + temperature in K (default is 293.6 K) Returns ------- @@ -652,41 +652,41 @@ def _errorr_input(endfin, pendfin, gendfin, errorrout, mat, 20 21 0 22 0 / 9237 2 2 0 1 / 0 293.6 / - 0 33 1/ + 0 33 1 1 -1 / - Test argument `temp` - >>> print(sandy.njoy._errorr_input(20, 21, 0, 22, 9440, temp=600)) + Test argument `temperature` + >>> print(sandy.njoy._errorr_input(20, 21, 0, 22, 9440, temperature=600)) errorr 20 21 0 22 0 / 9440 2 2 0 1 / 0 600.0 / - 0 33 1/ + 0 33 1 1 -1 / - Test argument `iwt_errorr` - >>> print(sandy.njoy._errorr_input(20, 21, 0, 22, 9237, iwt_errorr=6)) + Test argument `iwt` + >>> print(sandy.njoy._errorr_input(20, 21, 0, 22, 9237, iwt=6)) errorr 20 21 0 22 0 / 9237 2 6 0 1 / 0 293.6 / - 0 33 1/ + 0 33 1 1 -1 / - Test argument `ek_errorr` - >>> print(sandy.njoy._errorr_input(20, 21, 0, 22, 9237, ek_errorr=[1e-2, 1e3, 2e5])) + Test argument `ek` + >>> print(sandy.njoy._errorr_input(20, 21, 0, 22, 9237, ek=[1e-2, 1e3, 2e5])) errorr 20 21 0 22 0 / 9237 1 2 0 1 / 0 293.6 / - 0 33 1/ + 0 33 1 1 -1 / 2 / 1.00000e-02 1.00000e+03 2.00000e+05 / - Test argument `ign_errorr` - >>> print(sandy.njoy._errorr_input(20, 21, 0, 22, 9237, ign_errorr=3)) + Test argument `ign` + >>> print(sandy.njoy._errorr_input(20, 21, 0, 22, 9237, ign=3)) errorr 20 21 0 22 0 / 9237 3 2 0 1 / 0 293.6 / - 0 33 1/ + 0 33 1 1 -1 / Test nubar >>> print(sandy.njoy._errorr_input(20, 21, 0, 22, 9237, mfcov=31)) @@ -694,7 +694,7 @@ def _errorr_input(endfin, pendfin, gendfin, errorrout, mat, 20 21 0 22 0 / 9237 2 2 0 1 / 0 293.6 / - 0 31 1/ + 0 31 1 1 -1 / Test mubar >>> print(sandy.njoy._errorr_input(20, 21, 0, 22, 9237, mfcov=34)) @@ -702,7 +702,7 @@ def _errorr_input(endfin, pendfin, gendfin, errorrout, mat, 20 21 0 22 0 / 9237 2 2 0 1 / 0 293.6 / - 0 34 1/ + 0 34 1 1 -1 / Test chi >>> print(sandy.njoy._errorr_input(20, 21, 0, 22, 9237, mfcov=35)) @@ -710,15 +710,7 @@ def _errorr_input(endfin, pendfin, gendfin, errorrout, mat, 20 21 0 22 0 / 9237 2 2 0 1 / 0 293.6 / - 0 35 1/ - - Test radioactive nuclide production - >>> print(sandy.njoy._errorr_input(20, 21, 0, 22, 9237, mfcov=40)) - errorr - 20 21 0 22 0 / - 9237 2 2 0 1 / - 0 293.6 / - 0 40 1/ + 0 35 1 1 -1 / Test keyword `relative` >>> print(sandy.njoy._errorr_input(20, 21, 0, 22, 9237, relative=False)) @@ -726,7 +718,7 @@ def _errorr_input(endfin, pendfin, gendfin, errorrout, mat, 20 21 0 22 0 / 9237 2 2 0 0 / 0 293.6 / - 0 33 1/ + 0 33 1 1 -1 / Test keyword `irespr` >>> print(sandy.njoy._errorr_input(20, 21, 0, 22, 9237, irespr=1)) @@ -734,7 +726,7 @@ def _errorr_input(endfin, pendfin, gendfin, errorrout, mat, 20 21 0 22 0 / 9237 2 2 0 1 / 0 293.6 / - 0 33 1/ + 0 33 1 1 -1 / Test keyword `mt` as `list` >>> print(sandy.njoy._errorr_input(20, 21, 0, 22, 9237, mt=[1, 2])) @@ -742,9 +734,9 @@ def _errorr_input(endfin, pendfin, gendfin, errorrout, mat, 20 21 0 22 0 / 9237 2 2 0 1 / 0 293.6 / - 1 33 1/ + 1 33 1 1 -1 / 2 0 / - 1 2 / + 1 2 / Test keyword `mt` as `int`: >>> print(sandy.njoy._errorr_input(20, 21, 0, 22, 9237, mt=2)) @@ -752,47 +744,74 @@ def _errorr_input(endfin, pendfin, gendfin, errorrout, mat, 20 21 0 22 0 / 9237 2 2 0 1 / 0 293.6 / - 1 33 1/ + 1 33 1 1 -1 / 1 0 / - 2 / + 2 / + + Test specific incident energy (1e6 eV) for ERRORR input + >>> print(sandy.njoy._errorr_input(-21, 0, -39, 35, 9228, efmean=1e6)) + errorr + -21 0 -39 35 0 / + 9228 2 2 0 1 / + 0 293.6 / + 0 33 1 1 -1 1000000.0 / + + Test ifissp without mean energy + >>> print(sandy.njoy._errorr_input(-21, 0, -39, 35, 9228, ifissp=1)) + errorr + -21 0 -39 35 0 / + 9228 2 2 0 1 / + 0 293.6 / + 0 33 1 1 1 / + + Test with both efmean and ifissp + >>> print(sandy.njoy._errorr_input(-21, 0, -39, 35, 9228, ifissp=1, efmean=1e5)) + errorr + -21 0 -39 35 0 / + 9228 2 2 0 1 / + 0 293.6 / + 0 33 1 1 1 100000.0 / """ irelco = 0 if relative is False else 1 - iread = 1 if mt is not None else 0 - iwt_ = 1 if spectrum_errorr is not None else iwt_errorr - ign_ = 1 if ek_errorr is not None else ign_errorr + iread = 1 if (mt is not None and mfcov == 33) else 0 + iwt_ = 1 if spectrum is not None else iwt + ign_ = 1 if ek is not None else ign text = ["errorr"] text += [f"{endfin:d} {pendfin:d} {gendfin:d} {errorrout:d} 0 /"] printflag = int(iprint) text += [f"{mat:d} {ign_:d} {iwt_:d} {printflag:d} {irelco} /"] - text += [f"{printflag:d} {temp:.1f} /"] - text += [f"{iread:d} {mfcov} {irespr:d}/"] + text += [f"{printflag:d} {temperature:.1f} /"] + if efmean: + text += [f"{iread:d} {mfcov} {irespr:d} 1 {ifissp} {efmean} /"] # Default for legord is one + else: + text += [f"{iread:d} {mfcov} {irespr:d} 1 {ifissp} /"] if iread == 1: # only specific mts mtlist = [mt] if isinstance(mt, int) else mt nmt = len(mtlist) text += [f"{nmt:d} 0 /"] text += [" ".join(map(str, mtlist)) + " /"] if ign_ == 1: - nk = len(ek_errorr) - 1 + nk = len(ek) - 1 text += [f"{nk} /"] - text += [" ".join(map("{:.5e}".format, ek_errorr)) + " /"] + text += [" ".join(map("{:.5e}".format, ek)) + " /"] if iwt_ == 1: INT = 1 # constant interpolation - NBT = int(len(spectrum_errorr) / 2) # only 1 interpolation group + NBT = int(len(spectrum) / 2) # only 1 interpolation group tab1 = "\n".join(sandy.write_tab1(0, 0, 0, 0, [NBT], [INT], - spectrum_errorr[::2], - spectrum_errorr[1::2])) + spectrum[::2], + spectrum[1::2])) text += [tab1] text += ["/"] return "\n".join(text) + "\n" def _groupr_input(endfin, pendfin, gendfout, mat, - ign_groupr=2, ek_groupr=None, igg=0, ep=None, - iwt_groupr=2, lord=0, sigz=[1e+10], - temp=NJOY_TEMPERATURES[0], - spectrum_groupr=None, mt=None, - iprint=False, nubar=False, mubar=False, chi=False, - nuclide_production=False, + ign=2, ek=None, igg=0, ep=None, + iwt=2, lord=0, sigz=[1e+10], + temperature=NJOY_TEMPERATURES[0], + spectrum=None, mt=None, + iprint=False, + mubar=False, chi=False, nubar=False, **kwargs): """ Write GROUPR input @@ -809,21 +828,21 @@ def _groupr_input(endfin, pendfin, gendfout, mat, MAT number chi : `bool`, optional Process chi (default is `False`) - ek_groupr : iterable, optional + ek : iterable, optional derived cross section energy bounds (default is None) ep : iterable, optional derived gamma cross section energy bounds (default is None) igg : `int`, optional gamma group option (default is 0, no structure) - ign_groupr : `int`, optional + ign : `int`, optional neutron group option (default is 2, csewg 239-group structure) iprint : `bool`, optional print option (default is `False`) - iwt_groupr : `int`, optional + iwt : `int`, optional weight function option (default is 2, constant) .. note:: this parameter will not be used if keyword argument - `spect` is provided + `spectrum` is provided lord : `int`, optional Legendre order (default is 0) @@ -838,9 +857,9 @@ def _groupr_input(endfin, pendfin, gendfout, mat, process MF10 (default is `False`) sigz : iterable of `float` sigma zero values (he default is 1.0e10) - spectrum_groupr : iterable, optional + spectrum : iterable, optional weight function (default is `None`) - temp : iterable of `float` + temperature : iterable of `float` iterable of temperature values in K (default is 293.6 K) Returns @@ -862,8 +881,8 @@ def _groupr_input(endfin, pendfin, gendfout, mat, 0/ 0/ - Test argument `temp` - >>> print(sandy.njoy._groupr_input(20, 21, 22, 9440, temp=600)) + Test argument `temperature` + >>> print(sandy.njoy._groupr_input(20, 21, 22, 9440, temperature=600)) groupr 20 21 0 22 / 9440 2 0 2 0 1 1 0 / @@ -874,8 +893,8 @@ def _groupr_input(endfin, pendfin, gendfout, mat, 0/ 0/ - Test argument `iwt_groupr` - >>> print(sandy.njoy._groupr_input(20, 21, 22, 9237, iwt_groupr=6)) + Test argument `iwt` + >>> print(sandy.njoy._groupr_input(20, 21, 22, 9237, iwt=6)) groupr 20 21 0 22 / 9237 2 0 6 0 1 1 0 / @@ -886,8 +905,8 @@ def _groupr_input(endfin, pendfin, gendfout, mat, 0/ 0/ - Test argument `ign_groupr` - >>> print(sandy.njoy._groupr_input(20, 21, 22, 9237, ign_groupr=3)) + Test argument `ign` + >>> print(sandy.njoy._groupr_input(20, 21, 22, 9237, ign=3)) groupr 20 21 0 22 / 9237 3 0 2 0 1 1 0 / @@ -910,8 +929,8 @@ def _groupr_input(endfin, pendfin, gendfout, mat, 0/ 0/ - Test argument `ek_groupr` - >>> print(sandy.njoy._groupr_input(20, 21, 0, 22, 9237, ek_groupr=[1e-2, 1e3, 2e5])) + Test argument `ek` + >>> print(sandy.njoy._groupr_input(20, 21, 0, 22, 9237, ek=[1e-2, 1e3, 2e5])) groupr 20 21 0 0 / 22 1 0 2 0 1 1 0 / @@ -959,7 +978,7 @@ def _groupr_input(endfin, pendfin, gendfout, mat, 293.6/ 10000000000.0/ 3/ - 3 251 'mubar' / + 3 251 / 0/ 0/ @@ -973,20 +992,7 @@ def _groupr_input(endfin, pendfin, gendfout, mat, 10000000000.0/ 3/ 5/ - 5 18 'chi' / - 0/ - 0/ - - Test radioactive nuclide production: - >>> print(sandy.njoy._groupr_input(20, 21, 22, 9237, nuclide_production=True)) - groupr - 20 21 0 22 / - 9237 2 0 2 0 1 1 0 / - 'sandy runs groupr' / - 293.6/ - 10000000000.0/ - 3/ - 10/ + 5 18 / 0/ 0/ @@ -1015,52 +1021,56 @@ def _groupr_input(endfin, pendfin, gendfout, mat, 0/ 0/ """ - iwt_ = 1 if spectrum_groupr is not None else iwt_groupr - ign_ = 1 if ek_groupr is not None else ign_groupr + iwt_ = 1 if spectrum is not None else iwt + ign_ = 1 if ek is not None else ign igg_ = 1 if ep is not None else igg + sigzlist = sigz if hasattr(sigz, "__len__") else [sigz] text = ["groupr"] text += [f"{endfin:d} {pendfin:d} 0 {gendfout:d} /"] - nsigz = len(sigz) + nsigz = len(sigzlist) printflag = int(iprint) text += [f"{mat:d} {ign_:d} {igg_:d} {iwt_:d} {lord:d} 1 {nsigz:d} {printflag:d} /"] text += ["'sandy runs groupr' /"] # run label - text += [f"{temp:.1f}/"] - text += [" ".join(map("{:.1f}".format, sigz)) + "/"] + text += [f"{temperature:.1f}/"] + text += [" ".join(map("{:.1f}".format, sigzlist)) + "/"] if ign_ == 1: - nk = len(ek_groupr) - 1 + nk = len(ek) - 1 text += [f"{nk} /"] - text += [" ".join(map("{:.5e}".format, ek_groupr)) + " /"] + text += [" ".join(map("{:.5e}".format, ek)) + " /"] if igg_ == 1: pk = len(ep) - 1 text += [f"{pk} /"] text += [" ".join(map("{:.5e}".format, ep)) + " /"] if iwt_ == 1: INT = 1 # constant interpolation - NBT = int(len(spectrum_groupr) / 2) # only 1 interpolation group + NBT = int(len(spectrum) / 2) # only 1 interpolation group tab1 = "\n".join(sandy.write_tab1(0, 0, 0, 0, [NBT], [INT], - spectrum_groupr[::2], - spectrum_groupr[1::2])) + spectrum[::2], + spectrum[1::2])) text += [tab1] text += ["/"] + if mt is None: text += ["3/"] # by default process all cross sections (MF=3) else: - mtlist = [mt] if isinstance(mt, int) else mt + mtlist = mt if hasattr(mt, "__len__") else [mt] for mt_ in mtlist: text += [f"3 {mt_:d} /"] + if nubar: + text += [f"3 452 /"] + text += [f"3 455 /"] + text += [f"3 456 /"] if mubar: - text += ["3 251 'mubar' /"] + text += [f"3 251 /"] if chi: text += ["5/"] - text += ["5 18 'chi' /"] - if nuclide_production: - text += ["10/"] - text += ["0/"] # terimnate list of reactions for this material + text += ["5 18 /"] + text += ["0/"] # terminate list of reactions for this material text += ["0/"] # terminate materials (only 1 allowed) return "\n".join(text) + "\n" -def _run_njoy(text, inputs, outputs, exe=None): +def _run_njoy(text, endf, pendf=None, exe=None): """ Run njoy executable for given input. @@ -1077,13 +1087,12 @@ def _run_njoy(text, inputs, outputs, exe=None): """ if exe is None: exe = get_njoy() - logging.debug("Use NJOY executable '{}'".format(exe)) stdout = stderr = None stdin = text.encode() - with tempfile.TemporaryDirectory() as tmpdir: - logging.debug("Create temporary directory '{}'".format(tmpdir)) - for tape, src in inputs.items(): - shutil.copy(src, os.path.join(tmpdir, tape)) + with TemporaryDirectory() as tmpdir: + shutil.copy(endf, os.path.join(tmpdir, "tape20")) + if pendf: + shutil.copy(pendf, os.path.join(tmpdir, "tape99")) process = sp.Popen(exe, shell=True, cwd=tmpdir, @@ -1096,60 +1105,75 @@ def _run_njoy(text, inputs, outputs, exe=None): retrn = process.returncode if retrn != 0: msg = f"process status={retrn}, cannot run njoy executable" - raise SandyError(msg) - for tape, dst in outputs.items(): - path = os.path.split(dst)[0] - if path: - os.makedirs(path, exist_ok=True) - shutil.move(os.path.join(tmpdir, tape), dst) - - -def process( - endftape, - pendftape=None, - kermas=[302, 303, 304, 318, 402, 442, 443, 444, 445, 446, 447], - temperatures=[293.6], - suffixes=None, + raise ValueError(msg) + + # Move outputs + tapes = { + 30: "pendf", + 40: "gendf", + 31: "errorr31", + 33: "errorr33", + 34: "errorr34", + 35: "errorr35", + } + outputs = {} + for k, v in tapes.items(): + out = os.path.join(tmpdir, f"tape{k}") + if os.path.exists(out): + with open(out, mode="r") as f: + outputs[v] = f.read() + text = "" + for k in range(50, 70): + out = os.path.join(tmpdir, f"tape{k}") + if os.path.exists(out): + with open(out, mode="r") as f: + text += f.read() + if text: + outputs["ace"] = text + text = "" + for k in range(70, 90): + out = os.path.join(tmpdir, f"tape{k}") + if os.path.exists(out): + with open(out, mode="r") as f: + text += f.read() + if text: + outputs["xsdir"] = text + return outputs + + +def _prepare_njoy_input( + mat, temperatures, suffixes, + acer=True, broadr=True, - thermr=True, - unresr=False, - heatr=True, gaspr=True, - purr=True, - errorr=False, groupr=False, - acer=True, - wdir="", - dryrun=False, - tag="", - method=None, - exe=None, - keep_pendf=True, - route="0", - addpath=None, - verbose=False, + heatr=True, + purr=True, + reconr=True, + thermr=True, + unresr=False, + errorr33=False, + errorr31=False, + errorr34=False, + errorr35=False, + acer_kws={}, + broadr_kws={}, + gaspr_kws={}, + groupr_kws={}, + heatr_kws={}, + purr_kws={}, + reconr_kws={}, + thermr_kws={}, + unresr_kws={}, + errorr31_kws={}, + errorr33_kws={}, + errorr34_kws={}, + errorr35_kws={}, **kwargs, ): """ - Run sequence to process file with njoy. - Parameters ---------- - pendftape : `str`, optional, default is `None` - name (with absolute of relative path) of a pendf file. - If given, skip module reconr and use this PENDF file, else run reconr - kermas : iterable of `int`, optional, default is - `[302, 303, 304, 318, 402, 442, 443, 444, 445, 446, 447]` - MT numbers for partial kermas to pass to heatr. - .. note:: `MT=301` is the KERMA total (energy balance) and is - always calculated - temperatures : iterable of `float`, optional, default is [293.6] - iterable of temperature values in K - suffixes : iterable of `int`, optional, default is `None` - iterable of suffix values for ACE files: if `None` is given, - use internal routine to determine suffixes - .. warning:: `suffixes` must match the number of entries in - `temperatures` broadr : `bool`, optional, default is `True` option to run module broadr thermr : `bool`, optional, default is `True` @@ -1168,219 +1192,211 @@ def process( option to run module errorr acer : `bool`, optional, default is `True` option to run module acer - wdir : `str`, optional, default is `""` - working directory (absolute or relative) where all output files are - saved - .. note:: `wdir` will appear as part of the `filename` in any - `xsdir` file if `addpath` is not set - addpath : `str`, optional, default is `None` - path to add in xsdir, by default use `wdir` - dryrun : `bool`, optional, default is `False` - option to produce the njoy input file without running njoy - tag : `str`, optional, default is `""` - tag to append to each output filename before the extension - (default is `None`) - .. hint:: to process JEFF-3.3 files you could set `tag = "_j33"` - exe : `str`, optional, default is `None` - njoy executable (with path) - .. note:: if no executable is given, SANDY looks for a default - executable in `PATH` and in env variable `NJOY` - keep_pendf : `bool`, optional, default is `True` - save output PENDF file - route : `str`, optional, default is `0` - xsdir "route" parameter - verbose : `bool`, optional, default is `False` - flag to print NJOY input to screen before running the executable - Returns - ------- - input : `str` - njoy input text - inputs : `map` - map of {`tape` : `file`) for input files - outputs : `map` - map of {`tape` : `file`) for ouptut files + Notes + ----- + .. note:: the four calls to NJOY module ERRORR (for MF31, MF33, MF34 + and MF35) are treated as if four different NJOY modules were + to be run. """ - tape = sandy.Endf6.from_file(endftape) - mat = tape.mat[0] - info = tape.read_section(mat, 1, 451) - meta = info["LISO"] - za = int(info["ZA"]) - zam = za*10 + meta - za_new = za + meta*100 + 300 if meta else za - outprefix = zam if method == "aleph" else za_new - inputs = {} - outputs = {} - # Only kwargs are passed to NJOY inputs, then add temperatures and mat - kwargs.update({ - "temperatures": temperatures, - "mat": mat, - }) - # Check input args - if not suffixes: - suffixes = [get_suffix(temp, meta, method) for temp in temperatures] - if len(suffixes) != len(temperatures): - msg = "number of suffixes must match number of temperatures" - raise ValueError(msg) - inputs["tape20"] = endftape + e = 21 p = e + 1 text = _moder_input(20, -e) - if pendftape: - inputs["tape99"] = pendftape - text += _moder_input(99, -p) + + # this part produces a single PENDF file + if reconr: + text += _reconr_input(-e, -p, + mat=mat, temperatures=temperatures, + **reconr_kws) else: - text += _reconr_input(-e, -p, **kwargs) + text += _moder_input(99, -p) if broadr: o = p + 1 - text += _broadr_input(-e, -p, -o, **kwargs) + text += _broadr_input(-e, -p, -o, + mat=mat, temperatures=temperatures, + **broadr_kws) p = o if thermr: o = p + 1 - text += _thermr_input(0, -p, -o, **kwargs) + text += _thermr_input(0, -p, -o, + mat=mat, temperatures=temperatures, + **thermr_kws) p = o if unresr: o = p + 1 - text += _unresr_input(-e, -p, -o, **kwargs) + text += _unresr_input(-e, -p, -o, + mat=mat, temperatures=temperatures, + **unresr_kws) p = o if heatr: + kermas=[302, 303, 304, 318, 402, 442, 443, 444, 445, 446, 447] for i in range(0, len(kermas), 7): o = p + 1 - kwargs["pks"] = kermas[i:i+7] - text += _heatr_input(-e, -p, -o, **kwargs) + pks = kermas[i:i+7] + text += _heatr_input(-e, -p, -o, + mat=mat, temperatures=temperatures, pks=pks, + **heatr_kws) p = o if gaspr: o = p + 1 - text += _gaspr_input(-e, -p, -o, **kwargs) + text += _gaspr_input(-e, -p, -o, + mat=mat, temperatures=temperatures, + **gaspr_kws) p = o if purr: o = p + 1 - text += _purr_input(-e, -p, -o, **kwargs) + text += _purr_input(-e, -p, -o, + mat=mat, temperatures=temperatures, + **purr_kws) p = o - if keep_pendf: - o = 30 - text += _moder_input(-p, o) - outputs[f"tape{o}"] = join( - wdir, - f"{outprefix}{tag}.pendf", - ) - if groupr and errorr is False: - for i, (temp, suff) in enumerate(zip(temperatures, suffixes)): - kwargs["temp"] = temp - kwargs["suff"] = suff = f".{suff}" - g = o + 1 + i - text += _groupr_input(-e, -p, -g, **kwargs) - o = 32 + i - text += _moder_input(-g, o) - outputs[f"tape{o}"] = join( - wdir, - f"{outprefix}{tag}{suff}.gendf", - ) - if errorr: - g = p+1 + o = 30 + text += _moder_input(-p, o) + + # this part produces a single GENDF file + if errorr31 or errorr34 or errorr35: + # groupr is needed by ERRORR31, 34 and 35 + # this is the only place where we make this check + groupr_ = True + else: + groupr_ = groupr + g = 39 + if groupr_: + if len(temperatures) > 1: + logging.info("Multiple temperatures were requested.\nGROUPR will only process the first.") + temperature = temperatures[0] + text += _groupr_input(-e, -p, -g, + mat=mat, temperature=temperature, + **groupr_kws) + o = 40 + text += _moder_input(-g, o) + + # this part produces a maximimum of four ERRORR files, one per data type + if errorr33 or errorr31 or errorr34 or errorr35: + if len(temperatures) > 1: + logging.info("Multiple temperatures were requested.\nERRORR will only process the first.") + temperature = temperatures[0] + if errorr33: + # for xs use a GENDF file only if explicitely asked, not just if + # groupr_=True because chi or nubar were present + o = errorr33_kws["mfcov"] = 33 p_ = 0 if groupr else p g_ = g if groupr else 0 - outputs = {} - for i, (temp, suff) in enumerate(zip(temperatures, suffixes)): - kwargs["temp"] = temp - kwargs["suff"] = suff = f".{suff}" - if groupr: - text += _groupr_input(-e, -p, -g_, **kwargs) - if kwargs['nubar']: - o = 31 + i * 5 - kwargs['mfcov'] = mfcov = 31 - text += _errorr_input(-e, -p_, -g_, o, **kwargs) - outputs[f"tape{o}"] = join( - wdir, - f"{outprefix}{tag}{suff}_{mfcov}.errorr", - ) - if kwargs['xs']: - o = 33 + i * 5 - kwargs['mfcov'] = mfcov = 33 - text += _errorr_input(-e, -p_, -g_, o, **kwargs) - outputs[f"tape{o}"] = join( - wdir, - f"{outprefix}{tag}{suff}_{mfcov}.errorr", - ) - # NJOY's errorr module WILL produce a MF35 covariance tape - # if the errorr module called before the errorr call to produce a MF34 - if kwargs['chi']: - o = 35 + i * 5 - kwargs['mfcov'] = mfcov = 35 - text += _errorr_input(-e, -p_, -g_, o, **kwargs) - outputs[f"tape{o}"] = join( - wdir, - f"{outprefix}{tag}{suff}_{mfcov}.errorr", - ) - if kwargs['mubar']: - o = 34 + i * 5 - kwargs['mfcov'] = mfcov = 34 - text += _errorr_input(-e, -p_, -g_, o, **kwargs) - outputs[f"tape{o}"] = join( - wdir, - f"{outprefix}{tag}{suff}_{mfcov}.errorr", - ) + text += _errorr_input(-e, -p_, -g_, o, + mat=mat, temperature=temperature, + **errorr33_kws) + if errorr31: + o = errorr31_kws["mfcov"] = 31 + text += _errorr_input(-e, 0, -g, o, + mat=mat, temperature=temperature, + **errorr31_kws) + # NJOY's errorr module WILL produce a MF35 covariance tape + # if the errorr module called before the errorr call to produce a MF34 + if errorr35: + o = errorr35_kws["mfcov"] = 35 + text += _errorr_input(-e, 0, -g, o, + mat=mat, temperature=temperature, + **errorr35_kws) + if errorr34: + o = errorr34_kws["mfcov"] = 34 + text += _errorr_input(-e, 0, -g, o, + mat=mat, temperature=temperature, + **errorr34_kws) + + # this part produces multiple ACE files (one per temperature) if acer: - for i, (temp, suff) in enumerate(zip(temperatures, suffixes)): + for i, (temperature, suffix) in enumerate(zip(temperatures, suffixes)): a = 50 + i x = 70 + i - kwargs["temp"] = temp - kwargs["suff"] = suff = f".{suff}" - text += _acer_input(-e, -p, a, x, **kwargs) - outputs[f"tape{a}"] = join( - wdir, - f"{outprefix}{tag}{suff}c", - ) - outputs[f"tape{x}"] = join( - wdir, - f"{outprefix}{tag}{suff}c.xsd", - ) + text += _acer_input(-e, -p, a, x, + mat=mat, temperature=temperature, suffix=suffix, + **acer_kws) text += "stop" - # stop here if a dryrun is requested + return text + + +def process_neutron( + endftape, + temperatures, + pendftape=None, + suffixes=None, + zaid="nndc", + route="0", + exe=None, + verbose=True, + dryrun=False, + **kwargs, + ): + """ + Run sequence to process file with njoy. + + Parameters + ---------- + pendftape : `str`, optional, default is `None` + name (with absolute of relative path) of a pendf file. + If given, skip module reconr and use this PENDF file, else run reconr + temperatures : iterable of `float`, optional, default is [293.6] + iterable of temperature values in K + dryrun : `bool`, optional, default is `False` + option to produce the njoy input file without running njoy + exe : `str`, optional, default is `None` + njoy executable (with path) + .. note:: if no executable is given, SANDY looks for a default + executable in `PATH` and in env variable `NJOY` + route : `str`, optional, default is `0` + xsdir "route" parameter + suffixes : iterable of `int`, optional, default is `None` + iterable of suffix values for ACE files: if `None` is given, + use internal routine to determine suffixes + + .. warning :: `suffixes` must match the number of entries in + `temperatures` + + verbose : `bool`, optional, default is `False` + flag to print NJOY input to screen before running the executable + + Returns + ------- + outputs : `map` + map of {`tape` : `text`) for ouptut files + """ + tape = sandy.Endf6.from_file(endftape) + mat = tape.mat[0] + info = tape.read_section(mat, 1, 451) + za = int(info["ZA"]) + meta = info["LISO"] + + kwargs["reconr"] = False if pendftape else True + + # Prepare njoy input + temperatures_ = temperatures if hasattr(temperatures, "__len__") else [temperatures] + if suffixes: + suffixes_ = suffixes if hasattr(suffixes, "__len__") else [suffixes] + else: + meta_ = 0 if zaid == "nndc" else meta + suffixes_ = ["." + get_temperature_suffix(t, meta_) for t in temperatures_] + text = _prepare_njoy_input(mat, temperatures_, suffixes_, **kwargs) if verbose: print(text) + + # Run njoy if dryrun: return text - - _run_njoy(text, inputs, outputs, exe=exe) - if acer: - # Change route and filename in xsdir file. - for i, (temp, suff) in enumerate(zip(temperatures, suffixes)): - a = 50 + i - x = 70 + i - acefile = outputs["tape{}".format(a)] - if addpath is None: - filename = acefile - else: - filename = os.path.basename(acefile) - if addpath: - filename = os.path.join(addpath, filename) - xsdfile = outputs["tape{}".format(x)] - text_xsd = open(xsdfile).read() \ - .replace("route", route) \ - .replace("filename", filename) - text_xsd = " ".join(text_xsd.split()) - # If isotope is metatable rewrite ZA in xsdir and ace as - # ZA = Z*1000 + 300 + A + META*100. - if meta and method != "aleph": - pattern = f'{za:d}' + '\.(?P\d{2}[ct])' - found = re.search(pattern, text_xsd) - ext = found.group("ext") - text_xsd = text_xsd.replace( - f"{za:d}.{ext}", - f"{za_new:d}.{ext}", - 1, - ) - text_ace = open(acefile).read().replace( - f"{za:d}.{ext}", - f"{za_new:d}.{ext}", - 1, - ) - with open(acefile, 'w') as f: - f.write(text_ace) - with open(xsdfile, 'w') as f: - f.write(text_xsd) - return text, inputs, outputs + outputs = _run_njoy(text, endftape, pendftape, exe=exe) + + # Minimal output post-processing + if "xsdir" in outputs: + outputs["xsdir"] = outputs["xsdir"].replace("route", route) + if zaid == "nndc": + za_new = sandy.zam.zam2za(za*10 + meta, method=zaid)[0] + for s in suffixes_: + pattern = f"{za}{s}[c]" + new_pattern = f"{za_new}{s}c" + if "xsdir" in outputs: + outputs["xsdir"] = re.sub(pattern, new_pattern, outputs["xsdir"]) + if "acer" in outputs: + outputs["acer"] = re.sub(pattern, new_pattern, outputs["acer"]) + return outputs def process_proton( diff --git a/sandy/sampling.py b/sandy/sampling.py index bdb38d49..5cd02e3e 100755 --- a/sandy/sampling.py +++ b/sandy/sampling.py @@ -1,515 +1,16 @@ -# -*- coding: utf-8 -*- -""" -Created on Mon Mar 19 22:51:03 2018 - -@author: Luca Fiorito -""" - import os import time -import pdb -import shutil -import sys -import argparse import logging -import tempfile -import multiprocessing as mp -import platform +import argparse +import filecmp import pytest -import numpy as np -import pandas as pd - import sandy -from sandy.settings import SandyError -from sandy.formats import read_formatted_file, get_file_format -from sandy.formats.endf6 import Endf6 -from sandy.formats.utils import FySamples, XsCov -from sandy import pfns from sandy.tools import is_valid_dir, is_valid_file -from sandy import njoy - -__author__ = "Luca Fiorito" -__all__ = [ - "SamplingManager", - "sampling", - ] - - -def get_parser(): - description = """Produce perturbed files containing sampled parameters - that represent the information\nstored in the evaluated nuclear data - covariances""" - parser = argparse.ArgumentParser( - prog="sandy", - description=description, - formatter_class=argparse.RawTextHelpFormatter, - ) - SamplingManager.add_file_argument(parser) - SamplingManager.add_covfile_argument(parser) - SamplingManager.add_mat_argument(parser) - SamplingManager.add_mf_argument(parser) - SamplingManager.add_mt_argument(parser) - SamplingManager.add_processes_argument(parser) - SamplingManager.add_samples_argument(parser) - SamplingManager.add_version_argument(parser) - return parser - - -class SamplingManager(): - """ - Attributes - ---------- - file : `str` - ENDF-6 or PENDF format file - covfile : `str` - ENDF-6 file containing covariances - mat : `list` of `int` - draw samples only from the selected MAT sections - mf : `list` of `int` - draw samples only from the selected MF sections - mt : `list` of `int` - draw samples only from the selected MT sections - processes : `int` - number of worker processes (default is 1) - samples : `int` - number of samples (default is 100) - """ - - def __repr__(self): - return self.__dict__.__repr__() - - def __init__(self, file): - self.file = file - - @property - def file(self): - """ - Examples - -------- - >>> with pytest.raises(Exception): sandy.SamplingManager("random_file") - """ - return self._file - - @file.setter - def file(self, file): - if not os.path.isfile(file): - raise ValueError(f"File '{file}' does not exist") - self._file = file - - @staticmethod - def add_file_argument(parser): - parser.add_argument( - 'file', - help="ENDF-6 or PENDF format file", - ) - - @property - def covfile(self): - """ - """ - if hasattr(self, "_covfile"): - return self._covfile - else: - return None - - @covfile.setter - def covfile(self, covfile): - if not covfile: - self._covfile = None - else: - if not os.path.isfile(covfile): - raise ValueError(f"File '{covfile}' does not exist") - self._covfile = covfile - - @staticmethod - def add_covfile_argument(parser): - parser.add_argument( - '--covfile', '-C', - help="ENDF-6 file containing covariances", - ) - - @property - def mat(self): - if hasattr(self, "_mat"): - return self._mat - else: - return list(range(1, 10000)) - - @mat.setter - def mat(self, mat): - self._mat = np.array(mat).astype(int).tolist() - - @staticmethod - def add_mat_argument(parser): - parser.add_argument( - '--mat', - type=int, - default=list(range(1, 10000)), - action='store', - nargs="+", - metavar="{1,..,9999}", - help="draw samples only from the selected MAT sections " - "(default is keep all)", - ) - - @property - def mf(self): - if hasattr(self, "_mf"): - return self._mf - else: - return [31, 33, 34, 35] - - @mf.setter - def mf(self, mf): - self._mf = np.array(mf).astype(int).tolist() - - @staticmethod - def add_mf_argument(parser): - parser.add_argument( - '--mf', - type=int, - default=[31, 33, 34, 35], - action='store', - nargs="+", - metavar="{31,33,34,35}", - help="draw samples only from the selected MF sections " - "(default is keep all)", - ) - - @property - def mt(self): - if hasattr(self, "_mt"): - return self._mt - else: - return list(range(1, 1000)) - - @mt.setter - def mt(self, mt): - self._mt = np.array(mt).astype(int).tolist() - - @staticmethod - def add_mt_argument(parser): - parser.add_argument( - '--mt', - type=int, - default=list(range(1, 1000)), - action='store', - nargs="+", - metavar="{1,..,999}", - help="draw samples only from the selected MT sections " - "(default = keep all)", - ) - - @property - def processes(self): - if hasattr(self, "_processes"): - return 1 - else: - return self._processes - - @processes.setter - def processes(self, processes): - if platform.system() == "Windows": - self._processes = 1 - logging.info("Running on Windows does not allow parallel " - "processing") - else: - self._processes = int(processes) - - @staticmethod - def add_processes_argument(parser): - parser.add_argument( - '--processes', '-N', - type=int, - default=1, - help="number of worker processes (default is 1)", - ) - - @property - def samples(self): - if hasattr(self, "_samples"): - return 100 - else: - return self._samples - - @samples.setter - def samples(self, samples): - self._samples = int(samples) - - @staticmethod - def add_samples_argument(parser): - parser.add_argument( - '--samples', '-S', - type=int, - default=100, - help="number of samples (default is 100)", - ) - - @staticmethod - def add_version_argument(parser): - parser.add_argument( - "--version", "-v", - action='version', - version=f'%(prog)s {sandy.__version__}', - help="code version", - ) - - @classmethod - def from_cli(cls, iargs=None): - """ - Parse command line arguments for sampling option. - - Parameters - ---------- - iargs : `list` of `str`, optional, default is `None` - list of strings to parse. - The default is taken from `sys.argv`. - - Returns - ------- - `sandy.SamplingManager` - object to draw samples from endf6 file - - Examples - -------- - >>> file = os.path.join(sandy.data.__path__[0], "h1.endf") - >>> sm = sandy.SamplingManager.from_cli([file]) - """ - arguments, skip = get_parser().parse_known_args(args=iargs) - sm = cls(arguments.file) - for k, v in arguments._get_kwargs(): - sm.__setattr__(k, v) - return sm - - @classmethod - def from_cli2(cls, iargs=None): - """ - Parse command line arguments for sampling option. - - Parameters - ---------- - iargs : `list` of `str`, optional, default is `None` - list of strings to parse. - The default is taken from `sys.argv`. - - Returns - ------- - `argparse.Namespace` - namespace object containing processed given arguments and/or - default options. - """ - description = """Produce perturbed files containing sampled parameters - that represent the information\nstored in the evaluated nuclear data - covariances""" - parser = argparse.ArgumentParser( - prog="sandy", - description=description, - formatter_class=argparse.RawTextHelpFormatter, - ) - parser.add_argument('--acer', - default=False, - action="store_true", - help="for each perturbed file, produce ACE files\n" - "(argument file must be in ENDF-6 format, not PENDF)\n(argument temperature is required)\n(default = False)") - parser.add_argument('--cov33csv', - type=lambda x: is_valid_file(parser, x), - help="file containing xs/nubar covariances in csv " - "format") - parser.add_argument('--debug', - default=False, - action="store_true", - help="turn on debug mode") - parser.add_argument('--eig', - type=int, - default=10, - metavar="N", - help="print the first N eigenvalues of the evaluated covariance matrices\n(default = do not print)") - parser.add_argument('--energy-sequence', '-E', - type=int, - metavar="EL", - default=49, - help=argparse.SUPPRESS) - parser.add_argument('--errorr', - default=False, - action="store_true", - help="run NJOY module ERRORR to produce covariance " - "matrix for xs data (default = False)") - parser.add_argument('--fission-yields', '-F', - default=False, - action="store_true", - help="input contains fission yields") - parser.add_argument('--max-polynomial', '-P', - type=int, - help="Maximum order of Legendre polynomial coefficients considered for sampling (default = all)") - parser.add_argument('--njoy', - type=lambda x: is_valid_file(parser, x), - default=None, - help="NJOY executable " - "(default search PATH, and env variable NJOY)") - parser.add_argument('--outdir', '-D', - metavar="DIR", - default=os.getcwd(), - type=lambda x: is_valid_dir(parser, x, mkdir=True), - help="target directory where outputs are stored\n(default = current working directory)\nif it does not exist it will be created") - parser.add_argument('--outname', '-O', - type=str, - help="basename for the output files " - "(default is the the basename of .)") - parser.add_argument('--seed31', - type=int, - default=None, - metavar="S31", - help="seed for random sampling of MF31 covariance " - "matrix (default = random)") - parser.add_argument('--seed33', - type=int, - default=None, - metavar="S33", - help="seed for random sampling of MF33 covariance " - "matrix (default = random)") - parser.add_argument('--seed34', - type=int, - default=None, - metavar="S34", - help="seed for random sampling of MF34 covariance " - "matrix (default = random)") - parser.add_argument('--seed35', - type=int, - default=None, - metavar="S35", - help="seed for random sampling of MF35 covariance " - "matrix (default = random)") - parser.add_argument('--temperatures', '-T', - default=[], - type=float, - action='store', - nargs="+", - metavar="T", - help="for each perturbed file, produce ACE files at " - "given temperatures") - init = parser.parse_known_args(args=iargs)[0] - if init.acer and not init.temperatures: - parser.error("--acer requires --temperatures") - if init.acer and sandy.formats.get_file_format(init.file) != "endf6": - parser.error("--acer requires file in 'endf6' format") - return init - - @property - def tape(self): - if not hasattr(self, "_tape"): - self._tape = sandy.Endf6.from_file(self.file) - return self._tape - - @tape.setter - def tape(self, tape): - self._tape = tape - - @property - def covtape(self): - if not self.covfile or self.covfile == self.file: - self._covtape = self.tape - if not hasattr(self, "_covtape"): - self._covtape = sandy.Endf6.from_file(self.covfile) - return self._covtape - - @covtape.setter - def covtape(self, covtape): - self._covtape = covtape - - def get_xs_samples(self): - """ - Draw samples using all covariance sections in the given tape. - """ - mf = 33 - pertxs = None - if mf in self.mf and mf in self.covtape.mf: - covtape = self.covtape.filter_by( - listmat=self.mat, - listmf=[33], - listmt=self.mt, - ) - xscov = sandy.XsCov.from_endf6(covtape) - if not xscov.empty: - pertxs = xscov.get_samples(self.samples)#, eig=init.eig, seed=init.seed33) - return pertxs - - -def _process_into_ace(ismp): - global init - outname = init.outname if init.outname else os.path.basename(init.file) - smpfile = os.path.join(init.outdir, f'{outname}-{ismp}') - print(ismp) - kwargs = dict( - purr=False, - wdir=init.outdir, - keep_pendf=False, - pendftape=smpfile, - tag=f"_{ismp}", - temperatures=init.temperatures, - err=0.005, - addpath="", - ) - fmt = sandy.formats.get_file_format(smpfile) - if fmt == "pendf": - kwargs["pendftape"] = smpfile - inp = init.file - elif fmt == "endf6": - inp = smpfile - input, inputs, outputs = njoy.process(inp, **kwargs) - - -def _sampling_mp(ismp, skip_title=False, skip_fend=False): - global init, pnu, pxs, plpc, pchi, pfy, tape - t0 = time.time() - mat = tape.mat[0] - newtape = Endf6(tape.copy()) - extra_points = np.logspace(-5, 7, init.energy_sequence) - if not pxs.empty: - xs = newtape.get_xs() - if not xs.empty: - xspert = xs.perturb(pxs[ismp]) - newtape = newtape.update_xs(xspert) - if not pnu.empty: - nubar = newtape.get_nubar() - if not nubar.empty: - nubarpert = nubar.perturb(pnu[ismp]) - newtape = newtape.update_nubar(nubarpert) - if not pchi.empty: - # use new format tape for energy distribution - endfnew = sandy.Endf6._from_old_format(newtape) - edistr = sandy.Edistr.from_endf6(endfnew).add_points(extra_points) - if not edistr.empty: - edistrpert = edistr.perturb(pchi[ismp]) - newtape = newtape.update_edistr(edistrpert) - if not plpc.empty: - lpc = newtape.get_lpc().add_points(extra_points) - if not lpc.empty: - lpcpert = lpc.perturb(plpc[ismp]) - newtape = newtape.update_lpc(lpcpert) - if not pfy.empty: - fy = newtape.get_fy() - if not fy.empty: - fypert = fy.perturb(pfy[ismp]) - newtape = newtape.update_fy(fypert) - print("Created sample {} for MAT {} in {:.2f} sec".format(ismp, mat, time.time()-t0,)) - descr = ["perturbed file No.{} created by SANDY".format(ismp)] - return newtape.delete_cov().update_info(descr=descr).write_string(skip_title=skip_title, skip_fend=skip_fend) - -# def _sampling_fy_mp(ismp, skip_title=False, skip_fend=False): - # global tape, PertFY, init - # t0 = time.time() - # mat = tape.mat[0] - # newtape = Endf6(tape.copy()) - # fy = newtape.get_fy() - # fynew = fy.perturb(PertFy[ismp]) - # newtape = newtape.update_fy(fynew) - # print("Created sample {} for MAT {} in {:.2f} sec".format(ismp, mat, time.time()-t0,)) - # descr = ["perturbed file No.{} created by SANDY".format(ismp)] - # return newtape.delete_cov().update_info(descr=descr).write_string(skip_title=skip_title, skip_fend=skip_fend) - +__author__ = "Luca Fiorito" +__all__ = [] def parse(iargs=None): @@ -517,8 +18,9 @@ def parse(iargs=None): Parameters ---------- - iargs : `list` of `str` - list of strings to parse. The default is taken from `sys.argv`. + iargs : `list` of `str` or `None`, default is `None`, + list of strings to parse. + The default is taken from `sys.argv`. Returns ------- @@ -526,52 +28,24 @@ def parse(iargs=None): namespace object containing processed given arguments and/or default options. """ - description = "Produce perturbed files containing sampled parameters that " - "represent the information\nstored in the evaluated nuclear " - "data covariances" + description = "Produce perturbed files containing sampled parameters that represent the information stored in the evaluated nuclear data covariances.""" parser = argparse.ArgumentParser( prog="sandy", description=description, formatter_class=argparse.RawTextHelpFormatter, ) + parser.add_argument('file', type=lambda x: is_valid_file(parser, x), - help="ENDF-6 or PENDF format file") + help="ENDF-6 file") + parser.add_argument('--acer', default=False, action="store_true", - help="for each perturbed file, produce ACE files\n" - "(argument file must be in ENDF-6 format, not PENDF)\n(argument temperature is required)\n(default = False)") - parser.add_argument('--cov', '-C', - type=lambda x: is_valid_file(parser, x), - help="file containing covariances") - parser.add_argument('--cov33csv', - type=lambda x: is_valid_file(parser, x), - help="file containing xs/nubar covariances in csv " - "format") - parser.add_argument('--debug', - default=False, - action="store_true", - help="turn on debug mode") - parser.add_argument('--eig', - type=int, - default=10, - metavar="N", - help="print the first N eigenvalues of the evaluated covariance matrices\n(default = do not print)") - parser.add_argument('--energy-sequence', '-E', - type=int, - metavar="EL", - default=49, - help=argparse.SUPPRESS) - parser.add_argument('--errorr', - default=False, - action="store_true", - help="run NJOY module ERRORR to produce covariance " - "matrix for xs data (default = False)") - parser.add_argument('--fission-yields', '-F', - default=False, - action="store_true", - help="input contains fission yields") + help="Process each perturbed file into ACE format " + "(default = False)\n" + "(--temperatures is required)") + parser.add_argument('--mat', type=int, default=list(range(1, 10000)), @@ -580,9 +54,7 @@ def parse(iargs=None): metavar="{1,..,9999}", help="draw samples only from the selected MAT " "sections (default = keep all)") - parser.add_argument('--max-polynomial', '-P', - type=int, - help="Maximum order of Legendre polynomial coefficients considered for sampling (default = all)") + parser.add_argument('--mf', type=int, default=[31, 33, 34, 35], @@ -591,434 +63,182 @@ def parse(iargs=None): metavar="{31,33,34,35}", help="draw samples only from the selected MF sections " "(default = keep all)") - parser.add_argument('--mt', + + parser.add_argument('--mt33', type=int, - default=list(range(1, 1000)), + default=None, action='store', nargs="+", metavar="{1,..,999}", help="draw samples only from the selected MT sections " "(default = keep all)") - parser.add_argument('--pdf', - type=str.lower, - choices=['normal', 'lognormal', 'uniform'], - default='normal', - help="draw samples according to the chosen distribution. " - "Available options are 'normal', 'lognormal' or 'uniform' " - "(default = 'normal')") + parser.add_argument('--njoy', type=lambda x: is_valid_file(parser, x), default=None, help="NJOY executable " "(default search PATH, and env variable NJOY)") - parser.add_argument('--outdir', '-D', - metavar="DIR", - default=os.getcwd(), - type=lambda x: is_valid_dir(parser, x, mkdir=True), - help="target directory where outputs are stored\n(default = current working directory)\nif it does not exist it will be created") + parser.add_argument('--outname', '-O', type=str, - help="basename for the output files " - "(default is the the basename of .)") + default="{ZA}_{SMP}", + help="name template for the output files\n" + "(use formatting options in https://pyformat.info/ ,\n" + "available keywords are MAT, ZAM, ZA, META, SMP)") + parser.add_argument('--processes', '-N', type=int, default=1, help="number of worker processes (default = 1)") + parser.add_argument('--samples', '-S', type=int, default=200, help="number of samples (default = 200)") + parser.add_argument('--seed31', type=int, default=None, metavar="S31", help="seed for random sampling of MF31 covariance " "matrix (default = random)") + parser.add_argument('--seed33', type=int, default=None, metavar="S33", help="seed for random sampling of MF33 covariance " "matrix (default = random)") + parser.add_argument('--seed34', type=int, default=None, metavar="S34", help="seed for random sampling of MF34 covariance " "matrix (default = random)") + parser.add_argument('--seed35', type=int, default=None, metavar="S35", help="seed for random sampling of MF35 covariance " "matrix (default = random)") + parser.add_argument('--temperatures', '-T', - default=[], + default=None, type=float, action='store', nargs="+", metavar="T", help="for each perturbed file, produce ACE files at " "given temperatures") + parser.add_argument("--version", "-v", action='version', version='%(prog)s {}'.format(sandy.__version__), help="SANDY's version.") + + parser.add_argument('--verbose', + default=False, + action="store_true", + help="print additional details to screen during execution") + init = parser.parse_known_args(args=iargs)[0] if init.acer and not init.temperatures: parser.error("--acer requires --temperatures") - if init.acer and sandy.formats.get_file_format(init.file) != "endf6": - parser.error("--acer requires file in 'endf6' format") return init -def extract_samples(ftape, covtape): - """ - Draw samples using all covariance sections in the given tape. +def run(cli): """ - global init - # EXTRACT FY PERTURBATIONS FROM COV FILE - PertFy = pd.DataFrame() - if 8 in covtape.mf and 454 in ftape.mt: - fy = ftape.get_fy(listmat=init.mat, listmt=init.mt) - if not fy.empty: - index = fy.index.to_frame(index=False) - dfperts = [] - for mat,dfmat in index.groupby("MAT"): - for mt,dfmt in dfmat.groupby("MT"): - for e,dfe in dfmt.groupby("E"): - fycov = fy.get_cov(mat, mt, e) - pert = fycov.get_samples(init.samples, eig=0) - dfperts.append(pert) - PertFy = FySamples(pd.concat(dfperts)) - if init.debug: - PertFy.to_csv("perts_mf8.csv") - # EXTRACT NUBAR PERTURBATIONS FROM ENDF6 FILE - PertNubar = pd.DataFrame() - if 31 in init.mf and 31 in ftape.mf: - nubarcov = XsCov.from_endf6(covtape.filter_by(listmat=init.mat, listmf=[31], listmt=init.mt)) - if not nubarcov.empty: - PertNubar = nubarcov.get_samples(init.samples, eig=init.eig) - if init.debug: - PertNubar.to_csv("perts_mf31.csv") - # EXTRACT PERTURBATIONS FROM EDISTR COV FILE - PertEdistr = pd.DataFrame() - if 35 in init.mf and 35 in ftape.mf: - edistrcov = ftape.get_edistr_cov() - if not edistrcov.empty: - PertEdistr = edistrcov.get_samples(init.samples, eig=init.eig) - if init.debug: - PertEdistr.to_csv("perts_mf35.csv") - # EXTRACT PERTURBATIONS FROM LPC COV FILE - PertLpc = pd.DataFrame() - if 34 in init.mf and 34 in covtape.mf: - lpccov = ftape.get_lpc_cov() - if not lpccov.empty: - if init.max_polynomial: - lpccov = lpccov.filter_p(init.max_polynomial) - PertLpc = lpccov.get_samples(init.samples, eig=init.eig) - if init.debug: - PertLpc.to_csv("perts_mf34.csv") - # EXTRACT XS PERTURBATIONS FROM COV FILE - PertXs = pd.DataFrame() - if 33 in init.mf and 33 in covtape.mf: - # This part is to get the pendf file - if ftape.get_file_format() == "endf6": - endf6 = sandy.Endf6.from_file(init.file) - pendf = endf6.get_pendf(njoy=init.njoy) - with tempfile.TemporaryDirectory() as td: - dst = os.path.join(td, "merged") - endf6.merge_pendf(pendf).to_file(dst) - ftape = read_formatted_file(dst) - if init.errorr: - if len(ftape.mat) > 1: - # Limit imposed by running ERRORR to get covariance matrices - raise sandy.Error("More than one MAT number was found") - endf6 = sandy.Endf6.from_file(init.file) - covtape = endf6.get_errorr(njoy=init.njoy) - - - - -# with tempfile.TemporaryDirectory() as td: -# outputs = njoy.process(init.file, broadr=False, thermr=False, -# unresr=False, heatr=False, gaspr=False, -# purr=False, errorr=init.errorr, acer=False, -# wdir=td, keep_pendf=True, exe=init.njoy, -# temperatures=[0], suffixes=[0], err=0.005)[2] -# ptape = read_formatted_file(outputs["tape30"]) -# if init.debug: shutil.move(outputs["tape30"], os.path.join(init.outdir, "tape30")) -# if init.errorr: -# covtape = read_formatted_file(outputs["tape33"]) # WARNING: by doing this we delete the original covtape -# if init.debug: shutil.move(outputs["tape33"], os.path.join(init.outdir, "tape33")) -# ftape = ftape.delete_sections((None, 3, None)). \ -# add_sections(ptape.filter_by(listmf=[3])). \ -# add_sections(ptape.filter_by(listmf=[1], listmt=[451])) - - listmt = sorted(set(init.mt + [451])) # ERRORR needs MF1/MT451 to get the energy grid - covtape = covtape.filter_by(listmat=init.mat, listmf=[1,33], listmt=listmt) - xscov = XsCov(covtape.get_cov(multigroup=False).data) if isinstance(covtape, sandy.errorr.Errorr) else XsCov.from_endf6(covtape) - if not xscov.empty: - PertXs = sandy.CategoryCov(xscov).sampling(init.samples, tolerance=0, seed=init.seed33, pdf=init.pdf).data.T - idx = PertXs.index - PertXs = pd.DataFrame(PertXs.values, index=idx, columns=range(1, init.samples + 1)) - PertXs.columns.name = "SMP" - if init.debug: - PertXs.to_csv(os.path.join(init.outdir, "perts_mf33.csv")) - return ftape, covtape, PertNubar, PertXs, PertLpc, PertEdistr, PertFy - - -def sampling_csv33(ftape, csv): - cov = sandy.CategoryCov.from_csv(csv) - return sandy.XsCov(cov).get_samples( - init.samples, - eig=init.eig, - seed=init.seed33 - ) + + Parameters + ---------- + cli : TYPE + DESCRIPTION. -def sampling(iargs=None): - """ - Construct multivariate normal distributions with a unit vector for - mean and with relative covariances taken from the evaluated files. - Perturbation factors are sampled with the same multigroup structure of - the covariance matrix, and are applied to the pointwise data to produce - the perturbed files. + Returns + ------- + None. + + Examples + -------- + Retrieve ENDF-6 tape and write it to file. + >>> sandy.get_endf6_file("jeff_33", "xs", 10010).to_file("H1.jeff33") + + Produce perturbed ACE file. + >>> cli = "H1.jeff33 --acer True --samples 2 --processes 2 --temperatures 900 --seed33 5" + >>> sandy.sampling.run(cli) + + Check if ACE and XSDIR files have the right content. + >>> assert "1001.09c" in open("1001_0.09c").read() + >>> assert "1001.09c" in open("1001_0.09c.xsd").read() + >>> assert "1001.09c" in open("1001_1.09c").read() + >>> assert "1001.09c" in open("1001_1.09c.xsd").read() + >>> assert not filecmp.cmp("1001_0.09c", "1001_1.09c") + + Run the same on a single process. + >>> cli = "H1.jeff33 --acer True --samples 2 --processes 2 --temperatures 900 --seed33 5 --outname={ZAM}_{SMP}_SP" + >>> sandy.sampling.run(cli) + + The identical seed ensures consistent results with the previous run. + >>> assert filecmp.cmp("1001_0.09c", "10010_0_SP.09c") + >>> assert filecmp.cmp("1001_1.09c", "10010_1_SP.09c") + >>> assert filecmp.cmp("1001_0.09c.xsd", "10010_0_SP.09c.xsd") + >>> assert filecmp.cmp("1001_1.09c.xsd", "10010_1_SP.09c.xsd") + + Produce perturbed ENDF6 and PENDF files. + >>> cli = "H1.jeff33 --samples 2 --processes 2 --outname=H1_{MAT}_{SMP} --mt 102" + >>> sandy.sampling.run(cli) + >>> assert os.path.getsize("H1_125_0.pendf") > 0 and os.path.getsize("H1_125_1.pendf") > 0 + + >>> assert filecmp.cmp("H1_125_0.endf6", "H1_125_1.endf6") + >>> assert filecmp.cmp("H1_125_0.endf6", "H1.jeff33") + >>> assert not filecmp.cmp("H1_125_0.pendf", "H1_125_1.pendf") """ - global init, pnu, pxs, plpc, pchi, pfy, tape - init = parse(iargs) - ftape = read_formatted_file(init.file) - if init.cov33csv: - logging.warning("found argument '--cov33csv', will skip any other" - " covariance") - catcov = sandy.CategoryCov.from_csv( - init.cov33csv, - index_col=[0, 1, 2], - header=[0, 1, 2], - ) - covtape = xscov = sandy.CategoryCov(catcov.data) - # This part is to get the pendf file - if ftape.get_file_format() == "endf6": - endf6 = sandy.Endf6.from_file(init.file) - pendf = endf6.get_pendf(njoy=init.njoy) - with tempfile.TemporaryDirectory() as td: - dst = os.path.join(td, "merged") - endf6.merge_pendf(pendf).to_file(dst) - ftape = read_formatted_file(dst) -# if ftape.get_file_format() == "endf6": -# with tempfile.TemporaryDirectory() as td: -# outputs = njoy.process(init.file, broadr=False, thermr=False, -# unresr=False, heatr=False, gaspr=False, -# purr=False, errorr=init.errorr, acer=False, -# wdir=td, keep_pendf=True, exe=init.njoy, -# temperatures=[0], suffixes=[0], err=0.005)[2] -# ptape = read_formatted_file(outputs["tape30"]) -# if init.debug: -# shutil.move(outputs["tape30"], os.path.join(init.outdir, "tape30")) -# ftape = ftape.delete_sections((None, 3, None)). \ -# add_sections(ptape.filter_by(listmf=[3])). \ -# add_sections(ptape.filter_by(listmf=[1], listmt=[451])) - pxs = xscov.sampling(init.samples, pdf=init.pdf, seed=init.seed33, tolerance=0) - cn = pxs.condition_number - print(f"Condition number : {cn:>15}") - pxs = pxs.data.T - idx = pxs.index - pxs = pd.DataFrame(pxs.values, index=idx, columns=range(1, init.samples + 1)) - pxs.columns.name = "SMP" - pnu = plpc = pchi = pfy = pd.DataFrame() - if init.debug: - pxs.to_csv(os.path.join(init.outdir, "perts_mf33.csv")) - else: - covtape = read_formatted_file(init.cov) if init.cov else ftape - ftape, covtape, pnu, pxs, plpc, pchi, pfy = extract_samples(ftape, covtape) - df = {} - if pnu.empty and pxs.empty and plpc.empty and pchi.empty and pfy.empty: - logging.warn("no covariance section was selected/found") - return ftape, covtape, df - # APPLY PERTURBATIONS BY MAT - for imat, (mat, tape) in enumerate(sorted(ftape.groupby('MAT'))): - skip_title = False if imat == 0 else True - skip_fend = False if imat == len(ftape.mat) - 1 else True - tape = Endf6(tape) - kw = dict(skip_title=skip_title, skip_fend=skip_fend) - if platform.system() == "Windows": - proc = 1 - logging.info("Running on Windows does not allow parallel " - "processing") - else: - proc = init.processes - seq = range(1, init.samples + 1) - if proc == 1: - outs = {i: _sampling_mp(i, **kw) for i in seq} - else: - pool = mp.Pool(processes=proc) - outs = {i: pool.apply_async(_sampling_mp, (i,), kw) for i in seq} - outs = {i: out.get() for i, out in outs.items()} - pool.close() - pool.join() - df.update({mat: outs}) - # DUMP TO FILES - frame = pd.DataFrame(df) - frame.index.name = "SMP" - frame.columns.name = "MAT" - frame = frame.stack() - outname = init.outname if init.outname else os.path.split(init.file)[1] - for ismp,dfsmp in frame.groupby("SMP"): - output = os.path.join(init.outdir, '{}-{}'.format(outname, ismp)) - with open(output, 'w') as f: - for mat,dfmat in dfsmp.groupby("MAT"): - f.write(frame[ismp,mat]) - # PRODUCE ACE FILES - if init.acer: - seq = range(1, init.samples + 1) - if init.processes == 1: - for i in seq: - _process_into_ace(i) - else: - pool = mp.Pool(processes=init.processes) - outs = {i: pool.apply_async(_process_into_ace, (i,)) for i in seq} - pool.close() - pool.join() - return ftape, covtape, df - - - - - pdb.set_trace() - df = {} - if init.fission_yields: - # EXTRACT FY PERTURBATIONS FROM COV FILE - fy = ftape.get_fy(listmat=init.mat, listmt=init.mt) - if fy.empty: - logging.warn("no fission yield section was selected/found") - return - index = fy.index.to_frame(index=False) - dfperts = [] - for mat,dfmat in index.groupby("MAT"): - for mt,dfmt in dfmat.groupby("MT"): - for e,dfe in dfmt.groupby("E"): - fycov = fy.get_cov(mat, mt, e) - pert = fycov.get_samples(init.samples, eig=0) - dfperts.append(pert) - PertFy = FySamples(pd.concat(dfperts)) - if init.debug: PertFy.to_csv("perts_mf8.csv") - # DELETE LOCAL VARIABLES - for k in locals().keys(): - del locals()[k] - # APPLY PERTURBATIONS BY MAT - for imat,(mat, tape) in enumerate(sorted(ftape.groupby('MAT'))): - skip_title = False if imat == 0 else True - skip_fend = False if imat == ftape.index.get_level_values("MAT").unique().size -1 else True - tape = Endf6(tape) - kw = dict(skip_title=skip_title, skip_fend=skip_fend) - if mat not in init.mat: - out = tape.write_string(**kw) - outs = {i : out for i in range(1,init.samples+1)} - else: - if init.processes == 1: - outs = {i : _sampling_fy_mp(i, **kw) for i in range(1,init.samples+1)} - else: - pool = mp.Pool(processes=init.processes) - outs = {i : pool.apply_async(_sampling_fy_mp, (i,), kw) for i in range(1,init.samples+1)} - outs = {i : out.get() for i,out in outs.items()} - pool.close() - pool.join() - df.update({ mat : outs }) - else: - # EXTRACT NUBAR PERTURBATIONS FROM ENDF6 FILE - PertNubar = pd.DataFrame() - if 31 in init.mf and 31 in ftape.mf: - nubarcov = XsCov.from_endf6(covtape.filter_by(listmat=init.mat, listmf=[31], listmt=listmt)) - if not nubarcov.empty: - PertNubar = nubarcov.get_samples(init.samples, eig=init.eig) - if init.debug: - PertNubar.to_csv("perts_mf31.csv") - # EXTRACT PERTURBATIONS FROM EDISTR COV FILE - PertEdistr = pd.DataFrame() - if 35 in init.mf and 35 in ftape.mf: - edistrcov = ftape.get_edistr_cov() - if not edistrcov.empty: - PertEdistr = edistrcov.get_samples(init.samples, eig=init.eig) - if init.debug: - PertEdistr.to_csv("perts_mf35.csv") - # EXTRACT PERTURBATIONS FROM LPC COV FILE - PertLpc = pd.DataFrame() - if 34 in init.mf and 34 in covtape.mf: - lpccov = ftape.get_lpc_cov() - if not lpccov.empty: - if init.max_polynomial: - lpccov = lpccov.filter_p(init.max_polynomial) - PertLpc = lpccov.get_samples(init.samples, eig=init.eig) - if init.debug: - PertLpc.to_csv("perts_mf34.csv") - # EXTRACT XS PERTURBATIONS FROM COV FILE - PertXs = pd.DataFrame() - if 33 in init.mf and 33 in covtape.mf: - if init.errorr and len(ftape.mat) > 1: # Limit imposed by running ERRORR to get covariance matrices - raise SandyError("More than one MAT number was found") - if ftape.get_file_format() == "endf6": - with tempfile.TemporaryDirectory() as td: - outputs = njoy.process(init.file, broadr=False, thermr=False, - unresr=False, heatr=False, gaspr=False, - purr=False, errorr=init.errorr, acer=False, - wdir=td, keep_pendf=True, - temperatures=[0], suffixes=[0], err=0.005)[2] - ptape = read_formatted_file(outputs["tape30"]) - if init.errorr: - covtape = read_formatted_file(outputs["tape33"]) # WARNING: by doing this we delete the original covtape - ftape = ftape.delete_sections((None, 3, None)). \ - add_sections(ptape.filter_by(listmf=[3])). \ - add_sections(ptape.filter_by(listmf=[1], listmt=[451])) - listmterr = init.mt if init.mt is None else [451].extend(init.mt) # ERRORR needs MF1/MT451 to get the energy grid - covtape = covtape.filter_by(listmat=init.mat, listmf=[1,33], listmt=listmterr) - covtype = covtape.get_file_format() - xscov = XsCov.from_errorr(covtape) if covtype == "errorr" else XsCov.from_endf6(covtape) - if not xscov.empty: - PertXs = xscov.get_samples(init.samples, eig=init.eig, seed=init.seed33) - if init.debug: - PertXs.to_csv(os.path.join(init.outdir, "perts_mf33.csv")) - if PertLpc.empty and PertEdistr.empty and PertXs.empty and PertNubar.empty: - sys.exit("no covariance section was selected/found") - return - pdb.set_trace() - # DELETE LOCAL VARIABLES - for k in locals().keys(): - del locals()[k] - # APPLY PERTURBATIONS BY MAT - for imat,(mat, tape) in enumerate(sorted(ftape.groupby('MAT'))): - skip_title = False if imat == 0 else True - skip_fend = False if imat == ftape.index.get_level_values("MAT").unique().size -1 else True - tape = Endf6(tape) - kw = dict(skip_title=skip_title, skip_fend=skip_fend) - if init.processes == 1: - outs = {i : _sampling_mp(i, **kw) for i in range(1,init.samples+1)} - else: - pool = mp.Pool(processes=init.processes) - outs = {i : pool.apply_async(_sampling_mp, (i,), kw) for i in range(1,init.samples+1)} - outs = {i : out.get() for i,out in outs.items()} - pool.close() - pool.join() - df.update({ mat : outs }) - # DUMP TO FILES - frame = pd.DataFrame(df) - frame.index.name = "SMP" - frame.columns.name = "MAT" - frame = frame.stack() - outname = init.outname if init.outname else os.path.split(init.file)[1] - for ismp,dfsmp in frame.groupby("SMP"): - output = os.path.join(init.outdir, '{}-{}'.format(outname, ismp)) - with open(output, 'w') as f: - for mat,dfmat in dfsmp.groupby("MAT"): - f.write(frame[ismp,mat]) - - -def run(): + # >>> cli = "H1.jeff33 --samples 2 --processes 2 --seed33 5 --outname=H1_{SMP}" + # >>> sandy.sampling.run(cli) t0 = time.time() - try: - sampling() - except SandyError as exc: - logging.error(exc.args[0]) - print("Total running time: {:.2f} sec".format(time.time() - t0)) + + iargs = parse(cli.split()) + + endf6 = sandy.Endf6.from_file(iargs.file) + + njoy_kws = dict( + err=0.01, + temperature=0, + ) + if iargs.mt33: + njoy_kws["errorr33_kws"] = dict(mt=iargs.mt33) + + smp_kws = {} + if iargs.seed31: + smp_kws["seed31"] = iargs.seed31 + if iargs.seed33: + smp_kws["seed33"] = iargs.seed33 + if iargs.seed34: + smp_kws["seed34"] = iargs.seed34 + if iargs.seed34: + smp_kws["seed35"] = iargs.seed35 + + smps = endf6.get_perturbations(iargs.samples, njoy_kws=njoy_kws, smp_kws=smp_kws) + + ace_kws = dict( + temperature=iargs.temperatures[0] if hasattr(iargs.temperatures, "__len__") else iargs.temperatures + ) + endf6._mp_apply_perturbations( + smps, + processes=iargs.processes, + to_file=True, + to_ace=iargs.acer, + filename=iargs.outname, + ace_kws=ace_kws, + verbose=iargs.verbose, + ) + + dt = time.time() - t0 + logging.info(f"Total running time: {dt:.2f} sec") if __name__ == "__main__": diff --git a/sandy/sections/mf1.py b/sandy/sections/mf1.py index 21dc760e..995eb92f 100644 --- a/sandy/sections/mf1.py +++ b/sandy/sections/mf1.py @@ -1,5 +1,5 @@ -import pdb import logging +import re import sandy @@ -20,7 +20,7 @@ def read_mf1(tape, mat, mt): - """ + r""" Parse MAT/MF=3/MT section from `sandy.Endf6` object and return structured content in nested dcitionaries. @@ -45,7 +45,7 @@ def read_mf1(tape, mat, mt): **mt = 451** : >>> tape = sandy.get_endf6_file("endfb_71", 'xs', 922350) - >>> test = read_mf1(tape, 9228, 451) + >>> test = sandy.read_mf1(tape, 9228, 451) >>> test['SECTIONS'][::5] [(1, 451, 934, 7), (1, 460, 253745, 0), @@ -75,45 +75,8 @@ def read_mf1(tape, mat, mt): (33, 2, 113418, 7), (33, 102, 23060, 7)] - >>> tape = sandy.get_endf6_file("endfb_71", 'nfpy', 922350) - >>> test = read_mf1(tape, 9228, 451) - >>> test['SECTIONS'] - [(1, 451, 17, 2), (8, 454, 2501, 2), (8, 459, 2501, 2)] - - >>> tape = sandy.get_endf6_file("endfb_71", 'decay', 922350) - >>> test = read_mf1(tape, 3515, 451) - >>> print(test['DESCRIPTION']) - *********************** Begin Description *********************** - ** ENDF/B-VII.1 RADIOACTIVE DECAY DATA FILE ** - ** Produced at the NNDC from the ENSDF database ** - ** Translated into ENDF format by: ** - ** T.D. Johnson, E.A. McCutchan and A.A. Sonzogni, 2011 ** - ***************************************************************** - ENSDF evaluation authors: E. BROWNE - Parent Excitation Energy: 0 - Parent Spin & Parity: 7/2- - Parent half-life: 703.8E+6 Y 5 - Decay Mode: A - ************************ Energy Balance ************************ - Mean Gamma Energy: 1.486E2 +- 1.440E0 keV - Mean X-Ray+511 Energy: 1.553E1 +- 7.609E-1 keV - Mean CE+Auger Energy: 4.170E1 +- 1.313E0 keV - Mean B- Energy: 0.000E0 +- 0.000E0 keV - Mean B+ Energy: 0.000E0 +- 0.000E0 keV - Mean Neutrino Energy: 0.000E0 +- 0.000E0 keV - Mean Neutron Energy: 0.000E0 +- 0.000E0 keV - Mean Proton Energy: 0.000E0 +- 0.000E0 keV - Mean Alpha Energy: 4.339E3 +- 1.648E2 keV - Mean Recoil Energy: 7.386E1 +- 2.806E0 keV - Sum Mean Energies: 4.619E3 +- 1.649E2 keV - Q effective: 4.679E3 keV - Missing Energy: 5.951E1 keV - Deviation: 1.272E0 % - ************************ End Description ************************ - **mt = 452** : - >>> tape = sandy.get_endf6_file("endfb_71", 'xs', 922350) - >>> test = sandy.sections.mf1.read_mf1(tape, 9228, 452) + >>> test = sandy.read_mf1(tape, 9228, 452) >>> test['E'] array([1.00e-05, 2.53e-02, 5.00e-02, 1.00e+01, 1.00e+02, 1.00e+03, 5.50e+03, 7.75e+03, 1.00e+04, 1.50e+04, 2.00e+04, 3.00e+04, @@ -131,16 +94,14 @@ def read_mf1(tape, mat, mt): 2.00e+07]) **mt = 455** : - >>> tape = sandy.get_endf6_file("endfb_71", 'xs', 922350) - >>> test = sandy.sections.mf1.read_mf1(tape, 9228, 455) + >>> test = sandy.read_mf1(tape, 9228, 455) >>> test['LAMBDA'] [0.013336, 0.032739, 0.12078, 0.30278, 0.84949, 2.853] >>> test['NU'] array([0.01585, 0.01585, 0.0167 , 0.0167 , 0.009 , 0.009 ]) **mt = 456** : - >>> tape = sandy.get_endf6_file("endfb_71", 'xs', 922350) - >>> test = sandy.sections.mf1.read_mf1(tape, 9228, 456) + >>> test = sandy.read_mf1(tape, 9228, 456) >>> test['NU'] array([2.42085 , 2.42085 , 2.42085 , 2.42085 , 2.417948, 2.417933, 2.417857, 2.417818, 2.41778 , 2.414463, 2.412632, 2.409341, @@ -158,8 +119,7 @@ def read_mf1(tape, mat, mt): 5.200845]) **mt = 458** : - >>> tape = sandy.get_endf6_file("endfb_71", 'xs', 922350) - >>> test = sandy.sections.mf1.read_mf1(tape, 9228, 458) + >>> test = sandy.read_mf1(tape, 9228, 458) >>> test['POLYNOMIALS'][1] {'EFR': -0.266, 'DEFR': 0.0266, @@ -179,6 +139,47 @@ def read_mf1(tape, mat, mt): 'DER': 0.00379, 'ET': -0.1379, 'DET': 0.01379} + + >>> tape = sandy.get_endf6_file("endfb_71", 'nfpy', 922350) + >>> test = read_mf1(tape, 9228, 451) + >>> test['SECTIONS'] + [(1, 451, 17, 2), (8, 454, 2501, 2), (8, 459, 2501, 2)] + + >>> tape = sandy.get_endf6_file("endfb_71", 'decay', 922350) + >>> test = sandy.read_mf1(tape, 3515, 451) + >>> print("\n".join(test['DESCRIPTION'])) + 92-U -235 BNL EVAL-NOV05 Conversion from ENSDF + /ENSDF/ 20111222 + ----ENDF/B-VII.1 Material 3515 + -----RADIOACTIVE DECAY DATA + ------ENDF-6 FORMAT + *********************** Begin Description *********************** + ** ENDF/B-VII.1 RADIOACTIVE DECAY DATA FILE ** + ** Produced at the NNDC from the ENSDF database ** + ** Translated into ENDF format by: ** + ** T.D. Johnson, E.A. McCutchan and A.A. Sonzogni, 2011 ** + ***************************************************************** + ENSDF evaluation authors: E. BROWNE + Parent Excitation Energy: 0 + Parent Spin & Parity: 7/2- + Parent half-life: 703.8E+6 Y 5 + Decay Mode: A + ************************ Energy Balance ************************ + Mean Gamma Energy: 1.486E2 +- 1.440E0 keV + Mean X-Ray+511 Energy: 1.553E1 +- 7.609E-1 keV + Mean CE+Auger Energy: 4.170E1 +- 1.313E0 keV + Mean B- Energy: 0.000E0 +- 0.000E0 keV + Mean B+ Energy: 0.000E0 +- 0.000E0 keV + Mean Neutrino Energy: 0.000E0 +- 0.000E0 keV + Mean Neutron Energy: 0.000E0 +- 0.000E0 keV + Mean Proton Energy: 0.000E0 +- 0.000E0 keV + Mean Alpha Energy: 4.339E3 +- 1.648E2 keV + Mean Recoil Energy: 7.386E1 +- 2.806E0 keV + Sum Mean Energies: 4.619E3 +- 1.649E2 keV + Q effective: 4.679E3 keV + Missing Energy: 5.951E1 keV + Deviation: 1.272E0 % + ************************ End Description ************************ """ if mt == 451: out = _read_intro(tape, mat) @@ -191,7 +192,7 @@ def read_mf1(tape, mat, mt): elif mt == 458: out = _read_fission_energy(tape, mat) elif mt in allowed_mt: - raise sandy.Error("'MF={mf}/MT={mt}' not yet implemented") + raise ValueError("'MF={mf}/MT={mt}' not yet implemented") else: raise ValueError("'MF={mf}/MT={mt}' not allowed") return out @@ -424,51 +425,66 @@ def _read_intro(tape, mat): add = { "TEMP": C.C1, # Target temperature (Kelvin) for data that have been generated by Doppler broadening "LDRV": C.L1, # Special derived material flag that distinguishes between different evaluations with the same material keys - "NWD": NWD, # Number of records with descriptive text for this material - "NXC": NXC, # Number of records in the directory for this material - } - out.update(add) - T, i = sandy.read_text(df, i) - add = { - "ZSYMAM": T.HL[:11], # Character representation of the material - "ALAB": T.HL[11:22], # Mnemonic for the originating laboratory(s) - "EDATE": T.HL[22:32], # Date of evaluation - "AUTH": T.HL[33:], # Author(s) name(s) - } - out.update(add) - T, i = sandy.read_text(df, i) - add = { - "REF": T.HL[1:22], # Primary reference for the evaluation - "DDATE": T.HL[22:32], # Original distribution date - "RDATE": T.HL[33:43], # Date and number of the last revision to this evaluation - "ENDATE": T.HL[55:63], # Author(s) name(s) +# "NWD": NWD, # Number of records with descriptive text for this material +# "NXC": NXC, # Number of records in the directory for this material } out.update(add) - H1, i = sandy.read_text(df, i) - H2, i = sandy.read_text(df, i) - H3, i = sandy.read_text(df, i) + descr = [] + for j in range(NWD): + T, i = sandy.read_text(df, i) + descr.append(T[0]) add = { - "HSUB": "\n".join([H1.HL, H2.HL, H3.HL]) - } + "DESCRIPTION": descr, + } out.update(add) - lines = [] - for j in range(NWD - 5): + # add = { + # "ZSYMAM": T.HL[:11], # Character representation of the material + # "ALAB": T.HL[11:22], # Mnemonic for the originating laboratory(s) + # "EDATE": T.HL[22:32], # Date of evaluation + # "AUTH": T.HL[33:], # Author(s) name(s) + # } + # out.update(add) + # T, i = sandy.read_text(df, i) + # add = { + # "REF": T.HL[1:22], # Primary reference for the evaluation + # "DDATE": T.HL[22:32], # Original distribution date + # "RDATE": T.HL[33:43], # Date and number of the last revision to this evaluation + # "ENDATE": T.HL[55:63], # Author(s) name(s) + # } + # out.update(add) + # H1, i = sandy.read_text(df, i) + # H2, i = sandy.read_text(df, i) + # H3, i = sandy.read_text(df, i) + # add = { + # "HSUB": "\n".join([H1.HL, H2.HL, H3.HL]) + # } + # out.update(add) + # lines = [] + # for j in range(NWD - 5): + # T, i = sandy.read_text(df, i) + # lines.append(T.HL) + # add = "\n".join(lines) + # out.update({ + # "DESCRIPTION": add, + # }) + # try: + # sections = _get_sections(df.iloc[-NXC:]) + # except Exception as e: + # msg = f"reported sections in MAT{mat}/MF1/MT451 are not consistent" + # logging.warning(msg) + # logging.warning(f"captured error: '{e}'") + # else: + # out.update({ + # "SECTIONS": sections, + # }) + sections = [] + for j in range(NXC): T, i = sandy.read_text(df, i) - lines.append(T.HL) - add = "\n".join(lines) + s = tuple(map(int, re.findall(".{11}" , T[0])[2:])) + sections.append(s) out.update({ - "DESCRIPTION": add, + "SECTIONS": sections, }) - try: - sections = _get_sections(df.iloc[-NXC:]) - except Exception as e: - msg = f"reported sections in MAT{mat}/MF1/MT451 are not consistent" - logging.warning(msg) - logging.warning(f"captured error: '{e}'") - else: - out.update({ - "SECTIONS": sections, - }) return out @@ -564,8 +580,8 @@ def write_mf1(sec): **mt = 452** : >>> tape = sandy.get_endf6_file("endfb_71", 'xs', 922350) - >>> sec = sandy.sections.mf1.read_mf1(tape, 9228, 452) - >>> text = sandy.sections.mf1.write_mf1(sec) + >>> sec = sandy.read_mf1(tape, 9228, 452) + >>> text = sandy.write_mf1(sec) >>> print(text[:1000]) 92235.0000 233.024800 0 2 0 09228 1452 1 0.00000000 0.00000000 0 0 1 799228 1452 2 @@ -582,9 +598,8 @@ def write_mf1(sec): 350000.000 2.47658400 40000 **mt = 455** : - >>> tape = sandy.get_endf6_file("endfb_71", 'xs', 922350) - >>> sec = sandy.sections.mf1.read_mf1(tape, 9228, 455) - >>> text = sandy.sections.mf1.write_mf1(sec) + >>> sec = sandy.read_mf1(tape, 9228, 455) + >>> text = sandy.write_mf1(sec) >>> print(text[:1000]) 92235.0000 233.024800 0 2 0 09228 1455 1 0.00000000 0.00000000 0 0 6 09228 1455 2 @@ -595,9 +610,8 @@ def write_mf1(sec): 4000000.00 1.670000-2 7000000.00 9.000000-3 20000000.0 9.000000-39228 1455 7 **mt = 456** : - >>> tape = sandy.get_endf6_file("endfb_71", 'xs', 922350) - >>> sec = sandy.sections.mf1.read_mf1(tape, 9228, 456) - >>> text = sandy.sections.mf1.write_mf1(sec) + >>> sec = sandy.read_mf1(tape, 9228, 456) + >>> text = sandy.write_mf1(sec) >>> print(text[:1000]) 92235.0000 233.024800 0 2 0 09228 1456 1 0.00000000 0.00000000 0 0 1 799228 1456 2 @@ -614,14 +628,16 @@ def write_mf1(sec): 350000.000 2.45988400 40000 """ mt = sec["MT"] - if mt == 452: + if mt == 451: + out = _write_intro(sec) + elif mt == 452: out = _write_nubar(sec) elif mt == 455: out = _write_dnubar(sec) elif mt == 456: out = _write_pnubar(sec) elif mt in allowed_mt: - raise sandy.Error(f"'MT={mt}' not yet implemented") + raise ValueError(f"'MT={mt}' not yet implemented") else: raise ValueError(f"'MT={mt}' not allowed") return out @@ -817,3 +833,48 @@ def _write_dnubar(sec): else: raise ValueError(f"'(LDG, LNU)' cannot be '({LDG}, {LNU})'") return "\n".join(sandy.write_eol(lines, mat, mf, mt)) + + +def _write_intro(sec): + mat = sec["MAT"] + mt = 451 + lines = sandy.write_cont( + sec["ZA"], + sec["AWR"], + sec["LRP"], + sec["LFI"], + sec["NLIB"], + sec["MOD"], + ) + lines += sandy.write_cont( + sec["ELIS"], + sec["STA"], + sec["LIS"], + sec["LISO"], + 0, + sec["NFOR"], + ) + lines += sandy.write_cont( + sec["AWI"], + sec["EMAX"], + sec["LREL"], + 0, + sec["NSUB"], + sec["NVER"], + ) + NWD = len(sec["DESCRIPTION"]) + NXC = len(sec["SECTIONS"]) + lines += sandy.write_cont( + sec["TEMP"], + 0, + sec["LDRV"], + 0, + NWD, + NXC, + ) + for t in sec["DESCRIPTION"]: + lines += sandy.write_text(t) + for MF, MT, NL, MOD in sec["SECTIONS"]: + t = " "*22 + f"{MF:>11d}{MT:>11d}{NL:>11d}{MOD:>11d}" + lines += sandy.write_text(t) + return "\n".join(sandy.write_eol(lines, mat, mf, mt)) diff --git a/sandy/spectra.py b/sandy/spectra.py new file mode 100644 index 00000000..54a95e3f --- /dev/null +++ b/sandy/spectra.py @@ -0,0 +1,35 @@ +import pandas as pd + +__author__ = "Luca Fiorito" +__all__ = [ + "custom_spectra", + "get_custom_spectrum", + ] + + +pd.options.display.float_format = '{:.5e}'.format + + +custom_spectra = { + "PWR_UO2_0_1102": "https://fispact.ukaea.uk/wiki/images/2/29/1102_PWR-UO2-0.txt", + "PWR_UO2_15_1102": "https://fispact.ukaea.uk/wiki/images/3/33/1102_PWR-UO2-15.txt", + "BIGTEN_407": "https://fispact.ukaea.uk/wiki/images/a/a6/407_Bigten.txt", + } + + +def get_custom_spectrum(key): + file = custom_spectra[key] + data = pd.read_csv(file, header=None).iloc[:-1].squeeze().astype(float) + split = data.size // 2 + e = data.iloc[:split].values[::-1] + # Set lower bin to 1e-5 eV + e[0] = 1e-5 + e = pd.IntervalIndex.from_breaks(e, name="E") + f = data.iloc[split:-1].values[::-1] + spe = pd.Series(f, index=e).rename("SPE") + return spe + + +# Allocate all spectra in "custom_spectra" as module attributes +for k in custom_spectra: + exec(f"{k} = get_custom_spectrum(k)") diff --git a/sandy/tools.py b/sandy/tools.py index ed2c88a7..27435a14 100755 --- a/sandy/tools.py +++ b/sandy/tools.py @@ -95,15 +95,6 @@ def recursively_load_dict_contents_from_group(h5file, path): return ans -def str2bool(v): - if v.lower() in ('yes', 'true', 't', 'y', '1'): - return True - elif v.lower() in ('no', 'false', 'f', 'n', '0'): - return False - else: - raise argparse.ArgumentTypeError('Boolean value expected.') - - def is_valid_file(parser, arg, r=True, w=False, x=False): if not os.path.isfile(arg): parser.error("File {} does not exist".format(arg)) @@ -176,48 +167,3 @@ def mkl_get_max_threads(): def mkl_set_num_threads(cores): mkl_rt = ctypes.CDLL('libmkl_rt.so') return mkl_rt.mkl_set_num_threads(ctypes.byref(ctypes.c_int(cores))) - - -def query_yes_no(question, default="yes"): - """ - Ask a yes/no question via `input()` and return their answer. - - Parameters - ---------- - question : `srt` - string that is presented to the user. - default : `str`, optional, default is `"yes"` - it is the presumed answer if the user just hits . - It must be "yes" (the default), "no" or None (meaning - an answer is required of the user). - - Returns - ------- - `bool` - The "answer" return value is `True` for `"yes"` or `False` for `"no"`. - - Raises - ------ - `ValueError` - if `default` is not a valid option - """ - valid = {"yes": True, "y": True, "ye": True, - "no": False, "n": False} - if default is None: - prompt = " [y/n] " - elif default == "yes": - prompt = " [Y/n] " - elif default == "no": - prompt = " [y/N] " - else: - raise ValueError("invalid default answer: '%s'" % default) - while True: - sys.stdout.write(question + prompt) - choice = input().lower() - if default is not None and choice == '': - return valid[default] - elif choice in valid: - return valid[choice] - else: - sys.stdout.write("Please respond with 'yes' or 'no' " - "(or 'y' or 'n').\n") \ No newline at end of file