jcmoptimizer 3.0.0__tar.gz → 3.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/PKG-INFO +13 -6
- {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/README.md +12 -5
- {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer/client.py +95 -22
- {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer/requestor.py +13 -5
- {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer/server.py +70 -49
- {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer/study.py +32 -5
- jcmoptimizer-3.0.2/jcmoptimizer/version.py +1 -0
- {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer.egg-info/PKG-INFO +13 -6
- jcmoptimizer-3.0.0/jcmoptimizer/version.py +0 -1
- {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/LICENSE +0 -0
- {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer/__init__.py +0 -0
- {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer/benchmark.py +0 -0
- {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer/drivers/__init__.py +0 -0
- {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer/drivers/active_learning_drivers.py +0 -0
- {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer/drivers/driver.py +0 -0
- {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer/drivers/third_party_drivers.py +0 -0
- {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer/objects.py +0 -0
- {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer.egg-info/SOURCES.txt +0 -0
- {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer.egg-info/dependency_links.txt +0 -0
- {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer.egg-info/requires.txt +0 -0
- {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer.egg-info/top_level.txt +0 -0
- {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/pyproject.toml +0 -0
- {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jcmoptimizer
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.2
|
|
4
4
|
Summary: The JCMoptimizer interface for Python
|
|
5
5
|
Author-email: Philipp Schneider <philipp.schneider@jcmwave.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -20,16 +20,23 @@ Dynamic: license-file
|
|
|
20
20
|
|
|
21
21
|
This package implements a python client for interacting with the JCMoptimizer server. It provides a convenient interface to set up, run, and analyze numerical studies of expensive black box functions.
|
|
22
22
|
|
|
23
|
-
## Getting Started
|
|
23
|
+
## Getting Started
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
* Browse to the [JCMoptimizer download](https://optimizer.jcmwave.com/download-jcmoptimizer) page to install JCMoptimzizer.
|
|
25
|
+
### Cloud Service
|
|
26
|
+
The easiest way to get started is to use the free tier of the JCMwave's cloud service for JCMoptimizer:
|
|
28
27
|
* Install the `jcmoptimizer` package into your python environmentm, e.g. by runing `pip install jcmoptimizer`.
|
|
28
|
+
* Signup to [optimizer.jcmwave.com](https://optimizer.jcmwave.com).
|
|
29
|
+
* Create an [API access token](https://optimizer.jcmwave.com/cloud/tokens/list/)
|
|
30
|
+
|
|
31
|
+
### Run JCMoptimizer Locally
|
|
32
|
+
For a self-hosted version, you need install JCMsuite and the JCMoptimizer extension for JCMsuite:
|
|
33
|
+
* Browse to the [JCMsuite download](https://installation.jcmwave.com/fb13885794fc64c42531c9656f8f6f73.php) page to install JCMsuite and to retrieve a demo or commercial license.
|
|
34
|
+
* Browse to the [JCMoptimizer download](https://optimizer.jcmwave.com/download) page to install JCMoptimzizer.
|
|
29
35
|
|
|
36
|
+
### First Example
|
|
30
37
|
A basic usage example of the python package is given in the tutorial on a [Bayesian Optimization](https://optimizer.jcmwave.com/documentation/python/latest/tutorials/vanilla_bayesian_optimization.html).
|
|
31
38
|
|
|
32
|
-
## Documentation
|
|
39
|
+
## Documentation
|
|
33
40
|
|
|
34
41
|
For a comprehensive overview of all available classes, methods, and their parameters, please refer to the official JCMoptimizer Python documentation:
|
|
35
42
|
|
|
@@ -2,16 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
This package implements a python client for interacting with the JCMoptimizer server. It provides a convenient interface to set up, run, and analyze numerical studies of expensive black box functions.
|
|
4
4
|
|
|
5
|
-
## Getting Started
|
|
5
|
+
## Getting Started
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* Browse to the [JCMoptimizer download](https://optimizer.jcmwave.com/download-jcmoptimizer) page to install JCMoptimzizer.
|
|
7
|
+
### Cloud Service
|
|
8
|
+
The easiest way to get started is to use the free tier of the JCMwave's cloud service for JCMoptimizer:
|
|
10
9
|
* Install the `jcmoptimizer` package into your python environmentm, e.g. by runing `pip install jcmoptimizer`.
|
|
10
|
+
* Signup to [optimizer.jcmwave.com](https://optimizer.jcmwave.com).
|
|
11
|
+
* Create an [API access token](https://optimizer.jcmwave.com/cloud/tokens/list/)
|
|
12
|
+
|
|
13
|
+
### Run JCMoptimizer Locally
|
|
14
|
+
For a self-hosted version, you need install JCMsuite and the JCMoptimizer extension for JCMsuite:
|
|
15
|
+
* Browse to the [JCMsuite download](https://installation.jcmwave.com/fb13885794fc64c42531c9656f8f6f73.php) page to install JCMsuite and to retrieve a demo or commercial license.
|
|
16
|
+
* Browse to the [JCMoptimizer download](https://optimizer.jcmwave.com/download) page to install JCMoptimzizer.
|
|
11
17
|
|
|
18
|
+
### First Example
|
|
12
19
|
A basic usage example of the python package is given in the tutorial on a [Bayesian Optimization](https://optimizer.jcmwave.com/documentation/python/latest/tutorials/vanilla_bayesian_optimization.html).
|
|
13
20
|
|
|
14
|
-
## Documentation
|
|
21
|
+
## Documentation
|
|
15
22
|
|
|
16
23
|
For a comprehensive overview of all available classes, methods, and their parameters, please refer to the official JCMoptimizer Python documentation:
|
|
17
24
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from typing import Optional, Any
|
|
2
|
-
from datetime import datetime
|
|
2
|
+
from datetime import datetime, timedelta
|
|
3
3
|
import webbrowser
|
|
4
4
|
from .requestor import (
|
|
5
5
|
OptimizerRequestor,
|
|
@@ -33,6 +33,13 @@ class Client:
|
|
|
33
33
|
For example, for a local server ``'http://localhost:4554'``
|
|
34
34
|
server_id: If the host is unkown, a cloud server can be accessed by its
|
|
35
35
|
server ID.
|
|
36
|
+
|
|
37
|
+
.. note:: If both `host` and `server_id` are not specified,
|
|
38
|
+
the client first tries to connects to a running cloud server.
|
|
39
|
+
If not possible, the client starts a new
|
|
40
|
+
:class:`~jcmoptimizer.Server` instance
|
|
41
|
+
with default setting and connects to it.
|
|
42
|
+
|
|
36
43
|
token: API access token. This is required for cloud servers.
|
|
37
44
|
In order to create a token, visit the
|
|
38
45
|
`JCMoptimizer Cloud <https://optimizer.jcmwave.com/cloud/tokens/list>`_
|
|
@@ -58,23 +65,49 @@ class Client:
|
|
|
58
65
|
|
|
59
66
|
if token is None:
|
|
60
67
|
token = self.config.get("token", None)
|
|
61
|
-
|
|
68
|
+
|
|
62
69
|
if host is None:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
from .server import _wait_for_server_startup
|
|
71
|
+
|
|
72
|
+
if server_id is not None:
|
|
73
|
+
if token is None:
|
|
74
|
+
raise ValueError(
|
|
75
|
+
"If server_id is specified, "
|
|
76
|
+
"the token must be specified as well."
|
|
77
|
+
)
|
|
78
|
+
# get host from cloud api
|
|
79
|
+
cloud_requestor = CloudRequestor(cloud_endpoint, token)
|
|
80
|
+
r = cloud_requestor.request(
|
|
81
|
+
"GET", "get server url", f"servers/{server_id}/"
|
|
82
|
+
)
|
|
83
|
+
data = r.json()
|
|
84
|
+
host = data["url"]
|
|
85
|
+
_wait_for_server_startup(cloud_endpoint, token, server_id, host)
|
|
86
|
+
|
|
87
|
+
if host is None:
|
|
88
|
+
# get host of first running server in cloud
|
|
89
|
+
if token is not None:
|
|
90
|
+
cloud_requestor = CloudRequestor(cloud_endpoint, token)
|
|
91
|
+
r = cloud_requestor.request("GET", "get server list", "servers/")
|
|
92
|
+
servers = r.json()
|
|
93
|
+
if len(servers):
|
|
94
|
+
server_id = servers[0]["server_id"]
|
|
95
|
+
host = servers[0]["url"]
|
|
96
|
+
inform(f"Connecting to server {server_id}.")
|
|
97
|
+
_wait_for_server_startup(cloud_endpoint, token, server_id, host)
|
|
98
|
+
else:
|
|
99
|
+
inform("No cloud server running. Starting new server.")
|
|
100
|
+
|
|
101
|
+
if host is None:
|
|
102
|
+
# start a server (locally or on cloud)
|
|
103
|
+
from .server import Server
|
|
104
|
+
|
|
105
|
+
server = Server(token=token, cloud_endpoint=cloud_endpoint)
|
|
106
|
+
host = server.host
|
|
74
107
|
|
|
75
108
|
if token is None:
|
|
76
109
|
token = "selfhosted"
|
|
77
|
-
|
|
110
|
+
|
|
78
111
|
self.requestor = OptimizerRequestor(host=host, token=token, verbose=verbose)
|
|
79
112
|
if check:
|
|
80
113
|
self.check_server()
|
|
@@ -91,7 +124,6 @@ class Client:
|
|
|
91
124
|
if self.requestor.verbose:
|
|
92
125
|
inform(f"Polling server at {self.requestor.endpoint}")
|
|
93
126
|
answer = self.requestor.get("check server")
|
|
94
|
-
answer = self.requestor.get("check server")
|
|
95
127
|
optimizer_version = str(answer["version"])
|
|
96
128
|
if optimizer_version != __version__:
|
|
97
129
|
warn(
|
|
@@ -134,7 +166,7 @@ class Client:
|
|
|
134
166
|
shutdown_time: The time at which the optimization server should be
|
|
135
167
|
shut down.
|
|
136
168
|
"""
|
|
137
|
-
|
|
169
|
+
|
|
138
170
|
self.requestor.post(
|
|
139
171
|
"set server shutdown time",
|
|
140
172
|
"server",
|
|
@@ -157,6 +189,8 @@ class Client:
|
|
|
157
189
|
dashboard: bool = True,
|
|
158
190
|
open_browser: Optional[bool] = None,
|
|
159
191
|
clear_storage: bool = False,
|
|
192
|
+
extend_on_request: Optional[timedelta] = None,
|
|
193
|
+
extend_on_suggestion: Optional[timedelta] = None,
|
|
160
194
|
**kwargs: Any,
|
|
161
195
|
) -> Study:
|
|
162
196
|
"""Creates a new :class:`~jcmoptimizer.Study` instance. Example::
|
|
@@ -301,6 +335,28 @@ class Client:
|
|
|
301
335
|
etc.) from previous runs of the study is cleared before creating the
|
|
302
336
|
study.
|
|
303
337
|
|
|
338
|
+
extend_on_request: If specified, the shutdown time of a cloud-based
|
|
339
|
+
server is extended in each request, such that the server will always run
|
|
340
|
+
for at least the specified amount of time after the request.
|
|
341
|
+
If not specified, the value is retrieved from the
|
|
342
|
+
:ref:`Configuration` file. Default: 5 minutes. Example::
|
|
343
|
+
|
|
344
|
+
study = client.create_study(
|
|
345
|
+
study_id="my-study",
|
|
346
|
+
extend_on_request=timedelta(minutes=10)
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
extend_on_suggestion: If specified, the shutdown time of a cloud-based
|
|
350
|
+
server is extended in each suggestion requested with ``study.get_suggestion()``,
|
|
351
|
+
such that the server will always run for at least the specified amount of time
|
|
352
|
+
after the request. If not specified, the value is retrieved from the
|
|
353
|
+
:ref:`Configuration` file. Default: 30 minutes. Example::
|
|
354
|
+
|
|
355
|
+
study = client.create_study(
|
|
356
|
+
study_id="my-study",
|
|
357
|
+
extend_on_suggestion=timedelta(hours=1, minutes=30)
|
|
358
|
+
)
|
|
359
|
+
|
|
304
360
|
"""
|
|
305
361
|
|
|
306
362
|
if "domain" in kwargs:
|
|
@@ -315,6 +371,24 @@ class Client:
|
|
|
315
371
|
"Please use the 'study_name' argument instead."
|
|
316
372
|
)
|
|
317
373
|
|
|
374
|
+
if extend_on_request is None:
|
|
375
|
+
if "extend_on_request" in self.config:
|
|
376
|
+
extend_on_request = self.config["extend_on_request"]
|
|
377
|
+
if not isinstance(extend_on_request, timedelta):
|
|
378
|
+
raise TypeError("Invalid format of 'extend_on_request' in config file")
|
|
379
|
+
else:
|
|
380
|
+
extend_on_request = timedelta(minutes=5)
|
|
381
|
+
if extend_on_suggestion is None:
|
|
382
|
+
if "extend_on_suggestion" in self.config:
|
|
383
|
+
extend_on_suggestion = self.config["extend_on_suggestion"]
|
|
384
|
+
if not isinstance(extend_on_suggestion, timedelta):
|
|
385
|
+
raise TypeError(
|
|
386
|
+
"Invalid format of 'extend_on_suggestion' in config file"
|
|
387
|
+
)
|
|
388
|
+
else:
|
|
389
|
+
extend_on_suggestion = timedelta(minutes=30)
|
|
390
|
+
|
|
391
|
+
|
|
318
392
|
answer = self.requestor.post(
|
|
319
393
|
"create study",
|
|
320
394
|
"study",
|
|
@@ -343,21 +417,20 @@ class Client:
|
|
|
343
417
|
)
|
|
344
418
|
if open_browser is None:
|
|
345
419
|
open_browser = self.config.get("open_browser", True)
|
|
346
|
-
|
|
420
|
+
|
|
347
421
|
if answer["dashboard_path"]:
|
|
348
|
-
dashboard_path = self.requestor.endpoint + answer[
|
|
349
|
-
inform(
|
|
350
|
-
f"The dashboard is accessible via {dashboard_path}"
|
|
351
|
-
)
|
|
422
|
+
dashboard_path = self.requestor.endpoint + answer["dashboard_path"]
|
|
423
|
+
inform(f"The dashboard is accessible via {dashboard_path}")
|
|
352
424
|
if open_browser:
|
|
353
425
|
webbrowser.open(dashboard_path)
|
|
354
|
-
|
|
355
426
|
|
|
356
427
|
study = Study(
|
|
357
428
|
study_id=answer["study_id"],
|
|
358
429
|
project_id=answer["project_id"],
|
|
359
430
|
driver=driver_instance,
|
|
360
431
|
requestor=self.requestor,
|
|
432
|
+
extend_on_request=extend_on_request,
|
|
433
|
+
extend_on_suggestion=extend_on_suggestion,
|
|
361
434
|
)
|
|
362
435
|
|
|
363
436
|
return study
|
|
@@ -3,6 +3,7 @@ import sys
|
|
|
3
3
|
import threading
|
|
4
4
|
import os
|
|
5
5
|
from datetime import datetime as dt
|
|
6
|
+
from datetime import timedelta
|
|
6
7
|
import json
|
|
7
8
|
import itertools
|
|
8
9
|
import threading
|
|
@@ -64,14 +65,14 @@ def work_animation(work_done: threading.Event, msg: str) -> None:
|
|
|
64
65
|
time.sleep(0.15)
|
|
65
66
|
|
|
66
67
|
|
|
67
|
-
def show_working(msg) -> None:
|
|
68
|
+
def show_working(msg: str) -> None:
|
|
68
69
|
WORK_DONE.clear()
|
|
69
70
|
t = threading.Thread(target=work_animation, args=(WORK_DONE, msg))
|
|
70
71
|
t.daemon = True
|
|
71
72
|
t.start()
|
|
72
73
|
|
|
73
74
|
|
|
74
|
-
def hide_working():
|
|
75
|
+
def hide_working() -> None:
|
|
75
76
|
wait = not WORK_DONE.is_set()
|
|
76
77
|
WORK_DONE.set()
|
|
77
78
|
if wait:
|
|
@@ -100,6 +101,9 @@ def _get_value(expr: str) -> Any:
|
|
|
100
101
|
return expr.strip('"')
|
|
101
102
|
if expr.isdigit():
|
|
102
103
|
return int(expr)
|
|
104
|
+
if expr.count(":") == 2:
|
|
105
|
+
h,m,s = expr.split(":")
|
|
106
|
+
return timedelta(hours=int(h),minutes=int(m),seconds=int(s))
|
|
103
107
|
try:
|
|
104
108
|
return float(expr)
|
|
105
109
|
except ValueError:
|
|
@@ -193,6 +197,7 @@ class Requestor:
|
|
|
193
197
|
if method == "GET":
|
|
194
198
|
response = self.session.get(url)
|
|
195
199
|
elif method == "POST":
|
|
200
|
+
assert data is not None
|
|
196
201
|
response = self.session.post(
|
|
197
202
|
url,
|
|
198
203
|
data=self.encode_data(data),
|
|
@@ -220,11 +225,14 @@ class Requestor:
|
|
|
220
225
|
try:
|
|
221
226
|
response_data = response.json()
|
|
222
227
|
except requests.exceptions.JSONDecodeError as err:
|
|
228
|
+
excerpt = ""
|
|
229
|
+
if response._content is not None:
|
|
230
|
+
excerpt = f"\n{response._content[:1000]!r}"
|
|
223
231
|
raise EnvironmentError(
|
|
224
232
|
f"Cannot decode answer: {err}"
|
|
225
233
|
f"\nRequest: {method} {url} {data}"
|
|
226
234
|
f"\nResponse: {response.status_code} {response.reason}"
|
|
227
|
-
f"
|
|
235
|
+
f"{excerpt}"
|
|
228
236
|
) from err
|
|
229
237
|
|
|
230
238
|
self.check_status_code(
|
|
@@ -236,7 +244,7 @@ class Requestor:
|
|
|
236
244
|
self,
|
|
237
245
|
purpose: str,
|
|
238
246
|
status_code: int,
|
|
239
|
-
data: dict[str, Any],
|
|
247
|
+
data: Optional[dict[str, Any]],
|
|
240
248
|
response_data: dict[str, Any],
|
|
241
249
|
) -> None:
|
|
242
250
|
if status_code >= 500:
|
|
@@ -288,7 +296,7 @@ class OptimizerRequestor(Requestor):
|
|
|
288
296
|
self,
|
|
289
297
|
purpose: str,
|
|
290
298
|
status_code: int,
|
|
291
|
-
data: dict[str, Any],
|
|
299
|
+
data: Optional[dict[str, Any]],
|
|
292
300
|
response_data: dict[str, Any],
|
|
293
301
|
) -> None:
|
|
294
302
|
if status_code == 202:
|
|
@@ -6,7 +6,7 @@ import json
|
|
|
6
6
|
import tempfile
|
|
7
7
|
import time
|
|
8
8
|
import subprocess
|
|
9
|
-
from datetime import datetime
|
|
9
|
+
from datetime import datetime, timedelta
|
|
10
10
|
import atexit
|
|
11
11
|
import requests
|
|
12
12
|
|
|
@@ -34,6 +34,51 @@ def _server_response(log_file: Any, error_file: Any, max_lines: int = 10) -> str
|
|
|
34
34
|
STARTUP_TIMEOUT = 5
|
|
35
35
|
|
|
36
36
|
|
|
37
|
+
def _wait_for_server_startup(
|
|
38
|
+
cloud_endpoint: str, token: str, server_id: str, host: str
|
|
39
|
+
) -> None:
|
|
40
|
+
cloud_requestor = CloudRequestor(
|
|
41
|
+
cloud_endpoint=cloud_endpoint,
|
|
42
|
+
token=token,
|
|
43
|
+
)
|
|
44
|
+
prev_state: Optional[str] = None
|
|
45
|
+
while True:
|
|
46
|
+
r = cloud_requestor.request(
|
|
47
|
+
method="GET",
|
|
48
|
+
purpose="check JCMoptimizer server state",
|
|
49
|
+
path=f"servers/{server_id}/",
|
|
50
|
+
)
|
|
51
|
+
data = r.json()
|
|
52
|
+
state_dict = json.loads(data["state"])
|
|
53
|
+
state = state_dict["key"]
|
|
54
|
+
if state == "running":
|
|
55
|
+
break
|
|
56
|
+
if prev_state != state:
|
|
57
|
+
hide_working()
|
|
58
|
+
show_working(state_dict["description"])
|
|
59
|
+
prev_state = state
|
|
60
|
+
time.sleep(0.5)
|
|
61
|
+
|
|
62
|
+
# Try to connect with client
|
|
63
|
+
t0 = time.time()
|
|
64
|
+
while time.time() - t0 < STARTUP_TIMEOUT:
|
|
65
|
+
try:
|
|
66
|
+
client = Client(
|
|
67
|
+
host=host,
|
|
68
|
+
verbose=False,
|
|
69
|
+
cloud_endpoint=cloud_endpoint,
|
|
70
|
+
token=token,
|
|
71
|
+
)
|
|
72
|
+
except ConnectionError:
|
|
73
|
+
time.sleep(0.1)
|
|
74
|
+
else:
|
|
75
|
+
break
|
|
76
|
+
|
|
77
|
+
# Hide only if show_working was called.
|
|
78
|
+
if prev_state is not None:
|
|
79
|
+
hide_working()
|
|
80
|
+
|
|
81
|
+
|
|
37
82
|
class TimeoutError(Exception):
|
|
38
83
|
pass
|
|
39
84
|
|
|
@@ -57,11 +102,11 @@ class Server:
|
|
|
57
102
|
study = client.create_study(...)
|
|
58
103
|
|
|
59
104
|
.. note::
|
|
60
|
-
If you know the ID of a running cloud-based server, you can
|
|
105
|
+
If you know the ID of a running cloud-based server, you can
|
|
61
106
|
directly connect to it via ``client = Client(server_id='leonardo')``.
|
|
62
107
|
Likewise, if you know the port of a local server you can connect via
|
|
63
108
|
``client = Client(host=http://locahost:4554')``.
|
|
64
|
-
|
|
109
|
+
|
|
65
110
|
General Arguments
|
|
66
111
|
~~~~~~~~~~~~~~~~~~
|
|
67
112
|
The following arguments are available for all server instances.
|
|
@@ -138,7 +183,7 @@ class Server:
|
|
|
138
183
|
|
|
139
184
|
def __init__(
|
|
140
185
|
self,
|
|
141
|
-
server_location: Literal["cloud", "local"]
|
|
186
|
+
server_location: Optional[Literal["cloud", "local"]] = None,
|
|
142
187
|
persistent: bool = False,
|
|
143
188
|
server_id: Optional[str] = None,
|
|
144
189
|
server_name: Optional[str] = None,
|
|
@@ -194,22 +239,32 @@ class Server:
|
|
|
194
239
|
"Server location 'cloud' requires 'token' to be specified."
|
|
195
240
|
)
|
|
196
241
|
shutdown_at_str = None
|
|
197
|
-
if shutdown_at is
|
|
198
|
-
if
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
242
|
+
if shutdown_at is None:
|
|
243
|
+
if "server_runtime" in config:
|
|
244
|
+
server_runtime = config["server_runtime"]
|
|
245
|
+
if not isinstance(server_runtime, timedelta):
|
|
246
|
+
raise ValueError(
|
|
247
|
+
"Invalid format of 'server_runtime' in config file"
|
|
248
|
+
)
|
|
249
|
+
else:
|
|
250
|
+
server_runtime = timedelta(minutes=30)
|
|
251
|
+
shutdown_at = datetime.now() + server_runtime
|
|
252
|
+
|
|
253
|
+
elif not isinstance(shutdown_at, datetime):
|
|
254
|
+
raise ValueError(
|
|
255
|
+
f"'shutdown_at' must be a {type(datetime)} object. "
|
|
256
|
+
f"Got {shutdown_at} of type {type(shutdown_at)}."
|
|
257
|
+
)
|
|
258
|
+
shutdown_at_str = shutdown_at.astimezone().isoformat()
|
|
204
259
|
|
|
205
260
|
self._start_cloud_server(
|
|
206
261
|
persistent=persistent,
|
|
207
262
|
server_id=server_id,
|
|
208
263
|
server_name=server_name,
|
|
209
264
|
shutdown_at=shutdown_at_str,
|
|
210
|
-
token=token,
|
|
211
|
-
cloud_endpoint=cloud_endpoint,
|
|
212
265
|
version=version,
|
|
266
|
+
cloud_endpoint=cloud_endpoint,
|
|
267
|
+
token=token,
|
|
213
268
|
)
|
|
214
269
|
|
|
215
270
|
elif self.server_location == "local":
|
|
@@ -254,11 +309,10 @@ class Server:
|
|
|
254
309
|
server_id: Optional[str],
|
|
255
310
|
server_name: Optional[str],
|
|
256
311
|
shutdown_at: Optional[str],
|
|
257
|
-
token: str,
|
|
258
312
|
cloud_endpoint: str,
|
|
313
|
+
token: str,
|
|
259
314
|
version: str,
|
|
260
315
|
) -> None:
|
|
261
|
-
|
|
262
316
|
cloud_requestor = CloudRequestor(
|
|
263
317
|
cloud_endpoint=cloud_endpoint,
|
|
264
318
|
token=token,
|
|
@@ -275,43 +329,10 @@ class Server:
|
|
|
275
329
|
),
|
|
276
330
|
)
|
|
277
331
|
data = response.json()
|
|
278
|
-
self._optimizer_id = data["optimizer_id"]
|
|
279
332
|
self._host = data["url"]
|
|
280
333
|
self._id = data["server_id"]
|
|
281
334
|
|
|
282
|
-
|
|
283
|
-
state = json.loads(data["state"])
|
|
284
|
-
while state["key"] != "running":
|
|
285
|
-
if prev_state_key != state["key"]:
|
|
286
|
-
hide_working()
|
|
287
|
-
show_working(state["description"])
|
|
288
|
-
prev_state_key = state["key"]
|
|
289
|
-
time.sleep(0.5)
|
|
290
|
-
r = cloud_requestor.request(
|
|
291
|
-
method="GET",
|
|
292
|
-
purpose="check JCMoptimizer server state",
|
|
293
|
-
path=f"servers/{self._id}/",
|
|
294
|
-
)
|
|
295
|
-
data = r.json()
|
|
296
|
-
state = json.loads(data["state"])
|
|
297
|
-
|
|
298
|
-
# Try to connect with client
|
|
299
|
-
t0 = time.time()
|
|
300
|
-
while time.time() - t0 < STARTUP_TIMEOUT:
|
|
301
|
-
try:
|
|
302
|
-
client = Client(
|
|
303
|
-
host=self._host,
|
|
304
|
-
verbose=False,
|
|
305
|
-
cloud_endpoint=cloud_endpoint,
|
|
306
|
-
token=token,
|
|
307
|
-
)
|
|
308
|
-
except ConnectionError:
|
|
309
|
-
time.sleep(0.1)
|
|
310
|
-
else:
|
|
311
|
-
break
|
|
312
|
-
|
|
313
|
-
hide_working()
|
|
314
|
-
|
|
335
|
+
_wait_for_server_startup(cloud_endpoint, token, self._id, self._host)
|
|
315
336
|
inform(f"JCMoptimizer {self._id!r} started. Host: {self._host}")
|
|
316
337
|
|
|
317
338
|
if not persistent:
|
|
@@ -5,6 +5,7 @@ import warnings
|
|
|
5
5
|
import traceback
|
|
6
6
|
import atexit
|
|
7
7
|
import requests
|
|
8
|
+
from datetime import datetime, timedelta
|
|
8
9
|
|
|
9
10
|
from .requestor import OptimizerRequestor, NumParallelError, warn
|
|
10
11
|
from .objects import Observation, Suggestion
|
|
@@ -12,7 +13,6 @@ from .drivers import Driver
|
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class Study:
|
|
15
|
-
|
|
16
16
|
"""
|
|
17
17
|
This class provides methods for controlling a numerical optimization study.
|
|
18
18
|
Example::
|
|
@@ -48,11 +48,15 @@ class Study:
|
|
|
48
48
|
project_id: str,
|
|
49
49
|
driver: Driver,
|
|
50
50
|
requestor: OptimizerRequestor,
|
|
51
|
+
extend_on_request: Optional[timedelta] = None,
|
|
52
|
+
extend_on_suggestion: Optional[timedelta] = None,
|
|
51
53
|
) -> None:
|
|
52
54
|
self.study_id = study_id
|
|
53
55
|
self.project_id = project_id
|
|
54
56
|
self._driver = driver
|
|
55
57
|
self._requestor = requestor
|
|
58
|
+
self._extend_on_request = extend_on_request
|
|
59
|
+
self._extend_on_suggestion = extend_on_suggestion
|
|
56
60
|
self.evaluator: Optional[Callable[..., Observation]] = None
|
|
57
61
|
self.num_failed = 0
|
|
58
62
|
self.max_num_failed = 3
|
|
@@ -80,12 +84,35 @@ class Study:
|
|
|
80
84
|
operation: str,
|
|
81
85
|
data: Optional[dict[str, Any]] = None,
|
|
82
86
|
) -> dict[str, Any]:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
result = self._requestor.post(purpose, object, operation, self.qualifier, data)
|
|
88
|
+
self._extend_shutdown_time(self._extend_on_request)
|
|
89
|
+
if purpose == "get suggestion":
|
|
90
|
+
self._extend_shutdown_time(self._extend_on_suggestion)
|
|
91
|
+
return result
|
|
86
92
|
|
|
87
93
|
def _get(self, purpose: str, object: str, type: str) -> dict[str, Any]:
|
|
88
|
-
|
|
94
|
+
result = self._requestor.get(purpose, object, type, self.qualifier)
|
|
95
|
+
self._extend_shutdown_time(self._extend_on_request)
|
|
96
|
+
return result
|
|
97
|
+
|
|
98
|
+
def _extend_shutdown_time(self, duration: Optional[timedelta]) -> None:
|
|
99
|
+
if duration is None:
|
|
100
|
+
return
|
|
101
|
+
r = self._requestor.get("check server")
|
|
102
|
+
if "shutdown_at" not in r:
|
|
103
|
+
# not a cloud-based server
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
shutdown_at = datetime.fromisoformat(r["shutdown_at"])
|
|
107
|
+
now = datetime.now().astimezone()
|
|
108
|
+
if shutdown_at < now + duration:
|
|
109
|
+
new_shutdown_at = now + duration
|
|
110
|
+
self._requestor.post(
|
|
111
|
+
"extend server shutdown time",
|
|
112
|
+
"server",
|
|
113
|
+
"update_shutdown_at",
|
|
114
|
+
data={"shutdown_at": new_shutdown_at.astimezone().isoformat()},
|
|
115
|
+
)
|
|
89
116
|
|
|
90
117
|
def _delete_on_server(self) -> None:
|
|
91
118
|
if self.deleted:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '3.0.2'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jcmoptimizer
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.2
|
|
4
4
|
Summary: The JCMoptimizer interface for Python
|
|
5
5
|
Author-email: Philipp Schneider <philipp.schneider@jcmwave.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -20,16 +20,23 @@ Dynamic: license-file
|
|
|
20
20
|
|
|
21
21
|
This package implements a python client for interacting with the JCMoptimizer server. It provides a convenient interface to set up, run, and analyze numerical studies of expensive black box functions.
|
|
22
22
|
|
|
23
|
-
## Getting Started
|
|
23
|
+
## Getting Started
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
* Browse to the [JCMoptimizer download](https://optimizer.jcmwave.com/download-jcmoptimizer) page to install JCMoptimzizer.
|
|
25
|
+
### Cloud Service
|
|
26
|
+
The easiest way to get started is to use the free tier of the JCMwave's cloud service for JCMoptimizer:
|
|
28
27
|
* Install the `jcmoptimizer` package into your python environmentm, e.g. by runing `pip install jcmoptimizer`.
|
|
28
|
+
* Signup to [optimizer.jcmwave.com](https://optimizer.jcmwave.com).
|
|
29
|
+
* Create an [API access token](https://optimizer.jcmwave.com/cloud/tokens/list/)
|
|
30
|
+
|
|
31
|
+
### Run JCMoptimizer Locally
|
|
32
|
+
For a self-hosted version, you need install JCMsuite and the JCMoptimizer extension for JCMsuite:
|
|
33
|
+
* Browse to the [JCMsuite download](https://installation.jcmwave.com/fb13885794fc64c42531c9656f8f6f73.php) page to install JCMsuite and to retrieve a demo or commercial license.
|
|
34
|
+
* Browse to the [JCMoptimizer download](https://optimizer.jcmwave.com/download) page to install JCMoptimzizer.
|
|
29
35
|
|
|
36
|
+
### First Example
|
|
30
37
|
A basic usage example of the python package is given in the tutorial on a [Bayesian Optimization](https://optimizer.jcmwave.com/documentation/python/latest/tutorials/vanilla_bayesian_optimization.html).
|
|
31
38
|
|
|
32
|
-
## Documentation
|
|
39
|
+
## Documentation
|
|
33
40
|
|
|
34
41
|
For a comprehensive overview of all available classes, methods, and their parameters, please refer to the official JCMoptimizer Python documentation:
|
|
35
42
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '3.0.0'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|