shepherd-core 2025.8.1__py3-none-any.whl → 2025.10.1__py3-none-any.whl

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 (38) hide show
  1. shepherd_core/data_models/__init__.py +4 -2
  2. shepherd_core/data_models/base/content.py +2 -0
  3. shepherd_core/data_models/content/__init__.py +4 -2
  4. shepherd_core/data_models/content/{virtual_harvester.py → virtual_harvester_config.py} +3 -3
  5. shepherd_core/data_models/content/{virtual_source.py → virtual_source_config.py} +82 -58
  6. shepherd_core/data_models/content/virtual_source_fixture.yaml +24 -24
  7. shepherd_core/data_models/content/virtual_storage_config.py +426 -0
  8. shepherd_core/data_models/content/virtual_storage_fixture_creator.py +267 -0
  9. shepherd_core/data_models/content/virtual_storage_fixture_ideal.yaml +637 -0
  10. shepherd_core/data_models/content/virtual_storage_fixture_lead.yaml +49 -0
  11. shepherd_core/data_models/content/virtual_storage_fixture_lipo.yaml +735 -0
  12. shepherd_core/data_models/content/virtual_storage_fixture_mlcc.yaml +200 -0
  13. shepherd_core/data_models/content/virtual_storage_fixture_param_experiments.py +151 -0
  14. shepherd_core/data_models/content/virtual_storage_fixture_super.yaml +150 -0
  15. shepherd_core/data_models/content/virtual_storage_fixture_tantal.yaml +550 -0
  16. shepherd_core/data_models/experiment/target_config.py +1 -1
  17. shepherd_core/data_models/task/emulation.py +1 -1
  18. shepherd_core/data_models/task/harvest.py +1 -1
  19. shepherd_core/decoder_waveform/uart.py +1 -1
  20. shepherd_core/inventory/system.py +1 -1
  21. shepherd_core/reader.py +4 -3
  22. shepherd_core/version.py +1 -1
  23. shepherd_core/vsource/__init__.py +4 -0
  24. shepherd_core/vsource/virtual_converter_model.py +27 -26
  25. shepherd_core/vsource/virtual_harvester_model.py +27 -19
  26. shepherd_core/vsource/virtual_harvester_simulation.py +38 -39
  27. shepherd_core/vsource/virtual_source_model.py +17 -13
  28. shepherd_core/vsource/virtual_source_simulation.py +71 -73
  29. shepherd_core/vsource/virtual_storage_model.py +164 -0
  30. shepherd_core/vsource/virtual_storage_model_fixed_point_math.py +58 -0
  31. shepherd_core/vsource/virtual_storage_models_kibam.py +449 -0
  32. shepherd_core/vsource/virtual_storage_simulator.py +104 -0
  33. {shepherd_core-2025.8.1.dist-info → shepherd_core-2025.10.1.dist-info}/METADATA +2 -1
  34. {shepherd_core-2025.8.1.dist-info → shepherd_core-2025.10.1.dist-info}/RECORD +37 -25
  35. shepherd_core/data_models/virtual_source_doc.txt +0 -207
  36. {shepherd_core-2025.8.1.dist-info → shepherd_core-2025.10.1.dist-info}/WHEEL +0 -0
  37. {shepherd_core-2025.8.1.dist-info → shepherd_core-2025.10.1.dist-info}/top_level.txt +0 -0
  38. {shepherd_core-2025.8.1.dist-info → shepherd_core-2025.10.1.dist-info}/zip-safe +0 -0
@@ -17,8 +17,11 @@ Compromises:
17
17
  import math
18
18
 
19
19
  from shepherd_core.data_models import CalibrationEmulator
20
- from shepherd_core.data_models.content.virtual_source import LUT_SIZE
21
- from shepherd_core.data_models.content.virtual_source import ConverterPRUConfig
20
+ from shepherd_core.data_models.content.virtual_source_config import LUT_SIZE
21
+ from shepherd_core.data_models.content.virtual_source_config import ConverterPRUConfig
22
+ from shepherd_core.data_models.content.virtual_storage_config import StoragePRUConfig
23
+
24
+ from .virtual_storage_model import VirtualStorageModelPRU
22
25
 
23
26
 
24
27
  class PruCalibration:
