mirror of
https://github.com/wassname/optuna-dashboard.git
synced 2026-10-30 13:30:17 +08:00
Merge branch 'main' into add_color_scale_setting
This commit is contained in:
commit
70752ddf51
48 files changed
+1295
-360
No files matched your search
@@ -1,10 +1,10 @@
|
||||
name: e2e-tests
|
||||
name: e2e-dashboard-tests
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- '.github/workflows/e2e-tests.yml'
|
||||
- '.github/workflows/e2e-dashboard-tests.yml'
|
||||
- '**.py'
|
||||
- '**.ts'
|
||||
- '**.tsx'
|
||||
@@ -47,4 +47,4 @@ jobs:
|
||||
run: playwright install
|
||||
|
||||
- name: Run e2e tests
|
||||
run: pytest e2e_tests
|
||||
run: pytest e2e_tests/test_dashboard
|
||||
@@ -0,0 +1,51 @@
|
||||
name: e2e-standalone-tests
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- '.github/workflows/e2e-standalone-tests.yml'
|
||||
- '**.py'
|
||||
- '**.ts'
|
||||
- '**.tsx'
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
- 'tsconfig.json'
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
optuna-version: ['optuna==2.10.0', 'git+https://github.com/optuna/optuna.git']
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust toolchains
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Setup Optuna ${{ matrix.optuna-version }}
|
||||
run: |
|
||||
python -m pip install --progress-bar off --upgrade pip setuptools
|
||||
python -m pip install --progress-bar off --upgrade ${{ matrix.optuna-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --progress-bar off .
|
||||
python -m pip install --progress-bar off pytest-playwright
|
||||
|
||||
- name: Build standalone_app
|
||||
run: make MODE="prd" standalone_app/public/bundle.js
|
||||
|
||||
- name: Install the required browsers
|
||||
run: playwright install
|
||||
|
||||
- name: Run e2e tests
|
||||
run: pytest e2e_tests/test_standalone
|
||||
@@ -6,6 +6,7 @@ on:
|
||||
paths:
|
||||
- '.github/workflows/python-tests.yml'
|
||||
- '**.py'
|
||||
- 'pyproject.toml'
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -57,7 +58,7 @@ jobs:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
python-version: '3.11'
|
||||
architecture: x64
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
|
||||
@@ -24,6 +24,6 @@ jobs:
|
||||
days-before-pr-close: 7 # default number
|
||||
stale-issue-label: 'stale'
|
||||
stale-pr-label: 'stale'
|
||||
exempt-issue-labels: 'no-stale'
|
||||
exempt-issue-labels: 'no-stale,good-first-issue,contribution-welcome'
|
||||
exempt-pr-labels: 'no-stale'
|
||||
operations-per-run: 1000
|
||||
@@ -77,6 +77,18 @@ $ docker run -it --rm -p 8080:8080 ghcr.io/optuna/optuna-dashboard postgresql+ps
|
||||
|
||||
</details>
|
||||
|
||||
## Jupyter Lab Extension (Experimental)
|
||||
|
||||
You can install the Jupyter Lab extension via [PyPI](https://pypi.org/project/jupyterlab-optuna/).
|
||||
|
||||
```
|
||||
$ pip install jupyterlab jupyterlab-optuna
|
||||
```
|
||||
|
||||
<img src="./docs/_static/jupyterlab-extension.png" style="width:600px;" alt="Jupyter Lab Extension">
|
||||
|
||||
To use, click the tile to launch the extension, and enter your Optuna’s storage URL (e.g. `sqlite:///db.sqlite3`) in the dialog.
|
||||
|
||||
## Browser-only version (Experimental)
|
||||
|
||||
<img src="./docs/_static/browser-app.gif" style="width:600px;" alt="Browser-only version">
|
||||
@@ -93,7 +105,7 @@ https://optuna.github.io/optuna-dashboard/
|
||||
|
||||
You can install the VS Code extension via [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=Optuna.optuna-dashboard#overview).
|
||||
|
||||
<img src="./docs/_static/vscode-extension.png" style="width:600px;" alt="VSCode Extension">
|
||||
<img src="./docs/_static/vscode-extension.png" style="width:600px;" alt="VS Code Extension">
|
||||
|
||||
Please right-click the SQLite3 files (`*.db` or `*.sqlite3`) in the VS Code file explorer and select the "Open in Optuna Dashboard" command from the dropdown menu.
|
||||
This extension leverages the browser-only version of Optuna Dashboard, so the same limitations apply.
|
||||
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 244 KiB |
@@ -44,6 +44,7 @@ Preferential Optimization
|
||||
optuna_dashboard.preferential.create_study
|
||||
optuna_dashboard.preferential.load_study
|
||||
optuna_dashboard.preferential.PreferentialStudy
|
||||
optuna_dashboard.preferential.samplers.gp.PreferentialGPSampler
|
||||
optuna_dashboard.register_preference_feedback_component
|
||||
|
||||
Streamlit
|
||||
|
||||
@@ -178,6 +178,19 @@ or
|
||||
$ pip install uwsgi
|
||||
$ uwsgi --http :8080 --workeers 4 --wsgi-file wsgi.py
|
||||
|
||||
Jupyter Lab Extension (Experimental)
|
||||
--------------------------------
|
||||
|
||||
You can install the Jupyter Lab extension via `PyPI <https://pypi.org/project/jupyterlab-optuna/>`_.
|
||||
|
||||
.. figure:: _static/jupyterlab-extension.png
|
||||
:alt: Screenshot for the Jupyter Lab Extension
|
||||
:align: center
|
||||
:width: 800px
|
||||
|
||||
Jupyter Lab Extension
|
||||
|
||||
To use, click the tile to launch the extension, and enter your Optuna’s storage URL (e.g. ``sqlite:///db.sqlite3``) in the dialog.
|
||||
|
||||
Browser-only version (Experimental)
|
||||
-----------------------------------
|
||||
|
||||
@@ -5,11 +5,11 @@ What is Preferential Optimization?
|
||||
----------------------------------
|
||||
|
||||
Preferential optimization is a method for optimizing hyperparameters, focusing of human preferences, by determining which trial is superior when comparing a pair.
|
||||
It differs from `human-in-the-loop optimization utilizing objective form widgets <tutorial-hitl-objective-form-widgets>`_,
|
||||
It differs from :ref:`human-in-the-loop optimization utilizing objective form widgets <tutorial-hitl-objective-form-widgets>`,
|
||||
which relies on absolute evaluations, as it significantly reduces fluctuations in evaluators' criteria, thus ensuring more consistent results.
|
||||
|
||||
In this tutorial, we'll interactively optimize RGB values to generate a color resembling a "sunset hue",
|
||||
aligining with the problem setting in `this tutorial <tutorial-hitl-objective-form-widgets>`_.
|
||||
aligining with the problem setting in :ref:`this tutorial <tutorial-hitl-objective-form-widgets>`.
|
||||
Familiarity with the tutorial ob objective form widgets may enhance your understanding.
|
||||
|
||||
How to Run Preferential Optimization
|
||||
@@ -27,7 +27,7 @@ First, ensure the necessary packages are installed by executing the following co
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ pip install "optuna>=3.3.0" "optuna-dashboard>=0.13.0b1" pillow botorch
|
||||
$ pip install "optuna>=3.3.0" "optuna-dashboard[preferential]>=0.13.0b1" pillow
|
||||
|
||||
Next, execute the Python script, copied from `generator.py`_.
|
||||
|
||||
|
||||
File renamed without changes.
Whitespace-only changes.
+1
-1
@@ -2,7 +2,7 @@ import optuna
|
||||
from playwright.sync_api import Page
|
||||
import pytest
|
||||
|
||||
from ..test_server import make_test_server
|
||||
from ...test_server import make_test_server
|
||||
|
||||
|
||||
def make_test_storage() -> optuna.storages.InMemoryStorage:
|
||||
+1
-1
@@ -4,7 +4,7 @@ import optuna
|
||||
from playwright.sync_api import Page
|
||||
import pytest
|
||||
|
||||
from .test_server import make_test_server
|
||||
from ..test_server import make_test_server
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -1,4 +1,6 @@
|
||||
import http.server
|
||||
import socket
|
||||
import socketserver
|
||||
import threading
|
||||
from wsgiref.simple_server import make_server
|
||||
|
||||
@@ -33,3 +35,29 @@ def make_test_server(
|
||||
request.addfinalizer(stop_server)
|
||||
|
||||
return f"http://{addr}:{port}/dashboard"
|
||||
|
||||
|
||||
def make_standalone_server(request: pytest.FixtureRequest) -> str:
|
||||
addr = "127.0.0.1"
|
||||
port = get_free_port()
|
||||
directory = "./standalone_app/"
|
||||
|
||||
Handler = http.server.SimpleHTTPRequestHandler
|
||||
httpd = socketserver.TCPServer(
|
||||
("", port), lambda *args, **kwargs: Handler(*args, directory=directory, **kwargs)
|
||||
)
|
||||
|
||||
def serve_httpd():
|
||||
httpd.serve_forever()
|
||||
|
||||
thread = threading.Thread(target=serve_httpd)
|
||||
thread.start()
|
||||
|
||||
def stop_server() -> None:
|
||||
httpd.shutdown()
|
||||
httpd.server_close()
|
||||
thread.join()
|
||||
|
||||
request.addfinalizer(stop_server)
|
||||
|
||||
return f"http://{addr}:{port}"
|
||||
Whitespace-only changes.
@@ -0,0 +1,22 @@
|
||||
from playwright.sync_api import Page
|
||||
import pytest
|
||||
|
||||
from ..test_server import make_standalone_server
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def server_url(request: pytest.FixtureRequest) -> str:
|
||||
return make_standalone_server(request)
|
||||
|
||||
|
||||
def test_home(
|
||||
page: Page,
|
||||
server_url: str,
|
||||
) -> None:
|
||||
url = f"{server_url}"
|
||||
page.goto(url)
|
||||
element = page.get_by_role("heading")
|
||||
assert element is not None
|
||||
title = element.text_content()
|
||||
assert title is not None
|
||||
assert title == "Optuna Dashboard (Wasm ver.)"
|
||||
@@ -17,4 +17,4 @@ from ._note import save_note # noqa
|
||||
from ._preference_setting import register_preference_feedback_component # noqa
|
||||
|
||||
|
||||
__version__ = "0.13.0b1"
|
||||
__version__ = "0.13.0"
|
||||
@@ -7,9 +7,9 @@ from typing import Iterable
|
||||
|
||||
import optuna
|
||||
from optuna import logging
|
||||
from optuna._imports import try_import
|
||||
from optuna.distributions import BaseDistribution
|
||||
from optuna.samplers import BaseSampler
|
||||
from optuna.samplers import RandomSampler
|
||||
from optuna.trial import FrozenTrial
|
||||
from optuna.trial import TrialState
|
||||
from optuna_dashboard.preferential._system_attrs import get_n_generate
|
||||
@@ -20,6 +20,10 @@ from optuna_dashboard.preferential._system_attrs import report_preferences
|
||||
from optuna_dashboard.preferential._system_attrs import set_n_generate
|
||||
|
||||
|
||||
with try_import() as _imports:
|
||||
from optuna_dashboard.preferential.samplers.gp import PreferentialGPSampler
|
||||
|
||||
|
||||
_logger = logging.get_logger(__name__)
|
||||
_SYSTEM_ATTR_PREFERENTIAL_STUDY = "preference:is_preferential"
|
||||
|
||||
@@ -37,6 +41,10 @@ class PreferentialStudy:
|
||||
To create and load a study, please refer to the documentation of
|
||||
:func:`~optuna_dashboard.preferential.create_study` and
|
||||
:func:`~optuna_dashboard.preferential.load_study` respectively.
|
||||
|
||||
.. note::
|
||||
Preferential optimization is an experimental feature (introduced in v0.13.0).
|
||||
The interface may change in newer versions without prior notice.
|
||||
"""
|
||||
|
||||
def __init__(self, study: optuna.Study) -> None:
|
||||
@@ -340,11 +348,10 @@ def create_study(
|
||||
|
||||
sampler:
|
||||
A sampler object that implements background algorithm for value suggestion.
|
||||
If :obj:`None` is specified, `RandomSampler`_ is used. Please note that
|
||||
most Optuna samplers does not work efficiently for preferential optimization.
|
||||
|
||||
.. _RandomSampler: https://optuna.readthedocs.io/en/stable/reference/\
|
||||
samplers/generated/optuna.samplers.RandomSampler.html
|
||||
If :obj:`None` is specified,
|
||||
:class:`~optuna_dashboard.preferential.samplers.gp.PreferentialGPSampler` is used.
|
||||
Please note that most Optuna samplers does not work efficiently for preferential
|
||||
optimization.
|
||||
|
||||
study_name:
|
||||
Study's name. If this argument is set to None, a unique name is generated
|
||||
@@ -359,11 +366,19 @@ def create_study(
|
||||
|
||||
Returns:
|
||||
A :class:`~optuna_dashboard.preferential.PreferentialStudy` object.
|
||||
|
||||
.. note::
|
||||
Preferential optimization is an experimental feature (introduced in v0.13.0).
|
||||
The interface may change in newer versions without prior notice.
|
||||
"""
|
||||
try:
|
||||
if sampler is None:
|
||||
_imports.check() # If BoTorch is not installed, raise ImportError.
|
||||
sampler = PreferentialGPSampler()
|
||||
|
||||
study = optuna.create_study(
|
||||
storage=storage,
|
||||
sampler=sampler or RandomSampler(),
|
||||
sampler=sampler,
|
||||
study_name=study_name,
|
||||
)
|
||||
study._storage.set_study_system_attr(
|
||||
@@ -433,18 +448,23 @@ def load_study(
|
||||
:func:`~optuna.study.create_study` for further details.
|
||||
sampler:
|
||||
A sampler object that implements background algorithm for value suggestion.
|
||||
If :obj:`None` is specified, `RandomSampler`_ is used. Please note that
|
||||
most Optuna samplers does not work efficiently for preferential optimization.
|
||||
|
||||
.. _RandomSampler: https://optuna.readthedocs.io/en/stable/reference/samplers/\
|
||||
generated/optuna.samplers.RandomSampler.html
|
||||
If :obj:`None` is specified,
|
||||
:class:`~optuna_dashboard.preferential.samplers.gp.PreferentialGPSampler` is used.
|
||||
Please note that most Optuna samplers does not work efficiently for preferential
|
||||
optimization.
|
||||
|
||||
Returns:
|
||||
A :class:`~optuna_dashboard.preferential.PreferentialStudy` object.
|
||||
|
||||
.. note::
|
||||
Preferential optimization is an experimental feature (introduced in v0.13.0).
|
||||
The interface may change in newer versions without prior notice.
|
||||
"""
|
||||
study = optuna.load_study(
|
||||
study_name=study_name, storage=storage, sampler=sampler or RandomSampler()
|
||||
)
|
||||
if sampler is None:
|
||||
_imports.check() # If BoTorch is not installed, raise ImportError.
|
||||
sampler = PreferentialGPSampler()
|
||||
|
||||
study = optuna.load_study(study_name=study_name, storage=storage, sampler=sampler)
|
||||
system_attrs = study._storage.get_study_system_attrs(study._study_id)
|
||||
if not system_attrs.get(_SYSTEM_ATTR_PREFERENTIAL_STUDY):
|
||||
raise ValueError("The study is not a PreferentialStudy.")
|
||||
|
||||
@@ -4,7 +4,7 @@ import itertools
|
||||
import math
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import cast
|
||||
import warnings
|
||||
|
||||
import botorch.acquisition.analytic
|
||||
import botorch.models.model
|
||||
@@ -17,6 +17,8 @@ import numpy as np
|
||||
import optuna
|
||||
import optuna._transform
|
||||
from optuna.distributions import CategoricalDistribution
|
||||
from optuna.distributions import FloatDistribution
|
||||
from optuna.distributions import IntDistribution
|
||||
import torch
|
||||
from torch import Tensor
|
||||
|
||||
@@ -48,15 +50,15 @@ def _orthants_MVN_Gibbs_sampling(cov_inv: Tensor, cycles: int, initial_sample: T
|
||||
|
||||
|
||||
def _one_side_trunc_norm_sampling(lower: Tensor) -> Tensor:
|
||||
if lower > 4.0:
|
||||
r = torch.clamp_min(torch.rand(torch.Size(()), dtype=torch.float64), min=1e-300)
|
||||
return (lower * lower - 2 * r.log()).sqrt()
|
||||
else:
|
||||
SQRT2 = math.sqrt(2)
|
||||
r = torch.rand(torch.Size(()), dtype=torch.float64) * torch.erfc(lower / SQRT2)
|
||||
while 1 - r == 1:
|
||||
r = torch.rand(torch.Size(()), dtype=torch.float64) * torch.erfc(lower / SQRT2)
|
||||
return torch.erfinv(1 - r) * SQRT2
|
||||
r = torch.rand(torch.Size(()), dtype=torch.float64)
|
||||
ret = -torch.special.ndtri(torch.exp(torch.special.log_ndtr(-lower) + r.log()))
|
||||
|
||||
# If sampled random number is very small, `ret` becomes inf.
|
||||
while torch.isinf(ret):
|
||||
r = torch.rand(torch.Size(()), dtype=torch.float64)
|
||||
ret = -torch.special.ndtri(torch.exp(torch.special.log_ndtr(-lower) + r.log()))
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
_orthants_MVN_Gibbs_sampling_jit = torch.jit.script(_orthants_MVN_Gibbs_sampling)
|
||||
@@ -156,6 +158,18 @@ def _truncnorm_mean_var_logz(alpha: Tensor) -> tuple[Tensor, Tensor, Tensor]:
|
||||
return mean, var, logz
|
||||
|
||||
|
||||
def _observation(var0: Tensor, mean0: Tensor, noise_var: Tensor) -> tuple[Tensor, Tensor, Tensor]:
|
||||
obs_var = var0 + noise_var
|
||||
obs_sigma = torch.sqrt(obs_var)
|
||||
alpha = -mean0 / torch.clamp_min(obs_sigma, min=1e-20)
|
||||
mean_norm, var_norm, logz = _truncnorm_mean_var_logz(alpha)
|
||||
|
||||
denom_factor = 1 / torch.clamp_min(noise_var + var_norm * var0, min=1e-20)
|
||||
da = (1 - var_norm) * denom_factor
|
||||
db = (mean0 * (1 - var_norm) + obs_sigma * mean_norm) * denom_factor
|
||||
return (da, db, logz)
|
||||
|
||||
|
||||
def _orthants_MVN_EP(
|
||||
cov0: Tensor, preferences: Tensor, noise_var: Tensor, cycles: int
|
||||
) -> tuple[Tensor, Tensor, Tensor]:
|
||||
@@ -176,25 +190,17 @@ def _orthants_MVN_EP(
|
||||
|
||||
r0 = (1 - var1 * virtual_obs_a[i]).reciprocal()
|
||||
var0 = var1 * r0
|
||||
mean0 = (mean1 + var1 * virtual_obs_b[i]) * r0
|
||||
mean0 = (mean1 - var1 * virtual_obs_b[i]) * r0
|
||||
|
||||
obs_var = var0 + noise_var
|
||||
obs_sigma = torch.sqrt(obs_var)
|
||||
alpha = -mean0 / torch.clamp_min(obs_sigma, min=1e-20)
|
||||
mean_norm, var_norm, logz = _truncnorm_mean_var_logz(alpha)
|
||||
virtual_obs_a2, virtual_obs_b2, logz = _observation(var0, mean0, noise_var)
|
||||
|
||||
kalman_factor = var0 / torch.clamp_min(obs_var, min=1e-20)
|
||||
mean2 = mean0 + obs_sigma * mean_norm * kalman_factor
|
||||
var2 = kalman_factor * (noise_var + var_norm * var0)
|
||||
|
||||
var1_var2_inv = torch.clamp_min(var1 * var2, min=1e-20).reciprocal()
|
||||
db = (mean1 * var2 - mean2 * var1) * var1_var2_inv
|
||||
da = (var1 - var2) * var1_var2_inv
|
||||
virtual_obs_b[i] = virtual_obs_b[i] + db
|
||||
virtual_obs_a[i] = virtual_obs_a[i] + da
|
||||
da = virtual_obs_a2 - virtual_obs_a[i]
|
||||
db = virtual_obs_b2 - virtual_obs_b[i]
|
||||
virtual_obs_a[i] = virtual_obs_a2
|
||||
virtual_obs_b[i] = virtual_obs_b2
|
||||
|
||||
dr = (1 + var1 * da).reciprocal()
|
||||
mu = mu - Sxy * ((db + mean1 * da) * dr)
|
||||
mu = mu + Sxy * ((db - mean1 * da) * dr)
|
||||
cov = cov - (Sxy[:, None] * (da * dr)) @ Sxy[None, :]
|
||||
log_zs[i] = logz
|
||||
return mu, cov, torch.sum(log_zs)
|
||||
@@ -278,6 +284,26 @@ class _PreferentialGP:
|
||||
|
||||
|
||||
class PreferentialGPSampler(optuna.samplers.BaseSampler):
|
||||
"""Sampler for preferential optimization using Gaussian process.
|
||||
|
||||
The sampling algorithm is based on `Takeno et al., 2023 <https://arxiv.org/abs/2302.01513>`_.
|
||||
This sampler uses BoTorch to optimize acquisition function.
|
||||
|
||||
Args:
|
||||
kernel:
|
||||
Kernel that computes the covariance on the Gaussian process. Defaults to
|
||||
Matern 3/2 Kernel + ARD.
|
||||
noise_prior:
|
||||
Prior of the observation noise. Defaults to gamma prior.
|
||||
independent_sampler:
|
||||
A :class:`~optuna.samplers.BaseSampler` instance that is used for independent
|
||||
sampling. The parameters not contained in the relative search space are sampled
|
||||
by this sampler. If :obj:`None` is specified,
|
||||
:class:`~optuna.samplers.RandomSampler` is used as the default.
|
||||
seed:
|
||||
Seed for random number generator.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -359,11 +385,54 @@ class PreferentialGPSampler(optuna.samplers.BaseSampler):
|
||||
)
|
||||
|
||||
# TODO: Make it possible to apply it on mixed search space
|
||||
if all(isinstance(dist, CategoricalDistribution) for dist in search_space.values()):
|
||||
def get_all_possible_params(dist: optuna.distributions.BaseDistribution) -> list[Any]:
|
||||
if isinstance(dist, CategoricalDistribution):
|
||||
return list(dist.choices)
|
||||
elif isinstance(dist, (IntDistribution, FloatDistribution)):
|
||||
return list(np.arange(dist.low, dist.high, dist.step))
|
||||
else:
|
||||
return []
|
||||
|
||||
all_possible_params = {
|
||||
name: get_all_possible_params(dist) for name, dist in search_space.items()
|
||||
}
|
||||
|
||||
is_all_discrete = all(
|
||||
len(possible_params) > 0 for possible_params in all_possible_params.values()
|
||||
)
|
||||
search_space_size = np.prod(
|
||||
[len(possible_params) for possible_params in all_possible_params.values()]
|
||||
)
|
||||
# TODO(contramundum53): Fix this arbitrarily chosen limit.
|
||||
size_limit = 1e6
|
||||
can_evaluate_all = is_all_discrete and search_space_size <= size_limit
|
||||
|
||||
if (
|
||||
any(isinstance(dist, CategoricalDistribution) for dist in search_space.values())
|
||||
and not can_evaluate_all
|
||||
):
|
||||
if is_all_discrete:
|
||||
warnings.warn(
|
||||
"The objective function has categorical parameters, "
|
||||
"but the total search space is too large to be enumerated. "
|
||||
f"(Search space size: {search_space_size} > limit: {size_limit})"
|
||||
"This may result in significantly bad performance."
|
||||
)
|
||||
else:
|
||||
warnings.warn(
|
||||
"The objective function has categorical parameters, "
|
||||
"but the search space cannot be enumerated because "
|
||||
"it also contains continuous parameters. "
|
||||
"This may result in significantly bad performance. "
|
||||
"You can work around this problem by specifying 'step' "
|
||||
"in each continuous parameter."
|
||||
)
|
||||
|
||||
if is_all_discrete and can_evaluate_all:
|
||||
all_param_combinations = itertools.product(
|
||||
*[
|
||||
[(name, choice) for choice in cast(CategoricalDistribution, dist).choices]
|
||||
for name, dist in search_space.items()
|
||||
[(name, choice) for choice in possible_params]
|
||||
for name, possible_params in all_possible_params.items()
|
||||
]
|
||||
)
|
||||
choices = torch.tensor(
|
||||
@@ -395,6 +464,11 @@ class PreferentialGPSampler(optuna.samplers.BaseSampler):
|
||||
param_name: str,
|
||||
param_distribution: optuna.distributions.BaseDistribution,
|
||||
) -> Any:
|
||||
warnings.warn(
|
||||
"Dynamic search space detected. "
|
||||
f"Falling back to {self.independent_sampler.__class__.__name__}."
|
||||
)
|
||||
|
||||
return self.independent_sampler.sample_independent(
|
||||
study, trial, param_name, param_distribution
|
||||
)
|
||||
@@ -17,7 +17,7 @@ import ListItemText from "@mui/material/ListItemText"
|
||||
import {
|
||||
drawerOpenState,
|
||||
reloadIntervalState,
|
||||
useStudyIsPreferencial,
|
||||
useStudyIsPreferential,
|
||||
} from "../state"
|
||||
import { Link } from "react-router-dom"
|
||||
import AutoGraphIcon from "@mui/icons-material/AutoGraph"
|
||||
@@ -133,7 +133,7 @@ export const AppDrawer: FC<{
|
||||
const [open, setOpen] = useRecoilState<boolean>(drawerOpenState)
|
||||
const reloadInterval = useRecoilValue<number>(reloadIntervalState)
|
||||
const isPreferential =
|
||||
studyId !== undefined ? useStudyIsPreferencial(studyId) : null
|
||||
studyId !== undefined ? useStudyIsPreferential(studyId) : null
|
||||
|
||||
const styleListItem = {
|
||||
display: "block",
|
||||
|
||||
@@ -21,6 +21,18 @@ export const ArtifactCardMedia: FC<{
|
||||
filetype={artifact.filename.split(".").pop()}
|
||||
/>
|
||||
)
|
||||
} else if (artifact.mimetype.startsWith("video")) {
|
||||
return (
|
||||
<video
|
||||
controls
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "auto",
|
||||
}}
|
||||
>
|
||||
<source src={urlPath} type={artifact.mimetype} />
|
||||
</video>
|
||||
)
|
||||
} else if (artifact.mimetype.startsWith("audio")) {
|
||||
return (
|
||||
<audio controls>
|
||||
|
||||
@@ -28,7 +28,7 @@ interface DataGridColumn<T> {
|
||||
field: keyof T
|
||||
label: string
|
||||
sortable?: boolean
|
||||
less?: (a: T, b: T) => number
|
||||
less?: (a: T, b: T, ascending: boolean) => number
|
||||
filterable?: boolean
|
||||
toCellValue?: (rowIndex: number) => string | React.ReactNode
|
||||
padding?: "normal" | "checkbox" | "none"
|
||||
@@ -358,7 +358,10 @@ function stableSort<T>(
|
||||
const stabilizedThis = array.map((el, index) => [el, index] as [T, number])
|
||||
stabilizedThis.sort((a, b) => {
|
||||
if (less) {
|
||||
const result = order == "asc" ? -less(a[0], b[0]) : less(a[0], b[0])
|
||||
const ascending = order == "asc"
|
||||
const result = ascending
|
||||
? -less(a[0], b[0], ascending)
|
||||
: less(a[0], b[0], ascending)
|
||||
if (result !== 0) return result
|
||||
} else {
|
||||
const result = comparator(a[0], b[0])
|
||||
|
||||
@@ -174,19 +174,19 @@ const plotContour = (
|
||||
return
|
||||
}
|
||||
|
||||
const xAxis = getAxisInfo(study, trials, xParam)
|
||||
const yAxis = getAxisInfo(study, trials, yParam)
|
||||
const xAxis = getAxisInfo(trials, xParam)
|
||||
const yAxis = getAxisInfo(trials, yParam)
|
||||
const xIndices = xAxis.indices
|
||||
const yIndices = yAxis.indices
|
||||
|
||||
const layout: Partial<plotly.Layout> = {
|
||||
xaxis: {
|
||||
title: xParam.name,
|
||||
type: xAxis.isCat ? "category" : undefined,
|
||||
type: xAxis.isCat ? "category" : xAxis.isLog ? "log" : "linear",
|
||||
},
|
||||
yaxis: {
|
||||
title: yParam.name,
|
||||
type: yAxis.isCat ? "category" : undefined,
|
||||
type: yAxis.isCat ? "category" : yAxis.isLog ? "log" : "linear",
|
||||
},
|
||||
margin: {
|
||||
l: 50,
|
||||
@@ -207,12 +207,16 @@ const plotContour = (
|
||||
const xValues: plotly.Datum[] = []
|
||||
const yValues: plotly.Datum[] = []
|
||||
const zValues: plotly.Datum[][] = new Array(yIndices.length)
|
||||
const feasibleXY = new Set<number>()
|
||||
for (let j = 0; j < yIndices.length; j++) {
|
||||
zValues[j] = new Array(xIndices.length).fill(null)
|
||||
}
|
||||
|
||||
filteredTrials.forEach((trial, i) => {
|
||||
if (xAxis.values[i] && yAxis.values[i] && trial.values) {
|
||||
if (trial.constraints.every((c) => c <= 0)) {
|
||||
feasibleXY.add(xValues.length)
|
||||
}
|
||||
const xValue = xAxis.values[i] as string | number
|
||||
const yValue = yAxis.values[i] as string | number
|
||||
xValues.push(xValue)
|
||||
@@ -246,12 +250,20 @@ const plotContour = (
|
||||
},
|
||||
{
|
||||
type: "scatter",
|
||||
x: xValues,
|
||||
y: yValues,
|
||||
x: xValues.filter((_, i) => feasibleXY.has(i)),
|
||||
y: yValues.filter((_, i) => feasibleXY.has(i)),
|
||||
marker: { line: { width: 2.0, color: "Grey" }, color: "black" },
|
||||
mode: "markers",
|
||||
showlegend: false,
|
||||
},
|
||||
{
|
||||
type: "scatter",
|
||||
x: xValues.filter((_, i) => !feasibleXY.has(i)),
|
||||
y: yValues.filter((_, i) => !feasibleXY.has(i)),
|
||||
marker: { line: { width: 2.0, color: "Grey" }, color: "#cccccc" },
|
||||
mode: "markers",
|
||||
showlegend: false,
|
||||
},
|
||||
]
|
||||
plotly.react(plotDomId, plotData, layout)
|
||||
return
|
||||
@@ -290,9 +302,19 @@ const getAxisInfoForNumericalParams = (
|
||||
paramName: string,
|
||||
distribution: FloatDistribution | IntDistribution
|
||||
): AxisInfo => {
|
||||
const padding = (distribution.high - distribution.low) * PADDING_RATIO
|
||||
const min = distribution.low - padding
|
||||
const max = distribution.high + padding
|
||||
let min = 0
|
||||
let max = 0
|
||||
if (distribution.log) {
|
||||
const padding =
|
||||
(Math.log10(distribution.high) - Math.log10(distribution.low)) *
|
||||
PADDING_RATIO
|
||||
min = Math.pow(10, Math.log10(distribution.low) - padding)
|
||||
max = Math.pow(10, Math.log10(distribution.high) + padding)
|
||||
} else {
|
||||
const padding = (distribution.high - distribution.low) * PADDING_RATIO
|
||||
min = distribution.low - padding
|
||||
max = distribution.high + padding
|
||||
}
|
||||
|
||||
const values = trials.map(
|
||||
(trial) =>
|
||||
@@ -353,11 +375,7 @@ const getAxisInfoForCategoricalParams = (
|
||||
}
|
||||
}
|
||||
|
||||
const getAxisInfo = (
|
||||
study: StudyDetail,
|
||||
trials: Trial[],
|
||||
param: SearchSpaceItem
|
||||
): AxisInfo => {
|
||||
const getAxisInfo = (trials: Trial[], param: SearchSpaceItem): AxisInfo => {
|
||||
if (param.distribution.type === "CategoricalDistribution") {
|
||||
return getAxisInfoForCategoricalParams(
|
||||
trials,
|
||||
|
||||
@@ -89,16 +89,21 @@ const plotIntermediateValue = (
|
||||
const values = trial.intermediate_values.filter(
|
||||
(iv) => iv.value !== "inf" && iv.value !== "-inf" && iv.value !== "nan"
|
||||
)
|
||||
const isFeasible = trial.constraints.every((c) => c <= 0)
|
||||
return {
|
||||
x: values.map((iv) => iv.step),
|
||||
y: values.map((iv) => iv.value),
|
||||
marker: { maxdisplayed: 10 },
|
||||
mode: "lines+markers",
|
||||
type: "scatter",
|
||||
name:
|
||||
trial.state !== "Running"
|
||||
? `trial #${trial.number}`
|
||||
: `trial #${trial.number} (running)`,
|
||||
name: `trial #${trial.number} ${
|
||||
trial.state === "Running"
|
||||
? "(running)"
|
||||
: !isFeasible
|
||||
? "(infeasible)"
|
||||
: ""
|
||||
}`,
|
||||
...(!isFeasible && { line: { color: "#CCCCCC" } }),
|
||||
}
|
||||
})
|
||||
plotly.react(plotDomId, plotData, layout)
|
||||
|
||||
@@ -181,6 +181,21 @@ const plotCoordinate = (
|
||||
.join("")
|
||||
}
|
||||
|
||||
const calculateLogScale = (values: number[]) => {
|
||||
const logValues = values.map((v) => {
|
||||
return Math.log10(v)
|
||||
})
|
||||
const minValue = Math.min(...logValues)
|
||||
const maxValue = Math.max(...logValues)
|
||||
const range = [Math.floor(minValue), Math.ceil(maxValue)]
|
||||
const tickvals = Array.from(
|
||||
{ length: Math.ceil(maxValue) - Math.floor(minValue) + 1 },
|
||||
(_, i) => i + Math.floor(minValue)
|
||||
)
|
||||
const ticktext = tickvals.map((x) => `${Math.pow(10, x).toPrecision(3)}`)
|
||||
return { logValues, range, tickvals, ticktext }
|
||||
}
|
||||
|
||||
const dimensions = targets.map((target) => {
|
||||
if (target.kind === "objective" || target.kind === "user_attr") {
|
||||
const values: number[] = trials.map(
|
||||
@@ -199,13 +214,7 @@ const plotCoordinate = (
|
||||
const values: number[] = trials.map(
|
||||
(t) => target.getTargetValue(t) as number
|
||||
)
|
||||
if (s.distribution.type !== "CategoricalDistribution") {
|
||||
return {
|
||||
label: breakLabelIfTooLong(s.name),
|
||||
values: values,
|
||||
range: [s.distribution.low, s.distribution.high],
|
||||
}
|
||||
} else {
|
||||
if (s.distribution.type === "CategoricalDistribution") {
|
||||
// categorical
|
||||
const vocabArr: string[] = s.distribution.choices.map((c) => c.value)
|
||||
const tickvals: number[] = vocabArr.map((v, i) => i)
|
||||
@@ -217,6 +226,24 @@ const plotCoordinate = (
|
||||
tickvals: tickvals,
|
||||
ticktext: vocabArr,
|
||||
}
|
||||
} else if (s.distribution.log) {
|
||||
// numerical and log
|
||||
const { logValues, range, tickvals, ticktext } =
|
||||
calculateLogScale(values)
|
||||
return {
|
||||
label: breakLabelIfTooLong(s.name),
|
||||
values: logValues,
|
||||
range,
|
||||
tickvals,
|
||||
ticktext,
|
||||
}
|
||||
} else {
|
||||
// numerical and linear
|
||||
return {
|
||||
label: breakLabelIfTooLong(s.name),
|
||||
values: values,
|
||||
range: [s.distribution.low, s.distribution.high],
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,414 @@
|
||||
import * as plotly from "plotly.js-dist-min"
|
||||
import React, { FC, useEffect, useState } from "react"
|
||||
import {
|
||||
Grid,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
MenuItem,
|
||||
Select,
|
||||
Typography,
|
||||
SelectChangeEvent,
|
||||
useTheme,
|
||||
Box,
|
||||
} from "@mui/material"
|
||||
import { plotlyDarkTemplate } from "./PlotlyDarkMode"
|
||||
import { makeHovertext } from "../graphUtil"
|
||||
import { useMergedUnionSearchSpace } from "../searchSpace"
|
||||
|
||||
const PADDING_RATIO = 0.05
|
||||
const plotDomId = "graph-rank"
|
||||
|
||||
interface AxisInfo {
|
||||
name: string
|
||||
range: [number, number]
|
||||
isLog: boolean
|
||||
isCat: boolean
|
||||
}
|
||||
|
||||
interface RankPlotInfo {
|
||||
xaxis: AxisInfo
|
||||
yaxis: AxisInfo
|
||||
xvalues: (string | number)[]
|
||||
yvalues: (string | number)[]
|
||||
zvalues: number[]
|
||||
colors: number[]
|
||||
is_feasible: boolean[]
|
||||
hovertext: string[]
|
||||
}
|
||||
|
||||
export const GraphRank: FC<{
|
||||
study: StudyDetail | null
|
||||
}> = ({ study = null }) => {
|
||||
const theme = useTheme()
|
||||
const [objectiveId, setobjectiveId] = useState<number>(0)
|
||||
const searchSpace = useMergedUnionSearchSpace(study?.union_search_space)
|
||||
const [xParam, setXParam] = useState<SearchSpaceItem | null>(null)
|
||||
const [yParam, setYParam] = useState<SearchSpaceItem | null>(null)
|
||||
const objectiveNames: string[] = study?.objective_names || []
|
||||
|
||||
if (xParam === null && searchSpace.length > 0) {
|
||||
setXParam(searchSpace[0])
|
||||
}
|
||||
if (yParam === null && searchSpace.length > 1) {
|
||||
setYParam(searchSpace[1])
|
||||
}
|
||||
|
||||
const handleObjectiveChange = (event: SelectChangeEvent<number>) => {
|
||||
setobjectiveId(Number(event.target.value))
|
||||
}
|
||||
const handleXParamChange = (event: SelectChangeEvent<string>) => {
|
||||
const param = searchSpace.find((item) => item.name === event.target.value)
|
||||
setXParam(param || null)
|
||||
}
|
||||
const handleYParamChange = (event: SelectChangeEvent<string>) => {
|
||||
const param = searchSpace.find((item) => item.name === event.target.value)
|
||||
setYParam(param || null)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (study != null) {
|
||||
const rankPlotInfo = getRankPlotInfo(study, objectiveId, xParam, yParam)
|
||||
plotRank(rankPlotInfo, theme.palette.mode)
|
||||
}
|
||||
}, [study, objectiveId, xParam, yParam, theme.palette.mode])
|
||||
|
||||
const space: SearchSpaceItem[] = study ? study.union_search_space : []
|
||||
|
||||
return (
|
||||
<Grid container direction="row">
|
||||
<Grid
|
||||
item
|
||||
xs={3}
|
||||
container
|
||||
direction="column"
|
||||
sx={{ paddingRight: theme.spacing(2) }}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{ margin: "1em 0", fontWeight: theme.typography.fontWeightBold }}
|
||||
>
|
||||
Rank
|
||||
</Typography>
|
||||
{study !== null && study.directions.length !== 1 ? (
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">Objective:</FormLabel>
|
||||
<Select value={objectiveId} onChange={handleObjectiveChange}>
|
||||
{study.directions.map((d, i) => (
|
||||
<MenuItem value={i} key={i}>
|
||||
{objectiveNames.length === study?.directions.length
|
||||
? objectiveNames[i]
|
||||
: `${i}`}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
) : null}
|
||||
{study !== null && space.length > 0 ? (
|
||||
<Grid container direction="column" gap={1}>
|
||||
<FormControl component="fieldset" fullWidth>
|
||||
<FormLabel component="legend">x:</FormLabel>
|
||||
<Select value={xParam?.name || ""} onChange={handleXParamChange}>
|
||||
{space.map((d) => (
|
||||
<MenuItem value={d.name} key={d.name}>
|
||||
{d.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl component="fieldset" fullWidth>
|
||||
<FormLabel component="legend">y:</FormLabel>
|
||||
<Select value={yParam?.name || ""} onChange={handleYParamChange}>
|
||||
{space.map((d) => (
|
||||
<MenuItem value={d.name} key={d.name}>
|
||||
{d.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
) : null}
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<Box id={plotDomId} sx={{ height: "450px" }} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
const getRankPlotInfo = (
|
||||
study: StudyDetail | null,
|
||||
objectiveId: number,
|
||||
xParam: SearchSpaceItem | null,
|
||||
yParam: SearchSpaceItem | null
|
||||
): RankPlotInfo | null => {
|
||||
if (study === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
const trials = study.trials
|
||||
const filteredTrials = trials.filter(filterFunc)
|
||||
if (filteredTrials.length < 2 || xParam === null || yParam === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
const xAxis = getAxisInfo(filteredTrials, xParam)
|
||||
const yAxis = getAxisInfo(filteredTrials, yParam)
|
||||
|
||||
const xValues: (string | number)[] = []
|
||||
const yValues: (string | number)[] = []
|
||||
const zValues: number[] = []
|
||||
const isFeasible: boolean[] = []
|
||||
const hovertext: string[] = []
|
||||
filteredTrials.forEach((trial) => {
|
||||
const xValue =
|
||||
trial.params.find((p) => p.name === xAxis.name)?.param_external_value ||
|
||||
null
|
||||
const yValue =
|
||||
trial.params.find((p) => p.name === yAxis.name)?.param_external_value ||
|
||||
null
|
||||
if (trial.values === undefined || xValue === null || yValue === null) {
|
||||
return
|
||||
}
|
||||
const zValue = Number(trial.values[objectiveId])
|
||||
const feasibility = trial.constraints.every((c) => c <= 0)
|
||||
xValues.push(xValue)
|
||||
yValues.push(yValue)
|
||||
zValues.push(zValue)
|
||||
isFeasible.push(feasibility)
|
||||
hovertext.push(makeHovertext(trial))
|
||||
})
|
||||
|
||||
const colors = getColors(zValues)
|
||||
|
||||
return {
|
||||
xaxis: xAxis,
|
||||
yaxis: yAxis,
|
||||
xvalues: xValues,
|
||||
yvalues: yValues,
|
||||
zvalues: zValues,
|
||||
colors,
|
||||
is_feasible: isFeasible,
|
||||
hovertext,
|
||||
}
|
||||
}
|
||||
|
||||
const filterFunc = (trial: Trial): boolean => {
|
||||
return trial.state === "Complete" && trial.values !== undefined
|
||||
}
|
||||
|
||||
const getAxisInfo = (trials: Trial[], param: SearchSpaceItem): AxisInfo => {
|
||||
if (param.distribution.type === "CategoricalDistribution") {
|
||||
return getAxisInfoForCategorical(trials, param.name, param.distribution)
|
||||
} else {
|
||||
return getAxisInfoForNumerical(trials, param.name, param.distribution)
|
||||
}
|
||||
}
|
||||
|
||||
const getAxisInfoForCategorical = (
|
||||
trials: Trial[],
|
||||
param: string,
|
||||
distribution: CategoricalDistribution
|
||||
): AxisInfo => {
|
||||
const values = trials.map(
|
||||
(trial) =>
|
||||
trial.params.find((p) => p.name === param)?.param_internal_value || null
|
||||
)
|
||||
const isDynamic = values.some((v) => v === null)
|
||||
const span = distribution.choices.length - (isDynamic ? 2 : 1)
|
||||
const padding = span * PADDING_RATIO
|
||||
const min = -padding
|
||||
const max = span + padding
|
||||
|
||||
return {
|
||||
name: param,
|
||||
range: [min, max],
|
||||
isLog: false,
|
||||
isCat: true,
|
||||
}
|
||||
}
|
||||
|
||||
const getAxisInfoForNumerical = (
|
||||
trials: Trial[],
|
||||
param: string,
|
||||
distribution: FloatDistribution | IntDistribution
|
||||
): AxisInfo => {
|
||||
const values = trials.map(
|
||||
(trial) =>
|
||||
trial.params.find((p) => p.name === param)?.param_internal_value || null
|
||||
)
|
||||
const nonNullValues: number[] = []
|
||||
values.forEach((value) => {
|
||||
if (value !== null) {
|
||||
nonNullValues.push(value)
|
||||
}
|
||||
})
|
||||
let min = Math.min(...nonNullValues)
|
||||
let max = Math.max(...nonNullValues)
|
||||
if (distribution.log) {
|
||||
const padding = (Math.log10(max) - Math.log10(min)) * PADDING_RATIO
|
||||
min = Math.pow(10, Math.log10(min) - padding)
|
||||
max = Math.pow(10, Math.log10(max) + padding)
|
||||
} else {
|
||||
const padding = (max - min) * PADDING_RATIO
|
||||
min = min - padding
|
||||
max = max + padding
|
||||
}
|
||||
|
||||
return {
|
||||
name: param,
|
||||
range: [min, max],
|
||||
isLog: distribution.log,
|
||||
isCat: false,
|
||||
}
|
||||
}
|
||||
|
||||
const getColors = (values: number[]): number[] => {
|
||||
const rawRanks = getOrderWithSameOrderAveraging(values)
|
||||
let colorIdxs: number[] = []
|
||||
if (values.length > 2) {
|
||||
colorIdxs = rawRanks.map((rank) => rank / (values.length - 1))
|
||||
} else {
|
||||
colorIdxs = [0.5]
|
||||
}
|
||||
return colorIdxs
|
||||
}
|
||||
|
||||
const getOrderWithSameOrderAveraging = (values: number[]): number[] => {
|
||||
const sortedValues = values.slice().sort()
|
||||
const ranks: number[] = []
|
||||
values.forEach((value) => {
|
||||
const firstIndex = sortedValues.indexOf(value)
|
||||
const lastIndex = sortedValues.lastIndexOf(value)
|
||||
const sumOfTheValue =
|
||||
((firstIndex + lastIndex) * (lastIndex - firstIndex + 1)) / 2
|
||||
const rank = sumOfTheValue / (lastIndex - firstIndex + 1)
|
||||
ranks.push(rank)
|
||||
})
|
||||
return ranks
|
||||
}
|
||||
|
||||
const plotRank = (rankPlotInfo: RankPlotInfo | null, mode: string) => {
|
||||
if (document.getElementById(plotDomId) === null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (rankPlotInfo === null) {
|
||||
plotly.react(plotDomId, [], {
|
||||
template: mode === "dark" ? plotlyDarkTemplate : {},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const xAxis = rankPlotInfo.xaxis
|
||||
const yAxis = rankPlotInfo.yaxis
|
||||
const layout: Partial<plotly.Layout> = {
|
||||
xaxis: {
|
||||
title: xAxis.name,
|
||||
type: xAxis.isCat ? "category" : xAxis.isLog ? "log" : "linear",
|
||||
},
|
||||
yaxis: {
|
||||
title: yAxis.name,
|
||||
type: yAxis.isCat ? "category" : yAxis.isLog ? "log" : "linear",
|
||||
},
|
||||
margin: {
|
||||
l: 50,
|
||||
t: 0,
|
||||
r: 50,
|
||||
b: 50,
|
||||
},
|
||||
uirevision: "true",
|
||||
template: mode === "dark" ? plotlyDarkTemplate : {},
|
||||
}
|
||||
|
||||
let xValues = rankPlotInfo.xvalues
|
||||
let yValues = rankPlotInfo.yvalues
|
||||
if (xAxis.isCat && !yAxis.isCat) {
|
||||
const xIndices: number[] = Array.from(Array(xValues.length).keys()).sort(
|
||||
(a, b) =>
|
||||
xValues[a]
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.localeCompare(xValues[b].toString().toLowerCase())
|
||||
)
|
||||
xValues = xIndices.map((i) => xValues[i])
|
||||
yValues = xIndices.map((i) => yValues[i])
|
||||
}
|
||||
if (!xAxis.isCat && yAxis.isCat) {
|
||||
const yIndices: number[] = Array.from(Array(yValues.length).keys()).sort(
|
||||
(a, b) =>
|
||||
yValues[a]
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.localeCompare(yValues[b].toString().toLowerCase())
|
||||
)
|
||||
xValues = yIndices.map((i) => xValues[i])
|
||||
yValues = yIndices.map((i) => yValues[i])
|
||||
}
|
||||
if (xAxis.isCat && yAxis.isCat) {
|
||||
const indices: number[] = Array.from(Array(xValues.length).keys()).sort(
|
||||
(a, b) => {
|
||||
const xComp = xValues[a]
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.localeCompare(xValues[b].toString().toLowerCase())
|
||||
if (xComp !== 0) {
|
||||
return xComp
|
||||
}
|
||||
return yValues[a]
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.localeCompare(yValues[b].toString().toLowerCase())
|
||||
}
|
||||
)
|
||||
xValues = indices.map((i) => xValues[i])
|
||||
yValues = indices.map((i) => yValues[i])
|
||||
}
|
||||
|
||||
const plotData: Partial<plotly.PlotData>[] = [
|
||||
{
|
||||
type: "scatter",
|
||||
x: xValues.filter((_, i) => rankPlotInfo.is_feasible[i]),
|
||||
y: yValues.filter((_, i) => rankPlotInfo.is_feasible[i]),
|
||||
marker: {
|
||||
color: rankPlotInfo.colors.filter(
|
||||
(_, i) => rankPlotInfo.is_feasible[i]
|
||||
),
|
||||
colorscale: "Portland",
|
||||
colorbar: {
|
||||
title: "Rank",
|
||||
},
|
||||
size: 10,
|
||||
line: {
|
||||
color: "Grey",
|
||||
width: 0.5,
|
||||
},
|
||||
},
|
||||
mode: "markers",
|
||||
showlegend: false,
|
||||
hovertemplate: "%{hovertext}<extra></extra>",
|
||||
hovertext: rankPlotInfo.hovertext.filter(
|
||||
(_, i) => rankPlotInfo.is_feasible[i]
|
||||
),
|
||||
},
|
||||
{
|
||||
type: "scatter",
|
||||
x: xValues.filter((_, i) => !rankPlotInfo.is_feasible[i]),
|
||||
y: yValues.filter((_, i) => !rankPlotInfo.is_feasible[i]),
|
||||
marker: {
|
||||
color: "#cccccc",
|
||||
size: 10,
|
||||
line: {
|
||||
color: "Grey",
|
||||
width: 0.5,
|
||||
},
|
||||
},
|
||||
mode: "markers",
|
||||
showlegend: false,
|
||||
hovertemplate: "%{hovertext}<extra></extra>",
|
||||
hovertext: rankPlotInfo.hovertext.filter(
|
||||
(_, i) => !rankPlotInfo.is_feasible[i]
|
||||
),
|
||||
},
|
||||
]
|
||||
plotly.react(plotDomId, plotData, layout)
|
||||
}
|
||||
@@ -193,65 +193,71 @@ const plotSlice = (
|
||||
return
|
||||
}
|
||||
|
||||
const objectiveValues: number[] = trials.map(
|
||||
const feasibleTrials: Trial[] = []
|
||||
const infeasibleTrials: Trial[] = []
|
||||
trials.forEach((t) => {
|
||||
if (t.constraints.every((c) => c <= 0)) {
|
||||
feasibleTrials.push(t)
|
||||
} else {
|
||||
infeasibleTrials.push(t)
|
||||
}
|
||||
})
|
||||
|
||||
const feasibleObjectiveValues: number[] = feasibleTrials.map(
|
||||
(t) => objectiveTarget.getTargetValue(t) as number
|
||||
)
|
||||
const values = trials.map(
|
||||
(t) => selectedParamTarget.getTargetValue(t) as number
|
||||
const infeasibleObjectiveValues: number[] = infeasibleTrials.map(
|
||||
(t) => objectiveTarget.getTargetValue(t) as number
|
||||
)
|
||||
|
||||
const trialNumbers: number[] = trials.map((t) => t.number)
|
||||
if (selectedParamSpace.distribution.type !== "CategoricalDistribution") {
|
||||
const trace: plotly.Data[] = [
|
||||
{
|
||||
type: "scatter",
|
||||
x: values,
|
||||
y: objectiveValues,
|
||||
mode: "markers",
|
||||
marker: {
|
||||
color: trialNumbers,
|
||||
colorscale: "Blues",
|
||||
reversescale: true,
|
||||
colorbar: {
|
||||
title: "Trial",
|
||||
},
|
||||
line: {
|
||||
color: "Grey",
|
||||
width: 0.5,
|
||||
},
|
||||
const feasibleValues = feasibleTrials.map(
|
||||
(t) => selectedParamTarget.getTargetValue(t) as number
|
||||
)
|
||||
const infeasibleValues = infeasibleTrials.map(
|
||||
(t) => selectedParamTarget.getTargetValue(t) as number
|
||||
)
|
||||
const trace: plotly.Data[] = [
|
||||
{
|
||||
type: "scatter",
|
||||
x: feasibleValues,
|
||||
y: feasibleObjectiveValues,
|
||||
mode: "markers",
|
||||
name: "Feasible Trial",
|
||||
marker: {
|
||||
color: feasibleTrials.map((t) => t.number),
|
||||
colorscale: "Blues",
|
||||
reversescale: true,
|
||||
colorbar: {
|
||||
title: "Trial",
|
||||
},
|
||||
line: {
|
||||
color: "Grey",
|
||||
width: 0.5,
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "scatter",
|
||||
x: infeasibleValues,
|
||||
y: infeasibleObjectiveValues,
|
||||
mode: "markers",
|
||||
name: "Infeasible Trial",
|
||||
marker: {
|
||||
color: "#cccccc",
|
||||
reversescale: true,
|
||||
},
|
||||
},
|
||||
]
|
||||
if (selectedParamSpace.distribution.type !== "CategoricalDistribution") {
|
||||
layout["xaxis"] = {
|
||||
title: selectedParamTarget.toLabel(),
|
||||
type: isLogScale(selectedParamSpace) ? "log" : "linear",
|
||||
gridwidth: 1,
|
||||
automargin: true, // Otherwise the label is outside of the plot
|
||||
}
|
||||
plotly.react(plotDomId, trace, layout)
|
||||
} else {
|
||||
const vocabArr = selectedParamSpace.distribution.choices.map((c) => c.value)
|
||||
const tickvals: number[] = vocabArr.map((v, i) => i)
|
||||
const trace: plotly.Data[] = [
|
||||
{
|
||||
type: "scatter",
|
||||
x: values,
|
||||
y: objectiveValues,
|
||||
mode: "markers",
|
||||
marker: {
|
||||
color: trialNumbers,
|
||||
colorscale: "Blues",
|
||||
reversescale: true,
|
||||
colorbar: {
|
||||
title: "Trial",
|
||||
},
|
||||
line: {
|
||||
color: "Grey",
|
||||
width: 0.5,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
layout["xaxis"] = {
|
||||
title: selectedParamTarget.toLabel(),
|
||||
type: "linear",
|
||||
@@ -260,6 +266,6 @@ const plotSlice = (
|
||||
ticktext: vocabArr,
|
||||
automargin: true, // Otherwise the label is outside of the plot
|
||||
}
|
||||
plotly.react(plotDomId, trace, layout)
|
||||
}
|
||||
plotly.react(plotDomId, trace, layout)
|
||||
}
|
||||
@@ -147,6 +147,16 @@ const CandidateTrial: FC<{
|
||||
overflow: "auto",
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: theme.spacing(2),
|
||||
right: theme.spacing(2),
|
||||
}}
|
||||
onClick={() => setDetailShown(false)}
|
||||
>
|
||||
<ClearIcon />
|
||||
</IconButton>
|
||||
<TrialListDetail
|
||||
trial={trial}
|
||||
isBestTrial={() => false}
|
||||
|
||||
@@ -18,7 +18,7 @@ import { actionCreator } from "../action"
|
||||
import {
|
||||
reloadIntervalState,
|
||||
useStudyDetailValue,
|
||||
useStudyIsPreferencial,
|
||||
useStudyIsPreferential,
|
||||
useStudyName,
|
||||
} from "../state"
|
||||
import { TrialTable } from "./TrialTable"
|
||||
@@ -27,6 +27,7 @@ import { GraphParallelCoordinate } from "./GraphParallelCoordinate"
|
||||
import { Contour } from "./GraphContour"
|
||||
import { GraphSlice } from "./GraphSlice"
|
||||
import { GraphEdf } from "./GraphEdf"
|
||||
import { GraphRank } from "./GraphRank"
|
||||
import { TrialList } from "./TrialList"
|
||||
import { StudyHistory } from "./StudyHistory"
|
||||
import { PreferentialTrials } from "./PreferentialTrials"
|
||||
@@ -55,7 +56,7 @@ export const StudyDetail: FC<{
|
||||
const studyDetail = useStudyDetailValue(studyId)
|
||||
const reloadInterval = useRecoilValue<number>(reloadIntervalState)
|
||||
const studyName = useStudyName(studyId)
|
||||
const isPreferential = useStudyIsPreferencial(studyId)
|
||||
const isPreferential = useStudyIsPreferential(studyId)
|
||||
|
||||
const title =
|
||||
studyName !== null ? `${studyName} (id=${studyId})` : `Study #${studyId}`
|
||||
@@ -122,6 +123,11 @@ export const StudyDetail: FC<{
|
||||
<Contour study={studyDetail} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card sx={{ margin: theme.spacing(2) }}>
|
||||
<CardContent>
|
||||
<GraphRank study={studyDetail} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Typography variant="h5" sx={{ margin: theme.spacing(2) }}>
|
||||
Empirical Distribution of the Objective Value
|
||||
</Typography>
|
||||
|
||||
@@ -64,8 +64,7 @@ export const StudyList: FC<{
|
||||
return useMemo(() => new URLSearchParams(search), [search])
|
||||
}
|
||||
const query = useQuery()
|
||||
const initialSortBy =
|
||||
query.get("studies_order_by") === "desc" ? "desc" : "asc"
|
||||
const initialSortBy = query.get("studies_order_by") === "asc" ? "asc" : "desc"
|
||||
const [sortBy, setSortBy] = useState<"asc" | "desc">(initialSortBy)
|
||||
|
||||
let filteredStudies = studies.filter((s) => !studyFilter(s))
|
||||
|
||||
@@ -5,7 +5,9 @@ import { GizmoHelper, GizmoViewport, OrbitControls } from "@react-three/drei"
|
||||
import { STLLoader } from "three/examples/jsm/loaders/STLLoader"
|
||||
import { Rhino3dmLoader } from "three/examples/jsm/loaders/3DMLoader"
|
||||
import { PerspectiveCamera } from "three"
|
||||
import { Modal, Box } from "@mui/material"
|
||||
import { Modal, Box, useTheme } from "@mui/material"
|
||||
import ClearIcon from "@mui/icons-material/Clear"
|
||||
import IconButton from "@mui/material/IconButton"
|
||||
|
||||
export const isThreejsArtifact = (artifact: Artifact): boolean => {
|
||||
return (
|
||||
@@ -32,7 +34,7 @@ const CustomGizmoHelper: React.FC = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const calculateBoundingBox = (geometries: THREE.BufferGeometry[]) => {
|
||||
const computeBoundingBox = (geometries: THREE.BufferGeometry[]) => {
|
||||
const boundingBox = new THREE.Box3()
|
||||
geometries.forEach((geometry) => {
|
||||
const mesh = new THREE.Mesh(geometry)
|
||||
@@ -45,8 +47,11 @@ export const ThreejsArtifactViewer: React.FC<ThreejsArtifactViewerProps> = (
|
||||
props
|
||||
) => {
|
||||
const [geometry, setGeometry] = useState<THREE.BufferGeometry[]>([])
|
||||
const [modelSize, setModelSize] = useState<THREE.Vector3>(
|
||||
new THREE.Vector3(10, 10, 10)
|
||||
const [boundingBox, setBoundingBox] = useState<THREE.Box3>(
|
||||
new THREE.Box3(
|
||||
new THREE.Vector3(-10, -10, -10),
|
||||
new THREE.Vector3(10, 10, 10)
|
||||
)
|
||||
)
|
||||
const [cameraSettings, setCameraSettings] = useState<PerspectiveCamera>(
|
||||
new THREE.PerspectiveCamera()
|
||||
@@ -54,11 +59,11 @@ export const ThreejsArtifactViewer: React.FC<ThreejsArtifactViewerProps> = (
|
||||
|
||||
const handleLoadedGeometries = (geometries: THREE.BufferGeometry[]) => {
|
||||
setGeometry(geometries)
|
||||
const boundingBox = calculateBoundingBox(geometries)
|
||||
const boundingBox = computeBoundingBox(geometries)
|
||||
if (boundingBox !== null) {
|
||||
const size = boundingBox.getSize(new THREE.Vector3())
|
||||
setModelSize(size)
|
||||
setBoundingBox(boundingBox)
|
||||
}
|
||||
return boundingBox
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@@ -81,20 +86,30 @@ export const ThreejsArtifactViewer: React.FC<ThreejsArtifactViewerProps> = (
|
||||
}
|
||||
})
|
||||
}
|
||||
const maxModelSize = Math.max(modelSize.x, modelSize.y, modelSize.z)
|
||||
const cameraSet = new THREE.PerspectiveCamera(
|
||||
modelSize
|
||||
? Math.min(
|
||||
45,
|
||||
Math.atan(modelSize.y / modelSize.z) * (180 / Math.PI) * 2
|
||||
)
|
||||
: 45,
|
||||
window.innerWidth / window.innerHeight
|
||||
)
|
||||
cameraSet.position.set(maxModelSize * 2, maxModelSize * 2, maxModelSize * 2)
|
||||
setCameraSettings(cameraSet)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const cameraSet = new THREE.PerspectiveCamera(
|
||||
50,
|
||||
window.innerWidth / window.innerHeight,
|
||||
0.1,
|
||||
boundingBox.getSize(new THREE.Vector3()).length() * 100
|
||||
)
|
||||
const maxPosition = Math.max(
|
||||
boundingBox.max.x,
|
||||
boundingBox.max.y,
|
||||
boundingBox.max.z
|
||||
)
|
||||
cameraSet.position.set(
|
||||
maxPosition * 1.5,
|
||||
maxPosition * 1.5,
|
||||
maxPosition * 1.5
|
||||
)
|
||||
const center = boundingBox.getCenter(new THREE.Vector3())
|
||||
cameraSet.lookAt(center.x, center.y, center.z)
|
||||
setCameraSettings(cameraSet)
|
||||
}, [boundingBox])
|
||||
|
||||
return (
|
||||
<Canvas
|
||||
camera={cameraSettings}
|
||||
@@ -102,7 +117,7 @@ export const ThreejsArtifactViewer: React.FC<ThreejsArtifactViewerProps> = (
|
||||
>
|
||||
<ambientLight />
|
||||
<OrbitControls />
|
||||
<gridHelper args={[Math.max(modelSize?.x, modelSize?.y) * 5]} />
|
||||
<gridHelper args={[Math.max(boundingBox.max.x, boundingBox.max.y) * 5]} />
|
||||
{props.hasGizmo && <CustomGizmoHelper />}
|
||||
<axesHelper />
|
||||
{geometry.length > 0 &&
|
||||
@@ -121,6 +136,7 @@ export const useThreejsArtifactModal = (): [
|
||||
] => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [target, setTarget] = useState<[string, Artifact | null]>(["", null])
|
||||
const theme = useTheme()
|
||||
|
||||
const openModal = (artifactUrlPath: string, artifact: Artifact) => {
|
||||
setTarget([artifactUrlPath, artifact])
|
||||
@@ -146,6 +162,19 @@ export const useThreejsArtifactModal = (): [
|
||||
borderRadius: "15px",
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: theme.spacing(2),
|
||||
right: theme.spacing(2),
|
||||
}}
|
||||
onClick={() => {
|
||||
setOpen(false)
|
||||
setTarget(["", null])
|
||||
}}
|
||||
>
|
||||
<ClearIcon />
|
||||
</IconButton>
|
||||
<ThreejsArtifactViewer
|
||||
src={target[0]}
|
||||
width={`${innerWidth * 0.8}px`}
|
||||
|
||||
@@ -28,7 +28,7 @@ export const TrialTable: FC<{
|
||||
field: "values",
|
||||
label: "Value",
|
||||
sortable: true,
|
||||
less: (firstEl, secondEl): number => {
|
||||
less: (firstEl, secondEl, ascending): number => {
|
||||
const firstVal = firstEl.values?.[0]
|
||||
const secondVal = secondEl.values?.[0]
|
||||
|
||||
@@ -36,9 +36,9 @@ export const TrialTable: FC<{
|
||||
return 0
|
||||
}
|
||||
if (firstVal === undefined) {
|
||||
return -1
|
||||
return ascending ? -1 : 1
|
||||
} else if (secondVal === undefined) {
|
||||
return 1
|
||||
return ascending ? 1 : -1
|
||||
}
|
||||
if (firstVal === "-inf" || secondVal === "inf") {
|
||||
return 1
|
||||
@@ -63,7 +63,7 @@ export const TrialTable: FC<{
|
||||
? objectiveNames[objectiveId]
|
||||
: `Objective ${objectiveId}`,
|
||||
sortable: true,
|
||||
less: (firstEl, secondEl): number => {
|
||||
less: (firstEl, secondEl, ascending): number => {
|
||||
const firstVal = firstEl.values?.[objectiveId]
|
||||
const secondVal = secondEl.values?.[objectiveId]
|
||||
|
||||
@@ -71,9 +71,9 @@ export const TrialTable: FC<{
|
||||
return 0
|
||||
}
|
||||
if (firstVal === undefined) {
|
||||
return -1
|
||||
return ascending ? -1 : 1
|
||||
} else if (secondVal === undefined) {
|
||||
return 1
|
||||
return ascending ? 1 : -1
|
||||
}
|
||||
if (firstVal === "-inf" || secondVal === "inf") {
|
||||
return 1
|
||||
@@ -106,7 +106,8 @@ export const TrialTable: FC<{
|
||||
?.param_external_value || null,
|
||||
sortable: sortable,
|
||||
filterable: filterable,
|
||||
less: (firstEl, secondEl): number => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
less: (firstEl, secondEl, _): number => {
|
||||
const firstVal = firstEl.params.find(
|
||||
(p) => p.name === s.name
|
||||
)?.param_internal_value
|
||||
@@ -146,7 +147,8 @@ export const TrialTable: FC<{
|
||||
?.value || null,
|
||||
sortable: attr_spec.sortable,
|
||||
filterable: !attr_spec.sortable,
|
||||
less: (firstEl, secondEl): number => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
less: (firstEl, secondEl, _): number => {
|
||||
const firstVal = firstEl.user_attrs.find(
|
||||
(attr) => attr.key === attr_spec.key
|
||||
)?.value
|
||||
|
||||
@@ -95,7 +95,7 @@ export const useStudyDirections = (
|
||||
return studyDetail?.directions || studySummary?.directions || null
|
||||
}
|
||||
|
||||
export const useStudyIsPreferencial = (studyId: number): boolean | null => {
|
||||
export const useStudyIsPreferential = (studyId: number): boolean | null => {
|
||||
const studyDetail = useStudyDetailValue(studyId)
|
||||
const studySummary = useStudySummaryValue(studyId)
|
||||
return studyDetail?.is_preferential || studySummary?.is_preferential || null
|
||||
|
||||
Generated
+130
-126
@@ -75,11 +75,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
|
||||
"integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
|
||||
"version": "7.22.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
|
||||
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
|
||||
"dependencies": {
|
||||
"@babel/highlight": "^7.18.6"
|
||||
"@babel/highlight": "^7.22.13",
|
||||
"chalk": "^2.4.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -125,13 +126,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz",
|
||||
"integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
|
||||
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.20.5",
|
||||
"@babel/types": "^7.23.0",
|
||||
"@jridgewell/gen-mapping": "^0.3.2",
|
||||
"@jridgewell/trace-mapping": "^0.3.17",
|
||||
"jsesc": "^2.5.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -250,9 +252,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-environment-visitor": {
|
||||
"version": "7.18.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz",
|
||||
"integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
|
||||
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -271,25 +273,25 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-function-name": {
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz",
|
||||
"integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
|
||||
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.18.10",
|
||||
"@babel/types": "^7.19.0"
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/types": "^7.23.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-hoist-variables": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
|
||||
"integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
|
||||
"integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.18.6"
|
||||
"@babel/types": "^7.22.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -417,29 +419,29 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-split-export-declaration": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
|
||||
"integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
|
||||
"version": "7.22.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
|
||||
"integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.18.6"
|
||||
"@babel/types": "^7.22.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.19.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
|
||||
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
|
||||
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
|
||||
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
|
||||
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@@ -483,12 +485,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
|
||||
"integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
|
||||
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.18.6",
|
||||
"chalk": "^2.0.0",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -496,9 +498,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
|
||||
"integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
|
||||
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
@@ -1657,33 +1659,33 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.18.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
|
||||
"integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==",
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
|
||||
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/parser": "^7.18.10",
|
||||
"@babel/types": "^7.18.10"
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/parser": "^7.22.15",
|
||||
"@babel/types": "^7.22.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz",
|
||||
"integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==",
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
|
||||
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/generator": "^7.20.5",
|
||||
"@babel/helper-environment-visitor": "^7.18.9",
|
||||
"@babel/helper-function-name": "^7.19.0",
|
||||
"@babel/helper-hoist-variables": "^7.18.6",
|
||||
"@babel/helper-split-export-declaration": "^7.18.6",
|
||||
"@babel/parser": "^7.20.5",
|
||||
"@babel/types": "^7.20.5",
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/generator": "^7.23.0",
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-function-name": "^7.23.0",
|
||||
"@babel/helper-hoist-variables": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@babel/types": "^7.23.0",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
},
|
||||
@@ -1692,12 +1694,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
|
||||
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
|
||||
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.19.4",
|
||||
"@babel/helper-validator-identifier": "^7.19.1",
|
||||
"@babel/helper-string-parser": "^7.22.5",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -12511,9 +12513,9 @@
|
||||
"integrity": "sha512-2b7w4CQI06px8HVpKpgZtfuoDjuCLA26VlgdnG71UDBrJvtCYvXb39H4ElNv+CA1bbD4S98KanpWPRqTqlxBZw=="
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.29",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.29.tgz",
|
||||
"integrity": "sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==",
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -15313,11 +15315,12 @@
|
||||
}
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
|
||||
"integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
|
||||
"version": "7.22.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
|
||||
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.18.6"
|
||||
"@babel/highlight": "^7.22.13",
|
||||
"chalk": "^2.4.2"
|
||||
}
|
||||
},
|
||||
"@babel/compat-data": {
|
||||
@@ -15350,13 +15353,14 @@
|
||||
}
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz",
|
||||
"integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
|
||||
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.20.5",
|
||||
"@babel/types": "^7.23.0",
|
||||
"@jridgewell/gen-mapping": "^0.3.2",
|
||||
"@jridgewell/trace-mapping": "^0.3.17",
|
||||
"jsesc": "^2.5.1"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -15444,9 +15448,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-environment-visitor": {
|
||||
"version": "7.18.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz",
|
||||
"integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
|
||||
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/helper-explode-assignable-expression": {
|
||||
@@ -15459,22 +15463,22 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-function-name": {
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz",
|
||||
"integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
|
||||
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/template": "^7.18.10",
|
||||
"@babel/types": "^7.19.0"
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/types": "^7.23.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-hoist-variables": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
|
||||
"integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
|
||||
"integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.18.6"
|
||||
"@babel/types": "^7.22.5"
|
||||
}
|
||||
},
|
||||
"@babel/helper-member-expression-to-functions": {
|
||||
@@ -15569,23 +15573,23 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-split-export-declaration": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
|
||||
"integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
|
||||
"version": "7.22.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
|
||||
"integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.18.6"
|
||||
"@babel/types": "^7.22.5"
|
||||
}
|
||||
},
|
||||
"@babel/helper-string-parser": {
|
||||
"version": "7.19.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
|
||||
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw=="
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
|
||||
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw=="
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
|
||||
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w=="
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
|
||||
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A=="
|
||||
},
|
||||
"@babel/helper-validator-option": {
|
||||
"version": "7.18.6",
|
||||
@@ -15617,19 +15621,19 @@
|
||||
}
|
||||
},
|
||||
"@babel/highlight": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
|
||||
"integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
|
||||
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.18.6",
|
||||
"chalk": "^2.0.0",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
|
||||
"integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
|
||||
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
|
||||
@@ -16401,41 +16405,41 @@
|
||||
}
|
||||
},
|
||||
"@babel/template": {
|
||||
"version": "7.18.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
|
||||
"integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==",
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
|
||||
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/parser": "^7.18.10",
|
||||
"@babel/types": "^7.18.10"
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/parser": "^7.22.15",
|
||||
"@babel/types": "^7.22.15"
|
||||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz",
|
||||
"integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==",
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
|
||||
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/generator": "^7.20.5",
|
||||
"@babel/helper-environment-visitor": "^7.18.9",
|
||||
"@babel/helper-function-name": "^7.19.0",
|
||||
"@babel/helper-hoist-variables": "^7.18.6",
|
||||
"@babel/helper-split-export-declaration": "^7.18.6",
|
||||
"@babel/parser": "^7.20.5",
|
||||
"@babel/types": "^7.20.5",
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/generator": "^7.23.0",
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-function-name": "^7.23.0",
|
||||
"@babel/helper-hoist-variables": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@babel/types": "^7.23.0",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
|
||||
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
|
||||
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
|
||||
"requires": {
|
||||
"@babel/helper-string-parser": "^7.19.4",
|
||||
"@babel/helper-validator-identifier": "^7.19.1",
|
||||
"@babel/helper-string-parser": "^7.22.5",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
@@ -24278,9 +24282,9 @@
|
||||
"integrity": "sha512-2b7w4CQI06px8HVpKpgZtfuoDjuCLA26VlgdnG71UDBrJvtCYvXb39H4ElNv+CA1bbD4S98KanpWPRqTqlxBZw=="
|
||||
},
|
||||
"postcss": {
|
||||
"version": "8.4.29",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.29.tgz",
|
||||
"integrity": "sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==",
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"nanoid": "^3.3.6",
|
||||
|
||||
+5
-1
@@ -36,6 +36,7 @@ dynamic = ["version"]
|
||||
[project.optional-dependencies]
|
||||
docs = [
|
||||
"boto3",
|
||||
"botorch",
|
||||
"streamlit",
|
||||
"sphinx",
|
||||
"sphinx_rtd_theme",
|
||||
@@ -51,9 +52,12 @@ test = [
|
||||
optional = [
|
||||
"streamlit",
|
||||
"boto3",
|
||||
"botorch",
|
||||
"botorch>=0.8.1; python_version>='3.8'",
|
||||
]
|
||||
|
||||
preferential = [
|
||||
"botorch>=0.8.1",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
optuna-dashboard = "optuna_dashboard._cli:main"
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import sys
|
||||
from unittest.mock import patch
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
from optuna_dashboard.preferential.samplers.gp import _one_side_trunc_norm_sampling
|
||||
import torch
|
||||
else:
|
||||
pytest.skip("BoTorch dropped Python3.7 support", allow_module_level=True)
|
||||
|
||||
|
||||
def test_one_side_trunc_norm_sampling() -> None:
|
||||
for lower in np.linspace(-10, 10, 100):
|
||||
assert _one_side_trunc_norm_sampling(torch.tensor([lower], dtype=torch.float64)) >= lower
|
||||
|
||||
with patch.object(torch, "rand", return_value=torch.tensor([0.4], dtype=torch.float64)):
|
||||
sampled_value = _one_side_trunc_norm_sampling(torch.tensor([0.1], dtype=torch.float64))
|
||||
assert np.allclose(sampled_value.numpy(), 0.899967154837563)
|
||||
|
||||
with patch.object(torch, "rand", return_value=torch.tensor([0.8], dtype=torch.float64)):
|
||||
sampled_value = _one_side_trunc_norm_sampling(torch.tensor([-2.3], dtype=torch.float64))
|
||||
assert np.allclose(sampled_value.numpy(), -0.8113606739551955)
|
||||
|
||||
with patch.object(torch, "rand", return_value=torch.tensor([0.1], dtype=torch.float64)):
|
||||
sampled_value = _one_side_trunc_norm_sampling(torch.tensor([5], dtype=torch.float64))
|
||||
assert np.allclose(sampled_value.numpy(), 5.426934003050024)
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import copy
|
||||
import multiprocessing
|
||||
import pickle
|
||||
import sys
|
||||
from typing import Callable
|
||||
from unittest.mock import patch
|
||||
import uuid
|
||||
@@ -22,6 +23,10 @@ from ..storage_supplier import parametrize_storages
|
||||
from ..storage_supplier import StorageSupplier
|
||||
|
||||
|
||||
if sys.version_info < (3, 8):
|
||||
pytest.skip("BoTorch dropped Python3.7 support", allow_module_level=True)
|
||||
|
||||
|
||||
@parametrize_storages
|
||||
def test_study_set_and_get_user_attrs(storage_supplier: Callable[[], StorageSupplier]) -> None:
|
||||
with storage_supplier() as storage:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from unittest import TestCase
|
||||
|
||||
import optuna
|
||||
@@ -14,6 +15,7 @@ from optuna_dashboard._preferential_history import remove_history
|
||||
from optuna_dashboard._preferential_history import report_history
|
||||
from optuna_dashboard._serializer import serialize_preference_history
|
||||
from optuna_dashboard.preferential import create_study
|
||||
import pytest
|
||||
|
||||
from .wsgi_client import send_request
|
||||
|
||||
@@ -105,6 +107,7 @@ class APITestCase(TestCase):
|
||||
)
|
||||
self.assertEqual(status, 400)
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 8), reason="BoTorch dropped Python3.7 support")
|
||||
def test_get_best_trials_of_preferential_study(self) -> None:
|
||||
storage = optuna.storages.InMemoryStorage()
|
||||
study = create_study(n_generate=4, storage=storage)
|
||||
@@ -128,6 +131,7 @@ class APITestCase(TestCase):
|
||||
assert len(best_trials) == 1
|
||||
assert best_trials[0]["number"] == 0
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 8), reason="BoTorch dropped Python3.7 support")
|
||||
def test_report_preference(self) -> None:
|
||||
storage = optuna.storages.InMemoryStorage()
|
||||
study = create_study(n_generate=4, storage=storage)
|
||||
@@ -161,6 +165,7 @@ class APITestCase(TestCase):
|
||||
assert better.number == 2
|
||||
assert worse.number == 1
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 8), reason="BoTorch dropped Python3.7 support")
|
||||
def test_report_preference_when_typo_mode(self) -> None:
|
||||
storage = optuna.storages.InMemoryStorage()
|
||||
study = create_study(storage=storage, n_generate=3)
|
||||
@@ -184,6 +189,7 @@ class APITestCase(TestCase):
|
||||
)
|
||||
self.assertEqual(status, 400)
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 8), reason="BoTorch dropped Python3.7 support")
|
||||
def test_change_component(self) -> None:
|
||||
storage = optuna.storages.InMemoryStorage()
|
||||
study = create_study(storage=storage, n_generate=3)
|
||||
@@ -214,6 +220,7 @@ class APITestCase(TestCase):
|
||||
assert study_detail["feedback_component_type"]["output_type"] == "artifact"
|
||||
assert study_detail["feedback_component_type"]["artifact_key"] == "image"
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 8), reason="BoTorch dropped Python3.7 support")
|
||||
def test_skip_trial(self) -> None:
|
||||
storage = optuna.storages.InMemoryStorage()
|
||||
study = create_study(n_generate=4, storage=storage)
|
||||
@@ -238,6 +245,7 @@ class APITestCase(TestCase):
|
||||
assert len(best_trials) == 1
|
||||
assert best_trials[0].number == 2
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 8), reason="BoTorch dropped Python3.7 support")
|
||||
def test_remove_history(self) -> None:
|
||||
storage = optuna.storages.InMemoryStorage()
|
||||
study = create_study(storage=storage, n_generate=3)
|
||||
@@ -271,6 +279,7 @@ class APITestCase(TestCase):
|
||||
assert histories[0]["is_removed"]
|
||||
assert len(study.get_preferences()) == 0
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 8), reason="BoTorch dropped Python3.7 support")
|
||||
def test_restore_history(self) -> None:
|
||||
storage = optuna.storages.InMemoryStorage()
|
||||
study = create_study(storage=storage, n_generate=3)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from typing import Callable
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
@@ -13,6 +14,7 @@ from optuna_dashboard._preferential_history import restore_history
|
||||
from optuna_dashboard._serializer import serialize_preference_history
|
||||
from optuna_dashboard.preferential import create_study
|
||||
from optuna_dashboard.preferential._system_attrs import _SYSTEM_ATTR_PREFIX_PREFERENCE
|
||||
import pytest
|
||||
|
||||
from .storage_supplier import parametrize_storages
|
||||
from .storage_supplier import StorageSupplier
|
||||
@@ -22,6 +24,10 @@ if TYPE_CHECKING:
|
||||
from optuna_dashboard._preferential_history import History
|
||||
|
||||
|
||||
if sys.version_info < (3, 8):
|
||||
pytest.skip("BoTorch dropped Python3.7 support", allow_module_level=True)
|
||||
|
||||
|
||||
@parametrize_storages
|
||||
def test_report_and_get_choices(storage_supplier: Callable[[], StorageSupplier]) -> None:
|
||||
with storage_supplier() as storage:
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
import optuna
|
||||
from optuna_dashboard._serializer import serialize_attrs
|
||||
from optuna_dashboard._serializer import serialize_study_detail
|
||||
from optuna_dashboard._serializer import serialize_study_summary
|
||||
from optuna_dashboard._storage import get_study_summaries
|
||||
from optuna_dashboard.preferential import create_study
|
||||
import pytest
|
||||
|
||||
|
||||
def test_serialize_bytes() -> None:
|
||||
@@ -22,6 +25,7 @@ def test_serialize_dict() -> None:
|
||||
assert len(serialized) <= 1
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 8), reason="BoTorch dropped Python3.7 support")
|
||||
def test_get_study_detail_is_preferential() -> None:
|
||||
storage = optuna.storages.InMemoryStorage()
|
||||
study = create_study(n_generate=4, storage=storage)
|
||||
@@ -48,6 +52,7 @@ def test_get_study_detail_is_not_preferential() -> None:
|
||||
assert not study_detail["is_preferential"]
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 8), reason="BoTorch dropped Python3.7 support")
|
||||
def test_get_study_summary_is_preferential() -> None:
|
||||
storage = optuna.storages.InMemoryStorage()
|
||||
create_study(n_generate=4, storage=storage)
|
||||
|
||||
+13
-8
@@ -1,16 +1,20 @@
|
||||
use fanova::{FanovaOptions, RandomForestOptions};
|
||||
use js_sys::Array;
|
||||
use serde_wasm_bindgen::from_value;
|
||||
use fanova::{FanovaOptions, RandomForestOptions};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn wasm_fanova_calculate(features: Array, targets: Array) -> Vec<f64> {
|
||||
// TODO(c-bata): Fix error handling
|
||||
pub fn wasm_fanova_calculate(features: Array, targets: Array) -> Result<Vec<f64>, JsError> {
|
||||
let features_vec: Vec<Vec<f64>> = features
|
||||
.iter()
|
||||
.map(|x| from_value::<Vec<f64>>(x).unwrap())
|
||||
.collect();
|
||||
let targets_vec: Vec<f64> = targets.iter().map(|x| x.as_f64().unwrap()).collect();
|
||||
.map(|x| from_value::<Vec<f64>>(x))
|
||||
.collect::<Result<_, _>>()
|
||||
.map_err(|_| JsError::new("features must be of type number[][]"))?;
|
||||
let targets_vec: Vec<f64> = targets
|
||||
.iter()
|
||||
.map(|x| x.as_f64())
|
||||
.collect::<Option<_>>()
|
||||
.ok_or(JsError::new("targets must be of type number[]"))?;
|
||||
|
||||
let mut fanova = FanovaOptions::new()
|
||||
.random_forest(RandomForestOptions::new().seed(0))
|
||||
@@ -18,9 +22,10 @@ pub fn wasm_fanova_calculate(features: Array, targets: Array) -> Vec<f64> {
|
||||
features_vec.iter().map(|x| x.as_slice()).collect(),
|
||||
&targets_vec,
|
||||
)
|
||||
.unwrap();
|
||||
.map_err(|e| JsError::new(&format!("failed to build fANOVA model: {}", e)))?;
|
||||
let importances = (0..features_vec.len())
|
||||
.map(|i| fanova.quantify_importance(&[i]).mean)
|
||||
.collect::<Vec<_>>();
|
||||
return importances;
|
||||
|
||||
Ok(importances)
|
||||
}
|
||||
@@ -28,7 +28,7 @@ interface DataGridColumn<T> {
|
||||
field: keyof T
|
||||
label: string
|
||||
sortable?: boolean
|
||||
less?: (a: T, b: T) => number
|
||||
less?: (a: T, b: T, ascending: boolean) => number
|
||||
filterable?: boolean
|
||||
toCellValue?: (rowIndex: number) => string | React.ReactNode
|
||||
padding?: "normal" | "checkbox" | "none"
|
||||
@@ -358,7 +358,10 @@ function stableSort<T>(
|
||||
const stabilizedThis = array.map((el, index) => [el, index] as [T, number])
|
||||
stabilizedThis.sort((a, b) => {
|
||||
if (less) {
|
||||
const result = order == "asc" ? -less(a[0], b[0]) : less(a[0], b[0])
|
||||
const ascending = order == "asc"
|
||||
const result = ascending
|
||||
? -less(a[0], b[0], ascending)
|
||||
: less(a[0], b[0], ascending)
|
||||
if (result !== 0) return result
|
||||
} else {
|
||||
const result = comparator(a[0], b[0])
|
||||
|
||||
@@ -39,6 +39,7 @@ export const PlotImportance: FC<{ study: Study }> = ({ study }) => {
|
||||
const values = filteredTrials.map(
|
||||
(t) => t.values?.[objectiveId] as number
|
||||
)
|
||||
// TODO: handle errors thrown by wasm_fanova_calculate
|
||||
const importance = wasm_fanova_calculate(features, values)
|
||||
return study.intersection_search_space.map((s, i) => ({
|
||||
name: s.name,
|
||||
|
||||
@@ -25,7 +25,7 @@ export const TrialTable: FC<{
|
||||
field: "values",
|
||||
label: "Value",
|
||||
sortable: true,
|
||||
less: (firstEl, secondEl): number => {
|
||||
less: (firstEl, secondEl, ascending): number => {
|
||||
const firstVal = firstEl.values?.[0]
|
||||
const secondVal = secondEl.values?.[0]
|
||||
|
||||
@@ -33,9 +33,9 @@ export const TrialTable: FC<{
|
||||
return 0
|
||||
}
|
||||
if (firstVal === undefined) {
|
||||
return -1
|
||||
return ascending ? -1 : 1
|
||||
} else if (secondVal === undefined) {
|
||||
return 1
|
||||
return ascending ? 1 : -1
|
||||
}
|
||||
if (firstVal === "-inf" || secondVal === "inf") {
|
||||
return 1
|
||||
@@ -57,7 +57,7 @@ export const TrialTable: FC<{
|
||||
field: "values",
|
||||
label: `Objective ${objectiveId}`,
|
||||
sortable: true,
|
||||
less: (firstEl, secondEl): number => {
|
||||
less: (firstEl, secondEl, ascending): number => {
|
||||
const firstVal = firstEl.values?.[objectiveId]
|
||||
const secondVal = secondEl.values?.[objectiveId]
|
||||
|
||||
@@ -65,9 +65,9 @@ export const TrialTable: FC<{
|
||||
return 0
|
||||
}
|
||||
if (firstVal === undefined) {
|
||||
return -1
|
||||
return ascending ? -1 : 1
|
||||
} else if (secondVal === undefined) {
|
||||
return 1
|
||||
return ascending ? 1 : -1
|
||||
}
|
||||
if (firstVal === "-inf" || secondVal === "inf") {
|
||||
return 1
|
||||
@@ -96,7 +96,8 @@ export const TrialTable: FC<{
|
||||
null,
|
||||
sortable: true,
|
||||
filterable: false,
|
||||
less: (firstEl, secondEl): number => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
less: (firstEl, secondEl, _): number => {
|
||||
const firstVal = firstEl.params.find(
|
||||
(p) => p.name === s.name
|
||||
)?.param_internal_value
|
||||
@@ -126,7 +127,8 @@ export const TrialTable: FC<{
|
||||
?.value || null,
|
||||
sortable: attr_spec.sortable,
|
||||
filterable: false,
|
||||
less: (firstEl, secondEl): number => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
less: (firstEl, secondEl, _): number => {
|
||||
const firstVal = firstEl.user_attrs.find(
|
||||
(attr) => attr.key === attr_spec.key
|
||||
)?.value
|
||||
|
||||
+155
-50
@@ -38,10 +38,11 @@ export const loadStorage = (
|
||||
)
|
||||
db.checkRc(rc)
|
||||
try {
|
||||
if (!isSupportedSchema(db)) {
|
||||
const schemaVersion = getSchemaVersion(db)
|
||||
if (!isSupportedSchema(schemaVersion)) {
|
||||
return
|
||||
}
|
||||
const studies = getStudies(db)
|
||||
const studies = getStudies(db, schemaVersion)
|
||||
setter((prev) => [...prev, ...studies])
|
||||
} finally {
|
||||
db.close()
|
||||
@@ -49,21 +50,41 @@ export const loadStorage = (
|
||||
})
|
||||
}
|
||||
|
||||
const isSupportedSchema = (db: SQLite3DB): boolean => {
|
||||
let supported = true
|
||||
const getSchemaVersion = (db: SQLite3DB): string => {
|
||||
let schemaVersion = ""
|
||||
db.exec({
|
||||
sql: "SELECT schema_version FROM version_info LIMIT 1",
|
||||
sql: "SELECT version_num FROM alembic_version LIMIT 1",
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
callback: (vals: any[]) => {
|
||||
if (vals[0] != 12) {
|
||||
supported = false
|
||||
}
|
||||
schemaVersion = vals[0]
|
||||
},
|
||||
})
|
||||
return supported
|
||||
return schemaVersion
|
||||
}
|
||||
|
||||
const getStudies = (db: SQLite3DB): Study[] => {
|
||||
const isSupportedSchema = (schemaVersion: string): boolean => {
|
||||
const lowestVersion = "v2.6.0.a" // supported: "v3.2.0.a", "v3.0.0.{a,b,c,d}", "v2.6.0.a"
|
||||
if (schemaVersion == lowestVersion) return true
|
||||
return isGreaterSchemaVersion(schemaVersion, lowestVersion)
|
||||
}
|
||||
|
||||
const isGreaterSchemaVersion = (
|
||||
leftVersion: string,
|
||||
rightVersion: string
|
||||
): boolean => {
|
||||
// return leftVersion > rightVersion
|
||||
const leftSuffix = leftVersion.split(".").reverse()[0]
|
||||
const rightSuffix = rightVersion.split(".").reverse()[0]
|
||||
leftVersion = leftVersion.replace(/\D/g, "")
|
||||
rightVersion = rightVersion.replace(/\D/g, "")
|
||||
|
||||
const left = Number(leftVersion)
|
||||
const right = Number(rightVersion)
|
||||
if (left == right) return leftSuffix > rightSuffix
|
||||
return left > right
|
||||
}
|
||||
|
||||
const getStudies = (db: SQLite3DB, schemaVersion: string): Study[] => {
|
||||
const studies: Study[] = []
|
||||
db.exec({
|
||||
sql:
|
||||
@@ -78,7 +99,7 @@ const getStudies = (db: SQLite3DB): Study[] => {
|
||||
vals[2] === "MINIMIZE" ? "minimize" : "maximize"
|
||||
const objective = vals[3]
|
||||
|
||||
const trials = getTrials(db, studyId)
|
||||
const trials = getTrials(db, studyId, schemaVersion)
|
||||
const union_search_space: SearchSpaceItem[] = []
|
||||
const union_user_attrs: AttributeSpec[] = []
|
||||
let intersection_search_space: Set<SearchSpaceItem> = new Set()
|
||||
@@ -136,7 +157,11 @@ const getStudies = (db: SQLite3DB): Study[] => {
|
||||
return studies
|
||||
}
|
||||
|
||||
const getTrials = (db: SQLite3DB, studyId: number): Trial[] => {
|
||||
const getTrials = (
|
||||
db: SQLite3DB,
|
||||
studyId: number,
|
||||
schemaVersion: string
|
||||
): Trial[] => {
|
||||
const trials: Trial[] = []
|
||||
db.exec({
|
||||
sql:
|
||||
@@ -160,8 +185,12 @@ const getTrials = (db: SQLite3DB, studyId: number): Trial[] => {
|
||||
number: vals[1],
|
||||
study_id: studyId,
|
||||
state: state,
|
||||
values: getTrialValues(db, trialId),
|
||||
intermediate_values: getTrialIntermediateValues(db, trialId),
|
||||
values: getTrialValues(db, trialId, schemaVersion),
|
||||
intermediate_values: getTrialIntermediateValues(
|
||||
db,
|
||||
trialId,
|
||||
schemaVersion
|
||||
),
|
||||
params: [], // Set this column later
|
||||
user_attrs: [], // Set this column later
|
||||
datetime_start: vals[3],
|
||||
@@ -173,24 +202,41 @@ const getTrials = (db: SQLite3DB, studyId: number): Trial[] => {
|
||||
return trials
|
||||
}
|
||||
|
||||
const getTrialValues = (db: SQLite3DB, trialId: number): TrialValueNumber[] => {
|
||||
const getTrialValues = (
|
||||
db: SQLite3DB,
|
||||
trialId: number,
|
||||
schemaVersion: string
|
||||
): TrialValueNumber[] => {
|
||||
const values: TrialValueNumber[] = []
|
||||
db.exec({
|
||||
sql:
|
||||
"SELECT value, value_type" +
|
||||
` FROM trial_values WHERE trial_id = ${trialId}` +
|
||||
" ORDER BY objective",
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
callback: (vals: any[]) => {
|
||||
values.push(
|
||||
vals[1] === "INF_NEG"
|
||||
? "-inf"
|
||||
: vals[1] === "INF_POS"
|
||||
? "+inf"
|
||||
: vals[0]
|
||||
)
|
||||
},
|
||||
})
|
||||
if (isGreaterSchemaVersion(schemaVersion, "v3.0.0.c")) {
|
||||
db.exec({
|
||||
sql:
|
||||
"SELECT value, value_type" +
|
||||
` FROM trial_values WHERE trial_id = ${trialId}` +
|
||||
" ORDER BY objective",
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
callback: (vals: any[]) => {
|
||||
values.push(
|
||||
vals[1] === "INF_NEG"
|
||||
? "-inf"
|
||||
: vals[1] === "INF_POS"
|
||||
? "+inf"
|
||||
: vals[0]
|
||||
)
|
||||
},
|
||||
})
|
||||
} else {
|
||||
db.exec({
|
||||
sql:
|
||||
"SELECT value" +
|
||||
` FROM trial_values WHERE trial_id = ${trialId}` +
|
||||
" ORDER BY objective",
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
callback: (vals: any[]) => {
|
||||
values.push(vals[0])
|
||||
},
|
||||
})
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
@@ -241,6 +287,30 @@ const parseDistributionJSON = (t: string): Distribution => {
|
||||
step: parsed.attributes.step as number,
|
||||
log: parsed.attributes.log as boolean,
|
||||
}
|
||||
} else if (parsed.name === "UniformDistribution") {
|
||||
return {
|
||||
type: "FloatDistribution",
|
||||
low: parsed.attributes.low as number,
|
||||
high: parsed.attributes.high as number,
|
||||
step: null,
|
||||
log: false,
|
||||
}
|
||||
} else if (parsed.name === "LogUniformDistribution") {
|
||||
return {
|
||||
type: "FloatDistribution",
|
||||
low: parsed.attributes.low as number,
|
||||
high: parsed.attributes.high as number,
|
||||
step: null,
|
||||
log: true,
|
||||
}
|
||||
} else if (parsed.name === "DiscreteUniformDistribution") {
|
||||
return {
|
||||
type: "FloatDistribution",
|
||||
low: parsed.attributes.low as number,
|
||||
high: parsed.attributes.high as number,
|
||||
step: parsed.attributes.q,
|
||||
log: false,
|
||||
}
|
||||
} else if (parsed.name === "IntDistribution") {
|
||||
return {
|
||||
type: "IntDistribution",
|
||||
@@ -249,6 +319,22 @@ const parseDistributionJSON = (t: string): Distribution => {
|
||||
step: parsed.attributes.step as number,
|
||||
log: parsed.attributes.log as boolean,
|
||||
}
|
||||
} else if (parsed.name === "IntUniformDistribution") {
|
||||
return {
|
||||
type: "IntDistribution",
|
||||
low: parsed.attributes.low as number,
|
||||
high: parsed.attributes.high as number,
|
||||
step: parsed.attributes.step as number,
|
||||
log: false,
|
||||
}
|
||||
} else if (parsed.name === "IntLogUniformDistribution") {
|
||||
return {
|
||||
type: "IntDistribution",
|
||||
low: parsed.attributes.low as number,
|
||||
high: parsed.attributes.high as number,
|
||||
step: parsed.attributes.step as number,
|
||||
log: true,
|
||||
}
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const choices = parsed.attributes.choices.map((value: any) => {
|
||||
@@ -287,26 +373,45 @@ const getTrialUserAttributes = (
|
||||
|
||||
const getTrialIntermediateValues = (
|
||||
db: SQLite3DB,
|
||||
trialId: number
|
||||
trialId: number,
|
||||
schemaVersion: string
|
||||
): TrialIntermediateValue[] => {
|
||||
const values: TrialIntermediateValue[] = []
|
||||
db.exec({
|
||||
sql:
|
||||
"SELECT step, intermediate_value, intermediate_value_type" +
|
||||
` FROM trial_intermediate_values WHERE trial_id = ${trialId}` +
|
||||
" ORDER BY step",
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
callback: (vals: any[]) => {
|
||||
values.push({
|
||||
step: vals[0],
|
||||
value:
|
||||
vals[2] === "INF_NEG"
|
||||
? "-inf"
|
||||
: vals[2] === "INF_POS"
|
||||
? "+inf"
|
||||
: vals[1],
|
||||
})
|
||||
},
|
||||
})
|
||||
if (isGreaterSchemaVersion(schemaVersion, "v3.0.0.c")) {
|
||||
db.exec({
|
||||
sql:
|
||||
"SELECT step, intermediate_value, intermediate_value_type" +
|
||||
` FROM trial_intermediate_values WHERE trial_id = ${trialId}` +
|
||||
" ORDER BY step",
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
callback: (vals: any[]) => {
|
||||
values.push({
|
||||
step: vals[0],
|
||||
value:
|
||||
vals[2] === "INF_NEG"
|
||||
? "-inf"
|
||||
: vals[2] === "INF_POS"
|
||||
? "+inf"
|
||||
: vals[2] === "NAN"
|
||||
? "nan"
|
||||
: vals[1],
|
||||
})
|
||||
},
|
||||
})
|
||||
} else {
|
||||
db.exec({
|
||||
sql:
|
||||
"SELECT step, intermediate_value" +
|
||||
` FROM trial_intermediate_values WHERE trial_id = ${trialId}` +
|
||||
" ORDER BY step",
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
callback: (vals: any[]) => {
|
||||
values.push({
|
||||
step: vals[0],
|
||||
value: vals[1],
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
return values
|
||||
}
|
||||
Vendored
+2
-2
@@ -10,7 +10,7 @@ type FloatDistribution = {
|
||||
type: "FloatDistribution"
|
||||
low: number
|
||||
high: number
|
||||
step: number
|
||||
step: number | null
|
||||
log: boolean
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ type IntDistribution = {
|
||||
type: "IntDistribution"
|
||||
low: number
|
||||
high: number
|
||||
step: number
|
||||
step: number | null
|
||||
log: boolean
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,10 @@ Nothing to configure.
|
||||
|
||||
## Release Notes
|
||||
|
||||
### 0.1.0
|
||||
|
||||
Added older database schemas support (Optuna 2.6.0 or later)
|
||||
|
||||
### 0.0.1
|
||||
|
||||
Initial Release
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
"displayName": "Optuna Dashboard",
|
||||
"description": "Web Dashboard for Optuna",
|
||||
"publisher": "Optuna",
|
||||
"version": "0.0.1",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"icon": "images/optuna-logo.png",
|
||||
"engines": {
|
||||
|
||||
Reference in new issue
Block a user