traitecoevo / plant

Trait-Driven Models of Ecology and Evolution :evergreen_tree:
https://traitecoevo.github.io/plant
53 stars 20 forks source link

Ideas for optimisation in water use model #342

Closed itowers1 closed 1 month ago

itowers1 commented 1 year ago

Just noting some ideas to optimise speed of water use model:

aornugent commented 1 year ago

Awesome - looks like you're really getting to the finer points. We discussed some potential solutions today and I think there's a way to achieve all three and significantly clean up the code.

Here's a sketch of some of the core ideas.

The environment and vars objects are passed to the leaf as pointers. set_physiology updates the leaf state, pre-calculating useful values as you suggest in point 3. When we optimise psi_stem the output is saved in the vars object for access in the next timestep as you suggest in point 2. @dfalster described this approach as 'handing over your checkbook' - what this means is that the side-effects of leaf.optimise_psi_stem is not known from FF16w_Strategy - we just trust that it does what we want. optimise_psi_stem then sets profit in the leaf rather than returning the value as you suggest in point 1.

@dfalster @devmitch - any major drawbacks for optimise_psi_stem having the side-effect of setting vars? I like the clean code but don't have a strong sense of modern C++ style.

FF16w_Strategy.cpp

FF16w_Strategy::net_mass_production_dt(...) {
   ...

   // update leaf to reflect env. conditions
   leaf.set_physiology(environment, height);

   // track stem hydrology as individual auxiliary state
   leaf.optimise_psi_stem(vars);

   double assimilation = leaf.profit * area_leaf_; 
   // or
   // double assimilation = leaf.assimilation_per_area * area_leaf_;

   ...
}

FF16w_Strategy.h

std::vector<std::string> aux_names() {
  std::vector<std::string> ret(
      {"competition_effect", "net_mass_production_dt",  "opt_ci"});
  // add the associated computation to compute_rates and compute there
  if (collect_all_auxiliary) {
    ret.push_back("area_sapwood");
  }
  return ret;
}

leaf_model.h

class Leaf {
   ...
   void set_physiology(const FF16_Environment &environment, double height);
   void optimise_psi_stem(Internals &vars);
}

leaf_model.cpp

// set up leaf internal state based on environmental conditions
void Leaf::set_physiology(const FF16_Environment &environment, double height) {
   psi_soil = environment.get_psi_soil() / 1000000;
   PPFD = environment.PPFD;
   huber_value = ...;
   k_l_max = K_s * huber_value / (height * eta_c);
   lambda = ...;
}

void Leaf::optimise_psi_stem(Internals &vars) {
   // start search at prev. node optima
   double psi_stem_initial = vars.aux(aux_index.at("opt_ci"));

   // calculate profit at initial psi_stem
   y_0 = calc_profit(PPFD, psi_soil, psi_stem_initial, k_l_max)

   // optimise
   ...

   // set new node optima for next timestep
   vars.set_aux(aux_index.at("opt_ci"), psi_stem_next)  // might have variable names confused.

   // set profit at optimal psi_stem
   leaf.profit = y_0;

  // return nothing because void 
}
itowers1 commented 1 year ago

Looks good, thanks for writing that up Andrew. I'll address these now and close when ready.

a-orn commented 1 year ago

I suppose it wouldn't be much extra code to avoid passing vars and setting opt_ci as a side-effect. This has the benefit of being able to test from R without having to create an Individual or Internals object.

FF16w_Strategy.cpp

FF16w_Strategy::net_mass_production_dt(...) {
   // update leaf to reflect env. conditions
   leaf.set_physiology(environment, height);

   // optimise for profit
   leaf.optimise_psi_stem(vars.aux(aux_index.at("opt_ci")));

   // track stem hydrology as individual auxiliary state
   vars.set_aux(aux_index.at("opt_ci"), leaf.opt_ci)

   double assimilation = leaf.profit * area_leaf_; 
   // or
   // double assimilation = leaf.assimilation_per_area * area_leaf_;
}

leaf_model.cpp

void Leaf::optimise_psi_stem(double psi_stem_initial) {
   // optimise for profit starting at psi_stem_initial aka. opt_ci at prev. timestep
   y_0 = calc_profit(PPFD, psi_soil, psi_stem_initial, k_l_max)

   // set new node optima for next timestep
   opt_ci = psi_stem_optimised; // might have variable names confused.

   // set profit at optimal psi_stem
   profit = y_0;

  // return nothing because void 
}
dfalster commented 1 year ago

Thanks for the suggested code Andrew. Yes, I agree with not passing in the vars variable, as this keeps clearer boundary between the different parts. Isaac and I caught up about that and I explained a bit more about vars.

dfalster commented 1 month ago

Closed as these have been addressed