phyelds 2.2.0__tar.gz → 2.3.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. {phyelds-2.2.0 → phyelds-2.3.0}/PKG-INFO +3 -1
  2. {phyelds-2.2.0 → phyelds-2.3.0}/pyproject.toml +3 -2
  3. {phyelds-2.2.0 → phyelds-2.3.0}/src/phyelds/libraries/device.py +5 -5
  4. phyelds-2.3.0/src/phyelds/simulator/exporter.py +75 -0
  5. {phyelds-2.2.0 → phyelds-2.3.0}/src/phyelds/simulator/runner.py +2 -1
  6. {phyelds-2.2.0 → phyelds-2.3.0}/LICENSE +0 -0
  7. {phyelds-2.2.0 → phyelds-2.3.0}/README.md +0 -0
  8. {phyelds-2.2.0 → phyelds-2.3.0}/src/phyelds/__init__.py +0 -0
  9. {phyelds-2.2.0 → phyelds-2.3.0}/src/phyelds/abstractions/__init__.py +0 -0
  10. {phyelds-2.2.0 → phyelds-2.3.0}/src/phyelds/calculus/__init__.py +0 -0
  11. {phyelds-2.2.0 → phyelds-2.3.0}/src/phyelds/calculus/align.py +0 -0
  12. {phyelds-2.2.0 → phyelds-2.3.0}/src/phyelds/data/__init__.py +0 -0
  13. {phyelds-2.2.0 → phyelds-2.3.0}/src/phyelds/internal/__init__.py +0 -0
  14. {phyelds-2.2.0 → phyelds-2.3.0}/src/phyelds/libraries/__init__.py +0 -0
  15. {phyelds-2.2.0 → phyelds-2.3.0}/src/phyelds/libraries/collect.py +0 -0
  16. {phyelds-2.2.0 → phyelds-2.3.0}/src/phyelds/libraries/distances.py +0 -0
  17. {phyelds-2.2.0 → phyelds-2.3.0}/src/phyelds/libraries/leader_election.py +0 -0
  18. {phyelds-2.2.0 → phyelds-2.3.0}/src/phyelds/libraries/spreading.py +0 -0
  19. {phyelds-2.2.0 → phyelds-2.3.0}/src/phyelds/libraries/time.py +0 -0
  20. {phyelds-2.2.0 → phyelds-2.3.0}/src/phyelds/libraries/utils.py +0 -0
  21. {phyelds-2.2.0 → phyelds-2.3.0}/src/phyelds/simulator/__init__.py +0 -0
  22. {phyelds-2.2.0 → phyelds-2.3.0}/src/phyelds/simulator/actions.py +0 -0
  23. {phyelds-2.2.0 → phyelds-2.3.0}/src/phyelds/simulator/deployments.py +0 -0
  24. {phyelds-2.2.0 → phyelds-2.3.0}/src/phyelds/simulator/neighborhood.py +0 -0
  25. {phyelds-2.2.0 → phyelds-2.3.0}/src/phyelds/simulator/render.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: phyelds
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: A lightweight Python library to support aggregate computing!
5
5
  Home-page: https://github.com/phyelds/phyelds
6
6
  License: Apache-2.0
@@ -12,6 +12,8 @@ Classifier: Operating System :: OS Independent
12
12
  Classifier: Programming Language :: Python :: 3
13
13
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
14
  Requires-Dist: matplotlib (>=3.10.1,<3.11.0)
15
+ Requires-Dist: pandas (>=2.2.3,<3.0.0)
16
+ Requires-Dist: pre-commit (>=4.2.0,<5.0.0)
15
17
  Requires-Dist: wrapt (>=1.17.2,<1.18.0)
16
18
  Project-URL: Changelog, https://github.com/phyelds/phyelds/blob/master/CHANGELOG.md
17
19
  Project-URL: Repository, https://github.com/phyelds/phyelds
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "phyelds"
3
- version = "2.2.0"
3
+ version = "2.3.0"
4
4
  description = "A lightweight Python library to support aggregate computing!"
5
5
  license = "Apache-2.0"
