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.
Files changed (23) hide show
  1. {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/PKG-INFO +13 -6
  2. {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/README.md +12 -5
  3. {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer/client.py +95 -22
  4. {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer/requestor.py +13 -5
  5. {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer/server.py +70 -49
  6. {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer/study.py +32 -5
  7. jcmoptimizer-3.0.2/jcmoptimizer/version.py +1 -0
  8. {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer.egg-info/PKG-INFO +13 -6
  9. jcmoptimizer-3.0.0/jcmoptimizer/version.py +0 -1
  10. {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/LICENSE +0 -0
  11. {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer/__init__.py +0 -0
  12. {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer/benchmark.py +0 -0
  13. {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer/drivers/__init__.py +0 -0
  14. {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer/drivers/active_learning_drivers.py +0 -0
  15. {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer/drivers/driver.py +0 -0
  16. {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer/drivers/third_party_drivers.py +0 -0
  17. {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer/objects.py +0 -0
  18. {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer.egg-info/SOURCES.txt +0 -0
  19. {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer.egg-info/dependency_links.txt +0 -0
  20. {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer.egg-info/requires.txt +0 -0
  21. {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/jcmoptimizer.egg-info/top_level.txt +0 -0
  22. {jcmoptimizer-3.0.0 → jcmoptimizer-3.0.2}/pyproject.toml +0 -0
  23. {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.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
- You need install JCMsuite and the JCMoptimizer extension for JCMsuite:
26
- * Browse to the [JCMsuite download](https://installation.jcmwave.com/fb13885794fc64c42531c9656f8f6f73.php) page to install JCMsuite and to retrieve a demo or commercial license.
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
- You need install JCMsuite and the JCMoptimizer extension for JCMsuite:
8
- * Browse to the [JCMsuite download](https://installation.jcmwave.com/fb13885794fc64c42531c9656f8f6f73.php) page to install JCMsuite and to retrieve a demo or commercial license.
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
- # get host from cloud api
64
- if server_id is None:
65
- raise ValueError("Either 'host' or 'server_id' must be set")
66
-
67
- if token is None:
68
- raise ValueError("Argument 'token' must be set if 'host' is unkown")
69
-
70
- cloud_requestor = CloudRequestor(cloud_endpoint, token)
71
- r = cloud_requestor.request("GET", "get server url", f"servers/{server_id}/")
72
- data = r.json()
73
- host = data["url"]
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['dashboard_path']
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"\n{response._content[:1000]!r}"
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"] | None = None,
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 not None:
198
- if not isinstance(shutdown_at, datetime):
199
- raise ValueError(
200
- f"'shutdown_at' must be a {type(datetime)} object. "
201
- f"Got {shutdown_at} of type {type(shutdown_at)}."
202
- )
203
- shutdown_at_str = shutdown_at.astimezone().isoformat()
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
- prev_state_key: Optional[str] = None
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
- return self._requestor.post(
84
- purpose, object, operation, self.qualifier, data
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
- return self._requestor.get(purpose, object, type, self.qualifier)
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.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
- You need install JCMsuite and the JCMoptimizer extension for JCMsuite:
26
- * Browse to the [JCMsuite download](https://installation.jcmwave.com/fb13885794fc64c42531c9656f8f6f73.php) page to install JCMsuite and to retrieve a demo or commercial license.
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