Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/add jinja2 support #139

Merged
merged 3 commits into from
Aug 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions boa/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from boa.runner import * # noqa
from boa.scheduler import * # noqa
from boa.storage import * # noqa
from boa.template import render_template_from_path # noqa
from boa.wrappers.base_wrapper import * # noqa
from boa.wrappers.script_wrapper import * # noqa
from boa.wrappers.wrapper_utils import * # noqa
Expand Down
1 change: 0 additions & 1 deletion boa/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@
)
@click.option(
"--rel-to-config/--rel-to-here", # more cli friendly name for config option of rel_to_launch
# default=fields_dict(BOAConfig)["rel_to_config"].default, # make default opposite of rel_to_config
default=None,
help="Define all path and dir options in your config file relative to where boa is launched from"
" instead of relative to the config file location (the default)"
Expand Down
18 changes: 15 additions & 3 deletions boa/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,32 @@
Configuration Guide
###################


***********************
Example Configurations
***********************

This configuration can be generated from BOA with the following command::

python -m boa.config --output-path [path to output]

Default Configuration
==============================

.. literalinclude:: ../../docs/examples/default_config.yaml
:language: YAML


Jinja2 Templating
==============================

BOA supports Jinja2 templating in the configuration file. This allows for
the use of variables and conditionals in the configuration file. For example,
the following configuration file uses Jinja2 templating to set the
``parameters`` and ``parameter_constraints`` fields based on a loop.
Much more complex templating is possible, including the use of conditionals.
See :std:doc:`Jinja2 <jinja2:intro>` for more information on Jinja2 templating
and additional options and examples.

.. literalinclude:: ../../tests/test_configs/test_config_jinja2.yaml

"""
from boa.config.config import * # noqa: F401, F403

Expand Down
4 changes: 2 additions & 2 deletions boa/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -583,9 +583,9 @@ def __init__(self, parameter_keys=None, **config):
self.__attrs_init__(**config, parameter_keys=parameter_keys)

@classmethod
def from_jsonlike(cls, file, rel_to_config: Optional[bool] = None):
def from_jsonlike(cls, file, rel_to_config: Optional[bool] = None, template_kw: Optional[dict] = None):
config_path = pathlib.Path(file).resolve()
config = load_jsonlike(config_path)
config = load_jsonlike(config_path, template_kw=template_kw)

config = cls.convert_deprecated(configd=config)

Expand Down
2 changes: 1 addition & 1 deletion boa/metrics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@
Metrics can be specified in your configuration file by passing in the name
of one of the predefined :mod:`Metrics <boa.metrics.metrics>` in BOA.

See :doc:`/user_guide/configuration` for details.
See :doc:`/api/boa.config` for details.
"""
50 changes: 23 additions & 27 deletions boa/metrics/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,21 @@
.. code-block:: YAML

# Single objective optimization config
optimization_options:
objective_options:
objectives:
# List all of your metrics here,
# only list 1 metric for a single objective optimization
- metric: RootMeanSquaredError
objective:
metrics:
# List all of your metrics here,
# only list 1 metric for a single objective optimization
- metric: RootMeanSquaredError

.. code-block:: YAML

# MultiObjective Optimization config
optimization_options:
objective_options:
objectives:
# List all of your metrics here,
# only list multiple objectives for a multi objective optimization
- metric: RMSE
- metric: R2
objective:
metrics:
# List all of your metrics here,
# only list multiple objectives for a multi objective optimization
- metric: RMSE
- metric: R2

PassThrough Metric
******************
Expand All @@ -47,27 +45,25 @@
.. code-block:: YAML

# Single objective optimization config
optimization_options:
objective_options:
objectives:
# List all of your metrics here,
# only list 1 metric for a single objective optimization
- metric: PassThrough
name: foo # optional, any name will work
minimize: False
objective:
metrics:
# List all of your metrics here,
# only list 1 metric for a single objective optimization
- metric: PassThrough
name: foo # optional, any name will work
minimize: False

You could also simply specify a name, with no metric type and it will
default to a pass through metric:

.. code-block:: YAML

# Single objective optimization config
optimization_options:
objective_options:
objectives:
# List all of your metrics here,
# only list 1 metric for a single objective optimization
- name: foo # optional, any name will work
objective:
metrics:
# List all of your metrics here,
# only list 1 metric for a single objective optimization
- name: foo # optional, any name will work

If working in a language agnostic way, you can write out your output.json file like this
(see more at :mod:`Wrappers <boa.wrappers>`):
Expand Down
10 changes: 9 additions & 1 deletion boa/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@
import click
import panel as pn

import boa.plotting
from boa.plotting import app_view


@click.command()
@click.command(
epilog=f"Name of Plots to be added here: {', '.join(plot for plot in boa.plotting.__all__ if plot != 'app_view')}"
)
@click.option(
"-sp",
"--scheduler-path",
Expand All @@ -27,6 +30,11 @@
help="Path to scheduler json file.",
)
def main(scheduler_path):
"""
Launch a basic EDA plot view of your optimization.
Creating a web app with the scheduler json file.

"""
template = app_view(scheduler=scheduler_path)
pn.serve({pathlib.Path(__file__).name: template})

Expand Down
15 changes: 14 additions & 1 deletion boa/plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@
pn.extension("plotly")


__all__ = [
"plot_contours",
"plot_metrics_trace",
"plot_pareto_frontier",
"plot_slice",
"scheduler_to_df",
"app_view",
]


def _maybe_load_scheduler(scheduler: SchedulerOrPath):
if isinstance(scheduler, PathLike_tup):
scheduler = scheduler_from_json_file(scheduler)
Expand Down Expand Up @@ -414,7 +424,10 @@ def app_view(
pareto = plot_pareto_frontier(scheduler=scheduler, metric_names=metric_names)
else:
pareto = None
view.append(pn.Row(plot_metrics_trace(schedulers=scheduler, metric_names=metric_names), pareto))
row1 = pn.Row(plot_metrics_trace(schedulers=scheduler, metric_names=metric_names))
if pareto:
row1.append(pareto)
view.append(row1)
view.append(plot_slice(scheduler=scheduler))
view.append(plot_contours(scheduler=scheduler, metric_names=metric_names))
view.append(scheduler_to_df(scheduler))
Expand Down
24 changes: 22 additions & 2 deletions boa/template.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
from __future__ import annotations

from typing import Optional

import jinja2


def render_template(template_name, **kwargs):
template = jinja2.Environment(loader=jinja2.FileSystemLoader("templates")).get_template(template_name)
def render_template_from_path(path, template_kw: Optional[dict] = None, **kwargs):
"""
Render a template from a path.

Parameters
----------
path : str
Path to the template file.
template_kw : dict, optional
Dictionary of keyword arguments to pass to the jinja2.Template constructor.
**kwargs
Keyword arguments to pass to the template as variables to render.
"""
if template_kw is None:
template_kw = {}
template_kw.setdefault("extensions", []).append("jinja2.ext.do")
with open(path) as f:
template = jinja2.Template(f.read(), **template_kw)
return template.render(**kwargs)
2 changes: 1 addition & 1 deletion boa/wrappers/base_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def load_config(self, config_path: PathLike, *args, **kwargs) -> BOAConfig:
your configuration dataclass.

Load_config will (unless overwritten in a subclass), do some basic "normalizations"
to your configuration for convenience. See :func:`.normalize_config`
to your configuration for convenience. See :class:`.BOAConfig` and its __init__ method
for more information about how the normalization works and what config options you
can control.

Expand Down
32 changes: 13 additions & 19 deletions boa/wrappers/wrapper_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

from boa.definitions import IS_WINDOWS, PathLike, PathLike_tup
from boa.logger import get_logger
from boa.template import render_template_from_path
from boa.utils import (
_load_attr_from_module,
_load_module_from_path,
Expand Down Expand Up @@ -175,7 +176,7 @@ def split_shell_command(cmd: str):
return shlex.split(cmd, posix=not IS_WINDOWS)


def load_json(file: PathLike) -> dict:
def load_json(file: PathLike, **kwargs) -> dict:
"""
Read experiment configuration file for setting up the optimization.
The configuration file contains the list of parameters, and whether each parameter is a fixed
Expand All @@ -186,12 +187,9 @@ def load_json(file: PathLike) -> dict:
----------
file
File path for the experiment configuration file
normalize
Whether to run :func:`.normalize_config` after loading config
to run certain predictable configuration normalization. (default true)
parameter_keys
Alternative keys or paths to keys to parse as parameters to optimize,
for more information, see :func:`.wpr_params_to_boa`
kwargs
variables to pass to :func:`boa.template.render_template_from_path`
for rendering in your loaded file

Examples
--------
Expand All @@ -203,32 +201,28 @@ def load_json(file: PathLike) -> dict:
-------
loaded_configs: dict

See Also
-------- jmn nmn
:func:`.normalize_config` for information on ``parameter_keys`` option
"""
file = pathlib.Path(file).expanduser()
with open(file, "r") as f:
config = json.load(f)

s = render_template_from_path(file, **kwargs)
config = json.loads(s)
return config


@copy_doc(load_json)
def load_yaml(file: PathLike) -> dict:
def load_yaml(file: PathLike, **kwargs) -> dict:
file = pathlib.Path(file).expanduser()
with open(file, "r") as f:
config: dict = yaml.safe_load(f)
s = render_template_from_path(file, **kwargs)
config: dict = yaml.safe_load(s)
return config


@copy_doc(load_json)
def load_jsonlike(file: PathLike):
def load_jsonlike(file: PathLike, **kwargs) -> dict:
file = pathlib.Path(file)
if file.suffix.lstrip(".").lower() in {"yaml", "yml"}:
return load_yaml(file)
return load_yaml(file, **kwargs)
elif file.suffix.lstrip(".").lower() == "json":
return load_json(file)
return load_json(file, **kwargs)
else:
raise ValueError(f"Invalid config file format for config file {file}\nAccepted file formats are YAML and JSON.")

Expand Down
2 changes: 2 additions & 0 deletions docs/code_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ Plotting your Experiment
:toctree: api
:template: custom_module_template_short.rst

boa.plot
boa.plotting


Expand All @@ -71,3 +72,4 @@ Advanced Usage/Direct Python Access
boa.utils
boa.metaclasses
boa.instantiation_base
boa.template
4 changes: 3 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@
"icon": "fab fa-github-square",
# Whether icon should be a FontAwesome class, or a local file
}
]
],
"show_nav_level": 2,
}


