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

Ptl mbal testing #21

Merged
merged 8 commits into from
Apr 4, 2024
Merged

Ptl mbal testing #21

merged 8 commits into from
Apr 4, 2024

Conversation

peterlafollette
Copy link
Contributor

@peterlafollette peterlafollette commented Mar 25, 2024

[Short description explaining the high-level reason for the pull request]

Additions

After the calibration workstream experienced some errors with LASAM, we decided it was a good idea to run LASAM with a large number of parameter sets and debug all errors that arose. In this branch, I've tested LASAM with 42k randomly generated parameter sets, including humid and arid forcing data, in the framework and in standalone mode, and in one and 3 layer scenarios. Currently the model runs successfully with each parameter set.

Debugging mostly had to do with implementing many small changes to address unlikely scenarios that we did not anticipate earlier in the model's development. While the bulk of the code changes address such errors (brief comments are given describing why the code was added in most cases), this push also has the implementation of an adaptive integral to calculate the capillary drive G, as well as a redefinition of field capacity as a head rather than as a fixed relative water content. Both of these changes were developed as part of LGARTO model development.

Removals

Removals focus on lines of code that yielded errors in some tested cases.

Changes

Testing

Testing was achieved by running the model with 42k unique parameter sets.

Screenshots

Notes

Todos

Checklist

  • PR has an informative and human-readable title
  • Changes are limited to a single goal (no scope creep)
  • Code can be automatically merged (no conflicts)
  • Code follows project standards (link if applicable)
  • Passes all existing automated tests
  • Any change in functionality is tested
  • New functions are documented (with a description, list of inputs, and expected output)
  • Placeholder code is flagged / future todos are captured in comments
  • Visually tested in supported browsers and devices (see checklist below 👇)
  • Project documentation has been updated (including the "Unreleased" section of the CHANGELOG)
  • Reviewers requested with the Reviewers tool ➡️

Testing checklist

Target Environment support

  • Windows
  • Linux
  • Browser

Accessibility

  • Keyboard friendly
  • Screen reader friendly

Other

  • Is useable without CSS
  • Is useable without JS
  • Flexible from small to large screens
  • No linting errors or warnings
  • JavaScript tests are passing

@peterlafollette
Copy link
Contributor Author

I just realized, the unit tests need to be updated because the calculation of capillary drive G is now slightly different ...

@peterlafollette
Copy link
Contributor Author

remaking PR with updated unit test.

@peterlafollette
Copy link
Contributor Author

Remade PR with unit test updated. I have not yet updated the synthetic tests and will likely suggest a new (simpler) way forward for unit testing in a meeting tomorrow. Essentially, I think we should just do unit testing on the single, simple test, and then provide a jupyter notebook that allows for more widespread testing.

Peter La Follette added 3 commits April 2, 2024 10:04
…ance errors and infinite loop errors that became apparent when LASAM was run with many parameter sets. Also updated field capacity formulation to use a head rather than a relative water content, and updated caluclation for capillary drive G to use an adaptive integral, both as in LGARTO.

