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/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 dataclasses import dataclass
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
- @dataclass(slots=True, frozen=True)
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], enum.ReprEnum):
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
- ZERO = (False, None)
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(enum.StrEnum):
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
- match _v:
115
- case None if _i.dist_type == SHRDistribution.UNI:
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
- case None:
118
- _fc_max = 1 + (
119
+ else:
120
+ fc_max = 1 + (
119
121
  len(DEFAULT_FCOUNT_WTS)
120
- if not hasattr(_i, "firm_counts_weights")
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(_fc_max, float)
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] * _fc_max, float)
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
- case _ if isinstance(_v, Sequence | np.ndarray):
142
- return np.asarray(_v, float)
143
- case _:
144
- raise ValueError(
145
- f"Input, {_v!r} has invalid type. Must be None, Sequence of floats, or Numpy ndarray."
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
- @this_yaml.register_class
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 _fcwd(_i: ShareSpec) -> ArrayFloat | None:
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 _fcv(_i: ShareSpec, _a: Attribute[ArrayFloat], _v: ArrayFloat) -> None:
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 _dpd(_i: ShareSpec) -> ArrayFloat:
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 _dpv(_i: ShareSpec, _a: Attribute[ArrayFloat], _v: ArrayFloat) -> None:
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 _rfv(_i: ShareSpec, _a: Attribute[RECForm], _v: RECForm) -> None:
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 _rrd(_i: ShareSpec) -> float | None:
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 _rrv(_i: ShareSpec, _a: Attribute[float], _v: float) -> None:
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(enum.StrEnum):
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(enum.StrEnum):
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
- @this_yaml.register_class
325
- @frozen_attrs
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 _dtd(_i: PCMSpec) -> PCMDistribution:
350
+ def __dtd(_i: PCMSpec) -> PCMDistribution:
346
351
  return PCMDistribution.UNI
347
352
 
348
- dist_parms: ArrayFloat | None = field(kw_only=True, eq=np.array_repr)
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 _dpwd(_i: PCMSpec) -> ArrayFloat | None:
360
- match _i.dist_type:
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 _dpv(
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, enum.ReprEnum):
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
- @dataclass(slots=True, frozen=True)
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
- fcounts: ArrayBIGINT
473
- """Number of firms in market"""
458
+ divr_array: ArrayDouble = field(eq=cmp_using(np.array_equal))
459
+ """Diversion ratio between the merging firms"""
474
460
 
475
- aggregate_purchase_prob: ArrayDouble
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
- nth_firm_share: ArrayDouble
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 for draws the do or
485
+ Relevant for testing draws that do or
486
486
  do not meet HSR filing thresholds.
487
487
  """
488
488
 
489
- divr_array: ArrayDouble
490
- """Diversion ratio between the merging firms"""
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
- hhi_delta: ArrayDouble
496
- """Change in HHI from combination of merging firms"""
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
- @dataclass(slots=True, frozen=True)
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: ArrayBIGINT
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
- @dataclass(slots=True, frozen=True)
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
- @dataclass(slots=True, frozen=True)
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(enum.StrEnum):
575
+ class INVResolution(str, Enameled):
554
576
  CLRN = "clearance"
555
577
  ENFT = "enforcement"
556
578
  BOTH = "investigation"
557
579
 
558
580
 
559
- @frozen_attrs
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
- @dataclass(slots=True, frozen=True)
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
- @dataclass(slots=True, frozen=True)
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
- # https://stackoverflow.com/questions/54668000
628
- class DataclassInstance(Protocol):
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)