prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  Improve runtime of `LightGBMArray` for long-horizon forecasting
  Two main approaches:
  
  - Disable optuna hyperparam search past some threshold.
  - Opt for a recursive strategy instead of direct (i.e. same regressor trained for all timesteps v/s one for each step).
  
  </issue>
  <code>
  [start of lightwood/mixer/lightgbm_array.py]
  1 from copy import deepcopy
  2 from typing import Dict, List, Union
  3 
  4 import numpy as np
  5 import pandas as pd
  6 
  7 from lightwood.helpers.log import log
  8 from lightwood.mixer.helpers.ts import _apply_stl_on_training, _stl_transform, _stl_inverse_transform
  9 from lightwood.encoder.base import BaseEncoder
  10 from lightwood.mixer.base import BaseMixer
  11 from lightwood.mixer.lightgbm import LightGBM
  12 from lightwood.api.types import PredictionArguments, TimeseriesSettings
  13 from lightwood.data.encoded_ds import EncodedDs, ConcatedEncodedDs
  14 
  15 
  16 class LightGBMArray(BaseMixer):
  17     """LightGBM-based model, intended for usage in time series tasks."""
  18     models: List[LightGBM]
  19     submodel_stop_after: float
  20     target: str
  21     supports_proba: bool
  22     ts_analysis: Dict
  23     tss: TimeseriesSettings
  24 
  25     def __init__(
  26             self,
  27             stop_after: float,
  28             target: str,
  29             dtype_dict: Dict[str, str],
  30             input_cols: List[str],
  31             fit_on_dev: bool,
  32             target_encoder: BaseEncoder,
  33             ts_analysis: Dict[str, object],
  34             use_stl: bool,
  35             tss: TimeseriesSettings
  36     ):
  37         super().__init__(stop_after)
  38         self.tss = tss
  39         self.horizon = tss.horizon
  40         self.submodel_stop_after = stop_after / self.horizon
  41         self.target = target
  42         self.offset_pred_cols = [f'{self.target}_timestep_{i}' for i in range(1, self.horizon)]
  43         if set(input_cols) != {self.tss.order_by}:
  44             input_cols.remove(self.tss.order_by)
  45         for col in self.offset_pred_cols:
  46             dtype_dict[col] = dtype_dict[self.target]
  47         self.models = [LightGBM(self.submodel_stop_after,
  48                                 target_col,
  49                                 dtype_dict,
  50                                 input_cols,
  51                                 False,  # fit_on_dev,
  52                                 True,  # use_optuna
  53                                 target_encoder)
  54                        for _, target_col in zip(range(self.horizon), [target] + self.offset_pred_cols)]
  55         self.ts_analysis = ts_analysis
  56         self.supports_proba = False
  57         self.use_stl = False
  58         self.stable = True
  59 
  60     def _fit(self, train_data: EncodedDs, dev_data: EncodedDs, submodel_method='fit') -> None:
  61         original_train = deepcopy(train_data.data_frame)
  62         original_dev = deepcopy(dev_data.data_frame)
  63 
  64         if self.use_stl and self.ts_analysis.get('stl_transforms', False):
  65             _apply_stl_on_training(train_data, dev_data, self.target, self.tss, self.ts_analysis)
  66 
  67         for timestep in range(self.horizon):
  68             getattr(self.models[timestep], submodel_method)(train_data, dev_data)
  69 
  70         # restore dfs
  71         train_data.data_frame = original_train
  72         dev_data.data_frame = original_dev
  73 
  74     def fit(self, train_data: EncodedDs, dev_data: EncodedDs) -> None:
  75         log.info('Started fitting LGBM models for array prediction')
  76         self._fit(train_data, dev_data, submodel_method='fit')
  77 
  78     def partial_fit(self, train_data: EncodedDs, dev_data: EncodedDs) -> None:
  79         log.info('Updating array of LGBM models...')
  80         self._fit(train_data, dev_data, submodel_method='partial_fit')
  81 
  82     def __call__(self, ds: Union[EncodedDs, ConcatedEncodedDs],
  83                  args: PredictionArguments = PredictionArguments()) -> pd.DataFrame:
  84         if args.predict_proba:
  85             log.warning('This model does not output probability estimates')
  86 
  87         original_df = deepcopy(ds.data_frame)
  88         length = sum(ds.encoded_ds_lenghts) if isinstance(ds, ConcatedEncodedDs) else len(ds)
  89         ydf = pd.DataFrame(0,  # zero-filled
  90                            index=np.arange(length),
  91                            columns=[f'prediction_{i}' for i in range(self.horizon)])
  92 
  93         if self.use_stl and self.ts_analysis.get('stl_transforms', False):
  94             ds.data_frame = _stl_transform(ydf, ds, self.target, self.tss, self.ts_analysis)
  95 
  96         for timestep in range(self.horizon):
  97             ydf[f'prediction_{timestep}'] = self.models[timestep](ds, args)['prediction'].values
  98 
  99         if self.use_stl and self.ts_analysis.get('stl_transforms', False):
  100             ydf = _stl_inverse_transform(ydf, ds, self.tss, self.ts_analysis)
  101 
  102         if self.models[0].positive_domain:
  103             ydf = ydf.clip(0)
  104 
  105         ydf['prediction'] = ydf.values.tolist()
  106         ds.data_frame = original_df
  107         return ydf[['prediction']]
  108
  [end of lightwood/mixer/lightgbm_array.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: diff --git a/lightwood/mixer/lightgbm_array.py b/lightwood/mixer/lightgbm_array.py
  --- a/lightwood/mixer/lightgbm_array.py
  +++ b/lightwood/mixer/lightgbm_array.py
  @@ -49,7 +49,7 @@
                                   dtype_dict,
                                   input_cols,
                                   False,  # fit_on_dev,
  -                                True,  # use_optuna
  +                                True if tss.horizon < 10 else False,  # use_optuna
                                   target_encoder)
                          for _, target_col in zip(range(self.horizon), [target] + self.offset_pred_cols)]
           self.ts_analysis = ts_analysis
  
