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

Simplify class names #29

Open
21ch216 opened this issue Feb 18, 2025 · 2 comments
Open

Simplify class names #29

21ch216 opened this issue Feb 18, 2025 · 2 comments
Assignees
Labels
enhancement New feature or request v2.0.0/nhpp

Comments

@21ch216
Copy link
Collaborator

21ch216 commented Feb 18, 2025

While working on branch v2.0.0/nhpp, I find it cumbersome to differentiate between OneCycleAgeReplacementPolicy, AgeReplacementPolicy, and NHPPAgeReplacementPolicy, as they are all, in essence, age replacement policies.

First question:

Why not create a factory to redirect to the correct policy instantiation depending on the passed arguments?
I can see two possible approaches:

  1. Create a factory within the __new__ method of a single AgeReplacementPolicy class. This could look like:

  2. Create a factory function, age_replacement_policy, which would return one of OneCycleAgeReplacementPolicy, AgeReplacementPolicy, or NHPPAgeReplacementPolicy.

Second question:

Why not rename AgeReplacementPolicy to AgeReplacement?
I believe the term "policy" could be implicit here, resulting in a more concise class name for the user. Names like NHPPAgeReplacementPolicy and OneCycleAgeReplacementPolicy are too long, making them harder to remember and prone to typos.
I do acknowledge that one might raise concerns about potential ambiguity between AgeReplacementModel and AgeReplacement. However, I don’t think this would be an issue since AgeReplacementModel is an internal class and isn’t directly exposed through the user API.

@21ch216 21ch216 added enhancement New feature or request v2.0.0/nhpp labels Feb 18, 2025
@21ch216
Copy link
Collaborator Author

21ch216 commented Feb 19, 2025

For the first question, I suggest something like :

class AgeReplacementPolicy:
    def __init__(self, model: LifetimeModel[*TupleArrays]):
        self.model = model
        self.policy = None
        self.fitted = False

    def load(self, /, *args, nature: str = "default", **kwargs):
        if self.policy is not None:
            raise ValueError("AgeReplacementPolicy is already load")
        elif nature == "default":
            self.policy = AgeReplacementPolicy(self.model, *args, **kwargs)
        elif nature == "one_cycle":
            self.policy = OneCycleAgeReplacementPolicy(self.model, *args, **kwargs)
        elif nature == "poisson":
            self.policy = NHPPAgeReplacementPolicy(self.model, *args, **kwargs)
        else:
            raise ValueError(f"Uncorrect nature {nature}")

    def load_fit(self, /, *args, nature: str = "default", **kwargs):
        if self.policy is not None:
            raise ValueError("AgeReplacementPolicy is already load")
        elif nature == "default":
            self.policy = AgeReplacementPolicy(self.model, *args, **kwargs).fit()
            self.fitted = True
        elif nature == "one_cycle":
            self.policy = OneCycleAgeReplacementPolicy(
                self.model, *args, **kwargs
            ).fit()
            self.fitted = True
        elif nature == "poisson":
            self.policy = NHPPAgeReplacementPolicy(self.model, *args, **kwargs).fit()
            self.fitted = True
        else:
            raise ValueError(f"Uncorrect nature {nature}")

    def fit(self):
        if self.fitted:
            raise ValueError("AgeReplacementPolicy is already fitted")
        self.policy = self.policy.fit()

    def __getattr__(self, item):
        class_name = type(self).__name__
        if self.policy is None:
            raise ValueError("No policy as been loaded")
        if item in super().__getattribute__("policy"):
            return super().__getattribute__("policy")[item]
        raise AttributeError(f"{class_name} has no attribute/method {item}")

This implementation provides a unified factory class to manage all age replacement policies. The __getattr__ magic method bridges methods from the self.policy instance, allowing the user to call policy-specific methods as they normally would have done if they were directly interacting with the policy.

Example usage would be something like :

>>> weibull = Weibull(...)
>>> gompertz = Gompertz(...)

>>> policy = AgeReplacementPolicy(model)
>>> policy.load_fit(cf = , cp =, ...) # default
>>> policy.expected_total_cost(timeline)

>>> another_policy = AgeReplacementPolicy(gompertz)
>>> another_policy.load_fit(nature="poisson", cf = , cr = , ...)

or maybe pass the nature arg to the initialization of AgeReplacementPolicy ?

I think that the big downside of this approach is the difficulty in documenting the arguments that can be passed to the load or load_fit methods, as these arguments can vary depending on the policy type defined by the nature parameter. However, documenting this is not impossible, and it can be achieved by providing detailed descriptions of the options in the factory's docstring or in separate documentation files.

@21ch216
Copy link
Collaborator Author

21ch216 commented Feb 24, 2025

To relate it to #32, load_and_optimize would be more appropriate than load_fit.

In addition, I would prefer using a common argument named costs whose type would be Dict[NDArray[np.float64] | float ]. This way, it would encapsulate varying denominations of cost in one common argument.

For other arguments, I would also remove a0 as I mentionned in #32, a better solution could be to set model1=model.left_truncate_by(a0).

At the moment, NHPPAgeReplacementPolicy does not expect model1, but that could be done in a future version (if it is mathematically doable). Otherwise, setting model1 would raise an error in this case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request v2.0.0/nhpp
Projects
None yet
Development

No branches or pull requests

2 participants