Skip to content

Commit 2527eb9

Browse files
committed
Merge remote-tracking branch 'origin/develop' into bugfix/fix-single-smooth
2 parents 975a5fc + b28326c commit 2527eb9

File tree

13 files changed

+197
-76
lines changed

13 files changed

+197
-76
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,4 @@ jobs:
8383
with:
8484
name: html-docs
8585
path: docs/_build/html/
86+
retention-days: 1

tests/tendencies/periodic/test_sine_wave.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def test_generate():
5252
user_start=0, user_duration=1, user_base=2, user_amplitude=3, user_phase=1
5353
)
5454
time, values = tendency.get_value()
55-
assert np.all(time == np.linspace(0, 1, 101))
55+
assert np.all(time == np.linspace(0, 1, 32 + 1))
5656
assert np.allclose(values, 2 + 3 * np.sin(2 * np.pi * time + 1))
5757
assert not tendency.annotations
5858

tests/tendencies/test_repeat.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,12 @@ def test_filled(repeat_waveform):
166166
assert tendencies[2].base == 2
167167
assert tendencies[2].amplitude == -1
168168
assert tendencies[2].frequency == 0.25
169+
170+
171+
def test_too_short(repeat_waveform):
172+
"""Check for warning in annotations if repeated waveform has not completed a
173+
single repetition."""
174+
repeat_waveform["user_duration"] = 2
175+
repeat_tendency = RepeatTendency(**repeat_waveform)
176+
assert repeat_tendency.annotations
177+
assert repeat_tendency.annotations[0]["type"] == "warning"

tests/test_yaml_parser.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,30 @@ def assert_tendencies_correct(tendencies):
9191
assert tendencies[6].duration == approx(2)
9292
assert tendencies[6].from_ == approx(8)
9393
assert tendencies[6].to == approx(0)
94+
95+
96+
def test_scientific_notation():
97+
"""Test if scientific notation is parsed correctly."""
98+
waveforms = {
99+
"waveform:\n- {type: linear, to: 1.5e5}": 1.5e5,
100+
"waveform:\n- {type: linear, to: 1.5e+5}": 1.5e5,
101+
"waveform:\n- {type: linear, to: 1.5E+5}": 1.5e5,
102+
"waveform:\n- {type: linear, to: 1.5e-5}": 1.5e-5,
103+
"waveform:\n- {type: linear, to: 1.5E-5}": 1.5e-5,
104+
"waveform:\n- {type: linear, to: 1e5}": 1e5,
105+
"waveform:\n- {type: linear, to: 1e+5}": 1e5,
106+
"waveform:\n- {type: linear, to: 1E+5}": 1e5,
107+
"waveform:\n- {type: linear, to: 1e-5}": 1e-5,
108+
"waveform:\n- {type: linear, to: 1E-5}": 1e-5,
109+
"waveform:\n- {type: linear, to: 0e5}": 0.0,
110+
"waveform:\n- {type: linear, to: 0E0}": 0.0,
111+
"waveform:\n- {type: linear, to: -1.5e5}": -1.5e5,
112+
"waveform:\n- {type: linear, to: -1E+5}": -1e5,
113+
"waveform:\n- {type: linear, to: -1.5e-5}": -1.5e-5,
114+
}
115+
116+
yaml_parser = YamlParser()
117+
118+
for waveform, expected_value in waveforms.items():
119+
yaml_parser.parse_waveforms(waveform)
120+
assert yaml_parser.waveform.tendencies[0].to == expected_value

waveform_editor/annotations.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33

44

