simudyne-pulse 0.7.0.dev3__tar.gz → 0.7.0.dev4__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.
- {simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/PKG-INFO +1 -1
- {simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/pyproject.toml +1 -1
- {simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/src/simudyne/resources/validation.py +128 -7
- {simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/src/simudyne_pulse.egg-info/PKG-INFO +1 -1
- {simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/tests/test_validation.py +110 -0
- {simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/LICENSE +0 -0
- {simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/README.md +0 -0
- {simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/setup.cfg +0 -0
- {simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/src/simudyne/__init__.py +0 -0
- {simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/src/simudyne/client.py +0 -0
- {simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/src/simudyne/exceptions.py +0 -0
- {simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/src/simudyne/resources/__init__.py +0 -0
- {simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/src/simudyne/resources/api_keys.py +0 -0
- {simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/src/simudyne/resources/data.py +0 -0
- {simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/src/simudyne/resources/fix.py +0 -0
- {simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/src/simudyne/resources/fm.py +0 -0
- {simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/src/simudyne/resources/historical.py +0 -0
- {simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/src/simudyne/resources/profile.py +0 -0
- {simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/src/simudyne/resources/simulation.py +0 -0
- {simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/src/simudyne/resources/simulator_gym.py +0 -0
- {simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/src/simudyne_pulse.egg-info/SOURCES.txt +0 -0
- {simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/src/simudyne_pulse.egg-info/dependency_links.txt +0 -0
- {simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/src/simudyne_pulse.egg-info/requires.txt +0 -0
- {simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/src/simudyne_pulse.egg-info/top_level.txt +0 -0
- {simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/tests/test_new_resources.py +0 -0
- {simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/tests/test_simulator_gym.py +0 -0
{simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/src/simudyne/resources/validation.py
RENAMED
|
@@ -53,11 +53,56 @@ _TRI_STATE_FLAGS = (
|
|
|
53
53
|
"plot_data",
|
|
54
54
|
)
|
|
55
55
|
|
|
56
|
+
#: Areas of checking selectable per job, as of pulse-check 1.10.0. Left unset
|
|
57
|
+
#: they take the server's default, so a job that names none behaves as before.
|
|
58
|
+
_AREA_FLAGS = ("statistical", "stylised_facts", "impact", "volume_correlation",
|
|
59
|
+
"fid", "mind")
|
|
60
|
+
|
|
61
|
+
#: Everything else the 1.10.0 schema accepts. ``lob`` marks the frames as L2
|
|
62
|
+
#: snapshots, which switches off anything needing the message stream;
|
|
63
|
+
#: ``sample_period`` and ``match_generated_sample`` set the grid the book is
|
|
64
|
+
#: resampled onto; ``plots`` is False, True, or a list of plot ids;
|
|
65
|
+
#: ``historical_output`` is the demo-only gate that ``plot_data`` used to be.
|
|
66
|
+
_EXTRA_FIELDS = ("lob", "sample_period", "match_generated_sample", "plots",
|
|
67
|
+
"historical_output")
|
|
68
|
+
|
|
56
69
|
#: SDK name -> API config field. The API kept ``run_fid`` for compatibility;
|
|
57
70
|
#: the SDK spells out what it actually gates.
|
|
58
71
|
_INCEPTION_WIRE_FIELD = "run_fid"
|
|
59
72
|
|
|
60
73
|
|
|
74
|
+
def _frame_to_parquet(entry, index: int):
|
|
75
|
+
"""Normalise one simulated run to ``(filename, parquet bytes)``.
|
|
76
|
+
|
|
77
|
+
Accepts a path, a ``(filename, bytes)`` pair, or a polars / pandas
|
|
78
|
+
DataFrame, which is written to parquet in memory. The frame must be pulse
|
|
79
|
+
format -- the same shape the engine writes to ``sim_data.parquet`` -- which
|
|
80
|
+
the server validates; sending something else fails there, not here.
|
|
81
|
+
"""
|
|
82
|
+
import io
|
|
83
|
+
|
|
84
|
+
if isinstance(entry, tuple):
|
|
85
|
+
return entry
|
|
86
|
+
|
|
87
|
+
# Duck-typed rather than imported: neither polars nor pandas is a hard
|
|
88
|
+
# dependency of the SDK, and importing one to test for the other would
|
|
89
|
+
# make it one.
|
|
90
|
+
writer = getattr(entry, "write_parquet", None) # polars
|
|
91
|
+
if writer is not None:
|
|
92
|
+
buf = io.BytesIO()
|
|
93
|
+
writer(buf)
|
|
94
|
+
return f"sim_{index}.parquet", buf.getvalue()
|
|
95
|
+
|
|
96
|
+
writer = getattr(entry, "to_parquet", None) # pandas
|
|
97
|
+
if writer is not None:
|
|
98
|
+
buf = io.BytesIO()
|
|
99
|
+
writer(buf, index=False)
|
|
100
|
+
return f"sim_{index}.parquet", buf.getvalue()
|
|
101
|
+
|
|
102
|
+
path = Path(entry)
|
|
103
|
+
return path.name, path.read_bytes()
|
|
104
|
+
|
|
105
|
+
|
|
61
106
|
def _build_config(
|
|
62
107
|
run_metrics,
|
|
63
108
|
run_impact,
|
|
@@ -66,12 +111,14 @@ def _build_config(
|
|
|
66
111
|
plot_data,
|
|
67
112
|
n_levels,
|
|
68
113
|
l2_only,
|
|
114
|
+
**extra,
|
|
69
115
|
) -> dict:
|
|
70
116
|
"""The validation config object as the API expects it.
|
|
71
117
|
|
|
72
118
|
Tri-state flags left as None are omitted rather than sent as null: the API
|
|
73
119
|
reads absence as "use my tier's default", and an explicit null would not
|
|
74
|
-
do that.
|
|
120
|
+
do that. The same rule covers the 1.10.0 area flags and everything in
|
|
121
|
+
``extra`` — naming nothing new leaves the job behaving exactly as before.
|
|
75
122
|
"""
|
|
76
123
|
config = {
|
|
77
124
|
"n_levels": n_levels,
|
|
@@ -84,6 +131,18 @@ def _build_config(
|
|
|
84
131
|
):
|
|
85
132
|
if value is not None:
|
|
86
133
|
config[flag] = value
|
|
134
|
+
|
|
135
|
+
for name in _AREA_FLAGS + _EXTRA_FIELDS:
|
|
136
|
+
value = extra.get(name)
|
|
137
|
+
if value is not None:
|
|
138
|
+
config[name] = value
|
|
139
|
+
|
|
140
|
+
unknown = set(extra) - set(_AREA_FLAGS) - set(_EXTRA_FIELDS)
|
|
141
|
+
if unknown:
|
|
142
|
+
raise ValueError(
|
|
143
|
+
f"Unknown validation option(s): {sorted(unknown)}. "
|
|
144
|
+
f"Valid: {sorted(_AREA_FLAGS + _EXTRA_FIELDS)}"
|
|
145
|
+
)
|
|
87
146
|
return config
|
|
88
147
|
|
|
89
148
|
|
|
@@ -106,6 +165,17 @@ class ValidationResource:
|
|
|
106
165
|
l2_only: bool = False,
|
|
107
166
|
provider: str = None,
|
|
108
167
|
exchange: str = None,
|
|
168
|
+
statistical: bool = None,
|
|
169
|
+
stylised_facts: bool = None,
|
|
170
|
+
impact: bool = None,
|
|
171
|
+
volume_correlation: bool = None,
|
|
172
|
+
fid: bool = None,
|
|
173
|
+
mind: bool = None,
|
|
174
|
+
lob: bool = None,
|
|
175
|
+
sample_period: str = None,
|
|
176
|
+
match_generated_sample: bool = None,
|
|
177
|
+
plots=None,
|
|
178
|
+
historical_output: bool = None,
|
|
109
179
|
) -> dict:
|
|
110
180
|
"""Submit a validation job.
|
|
111
181
|
|
|
@@ -155,6 +225,17 @@ class ValidationResource:
|
|
|
155
225
|
config = _build_config(
|
|
156
226
|
run_metrics, run_impact, run_inception_distances,
|
|
157
227
|
run_stylised_facts, plot_data, n_levels, l2_only,
|
|
228
|
+
statistical=statistical,
|
|
229
|
+
stylised_facts=stylised_facts,
|
|
230
|
+
impact=impact,
|
|
231
|
+
volume_correlation=volume_correlation,
|
|
232
|
+
fid=fid,
|
|
233
|
+
mind=mind,
|
|
234
|
+
lob=lob,
|
|
235
|
+
sample_period=sample_period,
|
|
236
|
+
match_generated_sample=match_generated_sample,
|
|
237
|
+
plots=plots,
|
|
238
|
+
historical_output=historical_output,
|
|
158
239
|
)
|
|
159
240
|
|
|
160
241
|
payload = {
|
|
@@ -186,6 +267,17 @@ class ValidationResource:
|
|
|
186
267
|
plot_data: bool = None,
|
|
187
268
|
n_levels: int = 10,
|
|
188
269
|
l2_only: bool = False,
|
|
270
|
+
statistical: bool = None,
|
|
271
|
+
stylised_facts: bool = None,
|
|
272
|
+
impact: bool = None,
|
|
273
|
+
volume_correlation: bool = None,
|
|
274
|
+
fid: bool = None,
|
|
275
|
+
mind: bool = None,
|
|
276
|
+
lob: bool = None,
|
|
277
|
+
sample_period: str = None,
|
|
278
|
+
match_generated_sample: bool = None,
|
|
279
|
+
plots=None,
|
|
280
|
+
historical_output: bool = None,
|
|
189
281
|
) -> dict:
|
|
190
282
|
"""Submit a validation job from simulation files you hold yourself.
|
|
191
283
|
|
|
@@ -227,15 +319,22 @@ class ValidationResource:
|
|
|
227
319
|
config = _build_config(
|
|
228
320
|
run_metrics, run_impact, run_inception_distances,
|
|
229
321
|
run_stylised_facts, plot_data, n_levels, l2_only,
|
|
322
|
+
statistical=statistical,
|
|
323
|
+
stylised_facts=stylised_facts,
|
|
324
|
+
impact=impact,
|
|
325
|
+
volume_correlation=volume_correlation,
|
|
326
|
+
fid=fid,
|
|
327
|
+
mind=mind,
|
|
328
|
+
lob=lob,
|
|
329
|
+
sample_period=sample_period,
|
|
330
|
+
match_generated_sample=match_generated_sample,
|
|
331
|
+
plots=plots,
|
|
332
|
+
historical_output=historical_output,
|
|
230
333
|
)
|
|
231
334
|
|
|
232
335
|
files = []
|
|
233
|
-
for entry in sim_files:
|
|
234
|
-
|
|
235
|
-
filename, content = entry
|
|
236
|
-
else:
|
|
237
|
-
path = Path(entry)
|
|
238
|
-
filename, content = path.name, path.read_bytes()
|
|
336
|
+
for index, entry in enumerate(sim_files):
|
|
337
|
+
filename, content = _frame_to_parquet(entry, index)
|
|
239
338
|
files.append(
|
|
240
339
|
("sim_files", (filename, content, "application/octet-stream"))
|
|
241
340
|
)
|
|
@@ -316,6 +415,17 @@ class ValidationResource:
|
|
|
316
415
|
exchange: str = None,
|
|
317
416
|
poll_interval: float = 3.0,
|
|
318
417
|
timeout: float = 600.0,
|
|
418
|
+
statistical: bool = None,
|
|
419
|
+
stylised_facts: bool = None,
|
|
420
|
+
impact: bool = None,
|
|
421
|
+
volume_correlation: bool = None,
|
|
422
|
+
fid: bool = None,
|
|
423
|
+
mind: bool = None,
|
|
424
|
+
lob: bool = None,
|
|
425
|
+
sample_period: str = None,
|
|
426
|
+
match_generated_sample: bool = None,
|
|
427
|
+
plots=None,
|
|
428
|
+
historical_output: bool = None,
|
|
319
429
|
) -> dict:
|
|
320
430
|
"""Submit a validation job and block until it completes.
|
|
321
431
|
|
|
@@ -364,6 +474,17 @@ class ValidationResource:
|
|
|
364
474
|
l2_only=l2_only,
|
|
365
475
|
provider=provider,
|
|
366
476
|
exchange=exchange,
|
|
477
|
+
statistical=statistical,
|
|
478
|
+
stylised_facts=stylised_facts,
|
|
479
|
+
impact=impact,
|
|
480
|
+
volume_correlation=volume_correlation,
|
|
481
|
+
fid=fid,
|
|
482
|
+
mind=mind,
|
|
483
|
+
lob=lob,
|
|
484
|
+
sample_period=sample_period,
|
|
485
|
+
match_generated_sample=match_generated_sample,
|
|
486
|
+
plots=plots,
|
|
487
|
+
historical_output=historical_output,
|
|
367
488
|
)
|
|
368
489
|
job_id = job["job_id"]
|
|
369
490
|
print(f"Validation job submitted: {job_id}", file=sys.stderr)
|
|
@@ -156,3 +156,113 @@ class TestRunPipeline:
|
|
|
156
156
|
)
|
|
157
157
|
assert result["status"] == "completed"
|
|
158
158
|
assert "spread" in result["distances"]
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class TestNewValidationOptions:
|
|
162
|
+
"""pulse-check 1.10.0 areas, sampling and plot selection.
|
|
163
|
+
|
|
164
|
+
Everything here is opt-in: a job naming none of it must send exactly the
|
|
165
|
+
config it sent before, so existing callers are untouched.
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
BASE = dict(
|
|
169
|
+
run_metrics=None,
|
|
170
|
+
run_impact=None,
|
|
171
|
+
run_inception_distances=True,
|
|
172
|
+
run_stylised_facts=None,
|
|
173
|
+
plot_data=None,
|
|
174
|
+
n_levels=10,
|
|
175
|
+
l2_only=False,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
def test_naming_nothing_new_is_unchanged(self):
|
|
179
|
+
from simudyne.resources.validation import _build_config
|
|
180
|
+
|
|
181
|
+
assert _build_config(**self.BASE) == {
|
|
182
|
+
"n_levels": 10,
|
|
183
|
+
"l2_only": False,
|
|
184
|
+
"run_fid": True,
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
@pytest.mark.parametrize(
|
|
188
|
+
"name,value",
|
|
189
|
+
[
|
|
190
|
+
("statistical", False),
|
|
191
|
+
("stylised_facts", True),
|
|
192
|
+
("impact", False),
|
|
193
|
+
("volume_correlation", True),
|
|
194
|
+
("fid", False),
|
|
195
|
+
("mind", True),
|
|
196
|
+
("lob", True),
|
|
197
|
+
("sample_period", "100ms"),
|
|
198
|
+
("match_generated_sample", True),
|
|
199
|
+
("historical_output", True),
|
|
200
|
+
("plots", ["volume_correlation.levels"]),
|
|
201
|
+
],
|
|
202
|
+
)
|
|
203
|
+
def test_option_is_forwarded(self, name, value):
|
|
204
|
+
from simudyne.resources.validation import _build_config
|
|
205
|
+
|
|
206
|
+
assert _build_config(**self.BASE, **{name: value})[name] == value
|
|
207
|
+
|
|
208
|
+
def test_unknown_option_is_rejected_by_name(self):
|
|
209
|
+
from simudyne.resources.validation import _build_config
|
|
210
|
+
|
|
211
|
+
with pytest.raises(ValueError, match="Unknown validation option"):
|
|
212
|
+
_build_config(**self.BASE, volume_corelation=True)
|
|
213
|
+
|
|
214
|
+
def test_none_is_omitted_not_sent_as_null(self):
|
|
215
|
+
"""Absence means 'use my tier's default'; null would not."""
|
|
216
|
+
from simudyne.resources.validation import _build_config
|
|
217
|
+
|
|
218
|
+
assert "volume_correlation" not in _build_config(
|
|
219
|
+
**self.BASE, volume_correlation=None
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class TestSimulatedFrameShapes:
|
|
224
|
+
"""run_upload takes a path, a (name, bytes) pair, or a DataFrame."""
|
|
225
|
+
|
|
226
|
+
def test_tuple_passes_through(self):
|
|
227
|
+
from simudyne.resources.validation import _frame_to_parquet
|
|
228
|
+
|
|
229
|
+
assert _frame_to_parquet(("a.parquet", b"raw"), 0) == ("a.parquet", b"raw")
|
|
230
|
+
|
|
231
|
+
def test_path(self, tmp_path):
|
|
232
|
+
import pandas as pd
|
|
233
|
+
|
|
234
|
+
from simudyne.resources.validation import _frame_to_parquet
|
|
235
|
+
|
|
236
|
+
p = tmp_path / "run.parquet"
|
|
237
|
+
pd.DataFrame({"a": [1, 2]}).to_parquet(p)
|
|
238
|
+
name, content = _frame_to_parquet(str(p), 0)
|
|
239
|
+
assert name == "run.parquet"
|
|
240
|
+
assert content[:4] == b"PAR1"
|
|
241
|
+
|
|
242
|
+
def test_pandas_frame(self):
|
|
243
|
+
import pandas as pd
|
|
244
|
+
|
|
245
|
+
from simudyne.resources.validation import _frame_to_parquet
|
|
246
|
+
|
|
247
|
+
name, content = _frame_to_parquet(pd.DataFrame({"a": [1, 2]}), 3)
|
|
248
|
+
assert name == "sim_3.parquet"
|
|
249
|
+
assert content[:4] == b"PAR1"
|
|
250
|
+
|
|
251
|
+
def test_polars_frame(self):
|
|
252
|
+
pl = pytest.importorskip("polars")
|
|
253
|
+
|
|
254
|
+
from simudyne.resources.validation import _frame_to_parquet
|
|
255
|
+
|
|
256
|
+
name, content = _frame_to_parquet(pl.DataFrame({"a": [1, 2]}), 1)
|
|
257
|
+
assert name == "sim_1.parquet"
|
|
258
|
+
assert content[:4] == b"PAR1"
|
|
259
|
+
|
|
260
|
+
def test_each_frame_gets_its_own_name(self):
|
|
261
|
+
"""Distinct names, or the multipart upload collapses the runs."""
|
|
262
|
+
import pandas as pd
|
|
263
|
+
|
|
264
|
+
from simudyne.resources.validation import _frame_to_parquet
|
|
265
|
+
|
|
266
|
+
frames = [pd.DataFrame({"a": [i]}) for i in range(3)]
|
|
267
|
+
names = [_frame_to_parquet(f, i)[0] for i, f in enumerate(frames)]
|
|
268
|
+
assert len(set(names)) == 3
|
|
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
|
{simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/src/simudyne/resources/historical.py
RENAMED
|
File without changes
|
|
File without changes
|
{simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/src/simudyne/resources/simulation.py
RENAMED
|
File without changes
|
{simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/src/simudyne/resources/simulator_gym.py
RENAMED
|
File without changes
|
{simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/src/simudyne_pulse.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/src/simudyne_pulse.egg-info/requires.txt
RENAMED
|
File without changes
|
{simudyne_pulse-0.7.0.dev3 → simudyne_pulse-0.7.0.dev4}/src/simudyne_pulse.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|