We are working on supporting diversity optimization algorithms such as novelty search (see #472). One difference from other algorithms is that such algorithms do not require objectives. However, the current API to Scheduler.tell() is:
tell(objective, measures, **fields)
i.e., it assumes objective and measures will be provided. This PR seeks to make it possible to call scheduler.tell(measures) in addition to the current scheduler.tell(objective, measures).
Use Cases
There are a couple of use cases that the scheduler must satisfy:
QD optimization (i.e., current behavior should not break):
Change scheduler API to tell(*args, **kwargs) and add a problem_type to each archive.
args and kwargs are interpreted based on the type of the archive. If the problem type is quality_diversity, then the args are interpreted as objectives and measures. If the problem type is diversity_optimization, then the args are interpreted as measures. If the problem type is single_objective, the args are interpreted as objective. kwargs will be treated the same as fields in all cases.
Pros: Backwards-compatible and handles all 4 use cases above.
Cons: Makes the archives a bit more complex, may be a bit confusing to users because the signature will not be very informative. However, I think we can get away with this with good docstrings and documentation.
Allow the current objective argument to be treated as measures, and assign a default argument of None to the current objective and measures -> i.e., tell(objective=None, measures=None, **fields)
Inspired by how numpy's integers can be either rng.integers(high) or rng.integers(low, high) -> see here
Pros: Minimal changes to current API, and still quite interpretable for users.
Cons: On the other hand, it may be confusing to see that objective can be set to measures. This will also fail case 3 above, i.e., scheduler.tell(measures, objective=objective) will throw an error because measures is actually objective due to the positional argument, so objective is being passed in twice.
Require the user to pass in objective=None when performing diversity optimization and maintain the same scheduler.tell API.
Pros: This requires the fewest changes to the API. It also provides a good model for what EvolutionStrategyEmitter and other emitters should do -- all these classes can now implement behavior for objective=None. It also does not require modifying the archives to have a problem_type or archive_type attribute. Furthermore, objective can still be provided if that is a field in the archive.
Cons: It is a bit verbose to have scheduler.tell(None, measures) or scheduler.tell(objective=None, measures=measures) but I think users can understand that.
Optionally, we can also set a default of objective=None and measures=None so that one can pass just measures, e.g., scheduler.tell(measures=...) or just objectives scheduler.tell(objective=). However, I think we may not want to add this feature for now as it requires adding default values to a lot of places.
Add a new parameter to scheduler.tell called mode or something similar to indicate diversity optimization is in effect, i.e., scheduler.tell(measures, mode="diversity")
Pros: This is very explicit and makes it clear that diversity optimization is happening without objectives.
Cons: Rather verbose.
Decision
I believe Solution 3 is the best solution, since it involves the fewest changes to the API and also contains the changes to the scheduler.
TODO
[x] Implement behavior for when objective=None in Scheduler.tell -- specifically, the scheduler will now allow any field to be None, and when the field is None, the scheduler will simply pass None down to the emitter tell functions.
[x] Write test for passing objective=None to the scheduler, both with positional and keyword arguments. This test will be updated once the UnstructuredArchive is implemented.
Description
We are working on supporting diversity optimization algorithms such as novelty search (see #472). One difference from other algorithms is that such algorithms do not require objectives. However, the current API to Scheduler.tell() is:
i.e., it assumes objective and measures will be provided. This PR seeks to make it possible to call
scheduler.tell(measures)
in addition to the currentscheduler.tell(objective, measures)
.Use Cases
There are a couple of use cases that the scheduler must satisfy:
scheduler.tell(objective, measures)
scheduler.tell(objective=objective, measures=measures)
scheduler.tell(objective, measures=measures)
scheduler.tell(measures)
scheduler.tell(measures=measures)
scheduler.tell(measures, objective=objective)
scheduler.tell(measures=measures, objective=objective)
Ideally, we would also be robust to a case where we need to only have objectives in the future:
scheduler.tell(objective)
scheduler.tell(objective=objective)
Potential Solutions
tell(*args, **kwargs)
and add aproblem_type
to each archive.tell(objective=None, measures=None, **fields)
integers
can be eitherrng.integers(high)
orrng.integers(low, high)
-> see herescheduler.tell(measures, objective=objective)
will throw an error becausemeasures
is actuallyobjective
due to the positional argument, soobjective
is being passed in twice.objective=None
when performing diversity optimization and maintain the samescheduler.tell
API.objective=None
. It also does not require modifying the archives to have aproblem_type
orarchive_type
attribute. Furthermore,objective
can still be provided if that is a field in the archive.scheduler.tell(None, measures)
orscheduler.tell(objective=None, measures=measures)
but I think users can understand that.objective=None
andmeasures=None
so that one can pass just measures, e.g.,scheduler.tell(measures=...)
or just objectivesscheduler.tell(objective=)
. However, I think we may not want to add this feature for now as it requires adding default values to a lot of places.scheduler.tell
calledmode
or something similar to indicate diversity optimization is in effect, i.e.,scheduler.tell(measures, mode="diversity")
Decision
I believe Solution 3 is the best solution, since it involves the fewest changes to the API and also contains the changes to the scheduler.
TODO
objective=None
inScheduler.tell
-- specifically, the scheduler will now allow any field to be None, and when the field is None, the scheduler will simply pass None down to the emitter tell functions.objective=None
to the scheduler, both with positional and keyword arguments. This test will be updated once the UnstructuredArchive is implemented.Status
yapf
pytest
pylint
HISTORY.md