blobmodel 1.2.2__tar.gz → 2.1.0__tar.gz

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 (106) hide show
  1. {blobmodel-1.2.2 → blobmodel-2.1.0}/PKG-INFO +21 -18
  2. {blobmodel-1.2.2 → blobmodel-2.1.0}/README.md +11 -11
  3. {blobmodel-1.2.2 → blobmodel-2.1.0}/blobmodel/__init__.py +8 -2
  4. {blobmodel-1.2.2 → blobmodel-2.1.0}/blobmodel/blob_shape.py +76 -22
  5. blobmodel-2.1.0/blobmodel/blobs.py +418 -0
  6. blobmodel-2.1.0/blobmodel/distributions.py +70 -0
  7. blobmodel-2.1.0/blobmodel/geometry.py +191 -0
  8. blobmodel-2.1.0/blobmodel/model.py +672 -0
  9. blobmodel-2.1.0/blobmodel/plotting.py +204 -0
  10. blobmodel-2.1.0/blobmodel/stochasticality.py +489 -0
  11. {blobmodel-1.2.2 → blobmodel-2.1.0}/blobmodel.egg-info/PKG-INFO +21 -18
  12. blobmodel-2.1.0/blobmodel.egg-info/SOURCES.txt +36 -0
  13. {blobmodel-1.2.2 → blobmodel-2.1.0}/blobmodel.egg-info/requires.txt +5 -0
  14. {blobmodel-1.2.2 → blobmodel-2.1.0}/pyproject.toml +10 -3
  15. {blobmodel-1.2.2 → blobmodel-2.1.0}/tests/test_analytical.py +11 -12
  16. {blobmodel-1.2.2 → blobmodel-2.1.0}/tests/test_blob.py +142 -10
  17. blobmodel-2.1.0/tests/test_blob_factories.py +164 -0
  18. {blobmodel-1.2.2 → blobmodel-2.1.0}/tests/test_changing_t_drain.py +10 -12
  19. blobmodel-2.1.0/tests/test_docs.py +359 -0
  20. blobmodel-2.1.0/tests/test_docs_scripts.py +30 -0
  21. blobmodel-2.1.0/tests/test_geometry.py +250 -0
  22. blobmodel-2.1.0/tests/test_imaging_layout.py +80 -0
  23. blobmodel-2.1.0/tests/test_input_validation.py +239 -0
  24. {blobmodel-1.2.2 → blobmodel-2.1.0}/tests/test_labels.py +23 -11
  25. blobmodel-2.1.0/tests/test_lifetime.py +521 -0
  26. blobmodel-2.1.0/tests/test_one_dim.py +94 -0
  27. {blobmodel-1.2.2 → blobmodel-2.1.0}/tests/test_pulse_shape.py +52 -3
  28. blobmodel-2.1.0/tests/test_seed.py +123 -0
  29. blobmodel-2.1.0/tests/test_show_model.py +129 -0
  30. blobmodel-2.1.0/tests/test_speed_up.py +366 -0
  31. blobmodel-2.1.0/tests/test_stochasticality.py +154 -0
  32. {blobmodel-1.2.2 → blobmodel-2.1.0}/tests/test_str.py +5 -11
  33. {blobmodel-1.2.2 → blobmodel-2.1.0}/tests/test_vx_or_vy=0.py +14 -23
  34. blobmodel-1.2.2/.coveragerc +0 -8
  35. blobmodel-1.2.2/.github/workflows/draft-pdf.yml +0 -27
  36. blobmodel-1.2.2/.github/workflows/workflow.yml +0 -73
  37. blobmodel-1.2.2/.gitignore +0 -148
  38. blobmodel-1.2.2/.readthedocs.yml +0 -32
  39. blobmodel-1.2.2/blobmodel/blobs.py +0 -294
  40. blobmodel-1.2.2/blobmodel/distributions.py +0 -77
  41. blobmodel-1.2.2/blobmodel/geometry.py +0 -80
  42. blobmodel-1.2.2/blobmodel/model.py +0 -369
  43. blobmodel-1.2.2/blobmodel/plotting.py +0 -162
  44. blobmodel-1.2.2/blobmodel/stochasticality.py +0 -252
  45. blobmodel-1.2.2/blobmodel.egg-info/SOURCES.txt +0 -85
  46. blobmodel-1.2.2/docs/1d_animation.gif +0 -0
  47. blobmodel-1.2.2/docs/2-sided_pulse_shape.png +0 -0
  48. blobmodel-1.2.2/docs/2d_animation.gif +0 -0
  49. blobmodel-1.2.2/docs/alignment_false.gif +0 -0
  50. blobmodel-1.2.2/docs/alignment_true.gif +0 -0
  51. blobmodel-1.2.2/docs/blob_factory.rst +0 -76
  52. blobmodel-1.2.2/docs/blob_labels.rst +0 -25
  53. blobmodel-1.2.2/docs/blob_shapes.rst +0 -81
  54. blobmodel-1.2.2/docs/blob_tilt.rst +0 -39
  55. blobmodel-1.2.2/docs/blobmodel.rst +0 -61
  56. blobmodel-1.2.2/docs/change_t_drain_plot.png +0 -0
  57. blobmodel-1.2.2/docs/changing_t_drain_plot.py +0 -50
  58. blobmodel-1.2.2/docs/conf.py +0 -37
  59. blobmodel-1.2.2/docs/contributor_guide.rst +0 -100
  60. blobmodel-1.2.2/docs/create_logo.py +0 -85
  61. blobmodel-1.2.2/docs/drainage_time.rst +0 -19
  62. blobmodel-1.2.2/docs/getting_started.rst +0 -64
  63. blobmodel-1.2.2/docs/index.rst +0 -85
  64. blobmodel-1.2.2/docs/install.rst +0 -25
  65. blobmodel-1.2.2/docs/labels_labels.png +0 -0
  66. blobmodel-1.2.2/docs/labels_n.png +0 -0
  67. blobmodel-1.2.2/docs/logo.png +0 -0
  68. blobmodel-1.2.2/docs/model_geometry.rst +0 -30
  69. blobmodel-1.2.2/docs/modules.rst +0 -4
  70. blobmodel-1.2.2/docs/one_dim.rst +0 -24
  71. blobmodel-1.2.2/docs/plot_pulses.py +0 -33
  72. blobmodel-1.2.2/docs/pulse_shapes.png +0 -0
  73. blobmodel-1.2.2/docs/requirements.txt +0 -136
  74. blobmodel-1.2.2/docs/visualize_model.rst +0 -31
  75. blobmodel-1.2.2/docs/xarray_example.png +0 -0
  76. blobmodel-1.2.2/docs/y-periodicity.gif +0 -0
  77. blobmodel-1.2.2/examples/1d_animation.py +0 -30
  78. blobmodel-1.2.2/examples/2_sided_exp_pulse.py +0 -36
  79. blobmodel-1.2.2/examples/2d_animation.py +0 -21
  80. blobmodel-1.2.2/examples/analyse_output.py +0 -28
  81. blobmodel-1.2.2/examples/blob_tilting.py +0 -86
  82. blobmodel-1.2.2/examples/changing_t_drain.py +0 -54
  83. blobmodel-1.2.2/examples/compare_to_analytical_sol.py +0 -45
  84. blobmodel-1.2.2/examples/custom_blobfactory.py +0 -76
  85. blobmodel-1.2.2/examples/point_process.py +0 -132
  86. blobmodel-1.2.2/examples/show_labels.py +0 -28
  87. blobmodel-1.2.2/examples/single_blob.py +0 -24
  88. blobmodel-1.2.2/paper/paper.bib +0 -213
  89. blobmodel-1.2.2/paper/paper.md +0 -140
  90. blobmodel-1.2.2/readme_files/1d_blobs.gif +0 -0
  91. blobmodel-1.2.2/readme_files/2d_blobs.gif +0 -0
  92. blobmodel-1.2.2/readme_files/logo.png +0 -0
  93. blobmodel-1.2.2/readme_files/tmp5kfvrjp5.svg +0 -1
  94. blobmodel-1.2.2/readme_files/tmpp0mi4n8p.svg +0 -1
  95. blobmodel-1.2.2/requirements.txt +0 -136
  96. blobmodel-1.2.2/tests/.gitignore +0 -139
  97. blobmodel-1.2.2/tests/test_docs.py +0 -266
  98. blobmodel-1.2.2/tests/test_model.py +0 -81
  99. blobmodel-1.2.2/tests/test_one_dim.py +0 -71
  100. blobmodel-1.2.2/tests/test_show_model.py +0 -52
  101. blobmodel-1.2.2/tests/test_stochasticality.py +0 -42
  102. {blobmodel-1.2.2 → blobmodel-2.1.0}/LICENSE +0 -0
  103. {blobmodel-1.2.2 → blobmodel-2.1.0}/blobmodel.egg-info/dependency_links.txt +0 -0
  104. {blobmodel-1.2.2 → blobmodel-2.1.0}/blobmodel.egg-info/top_level.txt +0 -0
  105. {blobmodel-1.2.2 → blobmodel-2.1.0}/setup.cfg +0 -0
  106. {blobmodel-1.2.2 → blobmodel-2.1.0}/tests/test_drain.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: blobmodel
