From dafbc8783160b5be3effba82b09ae8381b4ddaf9 Mon Sep 17 00:00:00 2001 From: Pedro Bressan Date: Sat, 25 Nov 2023 10:42:03 -0300 Subject: [PATCH 01/10] BUG: add Function inputs check headered csv support. --- rocketpy/mathutils/function.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rocketpy/mathutils/function.py b/rocketpy/mathutils/function.py index f07960f6c..e2dae7243 100644 --- a/rocketpy/mathutils/function.py +++ b/rocketpy/mathutils/function.py @@ -2830,7 +2830,13 @@ def _check_user_input( # Deal with csv or txt if isinstance(source, (str, Path)): # Convert to numpy array - source = np.loadtxt(source, delimiter=",", dtype=float) + try: + source = np.loadtxt(source, delimiter=",", dtype=float) + except ValueError: + # Skip header + source = np.loadtxt(source, delimiter=",", dtype=float, skiprows=1) + except Exception: + raise ValueError("The source file is not a valid csv or txt file.") else: # this will also trigger an error if the source is not a list of From af6180759bcd110b39d75ec759c07bb34566d3ae Mon Sep 17 00:00:00 2001 From: Pedro Bressan Date: Sat, 25 Nov 2023 10:44:52 -0300 Subject: [PATCH 02/10] DOC: adequate example liquid motor fluid level. --- docs/user/motors/liquidmotor.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/user/motors/liquidmotor.rst b/docs/user/motors/liquidmotor.rst index 5a1b4e8d5..401ab62c6 100644 --- a/docs/user/motors/liquidmotor.rst +++ b/docs/user/motors/liquidmotor.rst @@ -40,7 +40,7 @@ Then we must first define the tanks: fuel_gas = Fluid(name="ethanol_g", density=1.59) # Define tanks geometry - tanks_shape = CylindricalTank(radius = 0.1, height = 1, spherical_caps = True) + tanks_shape = CylindricalTank(radius = 0.1, height = 1.2, spherical_caps = True) # Define tanks oxidizer_tank = MassFlowRateBasedTank( @@ -48,7 +48,7 @@ Then we must first define the tanks: geometry=tanks_shape, flux_time=5, initial_liquid_mass=32, - initial_gas_mass=0.1, + initial_gas_mass=0.01, liquid_mass_flow_rate_in=0, liquid_mass_flow_rate_out=lambda t: 32 / 3 * exp(-0.25 * t), gas_mass_flow_rate_in=0, From 1896a67245b05d7a6bda8f1961fa871254602e08 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Sat, 25 Nov 2023 16:01:30 -0300 Subject: [PATCH 03/10] ENH: improves csv data source handling - single line headers are now officially supported... - with or without quotes. - The docstring was updated. --- rocketpy/mathutils/function.py | 83 +++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 36 deletions(-) diff --git a/rocketpy/mathutils/function.py b/rocketpy/mathutils/function.py index e2dae7243..f0ed22191 100644 --- a/rocketpy/mathutils/function.py +++ b/rocketpy/mathutils/function.py @@ -39,14 +39,9 @@ def __init__( Parameters ---------- - source : function, scalar, ndarray, string - The actual function. If type is function, it will be called for - evaluation. If type is int or float, it will be treated as a - constant function. If ndarray, its points will be used for - interpolation. An ndarray should be as [(x0, y0, z0), (x1, y1, z1), - (x2, y2, z2), ...] where x0 and y0 are inputs and z0 is output. If - string, imports file named by the string and treats it as csv. - The file is converted into ndarray and should not have headers. + source : function, scalar, ndarray, string, Function + The data source to be used for the function. See the ``set_source`` + method's documentation for more details. inputs : string, sequence of strings, optional The name of the inputs of the function. Will be used for representation and graphing (axis names). 'Scalar' is default. @@ -133,25 +128,39 @@ def set_outputs(self, outputs): return self def set_source(self, source): - """Set the source which defines the output of the function giving a - certain input. + """Sets the data source for the function, defining how the function + produces output from a given input. Parameters ---------- - source : function, scalar, ndarray, string, Function - The actual function. If type is function, it will be called for - evaluation. If type is int or float, it will be treated as a - constant function. If ndarray, its points will be used for - interpolation. An ndarray should be as [(x0, y0, z0), (x1, y1, z1), - (x2, y2, z2), ...] where x0 and y0 are inputs and z0 is output. If - string, imports file named by the string and treats it as csv. - The file is converted into ndarray and should not have headers. - If the source is a Function, its source will be copied and another - Function will be created following the new inputs and outputs. + source : function, scalar, ndarray, string, or Function + The data source to be used for the function: + + - Python function: Called for evaluation with input values. + + - int or float: Treated as a constant value. + + - ndarray: Used for interpolation. Format as [(x0, y0, z0), + (x1, y1, z1), ..., (xn, yn, zn)], where 'x' and 'y' are inputs, + and 'z' is the output. + + - string: Path to a CSV file. The file is read and converted into an + ndarray. The file can optionally contain a single header line. + + - Function: Copies the source of the provided Function object, + creating a new Function with adjusted inputs and outputs. + + Notes + ----- + (I) CSV files can optionally contain a single header line. If present, + the header is ignored during processing. + (II) Fields in CSV files may be enclosed in double quotes. If fields are + not quoted, double quotes should not appear inside them. Returns ------- self : Function + Returns the Function instance. """ _ = self._check_user_input( source, @@ -165,20 +174,20 @@ def set_source(self, source): source = source.get_source() # Import CSV if source is a string or Path and convert values to ndarray if isinstance(source, (str, Path)): - # Read file and check for headers - with open(source, mode="r") as f: - first_line = f.readline() - # If headers are found... - if first_line[0] in ['"', "'"]: - # Headers available - first_line = first_line.replace('"', " ").replace("'", " ") - first_line = first_line.split(" , ") - self.set_inputs(first_line[0]) - self.set_outputs(first_line[1:]) - source = np.loadtxt(source, delimiter=",", skiprows=1, dtype=float) - # if headers are not found - else: - source = np.loadtxt(source, delimiter=",", dtype=float) + with open(source, "r") as file: + first_line = file.readline() + try: + # Try to convert the first line to floats + np.array([float(item) for item in first_line.split(",")]) + # If successful, no headers are present + file.seek(0) # Reset file read position + source = np.loadtxt(file, delimiter=",", dtype=float) + except ValueError: + # If an error occurs, headers are present + source = np.genfromtxt( + source, delimiter=",", dtype=float, skip_header=1 + ) + # Convert to ndarray if source is a list if isinstance(source, (list, tuple)): source = np.array(source, dtype=np.float64) @@ -2835,8 +2844,10 @@ def _check_user_input( except ValueError: # Skip header source = np.loadtxt(source, delimiter=",", dtype=float, skiprows=1) - except Exception: - raise ValueError("The source file is not a valid csv or txt file.") + except Exception as e: + raise ValueError( + "The source file is not a valid csv or txt file." + ) from e else: # this will also trigger an error if the source is not a list of From 9dbe49eeea5809793521b9267b7a3c4c01a3457d Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Sat, 25 Nov 2023 16:02:16 -0300 Subject: [PATCH 04/10] TST: tests for Function from CSV file with header --- tests/fixtures/function/1d_no_quotes.csv | 4 ++++ tests/fixtures/function/1d_quotes.csv | 4 ++++ tests/test_function.py | 17 +++++++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 tests/fixtures/function/1d_no_quotes.csv create mode 100644 tests/fixtures/function/1d_quotes.csv diff --git a/tests/fixtures/function/1d_no_quotes.csv b/tests/fixtures/function/1d_no_quotes.csv new file mode 100644 index 000000000..239f38654 --- /dev/null +++ b/tests/fixtures/function/1d_no_quotes.csv @@ -0,0 +1,4 @@ +time,value +0,100 +1,200 +2,300 \ No newline at end of file diff --git a/tests/fixtures/function/1d_quotes.csv b/tests/fixtures/function/1d_quotes.csv new file mode 100644 index 000000000..0779c8c74 --- /dev/null +++ b/tests/fixtures/function/1d_quotes.csv @@ -0,0 +1,4 @@ +"time","value" +0,100 +1,200 +2,300 \ No newline at end of file diff --git a/tests/test_function.py b/tests/test_function.py index 1a5b4f189..c67a21b30 100644 --- a/tests/test_function.py +++ b/tests/test_function.py @@ -37,6 +37,23 @@ def test_function_from_csv(func_from_csv, func_2d_from_csv): ) +@pytest.mark.parametrize( + "csv_file", + [ + "tests/fixtures/function/1d_quotes.csv", + "tests/fixtures/function/1d_no_quotes.csv", + ], +) +def test_func_from_csv_with_header(csv_file): + """Tests if a Function can be created from a CSV file with a single header + line. It tests cases where the fields are separated by quotes and without + quotes.""" + f = Function(csv_file) + assert f.__repr__() == "'Function from R1 to R1 : (Scalar) → (Scalar)'" + assert np.isclose(f(0), 100) + assert np.isclose(f(0) + f(1), 300), "Error summing the values of the function" + + def test_getters(func_from_csv, func_2d_from_csv): """Test the different getters of the Function class. From a20b382d0bf133689903520c4e70d6f7cd69ea95 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Sat, 25 Nov 2023 16:06:56 -0300 Subject: [PATCH 05/10] DOC: updates CHANGELOG --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d0506df6..311189757 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,12 +42,18 @@ straightforward as possible. - +## [v1.1.2] - 2023-11-25 + +You can install this version by running `pip install rocketpy==1.1.2` + +### Fixed + +- BUG: Function breaks if a header is present in the csv file [#485](https://github.com/RocketPy-Team/RocketPy/pull/485) ## [v1.1.1] - 2023-11-23 You can install this version by running `pip install rocketpy==1.1.1` - ### Added - DOC: Added this changelog file [#472](https://github.com/RocketPy-Team/RocketPy/pull/472) From 5cd92062de8c78ef3986f5f99358c09ec2eeadda Mon Sep 17 00:00:00 2001 From: MateusStano <69485049+MateusStano@users.noreply.github.com> Date: Sun, 26 Nov 2023 16:14:36 -0300 Subject: [PATCH 06/10] Update rocketpy/mathutils/function.py Co-authored-by: Giovani Hidalgo Ceotto --- rocketpy/mathutils/function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocketpy/mathutils/function.py b/rocketpy/mathutils/function.py index f0ed22191..159723f2e 100644 --- a/rocketpy/mathutils/function.py +++ b/rocketpy/mathutils/function.py @@ -138,7 +138,7 @@ def set_source(self, source): - Python function: Called for evaluation with input values. - - int or float: Treated as a constant value. + - int or float: Treated as a constant value function. - ndarray: Used for interpolation. Format as [(x0, y0, z0), (x1, y1, z1), ..., (xn, yn, zn)], where 'x' and 'y' are inputs, From 8f07a0ac470f923116ff3e3bcb5efff9e3ec55e4 Mon Sep 17 00:00:00 2001 From: MateusStano <69485049+MateusStano@users.noreply.github.com> Date: Sun, 26 Nov 2023 16:15:41 -0300 Subject: [PATCH 07/10] Update rocketpy/mathutils/function.py Co-authored-by: Giovani Hidalgo Ceotto --- rocketpy/mathutils/function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocketpy/mathutils/function.py b/rocketpy/mathutils/function.py index 159723f2e..9e030e0b5 100644 --- a/rocketpy/mathutils/function.py +++ b/rocketpy/mathutils/function.py @@ -136,7 +136,7 @@ def set_source(self, source): source : function, scalar, ndarray, string, or Function The data source to be used for the function: - - Python function: Called for evaluation with input values. + - Callable: Called for evaluation with input values. Must have the desired inputs as arguments and return a single output value. Input order is important. Example: Python functions, classes, and methods. - int or float: Treated as a constant value function. From 4c7a427c3c6c677c95a67585288c179a85c7ba70 Mon Sep 17 00:00:00 2001 From: MateusStano Date: Sun, 26 Nov 2023 20:33:03 +0100 Subject: [PATCH 08/10] ENH: improve source arg docs and make csv/txt get source consistent --- rocketpy/mathutils/function.py | 50 +++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/rocketpy/mathutils/function.py b/rocketpy/mathutils/function.py index 9e030e0b5..5f9fc770f 100644 --- a/rocketpy/mathutils/function.py +++ b/rocketpy/mathutils/function.py @@ -39,9 +39,33 @@ def __init__( Parameters ---------- - source : function, scalar, ndarray, string, Function - The data source to be used for the function. See the ``set_source`` - method's documentation for more details. + source : callable, scalar, ndarray, string, or Function + The data source to be used for the function: + + - Callable: Called for evaluation with input values. Must have the + desired inputs as arguments and return a single output value. + Input order is important. Example: Python functions, classes, and + methods. + + - int or float: Treated as a constant value function. + + - ndarray: Used for interpolation. Format as [(x0, y0, z0), + (x1, y1, z1), ..., (xn, yn, zn)], where 'x' and 'y' are inputs, + and 'z' is the output. + + - string: Path to a CSV file. The file is read and converted into an + ndarray. The file can optionally contain a single header line. + + - Function: Copies the source of the provided Function object, + creating a new Function with adjusted inputs and outputs. + + Notes + ----- + (I) CSV files can optionally contain a single header line. If present, + the header is ignored during processing. + (II) Fields in CSV files may be enclosed in double quotes. If fields are + not quoted, double quotes should not appear inside them. + inputs : string, sequence of strings, optional The name of the inputs of the function. Will be used for representation and graphing (axis names). 'Scalar' is default. @@ -133,10 +157,13 @@ def set_source(self, source): Parameters ---------- - source : function, scalar, ndarray, string, or Function + source : callable, scalar, ndarray, string, or Function The data source to be used for the function: - - Callable: Called for evaluation with input values. Must have the desired inputs as arguments and return a single output value. Input order is important. Example: Python functions, classes, and methods. + - Callable: Called for evaluation with input values. Must have the + desired inputs as arguments and return a single output value. + Input order is important. Example: Python functions, classes, and + methods. - int or float: Treated as a constant value function. @@ -175,18 +202,15 @@ def set_source(self, source): # Import CSV if source is a string or Path and convert values to ndarray if isinstance(source, (str, Path)): with open(source, "r") as file: - first_line = file.readline() try: - # Try to convert the first line to floats - np.array([float(item) for item in first_line.split(",")]) - # If successful, no headers are present - file.seek(0) # Reset file read position source = np.loadtxt(file, delimiter=",", dtype=float) except ValueError: # If an error occurs, headers are present - source = np.genfromtxt( - source, delimiter=",", dtype=float, skip_header=1 - ) + source = np.loadtxt(source, delimiter=",", dtype=float, skiprows=1) + except Exception as e: + raise ValueError( + "The source file is not a valid csv or txt file." + ) from e # Convert to ndarray if source is a list if isinstance(source, (list, tuple)): From 66f6d5226a899f8578fb3f12986052f45d932dc8 Mon Sep 17 00:00:00 2001 From: MateusStano Date: Sun, 26 Nov 2023 20:48:18 +0100 Subject: [PATCH 09/10] REL: bump to v1.1.2 --- docs/conf.py | 2 +- docs/user/installation.rst | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 9331a2f41..9a056cbcc 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -24,7 +24,7 @@ author = "RocketPy Team" # The full version, including alpha/beta/rc tags -release = "1.1.1" +release = "1.1.2" # -- General configuration --------------------------------------------------- diff --git a/docs/user/installation.rst b/docs/user/installation.rst index 8454156ca..1945af799 100644 --- a/docs/user/installation.rst +++ b/docs/user/installation.rst @@ -19,7 +19,7 @@ If you want to choose a specific version to guarantee compatibility, you may ins .. code-block:: shell - pip install rocketpy==1.1.1 + pip install rocketpy==1.1.2 Optional Installation Method: ``conda`` diff --git a/setup.py b/setup.py index 85e184a02..4632bd009 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ setuptools.setup( name="rocketpy", - version="1.1.1", + version="1.1.2", install_requires=necessary_require, extras_require={ "env_analysis": env_analysis_require, From cd9d10cd4c485582e23ed3629e915e3771a51dd6 Mon Sep 17 00:00:00 2001 From: Pedro Bressan Date: Mon, 27 Nov 2023 14:33:21 -0300 Subject: [PATCH 10/10] DOC: correct Function docstring section placement. --- rocketpy/mathutils/function.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/rocketpy/mathutils/function.py b/rocketpy/mathutils/function.py index 5f9fc770f..e52fbb2fb 100644 --- a/rocketpy/mathutils/function.py +++ b/rocketpy/mathutils/function.py @@ -59,13 +59,6 @@ def __init__( - Function: Copies the source of the provided Function object, creating a new Function with adjusted inputs and outputs. - Notes - ----- - (I) CSV files can optionally contain a single header line. If present, - the header is ignored during processing. - (II) Fields in CSV files may be enclosed in double quotes. If fields are - not quoted, double quotes should not appear inside them. - inputs : string, sequence of strings, optional The name of the inputs of the function. Will be used for representation and graphing (axis names). 'Scalar' is default. @@ -93,6 +86,13 @@ def __init__( Returns ------- None + + Notes + ----- + (I) CSV files can optionally contain a single header line. If present, + the header is ignored during processing. + (II) Fields in CSV files may be enclosed in double quotes. If fields are + not quoted, double quotes should not appear inside them. """ # Set input and output if inputs is None: