Support water distribution

This commit is contained in:
WQY\qiong
2023-04-30 14:04:07 +08:00
parent 45296960e7
commit e30c81a891
6 changed files with 618 additions and 50 deletions

View File

@@ -136,4 +136,7 @@ from .s32_region_util import get_nodes_in_boundary, get_nodes_in_region, calcula
from .s33_region import get_region_schema, get_region, set_region, add_region, delete_region
from .s34_water_distribution import DISTRIBUTION_TYPE_ADD, DISTRIBUTION_TYPE_OVERRIDE
from .s34_water_distribution import distribute_demand_to_nodes, distribute_demand_to_region
from .s37_virtual_district import calculate_virtual_district

View File

@@ -32,7 +32,7 @@ from .s31_scada_element import set_scada_element, add_scada_element, delete_scad
from .batch_api_cs import rewrite_batch_api
def execute_add_command(name: str, cs: ChangeSet) -> ChangeSet:
def _execute_add_command(name: str, cs: ChangeSet) -> ChangeSet:
type = cs.operations[0]['type']
if type == s1_title:
@@ -109,7 +109,7 @@ def execute_add_command(name: str, cs: ChangeSet) -> ChangeSet:
return ChangeSet()
def execute_update_command(name: str, cs: ChangeSet) -> ChangeSet:
def _execute_update_command(name: str, cs: ChangeSet) -> ChangeSet:
type = cs.operations[0]['type']
if type == s1_title:
@@ -186,7 +186,7 @@ def execute_update_command(name: str, cs: ChangeSet) -> ChangeSet:
return ChangeSet()
def execute_delete_command(name: str, cs: ChangeSet) -> ChangeSet:
def _execute_delete_command(name: str, cs: ChangeSet) -> ChangeSet:
type = cs.operations[0]['type']
if type == s1_title:
@@ -277,11 +277,11 @@ def execute_batch_commands(name: str, cs: ChangeSet) -> ChangeSet:
todo = op
operation = op['operation']
if operation == API_ADD:
result.merge(execute_add_command(name, ChangeSet(op)))
result.merge(_execute_add_command(name, ChangeSet(op)))
elif operation == API_UPDATE:
result.merge(execute_update_command(name, ChangeSet(op)))
result.merge(_execute_update_command(name, ChangeSet(op)))
elif operation == API_DELETE:
result.merge(execute_delete_command(name, ChangeSet(op)))
result.merge(_execute_delete_command(name, ChangeSet(op)))
except:
print(f'ERROR: Fail to execute {todo}')
@@ -305,11 +305,11 @@ def execute_batch_command(name: str, cs: ChangeSet) -> ChangeSet:
todo = op
operation = op['operation']
if operation == API_ADD:
result.merge(execute_add_command(name, ChangeSet(op)))
result.merge(_execute_add_command(name, ChangeSet(op)))
elif operation == API_UPDATE:
result.merge(execute_update_command(name, ChangeSet(op)))
result.merge(_execute_update_command(name, ChangeSet(op)))
elif operation == API_DELETE:
result.merge(execute_delete_command(name, ChangeSet(op)))
result.merge(_execute_delete_command(name, ChangeSet(op)))
except:
print(f'ERROR: Fail to execute {todo}')

View File

