spikesafe-python 1.8.3__tar.gz → 1.10.2__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 (55) hide show
  1. {spikesafe_python-1.8.3/spikesafe_python.egg-info → spikesafe_python-1.10.2}/PKG-INFO +5 -2
  2. {spikesafe_python-1.8.3 → spikesafe_python-1.10.2}/README.md +2 -0
  3. {spikesafe_python-1.8.3 → spikesafe_python-1.10.2}/pyproject.toml +1 -1
  4. spikesafe_python-1.10.2/spikesafe_python/Compensation.py +566 -0
  5. {spikesafe_python-1.8.3 → spikesafe_python-1.10.2}/spikesafe_python/DigitizerData.py +2 -0
  6. spikesafe_python-1.10.2/spikesafe_python/DigitizerDataFetch.py +551 -0
  7. spikesafe_python-1.10.2/spikesafe_python/DigitizerInfo.py +40 -0
  8. spikesafe_python-1.10.2/spikesafe_python/Discharge.py +44 -0
  9. {spikesafe_python-1.8.3 → spikesafe_python-1.10.2}/spikesafe_python/EventData.py +9 -4
  10. {spikesafe_python-1.8.3 → spikesafe_python-1.10.2}/spikesafe_python/MemoryTableReadData.py +21 -14
  11. spikesafe_python-1.10.2/spikesafe_python/Precision.py +264 -0
  12. spikesafe_python-1.10.2/spikesafe_python/PulseWidthCorrection.py +2620 -0
  13. spikesafe_python-1.10.2/spikesafe_python/ReadAllEvents.py +194 -0
  14. spikesafe_python-1.10.2/spikesafe_python/ScpiFormatter.py +53 -0
  15. spikesafe_python-1.10.2/spikesafe_python/SerialPortConnection.py +207 -0
  16. {spikesafe_python-1.8.3 → spikesafe_python-1.10.2}/spikesafe_python/SpikeSafeError.py +2 -0
  17. {spikesafe_python-1.8.3 → spikesafe_python-1.10.2}/spikesafe_python/SpikeSafeEvents.py +20 -6
  18. spikesafe_python-1.10.2/spikesafe_python/SpikeSafeInfo.py +91 -0
  19. spikesafe_python-1.10.2/spikesafe_python/SpikeSafeInfoParser.py +348 -0
  20. spikesafe_python-1.10.2/spikesafe_python/Threading.py +37 -0
  21. spikesafe_python-1.10.2/spikesafe_python/__init__.py +117 -0
  22. {spikesafe_python-1.8.3 → spikesafe_python-1.10.2/spikesafe_python.egg-info}/PKG-INFO +5 -2
  23. {spikesafe_python-1.8.3 → spikesafe_python-1.10.2}/spikesafe_python.egg-info/SOURCES.txt +11 -1
  24. spikesafe_python-1.10.2/tests/test_custom_compensation.py +66 -0
  25. spikesafe_python-1.10.2/tests/test_digitizer_fetch_time_of_sampling.py +509 -0
  26. spikesafe_python-1.10.2/tests/test_event_data.py +30 -0
  27. spikesafe_python-1.10.2/tests/test_event_data_old.py +30 -0
  28. spikesafe_python-1.10.2/tests/test_optimum_compensation.py +42 -0
  29. spikesafe_python-1.10.2/tests/test_optimum_pulse_width_correction.py +29 -0
  30. spikesafe_python-1.8.3/spikesafe_python/Compensation.py +0 -506
  31. spikesafe_python-1.8.3/spikesafe_python/DigitizerDataFetch.py +0 -465
  32. spikesafe_python-1.8.3/spikesafe_python/Discharge.py +0 -29
  33. spikesafe_python-1.8.3/spikesafe_python/Precision.py +0 -152
  34. spikesafe_python-1.8.3/spikesafe_python/PulseWidthCorrection.py +0 -2600
  35. spikesafe_python-1.8.3/spikesafe_python/ReadAllEvents.py +0 -158
  36. spikesafe_python-1.8.3/spikesafe_python/ScpiFormatter.py +0 -29
  37. spikesafe_python-1.8.3/spikesafe_python/Threading.py +0 -18
  38. spikesafe_python-1.8.3/spikesafe_python/__init__.py +0 -0
  39. {spikesafe_python-1.8.3 → spikesafe_python-1.10.2}/LICENSE +0 -0
  40. {spikesafe_python-1.8.3 → spikesafe_python-1.10.2}/setup.cfg +0 -0
  41. {spikesafe_python-1.8.3 → spikesafe_python-1.10.2}/setup.py +0 -0
  42. {spikesafe_python-1.8.3 → spikesafe_python-1.10.2}/spikesafe_python/ChannelData.py +0 -0
  43. {spikesafe_python-1.8.3 → spikesafe_python-1.10.2}/spikesafe_python/DigitizerEnums.py +0 -0
  44. {spikesafe_python-1.8.3 → spikesafe_python-1.10.2}/spikesafe_python/DigitizerVfCustomSequence.py +0 -0
  45. {spikesafe_python-1.8.3 → spikesafe_python-1.10.2}/spikesafe_python/DigitizerVfCustomSequenceStep.py +0 -0
  46. {spikesafe_python-1.8.3 → spikesafe_python-1.10.2}/spikesafe_python/SpikeSafeEnums.py +0 -0
  47. {spikesafe_python-1.8.3 → spikesafe_python-1.10.2}/spikesafe_python/TcpSocket.py +0 -0
  48. {spikesafe_python-1.8.3 → spikesafe_python-1.10.2}/spikesafe_python/TemperatureData.py +0 -0
  49. {spikesafe_python-1.8.3 → spikesafe_python-1.10.2}/spikesafe_python.egg-info/dependency_links.txt +0 -0
  50. {spikesafe_python-1.8.3 → spikesafe_python-1.10.2}/spikesafe_python.egg-info/requires.txt +0 -0
  51. {spikesafe_python-1.8.3 → spikesafe_python-1.10.2}/spikesafe_python.egg-info/top_level.txt +0 -0
  52. /spikesafe_python-1.8.3/tests/test_custom_compensation.py → /spikesafe_python-1.10.2/tests/test_custom_compensation_old.py +0 -0
  53. /spikesafe_python-1.8.3/tests/test_digitizer_fetch_time_of_sampling.py → /spikesafe_python-1.10.2/tests/test_digitizer_fetch_time_of_sampling_old.py +0 -0
  54. /spikesafe_python-1.8.3/tests/test_optimum_compensation.py → /spikesafe_python-1.10.2/tests/test_optimum_compensation_old.py +0 -0
  55. /spikesafe_python-1.8.3/tests/test_optimum_pulse_width_correction.py → /spikesafe_python-1.10.2/tests/test_optimum_pulse_width_correction_old.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: spikesafe-python
