diff --git a/boa/__init__.py b/boa/__init__.py index b3f6671..08a060e 100644 --- a/boa/__init__.py +++ b/boa/__init__.py @@ -10,6 +10,7 @@ __version__ = "0.0.0" from boa.ax_instantiation_utils import * # noqa +from boa.controller import * # noqa from boa.instantiation_base import * # noqa from boa.metrics.modular_metric import * # noqa from boa.runner import * # noqa diff --git a/boa/_doc_utils.py b/boa/_doc_utils.py index 970d82a..8e4098f 100644 --- a/boa/_doc_utils.py +++ b/boa/_doc_utils.py @@ -1,26 +1,29 @@ -import inspect -from pathlib import Path +import importlib +import pkgutil -FILENAME = Path(__file__).name +import boa -def get_calling_file_path(): - frames = inspect.stack() - frame = 1 - while (file_path := Path(frames[frame].filename)).name == FILENAME: - frame += 1 - return file_path +def get_boa_submodules(): + print(boa) + module_list = [] + packages = [] + for module_finder, modname, is_pkg in pkgutil.walk_packages(boa.__path__): + if is_pkg: + packages.append(modname) + if len(modname.split(".")) > 1: + module_list.append(modname) + return module_list -def add_ref_to_rel_init(): - file_path = get_calling_file_path() +def import_boa_submod(submod_str): + return importlib.import_module(f"boa.{submod_str}") - parent = file_path.parent - pathing_ls = [] - for part in reversed(parent.parts): - pathing_ls.append(part) - if part == "boa": - break - pathing = ".".join(module for module in reversed(pathing_ls)) - return f"""**See More Information Here**: :mod:`{pathing}`""" +def add_ref_to_all_submodules_inits(): + module_list = get_boa_submodules() + for mod_str in module_list: + submod = import_boa_submod(mod_str) + info = f"""**Overview Information Here**: :mod:`{submod.__package__}`""" + + submod.__doc__ = f"{submod.__doc__}\n\n{info}" if submod.__doc__ else info diff --git a/boa/ax_instantiation_utils.py b/boa/ax_instantiation_utils.py index 8e779aa..79ee642 100644 --- a/boa/ax_instantiation_utils.py +++ b/boa/ax_instantiation_utils.py @@ -1,3 +1,12 @@ +""" +################################### +Ax Instantiation Utility Functions +################################### + +Utility functions to instantiate Ax objects + +""" + from __future__ import annotations import copy diff --git a/boa/controller.py b/boa/controller.py index d0b4dc7..c44096f 100644 --- a/boa/controller.py +++ b/boa/controller.py @@ -1,3 +1,11 @@ +""" +################################### +Controller +################################### + +""" + + import logging import os import time @@ -5,11 +13,11 @@ from ax.service.scheduler import Scheduler -from boa import BaseWrapper from boa.ax_instantiation_utils import get_experiment, get_scheduler from boa.runner import WrappedJobRunner from boa.storage import scheduler_to_json_file from boa.utils import get_dictionary_from_callable +from boa.wrappers.wrapper import BaseWrapper from boa.wrappers.wrapper_utils import get_dt_now_as_str diff --git a/boa/metaclasses.py b/boa/metaclasses.py index 1a1cb2f..df1eb0d 100644 --- a/boa/metaclasses.py +++ b/boa/metaclasses.py @@ -1,3 +1,14 @@ +""" +######################## +Meta Classes +######################## + +Meta class modify class behaviors, such as having all subclasses of +:class:`.BaseWrapper` will wrap functions in :func:`.cd_and_cd_back_dec` +to make sure whatever directory changes users do inside a wrapper function, +the directory is returned afterwards. + +""" import logging import traceback from abc import ABCMeta diff --git a/boa/metrics/__init__.py b/boa/metrics/__init__.py index c443a38..54230d6 100644 --- a/boa/metrics/__init__.py +++ b/boa/metrics/__init__.py @@ -1,8 +1,4 @@ -def foo(): - return "hi" - - -__doc__ = f""" +""" ################################################ Metrics Overview & Advanced Usage ################################################ @@ -17,8 +13,6 @@ def foo(): See :doc:`/user_guide/configuration` - -{foo()} """ from boa.metrics.metrics import * # noqa diff --git a/boa/metrics/metric_funcs.py b/boa/metrics/metric_funcs.py index 62c29b4..600f4c2 100644 --- a/boa/metrics/metric_funcs.py +++ b/boa/metrics/metric_funcs.py @@ -1,10 +1,18 @@ +""" +######################## +Metric Functions +######################## + +Functions used for Metrics + +""" + import logging import numpy as np import scipy.stats as stats from sklearn.metrics import mean_squared_error -from boa._doc_utils import add_ref_to_rel_init from boa.utils import get_dictionary_from_callable logger = logging.getLogger(__name__) @@ -15,10 +23,12 @@ def normalized_root_mean_squared_error(y_true, y_pred, normalizer="iqr", **kwarg Parameters ---------- - y_true : array-like of shape (n_samples,) or (n_samples, n_outputs) + y_true : array_like + With shape (n_samples,) or (n_samples, n_outputs) Ground truth (correct) target values. - y_pred : array-like of shape (n_samples,) or (n_samples, n_outputs) + y_pred : array_like + With shape (n_samples,) or (n_samples, n_outputs) Estimated target values. normalizer : str @@ -30,7 +40,7 @@ def normalized_root_mean_squared_error(y_true, y_pred, normalizer="iqr", **kwarg Returns ------- - nrmse : float or ndarray of floats + nrmse : float or numpy.ndarray[float] A normalized version of RMSE """ rmse = mean_squared_error(y_true, y_pred, squared=False, **get_dictionary_from_callable(mean_squared_error, kwargs)) @@ -47,14 +57,3 @@ def normalized_root_mean_squared_error(y_true, y_pred, normalizer="iqr", **kwarg nrmse = rmse / norm return nrmse - - -__doc__ = f""" -######################## -Metric Functions -######################## - -Functions used for Metrics - -{add_ref_to_rel_init()} -""" diff --git a/boa/metrics/metrics.py b/boa/metrics/metrics.py index c12072f..5c62123 100644 --- a/boa/metrics/metrics.py +++ b/boa/metrics/metrics.py @@ -1,6 +1,40 @@ +""" +######################## +List of Metrics +######################## + +List of Metrics that are already defined in BOA + +Any of these Metrics can be used directly in your configuration file + +Examples +======== +.. 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 + +.. 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 + +""" + + import numpy as np -from boa._doc_utils import add_ref_to_rel_init from boa.metrics.metric_funcs import ( normalized_root_mean_squared_error as normalized_root_mean_squared_error_, ) @@ -148,39 +182,3 @@ def __init__(self, metric_to_eval=normalized_root_mean_squared_error_, lower_is_ NRMSE = NormalizedRootMeanSquaredError normalized_root_mean_squared_error = NormalizedRootMeanSquaredError - - -__doc__ = f""" -######################## -List of Metrics -######################## - -List of Metrics that are already defined in BOA - -Any of these Metrics can be used directly in your configuration file - -Examples -======== -.. 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 - -.. 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 - -{add_ref_to_rel_init()} -""" diff --git a/boa/metrics/modular_metric.py b/boa/metrics/modular_metric.py index 5a08a33..e8028f9 100644 --- a/boa/metrics/modular_metric.py +++ b/boa/metrics/modular_metric.py @@ -1,3 +1,10 @@ +""" +######################## +Modular Metric +######################## + +""" + from __future__ import annotations import logging @@ -16,7 +23,6 @@ from sklearn.metrics import __all__ as sklearn_all import boa.metrics.synthetic_funcs -from boa._doc_utils import add_ref_to_rel_init from boa.metaclasses import MetricRegister from boa.utils import ( extract_init_args, @@ -155,8 +161,8 @@ class ModularMetric(NoisyFunctionMetric, metaclass=MetricRegister): ``metric_to_eval``. You can further customize the behavior of your metric by passing a - :class:`Wrapper`, which has will run methods - such as :meth:`~boa.wrapper.BaseWrapper.fetch_trial_data` before + :class:`Wrapper<.BaseWrapper>`, which has will run methods + such as :meth:`~.BaseWrapper.fetch_trial_data` before calling the specified metric to evaluate, which can allow you to preprocess/prepare model output data for your metric calculation. @@ -320,12 +326,3 @@ def __repr__(self) -> str: arg_str = " ".join(f"{k}={v}" for k, v in init_dict.items()) return f"{self.__class__.__name__}({arg_str})" - - -__doc__ = f""" -######################## -Modular Metric -######################## - -{add_ref_to_rel_init()} -""" diff --git a/boa/metrics/synthetic_funcs.py b/boa/metrics/synthetic_funcs.py index 4640055..436c182 100644 --- a/boa/metrics/synthetic_funcs.py +++ b/boa/metrics/synthetic_funcs.py @@ -1,9 +1,14 @@ +""" +######################## +Synthetic Function +######################## + +""" + from ax.utils.measurement.synthetic_functions import from_botorch from botorch.test_functions.synthetic import Hartmann from torch import Tensor -from boa._doc_utils import add_ref_to_rel_init - class Hartmann4(Hartmann): dim = 4 @@ -23,12 +28,3 @@ def optimizers(self) -> Tensor: hartmann4 = from_botorch(Hartmann4()) - - -__doc__ = f""" -######################## -Synthetic Function -######################## - -{add_ref_to_rel_init()} -""" diff --git a/boa/runner.py b/boa/runner.py index 6f0bc61..81ddc26 100644 --- a/boa/runner.py +++ b/boa/runner.py @@ -1,3 +1,12 @@ +""" +################################### +Wrapped Runner +################################### + +Runner that calls your :mod:`.wrappers` to run your model and poll the trial status. + +""" + from collections import defaultdict from typing import Any, Dict, Iterable, Set diff --git a/boa/storage.py b/boa/storage.py index b7d8561..af1a392 100644 --- a/boa/storage.py +++ b/boa/storage.py @@ -1,3 +1,13 @@ +""" +######################## +Saving and Loading +######################## + +Functions for saving and loading your experiment to +stop and restart. + +""" + import json import logging import os diff --git a/boa/utils.py b/boa/utils.py index 7281455..7584d49 100644 --- a/boa/utils.py +++ b/boa/utils.py @@ -1,3 +1,13 @@ +""" +################################### +General Package Utility Functions +################################### + +Utility functions useful for various package things like +getting the signature matching + +""" + from __future__ import annotations import inspect diff --git a/boa/wrappers/wrapper.py b/boa/wrappers/wrapper.py index 7be39bc..15359f2 100644 --- a/boa/wrappers/wrapper.py +++ b/boa/wrappers/wrapper.py @@ -1,3 +1,10 @@ +""" +######################## +Base Wrapper +######################## + +""" + from __future__ import annotations import logging @@ -6,7 +13,6 @@ from ax.core.base_trial import BaseTrial -from boa._doc_utils import add_ref_to_rel_init from boa.metaclasses import WrapperRegister from boa.wrappers.wrapper_utils import ( load_jsonlike, @@ -187,12 +193,3 @@ def fetch_trial_data(self, trial: BaseTrial, metric_properties: dict, metric_nam A dictionary with the keys matching the keys of the metric function used in the objective """ - - -__doc__ = f""" -######################## -Base Wrapper -######################## - -{add_ref_to_rel_init()} -""" diff --git a/boa/wrappers/wrapper_utils.py b/boa/wrappers/wrapper_utils.py index 9897da7..e1616cc 100644 --- a/boa/wrappers/wrapper_utils.py +++ b/boa/wrappers/wrapper_utils.py @@ -1,3 +1,12 @@ +""" +######################## +Wrapper Utility Tools +######################## + +Utility tools for to ease model wrapping. + +""" + from __future__ import annotations import datetime as dt @@ -14,8 +23,6 @@ from ax.core.parameter import ChoiceParameter, FixedParameter, RangeParameter from ax.utils.common.docutils import copy_doc -from boa._doc_utils import add_ref_to_rel_init - logger = logging.getLogger(__file__) @@ -123,11 +130,11 @@ def load_json(file: os.PathLike | str, normalize: bool = True, *args, **kwargs) file : os.PathLike File path for the experiment configuration file normalize : bool - Whether to run boa.wrapper_utils.normalize_config after loading config + Whether to run :func:`.normalize_config` after loading config to run certain predictable configuration normalization. (default true) parameter_keys : str | list[Union[str, list[str], list[Union[str, int]]]] Alternative keys or paths to keys to parse as parameters to optimize, - for more information, see :func:`~boa.wrapper_utils.wpr_params_to_boa` + for more information, see :func:`.wpr_params_to_boa` Examples -------- @@ -421,8 +428,8 @@ def get_dt_now_as_str(fmt: str = "%Y%m%dT%H%M%S"): def make_experiment_dir( - working_dir: os.PathLike = None, - experiment_dir: os.PathLike = None, + working_dir: os.PathLike | str = None, + experiment_dir: os.PathLike | str = None, experiment_name: str = "", append_timestamp: bool = True, exist_ok: bool = True, @@ -433,10 +440,10 @@ def make_experiment_dir( Parameters ---------- - working_dir : os.Pathlike + working_dir : os.PathLike | str Working directory, the parent directory where the experiment directory will be written. Specify either a working directory and an experiment name or an experiment_dir - experiment_dir : os.Pathlike + experiment_dir : os.PathLike | str The exact dir the experiment directory boa will use to write the runs to. Specify either a working directory and an experiment name or an experiment_dir experiment_name: str @@ -450,7 +457,7 @@ def make_experiment_dir( Returns ------- - Path + pathlib.Path Path to the directory for the experiment """ if (working_dir and experiment_dir) or (not working_dir and not experiment_dir): @@ -505,7 +512,7 @@ def get_trial_dir(experiment_dir: os.PathLike, trial_index: int, **kwargs): Returns ------- - Path + pathlib.Path Directory for the trial """ trial_dir = Path(experiment_dir) / zfilled_trial_index(trial_index, **kwargs) # zero-padded trial index @@ -529,21 +536,9 @@ def make_trial_dir(experiment_dir: os.PathLike, trial_index: int, **kwargs): Returns ------- - Path + pathlib.Path Directory for the trial """ trial_dir = get_trial_dir(experiment_dir, trial_index, **kwargs) trial_dir.mkdir() return trial_dir - - -__doc__ = f""" -######################## -Wrapper Utility Tools -######################## - -Utility tools for to ease model wrapping. - -{add_ref_to_rel_init()} - -""" diff --git a/docs/code_reference.rst b/docs/code_reference.rst index dc10910..239972c 100644 --- a/docs/code_reference.rst +++ b/docs/code_reference.rst @@ -3,8 +3,8 @@ Code reference ############## -:mod:`boa.wrappers`: Wrapping tools -=================================== +:mod:`Wrappers and Tools ` +============================================= This is where you will find information about :doc:`BOA's ` Wrapper classes for optimization, as well as general wrapping utility functions that might be useful to wrap a model @@ -18,8 +18,8 @@ This is where you will find information about :doc:`BOA's ` Wrapper clas boa.wrappers.wrapper_utils -:mod:`boa.metrics`: Metrics -============================ +:mod:`Metrics `: +================================= This is where you will find information about :doc:`BOA's ` Metrics. @@ -48,7 +48,9 @@ Advanced Usage/Direct Python Access :toctree: api :template: custom_module_template_short.rst + boa.controller boa.ax_instantiation_utils boa.runner boa.utils boa.metaclasses + boa.instantiation_base diff --git a/docs/conf.py b/docs/conf.py index 230b76c..f595a31 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,58 +13,62 @@ import os import sys -# sys.path.insert(0, os.path.abspath('.')) -sys.path.insert(0, os.path.abspath('..')) -# sys.path.insert(0, os.path.abspath("../boa/")) -import importlib +# sys.path.insert(0, os.path.abspath(".")) +sys.path.insert(0, os.path.abspath("..")) +sys.path.insert(0, os.path.abspath("../boa/")) -import jinja2 -import traitlets -from docutils import nodes -from docutils.parsers.rst import Directive -from docutils.statemachine import ViewList -from sphinx.ext.autodoc import ClassDocumenter -from sphinx.util.nodes import nested_parse_with_titles -from boa.metrics.synthetic_funcs import Hartmann4 +from boa._doc_utils import add_ref_to_all_submodules_inits # -- Project information ----------------------------------------------------- -project = 'boa' -copyright = '2022, Madeline Scyphers, Justine Missik' -author = 'Madeline Scyphers, Justine Missik' +project = "boa" +copyright = "2022, Madeline Scyphers, Justine Missik" +author = "Madeline Scyphers, Justine Missik" # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# extensions coming with Sphinx (named "sphinx.ext.*") or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.napoleon', - 'sphinx.ext.autodoc', - 'sphinx.ext.autosummary', - 'sphinx.ext.autosectionlabel', - 'sphinx.ext.intersphinx', - 'sphinx.ext.viewcode', - 'sphinxext.remoteliteralinclude', - 'myst_nb', + "sphinx.ext.autodoc", + "sphinx.ext.todo", + "sphinx.ext.coverage", + "sphinx.ext.napoleon", + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.autosectionlabel", + "sphinx.ext.intersphinx", + "sphinx.ext.viewcode", + "sphinxext.remoteliteralinclude", + "myst_nb", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] source_suffix = { - '.rst': 'restructuredtext', - '.ipynb': 'myst-nb', - '.md': 'myst-nb', - '.myst': 'myst-nb', + ".rst": "restructuredtext", + ".ipynb": "myst-nb", + ".md": "myst-nb", + ".myst": "myst-nb", } +nitpicky = True +nitpick_ignore = [ + ('py:class', 'SearchSpace'), + ('py:class', 'Objective'), + ('py:class', 'BaseTrial'), + ('py:class', 'BaseTrial'), +] +nitpick_ignore_regex = [ + (r'.*', r'.*ax.*'), + (r'.*', r'.*botorch.*'), + (r'.*', r'array.*like'), +] # torch.nn has doc string reference sphinx has troubles resolving in its own code # Which get reference when we import from pytorch in general @@ -79,31 +83,27 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'references'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "references"] # -- Autosummary ------------------------------------------------------------- autosummary_generate = True # Turn on sphinx.ext.autosummary autoclass_content = "both" # Add __init__ doc (ie. params) to class summaries # autosummary_imported_members = True -# html_show_sourcelink = False # Remove 'view source code' from top of page (for html, not python) +# html_show_sourcelink = False # Remove "view source code" from top of page (for html, not python) autodoc_inherit_docstrings = True # If no docstring, inherit from base class -autodoc_member_order = 'bysource' - -autodoc_default_options = { - 'ignore-module-all': False -} +autodoc_member_order = "bysource" # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'pydata_sphinx_theme' +html_theme = "pydata_sphinx_theme" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -# html_static_path = ['_static'] +# html_static_path = ["_static"] html_theme_options = { "icon_links": [ @@ -125,86 +125,13 @@ # external project mapping for intersphinx intersphinx_mapping = { - 'fetch3': ('https://fetch3-nhl.readthedocs.io/en/latest', None), - 'python': ('http://docs.python.org/3', None), - 'numpy': ('http://docs.scipy.org/doc/numpy', None), - 'scipy': ('http://docs.scipy.org/doc/scipy/reference', None), - 'sklearn': ('http://scikit-learn.org/stable', None), + "fetch3": ("https://fetch3-nhl.readthedocs.io/en/latest", None), + "python": ("https://docs.python.org/3", None), + "numpy": ("https://numpy.org/doc/stable", None), + "scipy": ("https://docs.scipy.org/doc/scipy", None), + "sklearn": ("https://scikit-learn.org/stable", None), + "torch": ("https://pytorch.org/docs/stable", None), } -# intersphinx_mapping = { -# # "pytorch": ("https://pytorch.org/docs/stable/", None), -# "botorch": ("https://botorch.org/api/", None), -# "ax": ("https://ax.dev/versions/latest/api/", None) -# } -# -# -# class ExcludeClassDocumenter(ClassDocumenter): -# -# directivetype = 'excluded-class' -# objtype = 'excludedclass' -# excluded_classes = [ -# Hartmann4 -# ] -# -# @classmethod -# def can_document_member(cls, member, membername, isattr, parent): -# can_document = isinstance(member, type) and issubclass(member, Hartmann4) -# if can_document: -# print("\n\n\n\n\n", can_document) -# print(member) -# return can_document -# -# -# EXCLUDE_INHERITED_CLASS = jinja2.Template( -# u""" -# .. autoclass:: {{ classname }} -# :members: -# :undoc-members: -# """) -# -# -# def _import_obj(args): -# """Utility to import the object given arguments to directive""" -# if len(args) == 1: -# mod, clsname = args[0].rsplit('.', 1) -# elif len(args) > 1 and args[-2] == ':module:': -# mod = args[-1] -# clsname = args[0].split('(')[0] -# else: -# raise ValueError("Args do not look as expected: {0}".format(args)) -# mod = importlib.import_module(mod) -# return getattr(mod, clsname) -# -# -# class ExcludeClassDirective(Directive): -# has_content = True -# required_arguments = 1 -# optional_arguments = 20 -# -# def run(self): -# -# print("\n\n\n\n\n\nrunning, class: ", self.arguments[0]) -# -# # generate the documentation string -# rst_text = EXCLUDE_INHERITED_CLASS.render( -# classname=self.arguments[0] -# ) -# -# -# -# -# # parse and return documentation -# result = ViewList() -# for line in rst_text.split("\n"): -# result.append(line, "") -# node = nodes.paragraph() -# node.document = self.state.document -# nested_parse_with_titles(self.state, result, node) -# -# -# return node.children -# -# -# def setup(app): -# app.add_autodocumenter(ExcludeClassDocumenter) -# app.add_directive_to_domain('py', 'excluded-class', ExcludeClassDirective) + +# add a reference to the relative __init__.py of all files __doc__ in a submodules +add_ref_to_all_submodules_inits() diff --git a/docs/user_guide/package_overview.rst b/docs/user_guide/package_overview.rst index e222b6e..8df60cb 100644 --- a/docs/user_guide/package_overview.rst +++ b/docs/user_guide/package_overview.rst @@ -26,7 +26,7 @@ See the :ref:`instructions for configurations files` for details. Objective functions ==================== -When specifying your objective function to minimize or maximize, boa comes with a number of metrics you can use with your model output, such as MSE, :math:`R^2`, and others. For a list of current list of premade available of metrics, see See :class:`boa.metrics.metrics.METRICS` +When specifying your objective function to minimize or maximize, boa comes with a number of metrics you can use with your model output, such as MSE, :math:`R^2`, and others. For a list of current list of premade available of metrics, see See :mod:`.metrics.metrics` @@ -42,7 +42,7 @@ the model). A goal for the next stage of development is to allow for model wrapper functions to be written in other languages (e.g., R) -See the :ref:`instructions for creating a model wrapper ` for details. +See the :mod:`instructions for creating a model wrapper ` for details. ************************ Creating a run script @@ -58,6 +58,6 @@ See the :ref:`instructions for creating a run script ` for details. .. toctree:: :maxdepth: 2 - wrapper + ../api/boa.wrappers configuration run_script