jcmoptimizer 3.0.9__tar.gz → 3.1.0__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 (25) hide show
  1. {jcmoptimizer-3.0.9 → jcmoptimizer-3.1.0}/PKG-INFO +1 -1
  2. {jcmoptimizer-3.0.9 → jcmoptimizer-3.1.0}/jcmoptimizer/benchmark.py +0 -1
  3. {jcmoptimizer-3.0.9 → jcmoptimizer-3.1.0}/jcmoptimizer/client.py +1 -1
  4. {jcmoptimizer-3.0.9 → jcmoptimizer-3.1.0}/jcmoptimizer/drivers/__init__.py +0 -0
  5. {jcmoptimizer-3.0.9 → jcmoptimizer-3.1.0}/jcmoptimizer/drivers/active_learning_drivers.py +47 -1
  6. {jcmoptimizer-3.0.9 → jcmoptimizer-3.1.0}/jcmoptimizer/drivers/driver.py +12 -32
  7. {jcmoptimizer-3.0.9 → jcmoptimizer-3.1.0}/jcmoptimizer/objects.py +0 -0
  8. {jcmoptimizer-3.0.9 → jcmoptimizer-3.1.0}/jcmoptimizer/requestor.py +43 -3
  9. {jcmoptimizer-3.0.9 → jcmoptimizer-3.1.0}/jcmoptimizer/server.py +39 -38
  10. {jcmoptimizer-3.0.9 → jcmoptimizer-3.1.0}/jcmoptimizer/study.py +13 -3
  11. jcmoptimizer-3.1.0/jcmoptimizer/utils/__init__.py +5 -0
  12. jcmoptimizer-3.1.0/jcmoptimizer/utils/neural_network.py +142 -0
  13. jcmoptimizer-3.1.0/jcmoptimizer/version.py +1 -0
  14. {jcmoptimizer-3.0.9 → jcmoptimizer-3.1.0}/jcmoptimizer.egg-info/PKG-INFO +1 -1
  15. {jcmoptimizer-3.0.9 → jcmoptimizer-3.1.0}/jcmoptimizer.egg-info/SOURCES.txt +3 -1
  16. jcmoptimizer-3.0.9/jcmoptimizer/version.py +0 -1
  17. {jcmoptimizer-3.0.9 → jcmoptimizer-3.1.0}/LICENSE +0 -0
  18. {jcmoptimizer-3.0.9 → jcmoptimizer-3.1.0}/README.md +0 -0
  19. {jcmoptimizer-3.0.9 → jcmoptimizer-3.1.0}/jcmoptimizer/__init__.py +0 -0
  20. {jcmoptimizer-3.0.9 → jcmoptimizer-3.1.0}/jcmoptimizer/drivers/third_party_drivers.py +0 -0
  21. {jcmoptimizer-3.0.9 → jcmoptimizer-3.1.0}/jcmoptimizer.egg-info/dependency_links.txt +0 -0
  22. {jcmoptimizer-3.0.9 → jcmoptimizer-3.1.0}/jcmoptimizer.egg-info/requires.txt +0 -0
  23. {jcmoptimizer-3.0.9 → jcmoptimizer-3.1.0}/jcmoptimizer.egg-info/top_level.txt +0 -0
  24. {jcmoptimizer-3.0.9 → jcmoptimizer-3.1.0}/pyproject.toml +0 -0
  25. {jcmoptimizer-3.0.9 → jcmoptimizer-3.1.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jcmoptimizer
3
- Version: 3.0.9
3
+ Version: 3.1.0
4
4
  Summary: The JCMoptimizer interface for Python
5
5
  Author-email: Philipp Schneider <philipp.schneider@jcmwave.com>
6
6
  License-Expression: MIT
@@ -1,7 +1,6 @@
1
1
  from typing import Any, Callable, Literal, Optional, Union
2
2
  import time
3
3
  import datetime as dt
4
- import requests
5
4
  import atexit
6
5
 
7
6
  from .requestor import OptimizerRequestor, warn, inform
@@ -242,7 +242,7 @@ class Client:
242
242
 
243
243
  :name: Name of the parameter. E.g. ``'x1'``. The name should contain
244
244
  no spaces and must not be equal to function names or mathematical
245
- constants like ``'sin'``, ``'cos'``, ``'exp'``, ``'pi'`` etc.
245
+ constants like ``'sin'``, ``'cos'``, ``'exp'``, ``'PI'`` etc.
246
246
  :type: Type of the parameter. Either ``'variable'`` or ``'fixed'``.
247
247
  Fixed parameters can be used in the constraint functions or other
248
248
  expressions.
@@ -1,10 +1,29 @@
1
1
  from typing import Any, Optional, Literal
2
- import requests
2
+ import base64
3
3
 
4
4
  from .driver import Driver, Minimizer, LeastSquaresDriver
5
5
  from ..requestor import OptimizerRequestor
6
6
 
7
7
  class ActiveLearningDriver(Driver):
8
+ def download_neural_network(self, path: str, filename: str) -> None:
9
+ """Download a trained neural network ensemble artifact.
10
+
11
+ Args:
12
+ path: Dot-separated driver path to the neural network surrogate,
13
+ for example ``"surrogates.0"``.
14
+ filename: Target zip artifact path. The downloaded artifact can be
15
+ loaded with
16
+ :meth:`jcmoptimizer.utils.NeuralNetworkEnsemblePredictor.from_file`.
17
+ """
18
+ answer = self._post(
19
+ "download neural network",
20
+ "driver",
21
+ "export_neural_network",
22
+ data={"path": path},
23
+ )
24
+ with open(filename, "wb") as f:
25
+ f.write(base64.b64decode(answer["artifact"]))
26
+
8
27
  def describe(self) -> dict[str, Any]:
9
28
  """Get description of all modules and their parameters that are used
10
29
  by the driver. Example::
@@ -182,6 +201,11 @@ class ActiveLearningDriver(Driver):
182
201
  used for training the surrogate are neglected if their distance
183
202
  is smaller than min_dist (in terms of the length scales of the
184
203
  surrogate).
204
+
205
+ .. note:: This method only optimizes hyperparameters. If a surrogate
206
+ needs to be prepared or a neural network needs to be trained after
207
+ the hyperparameter update, call :func:`train` explicitly. The train
208
+ task saves the processed driver state.
185
209
  """
186
210
  self._run_task(
187
211
  "optimize hyperparameters",
@@ -189,6 +213,17 @@ class ActiveLearningDriver(Driver):
189
213
  data={"path": path, "num_samples": num_samples, "min_dist": min_dist},
190
214
  )
191
215
 
216
+ def train(self, path: Optional[str] = None) -> None:
217
+ """Prepare/train surrogate models explicitly. Example::
218
+
219
+ driver.train("driver.surrogates.0")
220
+
221
+ Args:
222
+ path: A dot-separated path to a surrogate. If no path is specified,
223
+ all surrogates are trained/prepared.
224
+ """
225
+ self._run_task("train", "train_surrogates", data={"path": path})
226
+
192
227
  def get_minima(
193
228
  self,
194
229
  object_type: Literal["surrogate", "variable", "objective"] = "objective",
@@ -822,6 +857,17 @@ class BayesianLeastSquares(SingleGPSingleVariable, LeastSquaresDriver):
822
857
  )
823
858
  return answer["state"]
824
859
 
860
+ @property
861
+ def covariance(self) -> list[list[float]]:
862
+ """Covariance matrix of the reconstructed continuous parameters."""
863
+ answer = self._post(
864
+ "get parameter covariance",
865
+ "driver",
866
+ "get_state",
867
+ data={"path": "variables.0.parameter_covariance"},
868
+ )
869
+ return answer["state"]
870
+
825
871
  def predict(
826
872
  self,
827
873
  points: list[list[float]],
@@ -1,6 +1,4 @@
1
1
  from typing import Any, Optional, Union
2
- import requests
3
- import time
4
2
 
5
3
  from ..requestor import OptimizerRequestor
6
4
 
@@ -45,35 +43,7 @@ class Driver:
45
43
  return self._requestor.get(purpose, object, type, self.qualifier)
46
44
 
47
45
  def _run_task(self, purpose: str, task: str, data: dict[str, Any]) -> Any:
48
- data["task"] = task
49
- answer = self._post(purpose, "driver", "start_task", data=data)
50
-
51
- print("\r" + purpose[0].capitalize() + purpose[1:] + ": ", end="")
52
- task_id = answer["task_id"]
53
- try:
54
- while True:
55
- answer = self._post(
56
- purpose, "driver", "get_task_status", data={"task_id": task_id}
57
- )
58
-
59
- progress_msg = answer["progress_msg"]
60
- status = answer["status"]
61
- print("\r" + progress_msg + " ", end="")
62
-
63
- if status in ["stopped", "finished"]:
64
- print("")
65
- break
66
- time.sleep(1)
67
-
68
- except KeyboardInterrupt:
69
- answer = self._post(
70
- purpose, "driver", "stop_task", data={"task_id": task_id}
71
- )
72
-
73
- answer = self._post(
74
- purpose, "driver", "fetch_task_result", data={"task_id": task_id}
75
- )
76
- return answer["result"]
46
+ return self._requestor.run_task(purpose, task, self.qualifier, data)
77
47
 
78
48
  def describe(self) -> dict[str, Any]:
79
49
  """Get description of all modules and their parameters that are used
@@ -129,7 +99,6 @@ class Driver:
129
99
  )
130
100
  return answer["values"]
131
101
 
132
-
133
102
  class Minimizer(Driver):
134
103
  """
135
104
  This class provides methods for retrieving and setting information of the
@@ -210,3 +179,14 @@ class LeastSquaresDriver(Minimizer):
210
179
  data={"path": "parameter_uncertainties"},
211
180
  )
212
181
  return answer["state"]
182
+
183
+ @property
184
+ def covariance(self) -> list[list[float]]:
185
+ """Covariance matrix of the reconstructed continuous parameters."""
186
+ answer = self._post(
187
+ "get parameter covariance",
188
+ "driver",
189
+ "get_state",
190
+ data={"path": "parameter_covariance"},
191
+ )
192
+ return answer["state"]
@@ -6,7 +6,6 @@ from datetime import datetime as dt
6
6
  from datetime import timedelta
7
7
  import json
8
8
  import itertools
9
- import threading
10
9
  import time
11
10
 
12
11
  template = 'Please install the package {p} (e.g. run "pip install {p}") .'
@@ -141,7 +140,7 @@ def parse_yaml(f: TextIO) -> dict[str, Any]:
141
140
  "Expected format: key:value\n"
142
141
  f"Found format: {line}"
143
142
  )