Rebasing to incorporate recent changes.
…ance errors and infinite loop errors that became apparent when LASAM was run with many parameter sets. Also updated field capacity formulation to use a head rather than a relative water content, and updated caluclation for capillary drive G to use an adaptive integral, both as in LGARTO. Cleaned up some commenting and redundancy after edits were made.
src/lgar.cxx Outdated Show resolved Hide resolved
src/lgar.cxx Outdated Show resolved Hide resolved
src/lgar.cxx Show resolved Hide resolved
src/lgar.cxx Outdated
int iter = 0;
while (fabs(mass_balance_error - tolerance) > 1.E-10) {
iter++;
if (iter>10000000) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

this is a big number, how often does it happen that the code does not converge? Instead of aborting we need to fix this (but not in this PR)....

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In its current state, this code never triggers. I used it while debugging, but kept it in because it seems good to not let the loop get to this many iterations.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe the code should converge in 100s of iterations (<1000 I would guess). Is it (the number 10^6) related to avoiding an infinite loop? If so, we should use a smaller number (say, 1000-5000). If the convergence does not happen in 1000-5000 iterations, we should fix it in a better way. With 10^6, the code might converge in 100,000 iterations -and we will not know about it -- which I don't think is acceptable.

Copy link
Contributor Author

@peterlafollette peterlafollette Apr 4, 2024

Choose a reason for hiding this comment

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

Good point. Rewrote such that the loop terminates after 1e4 iterations (rather than the original 1e7) and puts the remaining mass balance error into AET, rather than aborting, similar to how we do it in the other mass balance loop (in the function lgar_fix_dry_over_wet_wetting_fronts).

src/lgar.cxx Outdated Show resolved Hide resolved
src/lgar.cxx Outdated Show resolved Hide resolved
src/soil_funcs.cxx Outdated Show resolved Hide resolved
src/soil_funcs.cxx Outdated Show resolved Hide resolved
src/soil_funcs.cxx Outdated Show resolved Hide resolved
src/soil_funcs.cxx Outdated Show resolved Hide resolved
src/lgar.cxx Outdated
@@ -1587,11 +1643,15 @@ extern void lgar_wetting_fronts_cross_layer_boundary(int num_layers,
current->dzdt_cm_per_h = 0;
current->to_bottom = TRUE;
next->to_bottom = FALSE;

if (isinf(next->depth_cm)){//in some rare cases, due to (very) different shapes of soil water rentention curves in adjacent soil layers near saturation, the WF that crossed a layer bdy can yield a theta value identical to the one below it, resulting in division by 0 and an infinite WF depth.
Copy link
Collaborator

Choose a reason for hiding this comment

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

the code should never assign infinity to a wetting front depth, instead of checking it here, the code should handle it early on. I think the right place is:

double mbal_Z_correction = mbal_correction / (theta_new - next_to_next->theta);

We should also add an assertion
assert (wetting_front->depth_cm > domain_depth)

Copy link
Contributor Author

@peterlafollette peterlafollette Apr 4, 2024

Choose a reason for hiding this comment

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

I updated the code as you suggested, fixing the error at mbal_Z_correction rather than when we assign the WF depth, but I don't think we should add that assertion. This is because we have a whole function for crossing the domain lower boundary that handles that case.

Copy link
Collaborator

Choose a reason for hiding this comment

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

okay, so I guess this was the only place where WF depth was crossing the domain boundary depth, correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, generally WF depth can cross the domain boundary depth any time WF depth is updated, so this is not the only place. However, the cases of wetting fronts crossing a layer boundary, wetting fronts crossing a domain boundary, and wetting fronts merging with one another are handled specially by an individual function for each of those, and in specific places. The code you commented on here is in the function where WFs cross a layer boundary, so in this function we don't deal with the case where a WF crosses the domain bdy.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I should add, this is probably the only place where WF depth can be calculated as infinite. This is because this is probably the only place where we have a delta theta in the denominator used to update WF depth (aside from in dzdt function, where we also deal with the issue of 0 in the denominator).

src/lgar.cxx Outdated
Comment on lines 2541 to 2545

//so the above code that avoids infinite loops by breaking in certain cases works, but a fringe case must be addressed.
//When theta is close to theta_r, the loop "while (delta_mass > tolerance)" will attempt to increase psi until mass balance closure occurs, but if the
//AET value is large enough to make theta less than theta_r (or if dzdt was large enough to make the WF move such that the updated theta would be less than theta_r),
//the loop would never be able to achieve mass balance closure, and it will break when the one of above conditions is met.
Copy link
Collaborator

Choose a reason for hiding this comment

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

please remove the comments that are no longer relevant such as theta less than theta_r, we don't allow this condition anymore, correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're correct; we don't allow this condition. This comment is relevant however, because it explains that the following code addresses the case where theta would go below theta_r and instead prevents this.

src/lgar.cxx Outdated
@@ -2432,6 +2537,17 @@ extern double lgar_theta_mass_balance(int layer_num, int soil_num, double psi_cm
if (psi_cm_loc <= 0 && psi_cm_loc_prev < 1E-50) break;
Copy link
Collaborator

Choose a reason for hiding this comment

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

1.0E-50 is 0.0

Copy link
Contributor Author

@peterlafollette peterlafollette Apr 4, 2024

Choose a reason for hiding this comment

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

I agree that 1e-50 is basically 0. I'm testing with 0 instead, I think that it should be fine.

src/lgar.cxx Show resolved Hide resolved
Copy link
Collaborator

@ajkhattak ajkhattak left a comment

Choose a reason for hiding this comment

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

I am sure with these changes the model is more stable now. Thanks for working on it.

@ajkhattak ajkhattak merged commit 4c21c46 into master Apr 4, 2024
4 checks passed
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