3
- Version: 1.2.2
3
+ Version: 2.1.0
4
4
  Summary: Two dimensional model of propagating blobs
5
5
  Author-email: gregordecristoforo <gregor.decristoforo@gmail.com>
6
6
  License: MIT License
@@ -25,7 +25,7 @@ License: MIT License
25
25
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
26
  SOFTWARE.
27
27
 
28
- Requires-Python: >=3
28
+ Requires-Python: >=3.10
29
29
  Description-Content-Type: text/markdown
30
30
  License-File: LICENSE
31
31
  Requires-Dist: numpy
@@ -34,11 +34,14 @@ Requires-Dist: tqdm
34
34
  Requires-Dist: nptyping
35
35
  Requires-Dist: matplotlib
36
36
  Requires-Dist: netcdf4
37
- Requires-Dist: pytest-cov
38
- Requires-Dist: black
39
- Requires-Dist: mypy
40
- Requires-Dist: sphinx
41
- Requires-Dist: sphinx-rtd-theme
37
+ Provides-Extra: dev
38
+ Requires-Dist: pytest; extra == "dev"
39
+ Requires-Dist: pytest-cov; extra == "dev"
40
+ Requires-Dist: black; extra == "dev"
41
+ Requires-Dist: mypy; extra == "dev"
42
+ Provides-Extra: docs
43
+ Requires-Dist: sphinx; extra == "docs"
44
+ Requires-Dist: sphinx-rtd-theme; extra == "docs"
42
45
  Dynamic: license-file
