wavesimpro 0.9.1__tar.gz → 0.9.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.
- {wavesimpro-0.9.1 → wavesimpro-0.9.2}/PKG-INFO +1 -1
- {wavesimpro-0.9.1 → wavesimpro-0.9.2}/pyproject.toml +1 -1
- {wavesimpro-0.9.1 → wavesimpro-0.9.2}/src/wavesimpro/__init__.py +2 -2
- {wavesimpro-0.9.1 → wavesimpro-0.9.2}/src/wavesimpro/client.py +5 -1
- {wavesimpro-0.9.1 → wavesimpro-0.9.2}/src/wavesimpro/execute.py +12 -5
- {wavesimpro-0.9.1 → wavesimpro-0.9.2}/src/wavesimpro/json_helper.py +26 -2
- {wavesimpro-0.9.1 → wavesimpro-0.9.2}/src/wavesimpro/simulate.py +129 -6
- {wavesimpro-0.9.1 → wavesimpro-0.9.2}/src/wavesimpro.egg-info/PKG-INFO +1 -1
- {wavesimpro-0.9.1 → wavesimpro-0.9.2}/setup.cfg +0 -0
- {wavesimpro-0.9.1 → wavesimpro-0.9.2}/src/wavesimpro/__main__.py +0 -0
- {wavesimpro-0.9.1 → wavesimpro-0.9.2}/src/wavesimpro/binary_utils.py +0 -0
- {wavesimpro-0.9.1 → wavesimpro-0.9.2}/src/wavesimpro/config.py +0 -0
- {wavesimpro-0.9.1 → wavesimpro-0.9.2}/src/wavesimpro/exceptions.py +0 -0
- {wavesimpro-0.9.1 → wavesimpro-0.9.2}/src/wavesimpro/py.typed +0 -0
- {wavesimpro-0.9.1 → wavesimpro-0.9.2}/src/wavesimpro/setup.py +0 -0
- {wavesimpro-0.9.1 → wavesimpro-0.9.2}/src/wavesimpro/validate.py +0 -0
- {wavesimpro-0.9.1 → wavesimpro-0.9.2}/src/wavesimpro.egg-info/SOURCES.txt +0 -0
- {wavesimpro-0.9.1 → wavesimpro-0.9.2}/src/wavesimpro.egg-info/dependency_links.txt +0 -0
- {wavesimpro-0.9.1 → wavesimpro-0.9.2}/src/wavesimpro.egg-info/requires.txt +0 -0
- {wavesimpro-0.9.1 → wavesimpro-0.9.2}/src/wavesimpro.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "wavesimpro"
|
|
7
|
-
version = "0.9.
|
|
7
|
+
version = "0.9.2"
|
|
8
8
|
description = "WavesimPro Python API — cloud execution client for the Rayfos simulation platform"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -30,7 +30,7 @@ from .binary_utils import (
|
|
|
30
30
|
)
|
|
31
31
|
from .validate import validate_parameters, ParameterValidationError
|
|
32
32
|
from .execute import execute_via_api
|
|
33
|
-
from .simulate import simulate, configure
|
|
33
|
+
from .simulate import simulate, simulate_pro, configure
|
|
34
34
|
from .json_helper import (
|
|
35
35
|
create_simulation_domain,
|
|
36
36
|
create_stl_primitive,
|
|
@@ -49,7 +49,7 @@ from .json_helper import (
|
|
|
49
49
|
parse_progress_data,
|
|
50
50
|
)
|
|
51
51
|
|
|
52
|
-
__version__ = "0.9.
|
|
52
|
+
__version__ = "0.9.2"
|
|
53
53
|
__all__ = [
|
|
54
54
|
# Client
|
|
55
55
|
"RayfosClient",
|
|
@@ -896,6 +896,7 @@ class RayfosClient:
|
|
|
896
896
|
version_id: str,
|
|
897
897
|
engine_preference: str = 'Any',
|
|
898
898
|
use_gpu: Optional[bool] = None,
|
|
899
|
+
preferred_machine_id: Optional[str] = None,
|
|
899
900
|
) -> Dict[str, Any]:
|
|
900
901
|
"""
|
|
901
902
|
Create a simulation command for a project version.
|
|
@@ -927,7 +928,7 @@ class RayfosClient:
|
|
|
927
928
|
if self.config.machine_service_url:
|
|
928
929
|
return self._create_command_machine_service(project_id, version_id)
|
|
929
930
|
else:
|
|
930
|
-
return self._create_command_cloud(project_id, version_id, engine_preference, use_gpu)
|
|
931
|
+
return self._create_command_cloud(project_id, version_id, engine_preference, use_gpu, preferred_machine_id)
|
|
931
932
|
|
|
932
933
|
def _create_command_machine_service(
|
|
933
934
|
self,
|
|
@@ -956,6 +957,7 @@ class RayfosClient:
|
|
|
956
957
|
version_id: str,
|
|
957
958
|
engine_preference: str = 'Any',
|
|
958
959
|
use_gpu: Optional[bool] = None,
|
|
960
|
+
preferred_machine_id: Optional[str] = None,
|
|
959
961
|
) -> Dict[str, Any]:
|
|
960
962
|
"""Create command via RayfosJobServer (cloud mode)"""
|
|
961
963
|
data: Dict[str, Any] = {}
|
|
@@ -963,6 +965,8 @@ class RayfosClient:
|
|
|
963
965
|
data['enginePreference'] = engine_preference
|
|
964
966
|
if use_gpu is not None:
|
|
965
967
|
data['useGpu'] = use_gpu
|
|
968
|
+
if preferred_machine_id:
|
|
969
|
+
data['preferredMachineServiceId'] = preferred_machine_id
|
|
966
970
|
return self._post(f"/api/projects/{project_id}/versions/{version_id}/command", json=data)
|
|
967
971
|
|
|
968
972
|
# ========================================================================
|
|
@@ -63,6 +63,9 @@ def execute_via_api(
|
|
|
63
63
|
progress_callback: Optional[Callable[[Dict[str, Any]], None]] = None,
|
|
64
64
|
engine_preference: str = "Any",
|
|
65
65
|
use_gpu: Optional[bool] = None,
|
|
66
|
+
preferred_machine_id: Optional[str] = None,
|
|
67
|
+
alpha: float = 0.75,
|
|
68
|
+
keep_residuals_list: bool = False,
|
|
66
69
|
) -> Tuple[np.ndarray, bool]:
|
|
67
70
|
"""
|
|
68
71
|
Execute a Wavesim simulation via the Rayfos REST API.
|
|
@@ -311,6 +314,8 @@ def execute_via_api(
|
|
|
311
314
|
periodic_x=bool(periodic[0]),
|
|
312
315
|
periodic_y=bool(periodic[1]),
|
|
313
316
|
periodic_z=bool(periodic[2]),
|
|
317
|
+
alpha=float(alpha),
|
|
318
|
+
keep_residuals_list=bool(keep_residuals_list),
|
|
314
319
|
)
|
|
315
320
|
|
|
316
321
|
logger.info(f" Payload created with {len(sources_info)} source(s)")
|
|
@@ -321,7 +326,7 @@ def execute_via_api(
|
|
|
321
326
|
|
|
322
327
|
# 12. Submit command
|
|
323
328
|
logger.info("Submitting simulation command...")
|
|
324
|
-
cmd_status = client.create_command(project_id, version_id, engine_preference, use_gpu)
|
|
329
|
+
cmd_status = client.create_command(project_id, version_id, engine_preference, use_gpu, preferred_machine_id)
|
|
325
330
|
command_id = cmd_status.get("commandId") or cmd_status.get("id")
|
|
326
331
|
if not command_id:
|
|
327
332
|
raise RuntimeError("No command ID found in response")
|
|
@@ -342,6 +347,8 @@ def execute_via_api(
|
|
|
342
347
|
print(f" Mode : {_mode}")
|
|
343
348
|
print(f" Domain : {_domain_str} λ={wavelength} µm Δx={pixel_size} µm")
|
|
344
349
|
print(f" Engine : {_engine} | GPU: {_gpu}")
|
|
350
|
+
if preferred_machine_id:
|
|
351
|
+
print(f" Machine : {preferred_machine_id}")
|
|
345
352
|
print(f" Threshold : {threshold:.1e} max iter: {max_iterations}")
|
|
346
353
|
print(f" {'─'*54}")
|
|
347
354
|
start_time = time.time()
|
|
@@ -513,10 +520,10 @@ def execute_via_api(
|
|
|
513
520
|
|
|
514
521
|
E = read_binary_field(output_data, dims, "Complex64")
|
|
515
522
|
|
|
516
|
-
# Squeeze trailing size-1 spatial dimensions
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
523
|
+
# Squeeze all trailing size-1 spatial dimensions to match local wavesim.simulate() output.
|
|
524
|
+
# Local always squeezes: [nx,1,1]→(nx,), [3,nx,1,1]→[3,nx], etc.
|
|
525
|
+
while E.ndim > 1 and E.shape[-1] == 1:
|
|
526
|
+
E = E[..., 0]
|
|
520
527
|
|
|
521
528
|
logger.info(f" Output shape: {E.shape}")
|
|
522
529
|
|
|
@@ -640,7 +640,9 @@ def create_wavesim_properties(
|
|
|
640
640
|
residual_norm_max_iterations: int = 10000,
|
|
641
641
|
periodic_x: bool = False,
|
|
642
642
|
periodic_y: bool = False,
|
|
643
|
-
periodic_z: bool = False
|
|
643
|
+
periodic_z: bool = False,
|
|
644
|
+
alpha: float = 0.75,
|
|
645
|
+
keep_residuals_list: bool = False,
|
|
644
646
|
) -> Dict[str, Any]:
|
|
645
647
|
"""
|
|
646
648
|
Create complete WavesimProperties structure
|
|
@@ -688,6 +690,8 @@ def create_wavesim_properties(
|
|
|
688
690
|
"PeriodicX": periodic_x,
|
|
689
691
|
"PeriodicY": periodic_y,
|
|
690
692
|
"PeriodicZ": periodic_z,
|
|
693
|
+
"Alpha": alpha,
|
|
694
|
+
"KeepResidualsList": keep_residuals_list,
|
|
691
695
|
"DefaultLengthUnit": "Micrometers",
|
|
692
696
|
"CoordinateSystemCenter": {
|
|
693
697
|
"X": {"Value": 0.0, "Unit": "Micrometers"},
|
|
@@ -719,6 +723,11 @@ def parse_progress_data(progress_obj: Optional[Dict[str, Any]]) -> Dict[str, Any
|
|
|
719
723
|
if "status" in norm:
|
|
720
724
|
info["status"] = norm["status"]
|
|
721
725
|
|
|
726
|
+
# Full residuals list (ProgressSnapshot.ResidualsList from machine service)
|
|
727
|
+
residuals_list = norm.get("residualslist") or norm.get("residuals_list")
|
|
728
|
+
if residuals_list and isinstance(residuals_list, list):
|
|
729
|
+
info["residuals_list"] = residuals_list
|
|
730
|
+
|
|
722
731
|
progress_str = norm.get("progress")
|
|
723
732
|
if progress_str and isinstance(progress_str, str):
|
|
724
733
|
# Try JSON first
|
|
@@ -731,7 +740,15 @@ def parse_progress_data(progress_obj: Optional[Dict[str, Any]]) -> Dict[str, Any
|
|
|
731
740
|
if "max_iterations" in progress_data:
|
|
732
741
|
info["max_iterations"] = progress_data["max_iterations"]
|
|
733
742
|
except (json.JSONDecodeError, TypeError, ValueError):
|
|
734
|
-
#
|
|
743
|
+
# Intermediate C++ format: "C++ iteration 400, residual: 1.23E-04"
|
|
744
|
+
m = re.search(r'iteration\s+(\d+)', progress_str, re.IGNORECASE)
|
|
745
|
+
if m:
|
|
746
|
+
info["iteration"] = int(m.group(1))
|
|
747
|
+
m = re.search(r'residual[:\s]+([\d.E+\-]+)', progress_str, re.IGNORECASE)
|
|
748
|
+
if m:
|
|
749
|
+
info["residual_norm"] = float(m.group(1))
|
|
750
|
+
|
|
751
|
+
# Final completion summary: "Last Iteration : 200 / 10000"
|
|
735
752
|
m = re.search(r'Last Iteration\s*:\s*(\d+)\s*/\s*(\d+)', progress_str)
|
|
736
753
|
if m:
|
|
737
754
|
info["iteration"] = int(m.group(1))
|
|
@@ -752,7 +769,14 @@ def parse_progress_data(progress_obj: Optional[Dict[str, Any]]) -> Dict[str, Any
|
|
|
752
769
|
info["residual_norm"] = data_obj["residual_norm"]
|
|
753
770
|
if "residual_norm_threshold" in data_obj:
|
|
754
771
|
info["threshold"] = data_obj["residual_norm_threshold"]
|
|
772
|
+
rlist = data_obj.get("ResidualsList") or data_obj.get("residuals_list")
|
|
773
|
+
if rlist and isinstance(rlist, list):
|
|
774
|
+
info["residuals_list"] = rlist
|
|
755
775
|
except (json.JSONDecodeError, TypeError, ValueError):
|
|
756
776
|
pass
|
|
757
777
|
|
|
778
|
+
# Derive percentage if not already set but iteration + max_iterations are known
|
|
779
|
+
if "percentage" not in info and info.get("iteration") and info.get("max_iterations"):
|
|
780
|
+
info["percentage"] = 100.0 * info["iteration"] / info["max_iterations"]
|
|
781
|
+
|
|
758
782
|
return info
|
|
@@ -11,17 +11,27 @@ Credentials are resolved automatically — run the setup wizard once on each mac
|
|
|
11
11
|
Call configure() to override programmatically.
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
+
import inspect
|
|
15
|
+
from pathlib import Path
|
|
14
16
|
from typing import Callable, Optional, Sequence
|
|
15
17
|
|
|
16
18
|
import numpy as np
|
|
17
19
|
|
|
18
20
|
from .execute import execute_via_api
|
|
19
21
|
|
|
22
|
+
|
|
23
|
+
def _caller_script_name() -> str:
|
|
24
|
+
for frame_info in inspect.stack():
|
|
25
|
+
path = frame_info.filename
|
|
26
|
+
if "wavesimpro" not in path and path not in ("<stdin>", "<string>"):
|
|
27
|
+
return Path(path).stem
|
|
28
|
+
return "wavesimpro"
|
|
29
|
+
|
|
20
30
|
# Module-level defaults — overridden by configure() or per-call kwargs
|
|
21
31
|
_defaults = {
|
|
22
32
|
"api_key": None,
|
|
23
33
|
"api_url": None,
|
|
24
|
-
"description":
|
|
34
|
+
"description": None,
|
|
25
35
|
"poll_interval": 10.0,
|
|
26
36
|
"engine_preference": "Any",
|
|
27
37
|
"use_gpu": None,
|
|
@@ -75,8 +85,8 @@ def simulate(
|
|
|
75
85
|
n_domains=None, # local only — accepted and ignored
|
|
76
86
|
max_iterations: int = 100000,
|
|
77
87
|
threshold: float = 1.0e-6,
|
|
78
|
-
alpha: float = 0.75,
|
|
79
|
-
full_residuals: bool = False,
|
|
88
|
+
alpha: float = 0.75,
|
|
89
|
+
full_residuals: bool = False,
|
|
80
90
|
crop_boundaries: bool = True, # local only — accepted and ignored
|
|
81
91
|
callback: Optional[Callable] = None,
|
|
82
92
|
**kwargs,
|
|
@@ -108,7 +118,7 @@ def simulate(
|
|
|
108
118
|
"Run the setup wizard once to get started:\n\n"
|
|
109
119
|
" python -m wavesimpro setup\n"
|
|
110
120
|
)
|
|
111
|
-
description = _defaults["description"]
|
|
121
|
+
description = _defaults["description"] or _caller_script_name()
|
|
112
122
|
poll_interval = _defaults["poll_interval"]
|
|
113
123
|
engine_preference = _defaults["engine_preference"]
|
|
114
124
|
effective_use_gpu = use_gpu if use_gpu is not None else _defaults["use_gpu"]
|
|
@@ -128,11 +138,13 @@ def simulate(
|
|
|
128
138
|
src_field = src_array.astype(np.complex64)
|
|
129
139
|
api_sources.append({"Es": src_field, "position": api_pos})
|
|
130
140
|
|
|
131
|
-
_last = {"iteration": None, "residual_norm": None}
|
|
141
|
+
_last = {"iteration": None, "residual_norm": None, "residuals_list": None}
|
|
132
142
|
|
|
133
143
|
def _progress_cb(info: dict):
|
|
134
144
|
_last["iteration"] = info.get("iteration", _last["iteration"])
|
|
135
145
|
_last["residual_norm"] = info.get("residual_norm", _last["residual_norm"])
|
|
146
|
+
if info.get("residuals_list") is not None:
|
|
147
|
+
_last["residuals_list"] = info["residuals_list"]
|
|
136
148
|
if callback is not None and _last["iteration"] is not None:
|
|
137
149
|
extra = {k: v for k, v in info.items() if k not in ("iteration", "residual_norm")}
|
|
138
150
|
callback(None, _last["iteration"], None, _last["residual_norm"], **extra)
|
|
@@ -154,10 +166,121 @@ def simulate(
|
|
|
154
166
|
poll_interval=poll_interval,
|
|
155
167
|
engine_preference=engine_preference,
|
|
156
168
|
use_gpu=effective_use_gpu,
|
|
169
|
+
alpha=alpha,
|
|
170
|
+
keep_residuals_list=full_residuals,
|
|
171
|
+
progress_callback=_progress_cb,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
iterations = _last["iteration"] or 1
|
|
175
|
+
final_residual = _last["residual_norm"] if _last["residual_norm"] is not None else float("nan")
|
|
176
|
+
if full_residuals:
|
|
177
|
+
residual_norm = _last["residuals_list"] if _last["residuals_list"] else [final_residual] * iterations
|
|
178
|
+
else:
|
|
179
|
+
residual_norm = final_residual
|
|
180
|
+
return E, iterations, residual_norm
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def simulate_pro(
|
|
184
|
+
permittivity: np.ndarray,
|
|
185
|
+
sources: Sequence[tuple],
|
|
186
|
+
wavelength: float,
|
|
187
|
+
pixel_size: float,
|
|
188
|
+
boundary_width: float = 1.0,
|
|
189
|
+
periodic: tuple = (False, False, False),
|
|
190
|
+
max_iterations: int = 100000,
|
|
191
|
+
threshold: float = 1.0e-6,
|
|
192
|
+
alpha: float = 0.75,
|
|
193
|
+
n_domains=None,
|
|
194
|
+
full_residuals: bool = False,
|
|
195
|
+
crop_boundaries: bool = True, # local only — accepted and ignored
|
|
196
|
+
callback: Optional[Callable] = None,
|
|
197
|
+
# remote execution options
|
|
198
|
+
engine: str = "Any",
|
|
199
|
+
use_gpu: Optional[bool] = None,
|
|
200
|
+
machine_id: Optional[str] = None,
|
|
201
|
+
poll_interval: float = 10.0,
|
|
202
|
+
description: Optional[str] = None,
|
|
203
|
+
server: Optional[str] = None,
|
|
204
|
+
**kwargs,
|
|
205
|
+
):
|
|
206
|
+
"""Execute a Wavesim simulation remotely with full control over execution options.
|
|
207
|
+
|
|
208
|
+
Drop-in compatible with wavesim.simulate() but exposes all remote-specific options
|
|
209
|
+
that simulate() manages via configure():
|
|
210
|
+
|
|
211
|
+
engine : 'Any' (default) | 'Cpp' | 'Python'
|
|
212
|
+
use_gpu : None (auto) | True (require GPU) | False (force CPU)
|
|
213
|
+
machine_id : preferred machine service ID to pin the job to a specific machine
|
|
214
|
+
poll_interval: seconds between status polls (default 10)
|
|
215
|
+
description : job label shown in the Rayfos dashboard (default: caller script name)
|
|
216
|
+
"""
|
|
217
|
+
from .setup import resolve_credentials
|
|
218
|
+
_api_key, _api_url = resolve_credentials()
|
|
219
|
+
api_key = _defaults["api_key"] or _api_key
|
|
220
|
+
api_url = server or _defaults["api_url"] or _api_url
|
|
221
|
+
if not api_key:
|
|
222
|
+
raise SystemExit(
|
|
223
|
+
"\nwavesimpro: no API key configured.\n"
|
|
224
|
+
"Run the setup wizard once to get started:\n\n"
|
|
225
|
+
" python -m wavesimpro setup\n"
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
effective_description = description or _defaults["description"] or _caller_script_name()
|
|
229
|
+
effective_use_gpu = use_gpu if use_gpu is not None else _defaults["use_gpu"]
|
|
230
|
+
|
|
231
|
+
is_vectorial = any(len(s[1]) == 4 for s in sources)
|
|
232
|
+
boundary_width_px = int(np.round(boundary_width / pixel_size))
|
|
233
|
+
|
|
234
|
+
api_sources = []
|
|
235
|
+
for src_array, src_pos in sources:
|
|
236
|
+
pos = list(src_pos)
|
|
237
|
+
if is_vectorial:
|
|
238
|
+
pol_1based = int(pos[0]) + 1
|
|
239
|
+
api_pos = [pos[1] * pixel_size, pos[2] * pixel_size, pos[3] * pixel_size, pol_1based]
|
|
240
|
+
src_field = (src_array[0] if src_array.ndim == 4 else src_array).astype(np.complex64)
|
|
241
|
+
else:
|
|
242
|
+
api_pos = [pos[0] * pixel_size, pos[1] * pixel_size, pos[2] * pixel_size]
|
|
243
|
+
src_field = src_array.astype(np.complex64)
|
|
244
|
+
api_sources.append({"Es": src_field, "position": api_pos})
|
|
245
|
+
|
|
246
|
+
_last = {"iteration": None, "residual_norm": None, "residuals_list": None}
|
|
247
|
+
|
|
248
|
+
def _progress_cb(info: dict):
|
|
249
|
+
_last["iteration"] = info.get("iteration", _last["iteration"])
|
|
250
|
+
_last["residual_norm"] = info.get("residual_norm", _last["residual_norm"])
|
|
251
|
+
if info.get("residuals_list") is not None:
|
|
252
|
+
_last["residuals_list"] = info["residuals_list"]
|
|
253
|
+
if callback is not None and _last["iteration"] is not None:
|
|
254
|
+
extra = {k: v for k, v in info.items() if k not in ("iteration", "residual_norm")}
|
|
255
|
+
callback(None, _last["iteration"], None, _last["residual_norm"], **extra)
|
|
256
|
+
|
|
257
|
+
E, _ = execute_via_api(
|
|
258
|
+
refractive_index=permittivity,
|
|
259
|
+
source=api_sources,
|
|
260
|
+
wavelength=wavelength,
|
|
261
|
+
pixel_size=pixel_size,
|
|
262
|
+
boundary_width=boundary_width_px,
|
|
263
|
+
is_vectorial=is_vectorial,
|
|
264
|
+
data_type="Permittivity",
|
|
265
|
+
max_iterations=max_iterations,
|
|
266
|
+
threshold=threshold,
|
|
267
|
+
periodic=periodic,
|
|
268
|
+
api_key=api_key,
|
|
269
|
+
api_url=api_url,
|
|
270
|
+
description=effective_description,
|
|
271
|
+
poll_interval=poll_interval,
|
|
272
|
+
engine_preference=engine,
|
|
273
|
+
use_gpu=effective_use_gpu,
|
|
274
|
+
preferred_machine_id=machine_id,
|
|
275
|
+
alpha=alpha,
|
|
276
|
+
keep_residuals_list=full_residuals,
|
|
157
277
|
progress_callback=_progress_cb,
|
|
158
278
|
)
|
|
159
279
|
|
|
160
280
|
iterations = _last["iteration"] or 1
|
|
161
281
|
final_residual = _last["residual_norm"] if _last["residual_norm"] is not None else float("nan")
|
|
162
|
-
|
|
282
|
+
if full_residuals:
|
|
283
|
+
residual_norm = _last["residuals_list"] if _last["residuals_list"] else [final_residual] * iterations
|
|
284
|
+
else:
|
|
285
|
+
residual_norm = final_residual
|
|
163
286
|
return E, iterations, residual_norm
|
|
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
|