mergeron 2024.739145.6__tar.gz → 2025.739265.2__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.

Potentially problematic release.


This version of mergeron might be problematic. Click here for more details.

Files changed (23) hide show
  1. {mergeron-2024.739145.6 → mergeron-2025.739265.2}/PKG-INFO +3 -3
  2. {mergeron-2024.739145.6 → mergeron-2025.739265.2}/pyproject.toml +4 -4
  3. {mergeron-2024.739145.6 → mergeron-2025.739265.2}/src/mergeron/__init__.py +6 -3
  4. {mergeron-2024.739145.6 → mergeron-2025.739265.2}/src/mergeron/core/__init__.py +2 -2
  5. {mergeron-2024.739145.6 → mergeron-2025.739265.2}/src/mergeron/core/empirical_margin_distribution.py +3 -3
  6. {mergeron-2024.739145.6 → mergeron-2025.739265.2}/src/mergeron/core/ftc_merger_investigations_data.py +1 -1
  7. {mergeron-2024.739145.6 → mergeron-2025.739265.2}/src/mergeron/core/guidelines_boundaries.py +8 -9
  8. {mergeron-2024.739145.6 → mergeron-2025.739265.2}/src/mergeron/core/guidelines_boundary_functions.py +1 -1
  9. {mergeron-2024.739145.6 → mergeron-2025.739265.2}/src/mergeron/core/guidelines_boundary_functions_extra.py +4 -4
  10. {mergeron-2024.739145.6 → mergeron-2025.739265.2}/src/mergeron/core/pseudorandom_numbers.py +115 -103
  11. {mergeron-2024.739145.6 → mergeron-2025.739265.2}/src/mergeron/demo/visualize_empirical_margin_distribution.py +1 -1
  12. {mergeron-2024.739145.6 → mergeron-2025.739265.2}/src/mergeron/gen/__init__.py +5 -5
  13. {mergeron-2024.739145.6 → mergeron-2025.739265.2}/src/mergeron/gen/data_generation.py +3 -3
  14. {mergeron-2024.739145.6 → mergeron-2025.739265.2}/src/mergeron/gen/data_generation_functions.py +11 -10
  15. {mergeron-2024.739145.6 → mergeron-2025.739265.2}/src/mergeron/gen/upp_tests.py +5 -5
  16. {mergeron-2024.739145.6 → mergeron-2025.739265.2}/README.rst +0 -0
  17. {mergeron-2024.739145.6 → mergeron-2025.739265.2}/src/mergeron/data/__init__.py +0 -0
  18. {mergeron-2024.739145.6 → mergeron-2025.739265.2}/src/mergeron/data/damodaran_margin_data.xls +0 -0
  19. {mergeron-2024.739145.6 → mergeron-2025.739265.2}/src/mergeron/data/damodaran_margin_data_dict.msgpack +0 -0
  20. {mergeron-2024.739145.6 → mergeron-2025.739265.2}/src/mergeron/data/ftc_invdata.msgpack +0 -0
  21. {mergeron-2024.739145.6 → mergeron-2025.739265.2}/src/mergeron/demo/__init__.py +0 -0
  22. {mergeron-2024.739145.6 → mergeron-2025.739265.2}/src/mergeron/gen/enforcement_stats.py +0 -0
  23. {mergeron-2024.739145.6 → mergeron-2025.739265.2}/src/mergeron/py.typed +0 -0
@@ -1,7 +1,7 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: mergeron
3
- Version: 2024.739145.6
4
- Summary: Merger Policy Analysis using Python
3
+ Version: 2025.739265.2
4
+ Summary: Analyze merger enforcement policy using Python
5
5
  License: MIT
6
6
  Keywords: merger policy analysis,merger guidelines,merger screening,policy presumptions,concentration standards,upward pricing pressure,GUPPI
7
7
  Author: Murthy Kambhampaty
@@ -1,7 +1,7 @@
1
1
  [tool.poetry]
2
2
  name = "mergeron"
3
3
  authors = ["Murthy Kambhampaty <smk@capeconomics.com>"]
4
- description = "Merger Policy Analysis using Python"
4
+ description = "Analyze merger enforcement policy using Python"
5
5
  readme = "README.rst"
6
6
  license = "MIT"