3
- Version: 1.8.3
3
+ Version: 1.10.2
4
4
  Summary: SpikeSafe Python Library
5
5
  Author-email: Vektrex <support@vektrex.com>
6
6
  Classifier: Programming Language :: Python :: 3
@@ -10,6 +10,7 @@ Requires-Python: >=3.10
10
10
  Description-Content-Type: text/markdown
11
11
  License-File: LICENSE
12
12
  Requires-Dist: jsonschema>=4.2.3
13
+ Dynamic: license-file
13
14
 
14
15
  # spikesafe-python
15
16
 
@@ -25,6 +26,8 @@ GitHub Repository: [SpikeSafe Python Samples](https://github.com/VektrexElectron
25
26
 
26
27
  Library help documentation: [spikesafe_python_lib_docs](https://github.com/VektrexElectronicSystems/SpikeSafePythonSamples/tree/master/spikesafe_python_lib_docs)
27
28
 
29
+ Release notes: [spikesafe_python_lib_docs/_releases](https://github.com/VektrexElectronicSystems/SpikeSafePythonSamples/tree/master/spikesafe_python_lib_docs/_releases)
30
+
28
31
  ## About
29
32
 
30
33
  The **spikesafe-python** package provides light-weight access Python helper classes and functions to easily communicate with to your SpikeSafe and parse data into easy to use objects.
@@ -12,6 +12,8 @@ GitHub Repository: [SpikeSafe Python Samples](https://github.com/VektrexElectron
12
12
 
13
13
  Library help documentation: [spikesafe_python_lib_docs](https://github.com/VektrexElectronicSystems/SpikeSafePythonSamples/tree/master/spikesafe_python_lib_docs)
14
14
 
15
+ Release notes: [spikesafe_python_lib_docs/_releases](https://github.com/VektrexElectronicSystems/SpikeSafePythonSamples/tree/master/spikesafe_python_lib_docs/_releases)
16
+
15
17
  ## About
16
18
 
17
19
  The **spikesafe-python** package provides light-weight access Python helper classes and functions to easily communicate with to your SpikeSafe and parse data into easy to use objects.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "spikesafe-python"
7
- version = "1.8.3"
7
+ version = "1.10.2"
8
8
  authors = [
9
9
  { name="Vektrex", email="support@vektrex.com" },
10
10
  ]
@@ -0,0 +1,566 @@
1
+ import json
2
+ import jsonschema
3
+ from jsonschema import validate
4
+ import logging
5
+ import os
6
+ from .SpikeSafeEnums import LoadImpedance, RiseTime
7
+
8
+ log = logging.getLogger(__name__)
9
+
10
+ class Compensation():
11
+ """
12
+ Provides a collection of helper functions you can use to help with SpikeSafe compensation settings.
13
+
14
+ ...
15
+
16
+ Attributes
17
+ ----------
18
+ custom_compensation_table_schema : dict
19
+ JSON schema to validate a custom compensation table
20
+
21
+ Methods
22
+ -------
23
+ Compensation.get_optimum_compensation(spikesafe_model_max_current_amps, set_current_amps, pulse_on_time_seconds = None, enable_logging = False)
24
+ Returns the optimum compensation for a given set current, and optionally a given pulse on time
25
+ Compensation.get_custom_compensation(spikesafe_model_max_current_amps, set_current_amps, device_type, custom_compensation_table, pulse_on_time_seconds = None, enable_logging = False)
26
+ Returns the custom compensation for a given set current, device type, and custom compensation table, and optionally a given pulse on time
27
+ Compensation.load_custom_compensation_table(file_path)
28
+ Loads a custom compensation table from a file path
29
+ Compensation.load_custom_compensation_unique_device_types(custom_compensation_table)
30
+ Returns a list of unique device types in a custom compensation table
31
+ """
32
+
33
+ # Dictionary string constants
34
+ low_current_range_maximum = 'low_current_range_maximum'
35
+ load_impedance_high_range_1 = 'load_impedance_high_range_1'
36
+ rise_time_high_range_1 = 'rise_time_high_range_1'
37
+ load_impedance_high_range_2 = 'load_impedance_high_range_2'
38
+ rise_time_high_range_2 = 'rise_time_high_range_2'
39
+ load_impedance_high_range_3 = 'load_impedance_high_range_3'
40
+ rise_time_high_range_3 = 'rise_time_high_range_3'
41
+ load_impedance_low_range_1 = 'load_impedance_low_range_1'
42
+ rise_time_low_range_1 = 'rise_time_low_range_1'
43
+ load_impedance_low_range_2 = 'load_impedance_low_range_2'
44
+ rise_time_low_range_2 = 'rise_time_low_range_2'
45
+
46
+ @staticmethod
47
+ def get_optimum_compensation(spikesafe_model_max_current_amps, set_current_amps, pulse_on_time_seconds = None, enable_logging = False):
48
+ """
49
+ Returns the optimum compensation for a given set current, and optionally a given pulse on time
50
+
51
+ Parameters
52
+ ----------
53
+ spikesafe_model_max_current_amps : float
54
+ Maximum current of the SpikeSafe model
55
+ set_current_amps : float
56
+ Current to be set on SpikeSafe
57
+ pulse_on_time_seconds : float, optional
58
+ Pulse On Time to be set on SpikeSafe
59
+ enable_logging : bool, optional
60
+ Enables logging (default is False)
61
+
62
+ Returns
63
+ -------
64
+ LoadImpedance
65
+ Load Impedance compensation value. This should be an instance of the LoadImpedance IntEnum from SpikeSafeEnums.
66
+ RiseTime
67
+ Rise Time compensation value. This should be an instance of the RiseTime IntEnum from SpikeSafeEnums.
68
+
69
+ Remarks
70
+ -------
71
+ This function assumes the set current is operating on the optimized current range. If operating on the high range with a set current normally programmed on the low range, the compensation values will not be optimal. See online specification for range limits.
72
+
73
+ If Load Impedance is returned as Medium or High, it is best practice to increase the Compliance Voltage setting by 5V to 30V. This helps the current amplifier to overcome inductance. If Compliance Voltage is not increased, then a Low Side Over Current or an Unstable Waveform error may occur.
74
+
75
+ If an Operating Mode is used to sweep through steps of currents where the compensation settings are the same across the sweep, such as Pulse Sweep or Multiple Pulse Burst, it is recommended use the optimum compensation settings targeting the Stop Current.
76
+
77
+ Raises
78
+ ------
79
+ ValueError
80
+ If set_current_amps is greater than spikesafe_model_max_current_amps
81
+ """
82
+
83
+ if set_current_amps > spikesafe_model_max_current_amps:
84
+ raise ValueError(f'Measurement current {set_current_amps}A exceeds SpikeSafe model maximum current capability of {spikesafe_model_max_current_amps}A.')
85
+
86
+ # Non-pulsing, or DC based modes, do not require compensation
87
+ if pulse_on_time_seconds is None:
88
+ if enable_logging:
89
+ log.warning("DC based modes do not require compensation, defaulting to LoadImpedance.VERY_LOW and RiseTime.VERY_SLOW")
90
+ return LoadImpedance.VERY_LOW, RiseTime.VERY_SLOW
91
+
92
+ # Optimum compensation is intended for Pulse On Time of 500us or less
93
+ optimum_compensation_minimum_pulse_on_time_seconds = 0.0005
94
+ if pulse_on_time_seconds is not None and pulse_on_time_seconds > optimum_compensation_minimum_pulse_on_time_seconds:
95
+ if enable_logging:
96
+ log.warning(f"Compensation is intended for Pulse On Time of {optimum_compensation_minimum_pulse_on_time_seconds}s or less, defaulting to LoadImpedance.VERY_LOW and RiseTime.VERY_SLOW")
97
+ return LoadImpedance.VERY_LOW, RiseTime.VERY_SLOW
98
+
99
+ # Dictionary to store values for different model max currents
100
+ model_params = {
101
+ 0.05: {
102
+ Compensation.low_current_range_maximum: 0.004,
103
+ Compensation.load_impedance_high_range_1: LoadImpedance.MEDIUM, Compensation.rise_time_high_range_1: RiseTime.MEDIUM,
104
+ Compensation.load_impedance_high_range_2: LoadImpedance.LOW, Compensation.rise_time_high_range_2: RiseTime.FAST,
105
+ Compensation.load_impedance_high_range_3: LoadImpedance.VERY_LOW, Compensation.rise_time_high_range_3: RiseTime.SLOW,
106
+ Compensation.load_impedance_low_range_1: LoadImpedance.HIGH, Compensation.rise_time_low_range_1: RiseTime.FAST,
107
+ Compensation.load_impedance_low_range_2: LoadImpedance.MEDIUM, Compensation.rise_time_low_range_2: RiseTime.FAST
108
+ },
109
+ 0.5: {
110
+ Compensation.low_current_range_maximum: 0.04,
111
+ Compensation.load_impedance_high_range_1: LoadImpedance.MEDIUM, Compensation.rise_time_high_range_1: RiseTime.FAST,
112
+ Compensation.load_impedance_high_range_2: LoadImpedance.LOW, Compensation.rise_time_high_range_2: RiseTime.FAST,
113
+ Compensation.load_impedance_high_range_3: LoadImpedance.LOW, Compensation.rise_time_high_range_3: RiseTime.MEDIUM,
114
+ Compensation.load_impedance_low_range_1: LoadImpedance.MEDIUM, Compensation.rise_time_low_range_1: RiseTime.FAST,
115
+ Compensation.load_impedance_low_range_2: LoadImpedance.MEDIUM, Compensation.rise_time_low_range_2: RiseTime.FAST
116
+ },
117
+ 2: {
118
+ Compensation.low_current_range_maximum: 0.2,
119
+ Compensation.load_impedance_high_range_1: LoadImpedance.MEDIUM, Compensation.rise_time_high_range_1: RiseTime.FAST,
120
+ Compensation.load_impedance_high_range_2: LoadImpedance.LOW, Compensation.rise_time_high_range_2: RiseTime.FAST,
121
+ Compensation.load_impedance_high_range_3: LoadImpedance.LOW, Compensation.rise_time_high_range_3: RiseTime.MEDIUM,
122
+ Compensation.load_impedance_low_range_1: LoadImpedance.HIGH, Compensation.rise_time_low_range_1: RiseTime.FAST,
123
+ Compensation.load_impedance_low_range_2: LoadImpedance.MEDIUM, Compensation.rise_time_low_range_2: RiseTime.FAST
124
+ },
125
+ 3: {
126
+ Compensation.low_current_range_maximum: 0.2,
127
+ Compensation.load_impedance_high_range_1: LoadImpedance.MEDIUM, Compensation.rise_time_high_range_1: RiseTime.FAST,
128
+ Compensation.load_impedance_high_range_2: LoadImpedance.LOW, Compensation.rise_time_high_range_2: RiseTime.FAST,
129
+ Compensation.load_impedance_high_range_3: LoadImpedance.LOW, Compensation.rise_time_high_range_3: RiseTime.MEDIUM,
130
+ Compensation.load_impedance_low_range_1: LoadImpedance.HIGH, Compensation.rise_time_low_range_1: RiseTime.FAST,
131
+ Compensation.load_impedance_low_range_2: LoadImpedance.MEDIUM, Compensation.rise_time_low_range_2: RiseTime.FAST
132
+ },
133
+ 4: {
134
+ Compensation.low_current_range_maximum: 0.2,
135
+ Compensation.load_impedance_high_range_1: LoadImpedance.MEDIUM, Compensation.rise_time_high_range_1: RiseTime.FAST,
136
+ Compensation.load_impedance_high_range_2: LoadImpedance.LOW, Compensation.rise_time_high_range_2: RiseTime.FAST,
137
+ Compensation.load_impedance_high_range_3: LoadImpedance.LOW, Compensation.rise_time_high_range_3: RiseTime.MEDIUM,
138
+ Compensation.load_impedance_low_range_1: LoadImpedance.HIGH, Compensation.rise_time_low_range_1: RiseTime.FAST,
139
+ Compensation.load_impedance_low_range_2: LoadImpedance.MEDIUM, Compensation.rise_time_low_range_2: RiseTime.FAST
140
+ },
141
+ 5: {
142
+ Compensation.low_current_range_maximum: 0.2,
143
+ Compensation.load_impedance_high_range_1: LoadImpedance.MEDIUM, Compensation.rise_time_high_range_1: RiseTime.FAST,
144
+ Compensation.load_impedance_high_range_2: LoadImpedance.MEDIUM, Compensation.rise_time_high_range_2: RiseTime.FAST,
145
+ Compensation.load_impedance_high_range_3: LoadImpedance.LOW, Compensation.rise_time_high_range_3: RiseTime.MEDIUM,
146
+ Compensation.load_impedance_low_range_1: LoadImpedance.HIGH, Compensation.rise_time_low_range_1: RiseTime.FAST,
147
+ Compensation.load_impedance_low_range_2: LoadImpedance.MEDIUM, Compensation.rise_time_low_range_2: RiseTime.FAST
148
+ },
149
+ 8: {
150
+ Compensation.low_current_range_maximum: 0.4,
151
+ Compensation.load_impedance_high_range_1: LoadImpedance.MEDIUM, Compensation.rise_time_high_range_1: RiseTime.FAST,
152
+ Compensation.load_impedance_high_range_2: LoadImpedance.MEDIUM, Compensation.rise_time_high_range_2: RiseTime.FAST,
153
+ Compensation.load_impedance_high_range_3: LoadImpedance.MEDIUM, Compensation.rise_time_high_range_3: RiseTime.MEDIUM,
154
+ Compensation.load_impedance_low_range_1: LoadImpedance.HIGH, Compensation.rise_time_low_range_1: RiseTime.FAST,
155
+ Compensation.load_impedance_low_range_2: LoadImpedance.MEDIUM, Compensation.rise_time_low_range_2: RiseTime.FAST
156
+ },
157
+ # Tested for Mini 10A, but will also be used for PSMU HC 10A
158
+ 10: {
159
+ Compensation.low_current_range_maximum: 0.4,
160
+ Compensation.load_impedance_high_range_1: LoadImpedance.MEDIUM, Compensation.rise_time_high_range_1: RiseTime.FAST,
161
+ Compensation.load_impedance_high_range_2: LoadImpedance.MEDIUM, Compensation.rise_time_high_range_2: RiseTime.FAST,
162
+ Compensation.load_impedance_high_range_3: LoadImpedance.MEDIUM, Compensation.rise_time_high_range_3: RiseTime.MEDIUM,
163
+ Compensation.load_impedance_low_range_1: LoadImpedance.HIGH, Compensation.rise_time_low_range_1: RiseTime.FAST,
164
+ Compensation.load_impedance_low_range_2: LoadImpedance.MEDIUM, Compensation.rise_time_low_range_2: RiseTime.FAST
165
+ },
166
+ 16: {
167
+ Compensation.low_current_range_maximum: 0.8,
168
+ Compensation.load_impedance_high_range_1: LoadImpedance.MEDIUM, Compensation.rise_time_high_range_1: RiseTime.FAST,
169
+ Compensation.load_impedance_high_range_2: LoadImpedance.LOW, Compensation.rise_time_high_range_2: RiseTime.FAST,
170
+ Compensation.load_impedance_high_range_3: LoadImpedance.LOW, Compensation.rise_time_high_range_3: RiseTime.MEDIUM,
171
+ Compensation.load_impedance_low_range_1: LoadImpedance.HIGH, Compensation.rise_time_low_range_1: RiseTime.FAST,
172
+ Compensation.load_impedance_low_range_2: LoadImpedance.MEDIUM, Compensation.rise_time_low_range_2: RiseTime.FAST
173
+ },
174
+ 20: {
175
+ Compensation.low_current_range_maximum: 0.8,
176
+ Compensation.load_impedance_high_range_1: LoadImpedance.MEDIUM, Compensation.rise_time_high_range_1: RiseTime.FAST,
177
+ Compensation.load_impedance_high_range_2: LoadImpedance.LOW, Compensation.rise_time_high_range_2: RiseTime.FAST,
178
+ Compensation.load_impedance_high_range_3: LoadImpedance.LOW, Compensation.rise_time_high_range_3: RiseTime.MEDIUM,
179
+ Compensation.load_impedance_low_range_1: LoadImpedance.HIGH, Compensation.rise_time_low_range_1: RiseTime.FAST,
180
+ Compensation.load_impedance_low_range_2: LoadImpedance.MEDIUM, Compensation.rise_time_low_range_2: RiseTime.FAST
181
+ },
182
+ 32: {
183
+ Compensation.low_current_range_maximum: 1.6,
184
+ Compensation.load_impedance_high_range_1: LoadImpedance.MEDIUM, Compensation.rise_time_high_range_1: RiseTime.FAST,
185
+ Compensation.load_impedance_high_range_2: LoadImpedance.LOW, Compensation.rise_time_high_range_2: RiseTime.FAST,
186
+ Compensation.load_impedance_high_range_3: LoadImpedance.LOW, Compensation.rise_time_high_range_3: RiseTime.MEDIUM,
187
+ Compensation.load_impedance_low_range_1: LoadImpedance.HIGH, Compensation.rise_time_low_range_1: RiseTime.FAST,
188
+ Compensation.load_impedance_low_range_2: LoadImpedance.MEDIUM, Compensation.rise_time_low_range_2: RiseTime.FAST
189
+ },
190
+ 40: {
191
+ Compensation.low_current_range_maximum: 1.6,
192
+ Compensation.load_impedance_high_range_1: LoadImpedance.MEDIUM, Compensation.rise_time_high_range_1: RiseTime.FAST,
193
+ Compensation.load_impedance_high_range_2: LoadImpedance.LOW, Compensation.rise_time_high_range_2: RiseTime.FAST,
194
+ Compensation.load_impedance_high_range_3: LoadImpedance.LOW, Compensation.rise_time_high_range_3: RiseTime.MEDIUM,
195
+ Compensation.load_impedance_low_range_1: LoadImpedance.HIGH, Compensation.rise_time_low_range_1: RiseTime.FAST,
196
+ Compensation.load_impedance_low_range_2: LoadImpedance.MEDIUM, Compensation.rise_time_low_range_2: RiseTime.FAST
197
+ },
198
+ 60: {
199
+ Compensation.low_current_range_maximum: 3.2,
200
+ Compensation.load_impedance_high_range_1: LoadImpedance.MEDIUM, Compensation.rise_time_high_range_1: RiseTime.FAST,
201
+ Compensation.load_impedance_high_range_2: LoadImpedance.LOW, Compensation.rise_time_high_range_2: RiseTime.FAST,
202
+ Compensation.load_impedance_high_range_3: LoadImpedance.LOW, Compensation.rise_time_high_range_3: RiseTime.MEDIUM,
203
+ Compensation.load_impedance_low_range_1: LoadImpedance.HIGH, Compensation.rise_time_low_range_1: RiseTime.FAST,
204
+ Compensation.load_impedance_low_range_2: LoadImpedance.MEDIUM, Compensation.rise_time_low_range_2: RiseTime.FAST
205
+ },
206
+ 80: {
207
+ Compensation.low_current_range_maximum: 3.2,
208
+ Compensation.load_impedance_high_range_1: LoadImpedance.MEDIUM, Compensation.rise_time_high_range_1: RiseTime.FAST,
209
+ Compensation.load_impedance_high_range_2: LoadImpedance.LOW, Compensation.rise_time_high_range_2: RiseTime.FAST,
210
+ Compensation.load_impedance_high_range_3: LoadImpedance.LOW, Compensation.rise_time_high_range_3: RiseTime.MEDIUM,
211
+ Compensation.load_impedance_low_range_1: LoadImpedance.HIGH, Compensation.rise_time_low_range_1: RiseTime.FAST,
212
+ Compensation.load_impedance_low_range_2: LoadImpedance.MEDIUM, Compensation.rise_time_low_range_2: RiseTime.FAST
213
+ }
214
+ }
215
+
216
+ if spikesafe_model_max_current_amps not in model_params:
217
+ if enable_logging:
218
+ log.warning(f"{spikesafe_model_max_current_amps}A SpikeSafe Model not defined for optimum compensation, defaulting to LoadImpedance.MEDIUM and RiseTime.FAST")
219
+ return LoadImpedance.MEDIUM, RiseTime.FAST
220
+
221
+ model_params_current = model_params[spikesafe_model_max_current_amps]
222
+
223
+ if Compensation.__is_high_range__(set_current_amps, model_params_current):
224
+ load_impedance, rise_time = Compensation.__use_high_range_compensation__(spikesafe_model_max_current_amps, set_current_amps, model_params_current)
225
+ else:
226
+ load_impedance, rise_time = Compensation.__use_low_range_compensation__(set_current_amps, model_params_current)
227
+
228
+ if enable_logging:
229
+ log.info(f"Optimum compensation for {set_current_amps}A on {spikesafe_model_max_current_amps}A SpikeSafe model is LoadImpedance.{load_impedance} and RiseTime.{rise_time}.")
230
+ return load_impedance, rise_time
231
+
232
+ @staticmethod
233
+ def get_custom_compensation(spikesafe_model_max_current_amps, set_current_amps, device_type, custom_compensation_table, pulse_on_time_seconds=None, enable_logging=False):
234
+ """
235
+ Returns the custom compensation values for a given set_current_amps and device_type based on a custom_compensation_table, and optionally a given pulse on time
236
+
237
+ Parameters
238
+ ----------
239
+ spikesafe_model_max_current_amps : float
240
+ Maximum current of the SpikeSafe model
241
+ set_current_amps : float
242
+ Current to be set on SpikeSafe
243
+ device_type : str
244
+ Device type of the DUT
245
+ custom_compensation_table : list
246
+ Custom compensation table to be used for compensation. This should be the result of calling the load_custom_compensation_table(file_path) function conforming to the custom_compensation_table_schema.
247
+ pulse_on_time_seconds : float, optional
248
+ Pulse On Time to be set on SpikeSafe (default is None)
249
+ enable_logging : bool, optional
250
+ Enables logging (default is False)
251
+
252
+ Returns
253
+ -------
254
+ LoadImpedance
255
+ Load Impedance compensation value. This should be an instance of the LoadImpedance IntEnum from SpikeSafeEnums.
256
+ RiseTime
257
+ Rise Time compensation value. This should be an instance of the RiseTime IntEnum from SpikeSafeEnums.
258
+
259
+ Raises
260
+ ------
261
+ ValueError
262
+ If set_current_amps is greater than spikesafe_model_max_current_amps
263
+ """
264
+ # Validate the custom_compensation_table against the JSON schema
265
+ try:
266
+ validate(instance=custom_compensation_table, schema=Compensation.custom_compensation_table_schema)
267
+ except Exception:
268
+ raise Exception("Invalid Custom Compensation Table format. Please ensure 'custom_compensation_table' is correctly instantiated by calling 'load_custom_compensation_table(file_path)'.")
269
+
270
+ # Check if the set_current_amps exceeds the model's max current capability
271
+ if set_current_amps > spikesafe_model_max_current_amps:
272
+ raise ValueError(f'Measurement current {set_current_amps}A exceeds SpikeSafe model maximum current capability of {spikesafe_model_max_current_amps}A.')
273
+
274
+ # Non-pulsing or DC-based modes do not require compensation
275
+ if pulse_on_time_seconds is None or pulse_on_time_seconds > 0.0005:
276
+ return LoadImpedance.VERY_LOW, RiseTime.VERY_SLOW
277
+
278
+ # Preprocess the custom compensation table into a dictionary
279
+ compensation_dict = Compensation.__preprocess_compensation_table(custom_compensation_table)
280
+
281
+ # Find the default entry
282
+ default_load_impedance = None
283
+ default_rise_time = None
284
+
285
+ if (spikesafe_model_max_current_amps, device_type) in compensation_dict:
286
+ for entry in compensation_dict[(spikesafe_model_max_current_amps, device_type)]:
287
+ if entry.get('is_default', True):
288
+ default_load_impedance = entry['load_impedance']
289
+ default_rise_time = entry['rise_time']
290
+ break # Once default is found, stop the search
291
+
292
+ # Raise an error if no default entry was found
293
+ if default_load_impedance is None or default_rise_time is None:
294
+ raise ValueError(f'No default entry specified for spikesafe_model_max_current_amps {spikesafe_model_max_current_amps}A {device_type}. Please specify a default entry.')
295
+
296
+ # Find the target entry based on set_current_amps range
297
+ target_load_impedance = default_load_impedance # Start with default values
298
+ target_rise_time = default_rise_time
299
+
300
+ for entry in compensation_dict.get((spikesafe_model_max_current_amps, device_type), []):
301
+ # Check if set_current_amps is within the start and end range
302
+ if (set_current_amps >= entry['set_current_amps_start_range'] and set_current_amps < entry['set_current_amps_end_range']) or set_current_amps == entry['spikesafe_model_max_current_amps']:
303
+ target_load_impedance = entry['load_impedance']
304
+ target_rise_time = entry['rise_time']
305
+ break # Exit the loop once the first match is found
306
+
307
+ # Use the target entry if found, otherwise return the default entry
308
+ # Convert from string to IntEnum before returning
309
+ if enable_logging:
310
+ log.info(f"Custom compensation for {set_current_amps}A on {spikesafe_model_max_current_amps}A SpikeSafe model and device type '{device_type}' is LoadImpedance.{target_load_impedance} and RiseTime.{target_rise_time}.")
311
+ return LoadImpedance[target_load_impedance], RiseTime[target_rise_time]
312
+
313
+ @staticmethod
314
+ def load_custom_compensation_table(file_path):
315
+ """
316
+ Returns a custom compensation table from a JSON file
317
+
318
+ Parameters
319
+ ----------
320
+ file_path : str
321
+ Path to the JSON file containing the custom compensation table
322
+
323
+ Returns
324
+ -------
325
+ list
326
+ Custom compensation table as a list of dictionaries conforming to the custom_compensation_table_schema
327
+
328
+ Raises
329
+ ------
330
+ FileNotFoundError
331
+ If the file does not exist
332
+ IOError
333
+ If an error occurs while loading the file
334
+ ValueError
335
+ If the file contains invalid JSON, schema validation error, or custom compensation table validation error
336
+ """
337
+ # Check if the file exists
338
+ if not os.path.exists(file_path):
339
+ raise FileNotFoundError(f"File not found: {file_path}")
340
+
341
+ # Try to load the file and parse the JSON
342
+ try:
343
+ with open(file_path, 'r') as file:
344
+ custom_compensation_table = json.load(file)
345
+ except json.JSONDecodeError as e:
346
+ raise ValueError(f"Custom Compensation Table file contains invalid JSON: {str(e)}")
347
+ except Exception as e:
348
+ raise IOError(f"An error occurred while loading the Custom Compensation Table file: {str(e)}")
349
+
350
+ # Validate the JSON against the schema
351
+ try:
352
+ validate(instance=custom_compensation_table, schema=Compensation.custom_compensation_table_schema)
353
+ except jsonschema.ValidationError as e:
354
+ raise ValueError(f"Custom Compensation Table file schema validation error: {e.message} in element {list(e.path)}")
355
+ except jsonschema.SchemaError as e:
356
+ raise ValueError(f"Custom Compensation Table file schema definition error: {e.message}")
357
+
358
+ Compensation.__validate_custom_compensation_table(custom_compensation_table)
359
+
360
+ return custom_compensation_table
361
+
362
+ @staticmethod
363
+ def load_custom_compensation_unique_device_types(custom_compensation_table):
364
+ """
365
+ Returns the unique device types from a custom compensation table
366
+
367
+ Parameters
368
+ ----------
369
+ custom_compensation_table : list
370
+ Custom compensation table to be used for compensation. This should be the result of calling the load_custom_compensation_table(file_path) function conforming to the custom_compensation_table_schema
371
+
372
+ Returns
373
+ -------
374
+ list
375
+ List of unique device types in the custom compensation table
376
+ """
377
+ device_types = {entry['device_type'] for entry in custom_compensation_table}
378
+ return list(device_types)
379
+
380
+ # Helper function to determine if high current range should be used
381
+ @staticmethod
382
+ def __is_high_range__(set_current_amps, model_params_current):
383
+ if set_current_amps > model_params_current[Compensation.low_current_range_maximum]:
384
+ return True
385
+ else:
386
+ return False
387
+
388
+ # Helper function to use high current range compensation settings
389
+ @staticmethod
390
+ def __use_high_range_compensation__(spikesafe_model_max_current_amps, set_current_amps, model_params_current):
391
+ range_ratio = set_current_amps / spikesafe_model_max_current_amps
392
+ if range_ratio < 0.5:
393
+ return model_params_current[Compensation.load_impedance_high_range_1], model_params_current[Compensation.rise_time_high_range_1]
394
+ elif range_ratio < 0.7:
395
+ return model_params_current[Compensation.load_impedance_high_range_2], model_params_current[Compensation.rise_time_high_range_2]
396
+ else:
397
+ return model_params_current[Compensation.load_impedance_high_range_3], model_params_current[Compensation.rise_time_high_range_3]
398
+
399
+ # Helper function to use low current range compensation settings
400
+ @staticmethod
401
+ def __use_low_range_compensation__(set_current_amps, model_params_current):
402
+ range_ratio = set_current_amps / model_params_current[Compensation.low_current_range_maximum]
403
+ if range_ratio < 0.7:
404
+ return model_params_current[Compensation.load_impedance_low_range_1], model_params_current[Compensation.rise_time_low_range_1]
405
+ else:
406
+ return model_params_current[Compensation.load_impedance_low_range_2], model_params_current[Compensation.rise_time_low_range_2]
407
+
408
+ # Define the schema for validation
409
+ custom_compensation_table_schema = {
410
+ "type": "array",
411
+ "minItems": 1,
412
+ "items": {
413
+ "type": "object",
414
+ "properties": {
415
+ "spikesafe_model_max_current_amps": {
416
+ "type": "number",
417
+ "minimum": 0
418
+ },
419
+ "device_type": {"type": "string"},
420
+ "is_default": {"type": "boolean"},
421
+ "set_current_amps_start_range": {
422
+ "type": "number",
423
+ "minimum": 0
424
+ },
425
+ "set_current_amps_end_range": {
426
+ "type": "number",
427
+ "minimum": 0
428
+ },
429
+ "load_impedance": {
430
+ "type": "string",
431
+ "enum": ["HIGH", "MEDIUM", "LOW", "VERY_LOW"]
432
+ },
433
+ "rise_time": {
434
+ "type": "string",
435
+ "enum": ["FAST", "MEDIUM", "LOW", "VERY_SLOW"]
436
+ },
437
+ },
438
+ "required": [
439
+ "spikesafe_model_max_current_amps",
440
+ "device_type",
441
+ "is_default",
442
+ "set_current_amps_start_range",
443
+ "set_current_amps_end_range",
444
+ "load_impedance",
445
+ "rise_time"
446
+ ]
447
+ }
448
+ }
449
+
450
+ @staticmethod
451
+ def __preprocess_compensation_table(custom_compensation_table):
452
+ compensation_dict = {}
453
+ for entry in custom_compensation_table:
454
+ key = (entry['spikesafe_model_max_current_amps'], entry['device_type'])
455
+ if key not in compensation_dict:
456
+ compensation_dict[key] = []
457
+ compensation_dict[key].append(entry)
458
+ return compensation_dict
459
+
460
+ @staticmethod
461
+ def __validate_custom_compensation_table(custom_compensation_table):
462
+ # Group entries by unique combination of "spikesafe_model_max_current_amps" and "device_type"
463
+ # e.g. (5.0, "laser_green"): [
464
+ # (0, { "spikesafe_model_max_current_amps": 5.0, "device_type": "laser_green" ... }),
465
+ # (1, { "spikesafe_model_max_current_amps": 5.0, "device_type": "laser_green" ... }),
466
+ # ...],
467
+ grouped_entries = {}
468
+
469
+ # Perform validations on each element
470
+ for index, entry in enumerate(custom_compensation_table):
471
+ Compensation.__validate_element_set_current_ranges(entry, index)
472
+
473
+ # Group by max current and device type
474
+ combination_key = (entry["spikesafe_model_max_current_amps"], entry["device_type"])
475
+
476
+ # Check if the combination_key already exists in grouped_entries
477
+ if combination_key not in grouped_entries:
478
+ grouped_entries[combination_key] = [] # Initialize with an empty list if it does not exist
479
+
480
+ # Append the current index and entry to the list for this combination_key
481
+ grouped_entries[combination_key].append((index, entry))
482
+
483
+ # Perform validations on each group
484
+ for (spikesafe_model_max_current_amps, device_type), entries in grouped_entries.items():
485
+ Compensation.__validate_group_has_is_default(entries, spikesafe_model_max_current_amps, device_type)
486
+ Compensation.__validate_group_set_current_ranges(entries, spikesafe_model_max_current_amps, device_type)
487
+
488
+ @staticmethod
489
+ def __validate_element_set_current_ranges(entry, index):
490
+ # Validation Rule: Validate that set_current_amps_start_range is less than or equal to set_current_amps_end_range
491
+ if entry['set_current_amps_start_range'] > entry['set_current_amps_end_range']:
492
+ raise ValueError(
493
+ f"'set_current_amps_start_range' ({entry['set_current_amps_start_range']}) cannot be greater than "
494
+ f"'set_current_amps_end_range' ({entry['set_current_amps_end_range']}) in element [{index}]"
495
+ )
496
+
497
+ @staticmethod
498
+ def __validate_group_has_is_default(entries, spikesafe_model_max_current_amps, device_type):
499
+ # Validation Rule: Ensure each group has exactly one "is_default" set to True
500
+
501
+ # Initialize a list to store entries with "is_default" set to True
502
+ defaults = []
503
+
504
+ # Iterate through each entry to check for the "is_default" flag
505
+ for index, entry in entries: # entry is a tuple (index, entry)
506
+ if entry['is_default']: # Access the dictionary part of the tuple
507
+ defaults.append(entry) # Append the dictionary to defaults
508
+
509
+ num_defaults = len(defaults)
510
+
511
+ if num_defaults == 0:
512
+ raise ValueError(f"No 'is_default' true entry found for combination: "
513
+ f"'spikesafe_model_max_current_amps' {spikesafe_model_max_current_amps}, 'device_type' {device_type}")
514
+
515
+ if num_defaults > 1:
516
+ raise ValueError(f"Multiple 'is_default' true entries found for combination: "
517
+ f"'spikesafe_model_max_current_amps' {spikesafe_model_max_current_amps}, 'device_type' {device_type}")
518
+
519
+ @staticmethod
520
+ def __validate_group_set_current_ranges(entries, spikesafe_model_max_current_amps, device_type):
521
+ # Validation Rule: Ensure that the ranges do not overlap
522
+ # Sort entries by set_current_amps_start_range to validate range continuity
523
+ sorted_entries = sorted(entries, key=lambda x: x[1]['set_current_amps_start_range'])
524
+
525
+ for i in range(1, len(sorted_entries)):
526
+ prev_index, prev_entry = sorted_entries[i - 1]
527
+ current_index, current_entry = sorted_entries[i]
528
+
529
+ # Check if the previous end range is greater than the current start range (overlap)
530
+ if prev_entry['set_current_amps_end_range'] > current_entry['set_current_amps_start_range']:
531
+ raise ValueError(
532
+ f"Set Current Range Overlap detected: 'set_current_amps_start_range' ({current_entry['set_current_amps_start_range']}) "
533
+ f"overlaps with element {prev_index} 'set_current_amps_end_range' ({prev_entry['set_current_amps_end_range']}) "
534
+ f"in element [{current_index}] for device_type: {device_type}, max current: {spikesafe_model_max_current_amps}"
535
+ )
536
+
537
+ # Optionally, check for gaps in the range
538
+ # if prev_entry['set_current_amps_end_range'] < current_entry['set_current_amps_start_range']:
539
+ # print(f"Warning: Gap detected between element {prev_index} end range ({prev_entry['set_current_amps_end_range']}) "
540
+ # f"and element {current_index} start range ({current_entry['set_current_amps_start_range']}) "
541
+ # f"for device_type: {device_type}, max current: {max_current}")
542
+
543
+
544
+ def get_optimum_compensation(spikesafe_model_max_current_amps, set_current_amps, pulse_on_time_seconds = None, enable_logging = False):
545
+ """
546
+ Obsolete: Use Compensation.get_optimum_compensation() instead.
547
+ """
548
+ return Compensation.get_optimum_compensation(spikesafe_model_max_current_amps, set_current_amps, pulse_on_time_seconds, enable_logging)
549
+
550
+ def get_custom_compensation(spikesafe_model_max_current_amps, set_current_amps, device_type, custom_compensation_table, pulse_on_time_seconds=None, enable_logging=False):
551
+ """
552
+ Obsolete: Use Compensation.get_custom_compensation() instead.
553
+ """
554
+ return Compensation.get_custom_compensation(spikesafe_model_max_current_amps, set_current_amps, device_type, custom_compensation_table, pulse_on_time_seconds, enable_logging)
555
+
556
+ def load_custom_compensation_table(file_path):
557
+ """
558
+ Obsolete: Use Compensation.load_custom_compensation_table() instead.
559
+ """
560
+ return Compensation.load_custom_compensation_table(file_path)
561
+
562
+ def load_custom_compensation_unique_device_types(custom_compensation_table):
563
+ """
564
+ Obsolete: Use Compensation.load_custom_compensation_unique_device_types() instead.
565
+ """
566
+ return Compensation.load_custom_compensation_unique_device_types(custom_compensation_table)
@@ -7,6 +7,8 @@ class DigitizerData():
7
7
 
8
8
  Generally, this class will be used within an array of DigitizerData objects.
9
9
 
10
+ ...
11
+
10
12
  Attributes
11
13
  ----------
12
14
  sample_number : int