Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Callback function #221

Merged
merged 4 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions .github/workflows/CI_Tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,11 @@ jobs:
python-version: '3.9'
cache: 'pip'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt

- name: Install this package
run: pip install -e .
run: pip install -e '.[dev]'

- name: Run pytest
run: cd tests && pytest
run: pytest

- name: Run the smoke tests
run: |
Expand Down
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,17 @@ The package is also available on PyPi and can be installed in any Python environ
pip install acom_music_box
```

# Tests
# Developing

After installing music box for local development `pip install -e .`
Install music box as an editable installation.

```
pip install -e '.[dev]'
```

After installing music box for local development

```
cd tests
pytest
```

Expand Down
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,9 @@ Home = "https://github.com/NCAR/music-box"
[project.scripts]
music_box = "acom_music_box.main:main"
waccmToMusicBox = "acom_music_box.tools.waccmToMusicBox:main"

[project.optional-dependencies]
dev = [
"pytest",
"pytest-mock"
]
5 changes: 0 additions & 5 deletions requirements.txt

This file was deleted.

12 changes: 8 additions & 4 deletions src/acom_music_box/music_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ def create_solver(
solver_type,
number_of_grid_cells)

def solve(self, output_path=None):
def solve(self, output_path=None, callback=None):
"""
Solves the box model simulation and optionally writes the output to a file.

Expand All @@ -439,8 +439,8 @@ def solve(self, output_path=None):
the specified file.

Args:
path_to_output (str, optional): The path to the file where the output will
be written. If None, no output file is created. Defaults to None.
output_path (str, optional): The path to the file where the output will be written. If None, no output file is created. Defaults to None.
callback (function, optional): A callback function that is called after each time step. Defaults to None. The callback will take the most recent results, the current time, conditions, and the total simulation time as arguments.

Returns:
list: A 2D list where each inner list represents the results of the simulation
Expand Down Expand Up @@ -539,6 +539,11 @@ def solve(self, output_path=None):
output_array.append(row)
next_output_time += self.box_model_options.output_step_time

# calls callback function if present
if callback is not None:
df = pd.DataFrame(output_array[:-1], columns=output_array[0])
callback(df, curr_time, curr_conditions, self.box_model_options.simulation_length)

# ensure the time step is not greater than the next update to the
# evolving conditions or the next output time
time_step = self.box_model_options.chem_step_time
Expand All @@ -561,7 +566,6 @@ def solve(self, output_path=None):

# increments time
curr_time += time_step

df = pd.DataFrame(output_array[1:], columns=output_array[0])
# outputs to file if output is present
if output_path is not None:
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ def test_run(self):
df = box_model.solve()
model_output = [df.columns.values.tolist()] + df.values.tolist()

current_dir = os.path.dirname(__file__)
expected_results_path = os.path.join(current_dir, "expected_results/full_gas_phase_mechanism.csv")

# read wall_loss_test.csv into test_output
with open("expected_results/full_gas_phase_mechanism.csv", "r") as file:
with open(expected_results_path, "r") as file:
reader = csv.reader(file)
test_output = list(reader)

Expand Down
5 changes: 4 additions & 1 deletion tests/test_chapman.py → tests/integration/test_chapman.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ def test_run(self):
df = box_model.solve()
model_output = [df.columns.values.tolist()] + df.values.tolist()

current_dir = os.path.dirname(__file__)
expected_results_path = os.path.join(current_dir, "expected_results/chapman_test.csv")

# read chapman_test.csv into test_output
with open("expected_results/chapman_test.csv", "r") as file:
with open(expected_results_path, "r") as file:
reader = csv.reader(file)
test_output = list(reader)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ def test_run(self):
df = box_model.solve()
model_output = [df.columns.values.tolist()] + df.values.tolist()

current_dir = os.path.dirname(__file__)
expected_results_path = os.path.join(current_dir, "expected_results/wall_loss_test.csv")

# read wall_loss_test.csv into test_output
with open("expected_results/wall_loss_test.csv", "r") as file:
with open(expected_results_path, "r") as file:
reader = csv.reader(file)
test_output = list(reader)

Expand Down
33 changes: 33 additions & 0 deletions tests/unit/test_callback.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from acom_music_box import MusicBox, Examples
import os

def callback(df, current_time, current_conditions, total_simulation_time):
print(f"Current time: {current_time} s, total simulation time: {total_simulation_time} s, percentage complete: {current_time / total_simulation_time * 100:.2f}%")


class TestCallbackFunction:
def test_run(self, mocker):
box_model = MusicBox()

conditions_path = Examples.Analytical.path
box_model.readConditionsFromJson(conditions_path)

camp_path = os.path.join(
os.path.dirname(conditions_path),
box_model.config_file)

box_model.create_solver(camp_path)

# Mock the callback function
callback_mock = mocker.Mock(side_effect=callback)

# Run the solver with the mocked callback
box_model.solve(callback=callback_mock)

# Assert that the callback was called at least once
callback_mock.assert_called()


if __name__ == "__main__":
test = TestCallbackFunction()
test.test_run()