1. Introduction

pydantic_sweep is built to construct parameter sweeps over pydantic.BaseModel classes. If you’re not familiar with how pydantic works, please first familiarize yourself with the pydantic documentation

The starting point for any parameter sweep is a (nested) pydantic.BaseModel. Since we want to use these models for configuring experiments, we need some custom configuration to make them safe. For now, this means we use the pydantic_sweep.BaseModel class instead. Please jump to the Model class section for details or if you want to use your own models.

import pprint

import pydantic_sweep as ps


class Sub(ps.BaseModel):
    x: int


class Model(ps.BaseModel):
    sub: Sub
    y: int = 6

While you could manually instantiate these models with your desired configuration, this library provides an automatic way to generate configuration. The basic entry-point is the field function, which constructs (nested) dictionaries of sub-configurations using “.” to identify nested fields. For example, to assign the values (1, 2, 3) to the field sub.x, we can run:

configs = ps.field("sub.x", [1, 2, 3])
pprint.pp(configs, width=45)
[{'sub': {'x': 1}},
 {'sub': {'x': 2}},
 {'sub': {'x': 3}}]

While we could instantiate the models with these configurations manually, we will use the initialize function, which will make for more compact code as we move to more complicated configuration setups later.

models = ps.initialize(Model, configs)
pprint.pp(models, width=45)
[Model(sub=Sub(x=1), y=6),
 Model(sub=Sub(x=2), y=6),
 Model(sub=Sub(x=3), y=6)]

Note that the models took the default values for y, while we only provided the partial configurations for x.

1.1. Fixed and Default values

Sometimes we want to set the same values for all the models. For this, we can use the constant field:

models = ps.initialize(Model, configs, constant=dict(y=0))
pprint.pp(models, width=45)
[Model(sub=Sub(x=1), y=0),
 Model(sub=Sub(x=2), y=0),
 Model(sub=Sub(x=3), y=0)]

The parameter y=0 is safely merged with the other configurations. If we only want to change the default values of the model, we need to use the default argument instead. We will also use the DefaultValue placeholder to define a placeholder that does not modify values.

models = ps.initialize(
    Model,
    ps.field("y", [10, ps.DefaultValue]),
    constant=dict(sub=dict(x=0)),
    default=dict(y=0),
)
pprint.pp(models, width=45)
[Model(sub=Sub(x=0), y=10),
 Model(sub=Sub(x=0), y=0)]

Note that while the original Model defines y=6, here the default value is 0 as defined above. For convenience, it is also possible to provide dot-seperated flattened dictionaries of the form {"sub.x": 0} for the constant and default arguments.

So far, we have encountered the two most important methods in this library: field and initialize. While these functions have a lot more features, so far we haven’t accomplished anything that couldn’t be easily done by a list-comprehension. In the next section, we will start to combine these configs in more interesting ways.