@@ -59,13 +62,16 @@ class PruCalibration:
59
62
  class VirtualConverterModel:
60
63
  """Ported python version of the pru vCnv."""
61
64
 
62
- def __init__(self, cfg: ConverterPRUConfig, cal: PruCalibration) -> None:
65
+ def __init__(
66
+ self, cfg: ConverterPRUConfig, cal: PruCalibration, storage_cfg: StoragePRUConfig
67
+ ) -> None:
63
68
  self._cal: PruCalibration = cal
64
69
  self._cfg: ConverterPRUConfig = cfg
65
70
 
71
+ self.storage = VirtualStorageModelPRU(storage_cfg)
72
+
66
73
  # simplifications for python
67
74
  self.R_input_kOhm = float(self._cfg.R_input_kOhm_n22) / 2**22
68
- self.Constant_us_per_nF = float(self._cfg.Constant_us_per_nF_n28) / 2**28
69
75
 
70
76
  # boost internal state
71
77
  self.V_input_uV: float = 0.0
@@ -74,7 +80,7 @@ class VirtualConverterModel:
74
80
  self.interval_startup_disabled_drain_n: int = self._cfg.interval_startup_delay_drain_n
75
81
 
76
82
  # container for the stored energy
77
- self.V_mid_uV: float = self._cfg.V_intermediate_init_uV
83
+ self.V_mid_uV: float = self.storage.calc_V_OC_uV()
78
84
 
79
85
  # buck internal state
80
86
  self.enable_storage: bool = (int(self._cfg.converter_mode) & 0b0001) > 0
@@ -83,19 +89,19 @@ class VirtualConverterModel:
83
89
  self.enable_log_mid: bool = (int(self._cfg.converter_mode) & 0b1000) > 0
84
90
  # back-channel to hrv
85
91
  self.feedback_to_hrv: bool = (int(self._cfg.converter_mode) & 0b1_0000) > 0
86
- self.V_input_request_uV: int = self._cfg.V_intermediate_init_uV
92
+ self.V_input_request_uV: int = int(self.V_mid_uV)
87
93
 
88
94
  self.V_out_dac_uV: float = self._cfg.V_output_uV
89
95
  self.V_out_dac_raw: int = self._cal.conv_uV_to_dac_raw(self._cfg.V_output_uV)
90
96
  self.power_good: bool = True
91
97
 
92
98
  # prepare hysteresis-thresholds
93
- self.dV_enable_output_uV: float = self._cfg.dV_enable_output_uV
94
- self.V_enable_output_threshold_uV: float = self._cfg.V_enable_output_threshold_uV
95
- self.V_disable_output_threshold_uV: float = self._cfg.V_disable_output_threshold_uV
99
+ self.dV_mid_enable_output_uV: float = self._cfg.dV_mid_enable_output_uV
100
+ self.V_mid_enable_output_threshold_uV: float = self._cfg.V_mid_enable_output_threshold_uV
101
+ self.V_mid_disable_output_threshold_uV: float = self._cfg.V_mid_disable_output_threshold_uV
96
102
 
