zhangshenhao vor 7 Monaten
Ursprung
Commit
9878a8c549

+ 0 - 0
apps/monitor/monitor.py


+ 69 - 0
apps/optimize/optimize.py

@@ -0,0 +1,69 @@
+import os
+from datetime import datetime
+from pathlib import Path
+
+import pandas as pd
+
+from ...model.DHU.DHU_A import DHU_A
+from ...model.DHU.DHU_B import DHU_B
+from ...tools.config_reader import ConfigReader
+from ...tools.data_loader import DataLoader
+
+
+def optimize(*inputs,config=None):
+    config = {} if config is None else config
+    
+    if '__LOCAL' in config.keys():
+        config_reader_path = config['__LOCAL']
+        data_URL           = config['__URL']
+    else:
+        config_reader_path = '/mnt/workflow_data'
+        data_URL           = 'http://basedataportal-svc:8080/data/getpointsdata'
+        
+    config_reader = ConfigReader(path=f'{config_reader_path}/DHU_A配置.xlsx')
+    if config_reader.meta['设备类型'] == 'DHU_A':
+        MODEL = DHU_A
+    elif config_reader.meta['设备类型'] == 'DHU_B':
+        MODEL = DHU_B
+    else:
+        raise NotImplementedError(config_reader.meta['设备类型'])
+    
+    ALL_RESULT = {
+        'EXCEPTION':{
+            'Data': {},
+            'Fit' : {},
+            'Save': {}
+        }
+    }
+    
+    for each_eaup_name in config_reader.all_equp_names:
+        # 加载数据
+        # 运行判断
+        #   稳态判断:房间露点设定值与反馈值是否接近
+        #   模型判断:模型精度是否满足要求
+        #   模式判断:
+        #       1. 基于当前露点优化模式:基于房间露点设定值减偏差
+        #       2. 快速提升送风露点模式:约束送风露点保持不变
+        
+        
+        
+        
+        # 获取数据
+        try:
+            data_loader = DataLoader(
+                path       = f'{config_reader_path}/data/train/data_his/',
+                start_time = config_reader.get_app_info(each_eaup_name,app_type='模型训练',key='开始时间',info_type='datetime'),
+                end_time   = config_reader.get_app_info(each_eaup_name,app_type='模型训练',key='结束时间',info_type='datetime')
+            )
+            data_loader.dowload_equp_data(
+                equp_name   = each_eaup_name,
+                point       = config_reader.get_equp_point(each_eaup_name,equp_class=['A','B']),
+                url         = data_URL,
+                clean_cache = False
+            )
+            equp_data = data_loader.get_equp_data(each_eaup_name)
+            equp_data = clean_data(equp_data)
+            save_data(f'{config_reader_path}/data/train/data_his_clean',f'{each_eaup_name}.pkl',equp_data)
+        except Exception as E:
+            ALL_RESULT['EXCEPTION']['Data'][each_eaup_name] = E
+            continue

+ 131 - 0
apps/train/train.py

@@ -0,0 +1,131 @@
+import os
+from datetime import datetime
+from pathlib import Path
+
+import pandas as pd
+
+from ...model.DHU.DHU_A import DHU_A
+from ...model.DHU.DHU_B import DHU_B
+from ...tools.config_reader import ConfigReader
+from ...tools.data_loader import DataLoader
+
+NOW             = datetime.now()
+PATH            = os.path.dirname(os.path.realpath(__file__)).replace('\\','/')
+MODEL_FUNC_PATH = f'{PATH}/model_func.py'
+MODEL_FILE_PATH = f'./model.pkl'
+
+def train(*inputs,config=None):
+    config = {} if config is None else config
+    
+    if '__LOCAL' in config.keys():
+        config_reader_path = config['__LOCAL']
+        data_URL           = config['__URL']
+    else:
+        config_reader_path = '/mnt/workflow_data'
+        data_URL           = 'http://basedataportal-svc:8080/data/getpointsdata'
+        
+    config_reader = ConfigReader(path=f'{config_reader_path}/DHU_A配置.xlsx')
+    if config_reader.meta['设备类型'] == 'DHU_A':
+        MODEL = DHU_A
+    elif config_reader.meta['设备类型'] == 'DHU_B':
+        MODEL = DHU_B
+    else:
+        raise NotImplementedError(config_reader.meta['设备类型'])
+    
+    ALL_RESULT = {
+        'EXCEPTION':{
+            'Data': {},
+            'Fit' : {},
+            'Save': {}
+        }
+    }
+    
+    for each_eaup_name in config_reader.all_equp_names:
+        
+        # 获取数据
+        try:
+            data_loader = DataLoader(
+                path       = f'{config_reader_path}/data/train/data_his/',
+                start_time = config_reader.get_app_info(each_eaup_name,app_type='模型训练',key='开始时间',info_type='datetime'),
+                end_time   = config_reader.get_app_info(each_eaup_name,app_type='模型训练',key='结束时间',info_type='datetime')
+            )
+            data_loader.dowload_equp_data(
+                equp_name   = each_eaup_name,
+                point       = config_reader.get_equp_point(each_eaup_name,equp_class=['A','B']),
+                url         = data_URL,
+                clean_cache = False
+            )
+            equp_data = data_loader.get_equp_data(each_eaup_name)
+            equp_data = clean_data(equp_data)
+            save_data(f'{config_reader_path}/data/train/data_his_clean',f'{each_eaup_name}.pkl',equp_data)
+        except Exception as E:
+            ALL_RESULT['EXCEPTION']['Data'][each_eaup_name] = E
+            continue
+        
+        # 训练模型 
+        try:
+            equp_model:DHU_A = MODEL()
+            equp_model.fit(
+                input_data    = equp_data,
+                observed_data = equp_data,
+                plot_TVP      = False,
+                rw_FA_val     = True, #TODO
+                exist_Fa_H    = config_reader.get_equp_info(each_eaup_name,'存在回风口','bool'),
+                exist_Fa_B    = config_reader.get_equp_info(each_eaup_name,'存在补风口','bool'),
+            )
+            Path(f'{config_reader_path}/data/train/model').mkdir(parents=True, exist_ok=True)
+            equp_model.save(f'{config_reader_path}/data/train/model/{each_eaup_name}.pkl')
+            save_data(f'{config_reader_path}/data/train/data_TVP',f'{each_eaup_name}.csv',equp_model.TVP_data)
+            save_data(f'{config_reader_path}/data/train/data_metric',f'{each_eaup_name}.csv',equp_model.TVP_metric)
+        except Exception as E:
+            ALL_RESULT['EXCEPTION']['Fit'][each_eaup_name] = E
+            continue
+        
+        # 模型迭代
+        if not config_reader.get_app_info(each_eaup_name,'模型训练','保存模型','bool'):
+            continue
+        try:
+            monitor_point = config_reader.point.loc[lambda dt:dt.类型=='B']
+            model_update_info = {}
+            for i in range(len(monitor_point)):
+                name    = monitor_point.loc[:,'编号'].iat[i]
+                name_cn = monitor_point.loc[:,'名称'].iat[i]
+                MAE     = monitor_point.loc[:,'指标MAE'].iat[i]
+                model_update_info[name] = {
+                    'point_id'   : name,
+                    'point_name' : name_cn,
+                    'point_class': name,
+                    'thre_mae'   : MAE,
+                    'thre_mape'  : 1,
+                    'thre_days'  : 7
+                }
+            equp_model.save_to_platform(
+                version_id      = datetime.now().strftime('%Y%m'),
+                model_id        = config_reader.get_equp_info(each_eaup_name,'模型编号','str'),
+                update_method   = 'update',
+                model_info      = model_update_info,
+                MODEL_FILE_PATH = MODEL_FILE_PATH,
+                MODEL_FUNC_PATH = MODEL_FUNC_PATH,
+            )
+        except Exception as E:
+            ALL_RESULT['EXCEPTION']['Save'][each_eaup_name] = E
+            continue
+    
+    print(ALL_RESULT)
+
+def clean_data(data) -> pd.DataFrame:
+    data = (
+        data
+        .resample('60min')
+        .mean()
+    )
+    return data
+
+def save_data(dir,file:str,data:pd.DataFrame):
+    Path(dir).mkdir(parents=True,exist_ok=True)
+    if file.endswith('.csv'):
+        data.to_csv(os.path.join(dir,file),index=True)
+    elif file.endswith('.pkl'):
+        data.to_pickle(os.path.join(dir,file))
+    else:
+        raise Exception('file type error')

+ 70 - 26
components/wheel.py → components/wheel2.py

@@ -6,7 +6,7 @@ except:
 
 from ._base_components import BaseComponents
 from ..tools.enthalpy import get_RH_from_Tdb_and_Hr
-from ..tools.enthalpy import get_Dew_from_HumRatio
+from ..tools.enthalpy import get_Dew_from_HumRatio,get_HumRatio_from_Tdb_and_RH
 
 
 class WheelS2(BaseComponents):
@@ -68,8 +68,7 @@ class WheelS2(BaseComponents):
         }
         return param
 
-
-class WheelS3(BaseComponents):
+class WheelS2V2(BaseComponents):
     def __init__(self, name):
         super().__init__(name)
     
@@ -78,7 +77,6 @@ class WheelS3(BaseComponents):
         cls,
         TinP,HinP,FP,
         TinR,HinR,FR,
-        TinC,HinC,FC,
         engine  : str,
         param   : dict,
     ):
@@ -90,13 +88,8 @@ class WheelS3(BaseComponents):
         beta_P3 = param['beta_P3']
         beta_P4 = param['beta_P4']
         beta_P5 = param['beta_P5']
-        beta_C1 = param['beta_C1']
-        beta_C2 = param['beta_C2']
-        beta_C3 = param['beta_C3']
-        beta_C4 = param['beta_C4']
         
         RinP = get_RH_from_Tdb_and_Hr(TinP,HinP,engine)
-        RinC = get_RH_from_Tdb_and_Hr(TinC,HinC,engine)
         
         # 处理侧
         HdiffP  = (beta_P1 * RinP**beta_P4 * HinP * TinR * EXP(-beta_P5 * FP) + beta_P2)/1000
@@ -108,27 +101,16 @@ class WheelS3(BaseComponents):
         TdiffP  = (Q_lat_P + Q_sen_P) / (FP * cls.CONSTANT['c_p_air'])
         ToutP   = TinP + TdiffP
         
-        # 冷却侧
-        TdiffC    = beta_C1 * EXP(-beta_C2 * EXP(-beta_C3 * (TinR - TinC))) * EXP(-beta_C4 * FC)
-        ToutC     = TinC + TdiffC
-        HdiffC    = (beta_P1 * RinC**beta_P4 * HinC * TinR * EXP(-beta_P5 * FC) + beta_P2)/1000
-        WdiffC    = HdiffC * FC
-        HoutC     = HinC - HdiffC
-        DoutC     = get_Dew_from_HumRatio(HoutC,engine)
-        Q_total_C = TdiffC * FC * cls.CONSTANT['c_p_air']
-        
         # 再生侧
-        WdiffR    = WdiffP + WdiffC
-        HoutR     = (HinR * FR + WdiffR) / FR
+        HoutR     = (HinR * FR + WdiffP) / FR
         DoutR     = get_Dew_from_HumRatio(HoutR,engine)
-        Q_total_R = Q_lat_P + Q_sen_P + Q_total_C
+        Q_total_R = Q_lat_P + Q_sen_P
         TdiffR    = Q_total_R / (FR * cls.CONSTANT['c_p_air'])
         ToutR     = TinR - TdiffR
         
         return {
             'ToutP':ToutP,'HoutP':HoutP,'DoutP':DoutP,'FP':FP,
             'ToutR':ToutR,'HoutR':HoutR,'DoutR':DoutR,'FR':FR,
-            'ToutC':ToutC,'HoutC':HoutC,'DoutC':DoutC,'FC':FC,
         } 
         
     def prior(self):
