code-loader 1.0.179.dev1__tar.gz → 1.0.180__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 (37) hide show
  1. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/PKG-INFO +1 -1
  2. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/contract/datasetclasses.py +20 -8
  3. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/contract/responsedataclasses.py +7 -0
  4. code_loader-1.0.180/code_loader/contract/sim_config.py +92 -0
  5. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/inner_leap_binder/leapbinder.py +14 -1
  6. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/inner_leap_binder/leapbinder_decorators.py +143 -16
  7. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/leaploader.py +22 -2
  8. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/mixpanel_tracker.py +1 -0
  9. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/pyproject.toml +1 -1
  10. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/LICENSE +0 -0
  11. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/README.md +0 -0
  12. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/__init__.py +0 -0
  13. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/contract/__init__.py +0 -0
  14. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/contract/enums.py +0 -0
  15. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/contract/exceptions.py +0 -0
  16. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/contract/mapping.py +0 -0
  17. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/contract/visualizer_classes.py +0 -0
  18. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/default_losses.py +0 -0
  19. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/default_metrics.py +0 -0
  20. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/experiment_api/__init__.py +0 -0
  21. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/experiment_api/api.py +0 -0
  22. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/experiment_api/cli_config_utils.py +0 -0
  23. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/experiment_api/client.py +0 -0
  24. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/experiment_api/epoch.py +0 -0
  25. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/experiment_api/experiment.py +0 -0
  26. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/experiment_api/experiment_context.py +0 -0
  27. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/experiment_api/types.py +0 -0
  28. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/experiment_api/utils.py +0 -0
  29. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/experiment_api/workingspace_config_utils.py +0 -0
  30. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/inner_leap_binder/__init__.py +0 -0
  31. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/leaploaderbase.py +0 -0
  32. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/plot_functions/__init__.py +0 -0
  33. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/plot_functions/plot_functions.py +0 -0
  34. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/plot_functions/visualize.py +0 -0
  35. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/utils.py +0 -0
  36. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/visualizers/__init__.py +0 -0
  37. {code_loader-1.0.179.dev1 → code_loader-1.0.180}/code_loader/visualizers/default_visualizers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: code-loader
3
- Version: 1.0.179.dev1
3
+ Version: 1.0.180
4
4
  Summary:
5
5
  Home-page: https://github.com/tensorleap/code-loader
6
6
  License: MIT
@@ -9,9 +9,12 @@ from code_loader.contract.enums import DataStateType, DataStateEnum, LeapDataTyp
9
9
  MetricDirection, DatasetMetadataType
10
10
  from code_loader.contract.visualizer_classes import LeapImage, LeapText, LeapGraph, LeapHorizontalBar, \
11
11
  LeapTextMask, LeapImageMask, LeapImageWithBBox, LeapImageWithHeatmap, LeapVideo
12
+ from code_loader.contract.sim_config import SimConfig
12
13
 
13
14
  custom_latent_space_attribute = "custom_latent_space"
14
15
 
16
+ _simulation_context: dict = {"active": False}
17
+
15
18
 
16
19
  @dataclass
17
20
  class PreprocessResponse:
@@ -41,6 +44,7 @@ class PreprocessResponse:
41
44
  sample_id_type: Optional[Union[Type[str], Type[int]]] = None
42
45
  sample_ids_to_instance_mappings: Optional[Dict[str, List[str]]] = None # in use only for element instance
43
46
  instance_to_sample_ids_mappings: Optional[Dict[str, str]] = None # in use only for element instance
47
+ tl_generated: bool = False
44
48
 
45
49
  def __post_init__(self) -> None:
46
50
  assert self.sample_ids_to_instance_mappings is None, f"Keep sample_ids_to_instance_mappings None when initializing PreprocessResponse"
@@ -60,14 +64,14 @@ class PreprocessResponse:
60
64
  raise Exception("length is deprecated, please use sample_ids instead.")
61
65
 
62
66
  if self.state is None:
