diff --git a/docs/_templates/autosummary/class.rst b/docs/_templates/autosummary/class.rst new file mode 100644 index 00000000..0c48f07c --- /dev/null +++ b/docs/_templates/autosummary/class.rst @@ -0,0 +1,20 @@ +{% extends "!autosummary/class.rst" %} + +{# +Ref: https://github.com/optuna/optuna/blob/41045cef7a8f5d0e5e4026bf0c5c649b269fc312/docs/source/_templates/autosummary/class.rst +An autosummary template to exclude the class constructor (__init__) +which doesn't contain any docstring in Optuna Dashboard. +#} + +{% block methods %} + {% set methods = methods | select("ne", "__init__") | list %} + {% if methods %} + .. rubric:: Methods + + .. autosummary:: + {% for item in methods %} + ~{{ name }}.{{ item }} + {%- endfor %} + {% endif %} + +{% endblock %} diff --git a/docs/api.rst b/docs/api.rst index 53b59d24..061e93fc 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -23,6 +23,7 @@ Human-in-the-loop :nosignatures: optuna_dashboard.register_objective_form_widgets + optuna_dashboard.register_user_attr_form_widgets optuna_dashboard.dict_to_form_widget optuna_dashboard.ChoiceWidget optuna_dashboard.SliderWidget diff --git a/docs/conf.py b/docs/conf.py index 35050f11..9056f0db 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -23,6 +23,7 @@ extensions = [ "sphinx.ext.autodoc", "sphinx.ext.doctest", "sphinx.ext.autosummary", + "sphinx.ext.napoleon", ] templates_path = ["_templates"] diff --git a/optuna_dashboard/_form_widget.py b/optuna_dashboard/_form_widget.py index 8afda925..2d6809ab 100644 --- a/optuna_dashboard/_form_widget.py +++ b/optuna_dashboard/_form_widget.py @@ -62,12 +62,37 @@ if TYPE_CHECKING: @dataclass class ChoiceWidget: + """A widget representing a choice with associated values. + + Args: + choices: A list of strings representing the available choices. + values: A list of float values associated with each choice. + description: A description of the widget. Defaults to None. + user_attr_key: The key used by `register_user_attr_form_widgets`. + Form output is saved as `trial.user_attrs[user_attr_key]`. Defaults to None. + + Example: + .. code-block:: python + + from optuna_dashboard import ChoiceWidget + + + choice_widget = ChoiceWidget( + choices=["A", "B", "C"], values=[1.0, 2.0, 3.0], description="Choose one" + ) + """ + choices: list[str] values: list[float] description: Optional[str] = None user_attr_key: Optional[str] = None def to_dict(self) -> ChoiceWidgetJSON: + """Convert the ChoiceWidget object to a dictionary. + + Returns: + ChoiceWidgetJSON: A dictionary representing the ChoiceWidget object. + """ return { "type": "choice", "description": self.description, @@ -89,6 +114,26 @@ class ChoiceWidget: @dataclass class SliderWidget: + """A widget representing a slider for selecting a value within a range. + + Args: + min: The minimum value of the slider. + max: The maximum value of the slider. + step: The step size for the slider. Defaults to None. + labels: A list of tuples containing value and label for the slider. Defaults to None. + description: A description for the slider. Defaults to None. + user_attr_key: The key used by `register_user_attr_form_widgets`. + Form output is saved as `trial.user_attrs[user_attr_key]`. Defaults to None. + + Example: + .. code-block:: python + + from optuna_dashboard import SliderWidget + + + slide_widget = SliderWidget(min=0, max=10, step=1, description="Example slider") + """ + min: float max: float step: Optional[float] = None @@ -97,6 +142,11 @@ class SliderWidget: user_attr_key: Optional[str] = None def to_dict(self) -> SliderWidgetJSON: + """Convert the SliderWidget instance to a dictionary. + + Returns: + SliderWidgetJSON: A dictionary representation of the SliderWidget instance. + """ labels: Optional[list[SliderWidgetLabel]] = None if self.labels is not None: labels = [{"value": value, "label": label} for value, label in self.labels] @@ -128,10 +178,33 @@ class SliderWidget: @dataclass class TextInputWidget: + """ + A text input widget class that defines a text input field. + + Args: + description: A description of the text input field. + user_attr_key: The key used by `register_user_attr_form_widgets`. + Form output is saved as `trial.user_attrs[user_attr_key]`. Defaults to None. + + Example: + .. code-block:: python + + from optuna_dashboard import TextInputWidget + + + text_input = TextInputWidget(description="Text Input Example") + """ + description: Optional[str] = None user_attr_key: Optional[str] = None def to_dict(self) -> TextInputWidgetJSON: + """ + Converts the TextInputWidget instance to a dictionary representation. + + Returns: + TextInputWidgetJSON: The dictionary representation of the TextInputWidget instance. + """ return { "type": "text", "description": self.description, @@ -149,9 +222,32 @@ class TextInputWidget: @dataclass class ObjectiveUserAttrRef: + """ + A class representing a reference to a value of `trial.user_attrs`. + When combined with `register_objective_form_widgets`, users can tell values that are + registered to `trial.user_attrs` during the human-in-the-loop optimization. + + Args: + key: The key of `trial.user_attrs` being referenced. + + Example: + .. code-block:: python + + from optuna_dashboard import ObjectiveUserAttrRef + + + user_attr_ref = ObjectiveUserAttrRef(key="key") + """ + key: str def to_dict(self) -> UserAttrRefJSON: + """ + Converts the ObjectiveUserAttrRef instance to a dictionary representation. + + Returns: + UserAttrRefJSON: The dictionary representation of the ObjectiveUserAttrRef instance. + """ return { "type": "user_attr", "key": self.key, @@ -197,6 +293,45 @@ def dict_to_form_widget(d: dict[str, Any]) -> ObjectiveFormWidget: def register_objective_form_widgets( study: optuna.Study, widgets: list[ObjectiveFormWidget] ) -> None: + """ + Register a list of form widgets to an Optuna study. + + Submitted values to the forms are told as each trial's objective values. + + Args: + study: The Optuna study object to register the form widgets for. + widgets: A list of ObjectiveFormWidget objects to be registered in the study. + + Raises: + ValueError: If the length of study directions is not equal to the length of widgets. + Warning: If any widget has `user_attr_key` specified, but it will not be used. + + Examples: + .. code-block:: python + + import optuna + from optuna_dashboard import ChoiceWidget, SliderWidget + from optuna_dashboard import register_objective_form_widgets + + + study = optuna.create_study() + register_objective_form_widgets( + study, + widgets=[ + ObjectiveChoiceWidget( + choices=["Good 👍", "Bad 👎"], + values=[-1, 1], + description="Please input your score!", + ), + ObjectiveSliderWidget( + min=1, + max=10, + step=1, + description="Higher is better.", + ), + ], + ) + """ if version.parse(optuna_ver) < version.Version("3.2") and isinstance( study._storage, optuna.storages._CachedStorage ): @@ -222,6 +357,47 @@ def register_objective_form_widgets( def register_user_attr_form_widgets( study: optuna.Study, widgets: list[ObjectiveFormWidget] ) -> None: + """ + Register a list of form widgets to an Optuna study. + + Submitted values to the forms are registered as each trial's user_attrs. + + Args: + study: The Optuna study object to register the form widgets for. + widgets: A list of ObjectiveFormWidget objects to be registered in the study. + + Raises: + ValueError: If an ObjectiveUserAttrRef is specified or if `user_attr_key` is not specified. + ValueError: If `user_attr_key` is not unique for each widget. + + Examples: + .. code-block:: python + + import optuna + from optuna_dashboard import ChoiceWidget, SliderWidget + from optuna_dashboard import register_user_attr_form_widgets + + + study = optuna.create_study() + register_user_attr_form_widgets( + study, + widgets=[ + ChoiceWidget( + choices=["Good 👍", "Bad 👎"], + values=[-1, 1], + description="Please input your score!", + user_attr_key="hitl/choice", + ), + SliderWidget( + min=1, + max=10, + step=1, + description="Higher is better.", + user_attr_key="hitl/slider", + ), + ], + ) + """ if version.parse(optuna_ver) < version.Version("3.2") and isinstance( study._storage, optuna.storages._CachedStorage ):