43
46
 
44
47
  # blobmodel <img align="left" src="https://github.com/uit-cosmo/blobmodel/blob/main/readme_files/logo.png" width="120px" >
@@ -97,9 +100,11 @@ python -m pip install -e .
97
100
  Create a grid on which the blobs are discretized using the `Model` class. The `make_realization()` method computes the output as an xarray dataset which can also be written out as a `netcdf` file if the argument `file_name` is specified. A simple example is shown below:
98
101
 
99
102
  ```Python
100
- from blobmodel import Model, show_model
103
+ from blobmodel import Geometry, Model, show_model
101
104
 
102
- bm = Model(Nx=200, Ny=100, Lx=10, Ly=10, dt=0.1, T=20, num_blobs=100)
105
+ bm = Model(
106
+ geometry=Geometry(Nx=200, Ny=100, Lx=10, Ly=10, dt=0.1, T=20), num_blobs=100
107
+ )
103
108
 
104
109
  ds = bm.make_realization(file_name="example.nc")
105
110
  ```
@@ -110,26 +115,24 @@ show_model(ds)
110
115
  You can specify the blob parameters with a BlobFactory class. The DefaultBlobFactory class has some of the most common distribution functions implemented. An example would look like this:
111
116
 
112
117
  ```Python
113
- from blobmodel import DefaultBlobFactory, DistributionEnum, Model
118
+ from blobmodel import DefaultBlobFactory, DistributionEnum, Geometry, Model
114
119
 
115
120
  # use DefaultBlobFactory to define distribution functions of random variables
116
- my_blob_factory = DefaultBlobFactory(A_dist=DistributionEnum.normal, A_parameter=5)
121
+ my_blob_factory = DefaultBlobFactory(t_drain=100).set_sampler(
122
+ "amplitude", DistributionEnum.normal, free_parameter=5
123
+ )
117
124
 
118
125
  # pass on my_blob_factory when creating the Model
119
126
  bm = Model(
120
- Nx=100,
121
- Ny=100,
122
- Lx=10,
123
- Ly=10,
124
- dt=0.1,
125
- T=20,
127
+ geometry=Geometry(Nx=100, Ny=100, Lx=10, Ly=10, dt=0.1, T=20),
126
128
  blob_factory=my_blob_factory,
127
- t_drain=100,
128
129
  num_blobs=100,
129
130
  )
130
131
  ```
