Skip to content

Commit

Permalink
Merge branch 'develop' into doc/environment-docstring
Browse files Browse the repository at this point in the history
  • Loading branch information
phmbressan authored Feb 29, 2024
2 parents f637a9d + 65b3315 commit 3cc91e7
Show file tree
Hide file tree
Showing 28 changed files with 1,689 additions and 1,311 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

### Added

- ENH: adds `Function.remove_outliers` method [#554](https://github.com/RocketPy-Team/RocketPy/pull/554)

### Changed

- ENH: Optional argument to show the plot in Function.compare_plots [#563](https://github.com/RocketPy-Team/RocketPy/pull/563)

### Fixed

- BUG: export_eng 'Motor' method would not work for liquid motors. [#559](https://github.com/RocketPy-Team/RocketPy/pull/559)

## [v1.2.1] - 2024-02-22

Expand Down
86 changes: 69 additions & 17 deletions rocketpy/mathutils/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -1144,6 +1144,51 @@ def low_pass_filter(self, alpha, file_path=None):
title=self.title,
)

def remove_outliers_iqr(self, threshold=1.5):
"""Remove outliers from the Function source using the interquartile
range method. The Function should have an array-like source.
Parameters
----------
threshold : float, optional
Threshold for the interquartile range method. Default is 1.5.
Returns
-------
Function
The Function with the outliers removed.
References
----------
[1] https://en.wikipedia.org/wiki/Outlier#Tukey's_fences
"""

if callable(self.source):
raise TypeError(
"Cannot remove outliers if the source is a callable object."
+ " The Function.source should be array-like."
)

x = self.x_array
y = self.y_array
y_q1 = np.percentile(y, 25)
y_q3 = np.percentile(y, 75)
y_iqr = y_q3 - y_q1
y_lower = y_q1 - threshold * y_iqr
y_upper = y_q3 + threshold * y_iqr

y_filtered = y[(y >= y_lower) & (y <= y_upper)]
x_filtered = x[(y >= y_lower) & (y <= y_upper)]

return Function(
source=np.column_stack((x_filtered, y_filtered)),
inputs=self.__inputs__,
outputs=self.__outputs__,
interpolation=self.__interpolation__,
extrapolation=self.__extrapolation__,
title=self.title,
)

# Define all presentation methods
def __call__(self, *args):
"""Plot the Function if no argument is given. If an
Expand Down Expand Up @@ -1474,45 +1519,51 @@ def compare_plots(
force_data=False,
force_points=False,
return_object=False,
show=True,
):
"""Plots N 1-Dimensional Functions in the same plot, from a lower
limit to an upper limit, by sampling the Functions several times in
the interval.
Parameters
----------
plot_list : list
plot_list : list[Tuple[Function,str]]
List of Functions or list of tuples in the format (Function,
label), where label is a string which will be displayed in the
legend.
lower : scalar, optional
The lower limit of the interval in which the Functions are to be
plotted. The default value for function type Functions is 0. By
contrast, if the Functions given are defined by a dataset, the
default value is the lowest value of the datasets.
upper : scalar, optional
The upper limit of the interval in which the Functions are to be
plotted. The default value for function type Functions is 10. By
contrast, if the Functions given are defined by a dataset, the
default value is the highest value of the datasets.
lower : float, optional
This represents the lower limit of the interval for plotting the
Functions. If the Functions are defined by a dataset, the smallest
value from the dataset is used. If no value is provided (None), and
the Functions are of Function type, 0 is used as the default.
upper : float, optional
This represents the upper limit of the interval for plotting the
Functions. If the Functions are defined by a dataset, the largest
value from the dataset is used. If no value is provided (None), and
the Functions are of Function type, 10 is used as the default.
samples : int, optional
The number of samples in which the functions will be evaluated for
plotting it, which draws lines between each evaluated point.
The default value is 1000.
title : string, optional
title : str, optional
Title of the plot. Default value is an empty string.
xlabel : string, optional
xlabel : str, optional
X-axis label. Default value is an empty string.
ylabel : string, optional
ylabel : str, optional
Y-axis label. Default value is an empty string.
force_data : Boolean, optional
force_data : bool, optional
If Function is given by an interpolated dataset, setting force_data
to True will plot all points, as a scatter, in the dataset.
Default value is False.
force_points : Boolean, optional
force_points : bool, optional
Setting force_points to True will plot all points, as a scatter, in
which the Function was evaluated to plot it. Default value is
False.
return_object : bool, optional
If True, returns the figure and axis objects. Default value is
False.
show : bool, optional
If True, shows the plot. Default value is True.
Returns
-------
Expand Down Expand Up @@ -1586,7 +1637,8 @@ def compare_plots(
plt.xlabel(xlabel)
plt.ylabel(ylabel)

plt.show()
if show:
plt.show()

if return_object:
return fig, ax
Expand Down
49 changes: 27 additions & 22 deletions rocketpy/motors/motor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1012,31 +1012,36 @@ def export_eng(self, file_name, motor_name):
None
"""
# Open file
file = open(file_name, "w")

# Write first line
file.write(
motor_name
+ " {:3.1f} {:3.1f} 0 {:2.3} {:2.3} RocketPy\n".format(
2000 * self.grain_outer_radius,
1000
* self.grain_number
* (self.grain_initial_height + self.grain_separation),
self.propellant_initial_mass,
self.propellant_initial_mass,
)
)
with open(file_name, "w") as file:
# Write first line
def get_attr_value(obj, attr_name, multiplier=1):
return multiplier * getattr(obj, attr_name, 0)

grain_outer_radius = get_attr_value(self, "grain_outer_radius", 2000)
grain_number = get_attr_value(self, "grain_number", 1000)
grain_initial_height = get_attr_value(self, "grain_initial_height")
grain_separation = get_attr_value(self, "grain_separation")

grain_total = grain_number * (grain_initial_height + grain_separation)

if grain_outer_radius == 0 or grain_total == 0:
warnings.warn(
"The motor object doesn't have some grain-related attributes. "
"Using zeros to write to file."
)

# Write thrust curve data points
for time, thrust in self.thrust.source[1:-1, :]:
# time, thrust = item
file.write("{:.4f} {:.3f}\n".format(time, thrust))
file.write(
f"{motor_name} {grain_outer_radius:3.1f} {grain_total:3.1f} 0 "
f"{self.propellant_initial_mass:2.3} "
f"{self.propellant_initial_mass:2.3} RocketPy\n"
)

# Write last line
file.write("{:.4f} {:.3f}\n".format(self.thrust.source[-1, 0], 0))
# Write thrust curve data points
for time, thrust in self.thrust.source[1:-1, :]:
file.write(f"{time:.4f} {thrust:.3f}\n")

# Close file
file.close()
# Write last line
file.write(f"{self.thrust.source[-1, 0]:.4f} {0:.3f}\n")

return None

Expand Down
Loading

0 comments on commit 3cc91e7

Please sign in to comment.