wavesimpro 1.0.0__tar.gz → 1.0.1__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-1.0.0 → wavesimpro-1.0.1}/PKG-INFO +1 -1
- {wavesimpro-1.0.0 → wavesimpro-1.0.1}/pyproject.toml +1 -1
- {wavesimpro-1.0.0 → wavesimpro-1.0.1}/src/wavesimpro/__init__.py +1 -1
- {wavesimpro-1.0.0 → wavesimpro-1.0.1}/src/wavesimpro/execute.py +117 -22
- {wavesimpro-1.0.0 → wavesimpro-1.0.1}/src/wavesimpro/json_helper.py +0 -2
- {wavesimpro-1.0.0 → wavesimpro-1.0.1}/src/wavesimpro/simulate.py +2 -1
- {wavesimpro-1.0.0 → wavesimpro-1.0.1}/src/wavesimpro.egg-info/PKG-INFO +1 -1
- {wavesimpro-1.0.0 → wavesimpro-1.0.1}/setup.cfg +0 -0
- {wavesimpro-1.0.0 → wavesimpro-1.0.1}/src/wavesimpro/__main__.py +0 -0
- {wavesimpro-1.0.0 → wavesimpro-1.0.1}/src/wavesimpro/binary_utils.py +0 -0
- {wavesimpro-1.0.0 → wavesimpro-1.0.1}/src/wavesimpro/client.py +0 -0
- {wavesimpro-1.0.0 → wavesimpro-1.0.1}/src/wavesimpro/config.py +0 -0
- {wavesimpro-1.0.0 → wavesimpro-1.0.1}/src/wavesimpro/exceptions.py +0 -0
- {wavesimpro-1.0.0 → wavesimpro-1.0.1}/src/wavesimpro/py.typed +0 -0
- {wavesimpro-1.0.0 → wavesimpro-1.0.1}/src/wavesimpro/setup.py +0 -0
- {wavesimpro-1.0.0 → wavesimpro-1.0.1}/src/wavesimpro/validate.py +0 -0
- {wavesimpro-1.0.0 → wavesimpro-1.0.1}/src/wavesimpro.egg-info/SOURCES.txt +0 -0
- {wavesimpro-1.0.0 → wavesimpro-1.0.1}/src/wavesimpro.egg-info/dependency_links.txt +0 -0
- {wavesimpro-1.0.0 → wavesimpro-1.0.1}/src/wavesimpro.egg-info/requires.txt +0 -0
- {wavesimpro-1.0.0 → wavesimpro-1.0.1}/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 = "1.0.
|
|
7
|
+
version = "1.0.1"
|
|
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"
|
|
@@ -7,13 +7,15 @@ command submission, polling, result download.
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
import os
|
|
10
|
+
import re
|
|
10
11
|
import json
|
|
11
12
|
import math
|
|
12
13
|
import time
|
|
14
|
+
import shutil
|
|
13
15
|
import logging
|
|
14
16
|
import tempfile
|
|
15
17
|
from pathlib import Path
|
|
16
|
-
from typing import Tuple, Optional, Callable, Dict, Any, List
|
|
18
|
+
from typing import Tuple, Optional, Callable, Dict, Any, List, Union
|
|
17
19
|
|
|
18
20
|
import numpy as np
|
|
19
21
|
|
|
@@ -44,6 +46,21 @@ def _extract_file_id(file_info: dict) -> str:
|
|
|
44
46
|
raise KeyError(f"No file ID found in upload response. Keys: {list(file_info.keys())}")
|
|
45
47
|
|
|
46
48
|
|
|
49
|
+
def _monitor_output_filename(monitor_name: str) -> str:
|
|
50
|
+
"""Expected on-server filename for a monitor's result.
|
|
51
|
+
|
|
52
|
+
Mirrors RayfosMachineService MonitorDataExtractor.BuildMonitorFileName: strip
|
|
53
|
+
characters illegal in a filename, drop a leading ``monitor[ _-]+`` token (so a
|
|
54
|
+
monitor named "Monitor_0" yields ``monitor_0.bin``, not ``monitor_Monitor_0.bin``),
|
|
55
|
+
then prefix ``monitor_`` and append ``.bin``.
|
|
56
|
+
"""
|
|
57
|
+
core = "".join(c for c in (monitor_name or "") if c not in '<>:"/\\|?*') or "unnamed"
|
|
58
|
+
stripped = re.sub(r"^monitor[ _\-]+", "", core, flags=re.IGNORECASE)
|
|
59
|
+
if stripped:
|
|
60
|
+
core = stripped
|
|
61
|
+
return f"monitor_{core}.bin"
|
|
62
|
+
|
|
63
|
+
|
|
47
64
|
def execute_via_api(
|
|
48
65
|
refractive_index: np.ndarray,
|
|
49
66
|
source,
|
|
@@ -71,7 +88,7 @@ def execute_via_api(
|
|
|
71
88
|
strict_validation: bool = False,
|
|
72
89
|
gpu_memory_max_gb: Optional[float] = None,
|
|
73
90
|
monitors: Optional[List[Dict[str, Any]]] = None,
|
|
74
|
-
) -> Tuple[np.ndarray, bool]:
|
|
91
|
+
) -> Tuple[Union[np.ndarray, List[np.ndarray]], bool]:
|
|
75
92
|
"""
|
|
76
93
|
Execute a Wavesim simulation via the Rayfos REST API.
|
|
77
94
|
|
|
@@ -102,10 +119,17 @@ def execute_via_api(
|
|
|
102
119
|
Ignored when machine_service_url is set (local mode always uses that machine).
|
|
103
120
|
use_gpu: GPU selection — None (auto, default), True (require GPU), False (force CPU).
|
|
104
121
|
Ignored when machine_service_url is set.
|
|
122
|
+
monitors: Optional list of monitor dicts (from create_monitor()). Each captures a 3D
|
|
123
|
+
ROI sub-cube of the field. When monitors are given, the server uploads only the
|
|
124
|
+
monitor_<name>.bin files (not the full output.bin), and this call downloads and
|
|
125
|
+
returns those instead of output.bin.
|
|
105
126
|
|
|
106
127
|
Returns:
|
|
107
|
-
Tuple of (
|
|
108
|
-
-
|
|
128
|
+
Tuple of (result, converged):
|
|
129
|
+
- result: WITHOUT monitors — the complex output field array (Vectorial:
|
|
130
|
+
[3, nx, ny, nz], Scalar: [nx, ny, nz]). WITH monitors — a LIST of monitor field
|
|
131
|
+
arrays in the SAME ORDER as the `monitors` argument (disabled monitors skipped),
|
|
132
|
+
each shaped per its own sub-cube.
|
|
109
133
|
- converged: True if simulation completed successfully
|
|
110
134
|
"""
|
|
111
135
|
# 1. Load credentials
|
|
@@ -543,34 +567,105 @@ def execute_via_api(
|
|
|
543
567
|
|
|
544
568
|
logger.info(f" Simulation completed in {total_time:.1f} seconds!")
|
|
545
569
|
|
|
546
|
-
# 14. Download
|
|
570
|
+
# 14. Download results
|
|
547
571
|
logger.info("Downloading results...")
|
|
548
572
|
server_info = client.get_server_info()
|
|
549
573
|
use_machine_service = server_info["uses_machine_service"]
|
|
550
574
|
|
|
551
|
-
|
|
552
|
-
|
|
575
|
+
# Collect this command's result files as (filename, file_id) pairs from
|
|
576
|
+
# whichever store holds them (machine service vs cloud blob storage).
|
|
577
|
+
result_files = []
|
|
553
578
|
if use_machine_service:
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
break
|
|
579
|
+
for f in client.list_command_files(command_id):
|
|
580
|
+
name = f.get("fileName") or f.get("originalFileName") or ""
|
|
581
|
+
fid = f.get("blobName")
|
|
582
|
+
if name and fid:
|
|
583
|
+
result_files.append((name, fid))
|
|
584
|
+
logger.info(f" Found {len(result_files)} files from machine service")
|
|
561
585
|
else:
|
|
562
586
|
version_info = client.get_project_version(project_id, version_id)
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
for fid in output_file_ids:
|
|
587
|
+
for fid in version_info.get("outputFileIds", []):
|
|
588
|
+
name = ""
|
|
566
589
|
try:
|
|
567
590
|
preview = client.get_file_preview(project_id, version_id, fid)
|
|
568
|
-
|
|
569
|
-
if fname.lower() == "output.bin":
|
|
570
|
-
output_file_id = fid # Use the original ID from outputFileIds
|
|
571
|
-
break
|
|
591
|
+
name = preview.get("fileName") or preview.get("originalFileName") or ""
|
|
572
592
|
except Exception:
|
|
573
|
-
|
|
593
|
+
pass
|
|
594
|
+
result_files.append((name, fid))
|
|
595
|
+
logger.info(f" Found {len(result_files)} output file IDs from cloud storage")
|
|
596
|
+
|
|
597
|
+
# ----- Monitor runs --------------------------------------------------------
|
|
598
|
+
# When monitors are defined, the server crops each monitor's sub-cube out of
|
|
599
|
+
# output.bin and uploads only the monitor_<name>.bin files — the full output.bin
|
|
600
|
+
# is intentionally NOT uploaded (it can be many GB; see RayfosMachineService
|
|
601
|
+
# CommandManager). So we must NOT require output.bin here. Just like the output.bin
|
|
602
|
+
# path, we stream each file to a temp dir, read it into numpy, and clean up; the
|
|
603
|
+
# return value is a LIST of monitor fields in the SAME ORDER as the `monitors`
|
|
604
|
+
# argument (disabled monitors skipped).
|
|
605
|
+
if monitors:
|
|
606
|
+
by_name = {n.lower(): fid for (n, fid) in result_files if n}
|
|
607
|
+
|
|
608
|
+
# (monitor name, expected filename) for each enabled monitor, in definition order.
|
|
609
|
+
ordered = [
|
|
610
|
+
(m.get("Name", ""), _monitor_output_filename(m.get("Name", "")))
|
|
611
|
+
for m in monitors if m.get("IsEnabled", True)
|
|
612
|
+
]
|
|
613
|
+
have_exact = any(fn.lower() in by_name for _, fn in ordered)
|
|
614
|
+
|
|
615
|
+
# Download list as (display_name, file_id). Exact matches preserve definition
|
|
616
|
+
# order; the fallback (name-sanitisation mismatch) returns every monitor_*.bin
|
|
617
|
+
# the server produced, in the order the store listed them.
|
|
618
|
+
if have_exact:
|
|
619
|
+
to_fetch = []
|
|
620
|
+
for mname, fname in ordered:
|
|
621
|
+
fid = by_name.get(fname.lower())
|
|
622
|
+
if fid is None:
|
|
623
|
+
logger.warning(f"Monitor '{mname}': expected file {fname} not found on server; skipping.")
|
|
624
|
+
continue
|
|
625
|
+
to_fetch.append((fname, fid))
|
|
626
|
+
else:
|
|
627
|
+
to_fetch = [
|
|
628
|
+
(n, fid) for (n, fid) in result_files
|
|
629
|
+
if n.lower().startswith("monitor_") and n.lower().endswith(".bin")
|
|
630
|
+
]
|
|
631
|
+
if to_fetch:
|
|
632
|
+
logger.warning("No exact monitor filename match; returning all monitor_*.bin files in server order.")
|
|
633
|
+
|
|
634
|
+
if not to_fetch:
|
|
635
|
+
available = ", ".join(n for n, _ in result_files) or "(none)"
|
|
636
|
+
raise RuntimeError(
|
|
637
|
+
"No monitor output files found for this command. Available files: " + available
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
tmp_dir = tempfile.mkdtemp(prefix="wavesimpro_mon_")
|
|
641
|
+
try:
|
|
642
|
+
monitor_fields = []
|
|
643
|
+
for name, fid in to_fetch:
|
|
644
|
+
dest = Path(tmp_dir) / name
|
|
645
|
+
logger.info(f" Downloading monitor file: {name}")
|
|
646
|
+
client.download_file(project_id, version_id, fid, str(dest), command_id=command_id)
|
|
647
|
+
# Monitor files carry a self-describing V2 header, so dims come from the file.
|
|
648
|
+
fld = read_binary_field(dest, None, "Complex64")
|
|
649
|
+
# Squeeze trailing size-1 dims, same convention as output.bin.
|
|
650
|
+
while fld.ndim > 1 and fld.shape[-1] == 1:
|
|
651
|
+
fld = fld[..., 0]
|
|
652
|
+
monitor_fields.append(fld)
|
|
653
|
+
logger.info(f" -> {fld.shape}")
|
|
654
|
+
finally:
|
|
655
|
+
shutil.rmtree(tmp_dir, ignore_errors=True)
|
|
656
|
+
|
|
657
|
+
converged = status_str == "Completed"
|
|
658
|
+
logger.info("=" * 60)
|
|
659
|
+
logger.info(f"Simulation completed successfully! Returning {len(monitor_fields)} monitor field(s).")
|
|
660
|
+
logger.info("=" * 60)
|
|
661
|
+
return monitor_fields, converged
|
|
662
|
+
|
|
663
|
+
# ----- Plain runs (no monitors): require output.bin ------------------------
|
|
664
|
+
output_file_id = None
|
|
665
|
+
for name, fid in result_files:
|
|
666
|
+
if name.lower() == "output.bin":
|
|
667
|
+
output_file_id = fid
|
|
668
|
+
break
|
|
574
669
|
|
|
575
670
|
if not output_file_id:
|
|
576
671
|
raise RuntimeError("output.bin not found in command files")
|
|
@@ -3,8 +3,6 @@ Helper functions to build WavesimProperties JSON structure
|
|
|
3
3
|
|
|
4
4
|
This module provides Python functions to build the correct JSON structure
|
|
5
5
|
that matches RayfosCommandModels.WavesimProperties C# class.
|
|
6
|
-
|
|
7
|
-
Ported from examples/wavesim_json_helper.py with fixes for MATLAB API parity.
|
|
8
6
|
"""
|
|
9
7
|
|
|
10
8
|
import json
|
|
@@ -125,7 +125,8 @@ def simulate(
|
|
|
125
125
|
4. RAYFOS_API_KEY / RAYFOS_API_URL environment variables
|
|
126
126
|
|
|
127
127
|
Returns:
|
|
128
|
-
u: np.ndarray
|
|
128
|
+
u: output field. WITHOUT monitors — np.ndarray (same shape as wavesim.simulate()).
|
|
129
|
+
WITH monitors — a list of monitor field arrays in the same order as `monitors`.
|
|
129
130
|
iterations: int or None, last reported iteration count.
|
|
130
131
|
residual_norm: float or None, last reported residual norm.
|
|
131
132
|
"""
|
|
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
|