@@ -138,9 +120,71 @@ class WheelS3(BaseComponents):
             'beta_P3': pm.TruncatedNormal(f'{self.name}_beta_P3',mu=1,sigma=2,initval=1.5,lower=0),
             'beta_P4': pm.TruncatedNormal(f'{self.name}_beta_P4',mu=1,sigma=0.3,initval=1,lower=0),
             'beta_P5': pm.TruncatedNormal(f'{self.name}_beta_P5',mu=5,sigma=2,initval=5,lower=0),
-            'beta_C1': pm.TruncatedNormal(f'{self.name}_beta_C1',mu=60,sigma=10,initval=60,lower=10),
-            'beta_C2': pm.TruncatedNormal(f'{self.name}_beta_C2',mu=30,sigma=10,initval=30,lower=1),
-            'beta_C3': pm.TruncatedNormal(f'{self.name}_beta_C3',mu=0.05,sigma=0.1,initval=0.05,lower=0),
-            'beta_C4': pm.TruncatedNormal(f'{self.name}_beta_C4',mu=1,sigma=1,initval=1,lower=0),
         }
         return param
+
+class WheelS2V3(BaseComponents):
+    def __init__(self, name):
+        super().__init__(name)
+    
+    @classmethod
+    def model(
+        cls,
+        TinP,HinP,FP,
+        TinR,HinR,FR,
+        engine  : str,
+        param   : dict,
+    ):
+        FUNC = cls.get_func_by_engine(engine)
+        EXP  = FUNC['EXP']
+        
+        beta_P1 = param['beta_P1'] # km
+        beta_P2 = param['beta_P2'] # eta_max
+        beta_P3 = param['beta_P3']
+        beta_P4 = param['beta_P4']
+        beta_P5 = param['beta_P5']
+        beta_C1 = param['beta_C1']
+        beta_C2 = param['beta_C2']
+        
+        # 处理侧 
+        # 湿度
+        alpha   = FR / (FR + FP)
+        T_wheel = TinR * alpha + TinP * (1 - alpha) + beta_P3
+        Hr_eq   = get_HumRatio_from_Tdb_and_RH(T_wheel,1,engine)
+        eta     = beta_P2 * (1-EXP(-beta_P1 * (HinP-Hr_eq)/Hr_eq))
+        HdiffP  = eta * (HinP - Hr_eq)
+        HoutP   = HinP - HdiffP
+        DoutP   = get_Dew_from_HumRatio(HoutP,engine)
+        # 温度
+        TdiffP_sen = beta_P4 * (T_wheel - TinP)
+        TdiffP_lat = cls.CONSTANT['h_ads'] / cls.CONSTANT['c_p_air'] * HdiffP * beta_P5
+        ToutP      = TinP + TdiffP_sen + TdiffP_lat
+        
+        # 再生侧
+        # 湿度
+        HdiffR = HdiffP * FP / FR
+        HoutR  = HinR + HdiffR
+        DoutR  = get_Dew_from_HumRatio(HoutR,engine)
+        # 温度
+        TdiffR_lat = cls.CONSTANT['h_ads'] / cls.CONSTANT['c_p_air'] * HdiffR * beta_C2
+        TdiffR_sen = beta_C1 * (TinR - T_wheel)
+        ToutR      = TinR - TdiffR_sen - TdiffR_lat
+        
+        return {
+            'ToutP':ToutP,'HoutP':HoutP,'DoutP':DoutP,'FP':FP,
+            'ToutR':ToutR,'HoutR':HoutR,'DoutR':DoutR,'FR':FR,
+            'T_wheel':T_wheel
+        } 
+        
+    def prior(self):
+        param = {
+            'beta_P1': pm.HalfNormal(f'{self.name}_beta_P1',sigma=10,initval=5),
+            'beta_P2': pm.Uniform(f'{self.name}_beta_P2',lower=0,upper=1,initval=0.6),
+            'beta_P3': pm.Normal(f'{self.name}_beta_P3',mu=0,sigma=2,initval=0),
+            'beta_P4': pm.HalfNormal(f'{self.name}_beta_P4', sigma=10,initval=1),
+            'beta_P5': pm.TruncatedNormal(f'{self.name}_beta_P5', mu=1, sigma=1,initval=1,lower=0),
+            'beta_C1': pm.HalfNormal(f'{self.name}_beta_C1', sigma=10,initval=1),
+            'beta_C2': pm.HalfNormal(f'{self.name}_beta_C2', sigma=1,initval=1),
+        }
+        return param
+

+ 279 - 0
components/wheel3.py