144
- except SyntaxError as e:
143
+ except SyntaxError:
145
144
  raise SyntaxError(
146
145
  f"Invalid format of config file "
147
146
  f"at {f.name}:{line_num + 1}.\n"
@@ -206,7 +205,7 @@ class Requestor:
206
205
  else:
207
206
  raise NotImplementedError
208
207
 
209
- except requests.exceptions.ConnectionError as e:
208
+ except requests.exceptions.ConnectionError:
210
209
  raise ConnectionError(
211
210
  f"Could not connect to server at URL {self.endpoint}. "
212
211
  "Please, check your internet connection and that the server is running."
@@ -366,3 +365,44 @@ class OptimizerRequestor(Requestor):
366
365
  r = self.request("POST", purpose, url, data=data)
367
366
  self._print_messages(r)
368
367
  return r.json()
368
+
369
+ def run_task(
370
+ self,
371
+ purpose: str,
372
+ task: str,
373
+ id: str,
374
+ data: dict[str, Any],
375
+ ) -> Any:
376
+ data["task"] = task
377
+ answer = self.post(purpose, "driver", "start_task", id=id, data=data)
378
+
379
+ print("\r" + purpose[0].capitalize() + purpose[1:] + ": ", end="")
380
+ task_id = answer["task_id"]
381
+ try:
382
+ while True:
383
+ answer = self.post(
384
+ purpose,
385
+ "driver",
386
+ "get_task_status",
387
+ id=id,
388
+ data={"task_id": task_id},
389
+ )
390
+
391
+ progress_msg = answer["progress_msg"]
392
+ status = answer["status"]
393
+ print("\r" + progress_msg + " ", end="")
394
+
395
+ if status in ["stopped", "finished"]:
396
+ print("")
397
+ break
398
+ time.sleep(1)
399
+
400
+ except KeyboardInterrupt:
401
+ self.post(
402
+ purpose, "driver", "stop_task", id=id, data={"task_id": task_id}
403
+ )
404
+
405
+ answer = self.post(
406
+ purpose, "driver", "fetch_task_result", id=id, data={"task_id": task_id}
407
+ )
408
+ return answer["result"]
@@ -1,5 +1,4 @@
1
- from typing import Any, Optional, Union, Callable, Literal, TextIO
2
- from io import BufferedRandom
1
+ from typing import Any, Optional, Literal
3
2
  import os
4
3
  import sys
5
4
  import json
@@ -8,11 +7,9 @@ import time
8
7
  import subprocess
9
8
  from datetime import datetime, timedelta
10
9
  import atexit
11
- import requests
12
10
 
13
11
  from .requestor import (
14
12
  read_yml_config_files,
15
- warn,
16
13
  inform,
17
14
  CloudRequestor,
18
15
  show_working,
@@ -65,7 +62,7 @@ def _wait_for_server_startup(
65
62
  t0 = time.time()
66
63
  while time.time() - t0 < STARTUP_TIMEOUT:
67
64
  try:
68
- client = Client(
65
+ Client(
69
66
  host=host,
70
67
  verbose=False,
71
68
  cloud_endpoint=cloud_endpoint,
@@ -343,7 +340,7 @@ class Server:
343
340
  def _shutdown_at_exit(self) -> None:
344
341
  try:
345
342
  self.shutdown(force=True)
346
- except ConnectionError as err:
343
+ except ConnectionError:
347
344
  pass
348
345
 
349
346
  def _start_local_server(
@@ -389,12 +386,12 @@ class Server:
389
386
  env["JCM_LICENSE_DIR"] = license_dir
390
387
 
391
388
  # Start JCMoptimizer
392
- cmd: list[str] = [f'"{jcmoptimizer_exe}"']
389
+ cmd = [jcmoptimizer_exe]
393
390
  if port is not None:
394
- cmd.append(f"--port {port}")
391
+ cmd.extend(["--port", str(port)])
395
392
  cmd.append("--print_json")
396
393
  if not persistent:
397
- cmd.append(f"--calling_pid {os.getpid()}")
394
+ cmd.extend(["--calling_pid", str(os.getpid())])
398
395
  close_fds = os.name != "nt"
399
396
 
400
397
  # Generate temporary files for errors and log
@@ -402,9 +399,8 @@ class Server:
402
399
  tempfile.TemporaryFile() as error_file,
403
400
  tempfile.TemporaryFile() as log_file,
404
401
  ):
405
- Popen(
406
- " ".join(cmd),
407
- shell=True,
402
+ server_proc = Popen(
403
+ cmd,
408
404
  stdout=log_file,
409
405
  stderr=error_file,
410
406
  close_fds=close_fds,
@@ -414,36 +410,41 @@ class Server:
414
410
  env=env,
415
411
  )
416
412
 
417
- # Poll process for new output until first line with port information
418
- line = b""
419
- for _ in range(round(10 * timeout)):
420
- error_file.seek(0)
421
- if len(error_file.readlines()):
413
+ try:
414
+ # Poll process for new output until first line with port information
415
+ line = b""
416
+ for _ in range(round(10 * timeout)):
417
+ error_file.seek(0)
418
+ if len(error_file.readlines()):
419
+ response = _server_response(log_file, error_file)
420
+ raise EnvironmentError(
421
+ "Could not start optimization server. "
422
+ f"Server response: \n{response}"
423
+ )
424
+
425
+ log_file.seek(0)
426
+ for line in iter(log_file.readline, b""):
427
+ if line[:17] == b'{"optimizer_port"':
428
+ break
429
+ else:
430
+ time.sleep(0.1)
431
+ continue
432
+ break
433
+ else:
422
434
  response = _server_response(log_file, error_file)
435
+ raise TimeoutError(response)
436
+ response = _server_response(log_file, error_file)
437
+ try:
438
+ info = json.loads(line)
439
+ except Exception as err:
423
440
  raise EnvironmentError(
424
441
  "Could not start optimization server. "
425
442
  f"Server response: \n{response}"
426
- )
427
-
428
- log_file.seek(0)
429
- for line in iter(log_file.readline, b""):
430
- if line[:17] == b'{"optimizer_port"':
431
- break
432
- else:
433
- time.sleep(0.1)
434
- continue
435
- break
436
- else:
437
- response = _server_response(log_file, error_file)
438
- raise TimeoutError(response)
439
- try:
440
- info = json.loads(line)
441
- except Exception as err:
442
- response = _server_response(log_file, error_file)
443
- raise EnvironmentError(
444
- "Could not start optimization server. "
445
- f"Server response: \n{response}"
446
- ) from err
443
+ ) from err
444
+ except BaseException:
445
+ server_proc.kill()
446
+ server_proc.wait(timeout=5)
447
+ raise
447
448
 
448
449
  self._port = int(info["optimizer_port"])
449
450
  self._pid = int(info["pid"])
@@ -1,10 +1,8 @@
1
1
  from typing import Any, Callable, Optional, Union
2
2
  import time
3
3
  import threading
4
- import warnings
5
4
  import traceback
6
5
  import atexit
7
- import requests
8
6
  from datetime import datetime, timedelta
9
7
 
10
8
  from .requestor import OptimizerRequestor, NumParallelError, warn
@@ -90,6 +88,11 @@ class Study:
90
88
  self._extend_shutdown_time(self._extend_on_suggestion)
91
89
  return result
92
90
 
91
+ def _run_task(self, purpose: str, task: str, data: dict[str, Any]) -> Any:
92
+ result = self._requestor.run_task(purpose, task, self.qualifier, data)
93
+ self._extend_shutdown_time(self._extend_on_request)
94
+ return result
95
+
93
96
  def _get(self, purpose: str, object: str, type: str) -> dict[str, Any]:
94
97
  result = self._requestor.get(purpose, object, type, self.qualifier)
95
98
  self._extend_shutdown_time(self._extend_on_request)
@@ -294,6 +297,7 @@ class Study:
294
297
  """
295
298
  observations: list[Observation] = []
296
299
  design_values: list[list[Union[float, str]]] = []
300
+ obs = self.new_observation()
297
301
  last_design_value = None
298
302
  for idx in range(len(data["value"])):
299
303
  design_value = data["design_value"][idx]
@@ -528,6 +532,13 @@ class Study:
528
532
  shown on the dashboard. To avoid this, one has to add the observations
529
533
  one by one using :func:`add_observation`.
530
534
 
535
+ .. note:: After adding many observations, we recommend explicitly running
536
+ :meth:`jcmoptimizer.ActiveLearning.optimize_hyperparameters` if desired,
537
+ followed by :meth:`jcmoptimizer.ActiveLearning.train`. This keeps the
538
+ bulk-import driver state clean
539
+ and makes the expensive processing steps and processed-state saving
540
+ explicit.
541
+
531
542
  """
532
543
  obs_data = []
533
544
  for o in observations:
@@ -535,7 +546,6 @@ class Study:
535
546
  raise TypeError("observations -> expected Observation objects.")
536
547
  obs_data.append(o.data)
537
548
 
538
- # todo: check
539
549
  self._post(
540
550
  "add observations",
541
551
  "observation",
@@ -0,0 +1,5 @@
1
+ """Utility classes for specialized client workflows."""
2
+
3
+ from .neural_network import NeuralNetworkEnsemblePredictor
4
+
5
+ __all__ = ["NeuralNetworkEnsemblePredictor"]
@@ -0,0 +1,142 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any, Optional, Union
4
+ import io
5
+ import json
6
+ import zipfile
7
+
8
+ if TYPE_CHECKING:
9
+ import torch
10
+
11
+
12
+ class NeuralNetworkEnsemblePredictor:
13
+ """Client-side predictor for a downloaded neural network ensemble.
14
+
15
+ Use :meth:`from_file` to load an artifact created with
16
+ :meth:`jcmoptimizer.ActiveLearning.download_neural_network`. Predictions are
17
+ evaluated locally with PyTorch and return one prediction per ensemble
18
+ member.
19
+ """
20
+
21
+ def __init__(
22
+ self,
23
+ metadata: dict[str, Any],
24
+ member_model_bytes: bytes,
25
+ member_state_bytes: bytes,
26
+ device: Optional[Union[str, torch.device]] = None,
27
+ ) -> None:
28
+ """Create a predictor from loaded artifact metadata and model bytes.
29
+
30
+ Args:
31
+ metadata: Artifact metadata read from ``metadata.json``.
32
+ member_model_bytes: Serialized exported PyTorch member model.
33
+ member_state_bytes: Serialized stacked ensemble member state.
34
+ device: PyTorch device for prediction. If omitted, CUDA is used when
35
+ available and CPU otherwise.
36
+ """
37
+ if metadata.get("format") != "jcmoptimizer_neural_network_ensemble":
38
+ raise ValueError(
39
+ f"Unsupported neural network artifact format {metadata.get('format')!r}."
40
+ )
41
+ if metadata.get("artifact_type") != "torch_export_member_vmap":
42
+ raise ValueError(
43
+ "Unsupported neural network artifact type "
44
+ f"{metadata.get('artifact_type')!r}."
45
+ )
46
+
47
+ try:
48
+ import torch
49
+ from torch.func import functional_call, vmap
50
+ except ImportError as error:
51
+ raise ImportError(
52
+ "NeuralNetworkEnsemblePredictor requires PyTorch. "
53
+ "Install the optional torch dependency to use it."
54
+ ) from error
55
+
56
+ self.metadata = metadata
57
+ self._torch = torch
58
+ self.device = torch.device(
59
+ device if device is not None else "cuda" if torch.cuda.is_available() else "cpu"
60
+ )
61
+ exported_program = torch.export.load(io.BytesIO(member_model_bytes))
62
+ self.base_model = exported_program.module().to(self.device)
63
+ stacked_state = torch.load(
64
+ io.BytesIO(member_state_bytes), map_location=self.device, weights_only=True
65
+ )
66
+ parameter_names = {name for name, _ in self.base_model.named_parameters()}
67
+ buffer_names = {name for name, _ in self.base_model.named_buffers()}
68
+ self.params = {
69
+ name: value.to(self.device)
70
+ for name, value in stacked_state.items()
71
+ if name in parameter_names
72
+ }
73
+ self.buffers = {
74
+ name: value.to(self.device)
75
+ for name, value in stacked_state.items()
76
+ if name in buffer_names
77
+ }
78
+ self.dtype = self._infer_dtype(stacked_state)
79
+ self.num_input = int(metadata["num_input"])
80
+
81
+ def predict_member(
82
+ params: dict[str, torch.Tensor],
83
+ buffers: dict[str, torch.Tensor],
84
+ x: torch.Tensor,
85
+ ) -> torch.Tensor:
86
+ return functional_call(self.base_model, (params, buffers), (x,))
87
+
88
+ self._vmap = vmap(predict_member, in_dims=(0, 0, None))
89
+
90
+ @classmethod
91
+ def from_file(
92
+ cls,
93
+ filename: str,
94
+ device: Optional[Union[str, torch.device]] = None,
95
+ ) -> "NeuralNetworkEnsemblePredictor":
96
+ """Load a downloaded neural network ensemble artifact.
97
+
98
+ Args:
99
+ filename: Path to the zip artifact written by
100
+ :meth:`jcmoptimizer.ActiveLearning.download_neural_network`.
101
+ device: PyTorch device for prediction. If omitted, CUDA is used when
102
+ available and CPU otherwise.
103
+
104
+ Returns:
105
+ A predictor that evaluates the ensemble locally.
106
+ """
107
+ with zipfile.ZipFile(filename, "r") as zf:
108
+ metadata = json.loads(zf.read("metadata.json").decode("utf-8"))
109
+ member_model_bytes = zf.read(metadata["member_model_file"])
110
+ member_state_bytes = zf.read(metadata["member_state_file"])
111
+ return cls(metadata, member_model_bytes, member_state_bytes, device=device)
112
+
113
+ def _infer_dtype(self, stacked_state: dict[str, torch.Tensor]) -> torch.dtype:
114
+ for value in stacked_state.values():
115
+ if self._torch.is_tensor(value) and value.is_floating_point():
116
+ return value.dtype
117
+ return self._torch.get_default_dtype()
118
+
119
+ def predict(self, x: Union[torch.Tensor, list[list[float]]]) -> torch.Tensor:
120
+ """Predict for one or more input points.
121
+
122
+ Args:
123
+ x: Input points with shape ``(num_points, num_input)``. For
124
+ one-dimensional input spaces, a one-dimensional sequence is also
125
+ accepted and interpreted as multiple points.
126
+
127
+ Returns:
128
+ Tensor with shape ``(num_models, num_points, num_output)``.
129
+ """
130
+ x_tensor = self._torch.as_tensor(x, dtype=self.dtype, device=self.device)
131
+ if x_tensor.ndim == 1:
132
+ if self.num_input != 1:
133
+ raise ValueError(
134
+ f"Expected input with {self.num_input} columns, got a 1D input."
135
+ )
136
+ x_tensor = x_tensor.reshape(-1, 1)
137
+ if x_tensor.ndim != 2 or x_tensor.shape[1] != self.num_input:
138
+ raise ValueError(
139
+ f"Expected input of shape (num_points, {self.num_input}), "
140
+ f"got {tuple(x_tensor.shape)}."
141
+ )
142
+ return self._vmap(self.params, self.buffers, x_tensor)
@@ -0,0 +1 @@
1
+ __version__ = '3.1.0'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jcmoptimizer
3
- Version: 3.0.9
3
+ Version: 3.1.0
4
4
  Summary: The JCMoptimizer interface for Python
5
5
  Author-email: Philipp Schneider <philipp.schneider@jcmwave.com>
6
6
  License-Expression: MIT
@@ -17,4 +17,6 @@ jcmoptimizer.egg-info/top_level.txt
17
17
  jcmoptimizer/drivers/__init__.py
18
18
  jcmoptimizer/drivers/active_learning_drivers.py
19
19
  jcmoptimizer/drivers/driver.py
20
- jcmoptimizer/drivers/third_party_drivers.py
20
+ jcmoptimizer/drivers/third_party_drivers.py
21
+ jcmoptimizer/utils/__init__.py
22
+ jcmoptimizer/utils/neural_network.py
@@ -1 +0,0 @@
1
- __version__ = '3.0.9'
File without changes
File without changes
File without changes