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

Use solve_ivp without the py-pde wrapper, but retain fields and grids from py-pde, for more readable code #44

Merged
merged 52 commits into from
Sep 25, 2024

Conversation

HannoSpreeuw
Copy link
Contributor

Given the arguments from this closed discussion, I think we should proceed with the methodology from the Use_solve_ivp_without_py-pde_wrapper_branch and merge it into main to include this into our release.

In summary, the Use solve ivp without py pde wrapper branch offers these features:

  • Faster integrations than main branch.
  • Allows for all features of solve_ivp, such as a Jacobian sparsity matrix
  • Event tracking has been implemented, for a number of fields and derived quantities, i.e. the times of sign changes are recorded.

while the unique features of the main branch are less important to us.

…eureux. Expressions become even more involved, unfortunately.
… is the opposite of what is expected. Also end up with equally unstable solutions, i.e. the well-known oscillations along the depth axis. Apparently the sparsity matrix does result in trying to find a better solution, but ultimately fails. Also fixed a bug in Derive_Jacobian.py which luckily was not present in the actual rhs computations. Implemented first steps in setting up the structure for Jacobian computations, so beyond the sparsity matrix. A lot of Numba obviously.
…ent yet. However, it is possible to integrate over longer times without an overflow error. But also running into memory overflows.
…r all Jacobian indices. I had to bypass a couple of Numba's limitations to get that working. However, it ultimately leads to a memory overflow for long integrations. Removing all njit decorators in Compute_jacobian.py makes it faster! So weird. So something is still wrong at the Numba level there. It is also clear now that, even with a Jacobian, the integrations fail and lead to bogus results such as negative cCO3 concentrations and negative values for Phi, so log Phi leads to a "FloatingPointError: invalid value encountered in log". Big bummer. Perhaps try Radau instead of BDF. Or LSODA?
…h slower than the noncompiled version. I still do not understand why and I tried some things to figure this out, e.g. by not using the jac00...jac44 functions, but that did not make a difference. The Jacobian_adjusted_to_reuse_common_factors_and_powers_cleared.txt file should have all the correct Jacobian terms since it comes directly from Derive_Jacobian.py after which I substituted some terms in an editor.
… a vectorize decorator to the overloaded np.heaviside function with the help of numba/numba@2ae154f
…ange has been replaced by range. This is done to investigate if there is an effect on memory use and run time. Less memory use is expected single threaded and perhaps the run times will not increase since integrations seem to scale very poorly with the number of available cores.
…ning that off may lead to more accurate results. Also, 'nogil = True' is no longer needed for single core computations.
… in combination with calling functional Jacobians. This was attributed to repeated Numba compilations, possibly from Numba problems with nested functions. That is why the compute_all_Jacobian_elements nested function has been removed (as a separate function), but slowness persisted.
…obians, since that was a long time ago, but as far as I remember none of these runs were successful. This is confirmed by more recent results when we provided a Jacobian sparsity matrix, see commit 2197188: the diagonals should be banded, i.e. have a width of more than 1 element, in order for the Jacobian sparsity matrix to enhance integration. The same should apply for analytical Jacobians. However, the off-diagional elements of an analytical Jacobian matrix will be very hard to compute. This means that at this point we will stop our efforts on deriving analytical Jacobian matrices and only provide Jacobian sparsity matrices. For some background on why Jacobian matrices are banded, please see the literature, e.g. equation 2.58 (page 28) from Finite Difference Methods Finite Difference Method for Differential Equationsby Randall J. Leveque (https://edisciplinas.usp.br/pluginfile.php/41896/mod_resource/content/1/LeVeque%20Finite%20Diff.pdf). This example is about the Jacobian for solving the pde describing the motion of a pendulum with a certain mass at the end of a rigid (but massless) bar.
…in'. This means that parameters will be in a separate file and not in ScenarioA.py. Also, we want to include the marlpde folder.
…als, i.e. the diagonals will have the width of only one depth node. We now know that due to discretisation, there should also be non-zero elements adjacent to the diagonals, but these are very hard to compute.
…be derived after 'eq' has been defined. 'number_of_depths' --> 'Number_of_depths'.
…d in this module. However, it is now calculated in the parameters module, so we no longer need these imports here.
…__' it will complain that it does not know 'no_t_eval'.
…ons, times and metadata using py-pde's FileStorage class seems cumbersome when no tracking is applied. Therefore I reverted to the regular way of saving Numpy arrays in an hdf5 file. All the metadata, i.e. 'stored_parms', which is a single dict, can be stored as well, except for the Jacobian sparsity matrix, since that will give rise to 'TypeError: Object dtype dtype('O') has no native HDF5 equivalent'. This csr_matrix has to be converted to a ndarray first, I reckon.
…che=False may be noticeably slower for short runs, but sometimes causes a run to halt from start, when a compiled object is missing.
… slightly different data format that solve_ivp returns, i.e. solution.y contains the solutions for the five fields and the [:, -1] indexing gives the last ones across all depths. integrate_equations now gives six return values instead of five. solve_ivp uses 'first_step' instead of 'dt'.
… Numba-based evaluations of the right-hand sides. The Solver dataclass now provides for that. A conditional has been added to check if the 'jac_sparsity' attribute exists. It will not exist for explicit solvers.
…ield, this is more convenient than a single dimension covering all depths for all fields. Also store in this way, as an hdf5. Have 'integrate_equations' only return the final solution, since we only use that for plotting.
…of 6. And only the final solutions, which makes comparison with the ground truth somewhat simpler in terms of indexing.
@HannoSpreeuw HannoSpreeuw self-assigned this Sep 3, 2024
@HannoSpreeuw HannoSpreeuw changed the title Use solve ivp without py pde wrapper Use solve_ivp without the py-pde wrapper, but retain fields and grids from py-pde, for more readable code Sep 4, 2024
HannoSpreeuw and others added 3 commits September 4, 2024 17:50
… formatting of floats is often more readable.
…store them in the hdf5 file, one has to iterate over this list and create a separate dataset for each list item, i.e. for each ndarray.
@HannoSpreeuw
Copy link
Contributor Author

I am currently looking at issue #43 , which is about documentation.

Last bullet: saving U at the bottom, this feature will vanish in the merge.

In main it is currently implemented through

    if tracker_parms["track_U_at_bottom"]:
        data_tracker = DataTracker(eq.track_U_at_bottom, \
                               interval = tracker_parms["data_tracker_interval"])

and

    sol, info = eq.solve(state, **solver_parms, tracker=["progress", \
                         storage.tracker(\
                             tracker_parms["progress_tracker_interval"]),\
                         live_plots, data_tracker])

without the py-pde wrapper of solve_ivp we can no longer apply the DataTracker class and tracking of U at the bottom will have to be implemented differently.

@EmiliaJarochowska Pls let me know if tracking (and saving) U at the bottom has to be readded.

@HannoSpreeuw
Copy link
Contributor Author

@EmiliaJarochowska Pls let me know if tracking (and saving) U at the bottom has to be readded.

After discussion: will not be readded anytime soon, instead a similar feature in rhythmite will be deployed.

@HannoSpreeuw HannoSpreeuw mentioned this pull request Sep 25, 2024
3 tasks
…en merged into 'main'.

Grammar correction.
A constant porosity diffusion coefficient is now in all branches.
Functional Jacobians turn out not to applicable for this project, because of the discretization.
The use of py-pde is now limited to its ScalarField and CartesianGrid.
@EmiliaJarochowska
Copy link
Contributor

FYI I managed to crash it:

/opt/homebrew/lib/python3.11/site-packages/scipy/integrate/_ivp/radau.py:401: RuntimeWarning: underflow encountered in nextafter
  min_step = 10 * np.abs(np.nextafter(t, self.direction * np.inf) - t)
 26%|█████████                          | 25866/100000 [03:56<07:03, 174.89it/s]/opt/homebrew/lib/python3.11/site-packages/scipy/integrate/_ivp/radau.py:118: RuntimeWarning: underflow encountered in divide
  dW_norm = norm(dW / scale)

by setting Phi0 to 0.9, Phi00 to 0.8, and b to 2.66667.
However, this also crashed the main so we knew it was prone to it.
So I suggest leaving it like this.

Otherwise works and is indeed very fast.

But the number of solver options and their implications for the numerical methods strengthens the argument that some additional documentation is needed. I will continue this discussion in the respective issue.

Copy link
Contributor

@EmiliaJarochowska EmiliaJarochowska left a comment

Choose a reason for hiding this comment

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

Pylint highlights some minor issues here and there but the code works so I'll merge now.

@EmiliaJarochowska EmiliaJarochowska merged commit 0c65692 into main Sep 25, 2024
1 check passed
@EmiliaJarochowska
Copy link
Contributor

I interrupted that run but here are the errors:

  File "/Users/emilia/Documents/Documents - UU101746/GitHub/Integrating-diagenetic-equations-using-Python/marlpde/Evolve_scenario.py", line 211, in <module>
    Plot_results(*integrate_equations(solver_parms, tracker_parms, pde_parms))
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/emilia/Documents/Documents - UU101746/GitHub/Integrating-diagenetic-equations-using-Python/marlpde/Evolve_scenario.py", line 104, in integrate_equations
    sol = solve_ivp(eq.fun if backend=="numpy" else eq.fun_numba, 
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/scipy/integrate/_ivp/ivp.py", line 591, in solve_ivp
    message = solver.step()
              ^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/scipy/integrate/_ivp/base.py", line 181, in step
    success, message = self._step_impl()
                       ^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/scipy/integrate/_ivp/radau.py", line 504, in _step_impl
    f_new = self.fun(t_new, y_new)
            ^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/scipy/integrate/_ivp/base.py", line 136, in fun
    def fun(t, y):

@HannoSpreeuw
Copy link
Contributor Author

FYI I managed to crash it:

/opt/homebrew/lib/python3.11/site-packages/scipy/integrate/_ivp/radau.py:401: RuntimeWarning: underflow encountered in nextafter
  min_step = 10 * np.abs(np.nextafter(t, self.direction * np.inf) - t)
 26%|█████████                          | 25866/100000 [03:56<07:03, 174.89it/s]/opt/homebrew/lib/python3.11/site-packages/scipy/integrate/_ivp/radau.py:118: RuntimeWarning: underflow encountered in divide
  dW_norm = norm(dW / scale)

by setting Phi0 to 0.9, Phi00 to 0.8, and b to 2.66667. However, this also crashed the main so we knew it was prone to it. So I suggest leaving it like this.

Otherwise works and is indeed very fast.

But the number of solver options and their implications for the numerical methods strengthens the argument that some additional documentation is needed. I will continue this discussion in the respective issue.

Yes, I think we discussed those cases as part of issue #36 .

@HannoSpreeuw HannoSpreeuw deleted the Use_solve_ivp_without_py-pde_wrapper branch September 26, 2024 07:45
@HannoSpreeuw
Copy link
Contributor Author

Btw, you shared the warning, was there also a crash?

We do not want marlpde to crash on very small time steps, so that is why we have under="warn"
in np.seterr(divide="raise", over="raise", under="warn", invalid="raise").

@EmiliaJarochowska
Copy link
Contributor

I am not sure if it crashed, it got stuck at 26% and didn't move, just displayed the warning. Shall I re-try?

@HannoSpreeuw
Copy link
Contributor Author

Yes, please retry, I'd be surprised if it really halted.

@EmiliaJarochowska
Copy link
Contributor

It didn't crash, just stuck at 26%. Been at 26% for > 1 h now without any progress. But again, this is a crash case.

@HannoSpreeuw
Copy link
Contributor Author

Okay, this is something we do not want to solve, let's leave it.

@EmiliaJarochowska
Copy link
Contributor

Oh, now I discovered that it actually finished. But with the following:

Status = -1 

Success = False 

and

Message from solve_ivp = Required step size is less than spacing between numbers. 

but yes, it's not to be fixed.

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.

2 participants