@@ -0,0 +1,279 @@
+import numpy as np
+try:
+    import pymc as pm
+except:
+    pass
+
+from ._base_components import BaseComponents
+from ..tools.enthalpy import (
+    get_Dew_from_HumRatio,
+    get_HumRatio_from_Tdb_and_RH,
+    get_RH_from_Tdb_and_Hr,
+)
+
+
+class WheelS3(BaseComponents):
+    def __init__(self, name):
+        super().__init__(name)
+    
+    @classmethod
+    def model(
+        cls,
+        TinP,HinP,FP,
+        TinR,HinR,FR,
+        TinC,HinC,FC,
+        engine  : str,
+        param   : dict,
+    ):
+        FUNC = cls.get_func_by_engine(engine)
+        EXP  = FUNC['EXP']
+        
+        beta_P1 = param['beta_P1']
+        beta_P2 = param['beta_P2']
+        beta_P3 = param['beta_P3']
+        beta_P4 = param['beta_P4']
+        beta_P5 = param['beta_P5']
+        beta_P6 = param['beta_P6']
+        beta_P7 = param['beta_P7']
+        beta_C1 = param['beta_C1']
+        beta_C2 = param['beta_C2']
+        beta_C3 = param['beta_C3']
+        beta_C4 = param['beta_C4']
+        
+        RinP = get_RH_from_Tdb_and_Hr(TinP,HinP,engine)
+        # RinC = get_RH_from_Tdb_and_Hr(TinC,HinC,engine)
+        
+        # 处理侧
+        HdiffP  = (
+            beta_P1 * RinP ** beta_P4  
+            * HinP 
+            * (TinR + beta_P6 * TinC + beta_P7 * TinP) 
+            * EXP(-beta_P5 * FP) 
+            + beta_P2 
+        ) / 1000
+        WdiffP  = HdiffP * FP
+        HoutP   = HinP - HdiffP
+        DoutP   = get_Dew_from_HumRatio(HoutP,engine)
+        Q_lat_P = WdiffP * cls.CONSTANT['h_ads']
+        Q_sen_P = beta_P3 * (TinR - TinP) * FP
+        TdiffP  = (Q_lat_P + Q_sen_P) / (FP * cls.CONSTANT['c_p_air'])
+        ToutP   = TinP + TdiffP
+        
+        # 冷却侧
+        TdiffC    = beta_C1 * EXP(-beta_C2 * EXP(-beta_C3 * (TinR - TinC))) * EXP(-beta_C4 * FC)
+        ToutC     = TinC + TdiffC
+        # HdiffC    = (beta_P1 * RinC**beta_P4 * HinC * TinR * EXP(-beta_P5 * FC) + beta_P2)/1000
+        HdiffC = 0
+        WdiffC    = HdiffC * FC
+        HoutC     = HinC - HdiffC
+        DoutC     = get_Dew_from_HumRatio(HoutC,engine)
+        Q_total_C = TdiffC * FC * cls.CONSTANT['c_p_air']
+        
+        # 再生侧
+        WdiffR    = WdiffP + WdiffC
+        HoutR     = (HinR * FR + WdiffR) / FR
+        DoutR     = get_Dew_from_HumRatio(HoutR,engine)
+        Q_total_R = Q_lat_P + Q_sen_P + Q_total_C
+        TdiffR    = Q_total_R / (FR * cls.CONSTANT['c_p_air'])
+        ToutR     = TinR - TdiffR
+        
+        return {
+            'ToutP':ToutP,'HoutP':HoutP,'DoutP':DoutP,'FP':FP,
+            'ToutR':ToutR,'HoutR':HoutR,'DoutR':DoutR,'FR':FR,
+            'ToutC':ToutC,'HoutC':HoutC,'DoutC':DoutC,'FC':FC,
+        } 
+        
+    def prior(self):
+        param = {
+            'beta_P1': pm.TruncatedNormal(f'{self.name}_beta_P1',mu=5,sigma=10,initval=5,lower=0),
+            'beta_P2': pm.Normal(f'{self.name}_beta_P2',sigma=1,initval=0),
+            'beta_P3': pm.TruncatedNormal(f'{self.name}_beta_P3',mu=1,sigma=2,initval=1.5,lower=0),
+            'beta_P4': pm.HalfNormal(f'{self.name}_beta_P4',sigma=1,initval=0.1),
+            'beta_P5': pm.TruncatedNormal(f'{self.name}_beta_P5',mu=5,sigma=2,initval=5,lower=0),
+            'beta_P6': pm.Normal(f'{self.name}_beta_P6',mu=0,sigma=1,initval=0),
+            'beta_P7': pm.Normal(f'{self.name}_beta_P7',mu=0,sigma=1,initval=0),
+            'beta_C1': pm.TruncatedNormal(f'{self.name}_beta_C1',mu=60,sigma=10,initval=60,lower=10),
+            'beta_C2': pm.TruncatedNormal(f'{self.name}_beta_C2',mu=30,sigma=10,initval=30,lower=1),
+            'beta_C3': pm.TruncatedNormal(f'{self.name}_beta_C3',mu=0.05,sigma=0.1,initval=0.05,lower=0),
+            'beta_C4': pm.TruncatedNormal(f'{self.name}_beta_C4',mu=1,sigma=1,initval=1,lower=0),
+        }
+        return param
+
+class WheelS3V2(BaseComponents):
+    def __init__(self, name):
+        super().__init__(name)
+    
+    @classmethod
+    def model(
+        cls,
+        TinP,HinP,FP,
+        TinR,HinR,FR,
+        TinC,HinC,FC,
+        engine  : str,
+        param   : dict,
+    ):
+        FUNC = cls.get_func_by_engine(engine)
+        EXP  = FUNC['EXP']
+        
+        K0 = param['K0']
+        eta = param['eta']
+        beta_P1 = param['beta_P1']
+        beta_P2 = param['beta_P2']
+        beta_P3 = param['beta_P3']
+        beta_P1a = param['beta_P1a']
+        beta_P2a = param['beta_P2a']
+        beta_P3a = param['beta_P3a']
+        beta_P4 = param['beta_P4']
+        beta_C1 = param['beta_C1']
+        beta_C2 = param['beta_C2']
+        beta_C3 = param['beta_C3']
+        beta_C4 = param['beta_C4']
+        
+        RinP = get_RH_from_Tdb_and_Hr(TinP,HinP,engine)
+        T_wheel_P = (
+            beta_P1a * TinP * FP ** beta_P1 
+            + beta_P2a * TinC * FC ** beta_P2
+            + beta_P3a * TinR * FR ** beta_P3
+        ) 
+        E = -45
+        R = 8.314
+        N = 0.45
+        H_eq    = K0 * EXP(-E / (R * (T_wheel_P + 273.15))) * RinP ** N # 处理侧平衡状态下转轮能够吸附的水
+        HoutP   = HinP - eta * (H_eq - HinP) * (1 - EXP(-beta_P4 * FP))
+        DoutP   = get_Dew_from_HumRatio(HoutP,engine)
+        WdiffP  = (HinP - HoutP) * FP
+        Q_lat_P = WdiffP * cls.CONSTANT['h_ads']
+        Q_sen_P = beta_P3 * (TinR - TinP) * FP 
+        TdiffP  = (Q_lat_P + Q_sen_P) / (FP * cls.CONSTANT['c_p_air'])
+        ToutP   = TinP + TdiffP
+        
+        # 冷却侧
+        TdiffC    = beta_C1 * EXP(-beta_C2 * EXP(-beta_C3 * (TinR - TinC))) * EXP(-beta_C4 * FC)
+        ToutC     = TinC + TdiffC
+        HdiffC    = 0
+        WdiffC    = HdiffC * FC
+        HoutC     = HinC - HdiffC
+        DoutC     = get_Dew_from_HumRatio(HoutC,engine)
+        Q_total_C = TdiffC * FC * cls.CONSTANT['c_p_air']
+        
+        # 再生侧
+        WdiffR    = WdiffP + WdiffC
+        HoutR     = (HinR * FR + WdiffR) / FR
+        DoutR     = get_Dew_from_HumRatio(HoutR,engine)
+        Q_total_R = Q_lat_P + Q_sen_P + Q_total_C
+        TdiffR    = Q_total_R / (FR * cls.CONSTANT['c_p_air'])
+        ToutR     = TinR - TdiffR
+        
+        return {
+            'ToutP':ToutP,'HoutP':HoutP,'DoutP':DoutP,'FP':FP,
+            'ToutR':ToutR,'HoutR':HoutR,'DoutR':DoutR,'FR':FR,
+            'ToutC':ToutC,'HoutC':HoutC,'DoutC':DoutC,'FC':FC,
+        } 
+        
+    def prior(self):
+        param = {
+            'beta_P1': pm.Normal(f'{self.name}_beta_P1',mu=0,sigma=1,initval=0),
+            'beta_P2': pm.Normal(f'{self.name}_beta_P2',mu=0,sigma=1,initval=0),
+            'beta_P3': pm.Normal(f'{self.name}_beta_P3',mu=0,sigma=1,initval=0),
+            'beta_P1a': pm.Normal(f'{self.name}_beta_P1a',mu=1,sigma=1,initval=1),
+            'beta_P2a': pm.Normal(f'{self.name}_beta_P2a',mu=1,sigma=1,initval=1),
+            'beta_P3a': pm.Normal(f'{self.name}_beta_P3a',mu=1,sigma=1,initval=1),
+            'beta_P4': pm.TruncatedNormal(f'{self.name}_beta_P4',mu=5,sigma=3,initval=5,lower=0),
+            'beta_C1': pm.TruncatedNormal(f'{self.name}_beta_C1',mu=60,sigma=10,initval=60,lower=10),
+            'beta_C2': pm.TruncatedNormal(f'{self.name}_beta_C2',mu=30,sigma=10,initval=30,lower=1),
+            'beta_C3': pm.TruncatedNormal(f'{self.name}_beta_C3',mu=0.05,sigma=0.1,initval=0.05,lower=0),
+            'beta_C4': pm.TruncatedNormal(f'{self.name}_beta_C4',mu=1,sigma=1,initval=1,lower=0),
+            'K0'     : pm.TruncatedNormal(f'{self.name}_k0',mu=0.4,sigma=0.1,initval=0.4,lower=0),
+            'eta'    : pm.HalfNormal(f'{self.name}_eta',sigma=0.1,initval=0.1)
+        }
+        return param
+
+
+class WheelS3V3(BaseComponents):
+    
+    def __init__(self, name):
+        super().__init__(name)
+    
+    @classmethod
+    def model(
+        cls,
+        TinP,HinP,FP,
+        TinR,HinR,FR,
+        TinC,HinC,FC,
+        engine  : str,
+        param   : dict,
+    ):
+        FUNC = cls.get_func_by_engine(engine)
+        EXP  = FUNC['EXP']
+        
+        beta_P1 = param['beta_P1'] # km
+        beta_P2 = param['beta_P2'] # eta_max
+        beta_P4 = param['beta_P4']
+        beta_P5 = param['beta_P5']
+        beta_C1 = param['beta_C1']
+        beta_C2 = param['beta_C2']
+        beta_C3 = param['beta_C3']
+        beta_C4 = param['beta_C4']
+        beta_R1 = param['beta_R1']
+        beta_R2 = param['beta_R2']
+        
+        # FP_w = EXP(FP)
+        # FR_w = EXP(FR)
+        # FC_w = EXP(FC)
+        T_wheel = (
+            TinP * (FP / (FP + FR + FC))
+            + TinR * (FR / (FP + FR + FC))
+            + TinC * (FC / (FP + FR + FC))
+        )
+        Hr_eq  = get_HumRatio_from_Tdb_and_RH(T_wheel,1,engine)
+        
+        # 处理侧
+        # 湿度
+        eta    = beta_P2 * (1-EXP(-beta_P1 * (HinP-Hr_eq)/Hr_eq))
+        HdiffP = eta * (HinP - Hr_eq)
+        HoutP  = HinP - HdiffP
+        DoutP  = get_Dew_from_HumRatio(HoutP,engine)
+        # 温度
+        TdiffP_sen = beta_P4 * (T_wheel - TinP)
+        TdiffP_lat = cls.CONSTANT['h_ads'] / cls.CONSTANT['c_p_air'] * HdiffP * beta_P5
+        ToutP      = TinP + TdiffP_sen + TdiffP_lat
+        
+        # 预冷侧
+        # 湿度
+        HdiffC = eta * (HinC - Hr_eq)
+        HoutC  = HinC - HdiffC
+        DoutC  = get_Dew_from_HumRatio(HoutC,engine)
+        # 温度
+        TdiffC = beta_C1 * EXP(-beta_C2 * EXP(-beta_C3 * (T_wheel - TinC))) * EXP(-beta_C4 * FC)
+        ToutC  = TinC + TdiffC
+        
+        # 再生侧
+        # 湿度
+        HdiffR = (HdiffP * FP + HdiffC * FC) / FR
+        HoutR  = HinR + HdiffR
+        DoutR  = get_Dew_from_HumRatio(HoutR,engine)
+        # 温度
+        TdiffR_lat = cls.CONSTANT['h_ads'] / cls.CONSTANT['c_p_air'] * HdiffR * beta_R1
+        TdiffR_sen = beta_R2 * (TinR - T_wheel)
+        ToutR      = TinR - TdiffR_sen - TdiffR_lat
+        
+        return {
+            'ToutP':ToutP,'HoutP':HoutP,'DoutP':DoutP,'FP':FP,
+            'ToutR':ToutR,'HoutR':HoutR,'DoutR':DoutR,'FR':FR,
+            'ToutC':ToutC,'HoutC':HoutC,'DoutC':DoutC,'FC':FC,
+        } 
+        
+    def prior(self):
+        param = {
+            'beta_P1': pm.HalfNormal(f'{self.name}_beta_P1',sigma=10,initval=0.01),
+            'beta_P2': pm.Uniform(f'{self.name}_beta_P2',lower=0,upper=1,initval=0.5),
+            'beta_P4': pm.HalfNormal(f'{self.name}_beta_P4', sigma=0.5,initval=0.2),
+            'beta_P5': pm.TruncatedNormal(f'{self.name}_beta_P5', mu=1, sigma=1,initval=1,lower=0),
+            'beta_C1': pm.TruncatedNormal(f'{self.name}_beta_C1',mu=60,sigma=10,initval=60,lower=10),
+            'beta_C2': pm.TruncatedNormal(f'{self.name}_beta_C2',mu=30,sigma=10,initval=30,lower=1),
+            'beta_C3': pm.TruncatedNormal(f'{self.name}_beta_C3',mu=0.05,sigma=0.1,initval=0.05,lower=0),
+            'beta_C4': pm.TruncatedNormal(f'{self.name}_beta_C4',mu=1,sigma=1,initval=1,lower=0),
+            'beta_R1': pm.TruncatedNormal(f'{self.name}_beta_R1', mu=1, sigma=1,initval=1,lower=0),
+            'beta_R2': pm.HalfNormal(f'{self.name}_beta_R2', sigma=10,initval=1),
+        }
+        return param

+ 42 - 18
doc/框架.drawio

@@ -1,38 +1,62 @@
 <mxfile host="65bd71144e">
     <diagram id="nKO4jLVDNhHVAdHXDorT" name="Page-1">
-        <mxGraphModel dx="1382" dy="1951" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
+        <mxGraphModel dx="1632" dy="1951" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
             <root>
                 <mxCell id="0"/>
                 <mxCell id="1" parent="0"/>
-                <mxCell id="2" value="配置文件" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
-                    <mxGeometry x="260" y="-110" width="120" height="60" as="geometry"/>
+                <mxCell id="23" value="训练模型" style="swimlane;whiteSpace=wrap;html=1;" vertex="1" parent="1">
+                    <mxGeometry x="140" y="-940" width="250" height="690" as="geometry"/>
                 </mxCell>
-                <mxCell id="20" style="edgeStyle=none;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="3" target="13">
+                <mxCell id="25" value="" style="edgeStyle=none;html=1;" edge="1" parent="23" source="22" target="24">
                     <mxGeometry relative="1" as="geometry"/>
                 </mxCell>
-                <mxCell id="3" value="加载数据&lt;br&gt;(模型输入)" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
-                    <mxGeometry x="160" width="120" height="60" as="geometry"/>
+                <mxCell id="22" value="加载数据" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="23">
+                    <mxGeometry x="65" y="80" width="120" height="60" as="geometry"/>
                 </mxCell>
-                <mxCell id="8" style="edgeStyle=none;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="5" target="7">
+                <mxCell id="27" value="" style="edgeStyle=none;html=1;" edge="1" parent="23" source="24" target="26">
                     <mxGeometry relative="1" as="geometry"/>
                 </mxCell>
-                <mxCell id="5" value="训练模型" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
-                    <mxGeometry x="220" y="320" width="120" height="60" as="geometry"/>
+                <mxCell id="24" value="数据清洗" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="23">
+                    <mxGeometry x="65" y="220" width="120" height="60" as="geometry"/>
                 </mxCell>
-                <mxCell id="7" value="保存模型" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
-                    <mxGeometry x="220" y="420" width="120" height="60" as="geometry"/>
+                <mxCell id="29" value="" style="edgeStyle=none;html=1;" edge="1" parent="23" source="26" target="28">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="26" value="训练模型" style="whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="23">
+                    <mxGeometry x="65" y="360" width="120" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="28" value="写入模型" style="whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="23">
+                    <mxGeometry x="65" y="500" width="120" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="30" value="模型监控" style="swimlane;whiteSpace=wrap;html=1;" vertex="1" parent="1">
+                    <mxGeometry x="500" y="-940" width="420" height="620" as="geometry"/>
+                </mxCell>
+                <mxCell id="33" value="" style="edgeStyle=none;html=1;" edge="1" parent="30" source="31" target="32">
+                    <mxGeometry relative="1" as="geometry"/>
                 </mxCell>
-                <mxCell id="13" value="模型监控" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
-                    <mxGeometry x="400" y="210" width="120" height="60" as="geometry"/>
+                <mxCell id="31" value="加载数据" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="30">
+                    <mxGeometry x="40" y="70" width="120" height="60" as="geometry"/>
                 </mxCell>
-                <mxCell id="15" value="实时优化" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
-                    <mxGeometry x="30" y="210" width="120" height="60" as="geometry"/>
+                <mxCell id="35" value="" style="edgeStyle=none;html=1;" edge="1" parent="30" source="32" target="34">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="32" value="数据清洗" style="whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="30">
+                    <mxGeometry x="40" y="210" width="120" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="39" value="" style="edgeStyle=none;html=1;" edge="1" parent="30" source="34" target="38">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="34" value="实时预测" style="whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="30">
+                    <mxGeometry x="40" y="350" width="120" height="60" as="geometry"/>
                 </mxCell>
-                <mxCell id="21" style="edgeStyle=none;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="19" target="13">
+                <mxCell id="37" style="edgeStyle=none;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="30" source="36" target="34">
                     <mxGeometry relative="1" as="geometry"/>
                 </mxCell>
