mergeron 2024.739099.1__tar.gz → 2024.739104.1__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 (33) hide show
  1. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/PKG-INFO +1 -1
  2. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/pyproject.toml +1 -1
  3. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/src/mergeron/__init__.py +7 -10
  4. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/src/mergeron/core/ftc_merger_investigations_data.py +4 -2
  5. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/src/mergeron/core/guidelines_boundaries.py +11 -11
  6. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/src/mergeron/core/guidelines_boundary_functions.py +3 -3
  7. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/src/mergeron/gen/__init__.py +140 -160
  8. mergeron-2024.739104.1/src/mergeron/gen/data_generation.py +518 -0
  9. mergeron-2024.739099.1/src/mergeron/gen/_data_generation_functions.py → mergeron-2024.739104.1/src/mergeron/gen/data_generation_functions.py +225 -139
  10. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/src/mergeron/gen/enforcement_stats.py +31 -33
  11. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/src/mergeron/gen/upp_tests.py +70 -212
  12. mergeron-2024.739099.1/src/mergeron/gen/data_generation.py +0 -283
  13. mergeron-2024.739099.1/src/mergeron/gen/market_sample.py +0 -143
  14. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/README.rst +0 -0
  15. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/src/mergeron/License.txt +0 -0
  16. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/src/mergeron/core/__init__.py +0 -0
  17. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/src/mergeron/core/damodaran_margin_data.py +0 -0
  18. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/src/mergeron/core/guidelines_boundary_functions_extra.py +0 -0
  19. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/src/mergeron/core/pseudorandom_numbers.py +0 -0
  20. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/src/mergeron/data/__init__.py +0 -0
  21. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/src/mergeron/data/damodaran_margin_data.xls +0 -0
  22. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/src/mergeron/data/damodaran_margin_data_dict.msgpack +0 -0
  23. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/src/mergeron/data/ftc_invdata.msgpack +0 -0
  24. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/src/mergeron/data/jinja2_LaTeX_templates/clrrate_cis_summary_table_template.tex.jinja2 +0 -0
  25. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/src/mergeron/data/jinja2_LaTeX_templates/ftcinvdata_byhhianddelta_table_template.tex.jinja2 +0 -0
  26. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/src/mergeron/data/jinja2_LaTeX_templates/ftcinvdata_summary_table_template.tex.jinja2 +0 -0
  27. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/src/mergeron/data/jinja2_LaTeX_templates/ftcinvdata_summarypaired_table_template.tex.jinja2 +0 -0
  28. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/src/mergeron/data/jinja2_LaTeX_templates/mergeron.cls +0 -0
  29. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/src/mergeron/data/jinja2_LaTeX_templates/mergeron_table_collection_template.tex.jinja2 +0 -0
  30. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/src/mergeron/data/jinja2_LaTeX_templates/setup_tikz_tables.tex +0 -0
  31. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/src/mergeron/demo/__init__.py +0 -0
  32. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/src/mergeron/demo/visualize_empirical_margin_distribution.py +0 -0
  33. {mergeron-2024.739099.1 → mergeron-2024.739104.1}/src/mergeron/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mergeron
3
- Version: 2024.739099.1
3
+ Version: 2024.739104.1
4
4
  Summary: Merger Policy Analysis using Python
5
5
  License: MIT
6
6
  Keywords: merger policy analysis,merger guidelines,merger screening,policy presumptions,concentration standards,upward pricing pressure,GUPPI
@@ -13,7 +13,7 @@ keywords = [
13
13
  "upward pricing pressure",
14
14
  "GUPPI",
15
15
  ]
16
- version = "2024.739099.1"
16
+ version = "2024.739104.1"
17
17
 
18
18
  # Classifiers list: https://pypi.org/classifiers/
