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.
- shepherd_core/data_models/__init__.py +4 -2
- shepherd_core/data_models/base/content.py +2 -0
- shepherd_core/data_models/content/__init__.py +4 -2
- shepherd_core/data_models/content/{virtual_harvester.py → virtual_harvester_config.py} +3 -3
- shepherd_core/data_models/content/{virtual_source.py → virtual_source_config.py} +82 -58
- shepherd_core/data_models/content/virtual_source_fixture.yaml +24 -24
- shepherd_core/data_models/content/virtual_storage_config.py +426 -0
- shepherd_core/data_models/content/virtual_storage_fixture_creator.py +267 -0
- shepherd_core/data_models/content/virtual_storage_fixture_ideal.yaml +637 -0
- shepherd_core/data_models/content/virtual_storage_fixture_lead.yaml +49 -0
- shepherd_core/data_models/content/virtual_storage_fixture_lipo.yaml +735 -0
- shepherd_core/data_models/content/virtual_storage_fixture_mlcc.yaml +200 -0
- shepherd_core/data_models/content/virtual_storage_fixture_param_experiments.py +151 -0
- shepherd_core/data_models/content/virtual_storage_fixture_super.yaml +150 -0
- shepherd_core/data_models/content/virtual_storage_fixture_tantal.yaml +550 -0
- shepherd_core/data_models/experiment/target_config.py +1 -1
- shepherd_core/data_models/task/emulation.py +1 -1
- shepherd_core/data_models/task/harvest.py +1 -1
- shepherd_core/decoder_waveform/uart.py +1 -1
- shepherd_core/inventory/system.py +1 -1
- shepherd_core/reader.py +4 -3
- shepherd_core/version.py +1 -1
- shepherd_core/vsource/__init__.py +4 -0
- shepherd_core/vsource/virtual_converter_model.py +27 -26
- shepherd_core/vsource/virtual_harvester_model.py +27 -19
- shepherd_core/vsource/virtual_harvester_simulation.py +38 -39
- shepherd_core/vsource/virtual_source_model.py +17 -13
- shepherd_core/vsource/virtual_source_simulation.py +71 -73
- shepherd_core/vsource/virtual_storage_model.py +164 -0
- shepherd_core/vsource/virtual_storage_model_fixed_point_math.py +58 -0
- shepherd_core/vsource/virtual_storage_models_kibam.py +449 -0
- shepherd_core/vsource/virtual_storage_simulator.py +104 -0
- {shepherd_core-2025.8.1.dist-info → shepherd_core-2025.10.1.dist-info}/METADATA +2 -1
- {shepherd_core-2025.8.1.dist-info → shepherd_core-2025.10.1.dist-info}/RECORD +37 -25
- shepherd_core/data_models/virtual_source_doc.txt +0 -207
- {shepherd_core-2025.8.1.dist-info → shepherd_core-2025.10.1.dist-info}/WHEEL +0 -0
- {shepherd_core-2025.8.1.dist-info → shepherd_core-2025.10.1.dist-info}/top_level.txt +0 -0
- {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.
|
|
21
|
-
from shepherd_core.data_models.content.
|
|
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__(
|
|
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.
|
|
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.
|
|
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.
|
|
94
|
-
self.
|
|
95
|
-
self.
|
|
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.
|
|
98
|
-
self.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
131
|
-
|
|
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.
|
|
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
|
|
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.
|
|
17
|
-
from shepherd_core.data_models.content.
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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.
|
|
16
|
-
from shepherd_core.data_models.content.
|
|
17
|
-
from shepherd_core.data_models.content.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
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, :]
|