-                <mxCell id="19" value="加载数据&lt;br&gt;(模型输出)" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
-                    <mxGeometry x="400" width="120" height="60" as="geometry"/>
+                <mxCell id="36" value="加载模型" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="30">
+                    <mxGeometry x="250" y="350" width="120" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="38" value="写入监控" style="whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="30">
+                    <mxGeometry x="40" y="490" width="120" height="60" as="geometry"/>
                 </mxCell>
             </root>
         </mxGraphModel>

+ 7 - 157
main.py

@@ -1,162 +1,12 @@
-import time
-from pprint import pprint
-
-import numpy as np
-import pandas as pd
-
-from .model.DHU_2 import DHU_2
-from ._opt.algorithm.sim_config import simulate_config as sim_opt_config
-from ._opt.algorithm.main import main as main_opt
-from ._opt.boundary.sim_config import simulate_config as sim_config_bound
-from ._opt.boundary.main import main as main_bound
-from ._opt.algorithm.model.model import SystemModel
+from .apps.train.train import train
 
 def main(*args,config=None):
+    mode   = config['mode']
     
-    args = [_.reset_index(drop=True) for _ in args]
-    
-    var_cur = {
-        'coil_1_ToutA': args[0],
-        'coil_1_HoutA': args[1],
-        'HzP'         : args[2],
-        'HzR'         : args[3],
-        'coil_1_TinW' : args[4],
-        'coil_2_TinW' : args[5],
-        'coil_3_TinW' : args[6],
-        'coil_1_Val'  : args[7],
-        'coil_2_Val'  : args[8],
-        'coil_3_Val'  : args[9],
-        'wheel_1_TinR': args[10],
-        'wheel_2_TinR': args[11],
-        'mixed_1_TinM': args[12],
-        'mixed_1_HinM': args[13],
-        'mixed_2_TinM': args[14],
-        'mixed_2_HinM': args[15],
-    }
-    var_cur      = {k:v.set_axis([k],axis=1) for k,v in var_cur.items()}
-    var_opt_name = ['coil_2_Val','wheel_1_TinR','wheel_2_TinR']
-    var_sys_name = [_ for _ in var_cur.keys() if _ not in var_opt_name]
-    var_opt_cur  = {k:v for k,v in var_cur.items() if k in var_opt_name}
-    var_sys_cur  = {k:v for k,v in var_cur.items() if k in var_sys_name}
-    var_opt_var  = {
-        'coil_2_Val': main_bound(
-            None,None,None,
-            var_opt_cur['coil_2_Val'],
-            config = sim_config_bound(
-                opt_var    = ['coil_2_Val'],
-                syn_opt    = False,
-                var_type   = True,
-                lb_static  = 100,
-                ub_static  = 100,
-                var_precis = 1
-            )
-        ),
-        'wheel_1_TinR': main_bound(
-            None,None,None,
-            var_opt_cur['wheel_1_TinR'],
-            config = sim_config_bound(
-                opt_var    = ['wheel_1_TinR'],
-                syn_opt    = False,
-                var_type   = True,
-                lb_static  = 50,
-                ub_static  = 120,
-                var_precis = 1
-            )
-        ),
-        'wheel_2_TinR': main_bound(
-            None,None,None,
-            var_opt_cur['wheel_2_TinR'],
-            config = sim_config_bound(
-                opt_var    = ['wheel_2_TinR'],
-                syn_opt    = False,
-                var_type   = True,
-                lb_static  = 50,
-                ub_static  = 120,
-                var_precis = 1
-            )
-        ),
-    }
-    
-    # 获取优化结果
-    opt_config = sim_opt_config(
-        target        = 'Fs',
-        dir_min       = True,
-        var_opt       = var_opt_name,
-        var_sys       = var_sys_name,
-        diag_model    = False,
-        algorithm     = 'soea_DE_best_1_L_templet',
-        NIND          = 5000,
-        MAXGEN        = 200,
-        constrains    = [
-            'coil_3_DoutA-[coil_3_DoutA]<0',
-            # 'steamcoil_1_Fs-220<0',
-            # 'steamcoil_2_Fs-100<0',
-            # '40-steamcoil_1_Fs<0',
-            # '30-steamcoil_2_Fs<0',
-        ],
-        allow_neg_opt = False,
-    )
-    
-    system = AirSystem()
-    opt_output  = main_opt(
-        *list(var_opt_var.values()),
-        *list(var_sys_cur.values()),
-        system_model = system,
-        config       = opt_config
-    )
-    sys_output = system.predict(
-        pd.concat([*opt_output[3:],*list(var_sys_cur.values())],axis=1),
-    )
+    if mode == 'train':
+        train(*args,config=config)
+    else:
+        raise NotImplementedError
     
-    show_data = None
-    # show_data = get_show_data(opt_res=opt_output[2])
-    # print(show_data)
     
-    return [
-        sys_output.loc[:,['coil_2_ToutA']],
-        opt_output[3:][1],
-        opt_output[3:][2],
-        show_data
-    ]
-
-
-class AirSystem(SystemModel):
-    def __init__(self):
-        super().__init__()
-        # self.model = DHU_2().load_from_platform()
-        path = r'C:\Users\zhangshenhao\Documents\WorkSpace\zsh_project\004_溧阳宁德\除湿机模型\model.pkl'
-        self.model = DHU_2().load(path)
-    
-    def predict(self,data:pd.DataFrame) -> pd.DataFrame:
-        time_start   = time.time()
-        self.PENALTY = {}
-        self.index   = data.index
-        sys_out      = self.model.predict_system(data)
-        time_end     = time.time()
-        past_time    = round(time_end-time_start,2)
-        self.PAST_TIME.append(past_time)
-        print(f'第{len(self.PAST_TIME)}次调用系统模型,本次调用时长为:{past_time}秒 \n')    
-        return sys_out
-
-
-def get_show_data(opt_res:pd.DataFrame) -> pd.DataFrame:
-    var_names = {
-        'coil_3_DoutA'        : '送风露点(℃)',
-        'coil_2_Q'            : '中表冷冷量(kW)',
-        'coil_3_Q'            : '后表冷冷量(kW)',
-        'steamcoil_1_Q'       : '前再生热量(kW)',
-        'steamcoil_2_Q'       : '后再生热量(kW)',
-        'wheel_1_E_diff'      : '前转轮焓升(kJ/kg)',
-        'wheel_2_E_diff'      : '后转轮焓升(kJ/kg)',
-        'summary_cost_cooling': '耗冷费用折算(元/h)',
-        'summary_cost_heating': '耗热费用折算(元/h)',
-        'summary_cost_total'  : '冷热费用合计(元/h)',
-    }
-    res = (
-        opt_res
-        .loc[list(var_names.keys()),['Before','After']]
-        .assign(指标=list(var_names.values()))
-        .rename(columns={'Before':'优化前状态','After':'优化后状态'})
-        .loc[:,['指标','优化前状态','优化后状态']]
-    )
-    return res
+        

+ 162 - 0
main1.py

@@ -0,0 +1,162 @@
+import time
+from pprint import pprint
+
+import numpy as np
+import pandas as pd
+
+from .model.DHU_2 import DHU_2
+from ._opt.algorithm.sim_config import simulate_config as sim_opt_config
+from ._opt.algorithm.main import main as main_opt
+from ._opt.boundary.sim_config import simulate_config as sim_config_bound
+from ._opt.boundary.main import main as main_bound
+from ._opt.algorithm.model.model import SystemModel
+
+def main(*args,config=None):
+    
+    args = [_.reset_index(drop=True) for _ in args]
+    
+    var_cur = {
+        'coil_1_ToutA': args[0],
+        'coil_1_HoutA': args[1],
+        'HzP'         : args[2],
+        'HzR'         : args[3],
+        'coil_1_TinW' : args[4],
+        'coil_2_TinW' : args[5],
+        'coil_3_TinW' : args[6],
+        'coil_1_Val'  : args[7],
+        'coil_2_Val'  : args[8],
+        'coil_3_Val'  : args[9],
+        'wheel_1_TinR': args[10],
+        'wheel_2_TinR': args[11],
+        'mixed_1_TinM': args[12],
+        'mixed_1_HinM': args[13],
+        'mixed_2_TinM': args[14],
+        'mixed_2_HinM': args[15],
+    }
+    var_cur      = {k:v.set_axis([k],axis=1) for k,v in var_cur.items()}
+    var_opt_name = ['coil_2_Val','wheel_1_TinR','wheel_2_TinR']
+    var_sys_name = [_ for _ in var_cur.keys() if _ not in var_opt_name]
+    var_opt_cur  = {k:v for k,v in var_cur.items() if k in var_opt_name}
+    var_sys_cur  = {k:v for k,v in var_cur.items() if k in var_sys_name}
+    var_opt_var  = {
+        'coil_2_Val': main_bound(
+            None,None,None,
+            var_opt_cur['coil_2_Val'],
+            config = sim_config_bound(
+                opt_var    = ['coil_2_Val'],
+                syn_opt    = False,
+                var_type   = True,
+                lb_static  = 100,
+                ub_static  = 100,
+                var_precis = 1
+            )
+        ),
+        'wheel_1_TinR': main_bound(
+            None,None,None,
+            var_opt_cur['wheel_1_TinR'],
+            config = sim_config_bound(
+                opt_var    = ['wheel_1_TinR'],
+                syn_opt    = False,
+                var_type   = True,
+                lb_static  = 50,
+                ub_static  = 120,
+                var_precis = 1
+            )
+        ),
+        'wheel_2_TinR': main_bound(
+            None,None,None,
+            var_opt_cur['wheel_2_TinR'],
+            config = sim_config_bound(
+                opt_var    = ['wheel_2_TinR'],
+                syn_opt    = False,
+                var_type   = True,
+                lb_static  = 50,
+                ub_static  = 120,
+                var_precis = 1
+            )
+        ),
+    }
+    
+    # 获取优化结果
+    opt_config = sim_opt_config(
+        target        = 'Fs',
+        dir_min       = True,
+        var_opt       = var_opt_name,
+        var_sys       = var_sys_name,
+        diag_model    = False,
+        algorithm     = 'soea_DE_best_1_L_templet',
+        NIND          = 5000,
+        MAXGEN        = 200,
+        constrains    = [
+            'coil_3_DoutA-[coil_3_DoutA]<0',
+            # 'steamcoil_1_Fs-220<0',
+            # 'steamcoil_2_Fs-100<0',
+            # '40-steamcoil_1_Fs<0',
+            # '30-steamcoil_2_Fs<0',
+        ],
+        allow_neg_opt = False,
+    )
+    
+    system = AirSystem()
+    opt_output  = main_opt(
+        *list(var_opt_var.values()),
+        *list(var_sys_cur.values()),
+        system_model = system,
+        config       = opt_config
+    )
+    sys_output = system.predict(
+        pd.concat([*opt_output[3:],*list(var_sys_cur.values())],axis=1),
+    )
+    
+    show_data = None
+    # show_data = get_show_data(opt_res=opt_output[2])
+    # print(show_data)
+    
+    return [
+        sys_output.loc[:,['coil_2_ToutA']],
+        opt_output[3:][1],
+        opt_output[3:][2],
+        show_data
+    ]
+
+
+class AirSystem(SystemModel):
+    def __init__(self):
+        super().__init__()
+        # self.model = DHU_2().load_from_platform()
+        path = r'C:\Users\zhangshenhao\Documents\WorkSpace\zsh_project\004_溧阳宁德\除湿机模型\model.pkl'
+        self.model = DHU_2().load(path)
+    
+    def predict(self,data:pd.DataFrame) -> pd.DataFrame:
+        time_start   = time.time()
+        self.PENALTY = {}
+        self.index   = data.index
+        sys_out      = self.model.predict_system(data)
+        time_end     = time.time()
+        past_time    = round(time_end-time_start,2)
+        self.PAST_TIME.append(past_time)
+        print(f'第{len(self.PAST_TIME)}次调用系统模型,本次调用时长为:{past_time}秒 \n')    
+        return sys_out
+
+
+def get_show_data(opt_res:pd.DataFrame) -> pd.DataFrame:
+    var_names = {
+        'coil_3_DoutA'        : '送风露点(℃)',
+        'coil_2_Q'            : '中表冷冷量(kW)',
+        'coil_3_Q'            : '后表冷冷量(kW)',
+        'steamcoil_1_Q'       : '前再生热量(kW)',
+        'steamcoil_2_Q'       : '后再生热量(kW)',
+        'wheel_1_E_diff'      : '前转轮焓升(kJ/kg)',
+        'wheel_2_E_diff'      : '后转轮焓升(kJ/kg)',
+        'summary_cost_cooling': '耗冷费用折算(元/h)',
+        'summary_cost_heating': '耗热费用折算(元/h)',
+        'summary_cost_total'  : '冷热费用合计(元/h)',
+    }
+    res = (
+        opt_res
+        .loc[list(var_names.keys()),['Before','After']]
+        .assign(指标=list(var_names.values()))
+        .rename(columns={'Before':'优化前状态','After':'优化后状态'})
+        .loc[:,['指标','优化前状态','优化后状态']]
+    )
+    return res

