mergeron 2025.739290.3__py3-none-any.whl → 2025.739290.5__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 +103 -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 +67 -138
- mergeron/core/guidelines_boundary_functions.py +202 -379
- mergeron/core/guidelines_boundary_functions_extra.py +264 -106
- mergeron/core/pseudorandom_numbers.py +73 -64
- 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 +138 -161
- mergeron/gen/data_generation.py +181 -149
- mergeron/gen/data_generation_functions.py +220 -237
- mergeron/gen/enforcement_stats.py +78 -109
- mergeron/gen/upp_tests.py +119 -194
- {mergeron-2025.739290.3.dist-info → mergeron-2025.739290.5.dist-info}/METADATA +2 -3
- mergeron-2025.739290.5.dist-info/RECORD +24 -0
- {mergeron-2025.739290.3.dist-info → mergeron-2025.739290.5.dist-info}/WHEEL +1 -1
- mergeron/data/damodaran_margin_data_dict.msgpack +0 -0
- mergeron-2025.739290.3.dist-info/RECORD +0 -23
mergeron/gen/__init__.py
CHANGED
|
@@ -7,15 +7,14 @@ containers for industry data generation and testing.
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import enum
|
|
10
|
+
import io
|
|
10
11
|
from collections.abc import Sequence
|
|
11
|
-
from
|
|
12
|
-
from typing import ClassVar, Protocol
|
|
12
|
+
from operator import attrgetter
|
|
13
13
|
|
|
14
|
+
import h5py
|
|
14
15
|
import numpy as np
|
|
15
|
-
from attrs import Attribute, Converter, cmp_using, field, validators
|
|
16
|
-
from attrs import frozen as frozen_attrs
|
|
16
|
+
from attrs import Attribute, Converter, cmp_using, field, frozen, validators
|
|
17
17
|
from numpy.random import SeedSequence
|
|
18
|
-
from ruamel import yaml
|
|
19
18
|
|
|
20
19
|
from .. import ( # noqa: TID252
|
|
21
20
|
DEFAULT_REC_RATIO,
|
|
@@ -25,9 +24,11 @@ from .. import ( # noqa: TID252
|
|
|
25
24
|
ArrayDouble,
|
|
26
25
|
ArrayFloat,
|
|
27
26
|
ArrayINT,
|
|
27
|
+
Enameled,
|
|
28
28
|
RECForm,
|
|
29
29
|
UPPAggrSelector,
|
|
30
30
|
this_yaml,
|
|
31
|
+
yamelize_attrs,
|
|
31
32
|
)
|
|
32
33
|
from ..core.pseudorandom_numbers import ( # noqa: TID252
|
|
33
34
|
DEFAULT_BETA_DIST_PARMS,
|
|
@@ -36,28 +37,29 @@ from ..core.pseudorandom_numbers import ( # noqa: TID252
|
|
|
36
37
|
|
|
37
38
|
__version__ = VERSION
|
|
38
39
|
|
|
39
|
-
|
|
40
40
|
DEFAULT_FCOUNT_WTS = np.asarray((_nr := np.arange(6, 0, -1)) / _nr.sum(), float)
|
|
41
41
|
|
|
42
|
+
DEFAULT_BETA_BND_DIST_PARMS = np.array([0.5, 1.0, 0.0, 1.0], float)
|
|
43
|
+
|
|
42
44
|
|
|
43
|
-
@
|
|
45
|
+
@frozen
|
|
44
46
|
class SeedSequenceData:
|
|
45
|
-
share: SeedSequence
|
|
46
|
-
pcm: SeedSequence
|
|
47
|
-
fcounts: SeedSequence | None
|
|
48
|
-
price: SeedSequence | None
|
|
47
|
+
share: SeedSequence = field(eq=attrgetter("state"))
|
|
48
|
+
pcm: SeedSequence = field(eq=attrgetter("state"))
|
|
49
|
+
fcounts: SeedSequence | None = field(eq=lambda x: x if x is None else x.state)
|
|
50
|
+
price: SeedSequence | None = field(eq=lambda x: x if x is None else x.state)
|
|
49
51
|
|
|
50
52
|
|
|
51
53
|
@this_yaml.register_class
|
|
52
54
|
@enum.unique
|
|
53
|
-
class PriceSpec(tuple[bool, str | None],
|
|
55
|
+
class PriceSpec(tuple[bool, str | None], Enameled):
|
|
54
56
|
"""Price specification.
|
|
55
57
|
|
|
56
58
|
Whether prices are symmetric and, if not, the direction of correlation, if any.
|
|
57
59
|
"""
|
|
58
60
|
|
|
59
61
|
SYM = (True, None)
|
|
60
|
-
|
|
62
|
+
RNG = (False, None)
|
|
61
63
|
NEG = (False, "negative share-correlation")
|
|
62
64
|
POS = (False, "positive share-correlation")
|
|
63
65
|
CSY = (False, "market-wide cost-symmetry")
|
|
@@ -65,7 +67,7 @@ class PriceSpec(tuple[bool, str | None], enum.ReprEnum):
|
|
|
65
67
|
|
|
66
68
|
@this_yaml.register_class
|
|
67
69
|
@enum.unique
|
|
68
|
-
class SHRDistribution(
|
|
70
|
+
class SHRDistribution(str, Enameled):
|
|
69
71
|
"""Market share distributions."""
|
|
70
72
|
|
|
71
73
|
UNI = "Uniform"
|
|
@@ -104,30 +106,28 @@ def _fc_wts_conv(
|
|
|
104
106
|
) -> ArrayFloat | None:
|
|
105
107
|
if _i.dist_type == SHRDistribution.UNI:
|
|
106
108
|
return None
|
|
107
|
-
elif _v is None or np.array_equal(_v, DEFAULT_FCOUNT_WTS):
|
|
109
|
+
elif _v is None or len(_v) == 0 or np.array_equal(_v, DEFAULT_FCOUNT_WTS):
|
|
108
110
|
return DEFAULT_FCOUNT_WTS
|
|
109
111
|
else:
|
|
110
112
|
return _tv if (_tv := np.asarray(_v, float)).sum() == 1 else _tv / _tv.sum()
|
|
111
113
|
|
|
112
114
|
|
|
113
115
|
def _shr_dp_conv(_v: Sequence[float] | ArrayFloat | None, _i: ShareSpec) -> ArrayFloat:
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
if _v is None or len(_v) == 0 or np.array_equal(_v, DEFAULT_DIST_PARMS):
|
|
117
|
+
if _i.dist_type == SHRDistribution.UNI:
|
|
116
118
|
return DEFAULT_DIST_PARMS
|
|
117
|
-
|
|
118
|
-
|
|
119
|
+
else:
|
|
120
|
+
fc_max = 1 + (
|
|
119
121
|
len(DEFAULT_FCOUNT_WTS)
|
|
120
|
-
if
|
|
121
|
-
or _i.firm_counts_weights is None
|
|
122
|
-
or len(_i.firm_counts_weights) == 0
|
|
122
|
+
if _i.firm_counts_weights is None
|
|
123
123
|
else len(_i.firm_counts_weights)
|
|
124
124
|
)
|
|
125
125
|
|
|
126
126
|
match _i.dist_type:
|
|
127
127
|
case SHRDistribution.DIR_FLAT | SHRDistribution.DIR_FLAT_CONSTR:
|
|
128
|
-
return np.ones(
|
|
128
|
+
return np.ones(fc_max, float)
|
|
129
129
|
case SHRDistribution.DIR_ASYM:
|
|
130
|
-
return np.array([2.0] * 6 + [1.5] * 5 + [1.25] *
|
|
130
|
+
return np.array([2.0] * 6 + [1.5] * 5 + [1.25] * fc_max, float)
|
|
131
131
|
case SHRDistribution.DIR_COND:
|
|
132
132
|
return np.array([], float)
|
|
133
133
|
case _ if isinstance(_i.dist_type, SHRDistribution):
|
|
@@ -138,16 +138,15 @@ def _shr_dp_conv(_v: Sequence[float] | ArrayFloat | None, _i: ShareSpec) -> Arra
|
|
|
138
138
|
raise ValueError(
|
|
139
139
|
f"Unsupported distribution for market share generation, {_i.dist_type!r}"
|
|
140
140
|
)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
141
|
+
elif isinstance(_v, Sequence | np.ndarray):
|
|
142
|
+
return np.asarray(_v, float)
|
|
143
|
+
else:
|
|
144
|
+
raise ValueError(
|
|
145
|
+
f"Input, {_v!r} has invalid type. Must be None, Sequence of floats, or Numpy ndarray."
|
|
146
|
+
)
|
|
147
147
|
|
|
148
148
|
|
|
149
|
-
@
|
|
150
|
-
@frozen_attrs
|
|
149
|
+
@frozen
|
|
151
150
|
class ShareSpec:
|
|
152
151
|
"""Market share specification
|
|
153
152
|
|
|
@@ -188,11 +187,11 @@ class ShareSpec:
|
|
|
188
187
|
"""
|
|
189
188
|
|
|
190
189
|
@firm_counts_weights.default
|
|
191
|
-
def
|
|
190
|
+
def __fcwd(_i: ShareSpec) -> ArrayFloat | None:
|
|
192
191
|
return _fc_wts_conv(None, _i)
|
|
193
192
|
|
|
194
193
|
@firm_counts_weights.validator
|
|
195
|
-
def
|
|
194
|
+
def __fcv(_i: ShareSpec, _a: Attribute[ArrayFloat], _v: ArrayFloat) -> None:
|
|
196
195
|
if _i.dist_type != SHRDistribution.UNI and not len(_v):
|
|
197
196
|
raise ValueError(
|
|
198
197
|
f"Attribute, {'"firm_counts_weights"'} must be populated if the share distribution is "
|
|
@@ -214,13 +213,13 @@ class ShareSpec:
|
|
|
214
213
|
"""
|
|
215
214
|
|
|
216
215
|
@dist_parms.default
|
|
217
|
-
def
|
|
216
|
+
def __dpd(_i: ShareSpec) -> ArrayFloat:
|
|
218
217
|
# converters run after defaults, and we
|
|
219
218
|
# avoid redundancy and confusion here
|
|
220
219
|
return _shr_dp_conv(None, _i)
|
|
221
220
|
|
|
222
221
|
@dist_parms.validator
|
|
223
|
-
def
|
|
222
|
+
def __dpv(_i: ShareSpec, _a: Attribute[ArrayFloat], _v: ArrayFloat) -> None:
|
|
224
223
|
if (
|
|
225
224
|
_i.firm_counts_weights is not None
|
|
226
225
|
and _v is not None
|
|
@@ -236,7 +235,7 @@ class ShareSpec:
|
|
|
236
235
|
"""See :class:`mergeron.RECForm`"""
|
|
237
236
|
|
|
238
237
|
@recapture_form.validator
|
|
239
|
-
def
|
|
238
|
+
def __rfv(_i: ShareSpec, _a: Attribute[RECForm], _v: RECForm) -> None:
|
|
240
239
|
if _i.dist_type == SHRDistribution.UNI and _v == RECForm.OUTIN:
|
|
241
240
|
raise ValueError(
|
|
242
241
|
"Outside-good choice probabilities cannot be generated if the "
|
|
@@ -271,11 +270,11 @@ class ShareSpec:
|
|
|
271
270
|
"""
|
|
272
271
|
|
|
273
272
|
@recapture_ratio.default
|
|
274
|
-
def
|
|
273
|
+
def __rrd(_i: ShareSpec) -> float | None:
|
|
275
274
|
return None if _i.recapture_form == RECForm.OUTIN else DEFAULT_REC_RATIO
|
|
276
275
|
|
|
277
276
|
@recapture_ratio.validator
|
|
278
|
-
def
|
|
277
|
+
def __rrv(_i: ShareSpec, _a: Attribute[float], _v: float) -> None:
|
|
279
278
|
if _v and not (0 < _v <= 1):
|
|
280
279
|
raise ValueError("Recapture ratio must lie in the interval, [0, 1).")
|
|
281
280
|
elif _v is None and _i.recapture_form != RECForm.OUTIN:
|
|
@@ -285,25 +284,10 @@ class ShareSpec:
|
|
|
285
284
|
"interval [0, 1)."
|
|
286
285
|
)
|
|
287
286
|
|
|
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
287
|
|
|
288
|
+
@this_yaml.register_class
|
|
305
289
|
@enum.unique
|
|
306
|
-
class PCMDistribution(
|
|
290
|
+
class PCMDistribution(str, Enameled):
|
|
307
291
|
"""Margin distributions."""
|
|
308
292
|
|
|
309
293
|
UNI = "Uniform"
|
|
@@ -312,8 +296,9 @@ class PCMDistribution(enum.StrEnum):
|
|
|
312
296
|
EMPR = "Damodaran margin data, resampled"
|
|
313
297
|
|
|
314
298
|
|
|
299
|
+
@this_yaml.register_class
|
|
315
300
|
@enum.unique
|
|
316
|
-
class FM2Constraint(
|
|
301
|
+
class FM2Constraint(str, Enameled):
|
|
317
302
|
"""Firm 2 margins - derivation methods."""
|
|
318
303
|
|
|
319
304
|
IID = "i.i.d"
|
|
@@ -321,8 +306,28 @@ class FM2Constraint(enum.StrEnum):
|
|
|
321
306
|
SYM = "symmetric"
|
|
322
307
|
|
|
323
308
|
|
|
324
|
-
|
|
325
|
-
|
|
309
|
+
def _pcm_dp_conv(
|
|
310
|
+
_v: Sequence[float] | ArrayFloat | None, _i: PCMSpec
|
|
311
|
+
) -> ArrayFloat | None:
|
|
312
|
+
if _i.dist_type == PCMDistribution.EMPR:
|
|
313
|
+
return None
|
|
314
|
+
elif _v is None or len(_v) == 0 or np.array_equal(_v, DEFAULT_DIST_PARMS):
|
|
315
|
+
match _i.dist_type:
|
|
316
|
+
case PCMDistribution.BETA:
|
|
317
|
+
return DEFAULT_BETA_DIST_PARMS
|
|
318
|
+
case PCMDistribution.BETA_BND:
|
|
319
|
+
return DEFAULT_BETA_BND_DIST_PARMS
|
|
320
|
+
case _:
|
|
321
|
+
return DEFAULT_DIST_PARMS
|
|
322
|
+
elif isinstance(_v, Sequence | np.ndarray):
|
|
323
|
+
return np.asarray(_v, float)
|
|
324
|
+
else:
|
|
325
|
+
raise ValueError(
|
|
326
|
+
f"Input, {_v!r} has invalid type. Must be None, Sequence of floats, or Numpy ndarray."
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
@frozen
|
|
326
331
|
class PCMSpec:
|
|
327
332
|
"""Price-cost margin (PCM) specification
|
|
328
333
|
|
|
@@ -342,10 +347,14 @@ class PCMSpec:
|
|
|
342
347
|
"""See :class:`PCMDistribution`"""
|
|
343
348
|
|
|
344
349
|
@dist_type.default
|
|
345
|
-
def
|
|
350
|
+
def __dtd(_i: PCMSpec) -> PCMDistribution:
|
|
346
351
|
return PCMDistribution.UNI
|
|
347
352
|
|
|
348
|
-
dist_parms: ArrayFloat | None = field(
|
|
353
|
+
dist_parms: ArrayFloat | None = field(
|
|
354
|
+
kw_only=True,
|
|
355
|
+
eq=cmp_using(eq=np.array_equal),
|
|
356
|
+
converter=Converter(_pcm_dp_conv, takes_self=True), # type: ignore
|
|
357
|
+
)
|
|
349
358
|
"""Parameter specification for tailoring PCM distribution
|
|
350
359
|
|
|
351
360
|
For Uniform distribution, bounds of the distribution; defaults to `(0, 1)`;
|
|
@@ -356,19 +365,11 @@ class PCMSpec:
|
|
|
356
365
|
"""
|
|
357
366
|
|
|
358
367
|
@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
|
|
368
|
+
def __dpwd(_i: PCMSpec) -> ArrayFloat | None:
|
|
369
|
+
return _pcm_dp_conv(None, _i)
|
|
369
370
|
|
|
370
371
|
@dist_parms.validator
|
|
371
|
-
def
|
|
372
|
+
def __dpv(
|
|
372
373
|
_i: PCMSpec, _a: Attribute[ArrayFloat | None], _v: ArrayFloat | None
|
|
373
374
|
) -> None:
|
|
374
375
|
if _i.dist_type.name.startswith("BETA"):
|
|
@@ -399,25 +400,10 @@ class PCMSpec:
|
|
|
399
400
|
firm2_pcm_constraint: FM2Constraint = field(kw_only=True, default=FM2Constraint.IID)
|
|
400
401
|
"""See :class:`FM2Constraint`"""
|
|
401
402
|
|
|
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
403
|
|
|
404
|
+
@this_yaml.register_class
|
|
419
405
|
@enum.unique
|
|
420
|
-
class SSZConstant(float,
|
|
406
|
+
class SSZConstant(float, Enameled):
|
|
421
407
|
"""
|
|
422
408
|
Scale factors to offset sample size reduction.
|
|
423
409
|
|
|
@@ -456,47 +442,82 @@ class SSZConstant(float, enum.ReprEnum):
|
|
|
456
442
|
"""When initial set of draws is not restricted in any way."""
|
|
457
443
|
|
|
458
444
|
|
|
459
|
-
@
|
|
445
|
+
@frozen
|
|
460
446
|
class MarketSampleData:
|
|
461
447
|
"""Container for generated markets data sample."""
|
|
462
448
|
|
|
463
|
-
frmshr_array: ArrayDouble
|
|
449
|
+
frmshr_array: ArrayDouble = field(eq=cmp_using(np.array_equal))
|
|
464
450
|
"""Merging-firm shares (with two merging firms)"""
|
|
465
451
|
|
|
466
|
-
pcm_array: ArrayDouble
|
|
452
|
+
pcm_array: ArrayDouble = field(eq=cmp_using(np.array_equal))
|
|
467
453
|
"""Merging-firms' prices (normalized to 1, in default specification)"""
|
|
468
454
|
|
|
469
|
-
price_array: ArrayDouble
|
|
455
|
+
price_array: ArrayDouble = field(eq=cmp_using(np.array_equal))
|
|
470
456
|
"""Merging-firms' price-cost margins (PCM)"""
|
|
471
457
|
|
|
472
|
-
|
|
473
|
-
"""
|
|
458
|
+
divr_array: ArrayDouble = field(eq=cmp_using(np.array_equal))
|
|
459
|
+
"""Diversion ratio between the merging firms"""
|
|
474
460
|
|
|
475
|
-
|
|
461
|
+
hhi_delta: ArrayDouble = field(eq=cmp_using(np.array_equal))
|
|
462
|
+
"""Change in HHI from combination of merging firms"""
|
|
463
|
+
|
|
464
|
+
aggregate_purchase_prob: ArrayDouble = field(eq=cmp_using(np.array_equal))
|
|
476
465
|
"""
|
|
477
466
|
One (1) minus probability that the outside good is chosen
|
|
478
467
|
|
|
479
468
|
Converts market shares to choice probabilities by multiplication.
|
|
480
469
|
"""
|
|
481
470
|
|
|
482
|
-
|
|
471
|
+
@aggregate_purchase_prob.default
|
|
472
|
+
def __appd(_i: MarketSampleData) -> ArrayDouble:
|
|
473
|
+
return np.nan * np.empty_like(_i.frmshr_array[:, :1], float)
|
|
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) -> ArrayDouble:
|
|
491
|
+
return np.nan * np.empty_like(_i.frmshr_array[:, :1], float)
|
|
491
492
|
|
|
492
|
-
hhi_post: ArrayDouble
|
|
493
|
+
hhi_post: ArrayDouble = field(eq=cmp_using(np.array_equal))
|
|
493
494
|
"""Post-merger change in Herfindahl-Hirschmann Index (HHI)"""
|
|
494
495
|
|
|
495
|
-
|
|
496
|
-
|
|
496
|
+
@hhi_post.default
|
|
497
|
+
def __hpd(_i: MarketSampleData) -> ArrayDouble:
|
|
498
|
+
return np.nan * np.empty_like(_i.frmshr_array[:, :1], float)
|
|
499
|
+
|
|
500
|
+
def to_h5bin(self) -> bytes:
|
|
501
|
+
"""Save market sample data to HDF5 file."""
|
|
502
|
+
byte_stream = io.BytesIO()
|
|
503
|
+
with h5py.File(byte_stream, "w") as _h5f:
|
|
504
|
+
for _a in self.__attrs_attrs__:
|
|
505
|
+
if all((
|
|
506
|
+
(_arr := getattr(self, _a.name)).any(),
|
|
507
|
+
not np.isnan(_arr).all(),
|
|
508
|
+
)):
|
|
509
|
+
_h5f.create_dataset(_a.name, data=_arr, fletcher32=True)
|
|
510
|
+
return byte_stream.getvalue()
|
|
511
|
+
|
|
512
|
+
@classmethod
|
|
513
|
+
def from_h5f(cls, _hfh: io.BufferedReader) -> MarketSampleData:
|
|
514
|
+
"""Load market sample data from HDF5 file."""
|
|
515
|
+
with h5py.File(_hfh, "r") as _h5f:
|
|
516
|
+
_retval = cls(**{_a: _h5f[_a][:] for _a in _h5f})
|
|
517
|
+
return _retval
|
|
497
518
|
|
|
498
519
|
|
|
499
|
-
@
|
|
520
|
+
@frozen
|
|
500
521
|
class ShareDataSample:
|
|
501
522
|
"""Container for generated market shares.
|
|
502
523
|
|
|
@@ -507,7 +528,7 @@ class ShareDataSample:
|
|
|
507
528
|
mktshr_array: ArrayDouble
|
|
508
529
|
"""All-firm shares (with two merging firms)"""
|
|
509
530
|
|
|
510
|
-
fcounts:
|
|
531
|
+
fcounts: ArrayINT
|
|
511
532
|
"""All-firm-count for each draw"""
|
|
512
533
|
|
|
513
534
|
nth_firm_share: ArrayDouble
|
|
@@ -517,7 +538,7 @@ class ShareDataSample:
|
|
|
517
538
|
"""Converts market shares to choice probabilities by multiplication."""
|
|
518
539
|
|
|
519
540
|
|
|
520
|
-
@
|
|
541
|
+
@frozen
|
|
521
542
|
class PriceDataSample:
|
|
522
543
|
"""Container for generated price array, and related."""
|
|
523
544
|
|
|
@@ -528,7 +549,7 @@ class PriceDataSample:
|
|
|
528
549
|
"""Flags draws as meeting HSR filing thresholds or not"""
|
|
529
550
|
|
|
530
551
|
|
|
531
|
-
@
|
|
552
|
+
@frozen
|
|
532
553
|
class MarginDataSample:
|
|
533
554
|
"""Container for generated margin array and related MNL test array."""
|
|
534
555
|
|
|
@@ -549,14 +570,15 @@ class MarginDataSample:
|
|
|
549
570
|
"""
|
|
550
571
|
|
|
551
572
|
|
|
573
|
+
@this_yaml.register_class
|
|
552
574
|
@enum.unique
|
|
553
|
-
class INVResolution(
|
|
575
|
+
class INVResolution(str, Enameled):
|
|
554
576
|
CLRN = "clearance"
|
|
555
577
|
ENFT = "enforcement"
|
|
556
578
|
BOTH = "investigation"
|
|
557
579
|
|
|
558
580
|
|
|
559
|
-
@
|
|
581
|
+
@frozen
|
|
560
582
|
class UPPTestRegime:
|
|
561
583
|
"""Configuration for UPP tests."""
|
|
562
584
|
|
|
@@ -576,7 +598,7 @@ class UPPTestRegime:
|
|
|
576
598
|
"""Aggregator for diversion ratio test."""
|
|
577
599
|
|
|
578
600
|
|
|
579
|
-
@
|
|
601
|
+
@frozen
|
|
580
602
|
class UPPTestsRaw:
|
|
581
603
|
"""Container for arrays marking test failures and successes
|
|
582
604
|
|
|
@@ -601,7 +623,7 @@ class UPPTestsRaw:
|
|
|
601
623
|
"""True if IPR (partial price-simulation) estimate meets criterion"""
|
|
602
624
|
|
|
603
625
|
|
|
604
|
-
@
|
|
626
|
+
@frozen
|
|
605
627
|
class UPPTestsCounts:
|
|
606
628
|
"""Counts of markets resolved as specified
|
|
607
629
|
|
|
@@ -612,9 +634,9 @@ class UPPTestsCounts:
|
|
|
612
634
|
|
|
613
635
|
"""
|
|
614
636
|
|
|
615
|
-
by_firm_count: ArrayBIGINT
|
|
616
|
-
by_delta: ArrayBIGINT
|
|
617
|
-
by_conczone: ArrayBIGINT
|
|
637
|
+
by_firm_count: ArrayBIGINT = field(eq=cmp_using(eq=np.array_equal))
|
|
638
|
+
by_delta: ArrayBIGINT = field(eq=cmp_using(eq=np.array_equal))
|
|
639
|
+
by_conczone: ArrayBIGINT = field(eq=cmp_using(eq=np.array_equal))
|
|
618
640
|
"""Zones are "unoncentrated", "moderately concentrated", and "highly concentrated",
|
|
619
641
|
with futher detail by HHI and ΔHHI for mergers in the "unconcentrated" and
|
|
620
642
|
"moderately concentrated" zones. See
|
|
@@ -624,50 +646,5 @@ class UPPTestsCounts:
|
|
|
624
646
|
"""
|
|
625
647
|
|
|
626
648
|
|
|
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.represent_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
|
-
)
|
|
649
|
+
for _typ in (SeedSequenceData, ShareSpec, PCMSpec, UPPTestsCounts, UPPTestRegime):
|
|
650
|
+
yamelize_attrs(_typ)
|