Expand All @@ -153,6 +154,7 @@
"torch": ("https://pytorch.org/docs/stable", None),
"panel": ("https://panel.holoviz.org", None),
"pandas": ("https://pandas.pydata.org/docs", None),
"jinja2": ("https://jinja.palletsprojects.com/", None),
}

# BOA things
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/1run_r_streamlined.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"This notebook demonstrates how to:\n",
"\n",
"Write a basic Wrapper script in R and have BOA launch your optimization using the BOA CLI interface. See {mod}`instructions for creating a model wrapper <.boa.wrappers>` for more details about creating a wrapper script.\n",
"You can also look at {doc}`instructions for configurations files</user_guide/configuration>` for more details on creating a configuration file."
"You can also look at {doc}`instructions for configurations files</api/boa.config>` for more details on creating a configuration file."
]
},
{
Expand Down
9 changes: 3 additions & 6 deletions docs/examples/default_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -351,12 +351,6 @@ scheduler:
global_stopping_strategy: '...'
suppress_storage_errors_after_retries: '...'

# ######
# name #
# ######

name: '...'

# #######################
# parameter_constraints #
# #######################
Expand Down Expand Up @@ -405,6 +399,9 @@ script_options:
# (if neither experiment_dir nor output_dir are specified, output_dir defaults
# to whatever pwd returns (and equivalent on windows))
output_dir: '...'
# name of the experiment. Used with output_dir to create the experiment directory
# if experiment_dir is not specified.
exp_name: '...'
# Whether to append a timestamp to the output directory to ensure uniqueness.
# Defaults to `True` if not specified.
append_timestamp: '...'
Expand Down
Loading