Skip to content

Commit 0ef9849

Browse files
authored
Merge pull request #485 from RocketPy-Team/bug/v1.1.1-docs-build
BUG: Function breaks if a header is present in the csv file
2 parents 5ff3476 + cd9d10c commit 0ef9849

File tree

9 files changed

+113
-41
lines changed

9 files changed

+113
-41
lines changed

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,18 @@ straightforward as possible.
4242

4343
-
4444

45+
## [v1.1.2] - 2023-11-25
46+
47+
You can install this version by running `pip install rocketpy==1.1.2`
48+
49+
### Fixed
50+
51+
- BUG: Function breaks if a header is present in the csv file [#485](https://github.com/RocketPy-Team/RocketPy/pull/485)
4552

4653
## [v1.1.1] - 2023-11-23
4754

4855
You can install this version by running `pip install rocketpy==1.1.1`
4956

50-
5157
### Added
5258

5359
- DOC: Added this changelog file [#472](https://github.com/RocketPy-Team/RocketPy/pull/472)

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
author = "RocketPy Team"
2525

2626
# The full version, including alpha/beta/rc tags
27-
release = "1.1.1"
27+
release = "1.1.2"
2828

2929

3030
# -- General configuration ---------------------------------------------------

docs/user/installation.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ If you want to choose a specific version to guarantee compatibility, you may ins
1919

2020
.. code-block:: shell
2121
22-
pip install rocketpy==1.1.1
22+
pip install rocketpy==1.1.2
2323
2424
2525
Optional Installation Method: ``conda``

docs/user/motors/liquidmotor.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,15 @@ Then we must first define the tanks:
4040
fuel_gas = Fluid(name="ethanol_g", density=1.59)
4141

4242
# Define tanks geometry
43-
tanks_shape = CylindricalTank(radius = 0.1, height = 1, spherical_caps = True)
43+
tanks_shape = CylindricalTank(radius = 0.1, height = 1.2, spherical_caps = True)
4444

4545
# Define tanks
4646
oxidizer_tank = MassFlowRateBasedTank(
4747
name="oxidizer tank",
4848
geometry=tanks_shape,
4949
flux_time=5,
5050
initial_liquid_mass=32,
51-
initial_gas_mass=0.1,
51+
initial_gas_mass=0.01,
5252
liquid_mass_flow_rate_in=0,
5353
liquid_mass_flow_rate_out=lambda t: 32 / 3 * exp(-0.25 * t),
5454
gas_mass_flow_rate_in=0,

rocketpy/mathutils/function.py

Lines changed: 76 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,26 @@ def __init__(
3939
4040
Parameters
4141
----------
42-
source : function, scalar, ndarray, string
43-
The actual function. If type is function, it will be called for
44-
evaluation. If type is int or float, it will be treated as a
45-
constant function. If ndarray, its points will be used for
46-
interpolation. An ndarray should be as [(x0, y0, z0), (x1, y1, z1),
47-
(x2, y2, z2), ...] where x0 and y0 are inputs and z0 is output. If
48-
string, imports file named by the string and treats it as csv.
49-
The file is converted into ndarray and should not have headers.
42+
source : callable, scalar, ndarray, string, or Function
43+
The data source to be used for the function:
44+
45+
- Callable: Called for evaluation with input values. Must have the
46+
desired inputs as arguments and return a single output value.
47+
Input order is important. Example: Python functions, classes, and
48+
methods.
49+
50+
- int or float: Treated as a constant value function.
51+
52+
- ndarray: Used for interpolation. Format as [(x0, y0, z0),
53+
(x1, y1, z1), ..., (xn, yn, zn)], where 'x' and 'y' are inputs,
54+
and 'z' is the output.
55+
56+
- string: Path to a CSV file. The file is read and converted into an
57+
ndarray. The file can optionally contain a single header line.
58+
59+
- Function: Copies the source of the provided Function object,
60+
creating a new Function with adjusted inputs and outputs.
61+
5062
inputs : string, sequence of strings, optional
5163
The name of the inputs of the function. Will be used for
5264
representation and graphing (axis names). 'Scalar' is default.
@@ -74,6 +86,13 @@ def __init__(
7486
Returns
7587
-------
7688
None
89+
90+
Notes
91+
-----
92+
(I) CSV files can optionally contain a single header line. If present,
93+
the header is ignored during processing.
94+
(II) Fields in CSV files may be enclosed in double quotes. If fields are
95+
not quoted, double quotes should not appear inside them.
7796
"""
7897
# Set input and output
7998
if inputs is None:
@@ -133,25 +152,42 @@ def set_outputs(self, outputs):
133152
return self
134153

135154
def set_source(self, source):
136-
"""Set the source which defines the output of the function giving a
137-
certain input.
155+
"""Sets the data source for the function, defining how the function
156+
produces output from a given input.
138157
139158
Parameters
140159
----------
141-
source : function, scalar, ndarray, string, Function
142-
The actual function. If type is function, it will be called for
143-
evaluation. If type is int or float, it will be treated as a
144-
constant function. If ndarray, its points will be used for
145-
interpolation. An ndarray should be as [(x0, y0, z0), (x1, y1, z1),
146-
(x2, y2, z2), ...] where x0 and y0 are inputs and z0 is output. If
147-
string, imports file named by the string and treats it as csv.
148-
The file is converted into ndarray and should not have headers.
149-
If the source is a Function, its source will be copied and another
150-
Function will be created following the new inputs and outputs.
160+
source : callable, scalar, ndarray, string, or Function
161+
The data source to be used for the function:
162+
163+
- Callable: Called for evaluation with input values. Must have the
164+
desired inputs as arguments and return a single output value.
165+
Input order is important. Example: Python functions, classes, and
166+
methods.
167+
168+
- int or float: Treated as a constant value function.
169+
170+
- ndarray: Used for interpolation. Format as [(x0, y0, z0),
171+
(x1, y1, z1), ..., (xn, yn, zn)], where 'x' and 'y' are inputs,
172+
and 'z' is the output.
173+
174+
- string: Path to a CSV file. The file is read and converted into an
175+
ndarray. The file can optionally contain a single header line.
176+
177+
- Function: Copies the source of the provided Function object,
178+
creating a new Function with adjusted inputs and outputs.
179+
180+
Notes
181+
-----
182+
(I) CSV files can optionally contain a single header line. If present,
183+
the header is ignored during processing.
184+
(II) Fields in CSV files may be enclosed in double quotes. If fields are
185+
not quoted, double quotes should not appear inside them.
151186
152187
Returns
153188
-------
154189
self : Function
190+
Returns the Function instance.
155191
"""
156192
_ = self._check_user_input(
157193
source,
@@ -165,20 +201,17 @@ def set_source(self, source):
165201
source = source.get_source()
166202
# Import CSV if source is a string or Path and convert values to ndarray
167203
if isinstance(source, (str, Path)):
168-
# Read file and check for headers
169-
with open(source, mode="r") as f:
170-
first_line = f.readline()
171-
# If headers are found...
172-
if first_line[0] in ['"', "'"]:
173-
# Headers available
174-
first_line = first_line.replace('"', " ").replace("'", " ")
175-
first_line = first_line.split(" , ")
176-
self.set_inputs(first_line[0])
177-
self.set_outputs(first_line[1:])
178-
source = np.loadtxt(source, delimiter=",", skiprows=1, dtype=float)
179-
# if headers are not found
180-
else:
181-
source = np.loadtxt(source, delimiter=",", dtype=float)
204+
with open(source, "r") as file:
205+
try:
206+
source = np.loadtxt(file, delimiter=",", dtype=float)
207+
except ValueError:
208+
# If an error occurs, headers are present
209+
source = np.loadtxt(source, delimiter=",", dtype=float, skiprows=1)
210+
except Exception as e:
211+
raise ValueError(
212+
"The source file is not a valid csv or txt file."
213+
) from e
214+
182215
# Convert to ndarray if source is a list
183216
if isinstance(source, (list, tuple)):
184217
source = np.array(source, dtype=np.float64)
@@ -2830,7 +2863,15 @@ def _check_user_input(
28302863
# Deal with csv or txt
28312864
if isinstance(source, (str, Path)):
28322865
# Convert to numpy array
2833-
source = np.loadtxt(source, delimiter=",", dtype=float)
2866+
try:
2867+
source = np.loadtxt(source, delimiter=",", dtype=float)
2868+
except ValueError:
2869+
# Skip header
2870+
source = np.loadtxt(source, delimiter=",", dtype=float, skiprows=1)
2871+
except Exception as e:
2872+
raise ValueError(
2873+
"The source file is not a valid csv or txt file."
2874+
) from e
28342875

28352876
else:
28362877
# this will also trigger an error if the source is not a list of

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
setuptools.setup(
2525
name="rocketpy",
26-
version="1.1.1",
26+
version="1.1.2",
2727
install_requires=necessary_require,
2828
extras_require={
2929
"env_analysis": env_analysis_require,
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
time,value
2+
0,100
3+
1,200
4+
2,300
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
"time","value"
2+
0,100
3+
1,200
4+
2,300

tests/test_function.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,23 @@ def test_function_from_csv(func_from_csv, func_2d_from_csv):
3737
)
3838

3939

40+
@pytest.mark.parametrize(
41+
"csv_file",
42+
[
43+
"tests/fixtures/function/1d_quotes.csv",
44+
"tests/fixtures/function/1d_no_quotes.csv",
45+
],
46+
)
47+
def test_func_from_csv_with_header(csv_file):
48+
"""Tests if a Function can be created from a CSV file with a single header
49+
line. It tests cases where the fields are separated by quotes and without
50+
quotes."""
51+
f = Function(csv_file)
52+
assert f.__repr__() == "'Function from R1 to R1 : (Scalar) → (Scalar)'"
53+
assert np.isclose(f(0), 100)
54+
assert np.isclose(f(0) + f(1), 300), "Error summing the values of the function"
55+
56+
4057
def test_getters(func_from_csv, func_2d_from_csv):
4158
"""Test the different getters of the Function class.
4259

0 commit comments

Comments
 (0)