mergeron 2025.739290.0__tar.gz → 2025.739290.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 (23) hide show
  1. {mergeron-2025.739290.0 → mergeron-2025.739290.1}/PKG-INFO +1 -1
  2. {mergeron-2025.739290.0 → mergeron-2025.739290.1}/pyproject.toml +1 -1
  3. {mergeron-2025.739290.0 → mergeron-2025.739290.1}/src/mergeron/__init__.py +17 -1
  4. {mergeron-2025.739290.0 → mergeron-2025.739290.1}/src/mergeron/core/guidelines_boundaries.py +69 -8
  5. {mergeron-2025.739290.0 → mergeron-2025.739290.1}/src/mergeron/core/pseudorandom_numbers.py +1 -17
  6. {mergeron-2025.739290.0 → mergeron-2025.739290.1}/src/mergeron/gen/data_generation.py +5 -11
  7. {mergeron-2025.739290.0 → mergeron-2025.739290.1}/README.rst +0 -0
  8. {mergeron-2025.739290.0 → mergeron-2025.739290.1}/src/mergeron/core/__init__.py +0 -0
  9. {mergeron-2025.739290.0 → mergeron-2025.739290.1}/src/mergeron/core/empirical_margin_distribution.py +0 -0
  10. {mergeron-2025.739290.0 → mergeron-2025.739290.1}/src/mergeron/core/ftc_merger_investigations_data.py +0 -0
  11. {mergeron-2025.739290.0 → mergeron-2025.739290.1}/src/mergeron/core/guidelines_boundary_functions.py +0 -0
  12. {mergeron-2025.739290.0 → mergeron-2025.739290.1}/src/mergeron/core/guidelines_boundary_functions_extra.py +0 -0
  13. {mergeron-2025.739290.0 → mergeron-2025.739290.1}/src/mergeron/data/__init__.py +0 -0
  14. {mergeron-2025.739290.0 → mergeron-2025.739290.1}/src/mergeron/data/damodaran_margin_data.xls +0 -0
  15. {mergeron-2025.739290.0 → mergeron-2025.739290.1}/src/mergeron/data/damodaran_margin_data_dict.msgpack +0 -0
  16. {mergeron-2025.739290.0 → mergeron-2025.739290.1}/src/mergeron/data/ftc_invdata.msgpack +0 -0
  17. {mergeron-2025.739290.0 → mergeron-2025.739290.1}/src/mergeron/demo/__init__.py +0 -0
  18. {mergeron-2025.739290.0 → mergeron-2025.739290.1}/src/mergeron/demo/visualize_empirical_margin_distribution.py +0 -0
  19. {mergeron-2025.739290.0 → mergeron-2025.739290.1}/src/mergeron/gen/__init__.py +0 -0
  20. {mergeron-2025.739290.0 → mergeron-2025.739290.1}/src/mergeron/gen/data_generation_functions.py +0 -0
  21. {mergeron-2025.739290.0 → mergeron-2025.739290.1}/src/mergeron/gen/enforcement_stats.py +0 -0
  22. {mergeron-2025.739290.0 → mergeron-2025.739290.1}/src/mergeron/gen/upp_tests.py +0 -0
  23. {mergeron-2025.739290.0 → mergeron-2025.739290.1}/src/mergeron/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: mergeron
3
- Version: 2025.739290.0
3
+ Version: 2025.739290.1
4
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
@@ -13,7 +13,7 @@ keywords = [
13
13
  "upward pricing pressure",
14
14
  "GUPPI",
15
15
  ]
16
- version = "2025.739290.0"
16
+ version = "2025.739290.1"
17
17
 
18
18
  # Classifiers list: https://pypi.org/classifiers/
19
19
  classifiers = [
@@ -6,12 +6,13 @@ from pathlib import Path
6
6
  from typing import Literal
7
7
 
8
8
  import numpy as np
9
+ from numpy.random import SeedSequence
9
10
  from numpy.typing import NDArray
10
11
  from ruamel import yaml
11
12
 
12
13
  _PKG_NAME: str = Path(__file__).parent.stem
13
14
 
14
- VERSION = "2025.739290.0"
15
+ VERSION = "2025.739290.1"
15
16
 
16
17
  __version__ = VERSION
17
18
 
@@ -66,6 +67,21 @@ type ArrayBIGINT = NDArray[np.int64]
66
67
  ),
67
68
  )
68
69
 
70
+ # Add yaml representer, constructor for SeedSequence
71
+ this_yaml.representer.add_representer(
72
+ SeedSequence,
73
+ lambda _r, _d: _r.represent_mapping(
74
+ "!SeedSequence",
75
+ {
76
+ _a: getattr(_d, _a)
77
+ for _a in ("entropy", "spawn_key", "pool_size", "n_children_spawned")
78
+ },
79
+ ),
80
+ )
81
+ this_yaml.constructor.add_constructor(
82
+ "!SeedSequence", lambda _c, _n, /: SeedSequence(**_c.construct_mapping(_n))
83
+ )
84
+
69
85
 
70
86
  @enum.unique
71
87
  class RECForm(enum.StrEnum):
