# Copyright 2021 John Harwell, All rights reserved.
#
# SPDX-License-Identifier: MIT
"""
Reusable classes related to the homogeneous populations of agents.
"""
# Core packages
import typing as tp
import re
import math
import pathlib
# 3rd party packages
# Project packages
from sierra.core import types
from sierra.core.variables import batch_criteria as bc
from sierra.core.graphs import bcbridge
[docs]
class PopulationSize(bc.UnivarBatchCriteria):
"""
Base class for changing the # agents/robots to reduce code duplication.
"""
def __init__(self, *args, **kwargs) -> None:
bc.UnivarBatchCriteria.__init__(self, *args, **kwargs)
def graph_xticks(
self,
cmdopts: types.Cmdopts,
batch_output_root: pathlib.Path,
exp_names: list[str],
) -> list[float]:
ret = list(map(float, self.populations(cmdopts, exp_names)))
if cmdopts["plot_log_xscale"]:
return [int(math.log2(x)) for x in ret]
if cmdopts["plot_enumerated_xscale"]:
return list(range(0, len(ret)))
return ret
def graph_xticklabels(
self,
cmdopts: types.Cmdopts,
batch_output_root: pathlib.Path,
exp_names: list[str],
) -> list[str]:
if exp_names is None:
exp_names = self.gen_exp_names()
ret = map(float, self.populations(cmdopts, exp_names))
return [str(int(round(x, 4))) for x in ret]
def graph_xlabel(self, cmdopts: types.Cmdopts) -> str:
if cmdopts["plot_log_xscale"]:
return r"$\log$(System Size)"
return "System Size"
[docs]
def graph_info(
self,
cmdopts: types.Cmdopts,
batch_output_root: tp.Optional[pathlib.Path] = None,
exp_names: tp.Optional[list[str]] = None,
) -> bcbridge.GraphInfo:
"""Return graph info for base classes to use if they wish."""
info = bcbridge.GraphInfo(
cmdopts,
batch_output_root,
exp_names if exp_names else self.gen_exp_names(),
)
info.xlabel = self.graph_xlabel(info.cmdopts)
info.xticklabels = self.graph_xticklabels(
info.cmdopts, info.batch_output_root, info.exp_names
)
info.xticks = self.graph_xticks(
info.cmdopts, info.batch_output_root, info.exp_names
)
return info
[docs]
def parse(arg: str) -> list[int]:
"""Generate the system sizes for each experiment in a batch."""
spec = {
"max_size": 0,
"model": "",
"cardinality": None,
} # type: tp.Dict[str, tp.Union[str, tp.Optional[int]]]
sections = arg.split(".")
# remove batch criteria variable name, leaving only the spec
sections = sections[1:]
assert len(sections) >= 1 and len(sections) <= 2, (
"Spec must have 1 or 2 sections separated by '.'; "
f"have {len(sections)} from '{arg}'"
)
# Parse increment type
res = re.search("Log|Linear", sections[0])
assert (
res is not None
), f"Bad size increment spec in criteria section '{sections[0]}'"
spec["model"] = res.group(0)
# Parse max size
res = re.search("[0-9]+", sections[0])
assert res is not None, "Bad population max in criteria section '{sections[0]}'"
max_size = int(res.group(0))
spec["max_size"] = max_size
# Parse cardinality for linear models
if spec["model"] == "Linear":
if len(sections) == 2:
res = re.search("C[0-9]+", sections[1])
assert (
res is not None
), "Bad cardinality in criteria section '{sections[1]}'"
spec["cardinality"] = int(res.group(0)[1:])
else:
spec["cardinality"] = int(spec["max_size"] / 10.0)
elif spec["model"] == "Log":
spec["cardinality"] = len(range(0, int(math.log2(max_size)) + 1))
if spec["model"] == "Linear":
increment = int(spec["max_size"] / spec["cardinality"])
return [increment * x for x in range(1, spec["cardinality"] + 1)]
if spec["model"] == "Log":
return [int(2**x) for x in range(0, spec["cardinality"])]
raise ValueError("Bad value for model: {}".format(spec["model"]))
__all__ = [
"PopulationSize",
"parse",
]