63
- from code_loader.inner_leap_binder.leapbinder_decorators import store_warning_by_param
64
- store_warning_by_param(
65
- param_name="PreprocessResponse.state",
66
- user_func_name="tensorleap_preprocess",
67
- default_value=str("specific order"),
68
- link_to_docs="https://docs.tensorleap.ai/tensorleap-integration/writing-integration-code/preprocess-function",
69
- )
70
-
67
+ if not _simulation_context["active"]:
68
+ from code_loader.inner_leap_binder.leapbinder_decorators import store_warning_by_param
69
+ store_warning_by_param(
70
+ param_name="PreprocessResponse.state",
71
+ user_func_name="tensorleap_preprocess",
72
+ default_value=str("specific order"),
73
+ link_to_docs="https://docs.tensorleap.ai/tensorleap-integration/writing-integration-code/preprocess-function",
74
+ )
71
75
  else:
72
76
  assert isinstance(self.state, DataStateType), f"PreprocessResponse.state must be of type {DataStateType.__name__} but got {type(self.state)}"
73
77
 
@@ -252,6 +256,13 @@ class CustomLayerHandler:
252
256
  use_custom_latent_space: bool = False
253
257
 
254
258
 
259
+ @dataclass
260
+ class SimulationHandler:
261
+ name: str
262
+ function: Callable[..., PreprocessResponse]
263
+ sim_config: SimConfig
264
+
265
+
255
266
  @dataclass
256
267
  class DatasetIntegrationSetup:
257
268
  preprocess: Optional[PreprocessHandler] = None
@@ -267,6 +278,7 @@ class DatasetIntegrationSetup:
267
278
  instance_metrics: List[InstanceMetricHandler] = field(default_factory=list)
268
279
  custom_layers: Dict[str, CustomLayerHandler] = field(default_factory=dict)
269
280
  custom_latent_space: Optional[CustomLatentSpaceHandler] = None
281
+ simulations: List[SimulationHandler] = field(default_factory=list)
270
282
 
271
283
 
272
284
  @dataclass
@@ -69,6 +69,12 @@ class PredictionTypeInstance:
69
69
  channel_dim: int
70
70
 
71
71
 
72
+ @dataclass
73
+ class SimulationInstance:
74
+ name: str
75
+ sim_config: Dict[str, Any]
76
+
77
+
72
78
  @dataclass
73
79
  class DatasetSetup:
74
80
  preprocess: DatasetPreprocess
@@ -79,6 +85,7 @@ class DatasetSetup:
79
85
  prediction_types: List[PredictionTypeInstance]
80
86
  custom_losses: List[CustomLossInstance]
81
87
  metrics: List[MetricInstance] = field(default_factory=list)
88
+ simulations: List[SimulationInstance] = field(default_factory=list)
82
89
 
83
90
 
84
91
  @dataclass