+ 127 - 92
model/DHU/DHU_A.py

@@ -6,10 +6,10 @@ import pytensor.tensor as pt
 from .._base._base_device import BaseDevice
 from ...components.coil_water import CoolingCoil2
 from ...components.coil_steam import SteamCoilFs,SteamCoilFs2,SteamCoil
-from ...components.wheel import WheelS3
+from ...components.wheel3 import WheelS3,WheelS3V2,WheelS3V3
 from ...components.mixed import Mixed
 from ..utils.fit_utils import (
-    observe,record,reorder_posterior,get_fitted_result
+    observe,record,reorder_posterior
 )
 from ...tools.optimizer import optimizer
 
@@ -48,8 +48,8 @@ class DHU_A(BaseDevice):
         'steamcoil_2_FP' : 'steamcoil_2_FP',
         'steamcoil_1_Fs' : 'steamcoil_1_Fs',
         'steamcoil_2_Fs' : 'steamcoil_2_Fs',
-        'steamcoil_1_Val': 'steamcoil_1_Val',
-        'steamcoil_2_Val': 'steamcoil_2_Val',
+        # 'steamcoil_1_Val': 'steamcoil_1_Val',
+        # 'steamcoil_2_Val': 'steamcoil_2_Val',
     }
     
     def __init__(self) -> None:
@@ -59,10 +59,10 @@ class DHU_A(BaseDevice):
             WheelS3('wheel_2'),
             CoolingCoil2('coil_2'),
             CoolingCoil2('coil_3'),
-            # SteamCoil('steamcoil_1'),
-            # SteamCoil('steamcoil_2'),
-            SteamCoilFs2('steamcoil_1'),
-            SteamCoilFs('steamcoil_2'),
+            SteamCoil('steamcoil_1'),
+            SteamCoil('steamcoil_2'),
+            # SteamCoilFs2('steamcoil_1'),
+            # SteamCoilFs('steamcoil_2'),
             Mixed('mixed_1'),
             Mixed('mixed_2'),
         ]
@@ -72,14 +72,21 @@ class DHU_A(BaseDevice):
         self,
         input_data   : pd.DataFrame,
         observed_data: pd.DataFrame,
+        exist_Fa_H   : bool,
+        exist_Fa_B   : bool,
         rw_FA_val    : bool = False,
-        plot_TVP     : bool = True
+        plot_TVP     : bool = True,
     ):
         with pm.Model() as self.MODEL_PYMC:
             param_prior = {name:comp.prior() for name,comp in self.components.items()}
-            param_prior['F_air'] = AirFlow.prior(rw_FA_val=rw_FA_val,N=len(input_data))
+            param_prior['F_air'] = AirFlow_DHU_AB.prior(
+                rw_FA_val  = rw_FA_val,
+                N          = len(input_data),
+                exist_Fa_H = exist_Fa_H,
+                exist_Fa_B = exist_Fa_B
+            )
             
