diff --git a/.gitignore b/.gitignore index 0046d56..c713d15 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# vscode +.vscode/ + # python cache __pycache__/ @@ -7,4 +10,6 @@ __pycache__/ # epanet *.rpt *.opt -.vscode/ + +# dev_demo +dev_demo.py diff --git a/api/__init__.py b/api/__init__.py index b8abc42..b8ac9e9 100644 --- a/api/__init__.py +++ b/api/__init__.py @@ -13,7 +13,7 @@ from .operation import pick_operation, sync_with_server from .command import execute_batch_commands -from .s0_base import JUNCTION, RESERVOIR, TANK, PIPE, PUMP, VALVE +from .s0_base import JUNCTION, RESERVOIR, TANK, PIPE, PUMP, VALVE, PATTERN, CURVE from .s0_base import is_node, is_junction, is_reservoir, is_tank from .s0_base import is_link, is_pipe, is_pump, is_valve from .s0_base import is_curve @@ -38,4 +38,25 @@ from .s6_pumps import get_pump_schema, add_pump, get_pump, set_pump, delete_pump from .s7_valves import VALVES_TYPE_PRV, VALVES_TYPE_PSV, VALVES_TYPE_PBV, VALVES_TYPE_FCV, VALVES_TYPE_TCV, VALVES_TYPE_GPV from .s7_valves import get_valve_schema, add_valve, get_valve, set_valve, delete_valve +from .s9_demands import get_demand_schema, get_demand, set_demand + +from .s10_status import LINK_STATUS_OPEN, LINK_STATUS_CLOSED, LINK_STATUS_ACTIVE +from .s10_status import get_status_schema, get_status, set_status + +from .s11_patterns import get_pattern_schema, get_pattern, set_pattern + +from .s12_curves import get_curve_schema, get_curve, set_curve + +from .s16_emitters import get_emitter_schema, get_emitter, set_emitter + +from .s21_times import TIME_STATISTIC_NONE, TIME_STATISTIC_AVERAGED, TIME_STATISTIC_MINIMUM, TIME_STATISTIC_MAXIMUM, TIME_STATISTIC_RANGE +from .s21_times import get_time_schema, get_time, set_time + +from .s23_options import OPTION_UNITS_CFS, OPTION_UNITS_GPM, OPTION_UNITS_MGD, OPTION_UNITS_IMGD, OPTION_UNITS_AFD, OPTION_UNITS_LPS, OPTION_UNITS_LPM, OPTION_UNITS_MLD, OPTION_UNITS_CMH, OPTION_UNITS_CMD +from .s23_options import OPTION_HEADLOSS_HW, OPTION_HEADLOSS_DW, OPTION_HEADLOSS_CM +from .s23_options import OPTION_UNBALANCED_STOP, OPTION_UNBALANCED_CONTINUE +from .s23_options import OPTION_DEMAND_MODEL_DDA, OPTION_DEMAND_MODEL_PDA +from .s23_options import OPTION_QUALITY_NONE, OPTION_QUALITY_CHEMICAL, OPTION_QUALITY_AGE, OPTION_QUALITY_TRACE +from .s23_options import get_option_schema, get_option, set_option + from .s24_coordinates import get_node_coord diff --git a/api/command.py b/api/command.py index 4c7bc0c..791e147 100644 --- a/api/command.py +++ b/api/command.py @@ -5,6 +5,13 @@ from .s4_tanks import * from .s5_pipes import * from .s6_pumps import * from .s7_valves import * +from .s9_demands import * +from .s10_status import * +from .s11_patterns import * +from .s12_curves import * +from .s16_emitters import * +from .s21_times import * +from .s23_options import * def execute_add_command(name: str, cs: ChangeSet) -> ChangeSet: @@ -43,6 +50,20 @@ def execute_update_command(name: str, cs: ChangeSet) -> ChangeSet: return set_pump(name, cs) elif type == VALVE: return set_valve(name, cs) + elif type == 'demand': + return set_demand(name, cs) + elif type == 'status': + return set_status(name, cs) + elif type == PATTERN: + return set_pattern(name, cs) + elif type == CURVE: + return set_curve(name, cs) + elif type == 'emitter': + return set_emitter(name, cs) + elif type == 'time': + return set_time(name, cs) + elif type == 'option': + return set_option(name, cs) return ChangeSet() diff --git a/api/operation.py b/api/operation.py index c1e1c89..e807cf7 100644 --- a/api/operation.py +++ b/api/operation.py @@ -51,6 +51,18 @@ def read(name: str, sql: str) -> Row: return row +def read_all(name: str, sql: str) -> list[Row]: + with conn[name].cursor(row_factory=dict_row) as cur: + cur.execute(sql) + return cur.fetchall() + + +def try_read(name: str, sql: str) -> Row | None: + with conn[name].cursor(row_factory=dict_row) as cur: + cur.execute(sql) + return cur.fetchone() + + def write(name: str, sql: str) -> None: with conn[name].cursor() as cur: cur.execute(sql) diff --git a/api/parser.py b/api/parser.py index b2b7c29..0f68d45 100644 --- a/api/parser.py +++ b/api/parser.py @@ -7,6 +7,14 @@ from .s4_tanks import * from .s5_pipes import * from .s6_pumps import * from .s7_valves import * +from .s9_demands import * +from .s10_status import * +from .s11_patterns import * +from .s12_curves import * +from .s16_emitters import * +from .s21_times import * +from .s23_options import * + def read_inp(name: str, inp: str): if is_project_open(name): @@ -18,19 +26,30 @@ def read_inp(name: str, inp: str): create_project(name) open_project(name) + section = '' + + title : str = '' junctions : dict[str, dict[str, Any]] = {} reservoirs : dict[str, dict[str, Any]] = {} tanks : dict[str, dict[str, Any]] = {} pipes : dict[str, dict[str, Any]] = {} pumps : dict[str, dict[str, Any]] = {} valves : dict[str, dict[str, Any]] = {} - section = '' + demands : dict[str, list[dict[str, Any]]] = {} + status : dict[str, dict[str, Any]] = {} + patterns : dict[str, list[float]] = {} + curves : dict[str, list[dict[str, float]]] = {} + emitters : dict[str, float] = {} + times : dict[str, str] = {} + options : dict[str, str] = {} for line in open(inp): - line = line.lstrip() + line = line.strip() if line.startswith(';'): continue + if line.endswith(';'): + line = line.removesuffix(';') if line.startswith('[TITLE'): section = 'title' @@ -53,6 +72,27 @@ def read_inp(name: str, inp: str): if line.startswith('[VALVE'): section = VALVE continue + if line.startswith('[DEMAND'): + section = 'demand' + continue + if line.startswith('[STATUS'): + section = 'status' + continue + if line.startswith('[PATTERN'): + section = PATTERN + continue + if line.startswith('[CURVE'): + section = CURVE + continue + if line.startswith('[EMITTER'): + section = 'emitter' + continue + if line.startswith('[TIME'): + section = 'time' + continue + if line.startswith('[OPTION'): + section = 'option' + continue if line.startswith('[COORDINATE'): section = 'coordinate' continue @@ -60,38 +100,98 @@ def read_inp(name: str, inp: str): section = '' continue - tokens = line.split() - if len(tokens) == 0: + tokens = [] + if section == 'time' or section == 'option': + tokens = line.upper().split() + else: + tokens = line.split() + tokens_len = len(tokens) + if tokens_len == 0: continue if section == 'title': - # set_title(name, ChangeSet({'value': tokens[0]})) + if title == '': + title += '\n' + title += line continue elif section == JUNCTION: - if tokens[0] not in junctions: - junctions[tokens[0]] = {} - junctions[tokens[0]] |= {'id': tokens[0], 'elevation': tokens[1]} + junction_demand = float(tokens[2]) if tokens_len >= 3 else None + junction_pattern = tokens[3] if tokens_len == 4 else None + junctions[tokens[0]] = {'id': tokens[0], 'elevation': tokens[1], 'demand': junction_demand, 'pattern': junction_pattern} continue elif section == RESERVOIR: - if tokens[0] not in reservoirs: - reservoirs[tokens[0]] = {} - reservoirs[tokens[0]] |= {'id': tokens[0], 'head': tokens[1]} + reservoir_pattern = tokens[2] if tokens_len == 3 else None + reservoirs[tokens[0]] = {'id': tokens[0], 'head': tokens[1], 'pattern': reservoir_pattern} continue elif section == TANK: - if tokens[0] not in tanks: - tanks[tokens[0]] = {} - tanks[tokens[0]] = {'id': tokens[0], 'elevation': tokens[1], 'init_level': tokens[2], 'min_level': tokens[3], 'max_level': tokens[4], 'diameter': tokens[5], 'min_vol': tokens[6]} + tank_vol_curve = tokens[7] if tokens_len >= 8 else None + tank_overflow = tokens[8].upper() if tokens_len == 9 else None + tanks[tokens[0]] = {'id': tokens[0], 'elevation': tokens[1], 'init_level': tokens[2], 'min_level': tokens[3], 'max_level': tokens[4], 'diameter': tokens[5], 'min_vol': tokens[6], 'vol_curve': tank_vol_curve, 'overflow': tank_overflow} continue elif section == PIPE: - if len(tokens) == 7: - tokens.append(PIPE_STATUS_OPEN) - pipes[tokens[0]] = {'id': tokens[0], 'node1': tokens[1], 'node2': tokens[2], 'length': tokens[3], 'diameter': tokens[4], 'roughness': tokens[5], 'minor_loss': tokens[6], 'status': tokens[7].lower()} + # status is must-have, here fix input + pipe_status = tokens[7].upper() if tokens_len == 8 else PIPE_STATUS_OPEN + pipes[tokens[0]] = {'id': tokens[0], 'node1': tokens[1], 'node2': tokens[2], 'length': tokens[3], 'diameter': tokens[4], 'roughness': tokens[5], 'minor_loss': tokens[6], 'status': pipe_status} continue elif section == PUMP: pumps[tokens[0]] = {'id': tokens[0], 'node1': tokens[1], 'node2': tokens[2]} + for i in range(3, tokens_len, 2): + pumps[tokens[0]] |= { tokens[i].lower(): tokens[i + 1] } continue elif section == VALVE: - valves[tokens[0]] = {'id': tokens[0], 'node1': tokens[1], 'node2': tokens[2], 'diameter': tokens[3], 'v_type': tokens[4].lower(), 'setting': tokens[5], 'minor_loss': tokens[6]} + valves[tokens[0]] = {'id': tokens[0], 'node1': tokens[1], 'node2': tokens[2], 'diameter': tokens[3], 'v_type': tokens[4], 'setting': tokens[5], 'minor_loss': tokens[6]} + continue + elif section == 'demand': + demand_pattern = tokens[2] if tokens_len >= 3 else None + demand_category = tokens[3] if tokens_len == 4 else None + demands[tokens[0]].append({'demand': tokens[1], 'pattern': demand_pattern, 'category': demand_category}) + continue + elif section == 'status': + if tokens[0] not in status: + status[tokens[0]] = {} + setting = None + try: + setting = float(tokens[1]) + except: + setting = None + if setting != None: + status[tokens[0]]['setting'] = setting + else: + status[tokens[0]]['status'] = tokens[1].upper() + continue + elif section == PATTERN: + if tokens[0] not in patterns: + patterns[tokens[0]] = [] + for i in range(1, tokens_len): + patterns[tokens[0]].append(float(tokens[i])) + continue + elif section == CURVE: + if tokens[0] not in curves: + curves[tokens[0]] = [] + for i in range(1, tokens_len, 2): + curves[tokens[0]].append({ 'x': float(tokens[i]), 'y': float(tokens[i + 1]) }) + continue + elif section == 'emitter': + emitters[tokens[0]] = float(tokens[1]) + continue + elif section == 'time': + if tokens_len == 2: + times[tokens[0]] = tokens[1] + elif tokens_len == 3: + times[tokens[0] + ' ' + tokens[1]] = tokens[2] + elif tokens_len == 4: + times[tokens[0] + ' ' + tokens[1]] = tokens[2] + ' ' + tokens[3] + continue + elif section == 'option': + if tokens[0] == 'HYDRAULICS' or tokens[0] == 'MAP': + continue + if tokens_len == 2: + options[tokens[0]] = tokens[1] + elif tokens_len == 3: + if tokens[0] == 'UNBALANCED' or tokens[0] == 'QUALITY': + options[tokens[0]] = tokens[1] + ' ' + tokens[2] + else: + options[tokens[0] + ' ' + tokens[1]] = tokens[2] continue elif section == 'coordinate': if tokens[0] in junctions: @@ -102,27 +202,69 @@ def read_inp(name: str, inp: str): tanks[tokens[0]] |= {'x': tokens[1], 'y': tokens[2]} continue + # title + set_title(name, ChangeSet({ 'value': title })) + + # pattern + for key, value in patterns.items(): + set_pattern(name, ChangeSet({'id': key, 'factors': value})) + + # curve + for key, value in curves.items(): + set_curve(name, ChangeSet({'id': key, 'coords': value})) + + # junction for value in junctions.values(): if 'x' not in value: value['x'] = 0.0 if 'y' not in value: value['y'] = 0.0 add_junction(name, ChangeSet(value)) + + # reservoir for value in reservoirs.values(): if 'x' not in value: value['x'] = 0.0 if 'y' not in value: value['y'] = 0.0 add_reservoir(name, ChangeSet(value)) + + # tank for value in tanks.values(): if 'x' not in value: value['x'] = 0.0 if 'y' not in value: value['y'] = 0.0 add_tank(name, ChangeSet(value)) + + # pipe for value in pipes.values(): add_pipe(name, ChangeSet(value)) + + # pump for value in pumps.values(): add_pump(name, ChangeSet(value)) + + # valve for value in valves.values(): add_valve(name, ChangeSet(value)) + + # demand + for key, value in demands.items(): + set_demand(name, ChangeSet({'junction': key, 'demands': value})) + + # status + for key, value in status.items(): + set_status(name, ChangeSet({'link': key} | value)) + + # emitter + for key, value in emitters.items(): + set_emitter(name, ChangeSet({'junction': key, 'coefficient': value})) + + # time + set_time(name, ChangeSet(times)) + + # option + set_option(name, ChangeSet(options)) + + close_project(name) diff --git a/api/s0_base.py b/api/s0_base.py index 006373a..899f3fa 100644 --- a/api/s0_base.py +++ b/api/s0_base.py @@ -3,17 +3,20 @@ from .connection import g_conn_dict as conn from .operation import * -_NODE = "_node" -_LINK = "_link" -_CURVE = "_curve" -_PATTERN = "_pattern" +_NODE = '_node' +_LINK = '_link' +_CURVE = '_curve' +_PATTERN = '_pattern' -JUNCTION = "junction" -RESERVOIR = "reservoir" -TANK = "tank" -PIPE = "pipe" -PUMP = "pump" -VALVE = "valve" +JUNCTION = 'junction' +RESERVOIR = 'reservoir' +TANK = 'tank' +PIPE = 'pipe' +PUMP = 'pump' +VALVE = 'valve' + +PATTERN = 'pattern' +CURVE = 'curve' def _get_from(name: str, id: str, base_type: str) -> Row | None: diff --git a/api/s10_status.py b/api/s10_status.py new file mode 100644 index 0000000..9dc9b53 --- /dev/null +++ b/api/s10_status.py @@ -0,0 +1,64 @@ +from .operation import * + + +LINK_STATUS_OPEN = 'OPEN' +LINK_STATUS_CLOSED = 'CLOSED' +LINK_STATUS_ACTIVE = 'ACTIVE' + + +def get_status_schema(name: str) -> dict[str, dict[str, Any]]: + return { 'link' : {'type': 'str' , 'optional': False , 'readonly': True }, + 'status' : {'type': 'str' , 'optional': True , 'readonly': False}, + 'setting' : {'type': 'float' , 'optional': True , 'readonly': False} } + + +def get_status(name: str, link: str) -> dict[str, Any]: + s = try_read(name, f"select * from status where link = '{link}'") + if s == None: + return { 'link': link, 'status': None, 'setting': None } + d = {} + d['link'] = str(s['link']) + d['status'] = str(s['status']) if s['status'] != None else None + d['setting'] = float(s['setting']) if s['setting'] != None else None + return d + + +class Status(object): + def __init__(self, input: dict[str, Any]) -> None: + self.type = 'status' + self.link = str(input['link']) + self.status = str(input['status']) if 'status' in input and input['status'] != None else None + self.setting = float(input['setting']) if 'setting' in input and input['setting'] != None else None + + self.f_type = f"'{self.type}'" + self.f_link = f"'{self.link}'" + self.f_status = f"'{self.status}'" if self.status != None else 'null' + self.f_setting = self.setting if self.setting != None else 'null' + + def as_dict(self) -> dict[str, Any]: + return { 'type': self.type, 'link': self.link, 'status': self.status, 'setting': self.setting } + + +def set_status(name: str, cs: ChangeSet) -> ChangeSet: + old = Status(get_status(name, cs.operations[0]['link'])) + raw_new = get_status(name, cs.operations[0]['link']) + + new_dict = cs.operations[0] + schema = get_status_schema(name) + for key, value in schema.items(): + if key in new_dict and not value['readonly']: + raw_new[key] = new_dict[key] + new = Status(raw_new) + + redo_sql = f"delete from status where link = {new.f_link};" + if new.status != None or new.setting != None: + redo_sql += f"\ninsert into status (link, status, setting) values ({new.f_link}, {new.f_status}, {new.f_setting});" + + undo_sql = f"delete from status where link = {old.f_link};" + if old.status != None or old.setting != None: + undo_sql += f"\ninsert into status (link, status, setting) values ({old.f_link}, {old.f_status}, {old.f_setting});" + + redo_cs = g_update_prefix | new.as_dict() + undo_cs = g_update_prefix | old.as_dict() + + return execute_command(name, redo_sql, undo_sql, redo_cs, undo_cs) diff --git a/api/s11_patterns.py b/api/s11_patterns.py new file mode 100644 index 0000000..66960da --- /dev/null +++ b/api/s11_patterns.py @@ -0,0 +1,45 @@ +from .operation import * + + +def get_pattern_schema(name: str) -> dict[str, dict[str, Any]]: + return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True }, + 'factors' : {'type': 'float_list' , 'optional': False , 'readonly': False } } + + +def get_pattern(name: str, id: str) -> dict[str, Any]: + pas = read_all(name, f"select * from patterns where id = '{id}'") + ps = [] + for r in pas: + ps.append(float(r['factor'])) + return { 'id': id, 'factors': ps } + + +def set_pattern(name: str, cs: ChangeSet) -> ChangeSet: + id = cs.operations[0]['id'] + + old = get_pattern(name, id) + new = { 'id': id, 'factors': [] } + + f_id = f"'{id}'" + + # TODO: transaction ? + redo_sql = f"delete from patterns where id = {f_id};" + redo_sql += f"\ndelete from _pattern where id = {f_id};" + if len(cs.operations[0]['factors']) > 0: + redo_sql += f"\ninsert into _pattern (id) values ({f_id});" + for factor in cs.operations[0]['factors']: + f_factor = float(factor) + redo_sql += f"\ninsert into patterns (id, factor) values ({f_id}, {f_factor});" + new['factors'].append(factor) + + undo_sql = f"delete from patterns where id = {f_id};" + undo_sql += f"\ndelete from _pattern where id = {f_id};" + if len(old['factors']) > 0: + undo_sql += f"\ninsert into _pattern (id) values ({f_id});" + for f_factor in old['factors']: + undo_sql += f"\ninsert into patterns (id, factor) values ({f_id}, {f_factor});" + + redo_cs = g_update_prefix | { 'type': 'pattern' } | new + undo_cs = g_update_prefix | { 'type': 'pattern' } | old + + return execute_command(name, redo_sql, undo_sql, redo_cs, undo_cs) diff --git a/api/s12_curves.py b/api/s12_curves.py new file mode 100644 index 0000000..b171e5c --- /dev/null +++ b/api/s12_curves.py @@ -0,0 +1,49 @@ +from .operation import * + + +def get_curve_schema(name: str) -> dict[str, dict[str, Any]]: + return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True }, + 'coords' : {'type': 'list' , 'optional': False , 'readonly': False, + 'element': { 'x' : {'type': 'float' , 'optional': False , 'readonly': False }, + 'y' : {'type': 'float' , 'optional': False , 'readonly': False } }}} + + +def get_curve(name: str, id: str) -> dict[str, Any]: + cus = read_all(name, f"select * from curves where id = '{id}'") + cs = [] + for r in cus: + cs.append({ 'x': float(r['x']), 'y': float(r['y']) }) + return { 'id': id, 'coords': cs } + + +def set_curve(name: str, cs: ChangeSet) -> ChangeSet: + id = cs.operations[0]['id'] + + old = get_curve(name, id) + new = { 'id': id, 'coords': [] } + + f_id = f"'{id}'" + + # TODO: transaction ? + redo_sql = f"delete from curves where id = {f_id};" + redo_sql += f"\ndelete from _curve where id = {f_id};" + if len(cs.operations[0]['coords']) > 0: + redo_sql += f"\ninsert into _curve (id) values ({f_id});" + for xy in cs.operations[0]['coords']: + x, y = float(xy['x']), float(xy['y']) + f_x, f_y = x, y + redo_sql += f"\ninsert into curves (id, x, y) values ({f_id}, {f_x}, {f_y});" + new['coords'].append({ 'x': x, 'y': y }) + + undo_sql = f"delete from curves where id = {f_id};" + undo_sql += f"\ndelete from _curve where id = {f_id};" + if len(old['coords']) > 0: + undo_sql += f"\ninsert into _curve (id) values ({f_id});" + for xy in old['coords']: + f_x, f_y = xy['x'], xy['y'] + undo_sql += f"\ninsert into curves (id, x, y) values ({f_id}, {f_x}, {f_y});" + + redo_cs = g_update_prefix | { 'type': 'curve' } | new + undo_cs = g_update_prefix | { 'type': 'curve' } | old + + return execute_command(name, redo_sql, undo_sql, redo_cs, undo_cs) diff --git a/api/s16_emitters.py b/api/s16_emitters.py new file mode 100644 index 0000000..e08e731 --- /dev/null +++ b/api/s16_emitters.py @@ -0,0 +1,55 @@ +from .operation import * + + +def get_emitter_schema(name: str) -> dict[str, dict[str, Any]]: + return { 'junction' : {'type': 'str' , 'optional': False , 'readonly': True }, + 'coefficient' : {'type': 'float' , 'optional': True , 'readonly': False} } + + +def get_emitter(name: str, junction: str) -> dict[str, Any]: + e = try_read(name, f"select * from emitters where junction = '{junction}'") + if e == None: + return { 'junction': junction, 'coefficient': None } + d = {} + d['junction'] = str(e['junction']) + d['coefficient'] = float(e['coefficient']) if e['coefficient'] != None else None + return d + + +class Emitter(object): + def __init__(self, input: dict[str, Any]) -> None: + self.type = 'emitter' + self.junction = str(input['junction']) + self.coefficient = float(input['coefficient']) if 'coefficient' in input and input['coefficient'] != None else None + + self.f_type = f"'{self.type}'" + self.f_junction = f"'{self.junction}'" + self.f_coefficient = self.coefficient if self.coefficient != None else 'null' + + def as_dict(self) -> dict[str, Any]: + return { 'type': self.type, 'junction': self.junction, 'coefficient': self.coefficient } + + +def set_emitter(name: str, cs: ChangeSet) -> ChangeSet: + old = Emitter(get_emitter(name, cs.operations[0]['junction'])) + raw_new = get_emitter(name, cs.operations[0]['junction']) + + new_dict = cs.operations[0] + schema = get_emitter_schema(name) + for key, value in schema.items(): + if key in new_dict and not value['readonly']: + raw_new[key] = new_dict[key] + new = Emitter(raw_new) + + redo_sql = f"delete from emitters where junction = {new.f_junction};" + if new.coefficient != None: + redo_sql += f"\ninsert into emitters (junction, coefficient) values ({new.f_junction}, {new.f_coefficient});" + + undo_sql = f"delete from emitters where junction = {old.f_junction};" + if old.coefficient != None: + undo_sql += f"\ninsert into emitters (junction, coefficient) values ({old.f_junction}, {old.f_coefficient});" + + redo_cs = g_update_prefix | new.as_dict() + undo_cs = g_update_prefix | old.as_dict() + + return execute_command(name, redo_sql, undo_sql, redo_cs, undo_cs) diff --git a/api/s21_times.py b/api/s21_times.py new file mode 100644 index 0000000..d28dee1 --- /dev/null +++ b/api/s21_times.py @@ -0,0 +1,63 @@ +from .operation import * + +TIME_STATISTIC_NONE = 'NONE' +TIME_STATISTIC_AVERAGED = 'AVERAGED' +TIME_STATISTIC_MINIMUM = 'MINIMUM' +TIME_STATISTIC_MAXIMUM = 'MAXIMUM' +TIME_STATISTIC_RANGE = 'RANGE' + +element_schema = {'type': 'str' , 'optional': True , 'readonly': False} + +def get_time_schema(name: str) -> dict[str, dict[str, Any]]: + return { 'DURATION' : element_schema, + 'HYDRAULIC TIMESTEP' : element_schema, + 'QUALITY TIMESTEP' : element_schema, + 'RULE TIMESTEP' : element_schema, + 'PATTERN TIMESTEP' : element_schema, + 'PATTERN START' : element_schema, + 'REPORT TIMESTEP' : element_schema, + 'REPORT START' : element_schema, + 'START CLOCKTIME' : element_schema, + 'STATISTIC' : element_schema} + + +def get_time(name: str) -> dict[str, Any]: + ts = read_all(name, f"select * from times") + d = {} + for e in ts: + d[e['key']] = str(e['value']) + return d + + +def set_time(name: str, cs: ChangeSet) -> ChangeSet: + raw_old = get_time(name) + + old = {} + new = {} + + new_dict = cs.operations[0] + schema = get_time_schema(name) + for key in schema.keys(): + if key in new_dict: + old[key] = str(raw_old[key]) + new[key] = str(new_dict[key]) + + redo_cs = g_update_prefix | { 'type' : 'time' } + + redo_sql = '' + for key, value in new.items(): + if redo_sql != '': + redo_sql += '\n' + redo_sql += f"update times set value = '{value}' where key = '{key}';" + redo_cs |= { key: value } + + undo_cs = g_update_prefix | { 'type' : 'time' } + + undo_sql = '' + for key, value in old.items(): + if undo_sql != '': + undo_sql += '\n' + undo_sql += f"update times set value = '{value}' where key = '{key}';" + undo_cs |= { key: value } + + return execute_command(name, redo_sql, undo_sql, redo_cs, undo_cs) diff --git a/api/s23_options.py b/api/s23_options.py new file mode 100644 index 0000000..7d04fef --- /dev/null +++ b/api/s23_options.py @@ -0,0 +1,101 @@ +from .operation import * + +OPTION_UNITS_CFS = 'CFS' +OPTION_UNITS_GPM = 'GPM' +OPTION_UNITS_MGD = 'MGD' +OPTION_UNITS_IMGD = 'IMGD' +OPTION_UNITS_AFD = 'AFD' +OPTION_UNITS_LPS = 'LPS' +OPTION_UNITS_LPM = 'LPM' +OPTION_UNITS_MLD = 'MLD' +OPTION_UNITS_CMH = 'CMH' +OPTION_UNITS_CMD = 'CMD' + +OPTION_HEADLOSS_HW = 'H-W' +OPTION_HEADLOSS_DW = 'D-W' +OPTION_HEADLOSS_CM = 'C-M' + +#OPTION_HYDRAULICS_USE = 'USE' +#OPTION_HYDRAULICS_SAVE = 'SAVE' + +OPTION_UNBALANCED_STOP = 'STOP' +OPTION_UNBALANCED_CONTINUE = 'CONTINUE' + +OPTION_DEMAND_MODEL_DDA = 'DDA' +OPTION_DEMAND_MODEL_PDA = 'PDA' + +OPTION_QUALITY_NONE = 'NONE' +OPTION_QUALITY_CHEMICAL = 'CHEMICAL' +OPTION_QUALITY_AGE = 'AGE' +OPTION_QUALITY_TRACE = 'TRACE' + +element_schema = {'type': 'str' , 'optional': True , 'readonly': False} + +def get_option_schema(name: str) -> dict[str, dict[str, Any]]: + return { 'UNITS' : element_schema, + 'HEADLOSS' : element_schema, + #'HYDRAULICS' : element_schema, + 'VISCOSITY' : element_schema, + 'SPECIFIC GRAVITY' : element_schema, + 'TRIALS' : element_schema, + 'ACCURACY' : element_schema, + 'FLOWCHANGE' : element_schema, + 'HEADERROR' : element_schema, + 'CHECKFREQ' : element_schema, + 'MAXCHECK' : element_schema, + 'DAMPLIMIT' : element_schema, + 'UNBALANCED' : element_schema, + 'DEMAND MODEL' : element_schema, + 'MINIMUM PRESSURE' : element_schema, + 'REQUIRED PRESSURE' : element_schema, + 'PRESSURE EXPONENT' : element_schema, + 'PATTERN' : element_schema, + 'DEMAND MULTIPLIER' : element_schema, + 'EMITTER EXPONENT' : element_schema, + 'QUALITY' : element_schema, + 'DIFFUSIVITY' : element_schema, + 'TOLERANCE' : element_schema, + #'QUALITY' : element_schema, + } + + +def get_option(name: str) -> dict[str, Any]: + ts = read_all(name, f"select * from options") + d = {} + for e in ts: + d[e['key']] = str(e['value']) + return d + + +def set_option(name: str, cs: ChangeSet) -> ChangeSet: + raw_old = get_option(name) + + old = {} + new = {} + + new_dict = cs.operations[0] + schema = get_option_schema(name) + for key in schema.keys(): + if key in new_dict: + old[key] = str(raw_old[key]) + new[key] = str(new_dict[key]) + + redo_cs = g_update_prefix | { 'type' : 'option' } + + redo_sql = '' + for key, value in new.items(): + if redo_sql != '': + redo_sql += '\n' + redo_sql += f"update options set value = '{value}' where key = '{key}';" + redo_cs |= { key: value } + + undo_cs = g_update_prefix | { 'type' : 'option' } + + undo_sql = '' + for key, value in old.items(): + if undo_sql != '': + undo_sql += '\n' + undo_sql += f"update options set value = '{value}' where key = '{key}';" + undo_cs |= { key: value } + + return execute_command(name, redo_sql, undo_sql, redo_cs, undo_cs) diff --git a/api/s2_junctions.py b/api/s2_junctions.py index 0761ae6..0fe5e87 100644 --- a/api/s2_junctions.py +++ b/api/s2_junctions.py @@ -7,10 +7,10 @@ def get_junction_schema(name: str) -> dict[str, dict[str, Any]]: return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True }, 'x' : {'type': 'float' , 'optional': False , 'readonly': False}, 'y' : {'type': 'float' , 'optional': False , 'readonly': False}, - 'elevation' : {'type': "float" , 'optional': False , 'readonly': False}, - 'demand' : {'type': "float" , 'optional': True , 'readonly': False}, - 'pattern' : {'type': "str" , 'optional': True , 'readonly': False}, - 'links' : {'type': "str_list" , 'optional': False , 'readonly': True } } + 'elevation' : {'type': 'float' , 'optional': False , 'readonly': False}, + 'demand' : {'type': 'float' , 'optional': True , 'readonly': False}, + 'pattern' : {'type': 'str' , 'optional': True , 'readonly': False}, + 'links' : {'type': 'str_list' , 'optional': False , 'readonly': True } } def get_junction(name: str, id: str) -> dict[str, Any]: diff --git a/api/s3_reservoirs.py b/api/s3_reservoirs.py index 42452a7..76a1618 100644 --- a/api/s3_reservoirs.py +++ b/api/s3_reservoirs.py @@ -7,9 +7,9 @@ def get_reservoir_schema(name: str) -> dict[str, dict[str, Any]]: return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True }, 'x' : {'type': 'float' , 'optional': False , 'readonly': False}, 'y' : {'type': 'float' , 'optional': False , 'readonly': False}, - 'head' : {'type': "float" , 'optional': False , 'readonly': False}, - 'pattern' : {'type': "str" , 'optional': True , 'readonly': False}, - 'links' : {'type': "str_list" , 'optional': False , 'readonly': True } } + 'head' : {'type': 'float' , 'optional': False , 'readonly': False}, + 'pattern' : {'type': 'str' , 'optional': True , 'readonly': False}, + 'links' : {'type': 'str_list' , 'optional': False , 'readonly': True } } def get_reservoir(name: str, id: str) -> dict[str, Any]: diff --git a/api/s4_tanks.py b/api/s4_tanks.py index 2a5a7cc..68b4364 100644 --- a/api/s4_tanks.py +++ b/api/s4_tanks.py @@ -3,23 +3,23 @@ from .s0_base import * from .s24_coordinates import * -OVERFLOW_YES = 'yes' -OVERFLOW_NO = 'no' +OVERFLOW_YES = 'YES' +OVERFLOW_NO = 'NO' def get_tank_schema(name: str) -> dict[str, dict[str, Any]]: return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True }, 'x' : {'type': 'float' , 'optional': False , 'readonly': False}, 'y' : {'type': 'float' , 'optional': False , 'readonly': False}, - 'elevation' : {'type': "float" , 'optional': False , 'readonly': False}, - 'init_level' : {'type': "float" , 'optional': False , 'readonly': False}, - 'min_level' : {'type': "float" , 'optional': False , 'readonly': False}, - 'max_level' : {'type': "float" , 'optional': False , 'readonly': False}, - 'diameter' : {'type': "float" , 'optional': False , 'readonly': False}, - 'min_vol' : {'type': "float" , 'optional': False , 'readonly': False}, - 'vol_curve' : {'type': "str" , 'optional': True , 'readonly': False}, - 'overflow' : {'type': "str" , 'optional': True , 'readonly': False}, - 'links' : {'type': "str_list" , 'optional': False , 'readonly': True } } + 'elevation' : {'type': 'float' , 'optional': False , 'readonly': False}, + 'init_level' : {'type': 'float' , 'optional': False , 'readonly': False}, + 'min_level' : {'type': 'float' , 'optional': False , 'readonly': False}, + 'max_level' : {'type': 'float' , 'optional': False , 'readonly': False}, + 'diameter' : {'type': 'float' , 'optional': False , 'readonly': False}, + 'min_vol' : {'type': 'float' , 'optional': False , 'readonly': False}, + 'vol_curve' : {'type': 'str' , 'optional': True , 'readonly': False}, + 'overflow' : {'type': 'str' , 'optional': True , 'readonly': False}, + 'links' : {'type': 'str_list' , 'optional': False , 'readonly': True } } def get_tank(name: str, id: str) -> dict[str, Any]: diff --git a/api/s5_pipes.py b/api/s5_pipes.py index 5e008b4..3b90d89 100644 --- a/api/s5_pipes.py +++ b/api/s5_pipes.py @@ -2,20 +2,20 @@ from .operation import * from .s0_base import * -PIPE_STATUS_OPEN = 'open' -PIPE_STATUS_CLOSED = 'closed' -PIPE_STATUS_CV = 'cv' +PIPE_STATUS_OPEN = 'OPEN' +PIPE_STATUS_CLOSED = 'CLOSED' +PIPE_STATUS_CV = 'CV' def get_pipe_schema(name: str) -> dict[str, dict[str, Any]]: return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True }, - 'node1' : {'type': "str" , 'optional': False , 'readonly': False}, - 'node2' : {'type': "str" , 'optional': False , 'readonly': False}, - 'length' : {'type': "float" , 'optional': False , 'readonly': False}, - 'diameter' : {'type': "float" , 'optional': False , 'readonly': False}, - 'roughness' : {'type': "float" , 'optional': False , 'readonly': False}, - 'minor_loss' : {'type': "float" , 'optional': False , 'readonly': False}, - 'status' : {'type': "str" , 'optional': False , 'readonly': False} } + 'node1' : {'type': 'str' , 'optional': False , 'readonly': False}, + 'node2' : {'type': 'str' , 'optional': False , 'readonly': False}, + 'length' : {'type': 'float' , 'optional': False , 'readonly': False}, + 'diameter' : {'type': 'float' , 'optional': False , 'readonly': False}, + 'roughness' : {'type': 'float' , 'optional': False , 'readonly': False}, + 'minor_loss' : {'type': 'float' , 'optional': False , 'readonly': False}, + 'status' : {'type': 'str' , 'optional': False , 'readonly': False} } def get_pipe(name: str, id: str) -> dict[str, Any]: diff --git a/api/s6_pumps.py b/api/s6_pumps.py index 715fc93..87f71e3 100644 --- a/api/s6_pumps.py +++ b/api/s6_pumps.py @@ -3,9 +3,13 @@ from .s0_base import * def get_pump_schema(name: str) -> dict[str, dict[str, Any]]: - return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True }, - 'node1' : {'type': "str" , 'optional': False , 'readonly': False}, - 'node2' : {'type': "str" , 'optional': False , 'readonly': False} } + return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True }, + 'node1' : {'type': 'str' , 'optional': False , 'readonly': False}, + 'node2' : {'type': 'str' , 'optional': False , 'readonly': False}, + 'power' : {'type': 'float' , 'optional': True , 'readonly': False}, + 'head' : {'type': 'str' , 'optional': True , 'readonly': False}, + 'speed' : {'type': 'float' , 'optional': True , 'readonly': False}, + 'pattern' : {'type': 'str' , 'optional': True , 'readonly': False} } def get_pump(name: str, id: str) -> dict[str, Any]: @@ -14,6 +18,10 @@ def get_pump(name: str, id: str) -> dict[str, Any]: d['id'] = str(p['id']) d['node1'] = str(p['node1']) d['node2'] = str(p['node2']) + d['power'] = float(p['power']) if p['power'] != None else None + d['head'] = str(p['head']) if p['head'] != None else None + d['speed'] = float(p['speed']) if p['speed'] != None else None + d['pattern'] = str(p['pattern']) if p['pattern'] != None else None return d @@ -23,14 +31,22 @@ class Pump(object): self.id = str(input['id']) self.node1 = str(input['node1']) self.node2 = str(input['node2']) + self.power = float(input['power']) if 'power' in input and input['power'] != None else None + self.head = str(input['head']) if 'head' in input and input['head'] != None else None + self.speed = float(input['speed']) if 'speed' in input and input['speed'] != None else None + self.pattern = str(input['pattern']) if 'pattern' in input and input['pattern'] != None else None self.f_type = f"'{self.type}'" self.f_id = f"'{self.id}'" self.f_node1 = f"'{self.node1}'" self.f_node2 = f"'{self.node2}'" + self.f_power = self.power if self.power != None else 'null' + self.f_head = f"'{self.head}'" if self.head != None else 'null' + self.f_speed = self.speed if self.speed != None else 'null' + self.f_pattern = f"'{self.pattern}'" if self.pattern != None else 'null' def as_dict(self) -> dict[str, Any]: - return { 'type': self.type, 'id': self.id, 'node1': self.node1, 'node2': self.node2 } + return { 'type': self.type, 'id': self.id, 'node1': self.node1, 'node2': self.node2, 'power': self.power, 'head': self.head, 'speed': self.speed, 'pattern': self.pattern } def as_id_dict(self) -> dict[str, Any]: return { 'type': self.type, 'id': self.id } @@ -47,8 +63,8 @@ def set_pump(name: str, cs: ChangeSet) -> ChangeSet: raw_new[key] = new_dict[key] new = Pump(raw_new) - redo_sql = f"update pumps set node1 = {new.f_node1}, node2 = {new.f_node2} where id = {new.f_id};" - undo_sql = f"update pumps set node1 = {old.f_node1}, node2 = {old.f_node2} where id = {old.f_id};" + redo_sql = f"update pumps set node1 = {new.f_node1}, node2 = {new.f_node2}, power = {new.f_power}, head = {new.f_head}, speed = {new.f_speed}, pattern = {new.f_pattern} where id = {new.f_id};" + undo_sql = f"update pumps set node1 = {old.f_node1}, node2 = {old.f_node2}, power = {old.f_power}, head = {old.f_head}, speed = {old.f_speed}, pattern = {old.f_pattern} where id = {old.f_id};" redo_cs = g_update_prefix | new.as_dict() undo_cs = g_update_prefix | old.as_dict() @@ -60,7 +76,7 @@ def add_pump(name: str, cs: ChangeSet) -> ChangeSet: new = Pump(cs.operations[0]) redo_sql = f"insert into _link (id, type) values ({new.f_id}, {new.f_type});" - redo_sql += f"\ninsert into pumps (id, node1, node2) values ({new.f_id}, {new.f_node1}, {new.f_node2});" + redo_sql += f"\ninsert into pumps (id, node1, node2, power, head, speed, pattern) values ({new.f_id}, {new.f_node1}, {new.f_node2}, {new.f_power}, {new.f_head}, {new.f_speed}, {new.f_pattern});" undo_sql = f"delete from pumps where id = {new.f_id};" undo_sql += f"\ndelete from _link where id = {new.f_id};" @@ -78,7 +94,7 @@ def delete_pump(name: str, cs: ChangeSet) -> ChangeSet: redo_sql += f"\ndelete from _link where id = {old.f_id};" undo_sql = f"insert into _link (id, type) values ({old.f_id}, {old.f_type});" - undo_sql += f"\ninsert into pumps (id, node1, node2) values ({old.f_id}, {old.f_node1}, {old.f_node2});" + undo_sql += f"\ninsert into pumps (id, node1, node2, power, head, speed, pattern) values ({old.f_id}, {old.f_node1}, {old.f_node2}, {old.f_power}, {old.f_head}, {old.f_speed}, {old.f_pattern});" redo_cs = g_delete_prefix | old.as_id_dict() undo_cs = g_add_prefix | old.as_dict() diff --git a/api/s7_valves.py b/api/s7_valves.py index c08a76b..e7f1df9 100644 --- a/api/s7_valves.py +++ b/api/s7_valves.py @@ -2,22 +2,22 @@ from .operation import * from .s0_base import * -VALVES_TYPE_PRV = 'prv' -VALVES_TYPE_PSV = 'psv' -VALVES_TYPE_PBV = 'pbv' -VALVES_TYPE_FCV = 'fcv' -VALVES_TYPE_TCV = 'tcv' -VALVES_TYPE_GPV = 'gpv' +VALVES_TYPE_PRV = 'PRV' +VALVES_TYPE_PSV = 'PSV' +VALVES_TYPE_PBV = 'PBV' +VALVES_TYPE_FCV = 'FCV' +VALVES_TYPE_TCV = 'TCV' +VALVES_TYPE_GPV = 'GPV' def get_valve_schema(name: str) -> dict[str, dict[str, Any]]: return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True }, - 'node1' : {'type': "str" , 'optional': False , 'readonly': False}, - 'node2' : {'type': "str" , 'optional': False , 'readonly': False}, - 'diameter' : {'type': "float" , 'optional': False , 'readonly': False}, - 'v_type' : {'type': "str" , 'optional': False , 'readonly': False}, - 'setting' : {'type': "float" , 'optional': False , 'readonly': False}, - 'minor_loss' : {'type': "float" , 'optional': False , 'readonly': False} } + 'node1' : {'type': 'str' , 'optional': False , 'readonly': False}, + 'node2' : {'type': 'str' , 'optional': False , 'readonly': False}, + 'diameter' : {'type': 'float' , 'optional': False , 'readonly': False}, + 'v_type' : {'type': 'str' , 'optional': False , 'readonly': False}, + 'setting' : {'type': 'float' , 'optional': False , 'readonly': False}, + 'minor_loss' : {'type': 'float' , 'optional': False , 'readonly': False} } def get_valve(name: str, id: str) -> dict[str, Any]: diff --git a/api/s9_demands.py b/api/s9_demands.py new file mode 100644 index 0000000..a3c9438 --- /dev/null +++ b/api/s9_demands.py @@ -0,0 +1,56 @@ +from .operation import * + + +def get_demand_schema(name: str) -> dict[str, dict[str, Any]]: + return { 'junction' : {'type': 'str' , 'optional': False , 'readonly': True }, + 'demands' : {'type': 'list' , 'optional': False , 'readonly': False, + 'element': { 'demand' : {'type': 'float' , 'optional': False , 'readonly': False }, + 'patten' : {'type': 'str' , 'optional': True , 'readonly': False }, + 'category': {'type': 'str' , 'optional': True , 'readonly': False }}}} + + +def get_demand(name: str, junction: str) -> dict[str, Any]: + des = read_all(name, f"select * from demands where junction = '{junction}'") + ds = [] + for r in des: + d = {} + d['demand'] = float(r['demand']) + d['pattern'] = str(r['pattern']) if r['pattern'] != None else None + d['category'] = str(r['category']) if r['category'] != None else None + ds.append(d) + return { 'junction': junction, 'demands': ds } + +# { 'operation': 'update', 'type': 'demand', 'junction': 'j1', 'demands': [{'demand': 0.0, 'patten': None, 'category': None}] } +def set_demand(name: str, cs: ChangeSet) -> ChangeSet: + junction = cs.operations[0]['junction'] + old = get_demand(name, junction) + new = { 'junction': junction, 'demands': [] } + + f_junction = f"'{junction}'" + + # TODO: transaction ? + redo_sql = f"delete from demands where junction = {f_junction};" + for r in cs.operations[0]['demands']: + demand = float(r['demand']) + pattern = str(r['pattern']) if 'pattern' in r and r['pattern'] != None else None + category = str(r['category']) if 'category' in r and r['category'] != None else None + f_demand = demand + f_pattern = f"'{pattern}'" if pattern != None else 'null' + f_category = f"'{category}'" if category != None else 'null' + redo_sql += f"\ninsert into demands (junction, demand, pattern, category) values ({f_junction}, {f_demand}, {f_pattern}, {f_category});" + new['demands'].append({ 'demand': demand, 'pattern': pattern, 'category': category }) + + undo_sql = f"delete from demands where junction = {f_junction};" + for r in old['demands']: + demand = float(r['demand']) + pattern = str(r['pattern']) + category = str(r['category']) + f_demand = demand + f_pattern = f"'{pattern}'" if pattern != None else 'null' + f_category = f"'{category}'" if category != None else 'null' + undo_sql += f"\ninsert into demands (junction, demand, pattern, category) values ({f_junction}, {f_demand}, {f_pattern}, {f_category});" + + redo_cs = g_update_prefix | { 'type': 'demand' } | new + undo_cs = g_update_prefix | { 'type': 'demand' } | old + + return execute_command(name, redo_sql, undo_sql, redo_cs, undo_cs) diff --git a/parser_demo.py b/parser_demo.py index 8adf031..9d43ca0 100644 --- a/parser_demo.py +++ b/parser_demo.py @@ -1,7 +1,7 @@ from tjnetwork import * def inp2db(): - read_inp('changshu', './inp/v-16常熟模型.inp') + read_inp('net3', './inp/net3.inp') if __name__ == '__main__': inp2db() \ No newline at end of file diff --git a/script/sql/create/10.status.sql b/script/sql/create/10.status.sql index 44f007b..63a8256 100644 --- a/script/sql/create/10.status.sql +++ b/script/sql/create/10.status.sql @@ -1,29 +1,11 @@ -- [STATUS] -create type status_pipe_pump_status as enum ('open', 'closed'); +create type link_status as enum ('OPEN', 'CLOSED', 'ACTIVE'); -create table status_pipe +create table status ( - id varchar(32) primary key references pipes(id) -, status status_pipe_pump_status not null -); - -create table status_pump -( - id varchar(32) primary key references pumps(id) -, status status_pipe_pump_status not null -); - -create type status_valve_status as enum ('open', 'closed', 'active'); - -create table status_valve -( - id varchar(32) primary key references valves(id) -, status status_valve_status not null -); - -create table status_link -( - id varchar(32) primary key references _link(id) -, setting numeric not null + link varchar(32) primary key references _link(id) +, status link_status +, setting numeric +, check (status is not null or setting is not null) ); diff --git a/script/sql/create/11.patterns.sql b/script/sql/create/11.patterns.sql index d6aaa4e..8835fa2 100644 --- a/script/sql/create/11.patterns.sql +++ b/script/sql/create/11.patterns.sql @@ -3,5 +3,5 @@ create table patterns ( id varchar(32) references _pattern(id) not null -, multipliers numeric not null +, factor numeric not null ); diff --git a/script/sql/create/13.controls.sql b/script/sql/create/13.controls.sql index c5930ab..335db45 100644 --- a/script/sql/create/13.controls.sql +++ b/script/sql/create/13.controls.sql @@ -1,30 +1,6 @@ -- [CONTROLS] -create type controls_1_prep as enum ('above', 'below'); - --- LINK linkID status IF NODE nodeID ABOVE / BELOW value -create table controls_1 +create table controls ( - linkid varchar(32) primary key references _link(id) -, status text not null -- open / closed, a pump speed setting, or a control valve setting -, nodeid varchar(32) references _node(id) not null -, prep controls_1_prep not null -, value numeric not null -); - --- LINK linkID status AT TIME time -create table controls_2 -( - linkid varchar(32) primary key references _link(id) -, status text not null -- open / closed, a pump speed setting, or a control valve setting -, time interval hour -); - --- LINK linkID status AT CLOCKTIME clocktime AM / PM -create table controls_3 -( - linkid varchar(32) primary key references _link(id) -, status text not null -- open / closed, a pump speed setting, or a control valve setting -, clock_time_hour interval hour -- get am/pm from it -, clock_time_min interval minute + control text primary key ); diff --git a/script/sql/create/14.rules.sql b/script/sql/create/14.rules.sql index 7fb07d5..bfd74c3 100644 --- a/script/sql/create/14.rules.sql +++ b/script/sql/create/14.rules.sql @@ -2,5 +2,5 @@ create table rules ( - content text primary key + rule text primary key ); diff --git a/script/sql/create/15.energy.sql b/script/sql/create/15.energy.sql index dfb89ce..822a706 100644 --- a/script/sql/create/15.energy.sql +++ b/script/sql/create/15.energy.sql @@ -1,6 +1,6 @@ -- [ENERGY] -create type energy_param as enum ('price', 'pattern', 'effic'); +create type energy_param as enum ('PRICE', 'PATTERN', 'EFFIC'); -- GLOBAL PRICE / PATTERN / EFFIC value create table energy_global @@ -22,3 +22,12 @@ create table energy_demand_charge ( value numeric not null ); + +insert into energy_global (param, value) values + ('PRICE', 0.0) +, ('EFFIC', 75) +; + +insert into energy_demand_charge values + (0.0) +; diff --git a/script/sql/create/18.sources.sql b/script/sql/create/18.sources.sql index 0da8c6f..956d61d 100644 --- a/script/sql/create/18.sources.sql +++ b/script/sql/create/18.sources.sql @@ -1,6 +1,6 @@ -- [SOURCES] -create type sources_type as enum ('concen', 'mass', 'flowpaced', 'setpoint'); +create type sources_type as enum ('CONCEN', 'MASS', 'FLOWPACED', 'SETPOINT'); create table sources ( diff --git a/script/sql/create/19.reactions.sql b/script/sql/create/19.reactions.sql index ff05a7e..d22cb1b 100644 --- a/script/sql/create/19.reactions.sql +++ b/script/sql/create/19.reactions.sql @@ -1,6 +1,6 @@ -- [REACTIONS] -create type reactions_order_param as enum ('bulk', 'wall', 'tank'); +create type reactions_order_param as enum ('BULK', 'WALL', 'TANK'); create table reactions_order ( @@ -8,7 +8,7 @@ create table reactions_order , value numeric not null ); -create type reactions_global_param as enum ('bulk', 'wall'); +create type reactions_global_param as enum ('BULK', 'WALL'); create table reactions_global ( @@ -16,7 +16,7 @@ create table reactions_global , value numeric not null ); -create type reactions_pipe_param as enum ('bulk', 'wall'); +create type reactions_pipe_param as enum ('BULK', 'WALL'); create table reactions_pipe ( diff --git a/script/sql/create/20.mixing.sql b/script/sql/create/20.mixing.sql index afdb3bf..2575915 100644 --- a/script/sql/create/20.mixing.sql +++ b/script/sql/create/20.mixing.sql @@ -1,6 +1,6 @@ -- [MIXING] -create type mixing_model as enum ('mixed', '2comp', 'fifo', 'lifo'); +create type mixing_model as enum ('MIXED', '2COMP', 'FIFO', 'LIFO'); create table mixing ( diff --git a/script/sql/create/21.times.sql b/script/sql/create/21.times.sql index 0877871..ae150f9 100644 --- a/script/sql/create/21.times.sql +++ b/script/sql/create/21.times.sql @@ -1,9 +1,20 @@ -- [TIMES] --- TODO: constraint - create table times ( key text not null , value text not null ); + +insert into times (key, value) values + ('DURATION', '0:00') +, ('HYDRAULIC TIMESTEP', '1:00') +, ('QUALITY TIMESTEP', '0:05') +, ('RULE TIMESTEP', '0:05') +, ('PATTERN TIMESTEP', '1:00') +, ('PATTERN START', '0:00') +, ('REPORT TIMESTEP', '1:00') +, ('REPORT START', '0:00') +, ('START CLOCKTIME', '12:00 AM') +, ('STATISTIC', 'NONE') -- NONE / AVERAGED / MINIMUM / MAXIMUM / RANGE +; diff --git a/script/sql/create/22.report.sql b/script/sql/create/22.report.sql index edd1b3b..a1cdf22 100644 --- a/script/sql/create/22.report.sql +++ b/script/sql/create/22.report.sql @@ -7,3 +7,14 @@ create table report key text not null , value text not null ); + +insert into report (key, value) values + ('PAGESIZE', '0') +--, ('FILE', '') +, ('STATUS', 'NO') +, ('SUMMARY', 'YES') +--, ('MESSAGES', 'NO') +, ('ENERY', 'NO') +, ('NODES', 'NONE') +, ('LINKS', 'NONE') +; diff --git a/script/sql/create/23.options.sql b/script/sql/create/23.options.sql index 7b91411..d973e6d 100644 --- a/script/sql/create/23.options.sql +++ b/script/sql/create/23.options.sql @@ -7,3 +7,30 @@ create table options key text not null , value text not null ); + +insert into options (key, value) values + ('UNITS', 'GPM') +, ('HEADLOSS', 'H-W') +--, ('HYDRAULICS', '') +, ('VISCOSITY', '1.0') +, ('SPECIFIC GRAVITY', '1.0') +, ('TRIALS', '40') +, ('ACCURACY', '0.001') +, ('FLOWCHANGE', '0') +, ('HEADERROR', '0') +, ('CHECKFREQ', '2') +, ('MAXCHECK', '10') +, ('DAMPLIMIT', '0') +, ('UNBALANCED', 'STOP') +, ('DEMAND MODEL', 'DDA') +, ('MINIMUM PRESSURE', '0') +, ('REQUIRED PRESSURE', '0.1') +, ('PRESSURE EXPONENT', '0.5') +, ('PATTERN', '1') +, ('DEMAND MULTIPLIER', '1.0') +, ('EMITTER EXPONENT', '0.5') +, ('QUALITY', 'NONE') +, ('DIFFUSIVITY', '1.0') +, ('TOLERANCE', '0.01') +--, ('MAP', '') +; diff --git a/script/sql/create/4.tanks.sql b/script/sql/create/4.tanks.sql index b15c29f..20f3e0c 100644 --- a/script/sql/create/4.tanks.sql +++ b/script/sql/create/4.tanks.sql @@ -1,6 +1,6 @@ -- [TANKS] -create type tanks_overflow as enum ('yes', 'no'); +create type tanks_overflow as enum ('YES', 'NO'); create table tanks ( diff --git a/script/sql/create/5.pipes.sql b/script/sql/create/5.pipes.sql index f286bf9..996de26 100644 --- a/script/sql/create/5.pipes.sql +++ b/script/sql/create/5.pipes.sql @@ -1,6 +1,6 @@ -- [PIPES] -create type pipes_status as enum ('open', 'closed', 'cv'); +create type pipes_status as enum ('OPEN', 'CLOSED', 'CV'); create table pipes ( diff --git a/script/sql/create/6.pumps.sql b/script/sql/create/6.pumps.sql index 128958f..c93e88e 100644 --- a/script/sql/create/6.pumps.sql +++ b/script/sql/create/6.pumps.sql @@ -5,28 +5,10 @@ create table pumps id varchar(32) primary key references _link(id) , node1 varchar(32) references _node(id) not null , node2 varchar(32) references _node(id) not null -); - -create table pumps_property_power -( - id varchar primary key references pumps(id) -, value varchar(32) not null -); - -create table pumps_property_speed -( - id varchar primary key references pumps(id) -, value varchar(32) not null -); - -create table pumps_property_head -( - id varchar primary key references pumps(id) -, head varchar(32) references _curve(id) not null -); - -create table pumps_property_pattern -( - id varchar primary key references pumps(id) -, pattern varchar(32) references _pattern(id) not null +, power numeric +, head varchar(32) references _curve(id) +, speed numeric +, pattern varchar(32) references _pattern(id) +, check (power is not null or head is not null) +, check ((power is not null and head is not null) is false) ); diff --git a/script/sql/create/7.valves.sql b/script/sql/create/7.valves.sql index 8472d41..b765fc1 100644 --- a/script/sql/create/7.valves.sql +++ b/script/sql/create/7.valves.sql @@ -1,6 +1,6 @@ -- [VALVES] -create type valves_type as enum ('prv', 'psv', 'pbv', 'fcv', 'tcv', 'gpv'); +create type valves_type as enum ('PRV', 'PSV', 'PBV', 'FCV', 'TCV', 'GPV'); create table valves ( diff --git a/script/sql/create/9.demands.sql b/script/sql/create/9.demands.sql index 6e4b2a5..d2d2943 100644 --- a/script/sql/create/9.demands.sql +++ b/script/sql/create/9.demands.sql @@ -5,5 +5,5 @@ create table demands junction varchar(32) references junctions(id) not null , demand numeric not null , pattern varchar(32) references _pattern(id) -, category text not null +, category text ); diff --git a/script/sql/drop/10.status.sql b/script/sql/drop/10.status.sql index c3273b3..fe43c43 100644 --- a/script/sql/drop/10.status.sql +++ b/script/sql/drop/10.status.sql @@ -1,13 +1,5 @@ -- [STATUS] -drop table if exists status_link; +drop table if exists status; -drop table if exists status_valve; - -drop type if exists status_valve_status; - -drop table if exists status_pump; - -drop table if exists status_pipe; - -drop type if exists status_pipe_pump_status; +drop type if exists link_status; diff --git a/script/sql/drop/13.controls.sql b/script/sql/drop/13.controls.sql index 6dd2eb8..b17cb69 100644 --- a/script/sql/drop/13.controls.sql +++ b/script/sql/drop/13.controls.sql @@ -1,9 +1,3 @@ -- [CONTROLS] -drop table if exists controls_3; - -drop table if exists controls_2; - -drop table if exists controls_1; - -drop type if exists controls_1_prep; +drop table if exists controls; diff --git a/script/sql/drop/6.pumps.sql b/script/sql/drop/6.pumps.sql index 7d4aab1..9d646ff 100644 --- a/script/sql/drop/6.pumps.sql +++ b/script/sql/drop/6.pumps.sql @@ -1,11 +1,3 @@ -- [PUMPS] -drop table if exists pumps_property_pattern; - -drop table if exists pumps_property_head; - -drop table if exists pumps_property_speed; - -drop table if exists pumps_property_power; - drop table if exists pumps; diff --git a/test_tjnetwork.py b/test_tjnetwork.py index 5efecb3..c5d4b1a 100644 --- a/test_tjnetwork.py +++ b/test_tjnetwork.py @@ -165,7 +165,7 @@ class TestApi: add_junction(p, ChangeSet({'id': 'j2', 'x': 0.0, 'y': 10.0, 'elevation': 20.0})) add_junction(p, ChangeSet({'id': 'j3', 'x': 0.0, 'y': 10.0, 'elevation': 20.0})) add_pipe(p, ChangeSet({'id': 'p1', 'node1': 'j1', 'node2': 'j2', 'length': 100.0, 'diameter': 10.0, 'roughness': 0.1, 'minor_loss': 0.5, 'status': PIPE_STATUS_OPEN })) - add_pump(p, ChangeSet({'id': 'p2', 'node1': 'j1', 'node2': 'j2'})) + add_pump(p, ChangeSet({'id': 'p2', 'node1': 'j1', 'node2': 'j2', 'power': 0.0})) add_valve(p, ChangeSet({'id': 'v1', 'node1': 'j2', 'node2': 'j3', 'diameter': 10.0, 'v_type': VALVES_TYPE_FCV, 'setting': 0.1, 'minor_loss': 0.5 })) assert get_junction(p, 'j1')['links'] == ['p1', 'p2'] assert get_junction(p, 'j2')['links'] == ['p1', 'p2', 'v1'] @@ -322,7 +322,7 @@ class TestApi: add_reservoir(p, ChangeSet({'id': 'r2', 'x': 0.0, 'y': 10.0, 'head': 20.0})) add_reservoir(p, ChangeSet({'id': 'r3', 'x': 0.0, 'y': 10.0, 'head': 20.0})) add_pipe(p, ChangeSet({'id': 'p1', 'node1': 'r1', 'node2': 'r2', 'length': 100.0, 'diameter': 10.0, 'roughness': 0.1, 'minor_loss': 0.5, 'status': PIPE_STATUS_OPEN })) - add_pump(p, ChangeSet({'id': 'p2', 'node1': 'r1', 'node2': 'r2'})) + add_pump(p, ChangeSet({'id': 'p2', 'node1': 'r1', 'node2': 'r2', 'power': 0.0})) add_valve(p, ChangeSet({'id': 'v1', 'node1': 'r2', 'node2': 'r3', 'diameter': 10.0, 'v_type': VALVES_TYPE_FCV, 'setting': 0.1, 'minor_loss': 0.5 })) assert get_reservoir(p, 'r1')['links'] == ['p1', 'p2'] assert get_reservoir(p, 'r2')['links'] == ['p1', 'p2', 'v1'] @@ -497,7 +497,7 @@ class TestApi: add_tank(p, ChangeSet({'id': 't2', 'x': 0.0, 'y': 10.0, 'elevation': 20.0, 'init_level': 1.0, 'min_level': 0.0, 'max_level': 2.0, 'diameter': 10.0, 'min_vol': 100.0, 'vol_curve': None, 'overflow': OVERFLOW_NO})) add_tank(p, ChangeSet({'id': 't3', 'x': 0.0, 'y': 10.0, 'elevation': 20.0, 'init_level': 1.0, 'min_level': 0.0, 'max_level': 2.0, 'diameter': 10.0, 'min_vol': 100.0, 'vol_curve': None, 'overflow': OVERFLOW_NO})) add_pipe(p, ChangeSet({'id': 'p1', 'node1': 't1', 'node2': 't2', 'length': 100.0, 'diameter': 10.0, 'roughness': 0.1, 'minor_loss': 0.5, 'status': PIPE_STATUS_OPEN })) - add_pump(p, ChangeSet({'id': 'p2', 'node1': 't1', 'node2': 't2'})) + add_pump(p, ChangeSet({'id': 'p2', 'node1': 't1', 'node2': 't2', 'power': 0.0})) add_valve(p, ChangeSet({'id': 'v1', 'node1': 't2', 'node2': 't3', 'diameter': 10.0, 'v_type': VALVES_TYPE_FCV, 'setting': 0.1, 'minor_loss': 0.5 })) assert get_tank(p, 't1')['links'] == ['p1', 'p2'] assert get_tank(p, 't2')['links'] == ['p1', 'p2', 'v1'] @@ -846,17 +846,25 @@ class TestApi: assert is_junction(p, 'j3') assert is_junction(p, 'j4') - add_pump(p, ChangeSet({'id': 'p0', 'node1': 'j1', 'node2': 'j2'})) + add_pump(p, ChangeSet({'id': 'p0', 'node1': 'j1', 'node2': 'j2', 'power': 0.0})) p0 = get_pump(p, 'p0') assert p0['node1'] == 'j1' assert p0['node2'] == 'j2' + assert p0['power'] == 0.0 + assert p0['head'] == None + assert p0['speed'] == None + assert p0['pattern'] == None set_pump(p, ChangeSet({'id': 'p0', 'node1': 'j3', 'node2': 'j4'})) p0 = get_pump(p, 'p0') assert p0['node1'] == 'j3' assert p0['node2'] == 'j4' - add_pump(p, ChangeSet({'id': 'p1', 'node1': 'j1', 'node2': 'j2'})) + set_pump(p, ChangeSet({'id': 'p0', 'power': 100.0})) + p0 = get_pump(p, 'p0') + assert p0['power'] == 100.0 + + add_pump(p, ChangeSet({'id': 'p1', 'node1': 'j1', 'node2': 'j2', 'power': 0.0})) links = get_links(p) assert len(links) == 2 assert links[0] == 'p0' @@ -889,12 +897,13 @@ class TestApi: assert is_junction(p, 'j3') assert is_junction(p, 'j4') - cs = add_pump(p, ChangeSet({'id': 'p0', 'node1': 'j1', 'node2': 'j2'})).operations[0] + cs = add_pump(p, ChangeSet({'id': 'p0', 'node1': 'j1', 'node2': 'j2', 'power': 0.0})).operations[0] assert cs['operation'] == 'add' assert cs['type'] == PUMP assert cs['id'] == 'p0' assert cs['node1'] == 'j1' assert cs['node2'] == 'j2' + assert cs['power'] == 0.0 cs = execute_undo(p).operations[0] assert cs['operation'] == 'delete' @@ -919,7 +928,7 @@ class TestApi: links = get_links(p) assert len(links) == 0 - add_pump(p, ChangeSet({'id': 'p0', 'node1': 'j1', 'node2': 'j2'})) + add_pump(p, ChangeSet({'id': 'p0', 'node1': 'j1', 'node2': 'j2', 'power': 0.0})) links = get_links(p) assert len(links) == 1 @@ -968,6 +977,18 @@ class TestApi: assert cs['node1'] == 'j1' assert cs['node2'] == 'j2' + cs = set_pump(p, ChangeSet({'id': 'p0', 'power': 100.0})).operations[0] + assert cs['operation'] == 'update' + assert cs['type'] == PUMP + assert cs['id'] == 'p0' + assert cs['power'] == 100.0 + + cs = execute_undo(p).operations[0] + assert cs['operation'] == 'update' + assert cs['type'] == PUMP + assert cs['id'] == 'p0' + assert cs['power'] == 0.0 + self.leave(p) @@ -1153,6 +1174,596 @@ class TestApi: self.leave(p) + def test_demand(self): + p = 'test_demand' + self.enter(p) + + add_junction(p, ChangeSet({'id': 'j1', 'x': 0.0, 'y': 10.0, 'elevation': 20.0})) + assert is_junction(p, 'j1') + + d = get_demand(p, 'j1') + assert d['junction'] == 'j1' + assert d['demands'] == [] + + set_demand(p, ChangeSet({'junction': 'j1', 'demands': [{'demand': 10.0, 'pattern': None, 'category': 'x'}, + {'demand': 20.0, 'pattern': None, 'category': None}]})) + + d = get_demand(p, 'j1') + assert d['junction'] == 'j1' + ds = d['demands'] + assert len(ds) == 2 + assert ds[0]['demand'] == 10.0 + assert ds[0]['pattern'] == None + assert ds[0]['category'] == 'x' + assert ds[1]['demand'] == 20.0 + assert ds[1]['pattern'] == None + assert ds[1]['category'] == None + + set_demand(p, ChangeSet({'junction': 'j1', 'demands': []})) + + d = get_demand(p, 'j1') + assert d['junction'] == 'j1' + assert d['demands'] == [] + + self.leave(p) + + + def test_demand_op(self): + p = 'test_demand_op' + self.enter(p) + + add_junction(p, ChangeSet({'id': 'j1', 'x': 0.0, 'y': 10.0, 'elevation': 20.0})) + assert is_junction(p, 'j1') + + cs = set_demand(p, ChangeSet({'junction': 'j1', 'demands': [{'demand': 10.0, 'pattern': None, 'category': 'x'}, + {'demand': 20.0, 'pattern': None, 'category': None}]})).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'demand' + assert cs['junction'] == 'j1' + ds = cs['demands'] + assert len(ds) == 2 + assert ds[0]['demand'] == 10.0 + assert ds[0]['pattern'] == None + assert ds[0]['category'] == 'x' + assert ds[1]['demand'] == 20.0 + assert ds[1]['pattern'] == None + assert ds[1]['category'] == None + + cs = execute_undo(p).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'demand' + assert cs['junction'] == 'j1' + assert len(cs['demands']) == 0 + + cs = execute_redo(p).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'demand' + assert cs['junction'] == 'j1' + ds = cs['demands'] + assert len(ds) == 2 + assert ds[0]['demand'] == 10.0 + assert ds[0]['pattern'] == None + assert ds[0]['category'] == 'x' + assert ds[1]['demand'] == 20.0 + assert ds[1]['pattern'] == None + assert ds[1]['category'] == None + + self.leave(p) + + + def test_status(self): + p = 'test_status' + self.enter(p) + + add_junction(p, ChangeSet({'id': 'j1', 'x': 0.0, 'y': 10.0, 'elevation': 20.0})) + add_junction(p, ChangeSet({'id': 'j2', 'x': 0.0, 'y': 10.0, 'elevation': 20.0})) + assert is_junction(p, 'j1') + assert is_junction(p, 'j2') + + add_pipe(p, ChangeSet({'id': 'p0', 'node1': 'j1', 'node2': 'j2', 'length': 100.0, 'diameter': 10.0, 'roughness': 0.1, 'minor_loss': 0.5, 'status': PIPE_STATUS_OPEN })) + assert is_pipe(p, 'p0') + + s = get_status(p, 'p0') + assert s['link'] == 'p0' + assert s['status'] == None + assert s['setting'] == None + + set_status(p, ChangeSet({'link': 'p0', 'status': LINK_STATUS_OPEN, 'setting': 10.0})) + s = get_status(p, 'p0') + assert s['link'] == 'p0' + assert s['status'] == LINK_STATUS_OPEN + assert s['setting'] == 10.0 + + set_status(p, ChangeSet({'link': 'p0', 'status': None, 'setting': None})) + s = get_status(p, 'p0') + assert s['link'] == 'p0' + assert s['status'] == None + assert s['setting'] == None + + self.leave(p) + + + def test_status_op(self): + p = 'test_status_op' + self.enter(p) + + add_junction(p, ChangeSet({'id': 'j1', 'x': 0.0, 'y': 10.0, 'elevation': 20.0})) + add_junction(p, ChangeSet({'id': 'j2', 'x': 0.0, 'y': 10.0, 'elevation': 20.0})) + assert is_junction(p, 'j1') + assert is_junction(p, 'j2') + + add_pipe(p, ChangeSet({'id': 'p0', 'node1': 'j1', 'node2': 'j2', 'length': 100.0, 'diameter': 10.0, 'roughness': 0.1, 'minor_loss': 0.5, 'status': PIPE_STATUS_OPEN })) + assert is_pipe(p, 'p0') + + s = get_status(p, 'p0') + assert s['link'] == 'p0' + assert s['status'] == None + assert s['setting'] == None + + cs = set_status(p, ChangeSet({'link': 'p0', 'status': LINK_STATUS_OPEN, 'setting': 10.0})).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'status' + assert cs['link'] == 'p0' + assert cs['status'] == LINK_STATUS_OPEN + assert cs['setting'] == 10.0 + + cs = execute_undo(p).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'status' + assert cs['link'] == 'p0' + assert cs['status'] == None + assert cs['setting'] == None + + s = get_status(p, 'p0') + assert s['link'] == 'p0' + assert s['status'] == None + assert s['setting'] == None + + cs = execute_redo(p).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'status' + assert cs['link'] == 'p0' + assert cs['status'] == LINK_STATUS_OPEN + assert cs['setting'] == 10.0 + + self.leave(p) + + + def test_pattern(self): + p = 'test_pattern' + self.enter(p) + + assert is_pattern(p, 'p0') == False + p0 = get_pattern(p, 'p0') + assert p0['id'] == 'p0' + assert p0['factors'] == [] + + set_pattern(p, ChangeSet({'id' : 'p0', 'factors': [1.0, 2.0, 3.0]})) + + assert is_pattern(p, 'p0') + p0 = get_pattern(p, 'p0') + assert p0['id'] == 'p0' + assert p0['factors'] == [1.0, 2.0, 3.0] + + set_pattern(p, ChangeSet({'id' : 'p0', 'factors': []})) + + assert is_pattern(p, 'p0') == False + p0 = get_pattern(p, 'p0') + assert p0['id'] == 'p0' + assert p0['factors'] == [] + + self.leave(p) + + + def test_pattern_op(self): + p = 'test_pattern_op' + self.enter(p) + + cs = set_pattern(p, ChangeSet({'id' : 'p0', 'factors': [1.0, 2.0, 3.0]})).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == PATTERN + assert cs['id'] == 'p0' + assert cs['factors'] == [1.0, 2.0, 3.0] + + cs = execute_undo(p).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == PATTERN + assert cs['id'] == 'p0' + assert cs['factors'] == [] + + cs = execute_redo(p).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == PATTERN + assert cs['id'] == 'p0' + assert cs['factors'] == [1.0, 2.0, 3.0] + + self.leave(p) + + + def test_curve(self): + p = 'test_curve' + self.enter(p) + + assert is_curve(p, 'c0') == False + c0 = get_curve(p, 'c0') + assert c0['id'] == 'c0' + assert c0['coords'] == [] + + set_curve(p, ChangeSet({'id' : 'c0', 'coords': [{'x': 1.0, 'y': 2.0}, {'x': 2.0, 'y': 1.0}]})) + + assert is_curve(p, 'c0') + c0 = get_curve(p, 'c0') + assert c0['id'] == 'c0' + xys = c0['coords'] + assert len(xys) == 2 + assert xys[0]['x'] == 1.0 + assert xys[0]['y'] == 2.0 + assert xys[1]['x'] == 2.0 + assert xys[1]['y'] == 1.0 + + set_curve(p, ChangeSet({'id' : 'c0', 'coords': []})) + + assert is_curve(p, 'c0') == False + c0 = get_curve(p, 'c0') + assert c0['id'] == 'c0' + assert c0['coords'] == [] + + self.leave(p) + + + def test_curve_op(self): + p = 'test_curve_op' + self.enter(p) + + cs = set_curve(p, ChangeSet({'id' : 'c0', 'coords': [{'x': 1.0, 'y': 2.0}, {'x': 2.0, 'y': 1.0}]})).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == CURVE + assert cs['id'] == 'c0' + xys = cs['coords'] + assert len(xys) == 2 + assert xys[0]['x'] == 1.0 + assert xys[0]['y'] == 2.0 + assert xys[1]['x'] == 2.0 + assert xys[1]['y'] == 1.0 + + cs = execute_undo(p).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == CURVE + assert cs['id'] == 'c0' + assert cs['coords'] == [] + + cs = execute_redo(p).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == CURVE + assert cs['id'] == 'c0' + xys = cs['coords'] + assert len(xys) == 2 + assert xys[0]['x'] == 1.0 + assert xys[0]['y'] == 2.0 + assert xys[1]['x'] == 2.0 + assert xys[1]['y'] == 1.0 + + self.leave(p) + + + def test_emitter(self): + p = 'test_emitter' + self.enter(p) + + add_junction(p, ChangeSet({'id': 'j1', 'x': 0.0, 'y': 10.0, 'elevation': 20.0})) + assert is_junction(p, 'j1') + + e = get_emitter(p, 'j1') + assert e['junction'] == 'j1' + assert e['coefficient'] == None + + set_emitter(p, ChangeSet({'junction': 'j1', 'coefficient': 10.0})) + + e = get_emitter(p, 'j1') + assert e['junction'] == 'j1' + assert e['coefficient'] == 10.0 + + set_emitter(p, ChangeSet({'junction': 'j1', 'coefficient': None})) + + e = get_emitter(p, 'j1') + assert e['junction'] == 'j1' + assert e['coefficient'] == None + + self.leave(p) + + + def test_emitter_op(self): + p = 'test_emitter_op' + self.enter(p) + + add_junction(p, ChangeSet({'id': 'j1', 'x': 0.0, 'y': 10.0, 'elevation': 20.0})) + assert is_junction(p, 'j1') + + cs = set_emitter(p, ChangeSet({'junction': 'j1', 'coefficient': 10.0})).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'emitter' + assert cs['junction'] == 'j1' + assert cs['coefficient'] == 10.0 + + cs = execute_undo(p).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'emitter' + assert cs['junction'] == 'j1' + assert cs['coefficient'] == None + + cs = execute_redo(p).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'emitter' + assert cs['junction'] == 'j1' + assert cs['coefficient'] == 10.0 + + self.leave(p) + + + def test_time(self): + p = 'test_time' + self.enter(p) + + t = get_time(p) + assert t['DURATION'] == '0:00' + assert t['HYDRAULIC TIMESTEP'] == '1:00' + assert t['QUALITY TIMESTEP'] == '0:05' + assert t['RULE TIMESTEP'] == '0:05' + assert t['PATTERN TIMESTEP'] == '1:00' + assert t['PATTERN START'] == '0:00' + assert t['REPORT TIMESTEP'] == '1:00' + assert t['REPORT START'] == '0:00' + assert t['START CLOCKTIME'] == '12:00 AM' + assert t['STATISTIC'] == TIME_STATISTIC_NONE + + t['STATISTIC'] = TIME_STATISTIC_AVERAGED + set_time(p, ChangeSet(t)) + + t = get_time(p) + assert t['DURATION'] == '0:00' + assert t['HYDRAULIC TIMESTEP'] == '1:00' + assert t['QUALITY TIMESTEP'] == '0:05' + assert t['RULE TIMESTEP'] == '0:05' + assert t['PATTERN TIMESTEP'] == '1:00' + assert t['PATTERN START'] == '0:00' + assert t['REPORT TIMESTEP'] == '1:00' + assert t['REPORT START'] == '0:00' + assert t['START CLOCKTIME'] == '12:00 AM' + assert t['STATISTIC'] == TIME_STATISTIC_AVERAGED + + self.leave(p) + + + def test_time_op(self): + p = 'test_time_op' + self.enter(p) + + t = get_time(p) + assert t['DURATION'] == '0:00' + assert t['HYDRAULIC TIMESTEP'] == '1:00' + assert t['QUALITY TIMESTEP'] == '0:05' + assert t['RULE TIMESTEP'] == '0:05' + assert t['PATTERN TIMESTEP'] == '1:00' + assert t['PATTERN START'] == '0:00' + assert t['REPORT TIMESTEP'] == '1:00' + assert t['REPORT START'] == '0:00' + assert t['START CLOCKTIME'] == '12:00 AM' + assert t['STATISTIC'] == TIME_STATISTIC_NONE + + t['STATISTIC'] = TIME_STATISTIC_AVERAGED + cs = set_time(p, ChangeSet(t)).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'time' + assert cs['DURATION'] == '0:00' + assert cs['HYDRAULIC TIMESTEP'] == '1:00' + assert cs['QUALITY TIMESTEP'] == '0:05' + assert cs['RULE TIMESTEP'] == '0:05' + assert cs['PATTERN TIMESTEP'] == '1:00' + assert cs['PATTERN START'] == '0:00' + assert cs['REPORT TIMESTEP'] == '1:00' + assert cs['REPORT START'] == '0:00' + assert cs['START CLOCKTIME'] == '12:00 AM' + assert cs['STATISTIC'] == TIME_STATISTIC_AVERAGED + + cs = execute_undo(p).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'time' + assert cs['DURATION'] == '0:00' + assert cs['HYDRAULIC TIMESTEP'] == '1:00' + assert cs['QUALITY TIMESTEP'] == '0:05' + assert cs['RULE TIMESTEP'] == '0:05' + assert cs['PATTERN TIMESTEP'] == '1:00' + assert cs['PATTERN START'] == '0:00' + assert cs['REPORT TIMESTEP'] == '1:00' + assert cs['REPORT START'] == '0:00' + assert cs['START CLOCKTIME'] == '12:00 AM' + assert cs['STATISTIC'] == TIME_STATISTIC_NONE + + cs = execute_redo(p).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'time' + assert cs['DURATION'] == '0:00' + assert cs['HYDRAULIC TIMESTEP'] == '1:00' + assert cs['QUALITY TIMESTEP'] == '0:05' + assert cs['RULE TIMESTEP'] == '0:05' + assert cs['PATTERN TIMESTEP'] == '1:00' + assert cs['PATTERN START'] == '0:00' + assert cs['REPORT TIMESTEP'] == '1:00' + assert cs['REPORT START'] == '0:00' + assert cs['START CLOCKTIME'] == '12:00 AM' + assert cs['STATISTIC'] == TIME_STATISTIC_AVERAGED + + self.leave(p) + + + def test_option(self): + p = 'test_option' + self.enter(p) + + o = get_option(p) + assert o['UNITS'] == OPTION_UNITS_GPM + assert o['HEADLOSS'] == OPTION_HEADLOSS_HW + assert o['VISCOSITY'] == '1.0' + assert o['SPECIFIC GRAVITY'] == '1.0' + assert o['TRIALS'] == '40' + assert o['ACCURACY'] == '0.001' + assert o['FLOWCHANGE'] == '0' + assert o['HEADERROR'] == '0' + assert o['CHECKFREQ'] == '2' + assert o['MAXCHECK'] == '10' + assert o['DAMPLIMIT'] == '0' + assert o['UNBALANCED'] == OPTION_UNBALANCED_STOP + assert o['DEMAND MODEL'] == OPTION_DEMAND_MODEL_DDA + assert o['MINIMUM PRESSURE'] == '0' + assert o['REQUIRED PRESSURE'] == '0.1' + assert o['PRESSURE EXPONENT'] == '0.5' + assert o['PATTERN'] == '1' + assert o['DEMAND MULTIPLIER'] == '1.0' + assert o['EMITTER EXPONENT'] == '0.5' + assert o['QUALITY'] == OPTION_QUALITY_NONE + assert o['DIFFUSIVITY'] == '1.0' + assert o['TOLERANCE'] == '0.01' + + o['UNITS'] = OPTION_UNITS_LPS + set_option(p, ChangeSet(o)) + + o = get_option(p) + assert o['UNITS'] == OPTION_UNITS_LPS + assert o['HEADLOSS'] == OPTION_HEADLOSS_HW + assert o['VISCOSITY'] == '1.0' + assert o['SPECIFIC GRAVITY'] == '1.0' + assert o['TRIALS'] == '40' + assert o['ACCURACY'] == '0.001' + assert o['FLOWCHANGE'] == '0' + assert o['HEADERROR'] == '0' + assert o['CHECKFREQ'] == '2' + assert o['MAXCHECK'] == '10' + assert o['DAMPLIMIT'] == '0' + assert o['UNBALANCED'] == OPTION_UNBALANCED_STOP + assert o['DEMAND MODEL'] == OPTION_DEMAND_MODEL_DDA + assert o['MINIMUM PRESSURE'] == '0' + assert o['REQUIRED PRESSURE'] == '0.1' + assert o['PRESSURE EXPONENT'] == '0.5' + assert o['PATTERN'] == '1' + assert o['DEMAND MULTIPLIER'] == '1.0' + assert o['EMITTER EXPONENT'] == '0.5' + assert o['QUALITY'] == OPTION_QUALITY_NONE + assert o['DIFFUSIVITY'] == '1.0' + assert o['TOLERANCE'] == '0.01' + + self.leave(p) + + + def test_option_op(self): + p = 'test_option_op' + self.enter(p) + + o = get_option(p) + assert o['UNITS'] == OPTION_UNITS_GPM + assert o['HEADLOSS'] == OPTION_HEADLOSS_HW + assert o['VISCOSITY'] == '1.0' + assert o['SPECIFIC GRAVITY'] == '1.0' + assert o['TRIALS'] == '40' + assert o['ACCURACY'] == '0.001' + assert o['FLOWCHANGE'] == '0' + assert o['HEADERROR'] == '0' + assert o['CHECKFREQ'] == '2' + assert o['MAXCHECK'] == '10' + assert o['DAMPLIMIT'] == '0' + assert o['UNBALANCED'] == OPTION_UNBALANCED_STOP + assert o['DEMAND MODEL'] == OPTION_DEMAND_MODEL_DDA + assert o['MINIMUM PRESSURE'] == '0' + assert o['REQUIRED PRESSURE'] == '0.1' + assert o['PRESSURE EXPONENT'] == '0.5' + assert o['PATTERN'] == '1' + assert o['DEMAND MULTIPLIER'] == '1.0' + assert o['EMITTER EXPONENT'] == '0.5' + assert o['QUALITY'] == OPTION_QUALITY_NONE + assert o['DIFFUSIVITY'] == '1.0' + assert o['TOLERANCE'] == '0.01' + + o['UNITS'] = OPTION_UNITS_LPS + cs = set_option(p, ChangeSet(o)).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'option' + assert cs['UNITS'] == OPTION_UNITS_LPS + assert cs['HEADLOSS'] == OPTION_HEADLOSS_HW + assert cs['VISCOSITY'] == '1.0' + assert cs['SPECIFIC GRAVITY'] == '1.0' + assert cs['TRIALS'] == '40' + assert cs['ACCURACY'] == '0.001' + assert cs['FLOWCHANGE'] == '0' + assert cs['HEADERROR'] == '0' + assert cs['CHECKFREQ'] == '2' + assert cs['MAXCHECK'] == '10' + assert cs['DAMPLIMIT'] == '0' + assert cs['UNBALANCED'] == OPTION_UNBALANCED_STOP + assert cs['DEMAND MODEL'] == OPTION_DEMAND_MODEL_DDA + assert cs['MINIMUM PRESSURE'] == '0' + assert cs['REQUIRED PRESSURE'] == '0.1' + assert cs['PRESSURE EXPONENT'] == '0.5' + assert cs['PATTERN'] == '1' + assert cs['DEMAND MULTIPLIER'] == '1.0' + assert cs['EMITTER EXPONENT'] == '0.5' + assert cs['QUALITY'] == OPTION_QUALITY_NONE + assert cs['DIFFUSIVITY'] == '1.0' + assert cs['TOLERANCE'] == '0.01' + + cs = execute_undo(p).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'option' + assert cs['UNITS'] == OPTION_UNITS_GPM + assert cs['HEADLOSS'] == OPTION_HEADLOSS_HW + assert cs['VISCOSITY'] == '1.0' + assert cs['SPECIFIC GRAVITY'] == '1.0' + assert cs['TRIALS'] == '40' + assert cs['ACCURACY'] == '0.001' + assert cs['FLOWCHANGE'] == '0' + assert cs['HEADERROR'] == '0' + assert cs['CHECKFREQ'] == '2' + assert cs['MAXCHECK'] == '10' + assert cs['DAMPLIMIT'] == '0' + assert cs['UNBALANCED'] == OPTION_UNBALANCED_STOP + assert cs['DEMAND MODEL'] == OPTION_DEMAND_MODEL_DDA + assert cs['MINIMUM PRESSURE'] == '0' + assert cs['REQUIRED PRESSURE'] == '0.1' + assert cs['PRESSURE EXPONENT'] == '0.5' + assert cs['PATTERN'] == '1' + assert cs['DEMAND MULTIPLIER'] == '1.0' + assert cs['EMITTER EXPONENT'] == '0.5' + assert cs['QUALITY'] == OPTION_QUALITY_NONE + assert cs['DIFFUSIVITY'] == '1.0' + assert cs['TOLERANCE'] == '0.01' + + cs = execute_redo(p).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'option' + assert cs['UNITS'] == OPTION_UNITS_LPS + assert cs['HEADLOSS'] == OPTION_HEADLOSS_HW + assert cs['VISCOSITY'] == '1.0' + assert cs['SPECIFIC GRAVITY'] == '1.0' + assert cs['TRIALS'] == '40' + assert cs['ACCURACY'] == '0.001' + assert cs['FLOWCHANGE'] == '0' + assert cs['HEADERROR'] == '0' + assert cs['CHECKFREQ'] == '2' + assert cs['MAXCHECK'] == '10' + assert cs['DAMPLIMIT'] == '0' + assert cs['UNBALANCED'] == OPTION_UNBALANCED_STOP + assert cs['DEMAND MODEL'] == OPTION_DEMAND_MODEL_DDA + assert cs['MINIMUM PRESSURE'] == '0' + assert cs['REQUIRED PRESSURE'] == '0.1' + assert cs['PRESSURE EXPONENT'] == '0.5' + assert cs['PATTERN'] == '1' + assert cs['DEMAND MULTIPLIER'] == '1.0' + assert cs['EMITTER EXPONENT'] == '0.5' + assert cs['QUALITY'] == OPTION_QUALITY_NONE + assert cs['DIFFUSIVITY'] == '1.0' + assert cs['TOLERANCE'] == '0.01' + + self.leave(p) + + def test_snapshot(self): p = "test_snapshot" self.enter(p) @@ -1199,7 +1810,7 @@ class TestApi: def test_batch_commands(self): - p = 'test_valve_op' + p = 'test_batch_commands' self.enter(p) cs = ChangeSet() @@ -1210,6 +1821,16 @@ class TestApi: cs = execute_batch_commands(p, cs) assert len(cs.operations) == 2 + cs = ChangeSet() + cs.delete({'type': JUNCTION, 'id': 'j1'}) + cs.delete({'type': JUNCTION, 'id': 'j2'}) + + cs = execute_batch_commands(p, cs) + assert len(cs.operations) == 2 + + cs = execute_undo(p) + assert len(cs.operations) == 1 + self.leave(p) diff --git a/tjnetwork.py b/tjnetwork.py index a8151fe..ef8a177 100644 --- a/tjnetwork.py +++ b/tjnetwork.py @@ -23,6 +23,8 @@ TANK = api.TANK PIPE = api.PIPE PUMP = api.PUMP VALVE = api.VALVE +PATTERN = api.PATTERN +CURVE = api.CURVE OVERFLOW_YES = api.OVERFLOW_YES OVERFLOW_NO = api.OVERFLOW_NO @@ -38,6 +40,42 @@ VALVES_TYPE_FCV = api.VALVES_TYPE_FCV VALVES_TYPE_TCV = api.VALVES_TYPE_TCV VALVES_TYPE_GPV = api.VALVES_TYPE_GPV +LINK_STATUS_OPEN = api.LINK_STATUS_OPEN +LINK_STATUS_CLOSED = api.LINK_STATUS_CLOSED +LINK_STATUS_ACTIVE = api.LINK_STATUS_ACTIVE + +TIME_STATISTIC_NONE = api.TIME_STATISTIC_NONE +TIME_STATISTIC_AVERAGED = api.TIME_STATISTIC_AVERAGED +TIME_STATISTIC_MINIMUM = api.TIME_STATISTIC_MINIMUM +TIME_STATISTIC_MAXIMUM = api.TIME_STATISTIC_MAXIMUM +TIME_STATISTIC_RANGE = api.TIME_STATISTIC_RANGE + +OPTION_UNITS_CFS = api.OPTION_UNITS_CFS +OPTION_UNITS_GPM = api.OPTION_UNITS_GPM +OPTION_UNITS_MGD = api.OPTION_UNITS_MGD +OPTION_UNITS_IMGD = api.OPTION_UNITS_IMGD +OPTION_UNITS_AFD = api.OPTION_UNITS_AFD +OPTION_UNITS_LPS = api.OPTION_UNITS_LPS +OPTION_UNITS_LPM = api.OPTION_UNITS_LPM +OPTION_UNITS_MLD = api.OPTION_UNITS_MLD +OPTION_UNITS_CMH = api.OPTION_UNITS_CMH +OPTION_UNITS_CMD = api.OPTION_UNITS_CMD + +OPTION_HEADLOSS_HW = api.OPTION_HEADLOSS_HW +OPTION_HEADLOSS_DW = api.OPTION_HEADLOSS_DW +OPTION_HEADLOSS_CM = api.OPTION_HEADLOSS_CM + +OPTION_UNBALANCED_STOP = api.OPTION_UNBALANCED_STOP +OPTION_UNBALANCED_CONTINUE = api.OPTION_UNBALANCED_CONTINUE + +OPTION_DEMAND_MODEL_DDA = api.OPTION_DEMAND_MODEL_DDA +OPTION_DEMAND_MODEL_PDA = api.OPTION_DEMAND_MODEL_PDA + +OPTION_QUALITY_NONE = api.OPTION_QUALITY_NONE +OPTION_QUALITY_CHEMICAL = api.OPTION_QUALITY_CHEMICAL +OPTION_QUALITY_AGE = api.OPTION_QUALITY_AGE +OPTION_QUALITY_TRACE = api.OPTION_QUALITY_TRACE + ############################################################ # project @@ -264,7 +302,7 @@ def get_pump(name: str, id: str) -> dict[str, Any]: def set_pump(name: str, cs: ChangeSet) -> ChangeSet: return api.set_pump(name, cs) -# example: add_pump(p, ChangeSet({'id': 'p0', 'node1': 'j1', 'node2': 'j2'})) +# example: add_pump(p, ChangeSet({'id': 'p0', 'node1': 'j1', 'node2': 'j2', 'power': 0})) def add_pump(name: str, cs: ChangeSet) -> ChangeSet: return api.add_pump(name, cs) @@ -293,6 +331,105 @@ def delete_valve(name: str, cs: ChangeSet) -> ChangeSet: return api.delete_valve(name, cs) +############################################################ +# demand 9.[DEMANDS] +############################################################ + +def get_demand_schema(name: str) -> dict[str, dict[str, Any]]: + return api.get_demand_schema(name) + +def get_demand(name: str, junction: str) -> dict[str, Any]: + return api.get_demand(name, junction) + +# { 'operation': 'update', 'type': 'demand', 'junction': 'j1', 'demands': [{'demand': 0.0, 'patten': None, 'category': None}] } +def set_demand(name: str, cs: ChangeSet) -> ChangeSet: + return api.set_demand(name, cs) + + +############################################################ +# status 10.[STATUS] +############################################################ + +def get_status_schema(name: str) -> dict[str, dict[str, Any]]: + return api.get_status_schema(name) + +def get_status(name: str, link: str) -> dict[str, Any]: + return api.get_status(name, link) + +def set_status(name: str, cs: ChangeSet) -> ChangeSet: + return api.set_status(name, cs) + + +############################################################ +# pattern 11.[PATTERNS] +############################################################ + +def get_pattern_schema(name: str) -> dict[str, dict[str, Any]]: + return api.get_pattern_schema(name) + +def get_pattern(name: str, id: str) -> dict[str, Any]: + return api.get_pattern(name, id) + +def set_pattern(name: str, cs: ChangeSet) -> ChangeSet: + return api.set_pattern(name, cs) + + +############################################################ +# curve 12.[CURVES] +############################################################ + +def get_curve_schema(name: str) -> dict[str, dict[str, Any]]: + return api.get_curve_schema(name) + +def get_curve(name: str, id: str) -> dict[str, Any]: + return api.get_curve(name, id) + +def set_curve(name: str, cs: ChangeSet) -> ChangeSet: + return api.set_curve(name, cs) + + +############################################################ +# emitter 16.[EMITTERS] +############################################################ + +def get_emitter_schema(name: str) -> dict[str, dict[str, Any]]: + return api.get_emitter_schema(name) + +def get_emitter(name: str, junction: str) -> dict[str, Any]: + return api.get_emitter(name, junction) + +def set_emitter(name: str, cs: ChangeSet) -> ChangeSet: + return api.set_emitter(name, cs) + + +############################################################ +# time 21.[EMITTERS] +############################################################ + +def get_time_schema(name: str) -> dict[str, dict[str, Any]]: + return api.get_time_schema(name) + +def get_time(name: str) -> dict[str, Any]: + return api.get_time(name) + +def set_time(name: str, cs: ChangeSet) -> ChangeSet: + return api.set_time(name, cs) + + +############################################################ +# option 23.[OPTIONS] +############################################################ + +def get_option_schema(name: str) -> dict[str, dict[str, Any]]: + return api.get_option_schema(name) + +def get_option(name: str) -> dict[str, Any]: + return api.get_option(name) + +def set_option(name: str, cs: ChangeSet) -> ChangeSet: + return api.set_option(name, cs) + + ############################################################ # coord 24.[COORDINATES] ############################################################