@@ -13,6 +13,7 @@ from typing import Literal
13
13
  import numpy as np
14
14
  from attrs import Attribute, field, frozen, validators
15
15
  from mpmath import mp, mpf # type: ignore
16
+ from ruamel import yaml
16
17
 
17
18
  from .. import ( # noqa: TID252
18
19
  DEFAULT_REC_RATIO,
@@ -21,6 +22,7 @@ from .. import ( # noqa: TID252
21
22
  HMGPubYear,
22
23
  RECForm,
23
24
  UPPAggrSelector,
25
+ this_yaml,
24
26
  )
25
27
  from . import guidelines_boundary_functions as gbfn
26
28
 
@@ -42,6 +44,7 @@ class HMGThresholds:
42
44
  ipr: float
43
45
 
44
46
 
47
+ @this_yaml.register_class
45
48
  @frozen
46
49
  class GuidelinesThresholds:
47
50
  """
@@ -147,7 +150,24 @@ class GuidelinesThresholds:
147
150
  ),
148
151
  )
149
152
 
153
+ @classmethod
154
+ def to_yaml(
155
+ cls, _r: yaml.representer.SafeRepresenter, _d: GuidelinesThresholds
156
+ ) -> yaml.MappingNode:
157
+ _ret: yaml.MappingNode = _r.represent_mapping(
158
+ f"!{cls.__name__}",
159
+ {_a.name: getattr(_d, _a.name) for _a in _d.__attrs_attrs__},
160
+ )
161
+ return _ret
162
+
163
+ @classmethod
164
+ def from_yaml(
165
+ cls, _c: yaml.constructor.SafeConstructor, _n: yaml.MappingNode
166
+ ) -> GuidelinesThresholds:
167
+ return cls(**_c.construct_mapping(_n))
150
168
 
169
+
170
+ @this_yaml.register_class
151
171
  @frozen
152
172
  class ConcentrationBoundary:
153
173
  """Concentration parameters, boundary coordinates, and area under concentration boundary."""
@@ -184,12 +204,12 @@ class ConcentrationBoundary:
184
204
  kw_only=False, default=5, validator=validators.instance_of(int)
185
205
  )
186
206
 
187
- coordinates: ArrayDouble = field(init=False, kw_only=True)
188
- """Market-share pairs as Cartesian coordinates of points on the concentration boundary."""
189
-
190
207
  area: float = field(init=False, kw_only=True)
191
208
  """Area under the concentration boundary."""
192
209
 
210
+ coordinates: ArrayDouble = field(init=False, kw_only=True)
211
+ """Market-share pairs as Cartesian coordinates of points on the concentration boundary."""
212
+
193
213
  def __attrs_post_init__(self, /) -> None:
194
214
  match self.measure_name:
195
215
  case "ΔHHI":
@@ -202,10 +222,31 @@ class ConcentrationBoundary:
202
222
  _conc_fn = gbfn.hhi_post_contrib_boundary
203
223
 
204
224
  _boundary = _conc_fn(self.threshold, dps=self.precision)
205
- object.__setattr__(self, "coordinates", _boundary.coordinates)
206
225
  object.__setattr__(self, "area", _boundary.area)
226
+ object.__setattr__(self, "coordinates", _boundary.coordinates)
227
+
228
+ @classmethod
229
+ def to_yaml(
230
+ cls, _r: yaml.representer.SafeRepresenter, _d: ConcentrationBoundary
231
+ ) -> yaml.MappingNode:
232
+ _ret: yaml.MappingNode = _r.represent_mapping(
233
+ f"!{cls.__name__}",
234
+ {
235
+ _a.name: getattr(_d, _a.name)
236
+ for _a in _d.__attrs_attrs__
237
+ if _a.name not in ("area", "coordinates")
238
+ },
239
+ )
240
+ return _ret
207
241
 
242
+ @classmethod
243
+ def from_yaml(
244
+ cls, _c: yaml.constructor.SafeConstructor, _n: yaml.MappingNode
245
+ ) -> ConcentrationBoundary:
246
+ return cls(**_c.construct_mapping(_n))
208
247
 
248
+
249
+ @this_yaml.register_class
209
250
  @frozen
210
251
  class DiversionRatioBoundary:
211
252
  """
@@ -310,12 +351,12 @@ class DiversionRatioBoundary:
310
351
 
311
352
  """
312
353
 
313
- coordinates: ArrayDouble = field(init=False, kw_only=True)
314
- """Market-share pairs as Cartesian coordinates of points on the diversion ratio boundary."""
315
-
316
354
  area: float = field(init=False, kw_only=True)
317
355
  """Area under the diversion ratio boundary."""
318
356
 
357
+ coordinates: ArrayDouble = field(init=False, kw_only=True)
358
+ """Market-share pairs as Cartesian coordinates of points on the diversion ratio boundary."""
359
+
319
360
  def __attrs_post_init__(self, /) -> None:
320
361
  _share_ratio = critical_share_ratio(
321
362
  self.diversion_ratio, r_bar=self.recapture_ratio
@@ -359,8 +400,28 @@ class DiversionRatioBoundary:
359
400
  _upp_agg_kwargs |= {"agg_method": _aggregator, "weighting": _wgt_type}
360
401
 
361
402
  _boundary = _upp_agg_fn(_share_ratio, self.recapture_ratio, **_upp_agg_kwargs)
362
- object.__setattr__(self, "coordinates", _boundary.coordinates)
363
403
  object.__setattr__(self, "area", _boundary.area)
404
+ object.__setattr__(self, "coordinates", _boundary.coordinates)
405
+
406
+ @classmethod
407
+ def to_yaml(
408
+ cls, _r: yaml.representer.SafeRepresenter, _d: DiversionRatioBoundary
409
+ ) -> yaml.MappingNode:
410
+ _ret: yaml.MappingNode = _r.represent_mapping(
411
+ f"!{cls.__name__}",
412
+ {
413
+ _a.name: getattr(_d, _a.name)
414
+ for _a in _d.__attrs_attrs__
415
+ if _a.name not in ("area", "coordinates")
416
+ },
417
+ )
418
+ return _ret
419
+
420
+ @classmethod
421
+ def from_yaml(
422
+ cls, _c: yaml.constructor.SafeConstructor, _n: yaml.MappingNode
423
+ ) -> DiversionRatioBoundary:
424
+ return cls(**_c.construct_mapping(_n))
364
425
 
365
426
 
366
427
  def guppi_from_delta(
@@ -16,7 +16,7 @@ import numpy as np
16
16
  from attrs import Attribute, Converter, define, field
17
17
  from numpy.random import PCG64DXSM, Generator, SeedSequence
18
18
 
19
- from .. import NTHREADS, VERSION, ArrayDouble, ArrayFloat, this_yaml # noqa: TID252
19
+ from .. import NTHREADS, VERSION, ArrayDouble, ArrayFloat # noqa: TID252
20
20
 
21
21
  __version__ = VERSION
22
22
 
@@ -24,22 +24,6 @@ DEFAULT_DIST_PARMS: ArrayFloat = np.array([0.0, 1.0], float)
24
24
  DEFAULT_BETA_DIST_PARMS: ArrayFloat = np.array([1.0, 1.0], float)
25
25
 
26
26
 
27
- # Add yaml representer, constructor for SeedSequence
28
- this_yaml.representer.add_representer(
29
- SeedSequence,
30
- lambda _r, _d: _r.represent_mapping(
31
- "!SeedSequence",
32
- {
33
- _a: getattr(_d, _a)
34
- for _a in ("entropy", "spawn_key", "pool_size", "n_children_spawned")
35
- },
36
- ),
37
- )
38
- this_yaml.constructor.add_constructor(
39
- "!SeedSequence", lambda _c, _n, /: SeedSequence(**_c.construct_mapping(_n))
40
- )
41
-
42
-
43
27
  def prng(_s: SeedSequence | None = None, /) -> np.random.Generator:
44
28
  """Adopt the PCG64DXSM bit-generator, the future default in numpy.default_rng().
45
29
 
@@ -34,7 +34,7 @@ from .data_generation_functions import (
34
34
  gen_margin_price_data,
35
35
  gen_share_data,
36
36
  )
37
- from .upp_tests import SaveData, compute_upp_test_counts
37
+ from .upp_tests import compute_upp_test_counts
38
38
 
39
39
  __version__ = VERSION
40
40
 
@@ -51,12 +51,6 @@ class SamplingFunctionKWArgs(TypedDict, total=False):
51
51
  nthreads: int
52
52
  """number of parallel threads to use"""
53
53
 
54
- save_data_to_file: SaveData
55
- """optionally save data to HDF5 file"""
56
-
57
- saved_array_name_suffix: str
58
- """optionally specify a suffix for the HDF5 array names"""
59
-
60
54
 
61
55
  def _seed_data_conv(_v: SeedSequenceData | None, _i: MarketSample) -> SeedSequenceData:
62
56
  if isinstance(_v, SeedSequenceData):
@@ -104,9 +98,9 @@ class MarketSample:
104
98
  and _v.firm2_pcm_constraint == FM2Constraint.MNL
105
99
  ):
106
100
  raise ValueError(
107
- f'Specification of "recapture_form", "{self.share_spec.recapture_form}" '
108
- "requires Firm 2 margin must have property, "
109
- f'"{FM2Constraint.IID}" or "{FM2Constraint.SYM}".'
101
+ f'Specification of "PCMSpec.firm2_pcm_constraint", as {FM2Constraint.MNL!r} '
102
+ f'requires that "ShareSpec.recapture_form" be {RECForm.INOUT!r} '
103
+ f"or {RECForm.OUTIN!r}, not {RECForm.FIXED!r} as presently specified"
110
104
  )
111
105
 
112
106
  price_spec: PriceSpec = field(
@@ -484,7 +478,7 @@ class MarketSample:
484
478
  {
485
479
  _a.name: getattr(_d, _a.name)
486
480
  for _a in _d.__attrs_attrs__
487
- if _a.type not in (MarketSampleData, UPPTestsCounts)
481
+ if _a.name not in ("data", "enf_counts")
488
482
  },
489
483
  )
490
484
  return _ret