diff --git a/add_book_to_path.py b/add_book_to_path.py index bb4bd7ba..1b5a2460 100644 --- a/add_book_to_path.py +++ b/add_book_to_path.py @@ -11,9 +11,11 @@ add_book_to_path: ../ """ +import os import sys -sys.path.append('./docs/source') +path_to_here = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(path_to_here, 'docs')) def setup(dummy): diff --git a/docs/driver-example.ipynb b/docs/driver-example.ipynb index efc20960..49bf71f4 100644 --- a/docs/driver-example.ipynb +++ b/docs/driver-example.ipynb @@ -12,7 +12,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 1, "id": "46c9df26-5ac1-4511-9b9f-3b78a10dc4af", "metadata": {}, "outputs": [], @@ -30,7 +30,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 2, "id": "09ec80a2-06d0-42e8-be0f-935c0920cb81", "metadata": {}, "outputs": [], @@ -48,7 +48,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 3, "id": "f68ea35b-93e4-4dea-a67d-919ee79af8e9", "metadata": {}, "outputs": [], @@ -66,7 +66,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 4, "id": "7e0f185b-e03b-4650-9f41-01668903e797", "metadata": {}, "outputs": [ @@ -109,17 +109,17 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 5, "id": "fddbc69a-44fb-4877-8fc4-2f7fbf8d0076", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[]" + "[]" ] }, - "execution_count": 9, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" }, diff --git a/feisty/driver.py b/feisty/driver.py index e53f4613..5db6a2fb 100644 --- a/feisty/driver.py +++ b/feisty/driver.py @@ -8,6 +8,9 @@ from .core import settings as settings_mod from .core.interface import feisty_instance_type +path_to_here = os.path.dirname(os.path.realpath(__file__)) + + _test_domain = dict( tanh_shelf=testcase.domain_tanh_shelf, ) @@ -102,16 +105,13 @@ def __init__( def _forcing_t(self, t): return self.forcing.interp(time=t) - def _init_time_coord(self, nt): + def _init_output_arrays(self, nt): self.time = xr.DataArray( np.arange(1.0, nt + 1.0, 1.0), dims=('time'), name='time', attrs={'long_name': 'time'}, ) - - def _init_output_arrays(self): - zeros = xr.full_like(self.time, fill_value=0.0) ds_diag = zeros * self.obj.tendency_data[self._diagnostic_names] ds_prog = zeros * self.obj.get_prognostic().to_dataset() @@ -127,7 +127,46 @@ def ds(self): """Data comprising the output from a ``feisty`` simulation.""" return self._ds - def run(self, nt, file_out=None): + def _compute_tendency(self, t, state_t): + """Return the feisty time tendency.""" + gcm_data_t = self._forcing_t(t) + return self.obj.compute_tendencies( + state_t.isel(group=self.obj.prog_ndx_fish), + state_t.isel(group=self.obj.prog_ndx_benthic_prey), + gcm_data_t.zooC, + gcm_data_t.zoo_mort, + T_pelagic=gcm_data_t.T_pelagic, + T_bottom=gcm_data_t.T_bottom, + poc_flux=gcm_data_t.poc_flux_bottom, + ) + + def _solve(self, nt, method): + """Call a numerical ODE solver to integrate the feisty model in time.""" + + state_t = self.obj.get_prognostic().copy() + self._init_output_arrays(nt) + print('here') + if method == 'euler': + self._solve_foward_euler(nt, state_t) + + elif method in ['Radau', 'RK45']: + # TODO: make input arguments + self._solve_scipy(nt, state_t, method) + else: + raise ValueError(f'unknown method: {method}') + + def _solve_foward_euler(self, nt, state_t): + """use forward-euler to solve feisty model""" + for n in range(nt): + dfdt = self._compute_tendency(self.time[n], state_t) + state_t[self.obj.prog_ndx_fish, :] = state_t[self.obj.prog_ndx_fish, :] + dfdt * self.dt + self._post_data(n, state_t) + + def _solve_scipy(self, nt, state_t, method): + """use a SciPy solver to integrate the model equation.""" + raise NotImplementedError('scipy solvers not implemented') + + def run(self, nt, file_out=None, method='euler'): """Integrate the FEISTY model. Parameters @@ -135,37 +174,18 @@ def run(self, nt, file_out=None): nt : integer Number of timesteps to run. - """ - # get tracer values - state_t = self.obj.get_prognostic().copy() + file_out : string + File name to write model output data. - # set up time-coordinate - self._init_time_coord(nt) + method : string + Method of solving feisty equations. Options: ['euler', 'Radau', 'RK45']. - # initialize memory for output - self._init_output_arrays() - - # run loop - for n in range(nt): - # interpolate forcing - gcm_data_t = self._forcing_t(self.time[n]) - - # compute tendencies - dfdt = self.obj.compute_tendencies( - state_t.isel(group=self.obj.prog_ndx_fish), - state_t.isel(group=self.obj.prog_ndx_benthic_prey), - gcm_data_t.zooC, - gcm_data_t.zoo_mort, - T_pelagic=gcm_data_t.T_pelagic, - T_bottom=gcm_data_t.T_bottom, - poc_flux=gcm_data_t.poc_flux_bottom, - ) - - # advance FEISTY state - state_t[self.obj.prog_ndx_fish, :] = state_t[self.obj.prog_ndx_fish, :] + dfdt * self.dt - self._post_data(n, state_t) + .. note:: + Only ``method='euler'`` is supported currently. + """ + self._solve(nt, method) self._shutdown(file_out) def _shutdown(self, file_out): @@ -264,6 +284,7 @@ def simulate_testcase( domain_dict = _test_domain[domain_name](**domain_kwargs) forcing = _test_forcing[forcing_name](domain_dict, **forcing_kwargs) + return simulation( domain_dict, forcing, diff --git a/test-reports/junit.xml b/test-reports/junit.xml index f127aa9b..ede0fd99 100644 --- a/test-reports/junit.xml +++ b/test-reports/junit.xml @@ -1 +1 @@ - + diff --git a/tests/test_docs.py b/tests/test_docs.py new file mode 100644 index 00000000..5fbb4ba9 --- /dev/null +++ b/tests/test_docs.py @@ -0,0 +1,10 @@ +import os +import sys + +from . import conftest + + +def test_add_to_path(): + import add_book_to_path + + assert conftest.path_to_here.replace('tests', 'docs') in sys.path diff --git a/tests/test_driver.py b/tests/test_driver.py index 37071102..8b7cb230 100644 --- a/tests/test_driver.py +++ b/tests/test_driver.py @@ -25,6 +25,15 @@ def test_forcing_cyclic(): } +def test_not_implemented(): + """ensure appropriate failures with bad method.""" + testcase = feisty.driver.simulate_testcase('tanh_shelf', 'cyclic') + with pytest.raises(ValueError): + testcase.run(1, method='intuition') + with pytest.raises(NotImplementedError): + testcase.run(1, method='Radau') + + def test_read_settings(): """ensure we can update default settings from a file or dict""" sd_default = feisty.settings.get_defaults() @@ -67,11 +76,9 @@ def test_simulate_testcase_init_1(): def test_simulate_testcase_init_2(): testcase = feisty.driver.simulate_testcase('tanh_shelf', 'cyclic') - testcase._init_time_coord(365) - assert (testcase.time == np.arange(1.0, 366.0, 1.0)).all() - - testcase._init_output_arrays() + testcase._init_output_arrays(365) + assert (testcase.time == np.arange(1.0, 366.0, 1.0)).all() assert isinstance(testcase.ds, xr.Dataset) assert set(testcase.ds.data_vars) == {'biomass'}.union(testcase._diagnostic_names) assert len(testcase.ds.group) == len(testcase.obj.ndx_prognostic)