@@ -0,0 +1,92 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any, Dict, List, Union
3
+
4
+ from code_loader.contract.enums import DatasetMetadataType
5
+
6
+
7
+ @dataclass
8
+ class FloatBounds:
9
+ min: float
10
+ max: float
11
+
12
+
13
+ @dataclass
14
+ class IntBounds:
15
+ min: int
16
+ max: int
17
+
18
+
19
+ @dataclass
20
+ class CategoricalBounds:
21
+ values: List[str]
22
+
23
+
24
+ @dataclass
25
+ class SimParamConfig:
26
+ type: DatasetMetadataType
27
+ bounds: Union[FloatBounds, IntBounds, CategoricalBounds]
28
+
29
+
30
+ SimConfig = Dict[str, SimParamConfig]
31
+
32
+ PARAM_TYPE_MAP = {
33
+ float: DatasetMetadataType.float,
34
+ int: DatasetMetadataType.int,
35
+ str: DatasetMetadataType.string,
36
+ "float": DatasetMetadataType.float,
37
+ "int": DatasetMetadataType.int,
38
+ "str": DatasetMetadataType.string,
39
+ "string": DatasetMetadataType.string,
40
+ }
41
+
42
+
43
+ def _parse_bounds(name: str, metadata_type: DatasetMetadataType, bounds_raw: Dict[str, Any]) -> Union[FloatBounds, IntBounds, CategoricalBounds]:
44
+ if metadata_type in (DatasetMetadataType.float, DatasetMetadataType.int):
45
+ if "min" not in bounds_raw or "max" not in bounds_raw:
46
+ raise ValueError(
47
+ f"Parameter '{name}' bounds must have 'min' and 'max' for numeric type."
48
+ )
49
+ if bounds_raw["min"] >= bounds_raw["max"]:
50
+ raise ValueError(
51
+ f"Parameter '{name}': min ({bounds_raw['min']}) must be < max ({bounds_raw['max']})."
52
+ )
53
+ if metadata_type == DatasetMetadataType.float:
54
+ return FloatBounds(min=float(bounds_raw["min"]), max=float(bounds_raw["max"]))
55
+ return IntBounds(min=int(bounds_raw["min"]), max=int(bounds_raw["max"]))
56
+
57
+ if metadata_type == DatasetMetadataType.string:
58
+ if "values" not in bounds_raw:
59
+ raise ValueError(
60
+ f"Parameter '{name}' bounds must have 'values' for string type."
61
+ )
62
+ if not bounds_raw["values"]:
63
+ raise ValueError(f"Parameter '{name}' 'values' must not be empty.")
64
+ return CategoricalBounds(values=list(bounds_raw["values"]))
65
+
66
+ raise ValueError(f"Parameter '{name}' has unsupported metadata type: {metadata_type}.")
67
+
68
+
69
+ def parse_sim_config(raw: Dict[str, Any]) -> SimConfig:
70
+ if not raw:
71
+ raise ValueError("sim_config must have at least one parameter.")
72
+
73
+ result: SimConfig = {}
74
+ for name, param in raw.items():
75
+ if "type" not in param:
76
+ raise ValueError(f"Parameter '{name}' missing 'type'.")
77
+ if param["type"] not in PARAM_TYPE_MAP:
78
+ raise ValueError(
79
+ f"Parameter '{name}' has unsupported type '{param['type']}'. "
80
+ f"Supported: float, int, str (or string)."
81
+ )
82
+ metadata_type = PARAM_TYPE_MAP[param["type"]]
83
+
84
+ if "bounds" not in param:
85
+ raise ValueError(f"Parameter '{name}' missing 'bounds'.")
86
+
87
+ result[name] = SimParamConfig(
88
+ type=metadata_type,
89
+ bounds=_parse_bounds(name, metadata_type, param["bounds"]),
90
+ )
91
+
92
+ return result
@@ -12,7 +12,8 @@ from code_loader.contract.datasetclasses import SectionCallableInterface, InputH
12
12
  CustomCallableInterfaceMultiArgs, ConfusionMatrixCallableInterfaceMultiArgs, LeapData, \
13
13
  CustomMultipleReturnCallableInterfaceMultiArgs, DatasetBaseHandler, custom_latent_space_attribute, \
14
14
  RawInputsForHeatmap, VisualizerHandlerData, MetricHandlerData, CustomLossHandlerData, SamplePreprocessResponse, \
15
- ElementInstanceMasksHandler, InstanceCallableInterface, CustomLatentSpaceHandler, InstanceMetricHandler
15
+ ElementInstanceMasksHandler, InstanceCallableInterface, CustomLatentSpaceHandler, InstanceMetricHandler, \
16
+ SimulationHandler
16
17
  from code_loader.contract.enums import LeapDataType, DataStateEnum, DataStateType, MetricDirection, DatasetMetadataType
17
18
  from code_loader.contract.mapping import NodeConnection, NodeMapping, NodeMappingType
18
19
  from code_loader.contract.responsedataclasses import DatasetTestResultPayload, LeapAnalysisConfiguration
@@ -20,6 +21,7 @@ from code_loader.contract.visualizer_classes import map_leap_data_type_to_visual
20
21
  from code_loader.default_losses import loss_name_to_function
21
22
  from code_loader.default_metrics import metrics_names_to_functions_and_direction
22
23
  from code_loader.utils import to_numpy_return_wrapper, get_shape, to_numpy_return_masks_wrapper
24
+ from code_loader.contract.sim_config import parse_sim_config
23
25
  from code_loader.visualizers.default_visualizers import DefaultVisualizer, \
24
26
  default_graph_visualizer, \
25
27
  default_image_visualizer, default_horizontal_bar_visualizer, default_word_visualizer, \