97
- self.V_enable_output_threshold_uV = max(
98
- self.dV_enable_output_uV, self.V_enable_output_threshold_uV
103
+ self.V_mid_enable_output_threshold_uV = max(
104
+ self.dV_mid_enable_output_uV, self.V_mid_enable_output_threshold_uV
99
105
  )
100
106
 
101
107
  # pulled from update_states_and_output() due to easier static init
@@ -125,7 +131,7 @@ class VirtualConverterModel:
125
131
  input_voltage_uV = 0.0
126
132
  # TODO: vdrop in case of v_input > v_storage (non-boost)
127
133
  elif self.enable_storage:
128
- # no boost, but cap, for ie. diode+cap (+resistor)
134
+ # no boost, but cap, for i.e. diode+cap (+resistor)
129
135
  V_diff_uV = (
130
136
  (input_voltage_uV - self.V_mid_uV) if (input_voltage_uV >= self.V_mid_uV) else 0
131
137
  )
@@ -162,14 +168,14 @@ class VirtualConverterModel:
162
168
  current_adc_raw = max(0, current_adc_raw)
163
169
  current_adc_raw = min((2**18) - 1, current_adc_raw)
164
170
 
165
- P_leak_fW = self.V_mid_uV * self._cfg.I_intermediate_leak_nA
171
+ # TODO: remove P_leak in C-Code
166
172
  I_out_nA = self._cal.conv_adc_raw_to_nA(current_adc_raw)
167
173
  if self.enable_buck: # noqa: SIM108
168
174
  eta_inv_out = self.get_output_inv_efficiency(I_out_nA)
169
175
  else:
170
176
  eta_inv_out = 1.0
171
177
 
172
- self.P_out_fW = eta_inv_out * self.V_out_dac_uV * I_out_nA + P_leak_fW
178
+ self.P_out_fW = eta_inv_out * self.V_out_dac_uV * I_out_nA
173
179
 
174
180
  if self.interval_startup_disabled_drain_n > 0:
175
181
  self.interval_startup_disabled_drain_n -= 1
@@ -177,17 +183,12 @@ class VirtualConverterModel:
177
183
 
178
184
  return round(self.P_out_fW) # Python-specific, added for easier testing
179
185
 
180
- # TODO: add range-checks for add, sub Ops
181
186
  def update_cap_storage(self) -> int:
182
- # TODO: this calculation is wrong for everything beside boost-cnv
183
187
  if self.enable_storage:
184
- V_mid_prot_uV = max(1.0, self.V_mid_uV)
185
- P_sum_fW = self.P_inp_fW - self.P_out_fW
186
- I_mid_nA = P_sum_fW / V_mid_prot_uV
187
- dV_mid_uV = I_mid_nA * self.Constant_us_per_nF
188
- self.V_mid_uV += dV_mid_uV
189
-
190
- self.V_mid_uV = min(self.V_mid_uV, self._cfg.V_intermediate_max_uV)
188
+ I_delta_nA = abs((self.P_inp_fW - self.P_out_fW) / self.V_mid_uV)
189
+ is_charging: bool = self.P_inp_fW >= self.P_out_fW
190
+ self.V_mid_uV = self.storage.step(2**4 * I_delta_nA, is_charging=is_charging)
191
+ self.V_mid_uV = min(self.V_mid_uV, self._cfg.V_mid_max_uV)
191
192
  self.V_mid_uV = max(self.V_mid_uV, 1)
192
193
  return round(self.V_mid_uV) # Python-specific, added for easier testing
193
194
 
@@ -200,11 +201,11 @@ class VirtualConverterModel:
200
201
  if check_thresholds:
201
202
  self.sample_count = 0
202
203
  if self.is_outputting:
203
- if V_mid_uV_now < self.V_disable_output_threshold_uV:
204
+ if V_mid_uV_now < self.V_mid_disable_output_threshold_uV:
204
205
  self.is_outputting = False
205
- elif V_mid_uV_now >= self.V_enable_output_threshold_uV:
206
+ elif V_mid_uV_now >= self.V_mid_enable_output_threshold_uV:
206
207
  self.is_outputting = True
207
- self.V_mid_uV -= self.dV_enable_output_uV
208
+ self.V_mid_uV -= self.dV_mid_enable_output_uV
208
209
 
209
210
  if check_thresholds or self._cfg.immediate_pwr_good_signal:
210
211
  # generate power-good-signal
@@ -16,7 +16,7 @@ Compromises:
16
16
 
17
17
  """
18
18
 
19
- from shepherd_core.data_models.content.virtual_harvester import HarvesterPRUConfig
19
+ from shepherd_core.data_models.content.virtual_harvester_config import HarvesterPRUConfig
20
20
  from shepherd_core.logger import log
21
21
 
22
22
 
@@ -64,33 +64,34 @@ class VirtualHarvesterModel:
64
64
  self.voltage_step_x4_uV: int = 4 * self._cfg.voltage_step_uV
65
65
  self.age_max: int = 2 * self._cfg.window_size
66
66
 
67
+ self.voc_now: int = self._cfg.voltage_max_uV
68
+ self.voc_nxt: int = self._cfg.voltage_max_uV
69
+ self.voc_min: int = max(1000, self._cfg.voltage_min_uV)
70
+
71
+ self.lin_extrapolation: bool = bool(self._cfg.hrv_mode & (2**2))
72
+
67
73
  # INIT static vars: CV
68
74
  self.voltage_last: int = 0
69
75
  self.current_last: int = 0
70
- self.compare_last: int = 0
71
- self.lin_extrapolation: bool = bool(self._cfg.hrv_mode & (2**2))
72
- self.current_delta: int = 0
73
76
  self.voltage_delta: int = 0
77
+ self.current_delta: int = 0
78
+ self.compare_last: int = 0
74
79
 
75
80
  # INIT static vars: VOC
76
81
  self.age_now: int = 0
77
- self.voc_now: int = self._cfg.voltage_max_uV
78
82
  self.age_nxt: int = 0
79
- self.voc_nxt: int = self._cfg.voltage_max_uV
80
- self.voc_min: int = max(1000, self._cfg.voltage_min_uV)
81
83
 
82
84
  # INIT static vars: PO
83
- # already done: interval step
84
85
  self.power_last: int = 0
85
86
 
86
87
  # INIT static vars: OPT
87
- # already done: age_now, age_nxt
88
- self.power_now: int = 0
88
+ # already done in VOC: age_now, age_nxt
89
89
  self.voltage_now: int = 0
90
90
  self.current_now: int = 0
91
- self.power_nxt: int = 0
92
91
  self.voltage_nxt: int = 0
93
92
  self.current_nxt: int = 0
93
+ self.power_now: int = 0
94
+ self.power_nxt: int = 0
94
95
 
95
96
  def ivcurve_sample(self, _voltage_uV: int, _current_nA: int) -> tuple[int, int]:
96
97
  if self._cfg.window_size <= 1:
@@ -116,19 +117,25 @@ class VirtualHarvesterModel:
116
117
  if distance_now < distance_last and distance_now < self.voltage_step_x4_uV:
117
118
  self.voltage_hold = _voltage_uV
118
119
  self.current_hold = _current_nA
119
- self.current_delta = _current_nA - self.current_last
120
120
  self.voltage_delta = _voltage_uV - self.voltage_last
121
- # TODO: voltage_delta is static
121
+ self.current_delta = _current_nA - self.current_last
122
122
  elif distance_last < distance_now and distance_last < self.voltage_step_x4_uV:
123
123
  self.voltage_hold = self.voltage_last
124
124
  self.current_hold = self.current_last
125
- self.current_delta = _current_nA - self.current_last
126
125
  self.voltage_delta = _voltage_uV - self.voltage_last
126
+ self.current_delta = _current_nA - self.current_last
127
127
  elif self.lin_extrapolation:
128
128
  # apply the proper delta if needed
129
+ # TODO: C-Code differs here slightly, but only to handle unsigned int
129
130
  if (self.voltage_hold < self.voltage_set_uV) == (self.voltage_delta > 0):
130
- self.voltage_hold += self.voltage_delta
131
- self.current_hold += self.current_delta
131
+ if self.voltage_hold > -self.voltage_delta:
132
+ self.voltage_hold += self.voltage_delta
133
+ else:
134
+ self.voltage_hold = 0
135
+ if self.current_hold > -self.current_delta:
136
+ self.current_hold += self.current_delta
137
+ else:
138
+ self.current_hold = 0
132
139
  else:
133
140
  if self.voltage_hold > self.voltage_delta:
134
141
  self.voltage_hold -= self.voltage_delta
@@ -168,10 +175,11 @@ class VirtualHarvesterModel:
168
175
 
169
176
  _voltage_uV, _current_nA = self.ivcurve_2_cv(_voltage_uV, _current_nA)
170
177
  if self.interval_step < self._cfg.duration_n:
171
- self.voltage_set_uV = self.voc_now
178
+ self.voltage_set_uV = self._cfg.voltage_max_uV
179
+ _current_nA = 0
172
180
  elif self.interval_step == self._cfg.duration_n:
173
181
  self.voltage_set_uV = int(self.voc_now * self._cfg.setpoint_n8 / 256)
174
-
182
+ _current_nA = 0
175
183
  return _voltage_uV, _current_nA
176
184
 
177
185
  def ivcurve_2_mppt_po(self, _voltage_uV: int, _current_nA: int) -> tuple[int, int]:
@@ -211,7 +219,7 @@ class VirtualHarvesterModel:
211
219
  self.voltage_set_uV = self._cfg.voltage_min_uV
212
220
  self.is_rising = True
213
221
  self.volt_step_uV = self._cfg.voltage_step_uV
214
- if self.voltage_set_uV <= self._cfg.voltage_step_uV:
222
+ if self.voltage_set_uV < self._cfg.voltage_step_uV:
215
223
  self.voltage_set_uV = self._cfg.voltage_step_uV
216
224
  self.is_rising = True
217
225
  self.volt_step_uV = self._cfg.voltage_step_uV
@@ -13,8 +13,8 @@ from pathlib import Path
13
13
  from tqdm import tqdm
14
14
 
15
15
  from shepherd_core.data_models.base.calibration import CalibrationHarvester
16
- from shepherd_core.data_models.content.virtual_harvester import HarvesterPRUConfig
17
- from shepherd_core.data_models.content.virtual_harvester import VirtualHarvesterConfig
16
+ from shepherd_core.data_models.content.virtual_harvester_config import HarvesterPRUConfig
17
+ from shepherd_core.data_models.content.virtual_harvester_config import VirtualHarvesterConfig
18
18
  from shepherd_core.reader import Reader
19
19
  from shepherd_core.writer import Writer
20
20
 
@@ -28,45 +28,44 @@ def simulate_harvester(
28
28
 
29
29
  Fn return the harvested energy.
30
30
  """
31
- stack = ExitStack()
32
- file_inp = Reader(path_input, verbose=False)
33
- stack.enter_context(file_inp)
34
- cal_inp = file_inp.get_calibration_data()
31
+ with ExitStack() as stack:
32
+ file_inp = Reader(path_input, verbose=False)
33
+ stack.enter_context(file_inp)
34
+ cal_inp = file_inp.get_calibration_data()
35
35
 
36
- if path_output:
37
- cal_hrv = CalibrationHarvester()
38
- file_out = Writer(
39
- path_output, cal_data=cal_hrv, mode="harvester", verbose=False, force_overwrite=True
40
- )
41
- stack.enter_context(file_out)
42
- cal_out = file_out.get_calibration_data()
43
- file_out.store_hostname("hrv_sim_" + config.name)
36
+ if path_output:
37
+ cal_hrv = CalibrationHarvester()
38
+ file_out = Writer(
39
+ path_output, cal_data=cal_hrv, mode="harvester", verbose=False, force_overwrite=True
40
+ )
41
+ stack.enter_context(file_out)
42
+ cal_out = file_out.get_calibration_data()
43
+ file_out.store_hostname("hrv_sim_" + config.name)
44
44
 
45
- hrv_pru = HarvesterPRUConfig.from_vhrv(
46
- config,
47
- for_emu=True,
48
- dtype_in=file_inp.get_datatype(),
49
- window_size=file_inp.get_window_samples(),
50
- voltage_step_V=file_inp.get_voltage_step(),
51
- )
52
- hrv = VirtualHarvesterModel(hrv_pru)
53
- e_out_Ws = 0.0
45
+ hrv_pru = HarvesterPRUConfig.from_vhrv(
46
+ config,
47
+ for_emu=True,
48
+ dtype_in=file_inp.get_datatype(),
49
+ window_size=file_inp.get_window_samples(),
50
+ voltage_step_V=file_inp.get_voltage_step(),
51
+ )
52
+ hrv = VirtualHarvesterModel(hrv_pru)
53
+ e_out_Ws = 0.0
54
54
 
55
- for _t, v_inp, i_inp in tqdm(
56
- file_inp.read(is_raw=True), total=file_inp.chunks_n, desc="Chunk", leave=False
57
- ):
58
- v_uV = cal_inp.voltage.raw_to_si(v_inp) * 1e6
59
- i_nA = cal_inp.current.raw_to_si(i_inp) * 1e9
60
- length = min(v_uV.size, i_nA.size)
61
- for _n in range(length):
62
- v_uV[_n], i_nA[_n] = hrv.ivcurve_sample(
63
- _voltage_uV=int(v_uV[_n]), _current_nA=int(i_nA[_n])
64
- )
65
- e_out_Ws += (v_uV * i_nA).sum() * 1e-15 * file_inp.sample_interval_s
66
- if path_output:
67
- v_out = cal_out.voltage.si_to_raw(v_uV / 1e6)
68
- i_out = cal_out.current.si_to_raw(i_nA / 1e9)
69
- file_out.append_iv_data_raw(_t, v_out, i_out)
55
+ for _t, v_inp, i_inp in tqdm(
56
+ file_inp.read(is_raw=True), total=file_inp.chunks_n, desc="Chunk", leave=False
57
+ ):
58
+ v_uV = cal_inp.voltage.raw_to_si(v_inp) * 1e6
59
+ i_nA = cal_inp.current.raw_to_si(i_inp) * 1e9
60
+ length = min(v_uV.size, i_nA.size)
61
+ for _n in range(length):
62
+ v_uV[_n], i_nA[_n] = hrv.ivcurve_sample(
63
+ _voltage_uV=int(v_uV[_n]), _current_nA=int(i_nA[_n])
64
+ )
65
+ e_out_Ws += (v_uV * i_nA).sum() * 1e-15 * file_inp.sample_interval_s
66
+ if path_output:
67
+ v_out = cal_out.voltage.si_to_raw(v_uV / 1e6)
68
+ i_out = cal_out.current.si_to_raw(i_nA / 1e9)
69
+ file_out.append_iv_data_raw(_t, v_out, i_out)
70
70
 
71
- stack.close()
72
71
  return e_out_Ws
@@ -12,9 +12,10 @@ NOTE: DO NOT OPTIMIZE -> stay close to original code-base
12
12
 
13
13
  from shepherd_core.data_models.base.calibration import CalibrationEmulator
14
14
  from shepherd_core.data_models.content.energy_environment import EnergyDType
15
- from shepherd_core.data_models.content.virtual_harvester import HarvesterPRUConfig
16
- from shepherd_core.data_models.content.virtual_source import ConverterPRUConfig
17
- from shepherd_core.data_models.content.virtual_source import VirtualSourceConfig
15
+ from shepherd_core.data_models.content.virtual_harvester_config import HarvesterPRUConfig
16
+ from shepherd_core.data_models.content.virtual_source_config import ConverterPRUConfig
17
+ from shepherd_core.data_models.content.virtual_source_config import VirtualSourceConfig
18
+ from shepherd_core.data_models.content.virtual_storage_config import StoragePRUConfig
18
19
 
19
20
  from .virtual_converter_model import PruCalibration
20
21
  from .virtual_converter_model import VirtualConverterModel
@@ -36,25 +37,28 @@ class VirtualSourceModel:
36
37
  ) -> None:
37
38
  self._cal_emu: CalibrationEmulator = cal_emu
38
39
  self._cal_pru: PruCalibration = PruCalibration(cal_emu)
39
-
40
40
  self.cfg_src = VirtualSourceConfig() if vsrc is None else vsrc
41
- cnv_config = ConverterPRUConfig.from_vsrc(
42
- self.cfg_src,
43
- dtype_in=dtype_in,
44
- log_intermediate_node=log_intermediate,
45
- )
46
- self.cnv: VirtualConverterModel = VirtualConverterModel(cnv_config, self._cal_pru)
47
41
 
48
- hrv_config = HarvesterPRUConfig.from_vhrv(
42
+ # init Harvester
43
+ cfg_hrv = HarvesterPRUConfig.from_vhrv(
49
44
  self.cfg_src.harvester,
50
45
  for_emu=True,
51
46
  dtype_in=dtype_in,
52
47
  window_size=window_size,
53
48
  voltage_step_V=voltage_step_V,
54
49
  )
50
+ self.hrv: VirtualHarvesterModel = VirtualHarvesterModel(cfg_hrv)
55
51
 
56
- self.hrv: VirtualHarvesterModel = VirtualHarvesterModel(hrv_config)
52
+ # init Converters
53
+ cfg_cnv = ConverterPRUConfig.from_vsrc(
54
+ self.cfg_src,
55
+ dtype_in=dtype_in,
56
+ log_intermediate_node=log_intermediate,
57
+ )
58
+ cfg_storage = StoragePRUConfig.from_vstorage(self.cfg_src.storage, optimize_clamp=True)
59
+ self.cnv: VirtualConverterModel = VirtualConverterModel(cfg_cnv, self._cal_pru, cfg_storage)
57
60
 
61
+ # state for simulation
58
62
  self.W_inp_fWs: float = 0.0
59
63
  self.W_out_fWs: float = 0.0
60
64
 
@@ -74,8 +78,8 @@ class VirtualSourceModel:
74
78
 
75
79
  # fake ADC read
76
80
  A_out_raw = self._cal_emu.adc_C_A.si_to_raw(I_out_nA * 10**-9)
77
-
78
81
  P_out_fW = self.cnv.calc_out_power(A_out_raw)
82
+
79
83
  V_mid_uV = self.cnv.update_cap_storage()
80
84
  V_out_raw = self.cnv.update_states_and_output()
81
85
  V_out_uV = int(self._cal_emu.dac_V_A.raw_to_si(V_out_raw) * 10**6)
@@ -14,7 +14,7 @@ import numpy as np
14
14
  from tqdm import tqdm
15
15
 
16
16
  from shepherd_core.data_models.base.calibration import CalibrationEmulator
17
- from shepherd_core.data_models.content.virtual_source import VirtualSourceConfig
17
+ from shepherd_core.data_models.content.virtual_source_config import VirtualSourceConfig
18
18
  from shepherd_core.logger import log
19
19
  from shepherd_core.reader import Reader
20
20
  from shepherd_core.writer import Writer
@@ -35,81 +35,79 @@ def simulate_source(
35
35
 
36
36
  FN returns the consumed energy of the target.
37
37
  """
38
- stack = ExitStack()
39
- file_inp = Reader(path_input, verbose=False)
40
- stack.enter_context(file_inp)
41
- cal_emu = CalibrationEmulator()
42
- cal_inp = file_inp.get_calibration_data()
43
-
44
- if path_output:
45
- file_out = Writer(
46
- path_output, cal_data=cal_emu, mode="emulator", verbose=False, force_overwrite=True
38
+ with ExitStack() as stack:
39
+ file_inp = Reader(path_input, verbose=False)
40
+ stack.enter_context(file_inp)
41
+ cal_emu = CalibrationEmulator()
42
+ cal_inp = file_inp.get_calibration_data()
43
+
44
+ if path_output:
45
+ file_out = Writer(
46
+ path_output, cal_data=cal_emu, mode="emulator", verbose=False, force_overwrite=True
47
+ )
48
+ stack.enter_context(file_out)
49
+ file_out.store_hostname("emu_sim_" + config.name)
50
+ file_out.store_config(config.model_dump())
51
+ cal_out = file_out.get_calibration_data()
52
+
53
+ src = VirtualSourceModel(
54
+ config,
55
+ cal_emu,
56
+ dtype_in=file_inp.get_datatype(),
57
+ log_intermediate=False,
58
+ window_size=file_inp.get_window_samples(),
59
+ voltage_step_V=file_inp.get_voltage_step(),
47
60
  )
48
- stack.enter_context(file_out)
49
- file_out.store_hostname("emu_sim_" + config.name)
50
- file_out.store_config(config.model_dump())
51
- cal_out = file_out.get_calibration_data()
52
-
53
- src = VirtualSourceModel(
54
- config,
55
- cal_emu,
56
- dtype_in=file_inp.get_datatype(),
57
- log_intermediate=False,
58
- window_size=file_inp.get_window_samples(),
59
- voltage_step_V=file_inp.get_voltage_step(),
60
- )
61
- i_out_nA = 0
62
- e_out_Ws = 0.0
63
- if monitor_internals and path_output:
64
- stats_sample = 0
65
- stats_internal = np.empty((round(file_inp.runtime_s * file_inp.samplerate_sps), 11))
66
- try:
67
- # keep dependencies low
68
- from matplotlib import pyplot as plt # noqa: PLC0415
69
- except ImportError:
70
- log.warning("Matplotlib not installed, plotting of internals disabled")
61
+ i_out_nA = 0
62
+ e_out_Ws = 0.0
63
+ if monitor_internals and path_output:
64
+ stats_sample = 0
65
+ stats_internal = np.empty((round(file_inp.runtime_s * file_inp.samplerate_sps), 11))
66
+ try:
67
+ # keep dependencies low
68
+ from matplotlib import pyplot as plt # noqa: PLC0415
69
+ except ImportError:
70
+ log.warning("Matplotlib not installed, plotting of internals disabled")
71
+ stats_internal = None
72
+ else:
71
73
  stats_internal = None
72
- else:
73
- stats_internal = None
74
-
75
- for _t, v_inp, i_inp in tqdm(
76
- file_inp.read(is_raw=True), total=file_inp.chunks_n, desc="Chunk", leave=False
77
- ):
78
- v_uV = 1e6 * cal_inp.voltage.raw_to_si(v_inp)
79
- i_nA = 1e9 * cal_inp.current.raw_to_si(i_inp)
80
-
81
- for _n in range(len(_t)):
82
- v_uV[_n] = src.iterate_sampling(
83
- V_inp_uV=int(v_uV[_n]),
84
- I_inp_nA=int(i_nA[_n]),
85
- I_out_nA=i_out_nA,
86
- )
87
- i_out_nA = target.step(int(v_uV[_n]), pwr_good=src.cnv.get_power_good())
88
- i_nA[_n] = i_out_nA
89
-
90
- if stats_internal is not None:
91
- stats_internal[stats_sample] = [
92
- _t[_n] * 1e-9, # s
93
- src.hrv.voltage_hold * 1e-6,
94
- src.cnv.V_input_request_uV * 1e-6, # V
95
- src.hrv.voltage_set_uV * 1e-6,
96
- src.cnv.V_mid_uV * 1e-6,
97
- src.hrv.current_hold * 1e-6, # mA
98
- src.hrv.current_delta * 1e-6,
99
- i_out_nA * 1e-6,
100
- src.cnv.P_inp_fW * 1e-12, # mW
101
- src.cnv.P_out_fW * 1e-12,
102
- src.cnv.get_power_good(),
103
- ]
104
- stats_sample += 1
105
-
106
- e_out_Ws += (v_uV * i_nA).sum() * 1e-15 * file_inp.sample_interval_s
107
- if path_output:
108
- v_out = cal_out.voltage.si_to_raw(1e-6 * v_uV)
109
- i_out = cal_out.current.si_to_raw(1e-9 * i_nA)
110
- file_out.append_iv_data_raw(_t, v_out, i_out)
111
74
 
112
- stack.close()
75
+ for _t, v_inp, i_inp in tqdm(
76
+ file_inp.read(is_raw=True), total=file_inp.chunks_n, desc="Chunk", leave=False
77
+ ):
78
+ v_uV = 1e6 * cal_inp.voltage.raw_to_si(v_inp)
79
+ i_nA = 1e9 * cal_inp.current.raw_to_si(i_inp)
80
+
81
+ for _n in range(len(_t)):
82
+ v_uV[_n] = src.iterate_sampling(
83
+ V_inp_uV=int(v_uV[_n]),
84
+ I_inp_nA=int(i_nA[_n]),
85
+ I_out_nA=i_out_nA,
86
+ )
87
+ i_out_nA = target.step(int(v_uV[_n]), pwr_good=src.cnv.get_power_good())
88
+ i_nA[_n] = i_out_nA
89
+
90
+ if stats_internal is not None:
91
+ stats_internal[stats_sample] = [
92
+ _t[_n] * 1e-9, # s
93
+ src.hrv.voltage_hold * 1e-6,
94
+ src.cnv.V_input_request_uV * 1e-6, # V
95
+ src.hrv.voltage_set_uV * 1e-6,
96
+ src.cnv.V_mid_uV * 1e-6,
97
+ src.hrv.current_hold * 1e-6, # mA
98
+ src.hrv.current_delta * 1e-6,
99
+ i_out_nA * 1e-6,
100
+ src.cnv.P_inp_fW * 1e-12, # mW
101
+ src.cnv.P_out_fW * 1e-12,
102
+ src.cnv.get_power_good(),
103
+ ]
104
+ stats_sample += 1
105
+
106
+ e_out_Ws += (v_uV * i_nA).sum() * 1e-15 * file_inp.sample_interval_s
107
+ if path_output:
108
+ v_out = cal_out.voltage.si_to_raw(1e-6 * v_uV)
109
+ i_out = cal_out.current.si_to_raw(1e-9 * i_nA)
110
+ file_out.append_iv_data_raw(_t, v_out, i_out)
113
111
 
114
112
  if stats_internal is not None:
115
113
  stats_internal = stats_internal[:stats_sample, :]