7
7
  keywords = [
@@ -13,7 +13,7 @@ keywords = [
13
13
  "upward pricing pressure",
14
14
  "GUPPI",
15
15
  ]
16
- version = "2024.739145.6"
16
+ version = "2025.739265.2"
17
17
 
18
18
  # Classifiers list: https://pypi.org/classifiers/
19
19
  classifiers = [
@@ -61,6 +61,7 @@ urllib3 = "^2.2.2"
61
61
 
62
62
  [tool.poetry.group.dev.dependencies]
63
63
  icecream = ">=2.1.0"
64
+ jinja2 = ">=3.1.5"
64
65
  mypy = ">=1.8"
65
66
  openpyxl = ">=3.1.2"
66
67
  pendulum = ">=3.0.0"
@@ -74,8 +75,7 @@ sphinx-autoapi = ">=3.0"
74
75
  sphinx-immaterial = ">=0.11"
75
76
  pipdeptree = ">=2.15.1"
76
77
  types-openpyxl = ">=3.0.0"
77
- pyright = "^1.1.380"
78
-
78
+ virtualenv = ">=20.28.0"
79
79
  [tool.ruff]
80
80
 
81
81
  # Exclude a variety of commonly ignored directories.
@@ -2,13 +2,14 @@ from __future__ import annotations
2
2
 
3
3
  import enum
4
4
  from pathlib import Path
5
+ from typing import Literal
5
6
 
6
7
  import numpy as np
7
8
  from numpy.typing import NDArray
8
9
 
9
10
  _PKG_NAME: str = Path(__file__).parent.stem
10
11
 
11
- VERSION = "2024.739145.6"
12
+ VERSION = "2025.739265.2"
12
13
 
13
14
  __version__ = VERSION
14
15
 
@@ -23,11 +24,13 @@ if not DATA_DIR.is_dir():
23
24
 
24
25
  np.set_printoptions(precision=24, floatmode="fixed")
25
26
 
27
+ type HMGPubYear = Literal[1982, 1984, 1992, 2010, 2023]
28
+
26
29
  type ArrayBoolean = NDArray[np.bool_]
27
- type ArrayFloat = NDArray[np.half | np.single | np.double]
30
+ type ArrayFloat = NDArray[np.float16 | np.float32 | np.float64 | np.float128]
28
31
  type ArrayINT = NDArray[np.intp]
29
32
 
30
- type ArrayDouble = NDArray[np.double]
33
+ type ArrayDouble = NDArray[np.float64]
31
34
  type ArrayBIGINT = NDArray[np.int64]
32
35
 
33
36
  DEFAULT_REC_RATIO = 0.85
@@ -4,5 +4,5 @@ from .. import VERSION # noqa: TID252
4
4
 
5
5
  __version__ = VERSION
6
6
 
7
- type MPFloat = mp.mpf # pyright: ignore
8
- type MPMatrix = mp.matrix # pyright: ignore
7
+ type MPFloat = mp.mpf # type: ignore
8
+ type MPMatrix = mp.matrix # type: ignore
@@ -132,7 +132,7 @@ def mgn_data_getter( # noqa: PLR0912
132
132
  _xl_row[1] = int(_xl_row[1])
133
133
  _mgn_dict[_xl_row[0]] = dict(zip(_mgn_row_keys[1:], _xl_row[1:], strict=True))
134
134
 
135
- _ = _data_archive_path.write_bytes(msgpack.packb(_mgn_dict)) # pyright: ignore
135
+ _ = _data_archive_path.write_bytes(msgpack.packb(_mgn_dict))
136
136
 
137
137
  return MappingProxyType(_mgn_dict)
138
138
 
@@ -188,7 +188,7 @@ def mgn_data_builder(
188
188
 
189
189
 
190
190
  def mgn_data_resampler(
191
- _sample_size: int | tuple[int, int] = (10**6, 2),
191
+ _sample_size: int | tuple[int, ...] = (10**6, 2),
192
192
  /,
193
193
  *,
194
194
  seed_sequence: SeedSequence | None = None,
@@ -221,7 +221,7 @@ def mgn_data_resampler(
221
221
  _x, _w, _ = mgn_data_builder(mgn_data_getter())
222
222
 
223
223
  _mgn_kde = stats.gaussian_kde(_x, weights=_w, bw_method="silverman")
224
- _mgn_kde.set_bandwidth(bw_method=_mgn_kde.factor / 3.0) # pyright: ignore
224
+ _mgn_kde.set_bandwidth(bw_method=_mgn_kde.factor / 3.0)
225
225
 
226
226
  if isinstance(_sample_size, int):
227
227
  return np.array(
@@ -198,7 +198,7 @@ def construct_data(
198
198
  )
199
199
  }
200
200
 
201
- _ = INVDATA_ARCHIVE_PATH.write_bytes(msgpack.packb(_invdata)) # pyright: ignore
201
+ _ = INVDATA_ARCHIVE_PATH.write_bytes(msgpack.packb(_invdata))
202
202
 
203
203
  return MappingProxyType(_invdata)
204
204
 
@@ -18,6 +18,7 @@ from .. import ( # noqa: TID252
18
18
  DEFAULT_REC_RATIO,
19
19
  VERSION,
20
20
  ArrayDouble,
21
+ HMGPubYear,
21
22
  RECForm,
22
23
  UPPAggrSelector,
23
24
  )
@@ -29,8 +30,6 @@ __version__ = VERSION
29
30
  mp.dps = 32
30
31
  mp.trap_complex = True
31
32
 
32
- type HMGPubYear = Literal[1982, 1984, 1992, 2010, 2023]
33
-
34
33
 
35
34
  @dataclass(frozen=True)
36
35
  class HMGThresholds:
@@ -55,7 +54,7 @@ class GuidelinesThresholds:
55
54
  """
56
55
 
57
56
  pub_year: HMGPubYear = field(
58
- kw_only=True,
57
+ kw_only=False,
59
58
  default=2023,
60
59
  validator=validators.in_([1982, 1984, 1992, 2010, 2023]),
61
60
  )
@@ -157,7 +156,7 @@ class ConcentrationBoundary:
157
156
  "ΔHHI", "Combined share", "Pre-merger HHI", "Post-merger HHI"
158
157
  ] = field(kw_only=False, default="ΔHHI")
159
158
 
160
- @measure_name.validator # pyright: ignore
159
+ @measure_name.validator
161
160
  def __mnv(
162
161
  _instance: ConcentrationBoundary, _attribute: Attribute[str], _value: str, /
163
162
  ) -> None:
@@ -171,7 +170,7 @@ class ConcentrationBoundary:
171
170
 
172
171
  threshold: float = field(kw_only=False, default=0.01)
173
172
 
174
- @threshold.validator # pyright: ignore
173
+ @threshold.validator
175
174
  def __tv(
176
175
  _instance: ConcentrationBoundary, _attribute: Attribute[float], _value: float, /
177
176
  ) -> None:
@@ -221,14 +220,14 @@ class DiversionRatioBoundary:
221
220
 
222
221
  diversion_ratio: float = field(kw_only=False, default=0.065)
223
222
 
224
- @diversion_ratio.validator # pyright: ignore
223
+ @diversion_ratio.validator
225
224
  def __dvv(
226
225
  _instance: DiversionRatioBoundary,
227
226
  _attribute: Attribute[float],
228
227
  _value: float,
229
228
  /,
230
229
  ) -> None:
231
- if not 0 <= _value <= 1:
230
+ if not (isinstance(_value, float) and 0 <= _value <= 1):
232
231
  raise ValueError(
233
232
  "Margin-adjusted benchmark share ratio must lie between 0 and 1."
234
233
  )
@@ -260,7 +259,7 @@ class DiversionRatioBoundary:
260
259
 
261
260
  """
262
261
 
263
- @recapture_form.validator # pyright: ignore
262
+ @recapture_form.validator
264
263
  def __rsv(
265
264
  _instance: DiversionRatioBoundary,
266
265
  _attribute: Attribute[RECForm],
@@ -356,7 +355,7 @@ class DiversionRatioBoundary:
356
355
 
357
356
  _upp_agg_kwargs |= {"agg_method": _aggregator, "weighting": _wgt_type}
358
357
 
359
- _boundary = _upp_agg_fn(_share_ratio, self.recapture_ratio, **_upp_agg_kwargs) # pyright: ignore # TypedDict redefinition
358
+ _boundary = _upp_agg_fn(_share_ratio, self.recapture_ratio, **_upp_agg_kwargs)
360
359
  object.__setattr__(self, "coordinates", _boundary.coordinates)
361
360
  object.__setattr__(self, "area", _boundary.area)
362
361
 
@@ -909,7 +909,7 @@ def lerp[LerpT: (float, MPFloat, ArrayDouble, ArrayBIGINT)](
909
909
  elif _r == 1:
910
910
  return _x2
911
911
  else:
912
- return _r * _x2 + (1 - _r) * _x1 # pyright: ignore
912
+ return _r * _x2 + (1 - _r) * _x1
913
913
 
914
914
 
915
915
  def round_cust(
@@ -91,7 +91,7 @@ def hhi_delta_boundary_qdtr(_dh_val: float = 0.01, /) -> GuidelinesBoundaryCalla
91
91
 
92
92
  _hhi_bdry_area = 2 * (
93
93
  _s_nought
94
- + mp.quad(lambdify(_s_1, _hhi_bdry, "mpmath"), (_s_nought, 1 - _s_nought)) # pyright: ignore
94
+ + mp.quad(lambdify(_s_1, _hhi_bdry, "mpmath"), (_s_nought, 1 - _s_nought))
95
95
  )
96
96
 
97
97
  return GuidelinesBoundaryCallable(
@@ -159,7 +159,7 @@ def shrratio_boundary_qdtr_wtd_avg(
159
159
  2
160
160
  * (
161
161
  _s_naught
162
- + mp.quad(lambdify(_s_1, _bdry_func, "mpmath"), (_s_naught, _s_mid)) # pyright: ignore
162
+ + mp.quad(lambdify(_s_1, _bdry_func, "mpmath"), (_s_naught, _s_mid))
163
163
  )
164
164
  - (_s_mid**2 + _s_naught**2)
165
165
  )
@@ -189,7 +189,7 @@ def shrratio_boundary_qdtr_wtd_avg(
189
189
  ),
190
190
  (0, _s_mid),
191
191
  )
192
- ).real # pyright: ignore
192
+ ).real
193
193
  - _s_mid**2
194
194
  )
195
195
 
@@ -209,7 +209,7 @@ def shrratio_boundary_qdtr_wtd_avg(
209
209
 
210
210
  _bdry_func = solve(_bdry_eqn, _s_2)[0]
211
211
  _bdry_area = float(
212
- 2 * (mp.quad(lambdify(_s_1, _bdry_func, "mpmath"), (0, _s_mid))) # pyright: ignore
212
+ 2 * (mp.quad(lambdify(_s_1, _bdry_func, "mpmath"), (0, _s_mid)))
213
213
  - _s_mid**2
214
214
  )
215
215
 
@@ -6,12 +6,15 @@ https://github.com/numpy/numpy/issues/16313.
6
6
 
7
7
  """
8
8
 
9
+ from __future__ import annotations
10
+
9
11
  import concurrent.futures
10
12
  from collections.abc import Sequence
11
13
  from multiprocessing import cpu_count
12
14
  from typing import Literal
13
15
 
14
16
  import numpy as np
17
+ from attrs import Attribute, define, field
15
18
  from numpy.random import PCG64DXSM, Generator, SeedSequence
16
19
 
17
20
  from .. import VERSION, ArrayDouble # noqa: TID252
@@ -19,7 +22,8 @@ from .. import VERSION, ArrayDouble # noqa: TID252
19
22
  __version__ = VERSION
20
23
 
21
24
  NTHREADS = 2 * cpu_count()
22
- DEFAULT_DIST_PARMS = np.array([0.0, 1.0], np.float64)
25
+ DEFAULT_DIST_PARMS: ArrayDouble = np.array([0.0, 1.0], float)
26
+ DEFAULT_BETA_DIST_PARMS: ArrayDouble = np.array([1.0, 1.0], float)
23
27
 
24
28
 
25
29
  def prng(_s: SeedSequence | None = None, /) -> np.random.Generator:
@@ -106,6 +110,7 @@ def gen_seed_seq_list_default(
106
110
  return [SeedSequence(_s, pool_size=8) for _s in generated_entropy[:_sseq_list_len]]
107
111
 
108
112
 
113
+ @define
109
114
  class MultithreadedRNG:
110
115
  """Fill given array on demand with pseudo-random numbers as specified.
111
116
 
@@ -114,93 +119,102 @@ class MultithreadedRNG:
114
119
  If a seed sequence is provided, it is used in a thread-safe way
115
120
  to generate repeatable i.i.d. draws. All arguments are validated
116
121
  before commencing multithreaded random number generation.
122
+ """
117
123
 
118
- Parameters
119
- ----------
120
- _out_array
121
- The output array to which generated data are written.
122
- Its dimensions define the size of the sample.
123
- dist_type
124
- Distribution for the generated random numbers
125
- dist_parms
126
- Parameters, if any, for tailoring random number generation
127
- seed_sequence
128
- SeedSequence object for generating repeatable draws.
129
- nthreads
130
- Number of threads to spawn for random number generation.
124
+ values: ArrayDouble = field(kw_only=False, default=None)
125
+ """Output array to which generated data are over-written
131
126
 
127
+ Array-length defines the number of i.i.d. (vector) draws.
132
128
  """
133
129
 
134
- def __init__(
135
- self,
136
- _out_array: ArrayDouble,
137
- /,
138
- *,
139
- dist_type: Literal[
140
- "Beta", "Dirichlet", "Gaussian", "Normal", "Random", "Uniform"
141
- ] = "Uniform",
142
- dist_parms: ArrayDouble | None = DEFAULT_DIST_PARMS,
143
- seed_sequence: SeedSequence | None = None,
144
- nthreads: int = NTHREADS,
145
- ):
146
- self.thread_count = nthreads
147
-
148
- _seed_sequence = seed_sequence or SeedSequence(pool_size=8)
149
- self._random_generators = [
150
- prng(_t) for _t in _seed_sequence.spawn(self.thread_count)
151
- ]
152
-
153
- self.sample_sz = len(_out_array)
154
-
155
- if dist_type not in (_rdts := ("Beta", "Dirichlet", "Normal", "Uniform")):
156
- raise ValueError("Specified distribution must be one of {_rdts}")
157
-
158
- if not (dist_parms is None or isinstance(dist_parms, Sequence | np.ndarray)):
159
- raise ValueError(
160
- "When specified, distribution parameters must be a list, tuple or Numpy array"
161
- )
162
- if isinstance(dist_parms, Sequence):
163
- dist_parms = np.array(dist_parms)
164
- elif not dist_parms.any():
165
- dist_parms = None
166
-
167
- self.dist_type = dist_type
168
-
169
- if dist_parms is None or np.array_equal(dist_parms, DEFAULT_DIST_PARMS):
170
- match dist_type:
171
- case "Uniform":
172
- self.dist_type = "Random"
173
- case "Normal":
174
- self.dist_type = "Gaussian"
175
- case "Beta" | "Dirichlet":
176
- raise ValueError(
177
- f"parameter specification, {f'"{dist_parms}"'} "
178
- f"is invalid for specified distribution, f{'"{dist_type}"'}."
179
- )
180
- case _:
181
- raise ValueError(
182
- f"Invalid distributions specified, {f'"{dist_type}"'}."
183
- )
130
+ dist_type: Literal[
131
+ "Beta", "Dirichlet", "Gaussian", "Normal", "Random", "Uniform"
132
+ ] = field(kw_only=True, default="Uniform")
133
+ """Distribution for the generated random numbers.
134
+
135
+ Default is "Uniform".
136
+ """
137
+
138
+ @dist_type.validator
139
+ def __dtv(
140
+ _instance: MultithreadedRNG, _attribute: Attribute[str], _value: str, /
141
+ ) -> None:
142
+ if _value not in (
143
+ _rdts := ("Beta", "Dirichlet", "Gaussian", "Normal", "Random", "Uniform")
144
+ ):
145
+ raise ValueError(f"Specified distribution must be one of {_rdts}")
146
+
147
+ dist_parms: ArrayDouble | None = field(kw_only=True, default=DEFAULT_DIST_PARMS)
148
+ """Parameters, if any, for tailoring random number generation
149
+ """
184
150
 
185
- elif dist_type == "Dirichlet":
186
- if len(dist_parms) != _out_array.shape[1]:
151
+ @dist_parms.validator
152
+ def __dpv(
153
+ _instance: MultithreadedRNG, _attribute: Attribute[str], _value: ArrayDouble, /
154
+ ) -> None:
155
+ if _value is not None:
156
+ if not isinstance(_value, Sequence | np.ndarray):
187
157
  raise ValueError(
188
- f"Insufficient shape parameters for requested Dirichlet sample "
189
- f"of size, {_out_array.shape}"
158
+ "When specified, distribution parameters must be a list, tuple or Numpy array"
190
159
  )
191
160
 
192
- elif (_lrdp := len(dist_parms)) != 2:
193
- raise ValueError(f"Expected 2 parameters, got, {_lrdp}")
194
-
195
- self.dist_parms = dist_parms
161
+ elif (
162
+ _instance.dist_type != "Dirichlet"
163
+ and (_lrdp := len(_value)) != (_trdp := 2)
164
+ ) or (
165
+ _instance.dist_type == "Dirichlet"
166
+ and (_lrdp := len(_value)) != (_trdp := _instance.values.shape[1])
167
+ ):
168
+ raise ValueError(f"Expected {_trdp} parameters, got, {_lrdp}")
169
+
170
+ elif (
171
+ _instance.dist_type in ("Beta", "Dirichlet")
172
+ and (np.array(_value) <= 0.0).any()
173
+ ):
174
+ raise ValueError(
175
+ "Shape and location parameters must be strictly positive"
176
+ )
196
177
 
197
- self.values = _out_array
198
- self.executor = concurrent.futures.ThreadPoolExecutor(self.thread_count)
178
+ seed_sequence: SeedSequence | None = field(kw_only=True, default=None)
179
+ """Seed sequence for generating random numbers."""
199
180
 
200
- self.step_size = (len(self.values) / self.thread_count).__ceil__()
181
+ nthreads: int = field(kw_only=True, default=NTHREADS)
182
+ """Number of threads to spawn for random number generation."""
201
183
 
202
184
  def fill(self) -> None:
203
- """Fill the provided output array with random numbers as specified."""
185
+ """Fill the provided output array with random number draws as specified."""
186
+
187
+ if (
188
+ self.dist_parms is None
189
+ or not (
190
+ _dist_parms := np.array(self.dist_parms) # one-shot conversion
191
+ ).any()
192
+ ):
193
+ if self.dist_type == "Beta":
194
+ _dist_parms = DEFAULT_BETA_DIST_PARMS
195
+ elif self.dist_type == "Dirichlet":
196
+ _dist_parms = np.ones(self.values.shape[1], float)
197
+ else:
198
+ _dist_parms = DEFAULT_DIST_PARMS
199
+
200
+ if self.dist_parms is None or np.array_equal(
201
+ self.dist_parms, DEFAULT_DIST_PARMS
202
+ ):
203
+ if self.dist_type == "Uniform":
204
+ _dist_type = "Random"
205
+ elif self.dist_type == "Normal":
206
+ _dist_type = "Gaussian"
207
+ else:
208
+ _dist_type = self.dist_type
209
+
210
+ _step_size = (len(self.values) / self.nthreads).__ceil__()
211
+ # int; function gives float unsuitable for slicing
212
+
213
+ _seed_sequence = self.seed_sequence or SeedSequence(pool_size=8)
214
+
215
+ _random_generators = tuple(
216
+ prng(_t) for _t in _seed_sequence.spawn(self.nthreads)
217
+ )
204
218
 
205
219
  def _fill(
206
220
  _rng: np.random.Generator,
@@ -213,37 +227,35 @@ class MultithreadedRNG:
213
227
  ) -> None:
214
228
  _sz: tuple[int, ...] = _out[_first:_last].shape
215
229
  match _dist_type:
216
- case "Random":
217
- _rng.random(out=_out[_first:_last])
218
- case "Uniform":
219
- _uni_l, _uni_h = _dist_parms
220
- _out[_first:_last] = _rng.uniform(_uni_l, _uni_h, size=_sz)
221
- case "Dirichlet":
222
- _out[_first:_last] = _rng.dirichlet(_dist_parms, size=_sz[:-1])
223
230
  case "Beta":
224
231
  _shape_a, _shape_b = _dist_parms
225
232
  _out[_first:_last] = _rng.beta(_shape_a, _shape_b, size=_sz)
233
+ case "Dirichlet":
234
+ _out[_first:_last] = _rng.dirichlet(_dist_parms, size=_sz[:-1])
235
+ case "Gaussian":
236
+ _rng.standard_normal(out=_out[_first:_last])
226
237
  case "Normal":
227
238
  _mu, _sigma = _dist_parms
228
239
  _out[_first:_last] = _rng.normal(_mu, _sigma, size=_sz)
240
+ case "Random":
241
+ _rng.random(out=_out[_first:_last])
242
+ case "Uniform":
243
+ _uni_l, _uni_h = _dist_parms
244
+ _out[_first:_last] = _rng.uniform(_uni_l, _uni_h, size=_sz)
229
245
  case _:
230
- _rng.standard_normal(out=_out[_first:_last])
231
-
232
- futures = {}
233
- for i in range(self.thread_count):
234
- _range_first = i * self.step_size
235
- _range_last = min(len(self.values), (i + 1) * self.step_size)
236
- args = (
237
- _fill,
238
- self._random_generators[i],
239
- self.dist_type,
240
- self.dist_parms,
241
- self.values,
242
- _range_first,
243
- _range_last,
244
- )
245
- futures[self.executor.submit(*args)] = i # type: ignore
246
- concurrent.futures.wait(futures)
247
-
248
- def __del__(self) -> None:
249
- self.executor.shutdown(False)
246
+ "Unreachable. The validator would have rejected this as invalid."
247
+
248
+ with concurrent.futures.ThreadPoolExecutor(self.nthreads) as _executor:
249
+ for i in range(self.nthreads):
250
+ _range_first = i * _step_size
251
+ _range_last = min(len(self.values), (i + 1) * _step_size)
252
+
253
+ _executor.submit(
254
+ _fill,
255
+ _random_generators[i],
256
+ _dist_type,
257
+ _dist_parms,
258
+ self.values,
259
+ _range_first,
260
+ _range_last,
261
+ )
@@ -45,7 +45,7 @@ with warnings.catch_warnings():
45
45
  ])
46
46
 
47
47
  mgn_kde = stats.gaussian_kde(mgn_data_obs, weights=mgn_data_wts, bw_method="silverman")
48
- mgn_kde.set_bandwidth(bw_method=mgn_kde.factor / 3.0) # pyright: ignore
48
+ mgn_kde.set_bandwidth(bw_method=mgn_kde.factor / 3.0)
49
49
 
50
50
  mgn_ax.plot(
51
51
  (_xv := np.linspace(0, BIN_COUNT, 10**5) / BIN_COUNT),
@@ -70,7 +70,7 @@ class SHRDistribution(enum.StrEnum):
70
70
  DIR_FLAT_CONSTR = "Flat Dirichlet - Constrained"
71
71
  """Impose minimum probablility weight on each firm-count
72
72
 
73
- Only firm-counts with probability weight of no less than 3%
73
+ Only firm-counts with probability weight of 3% or more
74
74
  are included for data generation.
75
75
  """
76
76
 
@@ -119,7 +119,7 @@ class ShareSpec:
119
119
  dist_type: SHRDistribution = field(default=SHRDistribution.DIR_FLAT)
120
120
  """See :class:`SHRDistribution`"""
121
121
 
122
- @dist_type.validator # pyright: ignore
122
+ @dist_type.validator
123
123
  def __dtv(
124
124
  _i: ShareSpec, _a: Attribute[SHRDistribution], _v: SHRDistribution
125
125
  ) -> None:
@@ -152,7 +152,7 @@ class ShareSpec:
152
152
 
153
153
  """
154
154
 
155
- @dist_parms.validator # pyright: ignore
155
+ @dist_parms.validator
156
156
  def __dpv(
157
157
  _i: ShareSpec,
158
158
  _a: Attribute[ArrayFloat | ArrayINT | None],
@@ -205,7 +205,7 @@ class ShareSpec:
205
205
 
206
206
  """
207
207
 
208
- @recapture_ratio.validator # pyright: ignore
208
+ @recapture_ratio.validator
209
209
  def __rrv(_i: ShareSpec, _a: Attribute[float], _v: float) -> None:
210
210
  if _v and not (0 < _v <= 1):
211
211
  raise ValueError("Recapture ratio must lie in the interval, [0, 1).")
@@ -265,7 +265,7 @@ class PCMSpec:
265
265
 
266
266
  """
267
267
 
268
- @dist_parms.validator # pyright: ignore
268
+ @dist_parms.validator
269
269
  def __dpv(
270
270
  _i: PCMSpec, _a: Attribute[ArrayDouble | None], _v: ArrayDouble | None
271
271
  ) -> None:
@@ -85,7 +85,7 @@ class MarketSample:
85
85
  )
86
86
  """Margin specification, see :class:`PCMSpec`"""
87
87
 
88
- @pcm_spec.validator # pyright: ignore
88
+ @pcm_spec.validator
89
89
  def __psv(self, _a: Attribute[PCMSpec], _v: PCMSpec, /) -> None:
90
90
  if (
91
91
  self.share_spec.recapture_form == RECForm.FIXED
@@ -437,8 +437,8 @@ class MarketSample:
437
437
  _enf_parm_vec,
438
438
  _sim_test_regime,
439
439
  **_sim_enf_cnts_kwargs,
440
- saved_array_name_suffix=f"{saved_array_name_suffix}_{_iter_id:0{2 + int(np.ceil(np.log10(_iter_count)))}d}", # pyright: ignore
441
- seed_seq_list=_rng_seed_seq_list_ch, # pyright: ignore
440
+ saved_array_name_suffix=f"{saved_array_name_suffix}_{_iter_id:0{2 + int(np.ceil(np.log10(_iter_count)))}d}",
441
+ seed_seq_list=_rng_seed_seq_list_ch,
442
442
  )
443
443
  for _iter_id, _rng_seed_seq_list_ch in enumerate(_rng_seed_seq_list)
444
444
  )
@@ -138,7 +138,7 @@ def gen_market_shares_uniform(
138
138
 
139
139
  """
140
140
 
141
- _frmshr_array = np.empty((_s_size, 2), dtype=np.float64)
141
+ _frmshr_array: ArrayDouble = np.empty((_s_size, 2), dtype=np.float64)
142
142
 
143
143
  _dist_parms_mktshr = _dist_parms_mktshr or DEFAULT_DIST_PARMS
144
144
  _mrng = MultithreadedRNG(
@@ -151,10 +151,11 @@ def gen_market_shares_uniform(
151
151
  _mrng.fill()
152
152
  # Convert draws on U[0, 1] to Uniformly-distributed draws on simplex, s_1 + s_2 <= 1
153
153
  _frmshr_array.sort(axis=1)
154
- _frmshr_array = np.column_stack((
155
- _frmshr_array[:, 0],
156
- _frmshr_array[:, 1] - _frmshr_array[:, 0],
157
- ))
154
+
155
+ _frmshr_array = np.array(
156
+ (_frmshr_array[:, 0], _frmshr_array[:, 1] - _frmshr_array[:, 0]),
157
+ _frmshr_array.dtype,
158
+ ).T # faster than np.stack() and variants
158
159
 
159
160
  # Keep only share combinations representing feasible mergers
160
161
  # This is a no-op for 64-bit floats, but is necessary for 32-bit floats
@@ -372,7 +373,7 @@ def gen_market_shares_dirichlet(
372
373
  else SeedSequence(pool_size=8)
373
374
  )
374
375
 
375
- _mktshr_array = np.empty((_s_size, len(_dir_alphas)))
376
+ _mktshr_array = np.empty((_s_size, len(_dir_alphas)), dtype=np.float64)
376
377
  _mrng = MultithreadedRNG(
377
378
  _mktshr_array,
378
379
  dist_type="Dirichlet",
@@ -399,8 +400,8 @@ def gen_market_shares_dirichlet(
399
400
  _aggregate_purchase_prob = np.empty((_s_size, 1), dtype=np.float64)
400
401
  _aggregate_purchase_prob.fill(np.nan)
401
402
  if _recapture_form == RECForm.OUTIN:
402
- _aggregate_purchase_prob = 1 - _mktshr_array[:, [-1]]
403
- _mktshr_array = _mktshr_array[:, :-1] / _aggregate_purchase_prob
403
+ _aggregate_purchase_prob = 1 - _mktshr_array[:, [-1]] # type: ignore
404
+ _mktshr_array = _mktshr_array[:, :-1] / _aggregate_purchase_prob # type: ignore
404
405
 
405
406
  return ShareDataSample(
406
407
  _mktshr_array,
@@ -540,7 +541,7 @@ def gen_margin_price_data(
540
541
  _pr_max_ratio = 5.0
541
542
  match _price_spec:
542
543
  case PriceSpec.SYM:
543
- _nth_firm_price = np.ones((len(_frmshr_array), 1))
544
+ _nth_firm_price = np.ones((len(_frmshr_array), 1), np.float64)
544
545
  case PriceSpec.POS:
545
546
  _price_array, _nth_firm_price = (
546
547
  np.ceil(_p * _pr_max_ratio) for _p in (_frmshr_array, _nth_firm_share)
@@ -555,7 +556,7 @@ def gen_margin_price_data(
555
556
  1 + np.arange(_pr_max_ratio), size=(len(_frmshr_array), 3)
556
557
  )
557
558
  _price_array = _price_array_gen[:, :2]
558
- _nth_firm_price = _price_array_gen[:, [2]]
559
+ _nth_firm_price = _price_array_gen[:, [2]] # type: ignore
559
560
  # del _price_array_gen
560
561
  case PriceSpec.CSY:
561
562
  # TODO:
@@ -21,7 +21,7 @@ from .. import ( # noqa
21
21
  ArrayDouble,
22
22
  ArrayFloat,
23
23
  ArrayINT,
24
- RECForm,
24
+ HMGPubYear,
25
25
  UPPAggrSelector,
26
26
  )
27
27
  from ..core import guidelines_boundaries as gbl # noqa: TID252
@@ -326,16 +326,16 @@ def _compute_test_array_seq(
326
326
 
327
327
 
328
328
  def initialize_hd5(
329
- _h5_path: Path, _hmg_pub_year: gbl.HMGPubYear, _test_regime: UPPTestRegime, /
329
+ _h5_path: Path, _hmg_pub_year: HMGPubYear, _test_regime: UPPTestRegime, /
330
330
  ) -> tuple[SaveData, str]:
331
331
  _h5_title = f"HMG version: {_hmg_pub_year}; Test regime: {_test_regime}"
332
332
  if _h5_path.is_file():
333
333
  _h5_path.unlink()
334
- _h5_file = ptb.open_file(_h5_path, mode="w", title=_h5_title) # pyright: ignore
334
+ _h5_file = ptb.open_file(_h5_path, mode="w", title=_h5_title)
335
335
  _save_data_to_file: SaveData = (True, _h5_file, _h5_file.root)
336
336
  _next_subgroup_name_root = "enf_{}_{}_{}_{}".format(
337
337
  _hmg_pub_year,
338
- *(getattr(_test_regime, _f.name).name for _f in _test_regime.__attrs_attrs__), # pyright: ignore
338
+ *(getattr(_test_regime, _f.name).name for _f in _test_regime.__attrs_attrs__),
339
339
  )
340
340
  return _save_data_to_file, _next_subgroup_name_root
341
341
 
@@ -383,7 +383,7 @@ def save_array_to_hdf5(
383
383
  _h5_array_name,
384
384
  atom=ptb.Atom.from_dtype(_array_obj.dtype),
385
385
  shape=_array_obj.shape,
386
- filters=ptb.Filters(complevel=3, complib="blosc:lz4hc", fletcher32=True), # pyright: ignore
386
+ filters=ptb.Filters(complevel=3, complib="blosc:lz4hc", fletcher32=True),
387
387
  )
388
388
  _h5_array[:] = _array_obj
389
389