funcsim 0.1.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. funcsim/__init__.py +54 -0
  2. funcsim/addist.py +74 -0
  3. funcsim/conversions.py +203 -0
  4. funcsim/core.py +279 -0
  5. funcsim/cpt.py +466 -0
  6. funcsim/cvmdist.py +79 -0
  7. funcsim/dependence.py +619 -0
  8. funcsim/distfit.py +286 -0
  9. funcsim/ecdfgof.py +193 -0
  10. funcsim/edf.py +25 -0
  11. funcsim/eut.py +92 -0
  12. funcsim/imanconover.py +115 -0
  13. funcsim/kde.py +168 -0
  14. funcsim/ksdist.py +206 -0
  15. funcsim/multicore.py +77 -0
  16. funcsim/nearby.py +92 -0
  17. funcsim/plotting.py +569 -0
  18. funcsim/rdarrays.py +91 -0
  19. funcsim/screen.py +425 -0
  20. funcsim/shapiro.py +25 -0
  21. funcsim/shrinkage.py +288 -0
  22. funcsim/testsim.py +50 -0
  23. funcsim/utests.py +52 -0
  24. funcsim/vect.py +67 -0
  25. funcsim-0.1.4.dist-info/METADATA +83 -0
  26. funcsim-0.1.4.dist-info/RECORD +59 -0
  27. funcsim-0.1.4.dist-info/WHEEL +5 -0
  28. funcsim-0.1.4.dist-info/licenses/LICENSE +29 -0
  29. funcsim-0.1.4.dist-info/top_level.txt +2 -0
  30. tests/__init__.py +0 -0
  31. tests/test_GBM.py +113 -0
  32. tests/test_GBM_draw-stdnorm.py +66 -0
  33. tests/test_GBM_zero-draws.py +62 -0
  34. tests/test_addist.py +68 -0
  35. tests/test_conversions.py +153 -0
  36. tests/test_copula.py +76 -0
  37. tests/test_covtocorr.py +23 -0
  38. tests/test_cpt.py +95 -0
  39. tests/test_cvmdist.py +86 -0
  40. tests/test_ecdfgof.py +88 -0
  41. tests/test_edf.py +9 -0
  42. tests/test_hist_not_data.py +47 -0
  43. tests/test_imanconover.py +40 -0
  44. tests/test_indirect_next.py +28 -0
  45. tests/test_joint_norm.py +67 -0
  46. tests/test_kde.py +9 -0
  47. tests/test_ksdist.py +126 -0
  48. tests/test_nearbypd.py +28 -0
  49. tests/test_rdarrays.py +45 -0
  50. tests/test_recdyn_with_no_data0.py +36 -0
  51. tests/test_shrink.py +64 -0
  52. tests/test_spearman.py +10 -0
  53. tests/test_static.py +34 -0
  54. tests/test_static_zero-draws.py +21 -0
  55. tests/test_superfluous_data0.py +40 -0
  56. tests/test_testsim.py +24 -0
  57. tests/test_utests.py +21 -0
  58. tests/test_vect.py +50 -0
  59. tests/test_zzz_compare.py +13 -0
