"""Namers for generating simulation IDs."""
import math
from abc import ABC, abstractmethod
import json
[docs]class Namer(ABC):
"""
Abstract class for assigning simulation IDs to simulation.
Only the ``next`` method has to be implemented.
"""
[docs] def start(self, length):
"""
Initialize naming.
Parameters
----------
length : int
Indicates how many total simulations are in the sweep.
"""
pass
[docs] @abstractmethod
def generate_id(self, param_set, sweep_id):
"""Generate simulation ID for a given parameter set.
Parameters
----------
param_set : dict
The parameter set
sweep_id : str
The sweep ID
"""
pass
[docs]class SequentialNamer(Namer):
"""
Name simulations with consecutive numbers and leading zeros.
Parameters
----------
zfill : None or int, optional
If provided, sets the width to which the name string is to be
padded with zeros.
start_at : int, optional
Sets the integer to start at in the sequential naming.
Examples
--------
>>> counter = SequentialNamer()
>>> counter.start(length=11)
>>> counter.generate_id({'key1': 'value1'}, '')
'00'
>>> counter.generate_id({'key2': 'value2'}, '')
'01'
>>> counter.start(length=2)
>>> counter.generate_id({'key1': 'value1'}, 'sweep_id')
'sweep_id_0'
"""
def __init__(self, zfill=None, start_at=0):
self.zfill_arg = zfill
self.start_at = start_at
[docs] def start(self, length):
self.count = self.start_at - 1
# Need to have `zfill_arg` separate because otherwise state can persist
# across multiple evaluations of `run_sweep`
if self.zfill_arg is None:
# Compute how many digits are needed to represent all the numbers
self.zfill = (math.floor(math.log10(length - 1) + 1) if length != 1
else 1)
else:
self.zfill = self.zfill_arg
self.length = length
[docs] def generate_id(self, param_set, sweep_id):
if self.count + 1 >= self.length + self.start_at:
raise StopIteration
self.count += 1
if sweep_id:
return '_'.join([sweep_id, str(self.count).zfill(self.zfill)])
else:
return str(self.count).zfill(self.zfill)
[docs]class HashNamer(Namer):
"""
Name simulations using hashing.
Parameters
----------
hash_length : int, optional
How many hexadecimal numbers to truncate the hash to. 6 by default.
Examples
--------
>>> namer = HashNamer()
>>> namer.generate_id({'key1': 'value1'}, '')
'31fc462e'
>>> namer.generate_id({'key2': 'value2'}, '')
'9970c8f5'
"""
def __init__(self, hash_length=8):
self.hash_length = hash_length
[docs] def generate_id(self, param_set, sweep_id):
from hashlib import sha1
h = sha1()
h.update(bytes(json.dumps(param_set), 'utf-8'))
h.update(bytes(sweep_id, 'utf-8'))
return h.hexdigest()[:self.hash_length]
[docs]class SetNamer(Namer):
"""
Name simulations consecutively with a provided iterable.
Parameters
----------
names : Iterable[str]
The sequence of names to assign to consecutive simulations.
Examples
--------
>>> namer = SetNamer(['name1', 'name2'])
>>> namer.generate_id({'key1': 'value1'}, '')
'name1'
>>> namer.generate_id({'key2': 'value2'}, '')
'name2'
"""
def __init__(self, names):
self.names = iter(names)
[docs] def generate_id(self, param_set, sweep_id):
return next(self.names)