Skip to content

Make Wofost72 differentiable and vectorized#94

Open
SCiarella wants to merge 14 commits intomainfrom
wofost72
Open

Make Wofost72 differentiable and vectorized#94
SCiarella wants to merge 14 commits intomainfrom
wofost72

Conversation

@SCiarella
Copy link
Collaborator

This PR closes #49.

Noticeably, Wofost72 uses kiosk variables such as EVS that come from the soil model. So in order to make wofost72 consistently differentiable and vectorized, I had to implement a differentiable soil model soil/classic_waterbalance.py which is inspired by the corresponding model from pcse.

This PR also adds a lot of tests, making issue #81 more relevant than ever.

@SCiarella SCiarella requested a review from SarahAlidoost March 3, 2026 11:33
@SCiarella SCiarella marked this pull request as ready for review March 3, 2026 11:33
use torch.where in phenology

add config flag for carbon balance
@SCiarella
Copy link
Collaborator Author

SCiarella commented Mar 3, 2026

In order to reduce the execution time, I have implemented the following modifications:

  1. ComputeConfig now stores a flag _check_carbon_balance that is used by wofost72 to skip some of the expensive checks that slow down the integration [Removed]

  2. assimilation now has a cache to store the constants and share them between different submodules. More importantly, the 3-point quadrature methods have been refactored in order to be vectorized.

  3. phenology uses torch.where instead of if statements to handle mixed states

  4. removed the pcse.decorators that are redundant with Tensor

@SCiarella
Copy link
Collaborator Author

SCiarella commented Mar 4, 2026

In order to make the tests more manageable (the test action takes >2h if unchecked), I have introduced two pytest parser addoptions:

  1. --full_wofost72_test if used, runs the gradient test for all the parameter combinations used by wofost72. By default, we now only test the gradients of the parameters that are directly manipulated in wofost72.
  2. --fast when specified, it: (a) makes pytest use test files 0 to 5 instead of all the 45 available, and (b) skips GPU and tensor-input in all the gradient tests.

@SarahAlidoost, we could think about using --fast by default for the GitHub actions to significantly save time, and only run the tests without --fast once before PR merge. This is probably the only real solution to #81. What do you think?

@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 4, 2026

Copy link
Collaborator

@SarahAlidoost SarahAlidoost left a comment

Choose a reason for hiding this comment

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

@SCiarella Awesome! 👍 The changes look very good, nice to see that all differentiable crop modules are now integrated in wofost72 and gradients are valid 😄 In #88, we will show the optimization of wofost72_pp. Thanks for implementing the soil module too.

I left a few suggestions:

  • I added some inline comment about issue #60. In wofost72.py, we have conditions like if torch.all(self.pheno.states.STAGE == 0). Right now STAGE is either an integer or an array where all values are the same. But actually, with different crops and agro-management, this condition might not work. This should be fixed when engine can handle different crops and agro-management.
  • I suggested choosing different data for fast test.

After addressing these, we can merge this 🚀

self.rates = self.RateVariables(kiosk, shape=shape)
self.kiosk = kiosk

self._connect_signal(self._on_CROP_FINISH, signal=signals.crop_finish)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
# see issue #60
self._connect_signal(self._on_CROP_FINISH, signal=signals.crop_finish)


# Send crop_finish signal if maturity reached for one.
# assumption is that all elements mature simultaneously
# TODO: revisit this when fixing engine for agromanager
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
# TODO: revisit this when fixing engine for agromanager, see issue #60

msg = "Finished state integration for %s"
self.logger.debug(msg % day)

def _on_CROP_FINISH(self, day, finish_type=None):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
# TODO: revisit this when fixing engine for agromanager, see issue #60
def _on_CROP_FINISH(self, day, finish_type=None):

self.params.shape, day.toordinal(), dtype=self.dtype, device=self.device
)

def get_variable(self, varname):
Copy link
Collaborator

Choose a reason for hiding this comment

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

We can remove this function. I couldn't do it in the review because this part is not changed in this PR.

Comment on lines +38 to +39
EVS = Tensor(-99.0)
WTRA = Tensor(-99.0)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
EVS = Tensor(-99.0)
WTRA = Tensor(-99.0)
EVS = Tensor(0.0)
WTRA = Tensor(0.0)

test_data_url = params.get("test_data_url")
if test_data_url is not None:
match = re.search(r"_(\d+)\.yaml$", str(test_data_url))
if match and int(match.group(1)) > 5:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Good idea! Files 1:5 are all the same crop and similar agro management data. It is better to choose files 05, 06, 10, 20, 30, 44. These cover different crops and agro management info, and perhaps edge cases, if any. Is it possible to use those files instead? we can hard code the file names here.

self.pheno.calc_rates(day, drv)

# if before emergence there is no need to continue
# because only the phenology is running.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
# because only the phenology is running.
# because only the phenology is running.
# TODO: revisit this when fixing #60

# if before emergence there is no need to continue
# because only the phenology is running.
# Just run a touch() to to ensure that all state variables are available
# in the kiosk
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
# in the kiosk
# in the kiosk
# TODO: revisit this when fixing #60

@SarahAlidoost
Copy link
Collaborator

In order to make the tests more manageable (the test action takes >2h if unchecked), I have introduced two pytest parser addoptions:

  1. --full_wofost72_test if used, runs the gradient test for all the parameter combinations used by wofost72. By default, we now only test the gradients of the parameters that are directly manipulated in wofost72.
  1. --fast when specified, it: (a) makes pytest use test files 0 to 5 instead of all the 45 available, and (b) skips GPU and tensor-input in all the gradient tests.

Good idea! This has been done in pcse as well, quick and full test. I see that the action still takes 20 minutes. Later we can think about optimizing this further.

@SarahAlidoost, we could think about using --fast by default for the GitHub actions to significantly save time, and only run the tests without --fast once before PR merge. This is probably the only real solution to #81. What do you think?

That's a good solution. Also, we can remove windows and mac and python 3.11 and 3.12 from --full_wofost72_test that runs once before PR merge. In a separate github action, we can define a workflow with all OS and pythons for --full_wofost72_test with a workflow_dispatch that we can run the test once before a release.

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.

[Task]: Make wofost72 differentiable and efficient

2 participants