verification_info: {"golden_diff": "diff --git a/lightwood/mixer/lightgbm_array.py b/lightwood/mixer/lightgbm_array.py\n--- a/lightwood/mixer/lightgbm_array.py\n+++ b/lightwood/mixer/lightgbm_array.py\n@@ -49,7 +49,7 @@\n                                 dtype_dict,\n                                 input_cols,\n                                 False,  # fit_on_dev,\n-                                True,  # use_optuna\n+                                True if tss.horizon < 10 else False,  # use_optuna\n                                 target_encoder)\n                        for _, target_col in zip(range(self.horizon), [target] + self.offset_pred_cols)]\n         self.ts_analysis = ts_analysis\n", "issue": "Improve runtime of `LightGBMArray` for long-horizon forecasting\nTwo main approaches:\r\n\r\n- Disable optuna hyperparam search past some threshold.\r\n- Opt for a recursive strategy instead of direct (i.e. same regressor trained for all timesteps v/s one for each step).\n", "before_files": [{"content": "from copy import deepcopy\nfrom typing import Dict, List, Union\n\nimport numpy as np\nimport pandas as pd\n\nfrom lightwood.helpers.log import log\nfrom lightwood.mixer.helpers.ts import _apply_stl_on_training, _stl_transform, _stl_inverse_transform\nfrom lightwood.encoder.base import BaseEncoder\nfrom lightwood.mixer.base import BaseMixer\nfrom lightwood.mixer.lightgbm import LightGBM\nfrom lightwood.api.types import PredictionArguments, TimeseriesSettings\nfrom lightwood.data.encoded_ds import EncodedDs, ConcatedEncodedDs\n\n\nclass LightGBMArray(BaseMixer):\n    \"\"\"LightGBM-based model, intended for usage in time series tasks.\"\"\"\n    models: List[LightGBM]\n    submodel_stop_after: float\n    target: str\n    supports_proba: bool\n    ts_analysis: Dict\n    tss: TimeseriesSettings\n\n    def __init__(\n            self,\n            stop_after: float,\n            target: str,\n            dtype_dict: Dict[str, str],\n            input_cols: List[str],\n            fit_on_dev: bool,\n            target_encoder: BaseEncoder,\n            ts_analysis: Dict[str, object],\n            use_stl: bool,\n            tss: TimeseriesSettings\n    ):\n        super().__init__(stop_after)\n        self.tss = tss\n        self.horizon = tss.horizon\n        self.submodel_stop_after = stop_after / self.horizon\n        self.target = target\n        self.offset_pred_cols = [f'{self.target}_timestep_{i}' for i in range(1, self.horizon)]\n        if set(input_cols) != {self.tss.order_by}:\n            input_cols.remove(self.tss.order_by)\n        for col in self.offset_pred_cols:\n            dtype_dict[col] = dtype_dict[self.target]\n        self.models = [LightGBM(self.submodel_stop_after,\n                                target_col,\n                                dtype_dict,\n                                input_cols,\n                                False,  # fit_on_dev,\n                                True,  # use_optuna\n                                target_encoder)\n                       for _, target_col in zip(range(self.horizon), [target] + self.offset_pred_cols)]\n        self.ts_analysis = ts_analysis\n        self.supports_proba = False\n        self.use_stl = False\n        self.stable = True\n\n    def _fit(self, train_data: EncodedDs, dev_data: EncodedDs, submodel_method='fit') -> None:\n        original_train = deepcopy(train_data.data_frame)\n        original_dev = deepcopy(dev_data.data_frame)\n\n        if self.use_stl and self.ts_analysis.get('stl_transforms', False):\n            _apply_stl_on_training(train_data, dev_data, self.target, self.tss, self.ts_analysis)\n\n        for timestep in range(self.horizon):\n            getattr(self.models[timestep], submodel_method)(train_data, dev_data)\n\n        # restore dfs\n        train_data.data_frame = original_train\n        dev_data.data_frame = original_dev\n\n    def fit(self, train_data: EncodedDs, dev_data: EncodedDs) -> None:\n        log.info('Started fitting LGBM models for array prediction')\n        self._fit(train_data, dev_data, submodel_method='fit')\n\n    def partial_fit(self, train_data: EncodedDs, dev_data: EncodedDs) -> None:\n        log.info('Updating array of LGBM models...')\n        self._fit(train_data, dev_data, submodel_method='partial_fit')\n\n    def __call__(self, ds: Union[EncodedDs, ConcatedEncodedDs],\n                 args: PredictionArguments = PredictionArguments()) -> pd.DataFrame:\n        if args.predict_proba:\n            log.warning('This model does not output probability estimates')\n\n        original_df = deepcopy(ds.data_frame)\n        length = sum(ds.encoded_ds_lenghts) if isinstance(ds, ConcatedEncodedDs) else len(ds)\n        ydf = pd.DataFrame(0,  # zero-filled\n                           index=np.arange(length),\n                           columns=[f'prediction_{i}' for i in range(self.horizon)])\n\n        if self.use_stl and self.ts_analysis.get('stl_transforms', False):\n            ds.data_frame = _stl_transform(ydf, ds, self.target, self.tss, self.ts_analysis)\n\n        for timestep in range(self.horizon):\n            ydf[f'prediction_{timestep}'] = self.models[timestep](ds, args)['prediction'].values\n\n        if self.use_stl and self.ts_analysis.get('stl_transforms', False):\n            ydf = _stl_inverse_transform(ydf, ds, self.tss, self.ts_analysis)\n\n        if self.models[0].positive_domain:\n            ydf = ydf.clip(0)\n\n        ydf['prediction'] = ydf.values.tolist()\n        ds.data_frame = original_df\n        return ydf[['prediction']]\n", "path": "lightwood/mixer/lightgbm_array.py"}]}