mergeron 2025.739290.2__py3-none-any.whl → 2025.739290.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.
Potentially problematic release.
This version of mergeron might be problematic. Click here for more details.
- mergeron/__init__.py +74 -48
- mergeron/core/__init__.py +105 -4
- mergeron/core/empirical_margin_distribution.py +100 -78
- mergeron/core/ftc_merger_investigations_data.py +309 -316
- mergeron/core/guidelines_boundaries.py +62 -121
- mergeron/core/guidelines_boundary_functions.py +207 -384
- mergeron/core/guidelines_boundary_functions_extra.py +264 -104
- mergeron/core/pseudorandom_numbers.py +76 -67
- mergeron/data/damodaran_margin_data_serialized.zip +0 -0
- mergeron/data/ftc_invdata.zip +0 -0
- mergeron/demo/visualize_empirical_margin_distribution.py +9 -7
- mergeron/gen/__init__.py +123 -161
- mergeron/gen/data_generation.py +183 -149
- mergeron/gen/data_generation_functions.py +220 -237
- mergeron/gen/enforcement_stats.py +83 -115
- mergeron/gen/upp_tests.py +118 -193
- {mergeron-2025.739290.2.dist-info → mergeron-2025.739290.4.dist-info}/METADATA +2 -3
- mergeron-2025.739290.4.dist-info/RECORD +24 -0
- {mergeron-2025.739290.2.dist-info → mergeron-2025.739290.4.dist-info}/WHEEL +1 -1
- mergeron/data/damodaran_margin_data_dict.msgpack +0 -0
- mergeron-2025.739290.2.dist-info/RECORD +0 -23
mergeron/gen/__init__.py
CHANGED
|
@@ -8,14 +8,11 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
import enum
|
|
10
10
|
from collections.abc import Sequence
|
|
11
|
-
from
|
|
12
|
-
from typing import ClassVar, Protocol
|
|
11
|
+
from operator import attrgetter
|
|
13
12
|
|
|
14
13
|
import numpy as np
|
|
15
|
-
from attrs import Attribute, Converter, cmp_using, field, validators
|
|
16
|
-
from attrs import frozen as frozen_attrs
|
|
14
|
+
from attrs import Attribute, Converter, cmp_using, field, frozen, validators
|
|
17
15
|
from numpy.random import SeedSequence
|
|
18
|
-
from ruamel import yaml
|
|
19
16
|
|
|
20
17
|
from .. import ( # noqa: TID252
|
|
21
18
|
DEFAULT_REC_RATIO,
|
|
@@ -25,9 +22,11 @@ from .. import ( # noqa: TID252
|
|
|
25
22
|
ArrayDouble,
|
|
26
23
|
ArrayFloat,
|
|
27
24
|
ArrayINT,
|
|
25
|
+
EnumYAMLized,
|
|
28
26
|
RECForm,
|
|
29
27
|
UPPAggrSelector,
|
|
30
28
|
this_yaml,
|
|
29
|
+
yamelize_attrs,
|
|
31
30
|
)
|
|
32
31
|
from ..core.pseudorandom_numbers import ( # noqa: TID252
|
|
33
32
|
DEFAULT_BETA_DIST_PARMS,
|
|
@@ -36,28 +35,29 @@ from ..core.pseudorandom_numbers import ( # noqa: TID252
|
|
|
36
35
|
|
|
37
36
|
__version__ = VERSION
|
|
38
37
|
|
|
39
|
-
|
|
40
38
|
DEFAULT_FCOUNT_WTS = np.asarray((_nr := np.arange(6, 0, -1)) / _nr.sum(), float)
|
|
41
39
|
|
|
40
|
+
DEFAULT_BETA_BND_DIST_PARMS = np.array([0.5, 1.0, 0.0, 1.0], float)
|
|
41
|
+
|
|
42
42
|
|
|
43
|
-
@
|
|
43
|
+
@frozen
|
|
44
44
|
class SeedSequenceData:
|
|
45
|
-
share: SeedSequence
|
|
46
|
-
pcm: SeedSequence
|
|
47
|
-
fcounts: SeedSequence | None
|
|
48
|
-
price: SeedSequence | None
|
|
45
|
+
share: SeedSequence = field(eq=attrgetter("state"))
|
|
46
|
+
pcm: SeedSequence = field(eq=attrgetter("state"))
|
|
47
|
+
fcounts: SeedSequence | None = field(eq=lambda x: x if x is None else x.state)
|
|
48
|
+
price: SeedSequence | None = field(eq=lambda x: x if x is None else x.state)
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
@this_yaml.register_class
|
|
52
52
|
@enum.unique
|
|
53
|
-
class PriceSpec(tuple[bool, str | None],
|
|
53
|
+
class PriceSpec(tuple[bool, str | None], EnumYAMLized):
|
|
54
54
|
"""Price specification.
|
|
55
55
|
|
|
56
56
|
Whether prices are symmetric and, if not, the direction of correlation, if any.
|
|
57
57
|
"""
|
|
58
58
|
|
|
59
59
|
SYM = (True, None)
|
|
60
|
-
|
|
60
|
+
RNG = (False, None)
|
|
61
61
|
NEG = (False, "negative share-correlation")
|
|
62
62
|
POS = (False, "positive share-correlation")
|
|
63
63
|
CSY = (False, "market-wide cost-symmetry")
|
|
@@ -65,7 +65,7 @@ class PriceSpec(tuple[bool, str | None], enum.ReprEnum):
|
|
|
65
65
|
|
|
66
66
|
@this_yaml.register_class
|
|
67
67
|
@enum.unique
|
|
68
|
-
class SHRDistribution(
|
|
68
|
+
class SHRDistribution(str, EnumYAMLized):
|
|
69
69
|
"""Market share distributions."""
|
|
70
70
|
|
|
71
71
|
UNI = "Uniform"
|
|
@@ -104,30 +104,28 @@ def _fc_wts_conv(
|
|
|
104
104
|
) -> ArrayFloat | None:
|
|
105
105
|
if _i.dist_type == SHRDistribution.UNI:
|
|
106
106
|
return None
|
|
107
|
-
elif _v is None or np.array_equal(_v, DEFAULT_FCOUNT_WTS):
|
|
107
|
+
elif _v is None or len(_v) == 0 or np.array_equal(_v, DEFAULT_FCOUNT_WTS):
|
|
108
108
|
return DEFAULT_FCOUNT_WTS
|
|
109
109
|
else:
|
|
110
110
|
return _tv if (_tv := np.asarray(_v, float)).sum() == 1 else _tv / _tv.sum()
|
|
111
111
|
|
|
112
112
|
|
|
113
113
|
def _shr_dp_conv(_v: Sequence[float] | ArrayFloat | None, _i: ShareSpec) -> ArrayFloat:
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
if _v is None or len(_v) == 0 or np.array_equal(_v, DEFAULT_DIST_PARMS):
|
|
115
|
+
if _i.dist_type == SHRDistribution.UNI:
|
|
116
116
|
return DEFAULT_DIST_PARMS
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
else:
|
|
118
|
+
fc_max = 1 + (
|
|
119
119
|
len(DEFAULT_FCOUNT_WTS)
|
|
120
|
-
if
|
|
121
|
-
or _i.firm_counts_weights is None
|
|
122
|
-
or len(_i.firm_counts_weights) == 0
|
|
120
|
+
if _i.firm_counts_weights is None
|
|
123
121
|
else len(_i.firm_counts_weights)
|
|
124
122
|
)
|
|
125
123
|
|
|
126
124
|
match _i.dist_type:
|
|
127
125
|
case SHRDistribution.DIR_FLAT | SHRDistribution.DIR_FLAT_CONSTR:
|
|
128
|
-
return np.ones(
|
|
126
|
+
return np.ones(fc_max, float)
|
|
129
127
|
case SHRDistribution.DIR_ASYM:
|
|
130
|
-
return np.array([2.0] * 6 + [1.5] * 5 + [1.25] *
|
|
128
|
+
return np.array([2.0] * 6 + [1.5] * 5 + [1.25] * fc_max, float)
|
|
131
129
|
case SHRDistribution.DIR_COND:
|
|
132
130
|
return np.array([], float)
|
|
133
131
|
case _ if isinstance(_i.dist_type, SHRDistribution):
|
|
@@ -138,16 +136,15 @@ def _shr_dp_conv(_v: Sequence[float] | ArrayFloat | None, _i: ShareSpec) -> Arra
|
|
|
138
136
|
raise ValueError(
|
|
139
137
|
f"Unsupported distribution for market share generation, {_i.dist_type!r}"
|
|
140
138
|
)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
139
|
+
elif isinstance(_v, Sequence | np.ndarray):
|
|
140
|
+
return np.asarray(_v, float)
|
|
141
|
+
else:
|
|
142
|
+
raise ValueError(
|
|
143
|
+
f"Input, {_v!r} has invalid type. Must be None, Sequence of floats, or Numpy ndarray."
|
|
144
|
+
)
|
|
147
145
|
|
|
148
146
|
|
|
149
|
-
@
|
|
150
|
-
@frozen_attrs
|
|
147
|
+
@frozen
|
|
151
148
|
class ShareSpec:
|
|
152
149
|
"""Market share specification
|
|
153
150
|
|
|
@@ -188,11 +185,11 @@ class ShareSpec:
|
|
|
188
185
|
"""
|
|
189
186
|
|
|
190
187
|
@firm_counts_weights.default
|
|
191
|
-
def
|
|
188
|
+
def __fcwd(_i: ShareSpec) -> ArrayFloat | None:
|
|
192
189
|
return _fc_wts_conv(None, _i)
|
|
193
190
|
|
|
194
191
|
@firm_counts_weights.validator
|
|
195
|
-
def
|
|
192
|
+
def __fcv(_i: ShareSpec, _a: Attribute[ArrayFloat], _v: ArrayFloat) -> None:
|
|
196
193
|
if _i.dist_type != SHRDistribution.UNI and not len(_v):
|
|
197
194
|
raise ValueError(
|
|
198
195
|
f"Attribute, {'"firm_counts_weights"'} must be populated if the share distribution is "
|
|
@@ -214,13 +211,13 @@ class ShareSpec:
|
|
|
214
211
|
"""
|
|
215
212
|
|
|
216
213
|
@dist_parms.default
|
|
217
|
-
def
|
|
214
|
+
def __dpd(_i: ShareSpec) -> ArrayFloat:
|
|
218
215
|
# converters run after defaults, and we
|
|
219
216
|
# avoid redundancy and confusion here
|
|
220
217
|
return _shr_dp_conv(None, _i)
|
|
221
218
|
|
|
222
219
|
@dist_parms.validator
|
|
223
|
-
def
|
|
220
|
+
def __dpv(_i: ShareSpec, _a: Attribute[ArrayFloat], _v: ArrayFloat) -> None:
|
|
224
221
|
if (
|
|
225
222
|
_i.firm_counts_weights is not None
|
|
226
223
|
and _v is not None
|
|
@@ -236,7 +233,7 @@ class ShareSpec:
|
|
|
236
233
|
"""See :class:`mergeron.RECForm`"""
|
|
237
234
|
|
|
238
235
|
@recapture_form.validator
|
|
239
|
-
def
|
|
236
|
+
def __rfv(_i: ShareSpec, _a: Attribute[RECForm], _v: RECForm) -> None:
|
|
240
237
|
if _i.dist_type == SHRDistribution.UNI and _v == RECForm.OUTIN:
|
|
241
238
|
raise ValueError(
|
|
242
239
|
"Outside-good choice probabilities cannot be generated if the "
|
|
@@ -271,11 +268,11 @@ class ShareSpec:
|
|
|
271
268
|
"""
|
|
272
269
|
|
|
273
270
|
@recapture_ratio.default
|
|
274
|
-
def
|
|
271
|
+
def __rrd(_i: ShareSpec) -> float | None:
|
|
275
272
|
return None if _i.recapture_form == RECForm.OUTIN else DEFAULT_REC_RATIO
|
|
276
273
|
|
|
277
274
|
@recapture_ratio.validator
|
|
278
|
-
def
|
|
275
|
+
def __rrv(_i: ShareSpec, _a: Attribute[float], _v: float) -> None:
|
|
279
276
|
if _v and not (0 < _v <= 1):
|
|
280
277
|
raise ValueError("Recapture ratio must lie in the interval, [0, 1).")
|
|
281
278
|
elif _v is None and _i.recapture_form != RECForm.OUTIN:
|
|
@@ -285,25 +282,10 @@ class ShareSpec:
|
|
|
285
282
|
"interval [0, 1)."
|
|
286
283
|
)
|
|
287
284
|
|
|
288
|
-
@classmethod
|
|
289
|
-
def to_yaml(
|
|
290
|
-
cls, _r: yaml.representer.SafeRepresenter, _d: ShareSpec
|
|
291
|
-
) -> yaml.MappingNode:
|
|
292
|
-
_ret: yaml.MappingNode = _r.represent_mapping(
|
|
293
|
-
f"!{cls.__name__}",
|
|
294
|
-
{_a.name: getattr(_d, _a.name) for _a in _d.__attrs_attrs__},
|
|
295
|
-
)
|
|
296
|
-
return _ret
|
|
297
|
-
|
|
298
|
-
@classmethod
|
|
299
|
-
def from_yaml(
|
|
300
|
-
cls, _c: yaml.constructor.SafeConstructor, _n: yaml.MappingNode
|
|
301
|
-
) -> ShareSpec:
|
|
302
|
-
return cls(**_c.construct_mapping(_n))
|
|
303
|
-
|
|
304
285
|
|
|
286
|
+
@this_yaml.register_class
|
|
305
287
|
@enum.unique
|
|
306
|
-
class PCMDistribution(
|
|
288
|
+
class PCMDistribution(str, EnumYAMLized):
|
|
307
289
|
"""Margin distributions."""
|
|
308
290
|
|
|
309
291
|
UNI = "Uniform"
|
|
@@ -312,8 +294,9 @@ class PCMDistribution(enum.StrEnum):
|
|
|
312
294
|
EMPR = "Damodaran margin data, resampled"
|
|
313
295
|
|
|
314
296
|
|
|
297
|
+
@this_yaml.register_class
|
|
315
298
|
@enum.unique
|
|
316
|
-
class FM2Constraint(
|
|
299
|
+
class FM2Constraint(str, EnumYAMLized):
|
|
317
300
|
"""Firm 2 margins - derivation methods."""
|
|
318
301
|
|
|
319
302
|
IID = "i.i.d"
|
|
@@ -321,8 +304,28 @@ class FM2Constraint(enum.StrEnum):
|
|
|
321
304
|
SYM = "symmetric"
|
|
322
305
|
|
|
323
306
|
|
|
324
|
-
|
|
325
|
-
|
|
307
|
+
def _pcm_dp_conv(
|
|
308
|
+
_v: Sequence[float] | ArrayFloat | None, _i: PCMSpec
|
|
309
|
+
) -> ArrayFloat | None:
|
|
310
|
+
if _i.dist_type == PCMDistribution.EMPR:
|
|
311
|
+
return None
|
|
312
|
+
elif _v is None or len(_v) == 0 or np.array_equal(_v, DEFAULT_DIST_PARMS):
|
|
313
|
+
match _i.dist_type:
|
|
314
|
+
case PCMDistribution.BETA:
|
|
315
|
+
return DEFAULT_BETA_DIST_PARMS
|
|
316
|
+
case PCMDistribution.BETA_BND:
|
|
317
|
+
return DEFAULT_BETA_BND_DIST_PARMS
|
|
318
|
+
case _:
|
|
319
|
+
return DEFAULT_DIST_PARMS
|
|
320
|
+
elif isinstance(_v, Sequence | np.ndarray):
|
|
321
|
+
return np.asarray(_v, float)
|
|
322
|
+
else:
|
|
323
|
+
raise ValueError(
|
|
324
|
+
f"Input, {_v!r} has invalid type. Must be None, Sequence of floats, or Numpy ndarray."
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
@frozen
|
|
326
329
|
class PCMSpec:
|
|
327
330
|
"""Price-cost margin (PCM) specification
|
|
328
331
|
|
|
@@ -342,10 +345,14 @@ class PCMSpec:
|
|
|
342
345
|
"""See :class:`PCMDistribution`"""
|
|
343
346
|
|
|
344
347
|
@dist_type.default
|
|
345
|
-
def
|
|
348
|
+
def __dtd(_i: PCMSpec) -> PCMDistribution:
|
|
346
349
|
return PCMDistribution.UNI
|
|
347
350
|
|
|
348
|
-
dist_parms: ArrayFloat | None = field(
|
|
351
|
+
dist_parms: ArrayFloat | None = field(
|
|
352
|
+
kw_only=True,
|
|
353
|
+
eq=cmp_using(eq=np.array_equal),
|
|
354
|
+
converter=Converter(_pcm_dp_conv, takes_self=True), # type: ignore
|
|
355
|
+
)
|
|
349
356
|
"""Parameter specification for tailoring PCM distribution
|
|
350
357
|
|
|
351
358
|
For Uniform distribution, bounds of the distribution; defaults to `(0, 1)`;
|
|
@@ -356,19 +363,11 @@ class PCMSpec:
|
|
|
356
363
|
"""
|
|
357
364
|
|
|
358
365
|
@dist_parms.default
|
|
359
|
-
def
|
|
360
|
-
|
|
361
|
-
case PCMDistribution.EMPR:
|
|
362
|
-
return None
|
|
363
|
-
case PCMDistribution.BETA:
|
|
364
|
-
return DEFAULT_BETA_DIST_PARMS
|
|
365
|
-
case PCMDistribution.BETA_BND:
|
|
366
|
-
return np.array([0.5, 1.0, 0.0, 0.1], float)
|
|
367
|
-
case _:
|
|
368
|
-
return DEFAULT_DIST_PARMS
|
|
366
|
+
def __dpwd(_i: PCMSpec) -> ArrayFloat | None:
|
|
367
|
+
return _pcm_dp_conv(None, _i)
|
|
369
368
|
|
|
370
369
|
@dist_parms.validator
|
|
371
|
-
def
|
|
370
|
+
def __dpv(
|
|
372
371
|
_i: PCMSpec, _a: Attribute[ArrayFloat | None], _v: ArrayFloat | None
|
|
373
372
|
) -> None:
|
|
374
373
|
if _i.dist_type.name.startswith("BETA"):
|
|
@@ -399,25 +398,10 @@ class PCMSpec:
|
|
|
399
398
|
firm2_pcm_constraint: FM2Constraint = field(kw_only=True, default=FM2Constraint.IID)
|
|
400
399
|
"""See :class:`FM2Constraint`"""
|
|
401
400
|
|
|
402
|
-
@classmethod
|
|
403
|
-
def to_yaml(
|
|
404
|
-
cls, _r: yaml.representer.SafeRepresenter, _d: PCMSpec
|
|
405
|
-
) -> yaml.MappingNode:
|
|
406
|
-
_ret: yaml.MappingNode = _r.represent_mapping(
|
|
407
|
-
f"!{cls.__name__}",
|
|
408
|
-
{_a.name: getattr(_d, _a.name) for _a in _d.__attrs_attrs__},
|
|
409
|
-
)
|
|
410
|
-
return _ret
|
|
411
|
-
|
|
412
|
-
@classmethod
|
|
413
|
-
def from_yaml(
|
|
414
|
-
cls, _c: yaml.constructor.SafeConstructor, _n: yaml.MappingNode
|
|
415
|
-
) -> PCMSpec:
|
|
416
|
-
return cls(**_c.construct_mapping(_n))
|
|
417
|
-
|
|
418
401
|
|
|
402
|
+
@this_yaml.register_class
|
|
419
403
|
@enum.unique
|
|
420
|
-
class SSZConstant(float,
|
|
404
|
+
class SSZConstant(float, EnumYAMLized):
|
|
421
405
|
"""
|
|
422
406
|
Scale factors to offset sample size reduction.
|
|
423
407
|
|
|
@@ -456,47 +440,69 @@ class SSZConstant(float, enum.ReprEnum):
|
|
|
456
440
|
"""When initial set of draws is not restricted in any way."""
|
|
457
441
|
|
|
458
442
|
|
|
459
|
-
@
|
|
443
|
+
@frozen
|
|
460
444
|
class MarketSampleData:
|
|
461
445
|
"""Container for generated markets data sample."""
|
|
462
446
|
|
|
463
|
-
frmshr_array: ArrayDouble
|
|
447
|
+
frmshr_array: ArrayDouble = field(eq=cmp_using(np.array_equal))
|
|
464
448
|
"""Merging-firm shares (with two merging firms)"""
|
|
465
449
|
|
|
466
|
-
pcm_array: ArrayDouble
|
|
450
|
+
pcm_array: ArrayDouble = field(eq=cmp_using(np.array_equal))
|
|
467
451
|
"""Merging-firms' prices (normalized to 1, in default specification)"""
|
|
468
452
|
|
|
469
|
-
price_array: ArrayDouble
|
|
453
|
+
price_array: ArrayDouble = field(eq=cmp_using(np.array_equal))
|
|
470
454
|
"""Merging-firms' price-cost margins (PCM)"""
|
|
471
455
|
|
|
472
|
-
|
|
473
|
-
"""
|
|
456
|
+
divr_array: ArrayDouble = field(eq=cmp_using(np.array_equal))
|
|
457
|
+
"""Diversion ratio between the merging firms"""
|
|
474
458
|
|
|
475
|
-
|
|
459
|
+
hhi_delta: ArrayDouble = field(eq=cmp_using(np.array_equal))
|
|
460
|
+
"""Change in HHI from combination of merging firms"""
|
|
461
|
+
|
|
462
|
+
aggregate_purchase_prob: ArrayDouble = field(eq=cmp_using(np.array_equal))
|
|
476
463
|
"""
|
|
477
464
|
One (1) minus probability that the outside good is chosen
|
|
478
465
|
|
|
479
466
|
Converts market shares to choice probabilities by multiplication.
|
|
480
467
|
"""
|
|
481
468
|
|
|
482
|
-
|
|
469
|
+
@aggregate_purchase_prob.default
|
|
470
|
+
def __appd(_i: MarketSampleData) -> ArrayINT:
|
|
471
|
+
e_ = np.empty_like(_i.frmshr_array[:, :1], float)
|
|
472
|
+
e_.fill(np.nan)
|
|
473
|
+
return e_
|
|
474
|
+
|
|
475
|
+
fcounts: ArrayINT = field(eq=cmp_using(np.array_equal))
|
|
476
|
+
"""Number of firms in market"""
|
|
477
|
+
|
|
478
|
+
@fcounts.default
|
|
479
|
+
def __fcd(_i: MarketSampleData) -> ArrayINT:
|
|
480
|
+
return np.zeros_like(_i.frmshr_array[:, :1], np.uint8)
|
|
481
|
+
|
|
482
|
+
nth_firm_share: ArrayDouble = field(eq=cmp_using(np.array_equal))
|
|
483
483
|
"""Market-share of n-th firm
|
|
484
484
|
|
|
485
|
-
Relevant for testing
|
|
485
|
+
Relevant for testing draws that do or
|
|
486
486
|
do not meet HSR filing thresholds.
|
|
487
487
|
"""
|
|
488
488
|
|
|
489
|
-
|
|
490
|
-
|
|
489
|
+
@nth_firm_share.default
|
|
490
|
+
def __nfsd(_i: MarketSampleData) -> ArrayINT:
|
|
491
|
+
e_ = np.empty_like(_i.frmshr_array[:, :1], float)
|
|
492
|
+
e_.fill(np.nan)
|
|
493
|
+
return e_
|
|
491
494
|
|
|
492
|
-
hhi_post: ArrayDouble
|
|
495
|
+
hhi_post: ArrayDouble = field(eq=cmp_using(np.array_equal))
|
|
493
496
|
"""Post-merger change in Herfindahl-Hirschmann Index (HHI)"""
|
|
494
497
|
|
|
495
|
-
|
|
496
|
-
|
|
498
|
+
@hhi_post.default
|
|
499
|
+
def __hpd(_i: MarketSampleData) -> ArrayINT:
|
|
500
|
+
e_ = np.empty_like(_i.frmshr_array[:, :1], float)
|
|
501
|
+
e_.fill(np.nan)
|
|
502
|
+
return e_
|
|
497
503
|
|
|
498
504
|
|
|
499
|
-
@
|
|
505
|
+
@frozen
|
|
500
506
|
class ShareDataSample:
|
|
501
507
|
"""Container for generated market shares.
|
|
502
508
|
|
|
@@ -507,7 +513,7 @@ class ShareDataSample:
|
|
|
507
513
|
mktshr_array: ArrayDouble
|
|
508
514
|
"""All-firm shares (with two merging firms)"""
|
|
509
515
|
|
|
510
|
-
fcounts:
|
|
516
|
+
fcounts: ArrayINT
|
|
511
517
|
"""All-firm-count for each draw"""
|
|
512
518
|
|
|
513
519
|
nth_firm_share: ArrayDouble
|
|
@@ -517,7 +523,7 @@ class ShareDataSample:
|
|
|
517
523
|
"""Converts market shares to choice probabilities by multiplication."""
|
|
518
524
|
|
|
519
525
|
|
|
520
|
-
@
|
|
526
|
+
@frozen
|
|
521
527
|
class PriceDataSample:
|
|
522
528
|
"""Container for generated price array, and related."""
|
|
523
529
|
|
|
@@ -528,7 +534,7 @@ class PriceDataSample:
|
|
|
528
534
|
"""Flags draws as meeting HSR filing thresholds or not"""
|
|
529
535
|
|
|
530
536
|
|
|
531
|
-
@
|
|
537
|
+
@frozen
|
|
532
538
|
class MarginDataSample:
|
|
533
539
|
"""Container for generated margin array and related MNL test array."""
|
|
534
540
|
|
|
@@ -549,14 +555,15 @@ class MarginDataSample:
|
|
|
549
555
|
"""
|
|
550
556
|
|
|
551
557
|
|
|
558
|
+
@this_yaml.register_class
|
|
552
559
|
@enum.unique
|
|
553
|
-
class INVResolution(
|
|
560
|
+
class INVResolution(str, EnumYAMLized):
|
|
554
561
|
CLRN = "clearance"
|
|
555
562
|
ENFT = "enforcement"
|
|
556
563
|
BOTH = "investigation"
|
|
557
564
|
|
|
558
565
|
|
|
559
|
-
@
|
|
566
|
+
@frozen
|
|
560
567
|
class UPPTestRegime:
|
|
561
568
|
"""Configuration for UPP tests."""
|
|
562
569
|
|
|
@@ -576,7 +583,7 @@ class UPPTestRegime:
|
|
|
576
583
|
"""Aggregator for diversion ratio test."""
|
|
577
584
|
|
|
578
585
|
|
|
579
|
-
@
|
|
586
|
+
@frozen
|
|
580
587
|
class UPPTestsRaw:
|
|
581
588
|
"""Container for arrays marking test failures and successes
|
|
582
589
|
|
|
@@ -601,7 +608,7 @@ class UPPTestsRaw:
|
|
|
601
608
|
"""True if IPR (partial price-simulation) estimate meets criterion"""
|
|
602
609
|
|
|
603
610
|
|
|
604
|
-
@
|
|
611
|
+
@frozen
|
|
605
612
|
class UPPTestsCounts:
|
|
606
613
|
"""Counts of markets resolved as specified
|
|
607
614
|
|
|
@@ -612,9 +619,9 @@ class UPPTestsCounts:
|
|
|
612
619
|
|
|
613
620
|
"""
|
|
614
621
|
|
|
615
|
-
by_firm_count: ArrayBIGINT
|
|
616
|
-
by_delta: ArrayBIGINT
|
|
617
|
-
by_conczone: ArrayBIGINT
|
|
622
|
+
by_firm_count: ArrayBIGINT = field(eq=cmp_using(eq=np.array_equal))
|
|
623
|
+
by_delta: ArrayBIGINT = field(eq=cmp_using(eq=np.array_equal))
|
|
624
|
+
by_conczone: ArrayBIGINT = field(eq=cmp_using(eq=np.array_equal))
|
|
618
625
|
"""Zones are "unoncentrated", "moderately concentrated", and "highly concentrated",
|
|
619
626
|
with futher detail by HHI and ΔHHI for mergers in the "unconcentrated" and
|
|
620
627
|
"moderately concentrated" zones. See
|
|
@@ -624,50 +631,5 @@ class UPPTestsCounts:
|
|
|
624
631
|
"""
|
|
625
632
|
|
|
626
633
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
"""Generic dataclass-instance"""
|
|
630
|
-
|
|
631
|
-
__dataclass_fields__: ClassVar
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
for _typ in (
|
|
635
|
-
PriceSpec,
|
|
636
|
-
SHRDistribution,
|
|
637
|
-
PCMDistribution,
|
|
638
|
-
FM2Constraint,
|
|
639
|
-
SSZConstant,
|
|
640
|
-
INVResolution,
|
|
641
|
-
):
|
|
642
|
-
# NOTE: If additional enums are defined in this module,
|
|
643
|
-
# add themn to the list above
|
|
644
|
-
|
|
645
|
-
_, _ = (
|
|
646
|
-
this_yaml.representer.add_representer(
|
|
647
|
-
_typ,
|
|
648
|
-
lambda _r, _d: _r.represent_scalar(f"!{_d.__class__.__name__}", _d.name),
|
|
649
|
-
),
|
|
650
|
-
this_yaml.constructor.add_constructor(
|
|
651
|
-
f"!{_typ.__name__}",
|
|
652
|
-
lambda _c, _n, /: getattr(
|
|
653
|
-
globals().get(_n.tag.lstrip("!")), _c.construct_scalar(_n)
|
|
654
|
-
),
|
|
655
|
-
),
|
|
656
|
-
)
|
|
657
|
-
|
|
658
|
-
for _typ in (MarketSampleData, SeedSequenceData, UPPTestsCounts):
|
|
659
|
-
_, _ = (
|
|
660
|
-
this_yaml.representer.add_representer(
|
|
661
|
-
_typ,
|
|
662
|
-
lambda _r, _d: _r.rpresent_mapping(
|
|
663
|
-
f"!{_d.__class__.__name__}",
|
|
664
|
-
{_a: getattr(_d, _a) for _a in _d.__dataclass_fields__},
|
|
665
|
-
),
|
|
666
|
-
),
|
|
667
|
-
this_yaml.constructor.add_constructor(
|
|
668
|
-
_typ,
|
|
669
|
-
lambda _c, _n: globals().get(
|
|
670
|
-
_n.tag.lstrip("!")(**_c.construct_mapping(_n))
|
|
671
|
-
),
|
|
672
|
-
),
|
|
673
|
-
)
|
|
634
|
+
for _typ in (SeedSequenceData, ShareSpec, PCMSpec, UPPTestsCounts, UPPTestRegime):
|
|
635
|
+
yamelize_attrs(_typ)
|