@@ -136,15 +136,20 @@ async def _run_process(process: Process) -> None: # pragma: no cover
136136 async with process :
137137 await process .run ()
138138
139- def run (self , spec : ProcessSpec ) -> ray .tune .Result :
139+ @property
140+ def is_multi_objective (self ) -> bool :
141+ """Returns `True` if the optimisation is multi-objective."""
142+ return len (self ._objective ) > 1
143+
144+ def run (self , spec : ProcessSpec ) -> ray .tune .Result | list [ray .tune .Result ]:
140145 """Run the optimisation job on Ray.
141146
142147 Args:
143148 spec: The [`ProcessSpec`][plugboard.schemas.ProcessSpec] to optimise.
144149
145150 Returns:
146- A [`Result`][ray.tune.Result] containing the best trial result. Use the `result_grid`
147- property to get full trial results.
151+ Either a single of list of [`Result`][ray.tune.Result] objects containing the best trial
152+ result. Use the `result_grid` property to get full trial results.
148153 """
149154 self ._logger .info ("Running optimisation job on Ray" )
150155 spec = spec .model_copy ()
@@ -187,8 +192,11 @@ def _objective( # pragma: no cover
187192 self ._logger .info ("Starting Tuner" )
188193 self ._result_grid = _tune .fit ()
189194 self ._logger .info ("Tuner finished" )
190- return self ._result_grid .get_best_result (
191- # Choose the first metric and mode if multiple are provided
192- metric = self ._metric [0 ] if isinstance (self ._metric , list ) else self ._metric ,
193- mode = self ._mode [0 ] if isinstance (self ._mode , list ) else self ._mode ,
194- )
195+ if self .is_multi_objective :
196+ return [
197+ self ._result_grid .get_best_result (metric = metric , mode = mode )
198+ for metric , mode in zip (self ._metric , self ._mode )
199+ ]
200+ if isinstance (self ._metric , list ) or isinstance (self ._mode , list ): # pragma: no cover
201+ raise RuntimeError ("Invalid configuration found for single-objective optimisation." )
202+ return self ._result_grid .get_best_result (metric = self ._metric , mode = self ._mode )
0 commit comments