@@ -219,6 +221,17 @@ class LeapBinder:
219
221
  """
220
222
  self.setup_container.unlabeled_data_preprocess = UnlabeledDataPreprocessHandler(function)
221
223
 
224
+ def set_simulation(self, function: Callable[..., PreprocessResponse], name: str, sim_config_raw: Dict[str, Any]) -> None:
225
+ for sim in self.setup_container.simulations:
226
+ if sim.name == name:
227
+ raise Exception(
228
+ f"Simulation with name '{name}' already exists. Please choose another."
229
+ )
230
+ sim_config = parse_sim_config(sim_config_raw)
231
+ self.setup_container.simulations.append(
232
+ SimulationHandler(name=name, function=function, sim_config=sim_config)
233
+ )
234
+
222
235
  def set_input(self, function: SectionCallableInterface, name: str, channel_dim: int = -1) -> None:
223
236
  """
224
237
  Set the input handler function.
@@ -1,8 +1,10 @@
1
1
  # mypy: ignore-errors
2
+ import inspect
2
3
  import os
3
4
  import warnings
4
5
  import logging
5
6
  from collections import defaultdict
7
+ import functools
6
8
  from functools import lru_cache
7
9
  from pathlib import Path
8
10
  from typing import Optional, Union, Callable, List, Dict, Set, Any
@@ -28,10 +30,6 @@ from code_loader.contract.visualizer_classes import LeapImage, LeapImageMask, Le
28
30
  from code_loader.inner_leap_binder.leapbinder import mapping_runtime_mode_env_var_mame
29
31
  from code_loader.mixpanel_tracker import clear_integration_events, AnalyticsEvent, emit_integration_event_once
30
32
 
31
- import inspect
32
- import functools
33
- from pathlib import Path
34
-
35
33
  _called_from_inside_tl_decorator = 0
36
34
  _called_from_inside_tl_integration_test_decorator = False
37
35
  _call_from_tl_platform = os.environ.get('IS_TENSORLEAP_PLATFORM') == 'true'
