Browse Source

feat(DHU): 更新除湿机优化逻辑与安全约束条件

- 支持读取 E 类设备数据点
- 再生温度上下限从配置文件改为实时数据获取
- 新增再生加热盘管边界条件有效性检查
- 添加送风露点变化幅度的安全限制约束
- 优化 `get_limit` 函数逻辑以增强鲁棒性

feat(Room): 改进房间露点预测模型结构与调用方式

- RoomDewPredictor 初始化新增 coef_is_pos 参数控制系数符号
- 模型保存和加载支持 TinR、Dout 等多种输入模式
- 预测函数中引入平滑处理及异常捕获机制
- 更新滞后相关性分析图增加参考线和刻度设置
- 修复预测时序写入逻辑中的潜在错误并提高稳定性

refactor(train): 调整训练周期与模型输出路径结构

- 将默认训练数据长度由 3 天扩展至 7 天
- 分别构建基于 DHU 模型、实际送风露点与再生加热温度的预测模型
- 更改模型与图表存储目录结构以便分类管理
- 移除未使用的代码段,提升可维护性
zhangshenhao 4 months ago
parent
commit
1551a58f82
4 changed files with 155 additions and 89 deletions
  1. 21 7
      apps/DHU/optimize.py
  2. 26 22
      apps/DHU/train.py
  3. 58 51
      apps/Room/predict.py
  4. 50 9
      model/Room/room.py

+ 21 - 7
apps/DHU/optimize.py

@@ -242,7 +242,7 @@ def load_data_dhu(
     data_URL,
 ):
     dhu_steady_len   = config_reader.get_app_info(each_eaup_name,'实时优化','除湿机工况均值时长','float')
