Reorganize algorithm packages by business responsibility, move orchestration into services, and keep database access behind pooled repositories. Harden analysis API validation, remove unsafe legacy simulation endpoints, and add regression and architecture boundary coverage. BREAKING CHANGE: legacy algorithm module paths and obsolete simulation endpoints are removed.
662 lines
26 KiB
Python
662 lines
26 KiB
Python
from __future__ import annotations
|
||
|
||
import json
|
||
from datetime import datetime
|
||
from functools import wraps
|
||
from math import pi, sqrt
|
||
|
||
import pytz
|
||
|
||
import app.services.simulation as simulation
|
||
from app.native.wndb.core.projects import temporary_project_database
|
||
from app.native.wndb.core.database import ChangeSet
|
||
from app.native.wndb.model.demands import get_demand, set_demand
|
||
from app.native.wndb.model.elements import get_node_links, is_junction
|
||
from app.native.wndb.model.emitters import get_emitter, set_emitter
|
||
from app.native.wndb.model.options import (
|
||
OPTION_DEMAND_MODEL_PDA,
|
||
OPTION_QUALITY_CHEMICAL,
|
||
get_option,
|
||
set_option,
|
||
)
|
||
from app.native.wndb.model.patterns import add_pattern, get_pattern
|
||
from app.native.wndb.model.pipes import get_pipe
|
||
from app.native.wndb.model.sources import (
|
||
SOURCE_TYPE_SETPOINT,
|
||
add_source,
|
||
get_source,
|
||
set_source,
|
||
)
|
||
from app.native.wndb.model.times import get_time, set_time
|
||
from app.domain.time import parse_clock_duration_seconds
|
||
|
||
|
||
def _isolated_analysis(purpose: str):
|
||
def decorator(func):
|
||
@wraps(func)
|
||
def wrapper(name: str, *args, **kwargs):
|
||
with temporary_project_database(name, purpose) as temporary:
|
||
kwargs["_temporary_project"] = temporary
|
||
return func(name, *args, **kwargs)
|
||
|
||
return wrapper
|
||
|
||
return decorator
|
||
|
||
|
||
############################################################
|
||
# burst analysis 01
|
||
############################################################
|
||
def convert_to_local_unit(proj: str, emitters: float) -> float:
|
||
proj_opt = get_option(proj)
|
||
str_unit = proj_opt.get("UNITS")
|
||
|
||
if str_unit == "CMH":
|
||
return emitters * 3.6
|
||
elif str_unit == "LPS":
|
||
return emitters
|
||
elif str_unit == "CMS":
|
||
return emitters / 1000.0
|
||
elif str_unit == "MGD":
|
||
return emitters * 0.0438126
|
||
|
||
# Unknown unit: log and return original value
|
||
print(str_unit)
|
||
return emitters
|
||
|
||
|
||
@_isolated_analysis("burst_analysis")
|
||
def burst_analysis(
|
||
name: str,
|
||
modify_pattern_start_time: str,
|
||
burst_ID: list | str = None,
|
||
burst_size: list | float | int = None,
|
||
modify_total_duration: int = 900,
|
||
modify_fixed_pump_pattern: dict[str, list] = None,
|
||
modify_variable_pump_pattern: dict[str, list] = None,
|
||
modify_valve_opening: dict[str, float] = None,
|
||
scheme_name: str = None,
|
||
username: str | None = None,
|
||
_temporary_project: str | None = None,
|
||
) -> None:
|
||
"""
|
||
爆管模拟
|
||
:param name: 模型名称,数据库中对应的名字
|
||
:param modify_pattern_start_time: 模拟开始时间,格式为'2024-11-25T09:00:00+08:00'
|
||
:param burst_ID: 爆管管道的ID,选取的是管道,单独传入一个爆管管道,可以是str或list,传入多个爆管管道是用list
|
||
:param burst_size: 爆管管道破裂的孔口面积,和burst_ID列表各位置的ID对应,以cm*cm计算
|
||
:param modify_total_duration: 模拟总历时,秒
|
||
:param modify_fixed_pump_pattern: dict中包含多个水泵模式,str为工频水泵的id,list为修改后的pattern
|
||
:param modify_variable_pump_pattern: dict中包含多个水泵模式,str为变频水泵的id,list为修改后的pattern
|
||
:param modify_valve_opening: dict中包含多个阀门开启度,str为阀门的id,float为修改后的阀门开启度
|
||
:param scheme_name: 方案名称
|
||
:return:
|
||
"""
|
||
if not username:
|
||
raise ValueError("username is required when storing burst analysis scheme")
|
||
|
||
scheme_detail: dict = {
|
||
"burst_ID": burst_ID,
|
||
"burst_size": burst_size,
|
||
"modify_total_duration": modify_total_duration,
|
||
"modify_fixed_pump_pattern": modify_fixed_pump_pattern,
|
||
"modify_variable_pump_pattern": modify_variable_pump_pattern,
|
||
"modify_valve_opening": modify_valve_opening,
|
||
}
|
||
print(
|
||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||
+ " -- Start Analysis."
|
||
)
|
||
if _temporary_project is None:
|
||
raise RuntimeError("Burst analysis isolation was not prepared")
|
||
new_name = _temporary_project
|
||
print(
|
||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||
+ " -- Start Copying Database."
|
||
)
|
||
print(
|
||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||
+ " -- Start Opening Database."
|
||
)
|
||
simulation.run_simulation(
|
||
name=new_name,
|
||
simulation_type="manually_temporary",
|
||
modify_pattern_start_time=modify_pattern_start_time,
|
||
)
|
||
print(
|
||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||
+ " -- Database Loading OK."
|
||
)
|
||
##step 1 set the emitter coefficient of end node of busrt pipe
|
||
if isinstance(burst_ID, list):
|
||
if (burst_size is not None) and (type(burst_size) is not list):
|
||
return json.dumps("Type mismatch.")
|
||
# 转化为列表形式
|
||
elif isinstance(burst_ID, str):
|
||
burst_ID = [burst_ID]
|
||
if burst_size is not None:
|
||
if isinstance(burst_size, float) or isinstance(burst_size, int):
|
||
burst_size = [burst_size]
|
||
else:
|
||
return json.dumps("Type mismatch.")
|
||
else:
|
||
return json.dumps("Type mismatch.")
|
||
if burst_size is None:
|
||
burst_size = [-1] * len(burst_ID)
|
||
elif len(burst_size) < len(burst_ID):
|
||
burst_size += [-1] * (len(burst_ID) - len(burst_size))
|
||
elif len(burst_size) > len(burst_ID):
|
||
# burst_size = burst_size[:len(burst_ID)]
|
||
return json.dumps("Length mismatch.")
|
||
for burst_ID_, burst_size_ in zip(burst_ID, burst_size):
|
||
pipe = get_pipe(new_name, burst_ID_)
|
||
str_start_node = pipe["node1"]
|
||
str_end_node = pipe["node2"]
|
||
d_pipe = pipe["diameter"] / 1000.0
|
||
if burst_size_ <= 0:
|
||
burst_size_ = 3.14 * d_pipe * d_pipe / 4 / 8
|
||
else:
|
||
burst_size_ = burst_size_ / 10000
|
||
emitter_coeff = (
|
||
0.65 * burst_size_ * sqrt(19.6) * 1000
|
||
) # 1/8开口面积作为coeff,单位 L/S
|
||
emitter_coeff = convert_to_local_unit(new_name, emitter_coeff)
|
||
emitter_node = ""
|
||
if is_junction(new_name, str_end_node):
|
||
emitter_node = str_end_node
|
||
elif is_junction(new_name, str_start_node):
|
||
emitter_node = str_start_node
|
||
old_emitter = get_emitter(new_name, emitter_node)
|
||
if old_emitter != None:
|
||
old_emitter["coefficient"] = emitter_coeff # 爆管的emitter coefficient设置
|
||
else:
|
||
old_emitter = {"junction": emitter_node, "coefficient": emitter_coeff}
|
||
new_emitter = ChangeSet()
|
||
new_emitter.append(old_emitter)
|
||
set_emitter(new_name, new_emitter)
|
||
# step 2. run simulation
|
||
# 涉及关阀计算,可能导致关阀后仍有流量,改为压力驱动PDA
|
||
options = get_option(new_name)
|
||
options["DEMAND MODEL"] = OPTION_DEMAND_MODEL_PDA
|
||
options["REQUIRED PRESSURE"] = "10.0000"
|
||
cs_options = ChangeSet()
|
||
cs_options.append(options)
|
||
set_option(new_name, cs_options)
|
||
# valve_control = None
|
||
# if modify_valve_opening is not None:
|
||
# valve_control = {}
|
||
# for valve in modify_valve_opening:
|
||
# valve_control[valve] = {'status': 'CLOSED'}
|
||
# result = run_simulation_ex(new_name,'realtime', modify_pattern_start_time,
|
||
# end_datetime=modify_pattern_start_time,
|
||
# modify_total_duration=modify_total_duration,
|
||
# modify_pump_pattern=modify_pump_pattern,
|
||
# valve_control=valve_control,
|
||
# downloading_prohibition=True)
|
||
simulation.run_simulation(
|
||
name=new_name,
|
||
simulation_type="extended",
|
||
modify_pattern_start_time=modify_pattern_start_time,
|
||
modify_total_duration=modify_total_duration,
|
||
modify_fixed_pump_pattern=modify_fixed_pump_pattern,
|
||
modify_variable_pump_pattern=modify_variable_pump_pattern,
|
||
modify_valve_opening=modify_valve_opening,
|
||
scheme_type="burst_analysis",
|
||
scheme_name=scheme_name,
|
||
result_db_name=name,
|
||
scheme_username=username,
|
||
scheme_detail=scheme_detail,
|
||
)
|
||
# step 3. restore the base model status
|
||
# execute_undo(name) #有疑惑
|
||
|
||
|
||
############################################################
|
||
# valve closing analysis 02
|
||
############################################################
|
||
@_isolated_analysis("valve_close_analysis")
|
||
def valve_close_analysis(
|
||
name: str,
|
||
modify_pattern_start_time: str,
|
||
modify_total_duration: int = 900,
|
||
modify_valve_opening: dict[str, float] = None,
|
||
scheme_name: str = None,
|
||
_temporary_project: str | None = None,
|
||
) -> None:
|
||
"""
|
||
关阀模拟
|
||
:param name: 模型名称,数据库中对应的名字
|
||
:param modify_pattern_start_time: 模拟开始时间,格式为'2024-11-25T09:00:00+08:00'
|
||
:param modify_total_duration: 模拟总历时,秒
|
||
:param modify_valve_opening: dict中包含多个阀门开启度,str为阀门的id,float为修改后的阀门开启度
|
||
:param scheme_name: 方案名称
|
||
:return:
|
||
"""
|
||
print(
|
||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||
+ " -- Start Analysis."
|
||
)
|
||
if _temporary_project is None:
|
||
raise RuntimeError("Valve-close analysis isolation was not prepared")
|
||
new_name = _temporary_project
|
||
print(
|
||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||
+ " -- Start Copying Database."
|
||
)
|
||
print(
|
||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||
+ " -- Start Opening Database."
|
||
)
|
||
print(
|
||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||
+ " -- Database Loading OK."
|
||
)
|
||
# step 1. change the valves status to 'closed'
|
||
# for valve in valves:
|
||
# if not is_valve(new_name,valve):
|
||
# result='ID:{}is not a valve type'.format(valve)
|
||
# return result
|
||
# cs=ChangeSet()
|
||
# status=get_status(new_name,valve)
|
||
# status['status']='CLOSED'
|
||
# cs.append(status)
|
||
# set_status(new_name,cs)
|
||
# step 2. run simulation
|
||
# 涉及关阀计算,可能导致关阀后仍有流量,改为压力驱动PDA
|
||
options = get_option(new_name)
|
||
options["DEMAND MODEL"] = OPTION_DEMAND_MODEL_PDA
|
||
options["REQUIRED PRESSURE"] = "20.0000"
|
||
cs_options = ChangeSet()
|
||
cs_options.append(options)
|
||
set_option(new_name, cs_options)
|
||
# result = run_simulation_ex(new_name,'realtime', modify_pattern_start_time, modify_pattern_start_time, modify_total_duration,
|
||
# downloading_prohibition=True)
|
||
simulation.run_simulation(
|
||
name=new_name,
|
||
simulation_type="extended",
|
||
modify_pattern_start_time=modify_pattern_start_time,
|
||
modify_total_duration=modify_total_duration,
|
||
modify_valve_opening=modify_valve_opening,
|
||
scheme_type="valve_close_analysis",
|
||
scheme_name=scheme_name,
|
||
result_db_name=name,
|
||
)
|
||
# step 3. restore the base model
|
||
# for valve in valves:
|
||
# execute_undo(name)
|
||
# return result
|
||
|
||
|
||
############################################################
|
||
# flushing analysis 03
|
||
# Pipe_Flushing_Analysis(prj_name,date_time, Valve_id_list, Drainage_Node_Id, Flushing_flow[opt], Flushing_duration[opt])->out_file:string
|
||
############################################################
|
||
@_isolated_analysis("flushing_analysis")
|
||
def flushing_analysis(
|
||
name: str,
|
||
modify_pattern_start_time: str,
|
||
modify_total_duration: int = 900,
|
||
modify_valve_opening: dict[str, float] = None,
|
||
drainage_node_ID: str = None,
|
||
flushing_flow: float = 0,
|
||
scheme_name: str = None,
|
||
username: str | None = None,
|
||
valve_control: dict[str, dict] = None,
|
||
_temporary_project: str | None = None,
|
||
) -> None:
|
||
"""
|
||
管道冲洗模拟
|
||
:param name: 模型名称,数据库中对应的名字
|
||
:param modify_pattern_start_time: 模拟开始时间,格式为'2024-11-25T09:00:00+08:00'
|
||
:param modify_total_duration: 模拟总历时,秒
|
||
:param modify_valve_opening: dict中包含多个阀门开启度,str为阀门的id,float为修改后的阀门开启度
|
||
:param valve_control: dict中可分别指定阀门的status、setting和k
|
||
:param drainage_node_ID: 冲洗排放口所在节点ID
|
||
:param flushing_flow: 冲洗水量,传入参数单位为m3/h
|
||
:param scheme_name: 方案名称
|
||
:return:
|
||
"""
|
||
if not username:
|
||
raise ValueError("username is required when storing flushing analysis scheme")
|
||
|
||
scheme_detail: dict = {
|
||
"duration": modify_total_duration,
|
||
"valve_opening": modify_valve_opening,
|
||
"valve_control": valve_control,
|
||
"drainage_node_ID": drainage_node_ID,
|
||
"flushing_flow": flushing_flow,
|
||
}
|
||
print(
|
||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||
+ " -- Start Analysis."
|
||
)
|
||
if _temporary_project is None:
|
||
raise RuntimeError("Flushing analysis isolation was not prepared")
|
||
new_name = _temporary_project
|
||
print(
|
||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||
+ " -- Start Copying Database."
|
||
)
|
||
print(
|
||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||
+ " -- Start Opening Database."
|
||
)
|
||
print(
|
||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||
+ " -- Database Loading OK."
|
||
)
|
||
if not is_junction(new_name, drainage_node_ID):
|
||
return "Wrong Drainage node type"
|
||
# step 1. change the valves status to 'closed'
|
||
# for valve, valve_k in zip(valves, valves_k):
|
||
# cs=ChangeSet()
|
||
# status=get_status(new_name,valve)
|
||
# # status['status']='CLOSED'
|
||
# if valve_k == 0:
|
||
# status['status'] = 'CLOSED'
|
||
# elif valve_k < 1:
|
||
# status['status'] = 'OPEN'
|
||
# status['setting'] = 0.1036 * pow(valve_k, -3.105)
|
||
# cs.append(status)
|
||
# set_status(new_name,cs)
|
||
options = get_option(new_name)
|
||
units = options["UNITS"]
|
||
# step 2. set the emitter coefficient of drainage node or add flush flow to the drainage node
|
||
# 新建 pattern
|
||
time_option = get_time(new_name)
|
||
hydraulic_step = time_option["HYDRAULIC TIMESTEP"]
|
||
secs = parse_clock_duration_seconds(hydraulic_step)
|
||
cs_pattern = ChangeSet()
|
||
pt = {}
|
||
factors = []
|
||
tmp_duration = modify_total_duration
|
||
while tmp_duration > 0:
|
||
factors.append(1.0)
|
||
tmp_duration = tmp_duration - secs
|
||
pt["id"] = "flushing_pt"
|
||
pt["factors"] = factors
|
||
cs_pattern.append(pt)
|
||
add_pattern(new_name, cs_pattern)
|
||
# 为 emitter_demand 添加新的 pattern
|
||
emitter_demand = get_demand(new_name, drainage_node_ID)
|
||
cs = ChangeSet()
|
||
if flushing_flow > 0:
|
||
if units == "LPS":
|
||
emitter_demand["demands"].append(
|
||
{
|
||
"demand": flushing_flow / 3.6,
|
||
"pattern": "flushing_pt",
|
||
"category": None,
|
||
}
|
||
)
|
||
elif units == "CMH":
|
||
emitter_demand["demands"].append(
|
||
{"demand": flushing_flow, "pattern": "flushing_pt", "category": None}
|
||
)
|
||
cs.append(emitter_demand)
|
||
set_demand(new_name, cs)
|
||
else:
|
||
pipes = get_node_links(new_name, drainage_node_ID)
|
||
flush_diameter = 50
|
||
for pipe in pipes:
|
||
d = get_pipe(new_name, pipe)["diameter"]
|
||
if flush_diameter < d:
|
||
flush_diameter = d
|
||
flush_diameter /= 1000
|
||
emitter_coeff = (
|
||
0.65 * 3.14 * (flush_diameter * flush_diameter / 4) * sqrt(19.6) * 1000
|
||
) # 全开口面积作为coeff
|
||
|
||
old_emitter = get_emitter(new_name, drainage_node_ID)
|
||
if old_emitter != None:
|
||
old_emitter["coefficient"] = emitter_coeff # 爆管的emitter coefficient设置
|
||
else:
|
||
old_emitter = {"junction": drainage_node_ID, "coefficient": emitter_coeff}
|
||
new_emitter = ChangeSet()
|
||
new_emitter.append(old_emitter)
|
||
set_emitter(new_name, new_emitter)
|
||
# step 3. run simulation
|
||
# 涉及关阀计算,可能导致关阀后仍有流量,改为压力驱动PDA
|
||
options = get_option(new_name)
|
||
options["DEMAND MODEL"] = OPTION_DEMAND_MODEL_PDA
|
||
options["REQUIRED PRESSURE"] = "20.0000"
|
||
cs_options = ChangeSet()
|
||
cs_options.append(options)
|
||
set_option(new_name, cs_options)
|
||
# result = run_simulation_ex(new_name,'realtime', modify_pattern_start_time, modify_pattern_start_time, modify_total_duration,
|
||
# downloading_prohibition=True)
|
||
simulation.run_simulation(
|
||
name=new_name,
|
||
simulation_type="extended",
|
||
modify_pattern_start_time=modify_pattern_start_time,
|
||
modify_total_duration=modify_total_duration,
|
||
modify_valve_opening=modify_valve_opening,
|
||
valve_control=valve_control,
|
||
scheme_type="flushing_analysis",
|
||
scheme_name=scheme_name,
|
||
result_db_name=name,
|
||
scheme_username=username,
|
||
scheme_detail=scheme_detail,
|
||
)
|
||
# step 4. restore the base model
|
||
# return result
|
||
|
||
|
||
############################################################
|
||
# Contaminant simulation 04
|
||
#
|
||
############################################################
|
||
@_isolated_analysis("contaminant_simulation")
|
||
def contaminant_simulation(
|
||
name: str,
|
||
modify_pattern_start_time: str, # 模拟开始时间,格式为'2024-11-25T09:00:00+08:00'
|
||
modify_total_duration: int, # 模拟总历时,秒
|
||
source: str, # 污染源节点ID
|
||
concentration: float, # 污染源浓度,单位mg/L
|
||
scheme_name: str = None,
|
||
source_pattern: str = None, # 污染源时间变化模式名称
|
||
username: str | None = None,
|
||
_temporary_project: str | None = None,
|
||
) -> None:
|
||
"""
|
||
污染模拟
|
||
:param name: 模型名称,数据库中对应的名字
|
||
:param modify_pattern_start_time: 模拟开始时间,格式为'2024-11-25T09:00:00+08:00'
|
||
:param modify_total_duration: 模拟总历时,秒
|
||
:param source: 污染源所在的节点ID
|
||
:param concentration: 污染源位置处的浓度,单位mg/L。默认的污染模拟setting为SOURCE_TYPE_CONCEN(改为SOURCE_TYPE_SETPOINT)
|
||
:param source_pattern: 污染源的时间变化模式,若不传入则默认以恒定浓度持续模拟,时间长度等于duration;
|
||
若传入,则格式为{1.0,0.5,1.1}等系数列表pattern_step模拟等于模型的hydraulic time step
|
||
:param scheme_name: 方案名称
|
||
:return:
|
||
"""
|
||
if not username:
|
||
raise ValueError("username is required when storing contaminant analysis scheme")
|
||
|
||
scheme_detail: dict = {
|
||
"source": source,
|
||
"concentration": concentration,
|
||
"duration": modify_total_duration,
|
||
"pattern": source_pattern,
|
||
}
|
||
print(
|
||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||
+ " -- Start Analysis."
|
||
)
|
||
if _temporary_project is None:
|
||
raise RuntimeError("Contaminant simulation isolation was not prepared")
|
||
new_name = _temporary_project
|
||
print(
|
||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||
+ " -- Start Copying Database."
|
||
)
|
||
print(
|
||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||
+ " -- Start Opening Database."
|
||
)
|
||
print(
|
||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||
+ " -- Database Loading OK."
|
||
)
|
||
dic_time = get_time(new_name)
|
||
dic_time["QUALITY TIMESTEP"] = "0:05:00"
|
||
cs = ChangeSet()
|
||
cs.operations.append(dic_time)
|
||
set_time(new_name, cs) # set QUALITY TIMESTEP
|
||
time_option = get_time(new_name)
|
||
hydraulic_step = time_option["HYDRAULIC TIMESTEP"]
|
||
secs = parse_clock_duration_seconds(hydraulic_step)
|
||
operation_step = 0
|
||
# step 1. set duration
|
||
if modify_total_duration == None:
|
||
modify_total_duration = secs
|
||
# step 2. set pattern
|
||
if source_pattern != None:
|
||
pt = get_pattern(new_name, source_pattern)
|
||
if len(pt) == 0:
|
||
str_response = str("cant find source_pattern")
|
||
return str_response
|
||
else:
|
||
cs_pattern = ChangeSet()
|
||
pt = {}
|
||
factors = []
|
||
tmp_duration = modify_total_duration
|
||
while tmp_duration > 0:
|
||
factors.append(1.0)
|
||
tmp_duration = tmp_duration - secs
|
||
pt["id"] = "contam_pt"
|
||
pt["factors"] = factors
|
||
cs_pattern.append(pt)
|
||
add_pattern(new_name, cs_pattern)
|
||
operation_step += 1
|
||
# step 3. set source/initial quality
|
||
# source quality
|
||
cs_source = ChangeSet()
|
||
source_schema = {
|
||
"node": source,
|
||
"s_type": SOURCE_TYPE_SETPOINT,
|
||
"strength": concentration,
|
||
"pattern": pt["id"],
|
||
}
|
||
cs_source.append(source_schema)
|
||
source_node = get_source(new_name, source)
|
||
if len(source_node) == 0:
|
||
add_source(new_name, cs_source)
|
||
else:
|
||
set_source(new_name, cs_source)
|
||
dict_demand = get_demand(new_name, source)
|
||
for demands in dict_demand["demands"]:
|
||
dict_demand["demands"][dict_demand["demands"].index(demands)]["demand"] = -1
|
||
dict_demand["demands"][dict_demand["demands"].index(demands)]["pattern"] = None
|
||
cs = ChangeSet()
|
||
cs.append(dict_demand)
|
||
set_demand(new_name, cs) # set inflow node
|
||
# # initial quality
|
||
# dict_quality = get_quality(new_name, source)
|
||
# dict_quality['quality'] = concentration
|
||
# cs = ChangeSet()
|
||
# cs.append(dict_quality)
|
||
# set_quality(new_name, cs)
|
||
operation_step += 1
|
||
# step 4 set option of quality to chemical
|
||
opt = get_option(new_name)
|
||
opt["QUALITY"] = OPTION_QUALITY_CHEMICAL
|
||
cs_option = ChangeSet()
|
||
cs_option.append(opt)
|
||
set_option(new_name, cs_option)
|
||
operation_step += 1
|
||
# step 5. run simulation
|
||
# result = run_simulation_ex(new_name,'realtime', modify_pattern_start_time, modify_pattern_start_time, modify_total_duration,
|
||
# downloading_prohibition=True)
|
||
simulation.run_simulation(
|
||
name=new_name,
|
||
simulation_type="extended",
|
||
modify_pattern_start_time=modify_pattern_start_time,
|
||
modify_total_duration=modify_total_duration,
|
||
scheme_type="contaminant_analysis",
|
||
scheme_name=scheme_name,
|
||
result_db_name=name,
|
||
scheme_username=username,
|
||
scheme_detail=scheme_detail,
|
||
)
|
||
|
||
# for i in range(1,operation_step):
|
||
# execute_undo(name)
|
||
|
||
|
||
############################################################
|
||
# pressure regulation 06
|
||
############################################################
|
||
|
||
|
||
@_isolated_analysis("pressure_regulation")
|
||
def pressure_regulation(
|
||
name: str,
|
||
modify_pattern_start_time: str,
|
||
modify_total_duration: int = 900,
|
||
modify_tank_initial_level: dict[str, float] = None,
|
||
modify_fixed_pump_pattern: dict[str, list] = None,
|
||
modify_variable_pump_pattern: dict[str, list] = None,
|
||
scheme_name: str = None,
|
||
scada_mappings: simulation.ScadaElementMappings | None = None,
|
||
_temporary_project: str | None = None,
|
||
) -> None:
|
||
"""
|
||
区域调压模拟,用来模拟未来15分钟内,开关水泵对区域压力的影响
|
||
:param name: 模型名称,数据库中对应的名字
|
||
:param modify_pattern_start_time: 模拟开始时间,格式为'2024-11-25T09:00:00+08:00'
|
||
:param modify_total_duration: 模拟总历时,秒
|
||
:param modify_tank_initial_level: dict中包含多个水塔,str为水塔的id,float为修改后的initial_level
|
||
:param modify_fixed_pump_pattern: dict中包含多个水泵模式,str为工频水泵的id,list为修改后的pattern
|
||
:param modify_variable_pump_pattern: dict中包含多个水泵模式,str为变频水泵的id,list为修改后的pattern
|
||
:param scheme_name: 模拟方案名称
|
||
:return:
|
||
"""
|
||
print(
|
||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||
+ " -- Start Analysis."
|
||
)
|
||
if _temporary_project is None:
|
||
raise RuntimeError("Pressure-regulation isolation was not prepared")
|
||
new_name = _temporary_project
|
||
print(
|
||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||
+ " -- Start Copying Database."
|
||
)
|
||
print(
|
||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||
+ " -- Start Opening Database."
|
||
)
|
||
print(
|
||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||
+ " -- Database Loading OK."
|
||
)
|
||
# 全部关泵后,压力计算不合理,改为压力驱动PDA
|
||
options = get_option(new_name)
|
||
options["DEMAND MODEL"] = OPTION_DEMAND_MODEL_PDA
|
||
options["REQUIRED PRESSURE"] = "15.0000"
|
||
cs_options = ChangeSet()
|
||
cs_options.append(options)
|
||
set_option(new_name, cs_options)
|
||
# result = run_simulation_ex(name=new_name,
|
||
# simulation_type='realtime',
|
||
# start_datetime=start_datetime,
|
||
# duration=900,
|
||
# pump_control=pump_control,
|
||
# tank_initial_level_control=tank_initial_level_control,
|
||
# downloading_prohibition=True)
|
||
simulation.run_simulation(
|
||
name=new_name,
|
||
simulation_type="extended",
|
||
modify_pattern_start_time=modify_pattern_start_time,
|
||
modify_total_duration=modify_total_duration,
|
||
modify_tank_initial_level=modify_tank_initial_level,
|
||
modify_fixed_pump_pattern=modify_fixed_pump_pattern,
|
||
modify_variable_pump_pattern=modify_variable_pump_pattern,
|
||
scheme_type="pressure_regulation",
|
||
scheme_name=scheme_name,
|
||
result_db_name=name,
|
||
scada_mappings=scada_mappings,
|
||
)
|
||
# return result
|