@@ -77,6 +75,13 @@ def store_warning_by_param(
77
75
  _PARAM_DEFAULT_DOCS[param_name] = link_to_docs
78
76
 
79
77
 
78
+ def store_general_warning(*, key: tuple, message: str, link_to_docs: Optional[str] = None) -> None:
79
+ if key in _STORED_WARNING_KEYS:
80
+ return
81
+ _STORED_WARNING_KEYS.add(key)
82
+ _STORED_WARNINGS.append({"message": message, "link_to_docs": link_to_docs})
83
+
84
+
80
85
  def _get_param_default_warnings() -> Dict[str, Dict[str, Any]]:
81
86
  out: Dict[str, Dict[str, Any]] = {}
82
87
  for p, funcs in _PARAM_DEFAULT_FUNCS.items():
@@ -1226,8 +1231,9 @@ def tensorleap_metadata(
1226
1231
  def _validate_result(result):
1227
1232
  supported_result_types = (type(None), int, str, bool, float, dict, np.floating,
1228
1233
  np.bool_, np.unsignedinteger, np.signedinteger, np.integer)
1229
- validate_output_structure(result, func_name=user_function.__name__,
1230
- expected_type_name=supported_result_types)
1234
+ if isinstance(result, tuple):
1235
+ validate_output_structure(result, func_name=user_function.__name__,
1236
+ expected_type_name=supported_result_types)
1231
1237
  assert isinstance(result, supported_result_types), \
1232
1238
  (f'{user_function.__name__}() validation failed: '
1233
1239
  f'Unsupported return type. Got {type(result)}. should be any of {str(supported_result_types)}')
@@ -1295,17 +1301,17 @@ def tensorleap_custom_latent_space():
1295
1301
  (f'tensorleap_custom_latent_space validation failed: '
1296
1302
  f'The return type should be a numpy array. Got {type(result)}.')
1297
1303
  if result.ndim > 1:
1298
- warning_key = ("tensorleap_custom_latent_space_flatten", tuple(result.shape))
1299
- if warning_key not in _STORED_WARNING_KEYS:
1300
- _STORED_WARNING_KEYS.add(warning_key)
1301
- flat_dim = int(np.prod(result.shape))
1302
- warnings.warn(
1304
+ flat_dim = int(np.prod(result.shape))
1305
+ store_general_warning(
1306
+ key=("tensorleap_custom_latent_space_flatten", tuple(result.shape)),
1307
+ message=(
1303
1308
  f"tensorleap_custom_latent_space returned per-sample shape {tuple(result.shape)} "
1304
1309
  f"(ndim={result.ndim}). Tensorleap assumes per-sample shape (d, ...) and will "
1305
1310
  f"flatten to ({flat_dim},) before downstream visualization and clustering. "
1306
1311
  f"If you want a different aggregation (e.g. global average pooling), do it "
1307
1312
  f"inside your function."
1308
- )
1313
+ ),
1314
+ )
1309
1315
 
1310
1316
  def inner_without_validate(sample_id, preprocess_response):
1311
1317
  global _called_from_inside_tl_decorator
@@ -1393,6 +1399,89 @@ def tensorleap_preprocess():
1393
1399
  return decorating_function
1394
1400
 
1395
1401
 
1402
+ def tensorleap_simulation(name: str, sim_params: dict):
1403
+ def decorating_function(user_function: Callable[..., PreprocessResponse]):
1404
+ sig = inspect.signature(user_function)
1405
+ func_params = set(sig.parameters.keys())
1406
+ expected_params = set(sim_params.keys()) | {"N"}
1407
+
1408
+ missing = expected_params - func_params
1409
+ if missing:
1410
+ raise Exception(
1411
+ f"{user_function.__name__}() registration failed: "
1412
+ f"Missing required parameters: {missing}. "
1413
+ f"Function must accept all sim_params params plus 'N'."
1414
+ )
1415
+
1416
+ extra = func_params - expected_params - {"seed"}
1417
+ if extra:
1418
+ raise Exception(
1419
+ f"{user_function.__name__}() registration failed: "
1420
+ f"Unexpected parameters: {extra}. "
1421
+ f"Function must only accept sim_params params plus 'N' and optionally 'seed'."
1422
+ )
1423
+
1424
+ leap_binder.set_simulation(user_function, name, sim_params)
1425
+
1426
+ if not _call_from_tl_platform:
1427
+ register_sim(name)
1428
+
1429
+ def _validate_input_args(*args, **kwargs):
1430
+ assert len(args) == 0, (
1431
+ f"{user_function.__name__}() validation failed: "
1432
+ f"Simulation functions must be called with keyword arguments only. Got positional args: {args}."
1433
+ )
1434
+ missing = expected_params - set(kwargs.keys())
1435
+ assert not missing, (
1436
+ f"{user_function.__name__}() validation failed: "
1437
+ f"Missing required keyword arguments: {missing}."
1438
+ )
1439
+
1440
+ def _validate_result(result):
1441
+ assert isinstance(result, PreprocessResponse), (
1442
+ f"{user_function.__name__}() validation failed: "
1443
+ f"Expected return type PreprocessResponse. Got {type(result).__name__}."
1444
+ )
1445
+
1446
+ def inner(*args, **kwargs):
1447
+ if not _call_from_tl_platform:
1448
+ set_current('tensorleap simulation')
1449
+ try:
1450
+ from code_loader.contract.datasetclasses import _simulation_context
1451
+ _validate_input_args(*args, **kwargs)
1452
+ _simulation_context["active"] = True
1453
+ try:
1454
+ result = user_function(*args, **kwargs)
1455
+ finally:
1456
+ _simulation_context["active"] = False
1457
+ _validate_result(result)
1458
+ if result.state is not None and result.state != DataStateType.additional:
1459
+ logger.warning(
1460
+ f"{user_function.__name__}() returned state={result.state!r}; "
1461
+ f"overriding to DataStateType.additional."
1462
+ )
1463
+ result.state = DataStateType.additional
1464
+ result.tl_generated = True
1465
+ try:
1466
+ emit_integration_event_once(AnalyticsEvent.SIMULATION_INTEGRATION_TEST, {
1467
+ 'simulation_name': name,
1468
+ 'sim_params_count': len(sim_params)
1469
+ })
1470
+ except Exception as e:
1471
+ logger.debug(f"Failed to emit simulation integration test event: {e}")
1472
+ if not _call_from_tl_platform:
1473
+ mark_sim_result(name, True)
1474
+ return result
1475
+ except Exception:
1476
+ if not _call_from_tl_platform:
1477
+ mark_sim_result(name, False)
1478
+ raise
1479
+
1480
+ return inner
1481
+
1482
+ return decorating_function
1483
+
1484
+
1396
1485
  def tensorleap_element_instance_preprocess(
1397
1486
  instance_length_encoder: InstanceLengthCallableInterface, instance_mask_encoder: InstanceCallableInterface):
1398
1487
  def decorating_function(user_function: Callable[[], List[PreprocessResponse]]):
@@ -1989,7 +2078,8 @@ def tensorleap_status_table():
1989
2078
 
1990
2079
  def _print_param_default_warnings():
1991
2080
  data = _get_param_default_warnings()
1992
- if not data:
2081
+ stored = _get_stored_warnings()
2082
+ if not data and not stored:
1993
2083
  return
1994
2084
 
1995
2085
  print("\nWarnings (Default use. It is recommended to set values explicitly):")
@@ -2003,11 +2093,19 @@ def tensorleap_status_table():
2003
2093
  print(
2004
2094
  f" ⚠️ Parameter '{param_name}' defaults to {dv} in the following functions: [{funcs}]. "
2005
2095
  f"For more information, check {docs_part}")
2006
- print("\nIf this isn’t the intended behaviour, set them explicitly.")
2096
+ for w in stored:
2097
+ docs_link = w.get("link_to_docs")
2098
+ docs_part = f" {_link(docs_link)}" if docs_link else ""
2099
+ print(f" ⚠️ {w['message']}{docs_part}")
2100
+ if data:
2101
+ print("\nIf this isn’t the intended behaviour, set them explicitly.")
2007
2102
 
2008
2103
  def _print_table():
2009
2104
  _print_param_default_warnings()
2010
2105
 
2106
+ for msg in _sim_messages:
2107
+ print(f"\n⚠️ {msg}")
2108
+
2011
2109
  if not started_from("leap_integration.py"):
2012
2110
  return
2013
2111
 
@@ -2060,12 +2158,41 @@ def tensorleap_status_table():
2060
2158
  else:
2061
2159
  _set_status(name, CROSS)
2062
2160
 
2161
+ _sim_tracking: dict = {}
2162
+ _sim_messages: list = []
2163
+
2164
+ def register_sim(sim_name: str):
2165
+ if not _sim_tracking:
2166
+ table.append({"name": "tensorleap simulation (optional)", "Added to integration": UNKNOWN})
2167
+ _sim_tracking[sim_name] = "registered"
2168
+
2169
+ def mark_sim_result(sim_name: str, passed: bool):
2170
+ _sim_tracking[sim_name] = "passed" if passed else "failed"
2171
+
2172
+ def _resolve_sim_row():
2173
+ if not _sim_tracking:
2174
+ return
2175
+ row = _find_row("tensorleap simulation")
2176
+ if not row:
2177
+ return
2178
+ failed = [n for n, s in _sim_tracking.items() if s == "failed"]
2179
+ not_called = [n for n, s in _sim_tracking.items() if s == "registered"]
2180
+ if failed:
2181
+ row["Added to integration"] = CROSS
2182
+ _sim_messages.append(f"Simulation(s) failed validation: {', '.join(sorted(failed))}")
2183
+ elif not_called:
2184
+ row["Added to integration"] = UNKNOWN
2185
+ _sim_messages.append(f"Simulation(s) not called in integration test: {', '.join(sorted(not_called))}")
2186
+ else:
2187
+ row["Added to integration"] = CHECK
2188
+
2063
2189
  def run_on_exit():
2064
2190
  if _finalizer_called["done"]:
2065
2191
  return
2066
2192
  _finalizer_called["done"] = True
2067
2193
  if not _crashed["value"]:
2068
2194
  _mark_unknowns_as_cross()
2195
+ _resolve_sim_row()
2069
2196
 
2070
2197
  _print_table()
2071
2198
 
@@ -2083,11 +2210,11 @@ def tensorleap_status_table():
2083
2210
  atexit.register(run_on_exit)
2084
2211
  sys.excepthook = handle_exception
2085
2212
 
2086
- return set_current, update_env_params
2213
+ return set_current, update_env_params, register_sim, mark_sim_result
2087
2214
 
2088
2215
 
2089
2216
  if not _call_from_tl_platform:
2090
- set_current, update_env_params_func = tensorleap_status_table()
2217
+ set_current, update_env_params_func, register_sim, mark_sim_result = tensorleap_status_table()
2091
2218
 
2092
2219
 
2093
2220
 
@@ -22,13 +22,22 @@ from code_loader.contract.exceptions import DatasetScriptException
22
22
  from code_loader.contract.responsedataclasses import DatasetIntegParseResult, DatasetTestResultPayload, \
23
23
  DatasetPreprocess, DatasetSetup, DatasetInputInstance, DatasetOutputInstance, DatasetMetadataInstance, \
24
24
  VisualizerInstance, PredictionTypeInstance, ModelSetup, CustomLayerInstance, MetricInstance, CustomLossInstance, \
25
- EngineFileContract
25
+ EngineFileContract, SimulationInstance
26
+ from code_loader.contract.sim_config import FloatBounds, IntBounds, CategoricalBounds
26
27
  from code_loader.inner_leap_binder import global_leap_binder
27
28
  from code_loader.inner_leap_binder.leapbinder import mapping_runtime_mode_env_var_mame
28
29
  from code_loader.leaploaderbase import LeapLoaderBase
29
30
  from code_loader.utils import get_root_exception_file_and_line_number, get_metadata_type_from_variable
30
31
 
31
32
 
33
+ def _serialize_sim_bounds(bounds) -> dict:
34
+ if isinstance(bounds, (FloatBounds, IntBounds)):
35
+ return {"min": bounds.min, "max": bounds.max}
36
+ if isinstance(bounds, CategoricalBounds):
37
+ return {"values": bounds.values}
38
+ raise ValueError(f"Unknown bounds type: {type(bounds)}")
39
+
40
+
32
41
  class LeapLoader(LeapLoaderBase):
33
42
  def __init__(self, code_path: str, code_entry_name: str):
34
43
  super().__init__(code_path, code_entry_name)
@@ -454,9 +463,20 @@ class LeapLoader(LeapLoaderBase):
454
463
  metric_inst = MetricInstance(metric.metric_handler_data.name, metric.metric_handler_data.arg_names)
455
464
  metrics.append(metric_inst)
456
465
 
466
+ simulations = []
467
+ for sim in setup.simulations:
468
+ sim_config_serialized = {
469
+ name: {
470
+ "type": param.type.value,
471
+ "bounds": _serialize_sim_bounds(param.bounds),
472
+ }
473
+ for name, param in sim.sim_config.items()
474
+ }
475
+ simulations.append(SimulationInstance(name=sim.name, sim_config=sim_config_serialized))
476
+
457
477
  return DatasetSetup(preprocess=dataset_preprocess, inputs=inputs, outputs=ground_truths,
458
478
  metadata=metadata_instances, visualizers=visualizers, prediction_types=prediction_types,
459
- custom_losses=custom_losses, metrics=metrics)
479
+ custom_losses=custom_losses, metrics=metrics, simulations=simulations)
460
480
 
461
481
  def get_model_setup_response(self) -> ModelSetup:
462
482
  setup = global_leap_binder.setup_container
@@ -23,6 +23,7 @@ class AnalyticsEvent(str, Enum):
23
23
  PREPROCESS_INTEGRATION_TEST = "preprocess_integration_test"
24
24
  INPUT_ENCODER_INTEGRATION_TEST = "input_encoder_integration_test"
25
25
  GT_ENCODER_INTEGRATION_TEST = "gt_encoder_integration_test"
26
+ SIMULATION_INTEGRATION_TEST = "simulation_integration_test"
26
27
 
27
28
 
28
29
  class CodeLoaderLoadedProps(TypedDict, total=False):
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "code-loader"
3
- version = "1.0.179.dev1"
3
+ version = "1.0.180"
4
4
  description = ""
5
5
  authors = ["dorhar <doron.harnoy@tensorleap.ai>"]
6
6
  license = "MIT"