-    data_input_point = config_reader.get_equp_point(each_eaup_name,equp_class=['A','B'])
+    data_input_point = config_reader.get_equp_point(each_eaup_name,equp_class=['A','B','E'])
     data_last = (
         DataLoader(
             path          = f'{config_reader_path}/data/optimize/data_cur/',
@@ -288,12 +288,18 @@ def optimize_dhu(
     if each_equp_type in ['DHU_A','DHU_B']:
         wheel_1_TinR_cur       = data_cur.loc[:,'wheel_1_TinR'].iat[0]
         wheel_2_TinR_cur       = data_cur.loc[:,'wheel_2_TinR'].iat[0]
-        wheel_1_TinR_uplim_set = config_reader.get_equp_info(each_eaup_name,key='前再生温度上限',info_type='float')
-        wheel_1_TinR_dwlim_set = config_reader.get_equp_info(each_eaup_name,key='前再生温度下限',info_type='float')
-        wheel_2_TinR_uplim_set = config_reader.get_equp_info(each_eaup_name,key='后再生温度上限',info_type='float')
-        wheel_2_TinR_dwlim_set = config_reader.get_equp_info(each_eaup_name,key='后再生温度下限',info_type='float')
+        wheel_1_TinR_uplim_set = data_cur.loc[:,'wheel_1_TinR_UP'].iat[0]
+        wheel_1_TinR_dwlim_set = data_cur.loc[:,'wheel_1_TinR_DW'].iat[0]
+        wheel_2_TinR_uplim_set = data_cur.loc[:,'wheel_2_TinR_UP'].iat[0]
+        wheel_2_TinR_dwlim_set = data_cur.loc[:,'wheel_2_TinR_DW'].iat[0]
         heatingcoil_1_Val_cur  = data_cur.loc[:,'heatingcoil_1_Val'].iat[0]
         heatingcoil_2_Val_cur  = data_cur.loc[:,'heatingcoil_2_Val'].iat[0]
+        
+        if wheel_1_TinR_uplim_set <= wheel_1_TinR_dwlim_set:
+            raise Exception(f'无效的前再生加热盘管边界条件{wheel_1_TinR_dwlim_set}~{wheel_1_TinR_uplim_set}')
+        if wheel_2_TinR_uplim_set <= wheel_2_TinR_dwlim_set:
+            raise Exception(f'无效的后再生加热盘管边界条件{wheel_2_TinR_dwlim_set}~{wheel_2_TinR_uplim_set}')
+        
         def get_limit(cur,up_set,dw_set,Val):
             adj_lim = False
             if Val <= 5:
@@ -315,19 +321,27 @@ def optimize_dhu(
         wheel_1_TinR_dwlim,wheel_1_TinR_uplim,wheel_1_TinR_adjlim = get_limit(wheel_1_TinR_cur,wheel_1_TinR_uplim_set,wheel_1_TinR_dwlim_set,heatingcoil_1_Val_cur)
         wheel_2_TinR_dwlim,wheel_2_TinR_uplim,wheel_2_TinR_adjlim = get_limit(wheel_2_TinR_cur,wheel_2_TinR_uplim_set,wheel_2_TinR_dwlim_set,heatingcoil_2_Val_cur)
     elif each_equp_type in ['SDHU_A','SDHU_B']:
-        wheel_1_TinR_uplim_set = config_reader.get_equp_info(each_eaup_name,key='前再生温度上限',info_type='float')
-        wheel_1_TinR_dwlim_set = config_reader.get_equp_info(each_eaup_name,key='前再生温度下限',info_type='float')
+        wheel_1_TinR_uplim_set = data_cur.loc[:,'wheel_1_TinR_UP'].iat[0]
+        wheel_1_TinR_dwlim_set = data_cur.loc[:,'wheel_1_TinR_DW'].iat[0]
 
     # 约束条件
     constrains = []
     if each_equp_type in ['DHU_A','DHU_B']:
+        enable_safe = True
         constrains.append('#送风露点约束# coil_3_DoutA-[coil_3_DoutA]<0')
         Tdup = config_reader.get_equp_info(each_eaup_name,key='前后再生差值上限',info_type='float')
         Tddw = config_reader.get_equp_info(each_eaup_name,key='前后再生差值下限',info_type='float')
         if not np.isnan(Tdup) and not wheel_1_TinR_adjlim and not wheel_2_TinR_adjlim:
             constrains.append(f'#前后再生差值上限# (wheel_1_TinR-wheel_2_TinR)-{Tdup}<0')
+            enable_safe = False
         if not np.isnan(Tddw) and not wheel_1_TinR_adjlim and not wheel_2_TinR_adjlim:
             constrains.append(f'#前后再生差值下限# {Tddw}-(wheel_1_TinR-wheel_2_TinR)<0')
+            enable_safe = False
+        if enable_safe:
+            # constrains.append('(wheel_1_TinR-[wheel_1_TinR])*(wheel_2_TinR-[wheel_2_TinR])+0.1<0')
+            # constrains.append('abs((wheel_1_TinR-[wheel_1_TinR])+(wheel_2_TinR-[wheel_2_TinR]))-10<0')
+            constrains.append('abs((wheel_1_TinR-[wheel_1_TinR]))-10<0')
+            constrains.append('abs((wheel_2_TinR-[wheel_2_TinR]))-10<0')
     elif each_equp_type in ['SDHU_A','SDHU_B']:
         constrains.append('#送风露点约束# wheel_1_DoutP-[wheel_1_DoutP]<0')
     print('约束条件')

+ 26 - 22
apps/DHU/train.py

@@ -49,7 +49,6 @@ def train(*inputs,config=None):
             )
         except Exception as E:
             ALL_RESULT['EXCEPTION']['Data'][each_eaup_name] = E
-            raise E
             continue
         
         # 训练模型 
@@ -202,7 +201,7 @@ def train_room_model(each_eaup_name,each_equp_type,equp_data,config_reader:Confi
     if not config_reader.get_app_info(each_eaup_name,'模型训练','训练房间模型','bool'):
         return None
     
-    N_fit = 24 * 60 * 3
+    N_fit = 24 * 60 * 7
     
     try:
         equp_model_path = f'{config_reader_path}/model/{each_eaup_name}.pkl'
@@ -216,39 +215,44 @@ def train_room_model(each_eaup_name,each_equp_type,equp_data,config_reader:Confi
             raise NotImplementedError
     except Exception as E:
         Dout = None
-        print(f'{each_eaup_name}设备模型加载失败,只训练基于实际送风露点的房间露点模型')
+        print(f'{each_eaup_name}设备模型加载失败,不选了基于除湿机模型的方法')
     
     # 实际送风露点
     if each_equp_type in ['DHU_A','DHU_B']:
-        Dout_real  = equp_data.iloc[-N_fit:,:].loc[:,'wheel_2_DoutP'].values
+        wheel_1_TinR = equp_data.iloc[-N_fit:,:].loc[:,'wheel_1_TinR'].values
+        wheel_2_TinR = equp_data.iloc[-N_fit:,:].loc[:,'wheel_2_TinR'].values
+        wheel_TinR   = (wheel_1_TinR+wheel_2_TinR)/2
+        Dout_real    = equp_data.iloc[-N_fit:,:].loc[:,'wheel_2_DoutP'].values
     elif each_equp_type in ['SDHU_A','SDHU_B']:
-        Dout_real  = equp_data.iloc[-N_fit:,:].loc[:,'coil_2_DoutA'].values
+        wheel_TinR = equp_data.iloc[-N_fit:,:].loc[:,'wheel_1_TinR'].values
+        Dout_real    = equp_data.iloc[-N_fit:,:].loc[:,'coil_2_DoutA'].values
     else:
         raise NotImplementedError
     
     N_room = config_reader.get_equp_info(each_eaup_name,'房间数量','int')
     
-    path_diffdata    = f'{config_reader_path}/plot/plot_room_diffdata/'
-    path_lagcorr     = f'{config_reader_path}/plot/plot_room_lagcorr/'
-    path_diffdata_bk = f'{config_reader_path}/plot/plot_room_diffdata_bk/'
-    path_lagcorr_bk  = f'{config_reader_path}/plot/plot_room_lagcorr_bk/'
-    Path(path_diffdata).mkdir(parents=True, exist_ok=True)
-    Path(path_lagcorr).mkdir(parents=True, exist_ok=True)
-    Path(path_diffdata_bk).mkdir(parents=True, exist_ok=True)
-    Path(path_lagcorr_bk).mkdir(parents=True, exist_ok=True)
+    path_lagcorr_DHU  = f'{config_reader_path}/plot/plot_room_lagcorr_DHU/'
+    path_lagcorr_Dout = f'{config_reader_path}/plot/plot_room_lagcorr_Dout/'
+    path_lagcorr_TinR = f'{config_reader_path}/plot/plot_room_lagcorr_TinR/'
+    Path(path_lagcorr_DHU).mkdir(parents=True, exist_ok=True)
+    Path(path_lagcorr_Dout).mkdir(parents=True, exist_ok=True)
+    Path(path_lagcorr_TinR).mkdir(parents=True, exist_ok=True)
     
     for i in range(1,N_room+1):
         Droom = equp_data.iloc[-N_fit:,:].loc[:,f'room_{i}_Dpv'].values
+        # 基于DHU模型
         if Dout is not None:
-            room_model = RoomDewPredictor().fit_Droom(Dout=Dout,Droom=Droom)
-            room_model.save(f'{config_reader_path}/model/{each_eaup_name}_room_{i}_Dpv.pkl')
-            room_model.plot_diffdata(Dout,Droom).save(filename=f'{path_diffdata}/{each_eaup_name}_room_{i}_Dpv.png')
-            room_model.plot_diffdata_lagcorr(Dout,Droom).save(filename=f'{path_lagcorr}/{each_eaup_name}_room_{i}_Dpv.png')
-        room_model_bk = RoomDewPredictor().fit_Droom(Dout=Dout_real,Droom=Droom)
-        room_model_bk.save(f'{config_reader_path}/model/{each_eaup_name}_room_{i}_Dpv_bk.pkl')
-        room_model_bk.plot_diffdata(Dout_real,Droom).save(filename=f'{path_diffdata_bk}/{each_eaup_name}_room_{i}_Dpv.png')
-        room_model_bk.plot_diffdata_lagcorr(Dout_real,Droom).save(filename=f'{path_lagcorr_bk}/{each_eaup_name}_room_{i}_Dpv.png')
-    
+            room_model_DHU = RoomDewPredictor(coef_is_pos=True).fit_Droom(Dout=Dout,Droom=Droom)
+            room_model_DHU.save(f'{config_reader_path}/model/{each_eaup_name}_room_{i}_Dpv_DHU.pkl')
+            room_model_DHU.plot_diffdata_lagcorr(Dout,Droom).save(filename=f'{path_lagcorr_DHU}/{each_eaup_name}_room_{i}_Dpv.png')
+        # 基于实际送风露点
+        room_model_Dout = RoomDewPredictor(coef_is_pos=True).fit_Droom(Dout=wheel_TinR,Droom=Droom)
+        room_model_Dout.save(f'{config_reader_path}/model/{each_eaup_name}_room_{i}_Dpv_Dout.pkl')
+        room_model_Dout.plot_diffdata_lagcorr(Dout_real,Droom).save(filename=f'{path_lagcorr_Dout}/{each_eaup_name}_room_{i}_Dpv.png')
+        # 基于再生加热
+        room_model_TinR = RoomDewPredictor(coef_is_pos=False).fit_Droom(Dout=wheel_TinR,Droom=Droom)
+        room_model_TinR.save(f'{config_reader_path}/model/{each_eaup_name}_room_{i}_Dpv_TinR.pkl')
+        room_model_TinR.plot_diffdata_lagcorr(wheel_TinR,Droom).save(filename=f'{path_lagcorr_TinR}/{each_eaup_name}_room_{i}_Dpv.png')
 def save_train_info(equp_model,equp_data,config_reader_path,each_eaup_name):
     for plot_name,plot in equp_model.plot_check(equp_data).items():
         path = f'{config_reader_path}/plot/{plot_name}/'

+ 58 - 51
apps/Room/predict.py

@@ -34,7 +34,7 @@ def predict(*inputs,config=None):
         
         NOW = config_reader.get_app_info(each_eaup_name,'露点预测',key='开始时间',info_type='datetime')
         
-        use_DHU_model = config_reader.get_app_info(each_eaup_name,'露点预测',key='基于除湿机模型',info_type='bool')
+        use_DHU_model = config_reader.get_app_info(each_eaup_name,'露点预测',key='模型方法',info_type='str')=='除湿机模型'
 
         dhu_State = load_dhu_State(
             each_eaup_name     = each_eaup_name,
@@ -72,27 +72,29 @@ def predict(*inputs,config=None):
             else:
                 raise NotImplementedError
         else:
-            print('基于实测送风露点进行预测')
-            point_B = config_reader.get_equp_point(each_eaup_name,equp_class=['B'])
-            point = config_reader.get_equp_point(each_eaup_name,equp_class=['C'])
+            print('基于再生加热温度进行预测')
+            room_model_name = 'TinR'
+            point_A = config_reader.get_equp_point(each_eaup_name,equp_class=['A'])
+            point   = config_reader.get_equp_point(each_eaup_name,equp_class=['C'])
             if each_equp_type in ['DHU_A','DHU_B']:
-                Dout_name        = 'wheel_2_DoutP'
-                point[Dout_name] = point_B[Dout_name]
+                TinR_name             = ['wheel_1_TinR','wheel_2_TinR']
+                point['wheel_1_TinR'] = point_A['wheel_1_TinR']
+                point['wheel_2_TinR'] = point_A['wheel_2_TinR']
             elif each_equp_type in ['SDHU_A','SDHU_B']:
-                Dout_name        = 'coil_2_DoutA'
-                point[Dout_name] = point_B[Dout_name]
+                TinR_name             = ['wheel_1_TinR']
+                point['wheel_1_TinR'] = point_A['wheel_1_TinR']
             else:
                 raise NotImplementedError
             equp_data = (
                 DataLoader(
                     path          = f'{config_reader_path}/data/room_predict/data_cur/',
-                    start_time    = NOW - timedelta(minutes=120),
+                    start_time    = NOW - timedelta(minutes=60),
                     end_time      = NOW,
                     print_process = False
                 )
                 .download_equp_data(
                     equp_name   = each_eaup_name,
-                    point       = config_reader.get_equp_point(each_eaup_name,equp_class=['B','C']),
+                    point       = point,
                     url         = data_URL,
                     clean_cache = True
                 )
@@ -100,51 +102,56 @@ def predict(*inputs,config=None):
                     equp_name = each_eaup_name,
                 )
             )
-            Dout = equp_data.loc[:,Dout_name].values
+            equp_data_sm = equp_data.rolling(10,min_periods=0).mean()
+            wheel_TinR = equp_data_sm.loc[:,TinR_name].mean(axis=1).values
         room_SP_point = config_reader.get_equp_point(equp_name = each_eaup_name,equp_class = ['C','D'])
         
         for each_room_num in range(1,config_reader.get_equp_info(each_eaup_name,'房间数量','int') + 1):
-            Droom_cur = equp_data.loc[:,f'room_{each_room_num}_Dpv'].values[-1]
-            if use_DHU_model:
-                room_model = RoomDewPredictor.load(f'{config_reader_path}/model/{each_eaup_name}_room_{each_room_num}_Dpv.pkl')
-                Droom_pred = room_model.predict_Droom(Dout=Dout_pred,Droom_cur=Droom_cur)
-            else:
-                room_model = RoomDewPredictor.load(f'{config_reader_path}/model/{each_eaup_name}_room_{each_room_num}_Dpv_bk.pkl')
-                Droom_pred = room_model.predict_Droom(Dout=Dout,Droom_cur=Droom_cur)
-            pred_lag = room_model.model_info['model_Droom']['lag']
-            
-            print(f'{each_eaup_name}房间{each_room_num}在{NOW}的预测值')
-            print(f'完整预测时序:{Droom_pred}')
-            index      = pd.Index(
-                pd.date_range(
-                    start = NOW+timedelta(minutes=1),
-                    end   = NOW+timedelta(minutes=1)+timedelta(minutes=int(pred_lag)),
-                    freq  = '1min'
+            try:
+                Droom_cur = equp_data.loc[:,f'room_{each_room_num}_Dpv'].values[-1]
+                if use_DHU_model:
+                    room_model = RoomDewPredictor.load(f'{config_reader_path}/model/{each_eaup_name}_room_{each_room_num}_Dpv.pkl')
+                    Droom_pred = room_model.predict_Droom(Dout=Dout_pred,Droom_cur=Droom_cur)
+                else:
+                    room_model = RoomDewPredictor.load(f'{config_reader_path}/model/{each_eaup_name}_room_{each_room_num}_Dpv_{room_model_name}.pkl')
+                    Droom_pred = room_model.predict_Droom(Dout=wheel_TinR,Droom_cur=Droom_cur,sm_frac=0.5)
+                pred_lag = room_model.model_info['model_Droom']['lag']
+                
+                print(f'{each_eaup_name}房间{each_room_num}在{NOW}的预测值')
+                print(f'完整预测时序:{Droom_pred}')
+                index      = pd.Index(
+                    pd.date_range(
+                        start = NOW + timedelta(minutes=1),
+                        end   = NOW + timedelta(minutes=1)+timedelta(minutes=int(pred_lag)),
+                        freq  = '1min'
+                    )
                 )
-            )
-            # 完整时序预测
-            write_ts_predict(
-                point_id = f"{each_eaup_name}_{room_SP_point[f'room_{each_room_num}_Dpv']}",
-                value    = Droom_pred,
-                ts       = index
-            )
-            if config_reader.get_equp_info(each_eaup_name,'房间露点预测用当前值',info_type='bool'):
-                print(f'{each_eaup_name}房间{each_room_num}仅输出当前露点')
-                Droom_pred_last = Droom_cur
-                pred_lag = 1
-            else:
-                Droom_pred_last = Droom_pred[-1]
-            print(f'{pred_lag}分钟后的预测值为{Droom_pred_last}')
-            # 最远预测点
-            write_lag_predict(
-                point_id = f"{each_eaup_name}_{room_SP_point[f'room_{each_room_num}_Dpd']}",
-                value    = Droom_pred_last
-            )
-            # 最远预测步长
-            write_lag_predict(
-                point_id = f"{each_eaup_name}_{room_SP_point[f'room_{each_room_num}_Dpdlag']}",
-                value    = pred_lag
-            )
+                # 完整时序预测
+                write_ts_predict(
+                    point_id = f"{each_eaup_name}_{room_SP_point[f'room_{each_room_num}_Dpv']}",
+                    value    = Droom_pred,
+                    ts       = index
+                )
+                if config_reader.get_equp_info(each_eaup_name,'房间露点预测用当前值',info_type='bool') or len(Droom_pred)==0:
+                    print(f'{each_eaup_name}房间{each_room_num}仅输出当前露点')
+                    Droom_pred_last = Droom_cur
+                    pred_lag = 1
+                else:
+                    Droom_pred_last = Droom_pred[-1]
+                print(f'{pred_lag}分钟后的预测值为{Droom_pred_last}')
+                # 最远预测点
+                write_lag_predict(
+                    point_id = f"{each_eaup_name}_{room_SP_point[f'room_{each_room_num}_Dpd']}",
+                    value    = Droom_pred_last
+                )
+                # 最远预测步长
+                write_lag_predict(
+                    point_id = f"{each_eaup_name}_{room_SP_point[f'room_{each_room_num}_Dpdlag']}",
+                    value    = pred_lag
+                )
+            except Exception as e:
+                print(e)
+                continue
             
 
 def load_data(

+ 50 - 9
model/Room/room.py

@@ -11,14 +11,16 @@ except:
 from .._base._base import BaseModel
 
 class RoomDewPredictor(BaseModel):
-    def __init__(self):
+    def __init__(self,coef_is_pos:bool):
         super().__init__()
+        self.coef_is_pos = coef_is_pos
+        self.record_load_info(coef_is_pos = self.coef_is_pos)
     
     def fit_Droom(self,Dout:np.ndarray, Droom:np.ndarray):
-        _,_,best_lag,best_coef = self.get_lag_coef(Dout=Dout,Droom=Droom)
+        _,_,best_lag,best_coef,boundary = self.get_lag_coef(Dout=Dout,Droom=Droom)
         self.record_model(
             model_name   = 'Droom',
-            model        = {'coef':best_coef,'lag':best_lag},
+            model        = {'coef':best_coef,'lag':best_lag,'boundary':boundary},
             train_data   = {'Dout':Dout,'Droom':Droom},
             train_metric = {'R2':1,'MAE':1,'MAPE':1},
         )
@@ -34,13 +36,33 @@ class RoomDewPredictor(BaseModel):
         model      = self.model_info['model_Droom']
         lag        = model['lag']
         coef       = model['coef']
+        boundary   = model['boundary']
         Dout       = Dout[-lag-1:]
         Dout_diff  = np.diff(Dout)
-        Droom_diff = Dout_diff * coef
+        Droom_diff = np.clip(Dout_diff * coef,boundary[0],boundary[1])
         Droom      = smooth(Droom_diff.cumsum() + Droom_cur,sm_frac)
         return Droom
     
-    @classmethod
+    def plot_predict_Droom(self,Dout:np.ndarray,Droom_real:np.ndarray,sm_frac=0) -> np.ndarray:
+        all_pred = {}
+        for i in range(self.lag+1,len(Dout)-self.lag):
+            Droom_cur = Droom_real[i]
+            pred      = self.predict_Droom(Dout=Dout[:i],Droom_cur=Droom_cur,sm_frac=sm_frac)
+            all_pred[f'step_{i}'] = pd.Series(pred,index=np.arange(i+1,i+self.lag+1))
+        all_pred = pd.concat(all_pred,axis=1)
+        plot = (
+            all_pred
+            .assign(D=Droom_real[self.lag+2:])
+            .reset_index()
+            .melt(id_vars=['index','D'])
+            .pipe(gg.ggplot)
+            + gg.aes(x='index',y='value',color='variable')
+            + gg.geom_line()
+            + gg.geom_line(gg.aes(x='index',y='D'),color='black')
+            + gg.theme(legend_position='none')
+        )
+        return plot
+    
     def to_diffdata(
         self,
         Dout : np.ndarray,
@@ -88,7 +110,7 @@ class RoomDewPredictor(BaseModel):
     
     def plot_diffdata_lagcorr(self,Dout:np.ndarray, Droom:np.ndarray):
         
-        all_coef,all_r2,best_lag,best_coef = self.get_lag_coef(Dout=Dout,Droom=Droom)
+        all_coef,all_r2,best_lag,_,_ = self.get_lag_coef(Dout=Dout,Droom=Droom)
         data = pd.DataFrame({'coef':all_coef,'r2':all_r2,'lag':np.arange(1,30)})
         p1 = (
             data
@@ -97,6 +119,7 @@ class RoomDewPredictor(BaseModel):
             + gg.geom_point()
             + gg.geom_line()
             + gg.geom_vline(xintercept=best_lag,color='red',linetype='--')
+            + gg.geom_hline(yintercept=0,color='blue',linetype='--')
             + gg.scale_x_continuous(breaks=np.arange(1,30))
         )
         p2 = (
@@ -106,6 +129,7 @@ class RoomDewPredictor(BaseModel):
             + gg.geom_point()
             + gg.geom_line()
             + gg.geom_vline(xintercept=best_lag,color='red',linetype='--')
+            + gg.geom_hline(yintercept=0.5,color='blue',linetype='--')
             + gg.scale_x_continuous(breaks=np.arange(1,30))
             + gg.scale_y_continuous(breaks=np.arange(0,1,0.2))
         )
@@ -130,6 +154,8 @@ class RoomDewPredictor(BaseModel):
     
     def get_lag_coef(self,Dout:np.ndarray, Droom:np.ndarray):
         diffdata = self.to_diffdata(Dout=Dout,Droom=Droom)
+        boundary = diffdata.loc[:,'Droom'].quantile(q=[0.01,0.99])
+        boundary = (boundary.iat[0],boundary.iat[1])
         all_lag  = np.arange(1,30)
         all_coef = []
         all_r2   = []
@@ -145,9 +171,24 @@ class RoomDewPredictor(BaseModel):
             all_r2.append(r2)
         all_coef = np.array(all_coef)
         all_r2   = np.array(all_r2)
-        best_lag = all_lag[all_r2.argmax()]
-        best_coef = all_coef[all_r2.argmax()]
-        return all_coef,all_r2,best_lag,best_coef
+        
+        is_right_coef = all_coef > 0 if self.coef_is_pos else all_coef < 0
+        right_lag     = all_lag[is_right_coef]
+        right_coef    = all_coef[is_right_coef]
+        
+        if np.sum(is_right_coef) == 0:
+            best_lag  = 0
+            best_coef = 0
+        else:
+            best_lag   = right_lag[all_r2[is_right_coef].argmax()]
+            best_coef  = right_coef[all_r2[is_right_coef].argmax()]
+        return (
+            all_coef,
+            all_r2,
+            best_lag,
+            best_coef,
+            boundary
+        )
     
 def smooth(y:pd.Series,frac=0.1):
     import statsmodels.api as sm