Improving extensibility of GoalProgrammingMixin
!245 (merged) and !250 (closed) have shown a clear need for adding functionality on top of GPMixin. They both have suboptimal proposals now, especially !250 (closed) as it just adds yet another goal programming option. It would be nice to support similar constructs as these MRs (new goal types, auxiliary variables) in the future in a clean way.
A couple ideas of how to do this, and their advantages/disadvantages/deal-breakers. I mostly thought about (1), the others are not as worked out.
1. Friendly/protected method to turn goals into constraints
Rename __goal_constraints
to something that is not name-mangled, and can therefore be overridden. The idea is to still keep a single underscore to signify it is for internal use only.
We probably need to talk to the super() implementation of this method by using properties on Goals. Simply put, we do not want to reimplement all the logic of __goal_constraints
every time we want to change a small part of it. Typically we only want to add some auxiliary variable, and override either the formulation of the constraint(s) or the objective. I can see two ways here:
- let
__goal_constraints
check for some properties on a Goal, see if some custom objective/constraints are desired, and then pick/process those instead of the default/current ones. This means e.g. MinAbsMixin and LinearGoalOrder (and future additions) would not be able to use any information/variables in super() when making the objective func and constraint funcs, other than what is passed in. This includes e.g. "sym_index". I would suggest using the function signatures of_objective_func
and_soft_constraint_func
as they are. - some type of callbacks near
_objective_func
and_soft_constraint_func
(possibly also via the Goal), passing in locals() as an argument (such that the called function has access to all variables).
Something like !245 (merged) could then be implemented without min_abs_goals()
, and requiring only:
- That the user uses a
MinAbsGoal
- Inherits from
MinAbsGPMixin
Something like !250 (closed) would be similar, also requiring inheritance from some type of mixin.
The latter requirement is something that we should check for in GPMixin. MinAbsGoal would then signify that its processing requires inheritance of MinAbsGPMixin, and we can add a "isinstance()" check in __validate_goals
of GPMixin.
Advantages:
- Relatively easy to implement
-
__goal_constraints
can remain a method with access toself
. - Mixins can cleanly handle implementations of their
bounds()
,seed()
etc, on the auxiliary variables they add.
Disadvantages:
- Requires inheritance from a Mixin aside from using a special Goal.
- "Requires" check for inheritance
- Somewhat magic(?) that some goals in
goals()
are processed by GPMixin, whereas others could be processed elsewhere.
Possible deal breakers:
- Not sure how generic this all is.
2. Friendly/protected staticmethod on GPMixin to turn goals into constraints
Similar to (1), but now the method is a @staticmethod
.
The advantage:
- No longer need to inherit from e.g. MinAbsMixin when using MinAbsGoals. Can just let the goal refer to
MinAbsGPMixin._goal_to_constraints
with the right arguments. Then__goal_constraints
in GPMixin would check this attribute, and call it if it exists (default otherwise).
Disadvantage/deal breakers:
- Per goal, not for a list of all goals. Not sure how this will work with sym_index/j, etc.
- No access to local variables like min/max symbols
- No access to
self
, unless passed as an argument. We needself
for ensemble_size (can be worked around easily by passing it explicitly), but also need it for critical goals. In that case we callself.__goal_hard_constraint
(which calls other methods onself
that cannot be worked around easily).
3. Move __goal_constraints to the Goal class
Advantage:
- No Mixins anymore for additional functionality.
Disadvantage:
- Need to move everything (bounds, seed, priority completed, etc) to the Goal