Sweep through config settings with nested values to sweep
Project description
Config Sweep
Sweep through config settings with nested values to sweep
- Sweep existing yaml, json or dataclass configs
- Supports nested sweep definitions
- Prioritize sweeps to minimize cache thrash
- Use typed configs for dynamic systems with the create_affiliate protocol
- Save sweep and dataclass configs with ClassifiedJSON
pip install 'configsweep[classifiedjson]'
from configsweep import Sweep, Sweeper
from classifiedjson import dump
# set priorty on datasources to sweep through those values first
# to avoid reloading the same data
sweep_config = { "strategy": Sweep([
{"name": "strategy_one", "max": 10000},
{"name": "strategy_two", "min": Sweep([10,20,30]), "max": Sweep([10000,90000])}
]),
"datasources": Sweep(["en", "es", "de", "fr"], priority=1)
}
for combo in Sweeper(sweep_config):
print(combo.config)
with open('sweep_config.json', 'w') as f:
dump(sweep_config, f)
output
{'strategy': {'name': 'strategy_one', 'max': 10000}, 'datasources': 'en'}
{'strategy': {'name': 'strategy_two', 'min': 10, 'max': 10000}, 'datasources': 'en'}
{'strategy': {'name': 'strategy_two', 'min': 10, 'max': 90000}, 'datasources': 'en'}
{'strategy': {'name': 'strategy_two', 'min': 20, 'max': 10000}, 'datasources': 'en'}
{'strategy': {'name': 'strategy_two', 'min': 20, 'max': 90000}, 'datasources': 'en'}
{'strategy': {'name': 'strategy_two', 'min': 30, 'max': 10000}, 'datasources': 'en'}
{'strategy': {'name': 'strategy_two', 'min': 30, 'max': 90000}, 'datasources': 'en'}
{'strategy': {'name': 'strategy_one', 'max': 10000}, 'datasources': 'es'}
{'strategy': {'name': 'strategy_two', 'min': 10, 'max': 10000}, 'datasources': 'es'}
{'strategy': {'name': 'strategy_two', 'min': 10, 'max': 90000}, 'datasources': 'es'}
{'strategy': {'name': 'strategy_two', 'min': 20, 'max': 10000}, 'datasources': 'es'}
{'strategy': {'name': 'strategy_two', 'min': 20, 'max': 90000}, 'datasources': 'es'}
{'strategy': {'name': 'strategy_two', 'min': 30, 'max': 10000}, 'datasources': 'es'}
{'strategy': {'name': 'strategy_two', 'min': 30, 'max': 90000}, 'datasources': 'es'}
{'strategy': {'name': 'strategy_one', 'max': 10000}, 'datasources': 'de'}
{'strategy': {'name': 'strategy_two', 'min': 10, 'max': 10000}, 'datasources': 'de'}
{'strategy': {'name': 'strategy_two', 'min': 10, 'max': 90000}, 'datasources': 'de'}
{'strategy': {'name': 'strategy_two', 'min': 20, 'max': 10000}, 'datasources': 'de'}
{'strategy': {'name': 'strategy_two', 'min': 20, 'max': 90000}, 'datasources': 'de'}
{'strategy': {'name': 'strategy_two', 'min': 30, 'max': 10000}, 'datasources': 'de'}
{'strategy': {'name': 'strategy_two', 'min': 30, 'max': 90000}, 'datasources': 'de'}
{'strategy': {'name': 'strategy_one', 'max': 10000}, 'datasources': 'fr'}
{'strategy': {'name': 'strategy_two', 'min': 10, 'max': 10000}, 'datasources': 'fr'}
{'strategy': {'name': 'strategy_two', 'min': 10, 'max': 90000}, 'datasources': 'fr'}
{'strategy': {'name': 'strategy_two', 'min': 20, 'max': 10000}, 'datasources': 'fr'}
{'strategy': {'name': 'strategy_two', 'min': 20, 'max': 90000}, 'datasources': 'fr'}
{'strategy': {'name': 'strategy_two', 'min': 30, 'max': 10000}, 'datasources': 'fr'}
{'strategy': {'name': 'strategy_two', 'min': 30, 'max': 90000}, 'datasources': 'fr'}
Installation
Python >=3.8
pip install 'configsweep[classifiedjson]'
If you don't need support to save sweep or dataclass configs to a file/string, you can install without classifiedjson.
pip install configsweep
Examples
Sweep yaml
import yaml
from configsweep import Sweep, Sweeper
data = { "start": 2024, "state": 'FL'}
with open('myconfig.yml', 'w',) as f:
yaml.dump(data, f, sort_keys=False)
with open('myconfig.yml', 'r') as f:
config = yaml.safe_load(f)
config['state'] = Sweep(['FL', 'MI', 'IN', 'AL'])
for combo in Sweeper(config):
print(combo.config)
output
{'start': 2024, 'state': 'FL'}
{'start': 2024, 'state': 'MI'}
{'start': 2024, 'state': 'IN'}
{'start': 2024, 'state': 'AL'}
Sweep dataclass
from dataclasses import dataclass
from enum import Enum
from configsweep import Sweep, Sweeper
from classifiedjson import dumps, loads
class Direction(Enum):
LEFT = 0
RIGHT = 1
@dataclass
class AlternateAfter:
left_times: int = 2
right_times: int = 3
starting_direction: Direction = Direction.RIGHT
sweep_config = AlternateAfter()
sweep_config.starting_direction = Sweep([Direction.RIGHT, Direction.LEFT])
# save off sweep config
sweep_json = dumps(sweep_config)
for combo in Sweeper(sweep_config):
print(combo.config.starting_direction)
# save off each combo
config_json = dumps(combo.config)
output
Direction.RIGHT
Direction.LEFT
Typed Config with the create_affiliate protocol and ClassifiedJSON
Using typed configs makes it easier to work with to get intelli-sense, docstrings, etc. However, there is a need to instantiate the system being configured. Adding the function create_affiliate to every config class does just that. The function create_affiliate creates an instance of the class it configures, i.e. it's affiliate. The config can pass itself to the affiliate class or pass all needed values to the affiliate class. The config acts as a factory for the affiliate class.
This is useful for handling union and subclass scenarios where you don't know the exact type ahead of time.
loss: Hinge | MSE | L1 ...
metric: MetricBase
When creating a system from a config, rather than having a separate factory class or large case/if statement to handle the various union/subclass scenarios, the config itself creates the correct affiliate class.
In addition to typed config and the create_affiliate protocol, there still needs to be the ability to save a config for reuse later. This is where typed serialization such as ClassifiedJSON comes in. ClassifiedJSON supports serializing typed python classes, such as dataclasses to json strings and deserialized back into the same typed pthon classes.
Thus, instead of dealing with large, untyped datastructures and yaml files, config can now be managed with typed python classes with type-hints, intelli-sense, docstrings, etc.
# Psuedo-code for typed config with the create_affiliate protocol and ClassifiedJSON
# in this case, the config pass in all needed values to create it's affiliate
class FavorLeftStrategy(GoalieStrategy):
def __init__(self, times: int):
super().__init__('favor left')
self._times = times
...
@dataclass
class FavorLeftConfig:
times: int = 2
def create_affiliate(self) -> FavorLeftStrategy:
return FavorLeftStrategy(self.times)
# in this case, the config passes itself to the affiliate it creates
class AlternateAfterStrategy(GoalieStrategy):
def __init__(self, config: AlternateAfter):
super().__init__('aleternate after')
self._config = config
...
@dataclass
class AlternateAfterConfig:
left_times: int = 2
def create_affiliate(self) -> AlternateAfterStrategy:
return AlternateAfterStrategy(self)
# config with a union
@dataclass
class PkConfig:
goalie_strategy: FavorLeft | AlternateAfter
...
def play_pk(config: PkConfig):
goalie = config.goalie_strategy.create_affiliate()
...
# build up the config with typed code . . .
left = FavorLeft(5)
config = PkConfig(left)
# save off the config
serialized_json = classifiedjson.dumps(config)
#... tempus fugit...
# now use the config
config = classifiedjson.loads(serialized)
play_pk(config)
Example of typed config with create_affiliate protocol and ClassifiedJSON
from dataclasses import dataclass
from enum import Enum
from configsweep import Sweep, Sweeper
from classifiedjson import dumps, loads
class Direction(Enum):
LEFT = 0
RIGHT = 1
###################################
# Strategy sessions
class GoalieStrategy:
def __init__(self, name: str):
self.name = name
def go_direction(self) -> Direction:
pass
class FavorLeftStrategy(GoalieStrategy):
def __init__(self, times: int):
super().__init__('favor left')
self._times = times
self._counter = 0
def go_direction(self) -> Direction:
self._counter += 1
if self._counter % self._times == 0:
return Direction.RIGHT
else:
return Direction.LEFT
@dataclass
class FavorLeft:
times: int = 2
def create_affiliate(self) -> FavorLeftStrategy:
return FavorLeftStrategy(self.times)
class AlternateAfterStrategy(GoalieStrategy):
def __init__(self, config: AlternateAfter):
super().__init__('aleternate after')
self.config = config
self.counter = 0
self.next_direction = config.starting_direction
def go_direction(self) -> Direction:
direction = self.next_direction
self.counter += 1
if direction == Direction.LEFT and self.counter == self.config.left_times:
self.next_direction = Direction.RIGHT
self.counter = 0
elif direction == Direction.RIGHT and self.counter == self.config.right_times:
self.next_direction = Direction.LEFT
self.counter = 0
return direction
@dataclass
class AlternateAfter:
left_times: int = 2
right_times: int = 3
starting_direction: Direction = Direction.RIGHT
def create_affiliate(self) -> AlternateAfterStrategy:
return AlternateAfterStrategy(self)
#######################
# strikers
@dataclass
class Striker:
name: str
strikes: list[int]
ronaldo = Striker('ronaldo', [0,1,0,0,1,0,1,0,1,0,1,1,1,1,1])
mbappe = Striker('mbappe', [0,1,0,1,-1,0,-1,0,1,1,1,0,0,-1,0,-1])
###################
# Game Time!
@dataclass
class PkConfig:
striker: Striker
goalie_strategy: FavorLeft | AlternateAfter
def play_pk(config: PkConfig):
striker = config.striker
goalie = config.goalie_strategy.create_affiliate()
scores = 0
saves = 0
for strike in striker.strikes:
goalie_direction = goalie.go_direction()
if strike == -1: # cheat code!
scores += 1
else:
strike_direction = Direction(strike)
if strike_direction == goalie_direction:
saves += 1
else:
scores += 1
return scores, saves
alternate_after = AlternateAfter()
alternate_after.left_times = Sweep(list(range(1, 5)))
alternate_after.right_times = Sweep(list(range(1, 5)))
config = PkConfig(Sweep([ronaldo, mbappe]),
Sweep([
FavorLeft(Sweep([2,3,4])),
alternate_after
]))
most_scores = None
most_config = None
for combo in Sweeper(config):
config = combo.config
scores, saves = play_pk(config)
if most_scores is None or scores > most_scores:
most_scores = scores
most_config = config
print(f"most_scores={most_scores} ({most_config.striker.name})")
print(f"worst goalie strategy = {most_config.goalie_strategy}")
most_json = dumps(most_config)
#... tempus fugit...
config = loads(most_json)
#play back the most scores
scores, saves = play_pk(config)
print(f"scores={scores} ({config.striker.name})")
output
most_scores=13 (mbappe)
worst goalie strategy = AlternateAfter(left_times=2, right_times=3, starting_direction=<Direction.RIGHT: 1>)
scores=13 (mbappe)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file configsweep-2.0.0.tar.gz
.
File metadata
- Download URL: configsweep-2.0.0.tar.gz
- Upload date:
- Size: 15.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.1 CPython/3.12.4
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 9bf25ca6b3d7a8203947d4e269d29d202578f1dbc136205214a7373210eef8e9 |
|
MD5 | 0dc980fb638644b34d9a1e19bec2ef94 |
|
BLAKE2b-256 | 83c1fc6e035e37cc2833f9722fc7f4cfcc6f942bad5f230ba352b3f91000f57e |
File details
Details for the file configsweep-2.0.0-py3-none-any.whl
.
File metadata
- Download URL: configsweep-2.0.0-py3-none-any.whl
- Upload date:
- Size: 11.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.1 CPython/3.12.4
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 821ff97f1deb621a58efb0ca1a461cf69edeefff71de03dde19095c167f74b89 |
|
MD5 | a3b00a87018b17660725bb12f496ad6c |
|
BLAKE2b-256 | 6006d20de35d212239ebff55812724ce8241a17bc337ac607d22f7c96bdbd753 |