cellml / libcellml

Repository for libCellML development.
https://libcellml.org
Apache License 2.0
16 stars 21 forks source link

External RHS function API #1013

Open chrispbradley opened 2 years ago

chrispbradley commented 2 years ago

This may be more of a question than an issue. When using libCellML to generate code that can be called from an external integrator we have a specific RHS API generated that the external program can call. For the old CellML API this call was something like

void cellml_model_definition_call_rhs_routine_f(void model, double voi, double states, double rates, double wanted, double* known);

The last four parameters are the ones of importance here. States and known are input parameters and rates and wanted are output parameters. Now for the likes of OpenCMISS, we can have a large number of individual CellML models that correspond to spatially varying cells in some tissue model. In this tissue model it may be that individual CellML input variables e.g., gNa, might spatially vary and it may be that the modeller is interested in the resulting spatial variation of individual CellML output variables e.g., INa. The particular variables that OpenCMISS is required to be pass in and out of the RHS API were flagged to the CellML API as being known or wanted or both (state variables). For OpenCMISS the variables flagged would be the ones that varied spatially and all the others would just be set at their value as specified in the .cellml file. When the RHS code was generated the values that were not flagged known or wanted would just be set as local const variables (or #defines?) and the remaining variables were passed out/in via wanted and known.

Now, as I understand it, this has changed slightly in libCellML in that the in/out variable pointers are now variables and externalVariables? In addition, as I understand it, variables that have not been flagged are not local variables to the routines but rather are all passed in via variables? There is also, as I understand it, no separation of input and output variables, and they are all passed via the variables array?

If my understanding is correct then this would cause performance problems with how we use CellML in OpenCMISS. There are considerable performance/optimisation advantages by only storing the CellML variables that spatially change in OpenCMISS. Because we can have 10^lots of cells then unnecessarily storing variables that do not change would take up a lot of memory. In addition, there is a significant performance advantage if we are able to pass to the RHS routine a pointer to a contiguous block of memory that contains the variables that the CellML RHS model needs. If the block is not contiguous, then expensive copying to a temporary contiguous block is required which can be expensive with 10^lots of cells. Finally, not being able to separate input and output variables can inhibit optimisation as the compiler has to assume that an otherwise constant variable will change inside the CellML RHS.

If I am wrong in my understanding then is there some document that can explain the new RHS API routine etc.?

nickerso commented 2 years ago

@chrispbradley thanks for bringing this up. You are mostly right, in that the base code generation available in libCellML is not going to support that kind of use case out of the box. But when we were putting together the libCellML API (and particularly those externalVariables) we did keep the OpenCMISS use-case in mind and had mocked up a solution that we thought at the time would work. Similar to what I'd done with the CellML API, the code generation API in libCellML would be wrapped with a (slightly) higher level API to expose generated code that is amenable to those performance advantages.

There is currently no document that can explain this, but we should be able to put something together to demonstrate how this would work.

chrispbradley commented 2 years ago

Hi Andre, are you saying that the generated code will be wrapped in order to expose a similar API to the old CellML API? If so, this wrapper would be the one called which would then rearrange things to end up as variables and externalVariables? This may be an issue with 10^lots of calls as whatever manipulation is done under the hood will be required for every call? I wouldn't necessarily have a problem during the setup phase to have an API that had to do some sort of rearrangement but shouldn't the generated code be as "optimal" as possible? Once it is generated then the API has done its job as isn't really involved afterwards - it is the external program/integrator that is doing all the calling. If there is to be a manipulation wrapper then can't that manipulation be done and then the code generated so that it can be optimal for the case when there are a large number of calls made? Or have I misunderstood?

nickerso commented 2 years ago

yep, sorry - that is what I meant. The wrapper API is at the code generation phase to customise the generated code which is then used by external program/integrator. I think having an example demonstrating what I mean will be much clearer - I'll try to take a look at that over the next few days (I have a feeling I've got some python code somewhere that is close to this use case...)