Skip to content

Conversation

@GMinoruy
Copy link

@GMinoruy GMinoruy commented Dec 7, 2025

Pull request type

  • Code changes (bugfix, features)

Checklist

  • Implement export functionality for .csv.

  • Implement export functionality for .json.

  • Ensure that export_list and custom data_collector variables are included in the export.

  • Add unit tests in tests/ ensuring the files are created and contain correct data.

  • Update the documentation to include examples of how to export data.

Current behavior

Currently the MonteCarlo class saves the simulation results into .outputs.txt files. There is no method that allowa users to export these results into .csv or .json files. See issue #242.

New behavior

Implements the export_results method in the MonteCarlo class. This method reads the data from .outputs.txt files and rewrites them to .csv and .json format, based on the user`s selection

Breaking change

  • Yes
  • No

@GMinoruy GMinoruy requested a review from a team as a code owner December 7, 2025 22:25
@Gui-FernandesBR Gui-FernandesBR force-pushed the enh/issue-242-monte-carlo-outputs branch 2 times, most recently from e3473df to f38ac20 Compare December 9, 2025 00:13
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds CSV and JSON export functionality to the MonteCarlo class, addressing issue #242. The enhancement allows users to convert Monte Carlo simulation results from the default .outputs.txt format to more widely-used .csv and .json formats for easier data analysis and integration with external tools.

Key Changes

  • Added export_results() method to MonteCarlo class that reads .outputs.txt files and exports to CSV or JSON format based on user selection
  • Included unit test to verify file creation for both CSV and JSON exports
  • Updated documentation with usage examples in the Monte Carlo analysis notebook

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 13 comments.

File Description
rocketpy/simulation/monte_carlo.py Implements the export_results() method with CSV/JSON conversion logic using csv.DictWriter and json.dump
tests/unit/simulation/test_monte_carlo.py Adds unit test that verifies CSV and JSON files are created, includes mock data setup and file cleanup
docs/notebooks/monte_carlo_analysis/monte_carlo_class_usage.ipynb Adds documentation section demonstrating how to use the new export functionality with example code

mc.estimate_confidence_interval("apogee", statistic="not_a_function")


def test_monte_carlo_export_results_to_csv_and_json_files(monte_carlo_calisto, tmp_path):
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test function name could be more descriptive and follow the pattern test_methodname_expectedbehaviour. Consider renaming to test_export_results_creates_csv_and_json_files to better indicate what method is being tested and what behavior is expected.

Suggested change
def test_monte_carlo_export_results_to_csv_and_json_files(monte_carlo_calisto, tmp_path):
def test_export_results_creates_csv_and_json_files(monte_carlo_calisto, tmp_path):

Copilot uses AI. Check for mistakes.
assert expected_file_in_csv.exists()

mc.export_results(tmp_path / "mock_output_in_json", "json")
expected_file_in_json = tmp_path / f"{"mock_output_in_json"}.json"
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary f-string usage. The expression f"{"mock_output_in_json"}" is redundant and can be simplified to just "mock_output_in_json".

Suggested change
expected_file_in_json = tmp_path / f"{"mock_output_in_json"}.json"
expected_file_in_json = tmp_path / "mock_output_in_json.json"

Copilot uses AI. Check for mistakes.
Comment on lines +207 to +213
mc.export_results(tmp_path / "mock_outputs_in_csv", "csv")
expected_file_in_csv = tmp_path / f"{"mock_outputs_in_csv"}.csv"
assert expected_file_in_csv.exists()

mc.export_results(tmp_path / "mock_output_in_json", "json")
expected_file_in_json = tmp_path / f"{"mock_output_in_json"}.json"
assert expected_file_in_json.exists()
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test only verifies that files are created but doesn't validate their contents. Consider adding assertions to:

  1. Verify the CSV/JSON files contain the expected mock data (apogee: 100, max_velocity: 255)
  2. Check CSV header format
  3. Verify JSON structure and indentation

Example:

# For CSV
import csv
with open(expected_file_in_csv, "r") as f:
    reader = csv.DictReader(f)
    rows = list(reader)
    assert len(rows) == 1
    assert rows[0]["apogee"] == "100"
    assert rows[0]["max_velocity"] == "255"

# For JSON  
with open(expected_file_in_json, "r") as f:
    data = json.load(f)
    assert len(data) == 1
    assert data[0]["apogee"] == 100
    assert data[0]["max_velocity"] == 255

Copilot uses AI. Check for mistakes.
Comment on lines +192 to +217
def test_monte_carlo_export_results_to_csv_and_json_files(monte_carlo_calisto, tmp_path):
"""Checks that the export_results create .csv and .json files
Parameters
----------
monte_carlo_calisto : MonteCarlo
Fixture that has the .txt files necessary for the export_results
"""
try:
mc = monte_carlo_calisto
mc.filename = tmp_path / "mock_output"
mock_data = {"apogee": 100, "max_velocity": 255}
with open(tmp_path / "mock_output.outputs.txt", "w") as f:
f.write(json.dumps(mock_data) + "\n")

mc.export_results(tmp_path / "mock_outputs_in_csv", "csv")
expected_file_in_csv = tmp_path / f"{"mock_outputs_in_csv"}.csv"
assert expected_file_in_csv.exists()

mc.export_results(tmp_path / "mock_output_in_json", "json")
expected_file_in_json = tmp_path / f"{"mock_output_in_json"}.json"
assert expected_file_in_json.exists()
finally:
os.remove("monte_carlo_test.errors.txt")
os.remove("monte_carlo_test.inputs.txt")
os.remove("monte_carlo_test.outputs.txt")
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test doesn't cover important edge cases and error conditions:

  1. Invalid output_format parameter (e.g., "xml", "pdf")
  2. Non-existent .outputs.txt file
  3. Empty .outputs.txt file
  4. Malformed JSON in .outputs.txt file
  5. Case insensitivity of output_format (e.g., "CSV", "Json")

Consider adding separate test cases for these scenarios to ensure the method handles errors gracefully.

Copilot uses AI. Check for mistakes.
Comment on lines +215 to +217
os.remove("monte_carlo_test.errors.txt")
os.remove("monte_carlo_test.inputs.txt")
os.remove("monte_carlo_test.outputs.txt")
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File cleanup in the finally block will fail if the files don't exist. This can happen if the fixture fails to create these files or if the test is run in isolation. Use os.path.exists() checks or use a helper function similar to _post_test_file_cleanup() found in integration tests (see tests/integration/simulation/test_monte_carlo.py:12-25).

Suggested fix:

finally:
    for filepath in ["monte_carlo_test.errors.txt", "monte_carlo_test.inputs.txt", "monte_carlo_test.outputs.txt"]:
        if os.path.exists(filepath):
            os.remove(filepath)
Suggested change
os.remove("monte_carlo_test.errors.txt")
os.remove("monte_carlo_test.inputs.txt")
os.remove("monte_carlo_test.outputs.txt")
for filepath in [
"monte_carlo_test.errors.txt",
"monte_carlo_test.inputs.txt",
"monte_carlo_test.outputs.txt",
]:
if os.path.exists(filepath):
os.remove(filepath)

Copilot uses AI. Check for mistakes.
Comment on lines +608 to +612
with open(f"{self.filename}.outputs.txt", "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
data = json.loads(line)
txt_data.append(data)
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handling for file I/O operations. The method should handle FileNotFoundError when the .outputs.txt file doesn't exist and provide a clear error message to the user. Additionally, consider handling json.JSONDecodeError if the file contains malformed JSON data.

Suggested change
with open(f"{self.filename}.outputs.txt", "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
data = json.loads(line)
txt_data.append(data)
try:
with open(f"{self.filename}.outputs.txt", "r", encoding="utf-8") as f:
for line_number, line in enumerate(f, start=1):
line = line.strip()
try:
data = json.loads(line)
txt_data.append(data)
except json.JSONDecodeError as e:
_SimMonitor.reprint(
f"Error decoding JSON on line {line_number} of '{self.filename}.outputs.txt': {e}\n"
f"Line content: {line}\n"
"Please check the file for malformed JSON data."
)
raise
except FileNotFoundError:
_SimMonitor.reprint(
f"File '{self.filename}.outputs.txt' not found. "
"Please ensure that the Monte Carlo simulation has been run and the output file exists."
)
raise

Copilot uses AI. Check for mistakes.
json.dump(txt_data, f, indent=4)
_SimMonitor.reprint(f"Results saved to {Path(output_filename)} as .json file")
elif output_format == "csv":
output_csv_header = txt_data[0].keys()
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential IndexError if txt_data is empty. If the .outputs.txt file is empty or contains only blank lines, txt_data[0] will raise an IndexError. Add validation to check that txt_data is not empty before attempting to access its first element.

Copilot uses AI. Check for mistakes.
_SimMonitor.reprint(f"Results saved to {Path(output_filename)} as .json file")
elif output_format == "csv":
output_csv_header = txt_data[0].keys()
with open(f"{output_filename}.csv", "w", newline= "") as f:
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra space before empty string in newline= "" parameter. Should be newline="" (no space before the equals sign) to follow Python style conventions (PEP 8).

Suggested change
with open(f"{output_filename}.csv", "w", newline= "") as f:
with open(f"{output_filename}.csv", "w", newline="") as f:

Copilot uses AI. Check for mistakes.
Comment on lines +593 to +605
"""Converts the default Monte Carlo .txt output to .cvs or .json file
depending on the user's choice
Parameters
----------
output_filename : str
Name of the file in which the converted data will be saved
output_format : str
Format of the output file
Returns
-------
None
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring is missing important details. It should:

  1. Specify that output_filename can be a string or Path object
  2. Document the valid values for output_format ("csv" or "json")
  3. Explain what happens if the output file already exists (will it be overwritten?)
  4. Mention that the method reads from self.filename.outputs.txt
  5. Follow NumPy docstring format with a proper description section before Parameters

Example improvement:

\"\"\"Export Monte Carlo simulation results to CSV or JSON format.

This method reads simulation output data from the `.outputs.txt` file
and converts it to either CSV or JSON format based on user selection.

Parameters
----------
output_filename : str or Path
    Path and name of the output file (without extension). The appropriate
    extension (.csv or .json) will be added automatically.
output_format : str
    Format of the output file. Must be either "csv" or "json" (case-insensitive).

Returns
-------
None

Raises
------
FileNotFoundError
    If the `.outputs.txt` file does not exist.
ValueError
    If `output_format` is not "csv" or "json".
\"\"\"
Suggested change
"""Converts the default Monte Carlo .txt output to .cvs or .json file
depending on the user's choice
Parameters
----------
output_filename : str
Name of the file in which the converted data will be saved
output_format : str
Format of the output file
Returns
-------
None
"""Export Monte Carlo simulation results to CSV or JSON format.
This method reads simulation output data from the `.outputs.txt` file
(specifically, from `self.filename.outputs.txt`) and converts it to either
CSV or JSON format based on user selection. The output file will be
overwritten if it already exists.
Parameters
----------
output_filename : str or Path
Path and name of the output file (without extension). The appropriate
extension (.csv or .json) will be added automatically.
output_format : str
Format of the output file. Must be either "csv" or "json"
(case-insensitive).
Returns
-------
None
Raises
------
FileNotFoundError
If the `.outputs.txt` file does not exist.
ValueError
If `output_format` is not "csv" or "json".
Examples
--------
>>> mc.export_results("results", "csv")
>>> mc.export_results(Path("results_folder/results"), "json")

Copilot uses AI. Check for mistakes.
Comment on lines +208 to +212
expected_file_in_csv = tmp_path / f"{"mock_outputs_in_csv"}.csv"
assert expected_file_in_csv.exists()

mc.export_results(tmp_path / "mock_output_in_json", "json")
expected_file_in_json = tmp_path / f"{"mock_output_in_json"}.json"
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary f-string usage. The expression f"{"mock_outputs_in_csv"}" is redundant and can be simplified to just "mock_outputs_in_csv". The same applies to line 212.

Suggested change
expected_file_in_csv = tmp_path / f"{"mock_outputs_in_csv"}.csv"
assert expected_file_in_csv.exists()
mc.export_results(tmp_path / "mock_output_in_json", "json")
expected_file_in_json = tmp_path / f"{"mock_output_in_json"}.json"
expected_file_in_csv = tmp_path / "mock_outputs_in_csv.csv"
assert expected_file_in_csv.exists()
mc.export_results(tmp_path / "mock_output_in_json", "json")
expected_file_in_json = tmp_path / "mock_output_in_json.json"

Copilot uses AI. Check for mistakes.
@Gui-FernandesBR Gui-FernandesBR linked an issue Dec 9, 2025 that may be closed by this pull request
5 tasks
@Gui-FernandesBR Gui-FernandesBR force-pushed the enh/issue-242-monte-carlo-outputs branch from f38ac20 to 6503273 Compare December 9, 2025 12:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ENH: Save Monte Carlo outputs to .csv and .json formats

1 participant