OGC/docs/parsnip.md

131 lines
4.6 KiB
Markdown
Raw Permalink Normal View History

2024-06-25 16:22:33 +02:00
# `Parsnip`
## 🥕 `argparse` with conditional argument groups.
As `minimax.train` is the single point-of-entry for training, its command-line arguments can grow quickly in number with each additional autocurriculum method supported in `minimax`. This complexity arises for several reasons:
- New components in the form of training runners, environments, agents, and models may require additional arguments
- New components may require existing arguments shared with previous components
- New components may overload the meaning of existing arguments used by other components
We make use of a custom module called `Parsnip` to help manage the complexity of specifying and parsing command-line arguments. `Parsnip` allows the creation of named argument groups, which allows adding new arguments while explicitly separating them into name spaces. Each argument group results in its own kwarg dictionary when parsed.
`Parsnip` directly builds on `argparse` by adding the notion of a "subparser". Here, a subparser is simply an `argparse` parser responsible for a named argument group. Subparsers enable some useful behavior:
- Arguments can be added to the top-level `Parsnip` parser or to a subparser.
- Each subparser is initialized with a `name` for its corresponding argument group. All arguments under this subparser will be contained in a nested kwarg dictionary under the key equal to `name`.
- Each subparser can be initialized with an optional `prefix`, in which case all command-line arguments added to the subparser will be prepended with the value of `prefix` (see example below), thus creating a namespace for the corresponding argument group.
- Subparsers can be added conditionally, based on the specific value of a top-level argument (with support for the wildcard `*`).
- After parsing, `Parsnip` produces a kwargs dictionary containing a key:value pair for each top-level argument and a nested kwargs dictionary, under the key `<prefix>` containing the parsed arguments managed by each active subparser initialized with `prefix=<prefix>`.
Other than these details, `Parsnip`'s interface remains identical to that of `argparse`.
## A minimal example
In this example, we assume the parser is used inside a script called `run.py`.
```python
from util.parsnip import Parsnip
# Create a new Parsnip parser
parser = Parsnip()
# Add some top-level arguments (same as argparse)
parser.add_argument(
'--name',
type=str,
help='Name of my farm.')
parser.add_argument(
'--kind',
type=str,
choices=['apple', 'radish'],
help='What kind of farm I run.')
parser.add_argument(
'--n_acres',
type=str,
help='Size of my farm in acres.')
# Create a nested argument group with a prefix
crop_subparser = parser.add_subparser(name='crop', prefix='crop')
parser.add_argument(
'--n_acres',
type=str,
help='Size of land for growing radish, in acres.')
# Create a conditional argument group
radish_subparser = parser.add_subparser(
name='radish',
prefix='radish',
dependency={'crop': 'radish'},
dest='crop')
radish_subparser.add_argument(
'--is_pickled'
type=str2bool,
default=False,
help='Whether my farm produces pickled radish.')
# Create another conditional argument group
apple_subparser = parser.add_subparser(
name='apple',
prefix='apple',
dependency={'crop': 'apple'},
dest='crop')
apple_subparser.add_argument(
'--kind'
type=str,
choices=['fuji', 'mcintosh'],
default='fuji',
help='Whether my farm produces pickled radish.')
args = parser.parse_args()
```
Then running this command
```bash
python run.py \
--name 'Radelicious Farms' \
--kind radish \
--n_acres 200 \
--crop_n_acres 150 \
--radish_is_pickled
```
would produce this kwargs dictionary:
```python
{
'name': 'Radelicious Farms',
'kind': 'radish',
'n_acres': 200,
'crop_args': {
'n_acres': 150,
'is_pickled': True
}
}
```
Notice how the `prefix` for each subparser is appended to each argument name added to that subparser (e.g. `n_acres` became `crop_n_acres`, and `is_pickled` became `radish_is_pickled`). Also notice how the `radish_is_pickled` argument became active, as its activation conditions on `kind=radish`, as we specified when defining the `radish_subparser`.
Likewise, running this argument
```bash
python run.py \
--name 'Appledores Farms' \
--kind apple \
--n_acres 200 \
--crop_n_acres 150 \
--apple_kind fuji
```
results in this kwargs dictionary:
```python
{
'name': 'Appledores Farms',
'kind': 'apple',
'n_acres': 200,
'crop_args': {
'n_acres': 150,
'kind': 'fuji'
}
}
```