-            res = DHU_A.model(
+            res = self.model(
                 **{k:input_data.loc[:,v].values for k,v in self.model_input_data_columns.items()},
                 engine       = 'pymc',
                 components   = self.components,
@@ -95,14 +102,15 @@ class DHU_A(BaseDevice):
             observe('coil_2_ToutA',res['coil_2']['ToutA'],observed=observed_data)
             observe('coil_2_DoutA',res['coil_2']['DoutA'],observed=observed_data)
             observe('wheel_2_ToutP',res['wheel_2']['ToutP'],observed=observed_data)
-            observe('wheel_2_DoutP',res['wheel_2']['DoutP'],observed=observed_data)
+            observe('wheel_2_DoutP',res['wheel_2']['DoutP'],observed=observed_data,sigma=0.3)
             observe('wheel_2_ToutR',res['wheel_2']['ToutR'],observed=observed_data)
+            #TODO observe wheel_2_ToutC
             
-            observe('steamcoil_1_FP',res['steamcoil_1']['FP'],observed=observed_data,sigma=1000)
-            observe('steamcoil_1_Fs',res['steamcoil_1']['Fs'],observed=observed_data,sigma=20)
-            observe('steamcoil_2_Fs',res['steamcoil_2']['Fs'],observed=observed_data,sigma=20)
-            # record('steamcoil_1_Fs',res['steamcoil_1']['Fs'])
-            # record('steamcoil_2_Fs',res['steamcoil_2']['Fs'])
+            # observe('steamcoil_1_FP',res['steamcoil_1']['FP'],observed=observed_data,sigma=10000)
+            # observe('steamcoil_1_Fs',res['steamcoil_1']['Fs'],observed=observed_data,sigma=20)
+            # observe('steamcoil_2_Fs',res['steamcoil_2']['Fs'],observed=observed_data,sigma=20)
+            record('steamcoil_1_Fs',res['steamcoil_1']['Fs'])
+            record('steamcoil_2_Fs',res['steamcoil_2']['Fs'])
             
             record('wheel_2_ToutC',res['wheel_2']['ToutC'])
             record('mixed_2_ToutA',res['mixed_2']['ToutA'])
@@ -117,34 +125,17 @@ class DHU_A(BaseDevice):
             self.param_posterior = pm.find_MAP(maxeval=50000,include_transformed=False)
             
         self.record_model(
-            model_name   = 'DHU',
+            model_name   = 'ATD',
             model        = reorder_posterior(param_prior,self.param_posterior),
             train_data   = {'x':np.array([1])},
             train_metric = {'R2':1,'MAE':1,'MAPE':1}
         )
-        self.TVP_data,self.TVP_metric = get_fitted_result(self.param_posterior,observed_data,plot_TVP)
+        self.TVP_data   = self.get_TVP(self.param_posterior,observed_data)
+        self.TVP_metric = self.get_metric(self.TVP_data)
+        if plot_TVP:
+            self.plot_TVP(self.TVP_data).show()
         return self
     
-    def predict(self,input_data:pd.DataFrame) -> dict:
-        param_posterior = self.model_info['model_DHU']
-        res = DHU_A.model(
-            **{k:input_data.loc[:,v].values for k,v in self.model_input_data_columns.items()},
-            engine       = 'numpy',
-            components   = self.components,
-            param        = param_posterior
-        )
-        return res
-    
-    def predict_system(self,input_data:pd.DataFrame) -> pd.DataFrame:
-        pred_res = self.predict(input_data)
-        system_output = {}
-        for equp_name,output_info in pred_res.items():
-            for output_name,output_value in output_info.items():
-                system_output[f'{equp_name}_{output_name}'] = output_value
-        system_output       = pd.DataFrame(system_output)
-        system_output['Fs'] = system_output.steamcoil_1_Fs + system_output.steamcoil_2_Fs
-        return system_output
-    
     def optimize(
         self,
         cur_input_data : pd.DataFrame,
@@ -152,7 +143,8 @@ class DHU_A(BaseDevice):
         wheel_1_TinR_lb: float = 70,
         wheel_2_TinR_ub: float = 120,
         wheel_2_TinR_lb: float = 70,
-        constrains     : list  = None
+        constrains     : list  = None,
+        logging        : bool  = True
     ) -> list:
         constrains = [] if constrains is None else constrains
         cur_input_data = cur_input_data.iloc[[0],:]
@@ -171,7 +163,11 @@ class DHU_A(BaseDevice):
             opt_var_boundary = opt_var_boundary,
             opt_var_value    = opt_var_value,
             oth_var_value    = oth_var_value,
-            constrains       = constrains
+            target           = 'Fs',
+            target_min       = True,
+            constrains       = constrains,
+            logging          = logging,
+            other_kwargs     = {'NIND':2000,'MAXGEN':50}
         )
         return opt_res
     
@@ -203,7 +199,7 @@ class DHU_A(BaseDevice):
         coil_2_FW = coil_2_Val / 100
         coil_3_FW = coil_3_Val / 100
         # 空气的质量流量
-        air_flow = AirFlow.model(fan_1_Hz=fan_1_Hz,fan_2_Hz=fan_2_Hz,param=param)
+        air_flow = AirFlow_DHU_AB.model(fan_1_Hz=fan_1_Hz,fan_2_Hz=fan_2_Hz,param=param,type='DHU_A')
         
         # 前转轮
         wheel_1_res = components['wheel_1'].model(
@@ -336,53 +332,82 @@ class DHU_A(BaseDevice):
             'mixed_2'    : mixed_2_res,
             'steamcoil_1': steamcoil_1_res,
             'steamcoil_2': steamcoil_2_res,
-            'Fa'         : air_flow,
-            'summary'    : {}
+            'Fa'         : air_flow, 
+            'summary'    : {
+                'Fs':steamcoil_1_res['Fs'] + steamcoil_2_res['Fs'],
+            }
         }
 
 
-class AirFlow:
+class AirFlow_DHU_AB:
     
     @classmethod
-    def model(cls,fan_1_Hz,fan_2_Hz,param):
-        # 空气的质量流量
-        F_air_HzP_H = param['F_air']['HzP_H']
-        F_air_HzP_X = param['F_air']['HzP_X']
-        F_air_HzP_S = F_air_HzP_H + F_air_HzP_X
-        F_air_HzR_B = param['F_air']['HzR_B']
+    def model(cls,fan_1_Hz,fan_2_Hz,param,type):
         
+        # 当定频风机固定的时候,各出入口处的基准的风量
         F_air_S_base  = 1
         F_air_X_base  = param['F_air']['X_base']
-        F_air_H_base  = param['F_air']['H_base']
-        F_air_B_base  = param['F_air']['B_base']
+        F_air_H_base  = param['F_air'].get('H_base',0)
+        F_air_B_base  = param['F_air'].get('B_base',0)
         F_air_val_rw  = param['F_air'].get('val_rw',0)
         F_air_val_pct = param['F_air'].get('val_pct',0)
         
+        # 新风阀的变化造成的基准风量变化
+        F_air_S_base_adj = F_air_S_base
         F_air_X_base_adj = F_air_X_base + F_air_val_rw
-        F_air_H_base_adj = F_air_H_base - F_air_val_rw * F_air_val_pct
-        F_air_B_base_adj = F_air_B_base - F_air_val_rw * (1 - F_air_val_pct)
+        F_air_H_base_adj = F_air_H_base - F_air_val_rw * F_air_val_pct if 'H_base' in param['F_air'] else 0
+        F_air_B_base_adj = F_air_B_base - F_air_val_rw * (1 - F_air_val_pct) if 'B_base' in param['F_air'] else 0
+        # F_air_X_base_adj = F_air_X_base + F_air_val_rw
+        # F_air_B_base_adj = F_air_B_base - F_air_val_rw * F_air_val_pct[0] if 'B_base' in param['F_air'] else 0
+        # F_air_S_base_adj = F_air_S_base + F_air_val_rw * F_air_val_pct[1] if 'H_base' in param['F_air'] else 0
+        # F_air_H_base_adj = F_air_H_base - F_air_val_rw * F_air_val_pct[2] if 'H_base' in param['F_air'] else 0
+        
+        # 考虑风机频率变化对风量的影响,得到最终风量
+        # 因为从数据上看,排风机的频率不会影响到新风量,所以在新风阀不动的情况下,新风的增加量可以认为全部进入送风
+        F_air_HzP_X = param['F_air']['HzP_X']
+        F_air_HzP_H = param['F_air'].get('HzP_H',0)
+        F_air_HzP_S = F_air_HzP_X + F_air_HzP_H
+        F_air_HzR_B = param['F_air'].get('HzR_B',0)
+        Fa_S        = F_air_S_base_adj + F_air_HzP_S * (fan_1_Hz / 50)
+        Fa_H        = F_air_H_base_adj + F_air_HzP_H * (fan_1_Hz / 50)
+        Fa_X        = F_air_X_base_adj + F_air_HzP_X * (fan_1_Hz / 50)
+        Fa_B        = F_air_B_base_adj + F_air_HzR_B * (fan_2_Hz / 50)
+        Fa_P        = Fa_B + Fa_X + Fa_H - Fa_S
         
-        Fa_S = F_air_S_base + F_air_HzP_S * (fan_1_Hz / 50)            
-        Fa_H = F_air_H_base_adj + F_air_HzP_H * (fan_1_Hz / 50)            
-        Fa_X = F_air_X_base_adj + F_air_HzP_X * (fan_1_Hz / 50)
-        Fa_B = F_air_B_base_adj + F_air_HzR_B * (fan_2_Hz / 50)           
-        Fa_P = Fa_B + Fa_X + Fa_H - Fa_S  
+        if type == 'DHU_A':
+            wheel_1_FaP    = Fa_S - Fa_H
+            wheel_1_FaC    = Fa_X - wheel_1_FaP
+            wheel_1_FaR    = Fa_P
+            wheel_2_FaP    = Fa_S
+            wheel_2_FaC    = wheel_1_FaC
+            wheel_2_FaR    = wheel_1_FaC
+            mixed_1_FaM    = Fa_H
+            mixed_1_FaA    = wheel_1_FaP
+            mixed_2_FaM    = Fa_B
+            mixed_2_FaA    = wheel_1_FaC
+            coil_2_FaA     = Fa_S
+            coil_3_FaA     = Fa_S
+            steamcoil_1_Fa = Fa_P
+            steamcoil_2_Fa = wheel_1_FaC
         
-        wheel_1_FaP    = Fa_S - Fa_H
-        wheel_1_FaC    = Fa_X - wheel_1_FaP
-        wheel_1_FaR    = Fa_P
-        wheel_2_FaP    = Fa_S
-        wheel_2_FaC    = wheel_1_FaC
-        wheel_2_FaR    = wheel_1_FaC
-        mixed_1_FaM    = Fa_H
-        mixed_1_FaA    = wheel_1_FaP
-        mixed_2_FaM    = Fa_B
-        mixed_2_FaA    = wheel_1_FaC
-        coil_2_FaA     = Fa_S
-        coil_3_FaA     = Fa_S
-        steamcoil_1_Fa = Fa_P
-        steamcoil_2_Fa = wheel_1_FaC
+        elif type == 'DHU_B':
+            wheel_1_FaP    = Fa_X
+            wheel_1_FaC    = None
+            wheel_1_FaR    = Fa_P
+            wheel_2_FaP    = Fa_S
+            wheel_2_FaC    = Fa_X + Fa_H - Fa_S
+            wheel_2_FaR    = wheel_2_FaC
+            mixed_1_FaM    = Fa_H
+            mixed_1_FaA    = Fa_X
+            mixed_2_FaM    = Fa_B
+            mixed_2_FaA    = wheel_2_FaC
+            coil_2_FaA     = Fa_S
+            coil_3_FaA     = Fa_S
+            steamcoil_1_Fa = Fa_P
+            steamcoil_2_Fa = wheel_2_FaC
         
+        else:
+            raise Exception('type error')
         return {
             'Fa_S':Fa_S,'Fa_H':Fa_H,'Fa_X':Fa_X,'Fa_B':Fa_B,'Fa_P':Fa_P,
             'wheel_1_FaP':wheel_1_FaP,'wheel_1_FaC':wheel_1_FaC,'wheel_1_FaR':wheel_1_FaR,
@@ -392,31 +417,41 @@ class AirFlow:
             'coil_2_FaA':coil_2_FaA,'coil_3_FaA':coil_3_FaA,
             'steamcoil_1_Fa':steamcoil_1_Fa,'steamcoil_2_Fa':steamcoil_2_Fa
         }
-    
+        
     @classmethod
-    def prior(cls,rw_FA_val,N) -> dict:
-        HzP_X  = pm.HalfNormal('F_air_HzP_X',sigma=1,initval=1)
-        HzP_H  = pm.HalfNormal('F_air_HzP_H',sigma=1,initval=0.1)
-        HzR_B  = pm.HalfNormal('F_air_HzR_B',sigma=1,initval=0.5)
-        X_base = pm.TruncatedNormal('F_air_X_base',mu=0.5,sigma=0.2,lower=0,initval=0.5)
-        H_base = pm.TruncatedNormal('F_air_H_base',mu=0.6,sigma=0.2,lower=0,upper=0.999,initval=0.6)
-        B_base = pm.TruncatedNormal('F_air_B_base',mu=0.2,sigma=0.1,lower=0,initval=0.1)
+    def prior(
+        cls,
+        rw_FA_val : bool,
+        N         : int,
+        exist_Fa_H: bool,
+        exist_Fa_B: bool
+    ) -> dict:
+        param = {}
+        
+        # 新风参数
+        param['HzP_X']  = pm.HalfNormal('F_air_HzP_X',sigma=1,initval=1)
+        param['X_base'] = pm.TruncatedNormal('F_air_X_base',mu=0.5,sigma=0.2,lower=0,initval=0.5)
+        
+        if exist_Fa_H:
+            param['HzP_H']  = pm.HalfNormal('F_air_HzP_H',sigma=1,initval=0.1)
+            param['H_base'] = pm.TruncatedNormal('F_air_H_base',mu=0.6,sigma=0.2,lower=0,upper=0.999,initval=0.6)
+        
+        if exist_Fa_B:
+            param['HzR_B']  = pm.HalfNormal('F_air_HzR_B',sigma=1,initval=0.5)
+            param['B_base'] = pm.TruncatedNormal('F_air_B_base',mu=0.2,sigma=0.1,lower=0,initval=0.1)
         
         if rw_FA_val:
-            period     = 30
+            period     = 48
             n_segments = int(np.ceil(N/period))
             remainder  = N % period
             repeat     = [period] * (n_segments - 1) + ([remainder] if remainder != 0 else [])
             rw = pm.GaussianRandomWalk(
                 'rw',sigma=0.1,init_dist=pm.Normal.dist(mu=0,sigma=0.3),shape=n_segments)
-            val_rw  = pm.Deterministic('F_air_val_rw',pt.repeat(rw,repeat))
-            val_pct = pm.Beta('F_air_val_pct',alpha=8,beta=1,initval=0.9)
+            param['val_rw']  = pm.Deterministic('F_air_val_rw',pt.repeat(rw,repeat))
+            param['val_pct'] = pm.Beta('F_air_val_pct',alpha=8,beta=1,initval=0.9)
+            # param['val_pct'] = pm.Dirichlet('F_air_val_pct',np.array([0.1,0.1,0.8]),initval=np.array([0.1,0.1,0.8]))
         else:
-            val_rw  = 0
-            val_pct = 0
+            param['val_rw']  = 0
+            param['val_pct'] = 0
         
-        return {
-            'HzP_X':HzP_X,'HzP_H':HzP_H,'HzR_B':HzR_B,
-            'X_base':X_base,'H_base':H_base,'B_base':B_base,
-            'val_rw':val_rw,'val_pct':val_pct
-        }
+        return param

+ 299 - 0
model/DHU/DHU_B.py

@@ -0,0 +1,299 @@
+import numpy as np
+import pandas as pd
+import pymc as pm
+import pytensor.tensor as pt
+
+from .._base._base_device import BaseDevice
+from ...components.coil_water import CoolingCoil2
+from ...components.coil_steam import SteamCoil
+from ...components.wheel2 import WheelS2V3
+from ...components.wheel3 import WheelS3
+from ...components.mixed import Mixed
+from ..utils.fit_utils import (
+    observe,record,reorder_posterior
+)
+from ...tools.optimizer import optimizer
+from .DHU_A import AirFlow_DHU_AB
+
+
+
+class DHU_B(BaseDevice):
+    
+    model_input_data_columns = {}
+    model_observe_data_columns = {}
+    
+    def __init__(self):
+        super().__init__()
+        self.components = [
+            WheelS2V3('wheel_1'),
+            WheelS3('wheel_2'),
+            CoolingCoil2('coil_2'),
+            CoolingCoil2('coil_3'),
+            SteamCoil('steamcoil_1'),
+            SteamCoil('steamcoil_2'),
+            Mixed('mixed_1'),
+            Mixed('mixed_2'),
+        ]
+        self.components = {comp.name:comp for comp in self.components}
+    
+    def fit(
+        self,
+        input_data   : pd.DataFrame,
+        observed_data: pd.DataFrame,
+        exist_Fa_H   : bool,
+        exist_Fa_B   : bool,
+        rw_FA_val    : bool = False,
+        plot_TVP     : bool = True
+    ):
+        with pm.Model() as self.MODEL_PYMC:
+            param_prior = {name:comp.prior() for name,comp in self.components.items()}
+            param_prior['F_air'] = AirFlow_DHU_AB.prior(
+                rw_FA_val  = rw_FA_val,
+                N          = len(input_data),
+                exist_Fa_H = exist_Fa_H,
+                exist_Fa_B = exist_Fa_B
+            )
+            
+            res = self.model(
+                **{k:input_data.loc[:,v].values for k,v in self.model_input_data_columns.items()},
+                engine       = 'pymc',
+                components   = self.components,
+                param        = param_prior
+            )
+            for std_name,name in self.model_observe_data_columns.items():
+                if name not in observed_data.columns:
+                    continue
+                observed_data = observed_data.rename(columns={name:std_name})
+            observe('mixed_1_ToutA',res['mixed_1']['ToutA'],observed=observed_data)
+            observe('mixed_1_DoutA',res['mixed_1']['DoutA'],observed=observed_data)
+            observe('coil_2_ToutA',res['coil_2']['ToutA'],observed=observed_data)
+            observe('coil_2_DoutA',res['coil_2']['DoutA'],observed=observed_data)
+            observe('wheel_2_ToutP',res['wheel_2']['ToutP'],observed=observed_data)
+            observe('wheel_2_DoutP',res['wheel_2']['DoutP'],observed=observed_data)
+            observe('wheel_2_ToutC',res['wheel_2']['ToutC'],observed=observed_data)
+            observe('wheel_2_ToutR',res['wheel_2']['ToutR'],observed=observed_data)
+            observe('wheel_1_ToutR',res['wheel_1']['ToutR'],observed=observed_data,sigma=5)
+            
+            record('steamcoil_1_Fs',res['steamcoil_1']['Fs'])
+            record('steamcoil_2_Fs',res['steamcoil_2']['Fs'])
+            
+            record('mixed_2_ToutA',res['mixed_2']['ToutA'])
+            record('wheel_1_FaP',res['wheel_1']['FP'])
+            record('wheel_1_FaR',res['wheel_1']['FR'])
+            record('wheel_1_FaC',res['wheel_1']['FC'])
+            record('mixed_1_FaA',res['mixed_1']['FA'])
+            record('mixed_1_FaM',res['mixed_1']['FM'])
+            record('F_air_S',res['Fa']['Fa_S'])
+            record('F_air_H',res['Fa']['Fa_H'])
+            record('F_air_X',res['Fa']['Fa_X'])
+            self.param_posterior = pm.find_MAP(maxeval=50000,include_transformed=False)
+            
+        self.record_model(
+            model_name   = 'ATD',
+            model        = reorder_posterior(param_prior,self.param_posterior),
+            train_data   = {'x':np.array([1])},
+            train_metric = {'R2':1,'MAE':1,'MAPE':1}
+        )
+        self.TVP_data,self.TVP_metric = get_fitted_result(self.param_posterior,observed_data,plot_TVP)
+        return self
+    
+    def optimize(
+        self,
+        cur_input_data : pd.DataFrame,
+        wheel_1_TinR_ub: float = 120,
+        wheel_1_TinR_lb: float = 70,
+        wheel_2_TinR_ub: float = 120,
+        wheel_2_TinR_lb: float = 70,
+        constrains     : list  = None,
+        logging        : bool  = True
+    ) -> list:
+        constrains = [] if constrains is None else constrains
+        cur_input_data = cur_input_data.iloc[[0],:]
+        opt_var_boundary  = {
+            'wheel_1_TinR':{'lb':wheel_1_TinR_lb,'ub':wheel_1_TinR_ub},
+            'wheel_2_TinR':{'lb':wheel_2_TinR_lb,'ub':wheel_2_TinR_ub},
+        }
+        opt_var_value = cur_input_data.loc[:,list(opt_var_boundary.keys())]
+        oth_var_value = (
+            cur_input_data
+            .loc[:,list(self.model_input_data_columns.values())]
+            .drop(opt_var_value.columns,axis=1)
+        )
+        opt_res = optimizer(
+            model            = self,
+            opt_var_boundary = opt_var_boundary,
+            opt_var_value    = opt_var_value,
+            oth_var_value    = oth_var_value,
+            target           = 'Fs',
+            target_min       = True,
+            constrains       = constrains,
+            logging          = logging,
+            other_kwargs     = {'NIND':2000,'MAXGEN':50}
+        )
+        return opt_res
+    
+    
+    @classmethod
+    def model(
+        cls,
+        Tin_F,        # 前表冷后温度
+        Hin_F,        # 前表冷后湿度
+        fan_1_Hz,     # 处理侧风机频率 
+        fan_2_Hz,     # 再生侧风机频率
+        coil_1_TinW,  # 前表冷进水温度
+        coil_2_TinW,  # 中表冷进水温度
+        coil_3_TinW,  # 后表冷进水温度
+        coil_1_Val,   # 前表冷阀门开度
+        coil_2_Val,   # 中表冷阀门开度
+        coil_3_Val,   # 后表冷阀门开度
+        wheel_1_TinR, # 前转轮再生侧温度
+        wheel_2_TinR, # 后转轮再生侧温度
+        mixed_1_TinM, # 回风温度(处理侧)
+        mixed_1_HinM, # 回风湿度(处理侧)
+        mixed_2_TinM, # 补风温度(再生侧)
+        mixed_2_HinM, # 补风湿度(再生侧)
+        engine    : str,
+        components: dict,
+        param     : dict,
+    ) ->  dict:
+        
+        # 水的质量流量
+        coil_2_FW = coil_2_Val / 100
+        coil_3_FW = coil_3_Val / 100
+        # 空气的质量流量
+        air_flow = AirFlow_DHU_AB.model(fan_1_Hz=fan_1_Hz,fan_2_Hz=fan_2_Hz,param=param,type='DHU_B')
+        
+        # 前转轮
+        wheel_1_res = components['wheel_1'].model(
+            TinP   = Tin_F,
+            HinP   = Hin_F,
+            FP     = air_flow['wheel_1_FaP'],
+            TinR   = wheel_1_TinR,
+            HinR   = 0,
+            FR     = air_flow['wheel_1_FaR'],
+            engine = engine,
+            param  = param,
+        )
+        
+        # 处理侧混风(回风)
+        mixed_1_res = components['mixed_1'].model(
+            TinA   = wheel_1_res['ToutP'],
+            HinA   = wheel_1_res['HoutP'],
+            FA     = air_flow['mixed_1_FaA'],
+            TinM   = mixed_1_TinM,
+            HinM   = mixed_1_HinM,
+            FM     = air_flow['mixed_1_FaM'],
+            engine = engine
+        )
+        
+        # 中表冷
+        coil_2_res = components['coil_2'].model(
+            TinA   = mixed_1_res['ToutA'],
+            HinA   = mixed_1_res['HoutA'],
+            FA     = air_flow['coil_2_FaA'],
+            TinW   = coil_2_TinW,
+            FW     = coil_2_FW,
+            engine = engine,
+            param  = param['coil_2']
+        )
+        
+        # 后转轮
+        wheel_2_res = components['wheel_2'].model(
+            TinP   = coil_2_res['ToutA'],
+            HinP   = coil_2_res['HoutA'],
+            FP     = air_flow['wheel_2_FaP'],
+            TinC   = mixed_1_res['ToutA'],
+            HinC   = mixed_1_res['HoutA'],
+            FC     = air_flow['wheel_2_FaC'],
+            TinR   = wheel_2_TinR,
+            HinR   = 0,
+            FR     = air_flow['wheel_2_FaR'],
+            engine = engine,
+            param  = param['wheel_2'],
+        )
+        
+        # 后表冷
+        coil_3_res = components['coil_3'].model(
+            TinA   = wheel_2_res['ToutP'],
+            HinA   = wheel_2_res['HoutP'],
+            FA     = air_flow['coil_3_FaA'],
+            TinW   = coil_3_TinW,
+            FW     = coil_3_FW,
+            engine = engine,
+            param  = param['coil_3']
+        )
+        
+        
+        # 后转轮湿度修正
+        wheel_2_res_adj = components['wheel_2'].model(
+            TinP   = coil_2_res['ToutA'],
+            HinP   = coil_2_res['HoutA'],
+            FP     = air_flow['wheel_2_FaP'],
+            TinC   = mixed_1_res['ToutA'],
+            HinC   = mixed_1_res['HoutA'],
+            FC     = air_flow['wheel_2_FaC'],
+            TinR   = wheel_2_TinR,
+            HinR   = wheel_2_res['HoutC'],
+            FR     = air_flow['wheel_2_FaR'],
+            engine = engine,
+            param  = param['wheel_2'],
+        )
+        
+        # 再生侧混风(排风)
+        mixed_2_res = components['mixed_2'].model(
+            TinA   = wheel_2_res_adj['ToutR'],
+            HinA   = wheel_2_res_adj['HoutR'],
+            FA     = air_flow['mixed_2_FaA'],
+            TinM   = mixed_2_TinM,
+            HinM   = mixed_2_HinM,
+            FM     = air_flow['mixed_2_FaM'],
+            engine = engine
+        )
+        
+        # 前转轮湿度修正
+        wheel_1_res_adj = components['wheel_1'].model(
+            TinP   = Tin_F,
+            HinP   = Hin_F,
+            FP     = air_flow['wheel_1_FaP'],
+            TinR   = wheel_1_TinR,
+            HinR   = mixed_2_res['HoutA'],
+            FR     = air_flow['wheel_1_FaR'],
+            engine = engine,
+            param  = param,
+        )
+        
+        # 前蒸气盘管
+        steamcoil_1_res = components['steamcoil_1'].model(
+            TinA   = mixed_2_res['ToutA'],
+            ToutA  = wheel_1_TinR,
+            FA     = air_flow['steamcoil_1_Fa'],
+            param  = param['steamcoil_1'],
+            engine = engine
+        )
+        
+        # 后蒸气盘管
+        steamcoil_2_res = components['steamcoil_2'].model(
+            TinA   = wheel_2_res_adj['ToutC'],
+            ToutA  = wheel_2_TinR,
+            FA     = air_flow['steamcoil_2_Fa'],
+            param  = param['steamcoil_2'],
+            engine = engine
+        )
+        
+        return {
+            'coil_2'     : coil_2_res,
+            'coil_3'     : coil_3_res,
+            'wheel_1'    : wheel_1_res_adj,
+            'wheel_2'    : wheel_2_res_adj,
+            'mixed_1'    : mixed_1_res,
+            'mixed_2'    : mixed_2_res,
+            'steamcoil_1': steamcoil_1_res,
+            'steamcoil_2': steamcoil_2_res,
+            'Fa'         : air_flow,
+            'summary'    : {
+                'Fs':steamcoil_1_res['Fs'] + steamcoil_2_res['Fs'],
+            }
+        }
+        
+        
+        

+ 1 - 1
model/_base/_base.py

@@ -155,7 +155,7 @@ class BaseModel:
         # 设备模型组件对应模型管理中的文件
         if source == 'file':
             MODEL_PACKAGE   = importlib.import_module(
-                '..models.model_func', package='.'.join(__name__.split('.')[:-1]))
+                '...models.model_func', package='.'.join(__name__.split('.')[:-1]))
             if reload:
                 importlib.reload(MODEL_PACKAGE)
             model_info     = getattr(MODEL_PACKAGE,'model')