funcsim/__init__.py ADDED
@@ -0,0 +1,54 @@
1
+ import sys
2
+ import os
3
+ sys.path.append(os.path.dirname(os.path.realpath(__file__)))
4
+
5
+ from core import simulate
6
+
7
+ from dependence import covtocorr
8
+ from dependence import spearman
9
+ from dependence import MvNorm
10
+ from dependence import MvKde
11
+ from dependence import CopulaGauss
12
+ from dependence import CopulaStudent
13
+ from dependence import CopulaClayton
14
+ from dependence import CopulaGumbel
15
+
16
+ from imanconover import imanconover
17
+
18
+ from shrinkage import shrink
19
+ from nearby import nearestpd
20
+
21
+ from edf import edf
22
+
23
+ from kde import Kde
24
+
25
+ from ecdfgof import kstest
26
+ from ecdfgof import adtest
27
+ from ecdfgof import cvmtest
28
+
29
+ from shapiro import swtest
30
+
31
+ from distfit import fit
32
+ from distfit import compare
33
+
34
+ from utests import utests
35
+
36
+ from screen import screen
37
+
38
+ from cpt import cpt, utilPower, utilNormLog, weightTK, weightPrelec1
39
+ from cpt import weightPrelec2, cptBV
40
+
41
+ from eut import eut, utilIsoelastic
42
+
43
+ from plotting import fan, twofuncs, histpdf, dblscat, qqplot, show
44
+
45
+ def version():
46
+ """
47
+ Return the current version of the funcsim package.
48
+
49
+ Returns
50
+ -------
51
+ str
52
+ The version string of the package (e.g., '0.1.4').
53
+ """
54
+ return "0.1.4"
funcsim/addist.py ADDED
@@ -0,0 +1,74 @@
1
+ """
2
+ Distributions of the Anderson-Darling statistic.
3
+
4
+ After doi:18637/jss.v009.i02.
5
+
6
+ Original Work (scikit-gof) Copyright (c) 2015 Wojciech Ruszczewski <scipy@wr.waw.pl>
7
+
8
+ Modified Work Copyright (c) 2020 h-bryant
9
+ """
10
+ from __future__ import division
11
+
12
+ from numpy import exp, log, sqrt
13
+ from scipy.stats import rv_continuous
14
+
15
+ from vect import vectorize
16
+
17
+
18
+ class ad_unif_gen(rv_continuous):
19
+ """
20
+ Approximate distribution of the uniform Anderson-Darling statistic
21
+ (with the hypothesized distribution continuous and fully specified).
22
+ """
23
+ def _argcheck(self, samples):
24
+ return samples > 0
25
+
26
+ @vectorize(otypes=(float,))
27
+ def _cdf(self, statistic, samples):
28
+ if samples == 1:
29
+ # Exact distribution for a single sample (a bit more precise than
30
+ # the approximation). See doi:10.1214/aoms/1177704850 equation 8.
31
+ if statistic <= log(4) - 1:
32
+ return 0.
33
+ else:
34
+ return sqrt(1 - 4 * exp(-1 - statistic))
35
+ pinf = ad_unif_inf(statistic)
36
+ return pinf + ad_unif_fix(samples, pinf)
37
+
38
+
39
+ ad_unif = ad_unif_gen(a=0, name='ad-unif', shapes='samples')
40
+
41
+
42
+ def ad_unif_inf(statistic):
43
+ """
44
+ Approximates the limiting distribution to about 5 decimal digits.
45
+ """
46
+ z = statistic
47
+ if z < 2:
48
+ return (exp(-1.2337141 / z) / sqrt(z) *
49
+ (2.00012 + (.247105 - (.0649821 - (.0347962 -
50
+ (.011672 - .00168691 * z) * z) * z) * z) * z))
51
+ else:
52
+ return exp(-exp(1.0776 - (2.30695 - (.43424 - (.082433 -
53
+ (.008056 - .0003146 * z) * z) * z) * z) * z))
54
+
55
+
56
+ g1 = lambda x: sqrt(x) * (1 - x) * (49 * x - 102)
57
+ g2 = lambda x: (-.00022633 + (6.54034 - (14.6538 - (14.458 -
58
+ (8.259 - 1.91864 * x) * x) * x) * x) * x)
59
+ g3 = lambda x: (-130.2137 + (745.2337 - (1705.091 - (1950.646 -
60
+ (1116.36 - 255.7844 * x) * x) * x) * x) * x)
61
+
62
+
63
+ def ad_unif_fix(samples, pinf):
64
+ """
65
+ Corrects the limiting distribution for a finite sample size.
66
+ """
67
+ n = samples
68
+ c = .01265 + .1757 / n
69
+ if pinf < c:
70
+ return (((.0037 / n + .00078) / n + .00006) / n) * g1(pinf / c)
71
+ elif pinf < .8:
72
+ return ((.01365 / n + .04213) / n) * g2((pinf - c) / (.8 - c))
73
+ else:
74
+ return g3(pinf) / n
funcsim/conversions.py ADDED
@@ -0,0 +1,203 @@
1
+ from typing import TypeVar, Union, Sequence, Any
2
+ import numpy as np
3
+ import pandas as pd
4
+ import xarray as xr
5
+ from numpy.typing import NDArray
6
+
7
+ T = TypeVar("T")
8
+
9
+ VectorLike = Union[
10
+ Sequence[T], # list[T], tuple[T], etc.
11
+ NDArray[Any], # np.NDArray of any dtype (runtime shape not enforced)
12
+ pd.Series, # 1‑D pandas Series
13
+ pd.DataFrame, # 1‑D pandas DataFrame
14
+ xr.DataArray # 1‑D xarray DataArray
15
+ ]
16
+
17
+ ArrayLike = Union[
18
+ NDArray[Any], # np.NDArray of any dtype (runtime shape not enforced)
19
+ pd.DataFrame, # 2‑D pandas Series
20
+ xr.DataArray # 2‑D xarray DataArray
21
+ ]
22
+
23
+
24
+ def vlValidate(vl: VectorLike) -> bool:
25
+ """
26
+ Validate if the input is a vector-like object.
27
+
28
+ Parameters
29
+ ----------
30
+ vl : VectorLike
31
+ The object to validate.
32
+
33
+ Returns
34
+ -------
35
+ bool
36
+ True if the input is a vector-like object, False otherwise.
37
+ """
38
+ if isinstance(vl, (list, tuple, pd.Series)):
39
+ return True
40
+ elif isinstance(vl, np.ndarray):
41
+ a = np.asarray(vl)
42
+ if a.ndim > 2:
43
+ return False
44
+ if a.ndim == 2:
45
+ if not (a.shape[0] == 1 or a.shape[1] == 1):
46
+ return False
47
+ return True
48
+ elif isinstance(vl, xr.DataArray):
49
+ a = np.asarray(vl)
50
+ if a.ndim > 2:
51
+ return False
52
+ if a.ndim == 2:
53
+ if not (a.shape[0] == 1 or a.shape[1] == 1):
54
+ return False
55
+ return True
56
+ elif isinstance(vl, pd.DataFrame):
57
+ a = np.asarray(vl)
58
+ if a.ndim > 2:
59
+ return False
60
+ if a.ndim == 2:
61
+ if not (a.shape[0] == 1 or a.shape[1] == 1):
62
+ return False
63
+ return True
64
+ return False
65
+
66
+
67
+ def vlToArray(vl: VectorLike) -> NDArray:
68
+ """
69
+ Convert a vector-like object to a 1-D NumPy array.
70
+
71
+ Parameters
72
+ ----------
73
+ vl : VectorLike
74
+ The vector-like object to convert.
75
+
76
+ Returns
77
+ -------
78
+ NDArray
79
+ The converted 1-D NumPy array.
80
+ """
81
+ if not vlValidate(vl):
82
+ raise ValueError("argument passed is not a vector-like object")
83
+ if isinstance(vl, (list, tuple)):
84
+ a = np.array(vl)
85
+ elif isinstance(vl, pd.Series):
86
+ a = vl.to_numpy()
87
+ elif isinstance(vl, xr.DataArray):
88
+ a = vl.values
89
+ else:
90
+ a = np.asarray(vl)
91
+ return a.flatten() # Ensure the array is 1-D
92
+
93
+
94
+ def alValidate(al: ArrayLike) -> bool:
95
+ """
96
+ Validate if the input is a 2-D array-like object.
97
+
98
+ Parameters
99
+ ----------
100
+ al : ArrayLike
101
+ The object to validate.
102
+
103
+ Returns
104
+ -------
105
+ bool
106
+ True if the input is a array-like object, False otherwise.
107
+ """
108
+ if isinstance(al, np.ndarray):
109
+ a = np.asarray(al)
110
+ elif isinstance(al, xr.DataArray):
111
+ a = np.asarray(al)
112
+ elif isinstance(al, pd.DataFrame):
113
+ a = np.asarray(al)
114
+ else:
115
+ return False
116
+ if a.ndim != 2:
117
+ return False
118
+ return True
119
+
120
+
121
+ def alToArray(al: ArrayLike) -> NDArray:
122
+ """
123
+ Convert an array-like object to a 2-D NumPy array.
124
+
125
+ Parameters
126
+ ----------
127
+ al : ArrayLike
128
+ The array-like object to convert.
129
+
130
+ Returns
131
+ -------
132
+ NDArray
133
+ The converted 2-D NumPy array.
134
+ """
135
+ if not alValidate(al):
136
+ raise ValueError("argument passed is not an array-like object")
137
+ if isinstance(al, pd.DataFrame):
138
+ a = al.to_numpy()
139
+ elif isinstance(al, xr.DataArray):
140
+ a = al.values
141
+ else: # Assume it's a NumPy array
142
+ a = np.asarray(al)
143
+ return a
144
+
145
+
146
+ def vlCoords(vl: VectorLike) -> pd.Index:
147
+ """
148
+ Get or create coordinates for a vector-like object.
149
+
150
+ Parameters
151
+ ----------
152
+ vl : VectorLike
153
+ The vector-like object to convert.
154
+
155
+ Returns
156
+ -------
157
+ pandas.Index
158
+ A pandas index with coordinates corresponding to the vector-like object.
159
+ """
160
+ if not vlValidate(vl):
161
+ raise ValueError("argument passed is not a vector-like object")
162
+ if isinstance(vl, (list, tuple)):
163
+ return pd.Index(list(range(max(shape(vl)))))
164
+ if isinstance(vl, (np.ndarray)):
165
+ if vl.ndim == 1:
166
+ return pd.Index(list(range(len(vl))))
167
+ if vl.ndim == 2:
168
+ if vl.shape[0] == 1:
169
+ return pd.Index(list(range(vl.shape[1])))
170
+ if vl.shape[1] == 1:
171
+ return pd.Index(list(range(vl.shape[0])))
172
+ if isinstance(vl, pd.Series):
173
+ return vl.index
174
+ if isinstance(vl, xr.DataArray):
175
+ dim_size_to_name = dict(zip(vl.shape, vl.dims))
176
+ longest_dim = max(dim_size_to_name.keys())
177
+ ret = vl.coords[dim_size_to_name[longest_dim]]
178
+ return pd.Index(ret)
179
+
180
+
181
+ def alColNames(al: ArrayLike) -> pd.Index:
182
+ """
183
+ Get or create column names from an array-like object.
184
+
185
+ Parameters
186
+ ----------
187
+ al : ArrayLike
188
+ The array-like object to process.
189
+
190
+ Returns
191
+ -------
192
+ pandas.Index
193
+ Column names of the array-like object.
194
+ """
195
+ if not alValidate(al):
196
+ raise ValueError("argument passed is not an array-like object")
197
+ if isinstance(al, pd.DataFrame):
198
+ return al.columns
199
+ if isinstance(al, xr.DataArray):
200
+ return al.coords[al.dims[-1]]
201
+ if isinstance(al, np.ndarray):
202
+ return pd.Index([f"v{i}" for i in range(al.shape[1])])
203
+ raise ValueError("Unsupported array-like type")
funcsim/core.py ADDED
@@ -0,0 +1,279 @@
1
+ import sys
2
+ from copy import deepcopy as copy
3
+ import numpy as np
4
+ import pandas as pd
5
+ import xarray as xr
6
+ import multicore
7
+ import rdarrays
8
+ from scipy import stats
9
+ from collections.abc import Callable, Generator
10
+ from typing import Optional
11
+ import inspect
12
+
13
+
14
+ def _get_arg_count(func):
15
+ sig = inspect.signature(func)
16
+ params = sig.parameters.values()
17
+
18
+ # Count only parameters that are positional or keyword
19
+ # (excluding *args and **kwargs)
20
+ return sum(
21
+ 1 for p in params
22
+ if p.kind in (inspect.Parameter.POSITIONAL_ONLY,
23
+ inspect.Parameter.POSITIONAL_OR_KEYWORD,
24
+ inspect.Parameter.KEYWORD_ONLY)
25
+ )
26
+
27
+
28
+ def _checkhist0(hist0):
29
+ # check that user's hist0 seems sane. Return list of variable names
30
+ # in hist0 order.
31
+
32
+ if isinstance(hist0, xr.DataArray) is False:
33
+ raise ValueError('"hist0" must be an xarray.DataArray')
34
+
35
+ # check that hist0 has the right dimension names
36
+ hist0Coords = hist0.coords
37
+ if not len(hist0Coords) == 2:
38
+ raise ValueError('"hist0" must have exactly two dimensions')
39
+ if not ("variables" in hist0Coords.keys() and
40
+ "steps" in hist0Coords.keys()):
41
+ raise ValueError('"hist0" must have dimensions "variables" and "steps"')
42
+
43
+ # Check for an appropriate index for the 'variables' dimension
44
+ stepsCoords = hist0Coords["steps"]
45
+ if not (np.issubdtype(stepsCoords.dtype, np.integer) or
46
+ isinstance(stepsCoords.to_index(), pd.PeriodIndex)):
47
+ raise ValueError('"hist0" must have either an integer index or a '
48
+ 'pandas PeriodIndex for the "steps" dimension')
49
+
50
+ return list(hist0Coords["variables"])
51
+
52
+
53
+ def _checkf(f, data0):
54
+ # Count number of times 'f', and any code 'f' invokes, calls 'next(draw)'
55
+ # If 'data0' is None, infer that 'f' is the 'trial' func for a cross-sec sim
56
+ # If 'data0' is a xr.DataArray, infer that 'f' is 'step' for rec. dyn. sim
57
+ # Also, check that f returns something that makes sense.
58
+ fakeugen = _countgen()
59
+
60
+ # check that 'f' returns a dict
61
+ out = f(fakeugen, data0)
62
+ if type(out) != dict:
63
+ raise ValueError('"f" function must return a dict')
64
+
65
+ # check that the dict returned by 'f' has variable names as keys
66
+ varnames = list(out.keys())
67
+ if sum(1 if type(k) is str else 0 for k in varnames) < len(varnames):
68
+ raise ValueError('The keys of the dictionary returned by "f" must be '
69
+ 'variable names as strings')
70
+
71
+ calls = int(round((next(fakeugen) - 0.5) * 10**4))
72
+ return calls, varnames
73
+
74
+
75
+ def _countgen():
76
+ # dummy generator for counting calls but always returning approximately 0.5.
77
+ i = 0
78
+ while i < int(10000):
79
+ yield 0.5 + float(i) * 10**-4
80
+ i += 1
81
+
82
+
83
+ def _makewgen(w, r):
84
+ # given an array 'w' of indep. draws, where rows reflect variables
85
+ # and columns reflect trials, make a generator for trial 'r' tha emits
86
+ # a number of draws equal to the number of RVs
87
+ i = 0
88
+ while i < w.shape[0]:
89
+ yield w[i, r]
90
+ i += 1
91
+
92
+
93
+ def _strat(R):
94
+ # stratified sampling for a single uniformly distributed random variable.
95
+ # 'R' (an int) is the number of draws to perform
96
+ # returns a numpy array of floats, each in the interval [0, 1).
97
+ draws = (np.arange(0, R) + np.random.uniform(0.0, 1.0, R)) / float(R)
98
+ np.random.shuffle(draws) # warning: mutating 'draws'
99
+ return draws
100
+
101
+
102
+ def _mcs(K, R):
103
+ # Monte Carlo sampling. For each of K independent uniform (over the
104
+ # unit interval) random variables, create a sample of length R.
105
+ # 'K' (an int) is the number of variables
106
+ # 'R' (an int) is the number of trials
107
+ # returns a KxR numpy array containing draws
108
+ return np.concatenate([[np.random.uniform(0.0, 1.0, R)]
109
+ for i in range(K)], axis=0)
110
+
111
+
112
+ def _lhs(K, R):
113
+ # Latin hypercube sampling. For each of K independent uniform (over the
114
+ # unit interval) random variables, create a stratified sample of length R.
115
+ # 'K' (an int) is the number of variables
116
+ # 'R' (an int) is the number of trials
117
+ # returns a KxR numpy array containing draws
118
+ return np.concatenate([[_strat(R)] for i in range(K)], axis=0)
119
+
120
+
121
+ def _extendIndex(idx, nNewSteps):
122
+ # extend a 'steps' index; should work for ints or pd.Period
123
+ if len(idx) == 0: # no previous index; just use integers for the new index
124
+ return list(range(nNewSteps))
125
+ newIdx = list(idx)
126
+ [newIdx.append(newIdx[-1] + 1) for i in range(nNewSteps)]
127
+ return newIdx
128
+
129
+
130
+ def simulate(f: Callable[[Generator[int, float, None],
131
+ Optional[rdarrays.RDdata]],
132
+ dict[str, float]],
133
+ ntrials: Optional[int] = 500,
134
+ nsteps: Optional[int] = 1,
135
+ hist0: Optional[xr.DataArray] = None,
136
+ multi: Optional[bool] = False,
137
+ seed: Optional[int] = 6,
138
+ stdnorm: Optional[bool] = False,
139
+ sampling: Optional[str] = 'lh'
140
+ ) -> xr.DataArray:
141
+ """
142
+ Stochastic simulation.
143
+
144
+ Parameters
145
+ ----------
146
+ f : function
147
+ Function that performs a single trial in a static simulation or a s
148
+ single step through time in a recursive dynamic simulation. Should take
149
+ 'ugen' as a first argument in either case, where 'ugen' will be a
150
+ generator that emits standard uniform draws (or standard normal draws,
151
+ if `stdnorm` is True) that will be passed to f by ``simulate``.
152
+ In the case of a recursive dynamic simulation that employs past values,
153
+ f should take 'data' as a second argument, where this will be a type of
154
+ array that is also provided by ``simulate.`` This function should return
155
+ a dict with variable names (as strings) as keys and values for those
156
+ variables (as floats) as values.
157
+ ntrials : int, optional
158
+ The number of trials to perform. Default is 500.
159
+ nsteps : int, optional
160
+ The number of steps to perform in each trial. Default is 1.
161
+ hist0 : xarray.DataArray, optional
162
+ Initial and/or historical data relevant to the simulation.
163
+ Should have dimensions 'variables' and 'steps'. Any lagged values
164
+ needed (recalled) by `f` must be in `hist0`. The 'steps' dimension
165
+ should have either an integer index or a pandas PeriodIndex.
166
+ multi : bool, optional
167
+ Use multiple processes/cores for the simulation. Default is False.
168
+ seed : int, optional
169
+ Seed for pseudo-random number generation. Default is 6.
170
+ stdnorm : book, optional
171
+ If False, ``next(draw)`` within `trialf` will return standard uniform
172
+ random draws. If True, ``next(draw)`` will return standard normal draws.
173
+ Default is False.
174
+ sampling : {'lh', 'mc'}, optional
175
+ If 'lh', Latin Hypercube sampling is employed. If 'mc', simple
176
+ Monte Carlo sampling is employed. Default is 'lh'.
177
+
178
+ Returns
179
+ -------
180
+ xarray.DataArray
181
+ 3-D xarray.DataArray with dimensions 'trials', 'variables', and 'steps'.
182
+ """
183
+ if hist0 is not None:
184
+ _checkhist0(hist0)
185
+
186
+ # check that we know how to cope with the types for the 'steps' index
187
+ sidx = hist0.indexes['steps']
188
+ if len(sidx) > 0:
189
+ if not type(sidx[0]) in [pd.Period, np.int64]:
190
+ raise ValueError("'hist0' should have either an integer index"
191
+ " or a pandas.PeriodIndex for the 'steps' "
192
+ "dimension.")
193
+ else:
194
+ sidx = []
195
+ # create an empty hist0.
196
+ variables = np.array([], dtype=str)
197
+ steps = np.array([], dtype=int)
198
+ hist0 = xr.DataArray(
199
+ data=np.empty((0, 0)),
200
+ dims=("steps", "variables"),
201
+ coords={"steps": steps, "variables": variables}
202
+ )
203
+
204
+ # check for 'f'
205
+ if not isinstance(f, Callable):
206
+ raise ValueError('"f" must be a callable function')
207
+
208
+ # infer number of arguments in 'f'. If it takes only a single arg, wrap it
209
+ # in an outer func that takes "hist" as a second arg
210
+ numb_f_args = _get_arg_count(f)
211
+ if numb_f_args == 1:
212
+ stepf = lambda draw, hist: f(draw)
213
+ elif numb_f_args == 2:
214
+ stepf = f
215
+ else:
216
+ raise ValueError('"f" should take two arguments at most')
217
+
218
+ # indexes for the final output xr.DataArray
219
+ varNames = hist0.indexes['variables']
220
+ namePositionsPrelim = {nm: i for i, nm in enumerate(varNames)}
221
+ stepLabels = _extendIndex(sidx, nsteps)
222
+
223
+ # create example data object in which data for one trail can accumulate
224
+ dataPrelim = rdarrays.RDdata(hist0.to_masked_array(),
225
+ nsteps, namePositionsPrelim)
226
+
227
+ # infer number of random vars reflected in 'step' fucntion
228
+ # and the variable names being returned by 'step' and their order
229
+ rvs, stepfNames = _checkf(stepf, copy(dataPrelim))
230
+
231
+ # specify that the data objects that accumulate data for each trial
232
+ # will reflect the union of the variables in 'hist0' and the variables
233
+ # returned by 'step'
234
+ # breakpoint()
235
+ varNamesList = list(varNames)
236
+ if stepfNames == varNamesList:
237
+ finalNames = varNamesList
238
+ finalHist0 = hist0
239
+ else:
240
+ finalNames = list(set(varNamesList).union(set(stepfNames)))
241
+ finalHist0 = hist0.copy()
242
+ finalHist0 = finalHist0.reindex(variables=finalNames, fill_value=np.nan)
243
+
244
+ # create a DataArray to hold the data for one trial
245
+ # create example data object in which data for one trail can accumulate
246
+ namePositions = {nm: i for i, nm in enumerate(finalNames)}
247
+ data = rdarrays.RDdata(finalHist0.to_masked_array(), nsteps, namePositions)
248
+
249
+ # draws for all RVs in all time steps, w/ sampling stratified across trials
250
+ if rvs > 0:
251
+ np.random.seed(seed)
252
+ if sampling == 'lh':
253
+ u = _lhs(rvs * nsteps, ntrials) # np.array: (rvs*steps) x trials
254
+ elif sampling == 'mc':
255
+ u = _mcs(rvs * nsteps, ntrials) # monte carlo
256
+ else:
257
+ raise ValueError('sampling must be "lh" or "mc"')
258
+ w = stats.norm.ppf(u) if stdnorm is True else u
259
+
260
+ def trial(r):
261
+ wgen = _makewgen(w, r) if rvs > 0 else None # 'w' gener. for trial 'r'
262
+ # perform all time steps for one trial
263
+ # return _recurse(f=lambda x: step(x, wgen), x0=copy(data), S=steps)
264
+ dataWorking = copy(data)
265
+ for s in range(nsteps):
266
+ # step 's' of trial 'r'
267
+ dataWorking.append(stepf(wgen, dataWorking))
268
+ return dataWorking
269
+
270
+ # create and return 3-D output DataArray, with new dimension 'trials'
271
+ if multi is True:
272
+ out = multicore.parmap(lambda r: trial(r)._a, range(ntrials))
273
+ else:
274
+ out = [trial(r)._a for r in range(ntrials)]
275
+
276
+ prelim = xr.DataArray(out, coords=[('trials', list(range(ntrials))),
277
+ ('variables', finalNames),
278
+ ('steps', stepLabels)])
279
+ return prelim.transpose('trials', 'variables', 'steps')