19
19
  classifiers = [
@@ -2,14 +2,14 @@ from __future__ import annotations
2
2
 
3
3
  import enum
4
4
  from pathlib import Path
5
- from typing import TypeAlias, TypeVar
5
+ from typing import TypeAlias
6
6
 
7
7
  import numpy as np
8
- from numpy.typing import NBitBase, NDArray
8
+ from numpy.typing import NDArray
9
9
 
10
10
  _PKG_NAME: str = Path(__file__).parent.stem
11
11
 
12
- VERSION = "2024.739099.1"
12
+ VERSION = "2024.739104.1"
13
13
 
14
14
  __version__ = VERSION
15
15
 
@@ -22,24 +22,21 @@ If the subdirectory doesn't exist, it is created on package invocation.
22
22
  if not DATA_DIR.is_dir():
23
23
  DATA_DIR.mkdir(parents=False)
24
24
 
25
-
26
25
  np.set_printoptions(precision=18)
27
26
 
28
27
 
29
- TI = TypeVar("TI", bound=NBitBase)
30
- ArrayINT = NDArray[np.integer[TI]]
31
- TF = TypeVar("TF", bound=NBitBase)
32
- ArrayFloat = NDArray[np.floating[TF]]
28
+ ArrayINT = NDArray[np.intp]
29
+ ArrayFloat = NDArray[np.half | np.single | np.double]
33
30
 
34
31
 
35
32
  ArrayBoolean: TypeAlias = NDArray[np.bool_]
36
33
 
37
- ArrayDouble: TypeAlias = NDArray[np.float64]
34
+ ArrayDouble: TypeAlias = NDArray[np.double]
38
35
  ArrayBIGINT: TypeAlias = NDArray[np.int64]
39
36
 
40
37
 
41
38
  @enum.unique
42
- class RECConstants(enum.StrEnum):
39
+ class RECTypes(enum.StrEnum):
43
40
  """Recapture rate - derivation methods."""
44
41
 
45
42
  INOUT = "inside-out"
@@ -285,7 +285,7 @@ def _construct_new_period_data(
285
285
  # Consistency here means that the number of investigations reported
286
286
  # in each period is no less than the number reported in
287
287
  # any prior period.Although the time periods for table 3.2 through 3.5
288
- # are not the samein the data for 1996-2005 and 1996-2007 as in
288
+ # are not the same in the data for 1996-2005 and 1996-2007 as in
289
289
  # the data for the other periods, they are nonetheless shorter than
290
290
  # the period 1996-2011, and hence the counts reported for 1996-2011
291
291
  # cannot be less than those reported in these prior periods. Note that
@@ -337,7 +337,8 @@ def _construct_new_period_data(
337
337
  _invdata_cuml_array[:, -3:-1] - _invdata_base_array[:, -3:-1] # type: ignore
338
338
  )
339
339
 
340
- # To examine the number of corrected values per table,
340
+ # # // spellchecker: disable
341
+ # To examine the number of corrected values per table, // spellchecker: disable
341
342
  # uncomment the statements below
342
343
  # _invdata_array_bld_tbc = where(
343
344
  # _invdata_array_bld_enfcls < 0, _invdata_array_bld_enfcls, 0
@@ -347,6 +348,7 @@ def _construct_new_period_data(
347
348
  # f"{_data_period}, {_table_no}, {_invdata_ind_group}:",
348
349
  # abs(np.einsum('ij->', invdata_array_bld_tbc))
349
350
  # )
351
+ # # // spellchecker: disable
350
352
 
351
353
  # Enforce non-negativity
352
354
  _invdata_array_bld_enfcls = np.stack((
@@ -13,7 +13,7 @@ import numpy as np
13
13
  from attrs import Attribute, field, frozen, validators
14
14
  from mpmath import mp, mpf # type: ignore
15
15
 
16
- from .. import VERSION, ArrayDouble, RECConstants, UPPAggrSelector # noqa: TID252
16
+ from .. import VERSION, ArrayDouble, RECTypes, UPPAggrSelector # noqa: TID252
17
17
  from . import guidelines_boundary_functions as gbfn
18
18
 
19
19
  __version__ = VERSION
@@ -223,14 +223,14 @@ def _divr_value_validator(
223
223
 
224
224
  def _rec_spec_validator(
225
225
  _instance: DiversionRatioBoundary,
226
- _attribute: Attribute[RECConstants],
227
- _value: RECConstants,
226
+ _attribute: Attribute[RECTypes],
227
+ _value: RECTypes,
228
228
  /,
229
229
  ) -> None:
230
- if _value == RECConstants.OUTIN and _instance.recapture_rate:
230
+ if _value == RECTypes.OUTIN and _instance.recapture_rate:
231
231
  raise ValueError(
232
232
  f"Invalid recapture specification, {_value!r}. "
233
- "You may consider specifying `mergeron.RECConstants.INOUT` here, and "
233
+ "You may consider specifying `mergeron.RECTypes.INOUT` here, and "
234
234
  'assigning the default recapture rate as attribute, "recapture_rate" of '
235
235
  "this `DiversionRatioBoundarySpec` object."
236
236
  )
@@ -265,24 +265,24 @@ class DiversionRatioBoundary:
265
265
  kw_only=False, default=0.85, validator=validators.instance_of(float)
266
266
  )
267
267
 
268
- recapture_form: RECConstants | None = field(
268
+ recapture_form: RECTypes | None = field(
269
269
  kw_only=True,
270
- default=RECConstants.INOUT,
270
+ default=RECTypes.INOUT,
271
271
  validator=(
272
- validators.instance_of((type(None), RECConstants)),
272
+ validators.instance_of((type(None), RECTypes)),
273
273
  _rec_spec_validator,
274
274
  ),
275
275
  )
276
276
  """
277
277
  The form of the recapture rate.
278
278
 
279
- When :attr:`mergeron.RECConstants.INOUT`, the recapture rate for
279
+ When :attr:`mergeron.RECTypes.INOUT`, the recapture rate for
280
280
  he product having the smaller market-share is assumed to equal the default,
281
281
  and the recapture rate for the product with the larger market-share is
282
282
  computed assuming MNL demand. Fixed recapture rates are specified as
283
- :attr:`mergeron.RECConstants.FIXED`. (To specify that recapture rates be
283
+ :attr:`mergeron.RECTypes.FIXED`. (To specify that recapture rates be
284
284
  constructed from the generated purchase-probabilities for products in
285
- the market and for the outside good, specify :attr:`mergeron.RECConstants.OUTIN`.)
285
+ the market and for the outside good, specify :attr:`mergeron.RECTypes.OUTIN`.)
286
286
 
287
287
  The GUPPI boundary is a continuum of diversion ratio boundaries conditional on
288
288
  price-cost margins, :math:`d_{ij} = g_i * p_i / (m_j * p_j)`,
@@ -667,7 +667,7 @@ def shrratio_boundary_max(
667
667
  """
668
668
 
669
669
  # _r_val is not needed for max boundary, but is specified for consistency
670
- # of function call with other shrratio_mgnsym_boundary functions
670
+ # of function call with other share-ratio boundary functions
671
671
  del _r_val
672
672
  _delta_star = mpf(f"{_delta_star}")
673
673
  _s_intcpt = _delta_star
@@ -883,9 +883,9 @@ def boundary_plot(*, mktshares_plot_flag: bool = True) -> tuple[Any, ...]:
883
883
  mktshares_plot_flag: bool = False,
884
884
  mktshares_axlbls_flag: bool = False,
885
885
  ) -> mpa.Axes:
886
- # Set the width of axis gridlines, and tick marks:
886
+ # Set the width of axis grid lines, and tick marks:
887
887
  # both axes, both major and minor ticks
888
- # Frame, grid, and facecolor
888
+ # Frame, grid, and face color
889
889
  for _spos0 in "left", "bottom":
890
890
  _ax1.spines[_spos0].set_linewidth(0.5)
891
891
  _ax1.spines[_spos0].set_zorder(5)
@@ -1,5 +1,6 @@
1
1
  """
2
- Defines constants and containers for industry data generation and testing
2
+ Defines constants, specifications (classes with attributes defining varous parameters) and
3
+ containers for industry data generation and testing.
3
4
 
4
5
  """
5
6
 
@@ -7,17 +8,20 @@ from __future__ import annotations
7
8
 
8
9
  import enum
9
10
  from dataclasses import dataclass
10
- from typing import ClassVar, Protocol
11
+ from typing import ClassVar, NamedTuple, Protocol
11
12
 
12
13
  import numpy as np
13
- from attrs import Attribute, cmp_using, define, field, frozen, validators
14
+ from attrs import Attribute, cmp_using, field, frozen, validators
15
+ from numpy.random import SeedSequence
14
16
 
15
17
  from .. import ( # noqa: TID252
16
18
  VERSION,
17
19
  ArrayBIGINT,
18
20
  ArrayBoolean,
19
21
  ArrayDouble,
20
- RECConstants,
22
+ ArrayFloat,
23
+ ArrayINT,
24
+ RECTypes,
21
25
  UPPAggrSelector,
22
26
  )
23
27
  from ..core.pseudorandom_numbers import DIST_PARMS_DEFAULT # noqa: TID252
@@ -31,8 +35,15 @@ FCOUNT_WTS_DEFAULT = np.divide(
31
35
  )
32
36
 
33
37
 
38
+ class SeedSequenceData(NamedTuple):
39
+ mktshr_rng_seed_seq: SeedSequence
40
+ pcm_rng_seed_seq: SeedSequence
41
+ fcount_rng_seed_seq: SeedSequence | None
42
+ pr_rng_seed_seq: SeedSequence | None
43
+
44
+
34
45
  @enum.unique
35
- class PriceConstants(tuple[bool, str | None], enum.ReprEnum):
46
+ class PriceSpec(tuple[bool, str | None], enum.ReprEnum):
36
47
  """Price specification.
37
48
 
38
49
  Whether prices are symmetric and, if not, the direction of correlation, if any.
@@ -46,7 +57,7 @@ class PriceConstants(tuple[bool, str | None], enum.ReprEnum):
46
57
 
47
58
 
48
59
  @enum.unique
49
- class SHRConstants(enum.StrEnum):
60
+ class SHRDistributions(enum.StrEnum):
50
61
  """Market share distributions."""
51
62
 
52
63
  UNI = "Uniform"
@@ -80,39 +91,84 @@ class SHRConstants(enum.StrEnum):
80
91
  """
81
92
 
82
93
 
83
- @frozen
94
+ @frozen(kw_only=False)
84
95
  class ShareSpec:
85
96
  """Market share specification
86
97
 
87
98
  A key feature of market-share specification in this package is that
88
99
  the draws represent markets with multiple different firm-counts.
89
100
  Firm-counts are unspecified if the share distribution is
90
- :attr:`mergeron.SHRConstants.UNI`, for Dirichlet-distributed market-shares,
101
+ :attr:`mergeron.SHRDistributions.UNI`, for Dirichlet-distributed market-shares,
91
102
  the default specification is that firm-counts vary between
92
103
  2 and 7 firms with each value equally likely.
93
104
 
94
105
  Notes
95
106
  -----
96
- If :attr:`mergeron.gen.ShareSpec.dist_type`:code:` == `:attr:`mergeron.gen.SHRConstants.UNI`,
107
+ If :attr:`mergeron.gen.ShareSpec.dist_type`:code:` == `:attr:`mergeron.gen.SHRDistributions.UNI`,
97
108
  then it is infeasible that
98
- :attr:`mergeron.gen.ShareSpec.recapture_form`:code:` == `:attr:`mergeron.RECConstants.OUTIN`.
109
+ :attr:`mergeron.gen.ShareSpec.recapture_form`:code:` == `:attr:`mergeron.RECTypes.OUTIN`.
99
110
  In other words, if firm-counts are unspecified, the recapture rate cannot be
100
111
  estimated using outside good choice probabilities.
101
112
 
102
113
  For a sample with explicit firm counts, market shares must
103
114
  be specified as having a supported Dirichlet distribution
104
- (see :class:`mergeron.gen.SHRConstants`).
115
+ (see :class:`mergeron.gen.SHRDistributions`).
116
+
117
+ """
118
+
119
+ dist_type: SHRDistributions
120
+ """See :class:`SHRDistributions`"""
121
+
122
+ dist_parms: ArrayDouble | None = field(
123
+ default=None, eq=cmp_using(eq=np.array_equal)
124
+ )
125
+ """Parameters for tailoring market-share distribution
126
+
127
+ For Uniform distribution, bounds of the distribution; defaults to `(0, 1)`;
128
+ for Dirichlet-type distributions, a vector of shape parameters of length
129
+ no less than the length of firm-count weights below; defaults depend on
130
+ type of Dirichlet-distribution specified.
131
+
132
+ """
133
+ firm_counts_weights: ArrayFloat | ArrayINT | None = field(
134
+ default=None, eq=cmp_using(eq=np.array_equal)
135
+ )
136
+ """Relative or absolute frequencies of firm counts
137
+
138
+ Given frequencies are exogenous to generated market data sample;
139
+ for Dirichlet-type distributions, defaults to FCOUNT_WTS_DEFAULT, which specifies
140
+ firm-counts of 2 to 6 with weights in descending order from 5 to 1.
105
141
 
106
142
  """
107
143
 
108
- recapture_form: RECConstants
109
- """See :class:`mergeron.RECConstants`"""
144
+ @firm_counts_weights.validator
145
+ def _check_fcw(_i: ShareSpec, _a: Attribute[ArrayDouble], _v: ArrayDouble) -> None:
146
+ if _v is not None and _i.dist_type == SHRDistributions.UNI:
147
+ raise ValueError(
148
+ "Generated data for markets with specified firm-counts or "
149
+ "varying firm counts are not feasible for market shares "
150
+ "with Uniform distribution. Consider revising the "
151
+ r"distribution type to {SHRDistributions.DIR_FLAT}, which gives "
152
+ "uniformly distributed draws on the :math:`n+1` simplex "
153
+ "for firm-count, :math:`n`."
154
+ )
155
+
156
+ recapture_form: RECTypes = field(default=RECTypes.INOUT)
157
+ """See :class:`mergeron.RECTypes`"""
158
+
159
+ @recapture_form.validator
160
+ def _check_rf(_i: ShareSpec, _a: Attribute[RECTypes], _v: RECTypes) -> None:
161
+ if _v == RECTypes.OUTIN and _i.dist_type == SHRDistributions.UNI:
162
+ raise ValueError(
163
+ "Market share specification requires estimation of recapture rate from "
164
+ "generated data. Either delete recapture rate specification or set it to None."
165
+ )
110
166
 
111
- recapture_rate: float | None
167
+ recapture_rate: float | None = field(default=0.8)
112
168
  """A value between 0 and 1, typically 0.8.
113
169
 
114
170
  :code:`None` if market share specification requires direct generation of
115
- outside good choice probabilities (:attr:`mergeron.RECConstants.OUTIN`).
171
+ outside good choice probabilities (:attr:`mergeron.RECTypes.OUTIN`).
116
172
 
117
173
  The recapture rate is usually calibrated to the numbers-equivalent of the
118
174
  HHI threshold for the presumtion of harm from unilateral competitive effects
@@ -131,35 +187,20 @@ class ShareSpec:
131
187
 
132
188
  """
133
189
 
134
- dist_type: SHRConstants
135
- """See :class:`SHRConstants`"""
136
-
137
- dist_parms: ArrayDouble | None = field(
138
- default=None, eq=cmp_using(eq=np.array_equal)
139
- )
140
- """Parameters for tailoring market-share distribution
141
-
142
- For Uniform distribution, bounds of the distribution; defaults to `(0, 1)`;
143
- for Dirichlet-type distributions, a vector of shape parameters of length
144
- no less than the length of firm-count weights below; defaults depend on
145
- type of Dirichlet-distribution specified.
146
-
147
- """
148
- firm_counts_weights: (
149
- ArrayDouble | ArrayBIGINT | ArrayDouble | ArrayBIGINT | None
150
- ) = field(default=None, eq=cmp_using(eq=np.array_equal))
151
- """Relative or absolute frequencies of firm counts
152
-
153
-
154
- Given frequencies are exogenous to generated market data sample;
155
- for Dirichlet-type distributions, defaults to FCOUNT_WTS_DEFAULT, which specifies
156
- firm-counts of 2 to 6 with weights in descending order from 5 to 1.
157
-
158
- """
190
+ @recapture_rate.validator
191
+ def _check_rr(_i: ShareSpec, _a: Attribute[float], _v: float) -> None:
192
+ if _v and not (0 < _v <= 1):
193
+ raise ValueError("Recapture rate must lie in the interval, [0, 1).")
194
+ elif _v is None and _i.recapture_form != RECTypes.OUTIN:
195
+ raise ValueError(
196
+ f"Recapture specification, {_i.recapture_form!r} requires that "
197
+ "the market sample specification inclues a recapture rate in the "
198
+ "interval [0, 1)."
199
+ )
159
200
 
160
201
 
161
202
  @enum.unique
162
- class PCMConstants(enum.StrEnum):
203
+ class PCMDistributions(enum.StrEnum):
163
204
  """Margin distributions."""
164
205
 
165
206
  UNI = "Uniform"
@@ -193,13 +234,10 @@ class PCMSpec:
193
234
 
194
235
  """
195
236
 
196
- firm2_pcm_constraint: FM2Constants
197
- """See :class:`FM2Constants`"""
198
-
199
- dist_type: PCMConstants
200
- """See :class:`PCMConstants`"""
237
+ dist_type: PCMDistributions = field(kw_only=False, default=PCMDistributions.UNI)
238
+ """See :class:`PCMDistributions`"""
201
239
 
202
- dist_parms: ArrayDouble | None
240
+ dist_parms: ArrayDouble | None = field(kw_only=False, default=None)
203
241
  """Parameter specification for tailoring PCM distribution
204
242
 
205
243
  For Uniform distribution, bounds of the distribution; defaults to `(0, 1)`;
@@ -209,6 +247,38 @@ class PCMSpec:
209
247
 
210
248
  """
211
249
 
250
+ @dist_parms.validator
251
+ def _check_dp(
252
+ _i: PCMSpec, _a: Attribute[ArrayDouble | None], _v: ArrayDouble | None
253
+ ) -> None:
254
+ if _i.dist_type.name.startswith("BETA"):
255
+ if _v is None:
256
+ pass
257
+ elif np.array_equal(_v, DIST_PARMS_DEFAULT):
258
+ raise ValueError(
259
+ f"The distribution parameters, {DIST_PARMS_DEFAULT!r} "
260
+ "are not valid with margin distribution, {_dist_type_pcm!r}"
261
+ )
262
+ elif (
263
+ _i.dist_type == PCMDistributions.BETA and len(_v) != len(("a", "b"))
264
+ ) or (
265
+ _i.dist_type == PCMDistributions.BETA_BND
266
+ and len(_v) != len(("mu", "sigma", "max", "min"))
267
+ ):
268
+ raise ValueError(
269
+ f"Given number, {len(_v)} of parameters "
270
+ f'for PCM with distribution, "{_i.dist_type}" is incorrect.'
271
+ )
272
+
273
+ elif _i.dist_type == PCMDistributions.EMPR and _v is not None:
274
+ raise ValueError(
275
+ f"Empirical distribution does not require additional parameters; "
276
+ f'"given value, {_v!r} is ignored."'
277
+ )
278
+
279
+ firm2_pcm_constraint: FM2Constants = field(kw_only=False, default=FM2Constants.IID)
280
+ """See :class:`FM2Constants`"""
281
+
212
282
 
213
283
  @enum.unique
214
284
  class SSZConstants(float, enum.ReprEnum):
@@ -251,111 +321,6 @@ class SSZConstants(float, enum.ReprEnum):
251
321
 
252
322
 
253
323
  # Validators for selected attributes of MarketSpec
254
- def _share_spec_validator(
255
- _instance: MarketSpec, _attribute: Attribute[ShareSpec], _value: ShareSpec, /
256
- ) -> None:
257
- _r_bar = _value.recapture_rate
258
- if _r_bar and not (0 < _r_bar <= 1):
259
- raise ValueError("Recapture rate must lie in the interval, [0, 1).")
260
-
261
- elif _r_bar and _value.recapture_form == RECConstants.OUTIN:
262
- raise ValueError(
263
- "Market share specification requires estimation of recapture rate from "
264
- "generated data. Either delete recapture rate specification or set it to None."
265
- )
266
-
267
- if _value.dist_type == SHRConstants.UNI:
268
- if _value.recapture_form == RECConstants.OUTIN:
269
- raise ValueError(
270
- f"Invalid recapture specification, {_value.recapture_form!r} "
271
- "for market share specification with Uniform distribution. "
272
- "Redefine the market-sample specification, modifying the ."
273
- "market-share specification or the recapture specification."
274
- )
275
- elif _value.firm_counts_weights is not None:
276
- raise ValueError(
277
- "Generated data for markets with specified firm-counts or "
278
- "varying firm counts are not feasible for market shares "
279
- "with Uniform distribution. Consider revising the "
280
- r"distribution type to {SHRConstants.DIR_FLAT}, which gives "
281
- "uniformly distributed draws on the :math:`n+1` simplex "
282
- "for firm-count, :math:`n`."
283
- )
284
- elif _value.recapture_form != RECConstants.OUTIN and (
285
- _r_bar is None or not isinstance(_r_bar, float)
286
- ):
287
- raise ValueError(
288
- f"Recapture specification, {_value.recapture_form!r} requires that "
289
- "the market sample specification inclues a recapture rate."
290
- )
291
-
292
-
293
- def _pcm_spec_validator(
294
- _instance: MarketSpec, _attribute: Attribute[PCMSpec], _value: PCMSpec, /
295
- ) -> None:
296
- if (
297
- _instance.share_spec.recapture_form == RECConstants.FIXED
298
- and _value.firm2_pcm_constraint == FM2Constants.MNL
299
- ):
300
- raise ValueError(
301
- "{} {} {}".format(
302
- f'Specification of "recapture_form", "{_instance.share_spec.recapture_form}"',
303
- "requires Firm 2 margin must have property, ",
304
- f'"{FM2Constants.IID}" or "{FM2Constants.SYM}".',
305
- )
306
- )
307
- elif _value.dist_type.name.startswith("BETA"):
308
- if _value.dist_parms is None:
309
- pass
310
- elif np.array_equal(_value.dist_parms, DIST_PARMS_DEFAULT):
311
- raise ValueError(
312
- f"The distribution parameters, {DIST_PARMS_DEFAULT!r} "
313
- "are not valid with margin distribution, {_dist_type_pcm!r}"
314
- )
315
- elif (
316
- _value.dist_type == PCMConstants.BETA
317
- and len(_value.dist_parms) != len(("max", "min"))
318
- ) or (
319
- _value.dist_type == PCMConstants.BETA_BND
320
- and len(_value.dist_parms) != len(("mu", "sigma", "max", "min"))
321
- ):
322
- raise ValueError(
323
- f"Given number, {len(_value.dist_parms)} of parameters "
324
- f'for PCM with distribution, "{_value.dist_type}" is incorrect.'
325
- )
326
-
327
-
328
- @define(slots=False)
329
- class MarketSpec:
330
- """Parameter specification for market data generation."""
331
-
332
- share_spec: ShareSpec = field(
333
- kw_only=True,
334
- default=ShareSpec(RECConstants.INOUT, 0.85, SHRConstants.UNI, None, None),
335
- validator=[validators.instance_of(ShareSpec), _share_spec_validator],
336
- )
337
- """Market-share specification, see :class:`ShareSpec`"""
338
-
339
- pcm_spec: PCMSpec = field(
340
- kw_only=True,
341
- default=PCMSpec(FM2Constants.IID, PCMConstants.UNI, None),
342
- validator=[validators.instance_of(PCMSpec), _pcm_spec_validator],
343
- )
344
- """Margin specification, see :class:`PCMSpec`"""
345
-
346
- price_spec: PriceConstants = field(
347
- kw_only=True,
348
- default=PriceConstants.SYM,
349
- validator=validators.instance_of(PriceConstants),
350
- )
351
- """Price specification, see :class:`PriceConstants`"""
352
-
353
- hsr_filing_test_type: SSZConstants = field(
354
- kw_only=True,
355
- default=SSZConstants.ONE,
356
- validator=validators.instance_of(SSZConstants),
357
- )
358
- """Method for modeling HSR filing threholds, see :class:`SSZConstants`"""
359
324
 
360
325
 
361
326
  @dataclass(slots=True, frozen=True)
@@ -460,16 +425,22 @@ class INVResolution(enum.StrEnum):
460
425
 
461
426
  @frozen
462
427
  class UPPTestRegime:
428
+ """Configuration for UPP tests."""
429
+
463
430
  resolution: INVResolution = field(
464
- default=INVResolution.ENFT, validator=validators.instance_of(INVResolution)
431
+ kw_only=False,
432
+ default=INVResolution.ENFT,
433
+ validator=validators.in_([INVResolution.CLRN, INVResolution.ENFT]),
465
434
  )
435
+ """Whether to test clearance, enforcement, or both."""
436
+
466
437
  guppi_aggregator: UPPAggrSelector = field(
467
- default=UPPAggrSelector.MIN, validator=validators.instance_of(UPPAggrSelector)
468
- )
469
- divr_aggregator: UPPAggrSelector | None = field(
470
- default=UPPAggrSelector.MIN,
471
- validator=validators.instance_of((UPPAggrSelector, type(None))),
438
+ kw_only=False, default=UPPAggrSelector.MIN
472
439
  )
440
+ """Aggregator for GUPPI test."""
441
+
442
+ divr_aggregator: UPPAggrSelector = field(kw_only=False, default=UPPAggrSelector.MIN)
443
+ """Aggregator for diversion ratio test."""
473
444
 
474
445
 
475
446
  @dataclass(slots=True, frozen=True)
@@ -501,13 +472,22 @@ class UPPTestsRaw:
501
472
  class UPPTestsCounts:
502
473
  """Counts of markets resolved as specified
503
474
 
504
- Resolution may be either :attr:`INVResolution.ENFT` or :attr:`INVResolution.CLRN`.
475
+ Resolution may be either :attr:`INVResolution.ENFT`,
476
+ :attr:`INVResolution.CLRN`, or :attr:`INVResolution.BOTH`.
477
+ In the case of :attr:`INVResolution.BOTH`, two colums of counts
478
+ are returned: one for each resolution.
479
+
505
480
  """
506
481
 
507
482
  by_firm_count: ArrayBIGINT
508
483
  by_delta: ArrayBIGINT
509
484
  by_conczone: ArrayBIGINT
510
- """Zones are "unoncentrated", "moderately concentrated", and "highly concentrated"
485
+ """Zones are "unoncentrated", "moderately concentrated", and "highly concentrated",
486
+ with futher detail by HHI and ΔHHI for mergers in the "unconcentrated" and
487
+ "moderately concentrated" zones. See
488
+ :attr:`mergeron.gen.enforcement_stats.HMG_PRESUMPTION_ZONE_MAP` and
489
+ :attr:`mergeron.gen.enforcement_stats.ZONE_VALS` for more detail.
490
+
511
491
  """
512
492
 
513
493