131
132
  Alternatively, you can specify all blob parameters exactly as you want by writing your own class which inherits from BlobFactory. See `examples/custom_blobfactory.py` as an example or take a look at the [blobmodel documentation](https://blobmodel.readthedocs.io/en/latest/?badge=latest).
132
133
 
134
+ Blobs can decay exponentially from their initial time with a drainage time `t_drain`, and/or be given a finite pulse lifetime `t_lifetime`, a Gaussian envelope symmetric about the blob's `t_init` so that the blob grows, peaks and decays. Both are arguments of `DefaultBlobFactory` and are applied to every sampled blob; see the [drainage time](https://blobmodel.readthedocs.io/en/latest/drainage_time.html) and [pulse lifetime](https://blobmodel.readthedocs.io/en/latest/pulse_lifetime.html) sections of the documentation.
135
+
133
136
  ## Contributing
134
137
 
135
138
  Feel free to raise issues about anything. Contributions through pull requests are also very welcome. Please take a look at our [Contributor guide](https://blobmodel.readthedocs.io/en/latest/contributor_guide.html) for further details.
@@ -54,9 +54,11 @@ python -m pip install -e .
54
54
  Create a grid on which the blobs are discretized using the `Model` class. The `make_realization()` method computes the output as an xarray dataset which can also be written out as a `netcdf` file if the argument `file_name` is specified. A simple example is shown below:
55
55
 
56
56
  ```Python
57
- from blobmodel import Model, show_model
57
+ from blobmodel import Geometry, Model, show_model
58
58
 
59
- bm = Model(Nx=200, Ny=100, Lx=10, Ly=10, dt=0.1, T=20, num_blobs=100)
59
+ bm = Model(
60
+ geometry=Geometry(Nx=200, Ny=100, Lx=10, Ly=10, dt=0.1, T=20), num_blobs=100
61
+ )
60
62
 
61
63
  ds = bm.make_realization(file_name="example.nc")
62
64
  ```
@@ -67,26 +69,24 @@ show_model(ds)
67
69
  You can specify the blob parameters with a BlobFactory class. The DefaultBlobFactory class has some of the most common distribution functions implemented. An example would look like this:
68
70
 
69
71
  ```Python
70
- from blobmodel import DefaultBlobFactory, DistributionEnum, Model
72
+ from blobmodel import DefaultBlobFactory, DistributionEnum, Geometry, Model
71
73
 
72
74
  # use DefaultBlobFactory to define distribution functions of random variables
73
- my_blob_factory = DefaultBlobFactory(A_dist=DistributionEnum.normal, A_parameter=5)
75
+ my_blob_factory = DefaultBlobFactory(t_drain=100).set_sampler(
76
+ "amplitude", DistributionEnum.normal, free_parameter=5
77
+ )
74
78
 
75
79
  # pass on my_blob_factory when creating the Model
76
80
  bm = Model(
77
- Nx=100,
78
- Ny=100,
79
- Lx=10,
80
- Ly=10,
81
- dt=0.1,
82
- T=20,
81
+ geometry=Geometry(Nx=100, Ny=100, Lx=10, Ly=10, dt=0.1, T=20),
83
82
  blob_factory=my_blob_factory,
84
- t_drain=100,
85
83
  num_blobs=100,
86
84
  )
87
85
  ```
88
86
  Alternatively, you can specify all blob parameters exactly as you want by writing your own class which inherits from BlobFactory. See `examples/custom_blobfactory.py` as an example or take a look at the [blobmodel documentation](https://blobmodel.readthedocs.io/en/latest/?badge=latest).
89
87
 
88
+ Blobs can decay exponentially from their initial time with a drainage time `t_drain`, and/or be given a finite pulse lifetime `t_lifetime`, a Gaussian envelope symmetric about the blob's `t_init` so that the blob grows, peaks and decays. Both are arguments of `DefaultBlobFactory` and are applied to every sampled blob; see the [drainage time](https://blobmodel.readthedocs.io/en/latest/drainage_time.html) and [pulse lifetime](https://blobmodel.readthedocs.io/en/latest/pulse_lifetime.html) sections of the documentation.
89
+
90
90
  ## Contributing
91
91
 
92
92
  Feel free to raise issues about anything. Contributions through pull requests are also very welcome. Please take a look at our [Contributor guide](https://blobmodel.readthedocs.io/en/latest/contributor_guide.html) for further details.
@@ -1,7 +1,13 @@
1
- from .model import Model
1
+ from .model import Model, to_imaging_dataset
2
2
  from .blobs import Blob
3
3
  from .plotting import show_model
4
- from .stochasticality import BlobFactory, DefaultBlobFactory
4
+ from .stochasticality import (
5
+ BlobFactory,
6
+ BlobListFactory,
7
+ CallableBlobFactory,
8
+ DefaultBlobFactory,
9
+ ParameterSampler,
10
+ )
5
11
  from .geometry import Geometry
6
12
  from .blob_shape import AbstractBlobShape, BlobShapeImpl, BlobShapeEnum
7
13
  from .distributions import DistributionEnum
@@ -78,6 +78,19 @@ def _get_lorentz_shape(theta: np.ndarray, **kwargs) -> np.ndarray:
78
78
  def _get_double_exponential_shape(theta: np.ndarray, **kwargs) -> np.ndarray:
79
79
  """Compute the double-exponential pulse shape.
80
80
 
81
+ The shape is ``exp(-theta / lam)`` for ``theta >= 0`` and
82
+ ``exp(theta / (1 - lam))`` for ``theta < 0``, i.e. ``lam`` is the
83
+ e-folding fraction of the leading (``theta >= 0``) side of the pulse.
84
+ For a blob propagating with ``v_x > 0`` observed at a fixed position,
85
+ ``lam`` is therefore the temporal *rise* fraction of the measured pulse,
86
+ matching the asymmetry-parameter convention of the FPP literature.
87
+
88
+ .. versionchanged:: 2.0.0
89
+ The convention was flipped: ``lam`` previously weighted the trailing
90
+ (``theta < 0``) side, so ``lam`` was the temporal *fall* fraction.
91
+ Code written against older versions that passed ``1 - lam`` to
92
+ compensate should now pass ``lam`` directly.
93
+
81
94
  Parameters
82
95
  ----------
83
96
  theta : np.ndarray
@@ -85,18 +98,29 @@ def _get_double_exponential_shape(theta: np.ndarray, **kwargs) -> np.ndarray:
85
98
  kwargs
86
99
  Additional keyword arguments.
87
100
  lam : float
88
- Asymmetry parameter controlling the shape.
101
+ Asymmetry parameter controlling the shape, in the interval [0, 1].
102
+ The limits are the one-sided shapes: lam = 0 is nonzero only for
103
+ theta < 0 (a pure temporal decay for v_x > 0), lam = 1 is nonzero
104
+ only for theta >= 0 (a pure temporal rise for v_x > 0).
89
105
 
90
106
  Returns
91
107
  -------
92
108
  np.ndarray
93
109
  Array representing the double-exponential pulse shape.
110
+
111
+ Raises
112
+ ------
113
+ ValueError
114
+ If lam lies outside the interval [0, 1].
94
115
  """
95
116
  lam = kwargs["lam"]
96
- assert (lam > 0.0) & (lam < 1.0)
117
+ if not 0.0 <= lam <= 1.0:
118
+ raise ValueError(f"lam must be in the interval [0, 1], got lam = {lam}.")
97
119
  kern = np.zeros(shape=np.shape(theta))
98
- kern[theta < 0] = np.exp(theta[theta < 0] / lam)
99
- kern[theta >= 0] = np.exp(-theta[theta >= 0] / (1 - lam))
120
+ if lam < 1.0:
121
+ kern[theta < 0] = np.exp(theta[theta < 0] / (1 - lam))
122
+ if lam > 0.0:
123
+ kern[theta >= 0] = np.exp(-theta[theta >= 0] / lam)
100
124
  return kern
101
125
 
102
126
 
@@ -171,43 +195,73 @@ def _get_dipole_shape(theta: np.ndarray, **kwargs) -> np.ndarray:
171
195
 
172
196
 
173
197
  class BlobShapeImpl(AbstractBlobShape):
174
- """Implementation of the AbstractPulseShape class."""
198
+ """Implementation of the AbstractBlobShape class."""
175
199
 
176
200
  def __init__(
177
201
  self,
178
- pulse_shape_p=BlobShapeEnum.gaussian,
179
- pulse_shape_s=BlobShapeEnum.gaussian,
202
+ pulse_shape_p: BlobShapeEnum = BlobShapeEnum.gaussian,
203
+ pulse_shape_s: BlobShapeEnum = BlobShapeEnum.gaussian,
180
204
  ):
181
205
  """Initialize the BlobShapeImpl object.
182
206
 
183
- Attributes
207
+ Parameters
184
208
  ----------
185
- pulse_shape_p : str, optional
186
- Type of pulse shape in the principal direction, by default "gauss".
187
- pulse_shape_s : str, optional
188
- Type of pulse shape in the secondary direction, by default "gauss".
209
+ pulse_shape_p : BlobShapeEnum, optional
210
+ Type of pulse shape in the principal direction,
211
+ by default BlobShapeEnum.gaussian.
212
+ pulse_shape_s : BlobShapeEnum, optional
213
+ Type of pulse shape in the secondary direction,
214
+ by default BlobShapeEnum.gaussian.
215
+
216
+ Raises
217
+ ------
218
+ NotImplementedError
219
+ If a pulse shape is not a member of BlobShapeEnum with an
220
+ implemented shape function.
189
221
  """
190
222
  if (
191
- pulse_shape_p not in BlobShapeImpl.__GENERATORS.keys()
192
- or pulse_shape_s not in BlobShapeImpl.__GENERATORS.keys()
223
+ pulse_shape_p not in BlobShapeImpl.__GENERATORS
224
+ or pulse_shape_s not in BlobShapeImpl.__GENERATORS
193
225
  ):
194
226
  raise NotImplementedError(
195
227
  f"{self.__class__.__name__}.blob_shape not implemented"
196
228
  )
197
- self.get_blob_shape_p = BlobShapeImpl.__GENERATORS.get(pulse_shape_p)
198
- self.get_blob_shape_s = BlobShapeImpl.__GENERATORS.get(pulse_shape_s)
229
+ self._shape_p = BlobShapeImpl.__GENERATORS[pulse_shape_p]
230
+ self._shape_s = BlobShapeImpl.__GENERATORS[pulse_shape_s]
199
231
 
200
232
  def get_blob_shape_p(self, theta: np.ndarray, **kwargs) -> np.ndarray:
233
+ """Compute the pulse shape in the principal direction.
234
+
235
+ Parameters
236
+ ----------
237
+ theta : np.ndarray
238
+ Array of theta values.
239
+ kwargs
240
+ Additional keyword arguments passed to the shape function.
241
+
242
+ Returns
243
+ -------
244
+ np.ndarray
245
+ Array representing the pulse shape in the principal direction.
201
246
  """
202
- Compute the pulse shape in the principal direction.
203
- """
204
- raise NotImplementedError
247
+ return self._shape_p(theta, **kwargs)
205
248
 
206
249
  def get_blob_shape_s(self, theta: np.ndarray, **kwargs) -> np.ndarray:
250
+ """Compute the pulse shape in the secondary direction.
251
+
252
+ Parameters
253
+ ----------
254
+ theta : np.ndarray
255
+ Array of theta values.
256
+ kwargs
257
+ Additional keyword arguments passed to the shape function.
258
+
259
+ Returns
260
+ -------
261
+ np.ndarray
262
+ Array representing the pulse shape in the secondary direction.
207
263
  """
208
- Compute the pulse shape in the secondary direction.
209
- """
210
- raise NotImplementedError
264
+ return self._shape_s(theta, **kwargs)
211
265
 
212
266
  __GENERATORS = {
213
267
  BlobShapeEnum.exp: _get_exponential_shape,