@@ -2,8 +2,9 @@ import ctypes
import platform
import os
import math
import collections
from .s0_base import get_node_links, get_link_nodes
from typing import Any
from .s0_base import get_node_links, get_link_nodes, is_pipe
from .s5_pipes import get_pipe
from .database import read, try_read, read_all, write
from .s24_coordinates import node_has_coord, get_node_coord
@@ -89,35 +90,53 @@ def _angle_of_node_link(node: str, link: str, nodes, links) -> float:
return _angle(v)
class Topology:
def __init__(self, db: str, nodes: list[str]) -> None:
self._nodes: dict[str, Any] = {}
self._max_x_node = ''
for node in nodes:
if not node_has_coord(db, node):
continue
if get_node_links(db, node) == 0:
continue
self._nodes[node] = get_node_coord(db, node) | { 'links': [] }
if self._max_x_node == '' or self._nodes[node]['x'] > self._nodes[self._max_x_node]['x']:
self._max_x_node = node
self._links = {}
for node in self._nodes:
for link in get_node_links(db, node):
candidate = True
link_nodes = get_link_nodes(db, link)
for link_node in link_nodes:
if link_node not in self._nodes:
candidate = False
break
if candidate:
length = get_pipe(db, link)['length'] if is_pipe(db, link) else 0.0
self._links[link] = { 'node1' : link_nodes[0], 'node2' : link_nodes[1], 'length' : length }
if link not in self._nodes[link_nodes[0]]['links']:
self._nodes[link_nodes[0]]['links'].append(link)
if link not in self._nodes[link_nodes[1]]['links']:
self._nodes[link_nodes[1]]['links'].append(link)
def nodes(self):
return self._nodes
def max_x_node(self):
return self._max_x_node
def links(self):
return self._links
def calculate_boundary(name: str, nodes: list[str]) -> list[tuple[float, float]]:
new_nodes = {}
max_x_node = ''
for node in nodes:
if not node_has_coord(name, node):
continue
if get_node_links(name, node) == 0:
continue
new_nodes[node] = get_node_coord(name, node) | { 'links': [] }
if max_x_node == '' or new_nodes[node]['x'] > new_nodes[max_x_node]['x']:
max_x_node = node
topology = Topology(name, nodes)
t_nodes = topology.nodes()
t_links = topology.links()
new_links = {}
for node in new_nodes:
for link in get_node_links(name, node):
candidate = True
link_nodes = get_link_nodes(name, link)
for link_node in link_nodes:
if link_node not in new_nodes:
candidate = False
break
if candidate:
new_links[link] = { 'node1' : link_nodes[0], 'node2' : link_nodes[1] }
if link not in new_nodes[link_nodes[0]]['links']:
new_nodes[link_nodes[0]]['links'].append(link)
if link not in new_nodes[link_nodes[1]]['links']:
new_nodes[link_nodes[1]]['links'].append(link)
cursor = max_x_node
cursor = topology.max_x_node()
in_angle = 0
paths: list[str] = []
@@ -125,8 +144,8 @@ def calculate_boundary(name: str, nodes: list[str]) -> list[tuple[float, float]]
paths.append(cursor)
sorted_links = []
overlapped_link = ''
for link in new_nodes[cursor]['links']:
angle = _angle_of_node_link(cursor, link, new_nodes, new_links)
for link in t_nodes[cursor]['links']:
angle = _angle_of_node_link(cursor, link, t_nodes, t_links)
if angle == in_angle:
overlapped_link = link
continue
@@ -135,7 +154,7 @@ def calculate_boundary(name: str, nodes: list[str]) -> list[tuple[float, float]]
# work into a branch, return
if len(sorted_links) == 0:
cursor = paths[-2]
in_angle = in_angle = _angle_of_node_link(cursor, overlapped_link, new_nodes, new_links)
in_angle = in_angle = _angle_of_node_link(cursor, overlapped_link, t_nodes, t_links)
continue
sorted_links = sorted(sorted_links, key=lambda s:s[0])
@@ -145,17 +164,17 @@ def calculate_boundary(name: str, nodes: list[str]) -> list[tuple[float, float]]
out_link = link
break
cursor = new_links[out_link]['node1'] if cursor == new_links[out_link]['node2'] else new_links[out_link]['node2']
cursor = t_links[out_link]['node1'] if cursor == t_links[out_link]['node2'] else t_links[out_link]['node2']
# end up trip :)
if cursor == max_x_node:
if cursor == topology.max_x_node():
paths.append(cursor)
break
in_angle = _angle_of_node_link(cursor, out_link, new_nodes, new_links)
in_angle = _angle_of_node_link(cursor, out_link, t_nodes, t_links)
boundary: list[tuple[float, float]] = []
for node in paths:
boundary.append((new_nodes[node]['x'], new_nodes[node]['y']))
boundary.append((t_nodes[node]['x'], t_nodes[node]['y']))
return boundary

View File

@@ -0,0 +1,52 @@
from .database import ChangeSet
from .s0_base import is_junction
from .s9_demands import get_demand
from .s32_region_util import Topology, get_nodes_in_region
from .batch_exe import execute_batch_command
DISTRIBUTION_TYPE_ADD = 'ADD'
DISTRIBUTION_TYPE_OVERRIDE = 'OVERRIDE'
def distribute_demand_to_nodes(name: str, demand: float, nodes: list[str], type: str = DISTRIBUTION_TYPE_ADD) -> ChangeSet:
if len(nodes) == 0 or demand == 0.0:
return ChangeSet()
if type != DISTRIBUTION_TYPE_ADD and type != DISTRIBUTION_TYPE_OVERRIDE:
return ChangeSet()
topology = Topology(name, nodes)
t_nodes = topology.nodes()
t_links = topology.links()
length_sum = 0.0
for value in t_links.values():
length_sum += abs(value['length'])
if length_sum <= 0.0:
return ChangeSet()
demand_per_length = demand / length_sum
cs = ChangeSet()
for node, value in t_nodes.items():
if not is_junction(name, node):
continue
demand_per_node = 0.0
for link in value['links']:
demand_per_node += abs(t_links[link]['length']) * demand_per_length * 0.5
ds = get_demand(name, node)['demands']
if len(ds) == 0:
ds = [{'demand': demand_per_node, 'pattern': None, 'category': None}]
elif type == DISTRIBUTION_TYPE_ADD:
ds[0]['demand'] += demand_per_node
else:
ds[0]['demand'] = demand_per_node
cs.update({'type': 'demand', 'junction': node, 'demands': ds})
return execute_batch_command(name, cs)
def distribute_demand_to_region(name: str, demand: float, region: str, type: str = DISTRIBUTION_TYPE_ADD) -> ChangeSet:
nodes = get_nodes_in_region(name, region)
return distribute_demand_to_nodes(name, demand, nodes, type)