55
class Annotations(UserList):
6+
def __str__(self):
7+
sorted_annotations = sorted(self, key=lambda ann: ann["row"])
8+
return "\n".join(
9+
f"Line {a['row'] + 1}: {a['text']}" for a in sorted_annotations
10+
)
11+
612
def add_annotations(self, annotations):
713
"""Merge another Annotations instance into this instance by appending its
814
annotations.

waveform_editor/tendencies/base.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,16 +87,26 @@ def __init__(self, **kwargs):
8787
super().__init__()
8888
with param.parameterized.batch_call_watchers(self):
8989
for param_name, value in kwargs.items():
90+
if param_name not in self.param:
91+
unknown_kwargs.append(param_name.replace("user_", ""))
92+
continue
93+
9094
if value is None:
9195
self.annotations.add(
9296
self.line_number,
9397
f"The value of {param_name.replace('user_', '')!r} cannot be "
9498
"empty.\nIt will be set to its default value.\n",
9599
is_warning=True,
96100
)
101+
continue
97102

98-
if param_name not in self.param:
99-
unknown_kwargs.append(param_name.replace("user_", ""))
103+
if isinstance(value, (int, float)) and not np.isfinite(value):
104+
self.annotations.add(
105+
self.line_number,
106+
f"The value for {param_name.replace('user_', '')!r} is not a "
107+
"valid number.\nIt will be set to its default value.\n",
108+
is_warning=True,
109+
)
100110
continue
101111

102112
try:
@@ -132,6 +142,14 @@ def _handle_unknown_kwargs(self, unknown_kwargs):
132142
word.replace("user_", "") for word in self.param if "user_" in word
133143
]
134144
for unknown_kwarg in unknown_kwargs:
145+
if ":" in unknown_kwarg:
146+
error_msg = (
147+
f"Found ':' in {unknown_kwarg!r}. "
148+
"Did you forget a space after the ':'?\n"
149+
)
150+
self.annotations.add(self.line_number, error_msg, is_warning=True)
151+
continue
152+
135153
suggestion = self.annotations.suggest(unknown_kwarg, params_list)
136154
error_msg = (
137155
f"Unknown keyword passed: {unknown_kwarg!r}. {suggestion}"

waveform_editor/tendencies/linear.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,8 @@ def _calc_values(self):
124124
values = solve_with_constraints(inputs, constraint_matrix)
125125
except InconsistentInputsError:
126126
error_msg = (
127-
"Inputs are inconsistent: from + duration * rate != end\n"
128-
"The 'from', 'to', and 'rate' values are set to 0.\n"
127+
"Inputs are inconsistent: from + duration * rate != to\n"
128+
"The 'from', 'to', and 'rate' values will be set to 0.\n"
129129
)
130130
self.annotations.add(self.line_number, error_msg, is_warning=True)
131131
values = (0.0, 0.0, 0.0)

waveform_editor/tendencies/periodic/sine_wave.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ def get_value(
2222
Tuple containing the time and its tendency values.
2323
"""
2424
if time is None:
25-
sampling_rate = 100
25+
sampling_rate = 32 * self.frequency
2626
num_steps = int(self.duration * sampling_rate) + 1
27+
# Choosing prime number instead of 100k to reduce aliasing artifacts
28+
num_steps = min(num_steps, 100003)
2729
time = np.linspace(float(self.start), float(self.end), num_steps)
2830
values = self._calc_sine(time)
2931
return time, values

waveform_editor/tendencies/piecewise.py

Lines changed: 33 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,18 @@ class PiecewiseLinearTendency(BaseTendency):
2020
)
2121

2222
def __init__(self, user_time=None, user_value=None, **kwargs):
23-
user_start, user_end = self._handle_user_time(user_time)
24-
annotations = self._remove_user_time_params(kwargs)
25-
26-
super().__init__(user_start=user_start, user_end=user_end, **kwargs)
27-
self.annotations.add_annotations(annotations)
28-
self._validate_time_value(user_time, user_value)
23+
self.pre_check_annotations = Annotations()
24+
self.line_number = kwargs.get("line_number", 0)
25+
time, value = self._validate_time_value(user_time, user_value)
26+
self._remove_user_time_params(kwargs)
27+
super().__init__(
28+
user_start=time[0],
29+
user_end=time[-1],
30+
time=time,
31+
value=value,
32+
**kwargs,
33+
)
34+
self.annotations.add_annotations(self.pre_check_annotations)
2935

