mirror of
https://github.com/mpusz/mp-units.git
synced 2025-06-24 16:51:33 +02:00
140 lines
4.6 KiB
Python
140 lines
4.6 KiB
Python
import dataclasses
|
|
import itertools
|
|
import random
|
|
import typing
|
|
from dataclasses import dataclass
|
|
|
|
|
|
@dataclass(frozen=True, order=True)
|
|
class Compiler:
|
|
type: typing.Literal["GCC", "CLANG", "APPLE_CLANG", "MSVC"]
|
|
version: str | int
|
|
cc: str
|
|
cxx: str
|
|
|
|
|
|
@dataclass(frozen=True, order=True)
|
|
class Configuration:
|
|
name: str
|
|
os: str
|
|
compiler: Compiler
|
|
cxx_modules: bool
|
|
std_format_support: bool
|
|
conan_config: str = ""
|
|
lib: typing.Literal["libc++", "libstdc++"] | None = None
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
@dataclass(frozen=True, order=True)
|
|
class MatrixElement:
|
|
config: Configuration
|
|
std: typing.Literal[20, 23]
|
|
formatting: typing.Literal["std::format", "fmtlib"]
|
|
contracts: typing.Literal["none", "gsl-lite", "ms-gsl"]
|
|
build_type: typing.Literal["Release", "Debug"]
|
|
|
|
def as_json(self):
|
|
def dataclass_to_json(obj):
|
|
"""Convert dataclasses to something json-serialisable"""
|
|
if dataclasses.is_dataclass(obj):
|
|
return {
|
|
k: dataclass_to_json(v) for k, v in dataclasses.asdict(obj).items()
|
|
}
|
|
return obj
|
|
|
|
ret = dataclass_to_json(self)
|
|
# patch boolean conan configuration options
|
|
config = ret["config"]
|
|
for k in ["cxx_modules"]:
|
|
config[k] = "True" if config[k] else "False"
|
|
return ret
|
|
|
|
|
|
class CombinationCollector:
|
|
"""Incremental builder of MatrixElements, allowing successive selection of entries."""
|
|
|
|
def __init__(
|
|
self,
|
|
full_matrix: dict[str, list[typing.Any]],
|
|
*,
|
|
hard_excludes: typing.Callable[[MatrixElement], bool] | None = None,
|
|
):
|
|
self.full_matrix = full_matrix
|
|
self.hard_excludes = hard_excludes
|
|
self.combinations: set[MatrixElement] = set()
|
|
self.per_value_counts: dict[tuple[str, typing.Any], int] = {
|
|
(k, v): 0 for k, options in full_matrix.items() for v in options
|
|
}
|
|
|
|
def _make_submatrix(self, **overrides):
|
|
new_matrix = dict(self.full_matrix)
|
|
for k, v in overrides.items():
|
|
if not isinstance(v, list):
|
|
v = [v]
|
|
new_matrix[k] = v
|
|
return new_matrix
|
|
|
|
def _add_combination(self, e: MatrixElement):
|
|
if e in self.combinations or (
|
|
self.hard_excludes is not None and self.hard_excludes(e)
|
|
):
|
|
return
|
|
self.combinations.add(e)
|
|
# update per_value_counts
|
|
for k, v in vars(e).items():
|
|
idx = (k, v)
|
|
self.per_value_counts[idx] = self.per_value_counts.get(idx, 0) + 1
|
|
|
|
def all_combinations(
|
|
self,
|
|
*,
|
|
filter: typing.Callable[[MatrixElement], bool] | None = None,
|
|
**overrides,
|
|
):
|
|
"""Adds all combinations in the submatrix defined by `overrides`."""
|
|
matrix = self._make_submatrix(**overrides)
|
|
keys = tuple(matrix.keys())
|
|
for combination in itertools.product(*matrix.values()):
|
|
cand = MatrixElement(**dict(zip(keys, combination)))
|
|
if filter and not filter(cand):
|
|
continue
|
|
self._add_combination(cand)
|
|
|
|
def sample_combinations(
|
|
self,
|
|
*,
|
|
rgen: random.Random,
|
|
min_samples_per_value: int = 1,
|
|
filter: typing.Callable[[MatrixElement], bool] | None = None,
|
|
**overrides,
|
|
):
|
|
"""Adds samples from the submatrix defined by `overrides`,
|
|
ensuring each individual value appears at least n times.
|
|
"""
|
|
matrix = self._make_submatrix(**overrides)
|
|
missing: dict[tuple[str, typing.Any], int] = {}
|
|
for key, options in matrix.items():
|
|
for value in options:
|
|
idx = (key, value)
|
|
missing[idx] = min_samples_per_value - self.per_value_counts.get(idx, 0)
|
|
while missing:
|
|
(force_key, force_option), remaining = next(iter(missing.items()))
|
|
if remaining <= 0:
|
|
missing.pop((force_key, force_option))
|
|
continue
|
|
choice = {}
|
|
for key, options in matrix.items():
|
|
choice[key] = force_option if key == force_key else rgen.choice(options)
|
|
cand = MatrixElement(**choice)
|
|
if filter and not filter(cand):
|
|
continue
|
|
self._add_combination(cand)
|
|
for idx in choice.items():
|
|
if missing.pop(idx, 0) <= 0:
|
|
continue
|
|
remaining = min_samples_per_value - self.per_value_counts.get(idx, 0)
|
|
if remaining > 0:
|
|
missing[idx] = remaining
|