-
Notifications
You must be signed in to change notification settings - Fork 3
/
filter_rms_error.py
114 lines (104 loc) · 4.6 KB
/
filter_rms_error.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import numpy as np
import matplotlib.pyplot as plt
def filter_rms_error(filter_object,
to_filter_data_lambda,
desired_filter_data_lambda,
dt=0.01,
start_time=0.0,
end_time=10.0,
skip_initial=0,
use_pressure_error=False,
abs_tol=2.0,
rel_tol=0.02,
generate_plot=False):
"""Calculates root-mean-square (RMS) error between data calculated
by a filter and a reference function that nominally should yield
equal data.
Parameters
----------
filter_object : object
An object representing the filter being tested. It must have
the following functions defined.
filter_object(dt: float)
filter_object.append(datum: float)
filter_object.get_datum() -> float
to_filter_data_lambda : lambda
A function representing the data being fed to the filter. It
should be of the form
to_filter_lambda(time: np.array) -> np.array
desired_filter_data_lambda : lambda
A function representing output that the filter_object output
should be nominally equal to. It should be of the form
desired_filter_data_lambda(time: np.array) -> np.array
start_time=0.0 : float
end_time=10.0 : float
dt=0.01 : float
Represents a time interval in seconds of [start_time, end_time)
with steps of dt between. Calculated as
np.arange(start_time, end_time, dt).
skip_initial=0 : int
Ignores the first skip_inital data points when calculating
error. This is useful when a filter has an initial transient
before it starts returning useful data.
use_pressure_error=False : bool
Instead of calculating direct RMS error, this function will
calculate a normalized error based on given tolerances. This is
useful for ventilators trying to calculate pressure meeting
ISO 80601-2-80:2018 201.12.4.101.1. Default values for the
tolerances are based on this standard.
abs_tol=2.0 : float
The design absolute tolerance when calculating pressure error,
i.e. +/- abs_tol. Only used if use_pressure_error == True.
rel_tol=0.02 : float
The design relative tolerance when calculating pressure error,
i.e. +/- rel_tol * desired_filter_data(t).
generate_plot=False : bool
If True, then a plot of the filter data and
desired_filter_data_lambda with respect to time will be
generated. Note that this should be false in non-interactive
contexts.
Returns
-------
error : float
If use_pressure_error is False,
This returns the RMS error between the filter output and
the output of desired_filter_data_lambda.
If use_pressure_error is True,
This returns a normalized error between the filter output
and the output of desired_filter_data_lambda. If error < 1,
then the typical error is within the design tolerance. When
testing, you can add a safety factor to the error by
asserting that the error must be less than 1/safety_factor.
"""
t = np.arange(start_time, end_time, dt)
test_filter = filter_object(dt)
to_filter_data = to_filter_data_lambda(t)
filtered_data = np.array([])
desired_filtered_data = desired_filter_data_lambda(t)
for i in range(len(to_filter_data)):
test_filter.append(to_filter_data[i])
filtered_data = np.append(filtered_data,
test_filter.get_datum())
if generate_plot:
figure, axis = plt.subplots()
axis.plot(t, to_filter_data, label="To Filter Data")
axis.plot(t, filtered_data, label="Filtered Data")
axis.plot(t, desired_filtered_data, label="Desired Filtered Data")
axis.legend()
plt.show()
if not use_pressure_error:
return _root_mean_square(
(filtered_data - desired_filtered_data)[skip_initial:])
else:
return _pressure_error(filtered_data[skip_initial:],
desired_filtered_data[skip_initial:])
def _root_mean_square(np_array):
return np.sqrt(np.mean(np.square(np_array)))
def _pressure_error(calculated_pressure,
actual_pressure,
abs_tol=2.0,
rel_tol=0.02):
return _root_mean_square(
(calculated_pressure - actual_pressure)
/ (np.full_like(actual_pressure, abs_tol) + rel_tol * actual_pressure)
)