We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN 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).

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `lightwood/mixer/lightgbm_array.py`
Content:
```
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 FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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

{"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"}], "after_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 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        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"}]}