3036
self.start_value_set = True
3137
self.param.update(values_changed=True)
@@ -76,37 +82,42 @@ def _validate_time_value(self, time, value):
7682
Args:
7783
time: List of time values.
7884
value: List of values defined on each time step.
85+
86+
Returns:
87+
Tuple containing the validated time and value arrays. If any errors are
88+
encountered during the validation, the self.time and self.value defaults are
89+
returned instead.
7990
"""
8091
if time is None or value is None:
8192
error_msg = "Both the `time` and `value` arrays must be specified.\n"
82-
self.annotations.add(self.line_number, error_msg)
93+
self.pre_check_annotations.add(self.line_number, error_msg)
8394
elif len(time) != len(value):
8495
error_msg = (
8596
"The provided time and value arrays are not of the same length.\n"
8697
)
87-
self.annotations.add(self.line_number, error_msg)
98+
self.pre_check_annotations.add(self.line_number, error_msg)
8899
elif len(time) < 2:
89100
error_msg = (
90101
"The provided time and value arrays should have a length "
91102
"of at least 2.\n"
92103
)
104+
self.pre_check_annotations.add(self.line_number, error_msg)
93105

94-
self.annotations.add(self.line_number, error_msg)
95-
else:
106+
try:
107+
time = np.asarray_chkfinite(time, dtype=float)
108+
value = np.asarray_chkfinite(value, dtype=float)
96109
is_monotonic = np.all(np.diff(time) > 0)
97110
if not is_monotonic:
98111
error_msg = "The provided time array is not monotonically increasing.\n"
99-
self.annotations.add(self.line_number, error_msg)
100-
101-
try:
102-
time = np.asarray(time, dtype=float)
103-
value = np.asarray(value, dtype=float)
112+
self.pre_check_annotations.add(self.line_number, error_msg)
104113
except Exception as error:
105-
self.annotations.add(self.line_number, str(error))
106-
# Only update the time and value arrays if there are no errors
107-
if not self.annotations:
108-
self.time = np.array(time)
109-
self.value = np.array(value)
114+
self.pre_check_annotations.add(self.line_number, str(error))
115+
116+
# If there are any errors, use the default values instead
117+
if not self.pre_check_annotations:
118+
return time, value
119+
else:
120+
return self.time, self.value
110121

111122
def _remove_user_time_params(self, kwargs):
112123
"""Remove user_start, user_duration, and user_end if they are passed as kwargs,
@@ -115,37 +126,13 @@ def _remove_user_time_params(self, kwargs):
115126
116127
Args:
117128
kwargs: the keyword arguments.
118-
119-
Returns:
120-
annotations containing the errors, or an empty annotations object.
121129
"""
122130
line_number = kwargs.get("user_line_number", 0)
123-
annotations = Annotations()
124131

125-
error_msg = "is not allowed in a piecewise tendency\nIt will be ignored.\n"
132+
error_msg = "is not allowed in a piecewise tendency\n"
126133
for key in ["user_start", "user_duration", "user_end"]:
127134
if key in kwargs:
128135
kwargs.pop(key)
129-
annotations.add(
136+
self.pre_check_annotations.add(
130137
line_number, f"'{key.replace('user_', '')}' {error_msg}"
131138
)
132-
133-
return annotations
134-
135-
def _handle_user_time(self, user_time):
136-
"""Get the start and end of the time array.
137-
138-
Args:
139-
user_time: Time array provided by the user.
140-
"""
141-
if (
142-
user_time is not None
143-
and len(user_time) > 1
144-
and (user_time[-1] - user_time[0]) > 0
145-
):
146-
user_start = user_time[0]
147-
user_end = user_time[-1]
148-
else:
149-
user_start = self.time[0]
150-
user_end = self.time[-1]
151-
return user_start, user_end

waveform_editor/tendencies/repeat.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,21 @@ def __init__(self, **kwargs):
1818

1919
self.waveform = Waveform(waveform=waveform, is_repeated=True)
2020
if not self.waveform.tendencies:
21-
error_msg = "There are no tendencies in the repeated waveform."
21+
error_msg = "There are no tendencies in the repeated waveform.\n"
2222
self.annotations.add(self.line_number, error_msg)
2323
return
2424

2525
if self.waveform.tendencies[0].start != 0:
26-
error_msg = "The starting point of the first repeated must be set to 0."
26+
error_msg = "The starting point of the first repeated must be set to 0.\n"
2727
self.annotations.add(self.line_number, error_msg)
2828

29+
if self.duration < self.waveform.tendencies[-1].end:
30+
error_msg = (
31+
"The repeated tendency has not completed a single repetition.\n"
32+
"Perhaps increase the duration of the repeated tendency?\n"
33+
)
34+
self.annotations.add(self.line_number, error_msg, is_warning=True)
35+
2936
# Link the last tendency to the first tendency in the repeated waveform
3037
# We must lock the start to 0, otherwise it will take the start value of the
3138
# previous tendency.

0 commit comments

Comments
 (0)