ImperialCollegeLondon / virtual_ecosystem

This repository is the home for the codebase for the Virtual Ecosystem project.
https://virtual-ecosystem.readthedocs.io
BSD 3-Clause "New" or "Revised" License
9 stars 1 forks source link

Resolving constants milestone issues and constants bundling. #364

Open davidorme opened 9 months ago

davidorme commented 9 months ago

This is a cross between a meta-issue and a discussion to tidy up a backlog of constants questions that we've grouped in the "Constants system" milestone.

This issue is also a summary of the inaugural monthly "GitHub issues" developer meetings to bundle existing issues and clean them up πŸŽ‰ πŸ₯‚

Where we are now

Outstanding issues

We have five outstanding constants issues:

The remaining two issues are linked and this PR suggests a resolution for both:

Sharing constants and functionality

The proposed solution to #358 that we can define model "meta" constants classes and use class inheritance to allow the clean use of different constants classes with shared functions. So:


from virtual_rainforest.core.constants import ConstantsDataclass

class ModelMeta(ConstantsDataclass):
    """A meta constants class created in e.g. SimpleModel."""
    b = 2
    c = 3

class ActualModelSimple(ModelMeta):
    """The actual simple model constants from SimpleModel.

    This is derived from the meta class and only adds a constant specific to this model
    """
    d = 5

class ActualModelComplex(ModelMeta):
    """A more complex model constants from a parallel model.

    This adds two new constants and overwrites an existing constant with a model specific version
    """
    b = 4
    d = 5
    e = 6

def shared_function(constants: ModelMeta):
    """A function created in simple model that we also want to use in the complex model."""    
    return constants.b 

These can then be used seamlessly in both models - all of the statements below are mypy friendly because the typing of ModelMeta supports the meta class itself and any derived model specific subclasses.

In [7]: meta = ModelMeta()
In [8]: simple = ActualModelSimple()
In [10]: cplex = ActualModelComplex()
In [11]: shared_function(meta) # Although you'd never really do this in practice!
Out[11]: 2
In [12]: shared_function(simple)
Out[12]: 2
In [13]: shared_function(cplex)
Out[13]: 4

Merging core constants into models - we could but no

That solution also opens up this resolution for #342 - which seems really clean and avoids the whole issue with properties that was mentioned on this topic in #341.

from virtual_rainforest.core.constants import ConstantsDataclass

class CoreConstants(ConstantsDataclass):
    a = 1

class ModelMeta(CoreConstants):
    b = 2
    c = 3

class ActualModelSimple(ModelMeta):
    d = 5

And hence:

In [15]: const = ActualModelSimple()
In [16]: const.a
Out[16]: 1

However, the dev meeting concluded:

So - we should simply close #342 and live with having two constants classes.

dalonsoa commented 9 months ago

I was following the argument well until I got here:

That seems super clean and simple but we then have the core constants multiply defined independently in each model constants class - they shouldn't differ but why would we even open up the possibility?

How is that possibility open? Core constants will be defined using ClassVar, like in https://github.com/ImperialCollegeLondon/virtual_rainforest/blob/dd41f744e6177151e9e7c1b51ffb9ebe78904622/virtual_rainforest/core/constants.py#L21 So even if the constants are multiply defined, the core ones will be the same. By construction, they cannot be changed.

Did I miss anything?

davidorme commented 9 months ago

Not all core constants are necessarily defined using ClassVar - there may be core constants that users can sensibly alter. My concern here is that in theory, users could get models with different core constants, but looking back, that is extremely paranoid:

I think the more compelling argument is that it is just needless duplication.

dalonsoa commented 9 months ago

As we all know, in Python, if a user want to change something, there's absolutely nothing that can prevent that. But beyond certain reasonable cases, if a user changes something they should not and that results in useless results, to be honest, they deserve it.

On your last point, needless duplication does not sound too compelling to me if you sacrifice simplicity. For me needless duplication is having to keep two separate sets of constants. That makes the interface more complicated - not super complicated, just a bit more complicated. If the risk of having inconsistent set of constants is as small as you suggest, I honestly would go for the super clean and super simple approach you have come up with.

davidorme commented 9 months ago

I agree about users deserving what they get in those cases πŸ˜„

I think there is a cost to simplicity in what we would need to do to load_constants to merge the currently separated config namespaces for core and model constants. In the meeting, I think @vgro , @jacobcook1995 and I generally agreed that having two constants namespaces was a mild complication we were happy to live with?

Beyond the handling of core constants, though, the approach for the meta constants seems sound?

dalonsoa commented 9 months ago

Yes, that looked sensible, considering the problem to solve. I guess one potential difficulty might be finding the common constants that should go in the Meta model, but maybe I'm seeing a problem where there is none.