+ 77 - 1
model/_base/_base_device.py

@@ -1,14 +1,90 @@
 import numpy as np
 import pandas as pd
-import plotnine as gg
+from sklearn.metrics import (
+    r2_score,
+    mean_absolute_error,
+    mean_absolute_percentage_error
+)
+try:
+    import plotnine as gg
+except:
+    pass
 
 from ._base import BaseModel
 
+
+
+
 class BaseDevice(BaseModel):
     
     def __init__(self) -> None:
         super().__init__()
     
+    def predict(self,input_data:pd.DataFrame) -> dict:
+        param_posterior = self.model_info['model_ATD']
+        res = self.model(
+            **{k:input_data.loc[:,v].values for k,v in self.model_input_data_columns.items()},
+            engine       = 'numpy',
+            components   = self.components,
+            param        = param_posterior
+        )
+        return res
+    
+    def predict_system(self,input_data : pd.DataFrame) -> pd.DataFrame:
+        pred_res = self.predict(input_data)
+        system_output = {}
+        for equp_name,output_info in pred_res.items():
+            for output_name,output_value in output_info.items():
+                system_output[f'{equp_name}_{output_name}'] = output_value
+        system_output = pd.DataFrame(system_output)
+        return system_output
+    
+    def get_TVP(self,posterior:dict,observed_data:pd.DataFrame):
+        TVP_data = []
+        for param_name in posterior.keys():
+            if param_name.replace('_mu','') not in observed_data.columns:
+                continue
+            TVP_data.append(
+                pd.DataFrame(
+                    {
+                        'idx'       : observed_data.index,
+                        'param_name': param_name.replace('_mu',''),
+                        'real'      : observed_data.loc[:,param_name.replace('_mu','')].values,
+                        'pred'      : posterior[param_name]
+                    }
+                )
+            )
+        TVP_data = pd.concat(TVP_data,axis=0)
+        return TVP_data
+    
+    def get_metric(self,TVP:pd.DataFrame):
+        group_by_data = TVP.groupby(['param_name'])[['pred','real']]
+        TVP_metric = (
+            pd.concat(
+                [
+                    group_by_data.apply(lambda dt:r2_score(dt.real,dt.pred)),
+                    group_by_data.apply(lambda dt:mean_absolute_error(dt.real,dt.pred)),
+                    group_by_data.apply(lambda dt:mean_absolute_percentage_error(dt.real,dt.pred)),
+                ],
+                axis=1
+            )
+            .set_axis(['R2','MAE','MAPE'],axis=1)
+            .sort_values(by='R2',ascending=True)
+        )
+        return TVP_metric
+    
+    def plot_TVP(self,TVP):
+        plot = (
+            TVP
+            .pipe(gg.ggplot)
+            + gg.aes(x='real',y='pred')
+            + gg.geom_point()
+            + gg.facet_wrap(facets='param_name',scales='free')
+            + gg.geom_abline(intercept=0,slope=1,color='red')
+            + gg.theme(figure_size=[10,10])
+        )
+        return plot
+    
     def curve(
         self,
         input_data: pd.DataFrame,

+ 3 - 52
model/utils/fit_utils.py

@@ -1,13 +1,14 @@
 import numpy as np
 import pandas as pd
 import pymc as pm
-from sklearn.metrics import r2_score,mean_absolute_error,mean_absolute_percentage_error
 
 def record(name,var):
     pm.Deterministic(f'{name}_mu',var)
 
 def observe(name,var,observed,sigma=1):
     if isinstance(observed,pd.DataFrame):
+        if name not in observed.columns:
+            raise Exception(f'observed data中未找到{name}')
         observed = observed.loc[:,name].values
     mu    = pm.Deterministic(f'{name}_mu',var)
     sigma = pm.HalfNormal(f'{name}_sigma',sigma=sigma)
@@ -21,58 +22,8 @@ def reorder_posterior(prior:dict,posterior:dict):
             if '__' in param_name:
                 continue
             if param_name == 'F_air_val_rw':
-                param_value = np.median(param_value[-5:])
+                param_value = param_value[-1]
             if param_name.startswith(equp_name):
                 param_name_adj = param_name.replace(f'{equp_name}_','')
                 param_posterior_reorder[equp_name][param_name_adj] = param_value
     return param_posterior_reorder
-
-def get_fitted_result(
-    posterior    : dict,
-    observed_data: pd.DataFrame,
-    plot_TVP     : bool
-) -> tuple:
-    # 样本内预测数据
-    TVP_data = []
-    for param_name in posterior.keys():
-        if param_name.replace('_mu','') not in observed_data.columns:
-            continue
-        TVP_data.append(
-            pd.DataFrame(
-                {
-                    'param_name': param_name.replace('_mu',''),
-                    'real'      : observed_data.loc[:,param_name.replace('_mu','')].values,
-                    'pred'      : posterior[param_name]
-                }
-            )
-        )
-    TVP_data = pd.concat(TVP_data,axis=0)
-    
-    group_by_data = TVP_data.groupby(['param_name'])[['pred','real']]
-    TVP_metric = (
-        pd.concat(
-            [
-                group_by_data.apply(lambda dt:r2_score(dt.real,dt.pred)),
-                group_by_data.apply(lambda dt:mean_absolute_error(dt.real,dt.pred)),
-                group_by_data.apply(lambda dt:mean_absolute_percentage_error(dt.real,dt.pred)),
-            ],
-            axis=1
-        )
-        .set_axis(['R2','MAE','MAPE'],axis=1)
-        .sort_values(by='R2',ascending=True)
-    )
-    
-    if plot_TVP:
-        import plotnine as gg
-        plot = (
-            TVP_data
-            .pipe(gg.ggplot)
-            + gg.aes(x='real',y='pred')
-            + gg.geom_point()
-            + gg.facet_wrap(facets='param_name',scales='free')
-            + gg.geom_abline(intercept=0,slope=1,color='red')
-            + gg.theme(figure_size=[10,10])
-        )
-        plot.show()
-        
-    return TVP_data,TVP_metric

+ 49 - 6
tools/config_reader.py

@@ -1,4 +1,5 @@
 from typing import Union
+from datetime import datetime
 
 import pandas as pd
 
@@ -8,23 +9,26 @@ class ConfigReader:
         self.path  = path
         self.equp  = pd.read_excel(self.path,sheet_name='设备')
         self.point = pd.read_excel(self.path,sheet_name='点位')
+        self.app   = pd.read_excel(self.path,sheet_name='程序')
+        self.meta  = pd.read_excel(self.path,sheet_name='元数据')
+        self.meta  = {self.meta.loc[:,'Key'].iat[i]:self.meta.loc[:,'Value'].iat[i] for i in range(len(self.meta))}
     
     @property
     def all_equp_names(self):
         return self.equp.loc[:,'编号'].to_list()
     
-    def get_equp_info(self,equp_name) -> dict:
+    def get_equp_info(self,equp_name,key:str,info_type):
         self.check_equp_is_exist(equp_name)
         equp_info = self.equp.loc[self.equp.loc[:,'编号']==equp_name,'名称':].to_dict(orient='list')
         equp_info = {k:v[0] for k,v in equp_info.items()}
-        return equp_info
+        info_value = equp_info[key]
+        info_value = convert_info_type(info_value,info_type)
+        return info_value
     
     def get_equp_point(self,equp_name,equp_class:Union[str,list]=None) -> dict:
-        
         # 点位类型
         # A: 模型输入
         # B: 模型输出
-        
         self.check_equp_is_exist(equp_name)
         equp_class = self.point.类型.to_list() if equp_class is None else equp_class
         equp_class = [equp_class] if isinstance(equp_class,str) else equp_class
@@ -37,8 +41,47 @@ class ConfigReader:
                 if isinstance(new_point_id,str):
                     point_map[point_name] = new_point_id
         return point_map
-        
     
+    def get_app_info(
+        self,
+        equp_name: str,
+        app_type : str,
+        key      : str,
+        info_type: str = None
+    ) -> str:
+        value = self.app.loc[lambda dt:(dt.程序类型==app_type) & (dt.项目==key)]
+        if value.shape[0] != 1:
+            raise Exception(f'程序类型({app_type})下不存在唯一的的项目({key})')
+        info_value = value.loc[:,'全局配置'].iat[0]
+        # 更新全局配置
+        if equp_name in value.columns:
+            equp_value = value.loc[:,equp_name].iat[0]
+            if isinstance(equp_value,str):
+                info_value = equp_value
+        # 调整数据类型
+        info_value = convert_info_type(info_value,info_type)
+        
+        return info_value
+        
     def check_equp_is_exist(self,equp_name):
         if equp_name not in self.all_equp_names:
-            raise Exception(f"设备{equp_name}不存在")
+            raise Exception(f"设备{equp_name}不存在")
+
+
+def convert_info_type(info_value,info_type):
+    if info_type == 'int':
+        info_value = int(info_value)
+    elif info_type == 'float':
+        info_value = float(info_value)
+    elif info_type == 'bool':
+        info_value = bool(info_value)
+    elif info_type == 'datetime':
+        if not isinstance(info_value,datetime):
+            info_value = datetime.strptime(info_value,'%Y/%m/%d %H:%M:%S')
+    elif info_type == 'str':
+        info_value = str(info_value)
+    elif info_type is None:
+        pass
+    else:
+        raise ValueError(f"{info_type} type error!")
+    return info_value

+ 1 - 1
tools/data_loader.py

@@ -104,7 +104,7 @@ class DataLoader:
                 pd.to_pickle(Hr,os.path.join(equp_path,file.replace('_D','_H')))
                 
     
-    def get_equp_data(self,equp_name:str):
+    def get_equp_data(self,equp_name:str) -> pd.DataFrame:
         equp_path     = os.path.join(self.path,equp_name)
         all_file_path = os.listdir(equp_path)
         all_data      = []

+ 19 - 1
tools/enthalpy.py

@@ -7,6 +7,19 @@ except:
 
 PRESSURE = 101325
 
+def get_HumRatio_from_Tdb_and_RH(Tdb, RH, engine):
+    """
+    float: 含湿量 W (kg/kg干空气)
+    """
+    if engine == 'pymc':
+        EXP = pm.math.exp
+    elif engine == 'numpy':
+        EXP = np.exp
+    Psat     = 611.2 * EXP(17.67 * Tdb / (Tdb + 243.5))
+    Pv       = RH * Psat
+    HumRatio = 0.622 * Pv / (PRESSURE - Pv)
+    return HumRatio
+
 # 计算含湿量
 def get_HumRatio_from_Dew(dew_point,engine):
     """
@@ -119,4 +132,9 @@ def get_RH_from_Tdb_and_Hr(Tdb, Hr, engine):
     e = Hr * PRESSURE / (0.621945 + Hr)  # 0.621945 ≈ 分子量比 (18.01528 / 28.966)
     # 3. 计算相对湿度 (RH)
     RH = e / es
-    return RH
+    return RH
+
+def get_VP_from_Hr(Hr):
+    # 水蒸气分压力
+    VP = Hr * PRESSURE / (0.621945 + Hr)
+    return VP

+ 50 - 15
tools/optimizer.py

@@ -1,4 +1,8 @@
+import os
+import sys
 import time 
+import warnings
+from contextlib import contextmanager
 
 import pandas as pd
 
@@ -14,8 +18,15 @@ def optimizer(
     opt_var_boundary: dict,
     opt_var_value   : pd.DataFrame,
     oth_var_value   : pd.DataFrame,
-    constrains      : list
+    target          : str,
+    target_min      : bool,
+    constrains      : list,
+    other_kwargs    : dict = None,
+    logging         : bool = True
 ):
+    if other_kwargs is None:
+        other_kwargs = {}
+    
     var_name_opt  = []
     boundary_info = {}
     for var_name,var_info in opt_var_boundary.items():
@@ -39,25 +50,29 @@ def optimizer(
         oth_var_info[var_name] = oth_var_value.iloc[[0],:].loc[:,[var_name]]
 
     opt_config = sim_opt_config(
-        target        = 'Fs',
-        dir_min       = True,
+        target        = target,
+        dir_min       = target_min,
         var_opt       = var_name_opt,
         var_sys       = var_name_oth,
         diag_model    = False,
         algorithm     = 'soea_DE_best_1_L_templet',
-        NIND          = 1000,
-        MAXGEN        = 200,
+        NIND          = other_kwargs.get('NIND',1000),
+        MAXGEN        = other_kwargs.get('MAXGEN',100),
         constrains    = constrains,
         allow_neg_opt = False,
     )
     
-    opt_output  = main_opt(
-        *list(boundary_info.values()),
-        *list(oth_var_info.values()),
-        system_model = AirSystem(model=model),
-        config       = opt_config
-    )
-    return opt_output
+    with suppress_output(show=logging):
+        opt_output  = main_opt(
+            *list(boundary_info.values()),
+            *list(oth_var_info.values()),
+            system_model = AirSystem(model=model),
+            config       = opt_config
+        )
+    return {
+        'opt_summary': opt_output[:3],
+        'opt_var'    : opt_output[3:],
+    }
 
 class AirSystem(SystemModel):
     def __init__(self,model):
@@ -68,9 +83,29 @@ class AirSystem(SystemModel):
         time_start   = time.time()
         self.PENALTY = {}
         self.index   = data.index
-        sys_out      = self.model.predict_system(data)
-        time_end     = time.time()
-        past_time    = round(time_end-time_start,2)
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore",category=RuntimeWarning)
+            sys_out = self.model.predict_system(data)
+        time_end  = time.time()
+        past_time = round(time_end-time_start,2)
         self.PAST_TIME.append(past_time)
         # print(f'第{len(self.PAST_TIME)}次调用系统模型,本次调用时长为:{past_time}秒 \n')    
         return sys_out
+
+@contextmanager
+def suppress_output(show=True):
+    """
+    上下文管理器,根据参数控制是否显示输出
+    
+    参数:
+        show (bool): 如果为True则显示输出,False则隐藏输出
+    """
+    if not show:
+        original_stdout = sys.stdout
+        sys.stdout = open(os.devnull, 'w')
+    
+    try:
+        yield
+    finally:
+        if not show:
+            sys.stdout = original_stdout