6
6
  authors = [
@@ -39,6 +39,8 @@ build-backend = "poetry.core.masonry.api"
39
39
  python = "^3.13"
40
40
  wrapt = ">=1.17.2,<1.18.0"
41
41
  matplotlib = ">=3.10.1,<3.11.0"
42
+ pre-commit = "^4.2.0"
43
+ pandas = "^2.2.3"
42
44
 
43
45
  [tool.poetry.group.dev.dependencies]
44
46
  pytest = "^8.0.0"
@@ -46,4 +48,3 @@ pylint = "^3.0.0"
46
48
  pytest-cov = "^3.0.0"
47
49
  ruff = "^0.3.0"
48
50
  flake8 = "^7.0.0"
49
-
@@ -30,11 +30,11 @@ def local_position():
30
30
  return engine.get().node_context.position()
31
31
 
32
32
 
33
- def store(sensor: str, value: any):
33
+ def store(output: str, value: any):
34
34
  """
35
- Store the value of the sensor.
36
- :param sensor: The name of the sensor.
37
- :param value: The value of the sensor.
35
+ Store the value in the context.
36
+ :param output: The name of the stored value.
37
+ :param value: The value to be stored.
38
38
  :return:
39
39
  """
40
- engine.get().node_context.store(sensor, value)
40
+ engine.get().node_context.store(output, value)
@@ -0,0 +1,75 @@
1
+ """Exporter module for saving simulation metrics to CSV during simulation."""
2
+
3
+ import os
4
+ import itertools
5
+ from pathlib import Path
6
+ from dataclasses import dataclass, replace
7
+ import pandas as pd
8
+ from phyelds.simulator import Simulator
9
+
10
+
11
+ __all__ = ["csv_exporter", "ExporterConfig"]
12
+
13
+
14
+ @dataclass
15
+ class ExporterConfig:
16
+ """Configuration for CSV exporting of simulation metrics."""
17
+ output_directory: str
18
+ experiment_name: str
19
+ metrics: list[str]
20
+ aggregators: list[str]
21
+ precision: int
22
+ initial: bool = True
23
+
24
+
25
+ def csv_exporter(
26
+ simulator: Simulator, time_delta: float, config: ExporterConfig, **kwargs
27
+ ):
28
+ """Exporter module for saving simulation metrics to CSV."""
29
+ file_path = f'{config.output_directory}{config.experiment_name}.csv'
30
+ if not os.path.exists(file_path) or config.initial:
31
+ Path(config.output_directory).mkdir(parents=True, exist_ok=True)
32
+ df = init_dataframe(config, file_path)
33
+ else:
34
+ df = pd.read_csv(file_path)
35
+ new_data = {}
36
+ nodes = simulator.environment.nodes.values()
37
+ for metric in config.metrics:
38
+ nodes_data = [node.data['outputs'][metric] for node in nodes]
39
+ for aggregator in config.aggregators:
40
+ column = f'{metric}_{aggregator}'
41
+ new_data[column] = aggregate_values(nodes_data, config.precision, aggregator)
42
+ print(new_data)
43
+ df = pd.concat([df, pd.DataFrame([new_data])], ignore_index=True)
44
+ df.to_csv(file_path, mode='w', index=False)
45
+ config = replace(config, initial=False)
46
+ simulator.schedule_event(
47
+ time_delta, csv_exporter, simulator, time_delta, config, **kwargs
48
+ )
49
+
50
+
51
+ def aggregate_values(values: list[float], precision, aggregator: str):
52
+ """Aggregates a list of values using the given method and rounds to the specified precision."""
53
+ if aggregator == "mean":
54
+ value = sum(values) / len(values)
55
+ elif aggregator == "std":
56
+ mean = sum(values) / len(values)
57
+ value = sum((v - mean) ** 2 for v in values) / len(values)
58
+ elif aggregator == "min":
59
+ value = min(values)
60
+ elif aggregator == "max":
61
+ value = max(values)
62
+ else:
63
+ raise ValueError(f"Invalid aggregator: {aggregator}")
64
+ return round(value, precision)
65
+
66
+
67
+ def init_dataframe(config, file_path):
68
+ """Initializes an empty DataFrame with appropriate columns based on config."""
69
+ columns = list(f'{s}_{a}' for s, a in itertools.product(config.metrics, config.aggregators))
70
+ print(columns)
71
+ try:
72
+ os.remove(file_path)
73
+ except OSError:
74
+ pass
75
+ return pd.DataFrame(columns=columns).astype('float64')
@@ -55,8 +55,9 @@ def aggregate_program_runner(
55
55
  node.data["result"] = result
56
56
  node.data["messages"] = engine.get().cooldown()
57
57
  node.data["state"] = engine.get().state_trace()
58
+ node.data["outputs"] = engine.get().node_context.outputs
58
59
  simulator.schedule_event(
59
- time_delta, aggregate_program_runner, simulator, time_delta, node, program
60
+ time_delta, aggregate_program_runner, simulator, time_delta, node, program, **kwargs
60
61
  )
61
62
 
62
63
 
File without changes
File without changes
File without changes