Spaces:
Runtime error
Runtime error
| """ | |
| Generic diversity metrics for WorldSmithAI. | |
| This module computes domain-agnostic diversity over agents, resources, or both. | |
| It deliberately avoids hardcoded species assumptions. A "group" can be any | |
| DSL-defined label, such as agent type, resource type, faction, strategy, | |
| profession, role, technology, market sector, node class, or civilization tier. | |
| The default grouping path is ``type``, which means: | |
| - agents are grouped by ``agent.type`` | |
| - resources are grouped by ``resource.type`` | |
| Example: | |
| metric = DiversityMetric(collection="agents", group_by_path="type") | |
| result = metric.compute(world) | |
| print(result.richness) | |
| print(result.gini_simpson_index) | |
| Weighted resource diversity: | |
| metric = DiversityMetric( | |
| collection="resources", | |
| group_by_path="type", | |
| weight_path="amount", | |
| ) | |
| result = metric(world) | |
| Future extensibility: | |
| - Add rolling-window diversity metrics. | |
| - Add spatial diversity over regions. | |
| - Add interaction-network diversity. | |
| - Add behavioral diversity based on executed actions. | |
| - Add diversity deltas for interestingness scoring. | |
| - Add "God Agent" hooks for judging world health and novelty. | |
| """ | |
| from __future__ import annotations | |
| import copy | |
| import logging | |
| import math | |
| from collections.abc import Iterable, Mapping, MutableSequence, Sequence | |
| from dataclasses import dataclass, field | |
| from enum import Enum | |
| from numbers import Real | |
| from types import MappingProxyType | |
| from typing import TYPE_CHECKING, Any, ClassVar | |
| import numpy as np | |
| if TYPE_CHECKING: | |
| from core.agent import Agent | |
| from core.resource import Resource | |
| from core.world import World | |
| logger = logging.getLogger(__name__) | |
| _MISSING = object() | |
| _EPSILON = 1.0e-12 | |
| class DiversityCollection(str, Enum): | |
| """Collections over which diversity may be computed.""" | |
| AGENTS = "agents" | |
| RESOURCES = "resources" | |
| BOTH = "both" | |
| class DiversityResult: | |
| """Result produced by a diversity metric computation. | |
| Attributes: | |
| metric_name: Stable metric name. | |
| collection: Collection analyzed, such as ``agents`` or ``resources``. | |
| group_by_path: Path used to derive group labels. | |
| weight_path: Optional path used to derive item weights. | |
| item_count: Number of world objects included after filters. | |
| total_weight: Sum of group weights. For unweighted diversity this is | |
| equal to ``item_count``. | |
| richness: Number of distinct groups. | |
| counts: Group weights keyed by group label. | |
| proportions: Group proportions keyed by group label. | |
| dominant_group: Group with the largest proportion, if any. | |
| dominance: Largest group proportion. | |
| simpson_concentration: Sum of squared proportions. | |
| gini_simpson_index: ``1 - simpson_concentration``. | |
| inverse_simpson_index: ``1 / simpson_concentration`` when defined. | |
| shannon_entropy: Shannon entropy using natural logarithms. | |
| pielou_evenness: Shannon entropy divided by maximum possible entropy. | |
| step: Optional world step. | |
| metadata: Additional JSON-compatible details. | |
| """ | |
| metric_name: str | |
| collection: str | |
| group_by_path: str | |
| weight_path: str | None | |
| item_count: int | |
| total_weight: float | |
| richness: int | |
| counts: Mapping[str, float] = field(default_factory=dict) | |
| proportions: Mapping[str, float] = field(default_factory=dict) | |
| dominant_group: str | None = None | |
| dominance: float = 0.0 | |
| simpson_concentration: float = 0.0 | |
| gini_simpson_index: float = 0.0 | |
| inverse_simpson_index: float = 0.0 | |
| shannon_entropy: float = 0.0 | |
| pielou_evenness: float = 0.0 | |
| step: int | None = None | |
| metadata: Mapping[str, Any] = field(default_factory=dict) | |
| def group_count(self) -> int: | |
| """Return the number of distinct groups.""" | |
| return self.richness | |
| def is_empty(self) -> bool: | |
| """Return whether no items contributed to the metric.""" | |
| return self.item_count == 0 or self.total_weight <= 0.0 | |
| def to_dict(self) -> dict[str, Any]: | |
| """Return a JSON-friendly representation of the diversity result.""" | |
| return { | |
| "metric_name": self.metric_name, | |
| "collection": self.collection, | |
| "group_by_path": self.group_by_path, | |
| "weight_path": self.weight_path, | |
| "item_count": self.item_count, | |
| "total_weight": self.total_weight, | |
| "richness": self.richness, | |
| "group_count": self.group_count, | |
| "counts": copy.deepcopy(dict(self.counts)), | |
| "proportions": copy.deepcopy(dict(self.proportions)), | |
| "dominant_group": self.dominant_group, | |
| "dominance": self.dominance, | |
| "simpson_concentration": self.simpson_concentration, | |
| "gini_simpson_index": self.gini_simpson_index, | |
| "inverse_simpson_index": self.inverse_simpson_index, | |
| "shannon_entropy": self.shannon_entropy, | |
| "pielou_evenness": self.pielou_evenness, | |
| "step": self.step, | |
| "metadata": copy.deepcopy(dict(self.metadata)), | |
| } | |
| class DiversityMetric: | |
| """Compute generic group diversity over a world collection. | |
| By default, this metric computes diversity over alive agents grouped by | |
| their ``type`` attribute. The same class can compute resource diversity, | |
| strategy diversity, role diversity, faction diversity, or any other | |
| path-based grouping. | |
| Examples: | |
| Agent type diversity: | |
| metric = DiversityMetric() | |
| result = metric.compute(world) | |
| Resource amount diversity: | |
| metric = DiversityMetric( | |
| collection="resources", | |
| group_by_path="type", | |
| weight_path="amount", | |
| ) | |
| result = metric.compute(world) | |
| Diversity by active goal: | |
| metric = DiversityMetric( | |
| collection="agents", | |
| group_by_path="state.current_goal", | |
| include_missing=True, | |
| ) | |
| result = metric.compute(world) | |
| """ | |
| name: ClassVar[str] = "diversity" | |
| collection: DiversityCollection | str = DiversityCollection.AGENTS | |
| group_by_path: str = "type" | |
| weight_path: str | None = None | |
| alive_only: bool = True | |
| include_missing: bool = False | |
| missing_label: str = "unknown" | |
| include_group_values: tuple[str, ...] = () | |
| exclude_group_values: tuple[str, ...] = () | |
| case_sensitive: bool = True | |
| strip_labels: bool = True | |
| include_collection_prefix: bool = False | |
| minimum_weight: float = 0.0 | |
| metadata: Mapping[str, Any] = field(default_factory=dict) | |
| def compute(self, world: World) -> DiversityResult: | |
| """Compute diversity for the configured world collection. | |
| Args: | |
| world: Runtime world object. | |
| Returns: | |
| ``DiversityResult`` containing counts, proportions, and diversity | |
| indices. | |
| """ | |
| collection = _normalize_collection(self.collection) | |
| items = self._items(world, collection) | |
| counts, included_count, skipped_count = self._counts(items, collection) | |
| result = diversity_from_counts( | |
| counts, | |
| metric_name=self.name, | |
| collection=collection.value, | |
| group_by_path=self.group_by_path, | |
| weight_path=self.weight_path, | |
| item_count=included_count, | |
| step=_world_step(world), | |
| metadata={ | |
| **copy.deepcopy(dict(self.metadata)), | |
| "skipped_count": skipped_count, | |
| "alive_only": self.alive_only, | |
| "include_missing": self.include_missing, | |
| "minimum_weight": self.minimum_weight, | |
| }, | |
| ) | |
| logger.debug( | |
| "Computed %s diversity over %s: richness=%s total_weight=%.3f", | |
| self.name, | |
| collection.value, | |
| result.richness, | |
| result.total_weight, | |
| ) | |
| return result | |
| def __call__(self, world: World) -> DiversityResult: | |
| """Compute diversity, allowing metric instances to be called directly.""" | |
| return self.compute(world) | |
| def _items(self, world: World, collection: DiversityCollection) -> tuple[Any, ...]: | |
| """Return world items for the configured collection.""" | |
| return _iter_world_items(world, collection) | |
| def _counts( | |
| self, | |
| items: Sequence[Any], | |
| collection: DiversityCollection, | |
| ) -> tuple[dict[str, float], int, int]: | |
| """Return group weights, included item count, and skipped item count.""" | |
| counts: dict[str, float] = {} | |
| included_count = 0 | |
| skipped_count = 0 | |
| include_values = {_normalize_label(value, case_sensitive=self.case_sensitive, strip=True) for value in self.include_group_values} | |
| exclude_values = {_normalize_label(value, case_sensitive=self.case_sensitive, strip=True) for value in self.exclude_group_values} | |
| for item in items: | |
| if self.alive_only and _is_agent_like(item) and not _is_alive(item): | |
| skipped_count += 1 | |
| continue | |
| label = self._label_for(item, collection) | |
| if label is None: | |
| skipped_count += 1 | |
| continue | |
| normalized_label = _normalize_label( | |
| label, | |
| case_sensitive=self.case_sensitive, | |
| strip=self.strip_labels, | |
| ) | |
| if include_values and normalized_label not in include_values: | |
| skipped_count += 1 | |
| continue | |
| if exclude_values and normalized_label in exclude_values: | |
| skipped_count += 1 | |
| continue | |
| weight = self._weight_for(item) | |
| if weight <= max(0.0, float(self.minimum_weight)): | |
| skipped_count += 1 | |
| continue | |
| counts[normalized_label] = counts.get(normalized_label, 0.0) + weight | |
| included_count += 1 | |
| return counts, included_count, skipped_count | |
| def _label_for(self, item: Any, collection: DiversityCollection) -> str | None: | |
| """Return the group label for an item.""" | |
| raw_label = _read_path(item, self.group_by_path, _MISSING) | |
| if raw_label is _MISSING or raw_label is None or str(raw_label) == "": | |
| if not self.include_missing: | |
| return None | |
| raw_label = self.missing_label | |
| label = _stable_label(raw_label) | |
| if self.include_collection_prefix and collection is DiversityCollection.BOTH: | |
| return f"{_item_collection_name(item)}:{label}" | |
| return label | |
| def _weight_for(self, item: Any) -> float: | |
| """Return the non-negative contribution weight for an item.""" | |
| if self.weight_path is None: | |
| return 1.0 | |
| raw_weight = _read_path(item, self.weight_path, 0.0) | |
| if not _is_number(raw_weight): | |
| return 0.0 | |
| return max(0.0, float(raw_weight)) | |
| class DiversityTracker: | |
| """Track diversity metric values over time. | |
| This class is useful for simulations that want a small in-memory time series | |
| without requiring a full metrics subsystem. | |
| """ | |
| metric: DiversityMetric = field(default_factory=DiversityMetric) | |
| max_history: int = 1000 | |
| history: list[DiversityResult] = field(default_factory=list) | |
| def update(self, world: World) -> DiversityResult: | |
| """Compute the metric for the current world and append it to history.""" | |
| result = self.metric.compute(world) | |
| _append_bounded(self.history, result, self.max_history) | |
| return result | |
| def latest(self) -> DiversityResult | None: | |
| """Return the latest diversity result, if any.""" | |
| if not self.history: | |
| return None | |
| return self.history[-1] | |
| def to_series(self, value_key: str = "gini_simpson_index") -> list[dict[str, Any]]: | |
| """Return a JSON-friendly time series for one result field. | |
| Args: | |
| value_key: Name of the ``DiversityResult`` attribute to extract. | |
| Returns: | |
| List of dictionaries with ``step`` and ``value`` keys. | |
| """ | |
| series: list[dict[str, Any]] = [] | |
| for index, result in enumerate(self.history): | |
| value = getattr(result, value_key, None) | |
| if not _is_number(value): | |
| continue | |
| series.append( | |
| { | |
| "index": index, | |
| "step": result.step, | |
| "value": float(value), | |
| } | |
| ) | |
| return series | |
| def to_dict(self) -> dict[str, Any]: | |
| """Return a JSON-friendly tracker representation.""" | |
| return { | |
| "metric": { | |
| "name": self.metric.name, | |
| "collection": _normalize_collection(self.metric.collection).value, | |
| "group_by_path": self.metric.group_by_path, | |
| "weight_path": self.metric.weight_path, | |
| }, | |
| "max_history": self.max_history, | |
| "history": [result.to_dict() for result in self.history], | |
| } | |
| def compute_diversity( | |
| world: World, | |
| *, | |
| collection: DiversityCollection | str = DiversityCollection.AGENTS, | |
| group_by_path: str = "type", | |
| weight_path: str | None = None, | |
| alive_only: bool = True, | |
| include_missing: bool = False, | |
| ) -> DiversityResult: | |
| """Convenience function for computing generic diversity.""" | |
| metric = DiversityMetric( | |
| collection=collection, | |
| group_by_path=group_by_path, | |
| weight_path=weight_path, | |
| alive_only=alive_only, | |
| include_missing=include_missing, | |
| ) | |
| return metric.compute(world) | |
| def compute_agent_type_diversity( | |
| world: World, | |
| *, | |
| alive_only: bool = True, | |
| include_missing: bool = False, | |
| ) -> DiversityResult: | |
| """Compute diversity over agent ``type`` labels.""" | |
| return compute_diversity( | |
| world, | |
| collection=DiversityCollection.AGENTS, | |
| group_by_path="type", | |
| alive_only=alive_only, | |
| include_missing=include_missing, | |
| ) | |
| def compute_resource_type_diversity( | |
| world: World, | |
| *, | |
| weight_by_amount: bool = False, | |
| include_missing: bool = False, | |
| ) -> DiversityResult: | |
| """Compute diversity over resource ``type`` labels. | |
| Args: | |
| world: Runtime world object. | |
| weight_by_amount: If true, resource types are weighted by ``amount``. | |
| If false, each resource object contributes equally. | |
| include_missing: Whether resources missing ``type`` should be grouped | |
| under the missing label. | |
| """ | |
| return compute_diversity( | |
| world, | |
| collection=DiversityCollection.RESOURCES, | |
| group_by_path="type", | |
| weight_path="amount" if weight_by_amount else None, | |
| alive_only=False, | |
| include_missing=include_missing, | |
| ) | |
| def diversity_from_counts( | |
| counts: Mapping[str, float], | |
| *, | |
| metric_name: str = "diversity", | |
| collection: str = "custom", | |
| group_by_path: str = "group", | |
| weight_path: str | None = None, | |
| item_count: int | None = None, | |
| step: int | None = None, | |
| metadata: Mapping[str, Any] | None = None, | |
| ) -> DiversityResult: | |
| """Compute diversity statistics from precomputed group counts. | |
| Args: | |
| counts: Mapping from group label to non-negative weight. | |
| metric_name: Name stored in the result. | |
| collection: Collection label stored in the result. | |
| group_by_path: Grouping path stored in the result. | |
| weight_path: Optional weight path stored in the result. | |
| item_count: Optional number of contributing items. | |
| step: Optional world step. | |
| metadata: Optional result metadata. | |
| Returns: | |
| ``DiversityResult`` with diversity statistics. | |
| """ | |
| cleaned_counts = { | |
| str(label): max(0.0, float(weight)) | |
| for label, weight in counts.items() | |
| if _is_number(weight) and float(weight) > 0.0 | |
| } | |
| sorted_counts = dict(sorted(cleaned_counts.items(), key=lambda item: item[0])) | |
| total_weight = float(sum(sorted_counts.values())) | |
| richness = len(sorted_counts) | |
| if total_weight <= _EPSILON or richness == 0: | |
| return DiversityResult( | |
| metric_name=metric_name, | |
| collection=collection, | |
| group_by_path=group_by_path, | |
| weight_path=weight_path, | |
| item_count=0 if item_count is None else int(item_count), | |
| total_weight=0.0, | |
| richness=0, | |
| counts={}, | |
| proportions={}, | |
| step=step, | |
| metadata=metadata or {}, | |
| ) | |
| proportions = { | |
| label: weight / total_weight | |
| for label, weight in sorted_counts.items() | |
| } | |
| dominant_group, dominance = max( | |
| proportions.items(), | |
| key=lambda item: (item[1], item[0]), | |
| ) | |
| probability_values = np.asarray(list(proportions.values()), dtype=float) | |
| simpson_concentration = float(np.sum(probability_values**2)) | |
| gini_simpson_index = float(1.0 - simpson_concentration) | |
| inverse_simpson_index = ( | |
| float(1.0 / simpson_concentration) | |
| if simpson_concentration > _EPSILON | |
| else 0.0 | |
| ) | |
| shannon_entropy = float( | |
| -np.sum( | |
| probability_values | |
| * np.log(np.clip(probability_values, _EPSILON, None)) | |
| ) | |
| ) | |
| max_entropy = math.log(richness) if richness > 1 else 0.0 | |
| pielou_evenness = ( | |
| float(shannon_entropy / max_entropy) | |
| if max_entropy > _EPSILON | |
| else 1.0 | |
| ) | |
| return DiversityResult( | |
| metric_name=metric_name, | |
| collection=collection, | |
| group_by_path=group_by_path, | |
| weight_path=weight_path, | |
| item_count=len(sorted_counts) if item_count is None else int(item_count), | |
| total_weight=total_weight, | |
| richness=richness, | |
| counts=sorted_counts, | |
| proportions=proportions, | |
| dominant_group=dominant_group, | |
| dominance=float(dominance), | |
| simpson_concentration=simpson_concentration, | |
| gini_simpson_index=gini_simpson_index, | |
| inverse_simpson_index=inverse_simpson_index, | |
| shannon_entropy=shannon_entropy, | |
| pielou_evenness=pielou_evenness, | |
| step=step, | |
| metadata=metadata or {}, | |
| ) | |
| def _normalize_collection(value: DiversityCollection | str) -> DiversityCollection: | |
| """Normalize a diversity collection value.""" | |
| if isinstance(value, DiversityCollection): | |
| return value | |
| return DiversityCollection(str(value)) | |
| def _world_step(world: World) -> int | None: | |
| """Return the current world step if available.""" | |
| value = getattr(world, "step_count", None) | |
| if isinstance(value, Real) and not isinstance(value, bool): | |
| return int(value) | |
| return None | |
| def _is_number(value: Any) -> bool: | |
| """Return whether a value is a real numeric scalar, excluding booleans.""" | |
| return isinstance(value, (Real, np.integer, np.floating)) and not isinstance(value, bool) | |
| def _is_alive(item: Any) -> bool: | |
| """Return whether an item is alive when it exposes an ``alive`` field.""" | |
| return bool(getattr(item, "alive", True)) | |
| def _is_agent_like(item: Any) -> bool: | |
| """Return whether an item looks like a runtime agent.""" | |
| return hasattr(item, "alive") or hasattr(item, "behaviors") or hasattr(item, "policy") | |
| def _item_collection_name(item: Any) -> str: | |
| """Return a best-effort collection name for an item.""" | |
| if _is_agent_like(item): | |
| return DiversityCollection.AGENTS.value | |
| if hasattr(item, "amount"): | |
| return DiversityCollection.RESOURCES.value | |
| return "items" | |
| def _iter_world_items(world: World, collection: DiversityCollection) -> tuple[Any, ...]: | |
| """Return world items for a configured diversity collection.""" | |
| if collection is DiversityCollection.AGENTS: | |
| return _iter_collection(getattr(world, "agents", ())) | |
| if collection is DiversityCollection.RESOURCES: | |
| return _iter_collection(getattr(world, "resources", ())) | |
| agents = _iter_collection(getattr(world, "agents", ())) | |
| resources = _iter_collection(getattr(world, "resources", ())) | |
| return agents + resources | |
| def _iter_collection(raw_collection: Any) -> tuple[Any, ...]: | |
| """Return items from a mapping-backed or sequence-backed collection.""" | |
| if raw_collection is None: | |
| return () | |
| if isinstance(raw_collection, Mapping): | |
| values = raw_collection.values() | |
| elif isinstance(raw_collection, Iterable) and not isinstance(raw_collection, (str, bytes)): | |
| values = raw_collection | |
| else: | |
| values = (raw_collection,) | |
| return tuple(item for item in values if item is not None) | |
| def _split_path(path: str) -> tuple[str, ...]: | |
| """Split a dot-separated path into components.""" | |
| return tuple(part for part in str(path).split(".") if part) | |
| def _get_mapping_path(container: Mapping[str, Any], path: str, default: Any = _MISSING) -> Any: | |
| """Read a nested mapping value using dot notation.""" | |
| parts = _split_path(path) | |
| if not parts: | |
| return default | |
| current: Any = container | |
| for part in parts: | |
| if not isinstance(current, Mapping) or part not in current: | |
| return default | |
| current = current[part] | |
| return current | |
| def _get_object_path(root: Any, path: str, default: Any = _MISSING) -> Any: | |
| """Read nested values from mappings, sequences, or object attributes.""" | |
| parts = _split_path(path) | |
| if not parts: | |
| return root | |
| current: Any = root | |
| for part in parts: | |
| if isinstance(current, Mapping): | |
| if part not in current: | |
| return default | |
| current = current[part] | |
| continue | |
| if isinstance(current, Sequence) and not isinstance(current, (str, bytes)) and part.isdigit(): | |
| index = int(part) | |
| if index >= len(current): | |
| return default | |
| current = current[index] | |
| continue | |
| if not hasattr(current, part): | |
| return default | |
| current = getattr(current, part) | |
| return current | |
| def _read_path(item: Any, path: str, default: Any = _MISSING) -> Any: | |
| """Read a path from an item. | |
| Supported special prefixes: | |
| - ``state.foo`` | |
| - ``state:foo`` | |
| - ``memory.foo`` | |
| - ``memory:foo`` | |
| - ``metadata.foo`` | |
| - ``metadata:foo`` | |
| Without a prefix, object attributes and mapping keys are read directly. | |
| """ | |
| normalized_path = str(path) | |
| if normalized_path.startswith("state."): | |
| state = getattr(item, "state", {}) | |
| return _get_mapping_path( | |
| state if isinstance(state, Mapping) else {}, | |
| normalized_path.removeprefix("state."), | |
| default, | |
| ) | |
| if normalized_path.startswith("state:"): | |
| state = getattr(item, "state", {}) | |
| return _get_mapping_path( | |
| state if isinstance(state, Mapping) else {}, | |
| normalized_path.removeprefix("state:"), | |
| default, | |
| ) | |
| if normalized_path.startswith("memory."): | |
| memory = getattr(item, "memory", {}) | |
| return _get_mapping_path( | |
| memory if isinstance(memory, Mapping) else {}, | |
| normalized_path.removeprefix("memory."), | |
| default, | |
| ) | |
| if normalized_path.startswith("memory:"): | |
| memory = getattr(item, "memory", {}) | |
| return _get_mapping_path( | |
| memory if isinstance(memory, Mapping) else {}, | |
| normalized_path.removeprefix("memory:"), | |
| default, | |
| ) | |
| if normalized_path.startswith("metadata."): | |
| metadata = getattr(item, "metadata", {}) | |
| return _get_mapping_path( | |
| metadata if isinstance(metadata, Mapping) else {}, | |
| normalized_path.removeprefix("metadata."), | |
| default, | |
| ) | |
| if normalized_path.startswith("metadata:"): | |
| metadata = getattr(item, "metadata", {}) | |
| return _get_mapping_path( | |
| metadata if isinstance(metadata, Mapping) else {}, | |
| normalized_path.removeprefix("metadata:"), | |
| default, | |
| ) | |
| return _get_object_path(item, normalized_path, default) | |
| def _stable_label(value: Any) -> str: | |
| """Return a stable string label for a group value.""" | |
| if value is None: | |
| return "none" | |
| if isinstance(value, bool): | |
| return "true" if value else "false" | |
| if _is_number(value): | |
| numeric_value = float(value) | |
| if numeric_value.is_integer(): | |
| return str(int(numeric_value)) | |
| return str(numeric_value) | |
| if isinstance(value, Mapping): | |
| parts = [f"{key}={_stable_label(value[key])}" for key in sorted(value.keys(), key=str)] | |
| return "{" + ",".join(parts) + "}" | |
| if isinstance(value, Sequence) and not isinstance(value, (str, bytes)): | |
| return "[" + ",".join(_stable_label(item) for item in value) + "]" | |
| return str(value) | |
| def _normalize_label(value: Any, *, case_sensitive: bool, strip: bool) -> str: | |
| """Normalize a group label for deterministic comparisons.""" | |
| label = _stable_label(value) | |
| if strip: | |
| label = label.strip() | |
| if not case_sensitive: | |
| label = label.lower() | |
| return label | |
| def _append_bounded(items: MutableSequence[Any], value: Any, max_items: int) -> None: | |
| """Append an item while enforcing an optional maximum history length.""" | |
| items.append(value) | |
| if max_items > 0 and len(items) > max_items: | |
| del items[: len(items) - max_items] | |
| METRIC_REGISTRY: Mapping[str, type[DiversityMetric]] = MappingProxyType( | |
| { | |
| DiversityMetric.name: DiversityMetric, | |
| } | |
| ) | |
| __all__ = [ | |
| "DiversityCollection", | |
| "DiversityMetric", | |
| "DiversityResult", | |
| "DiversityTracker", | |
| "METRIC_REGISTRY", | |
| "compute_agent_type_diversity", | |
| "compute_diversity", | |
| "compute_resource_type_diversity", | |
| "diversity_from_counts", | |
| ] |