epyt-flow 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- epyt_flow/EPANET/EPANET/SRC_engines/AUTHORS +28 -0
- epyt_flow/EPANET/EPANET/SRC_engines/LICENSE +21 -0
- epyt_flow/EPANET/EPANET/SRC_engines/Readme_SRC_Engines.txt +18 -0
- epyt_flow/EPANET/EPANET/SRC_engines/enumstxt.h +134 -0
- epyt_flow/EPANET/EPANET/SRC_engines/epanet.c +5578 -0
- epyt_flow/EPANET/EPANET/SRC_engines/epanet2.c +865 -0
- epyt_flow/EPANET/EPANET/SRC_engines/epanet2.def +131 -0
- epyt_flow/EPANET/EPANET/SRC_engines/errors.dat +73 -0
- epyt_flow/EPANET/EPANET/SRC_engines/funcs.h +193 -0
- epyt_flow/EPANET/EPANET/SRC_engines/genmmd.c +1000 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hash.c +177 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hash.h +28 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hydcoeffs.c +1151 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hydraul.c +1117 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hydsolver.c +720 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hydstatus.c +476 -0
- epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2.h +431 -0
- epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_2.h +1786 -0
- epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_enums.h +468 -0
- epyt_flow/EPANET/EPANET/SRC_engines/inpfile.c +810 -0
- epyt_flow/EPANET/EPANET/SRC_engines/input1.c +707 -0
- epyt_flow/EPANET/EPANET/SRC_engines/input2.c +864 -0
- epyt_flow/EPANET/EPANET/SRC_engines/input3.c +2170 -0
- epyt_flow/EPANET/EPANET/SRC_engines/main.c +93 -0
- epyt_flow/EPANET/EPANET/SRC_engines/mempool.c +142 -0
- epyt_flow/EPANET/EPANET/SRC_engines/mempool.h +24 -0
- epyt_flow/EPANET/EPANET/SRC_engines/output.c +852 -0
- epyt_flow/EPANET/EPANET/SRC_engines/project.c +1359 -0
- epyt_flow/EPANET/EPANET/SRC_engines/quality.c +685 -0
- epyt_flow/EPANET/EPANET/SRC_engines/qualreact.c +743 -0
- epyt_flow/EPANET/EPANET/SRC_engines/qualroute.c +694 -0
- epyt_flow/EPANET/EPANET/SRC_engines/report.c +1489 -0
- epyt_flow/EPANET/EPANET/SRC_engines/rules.c +1362 -0
- epyt_flow/EPANET/EPANET/SRC_engines/smatrix.c +871 -0
- epyt_flow/EPANET/EPANET/SRC_engines/text.h +497 -0
- epyt_flow/EPANET/EPANET/SRC_engines/types.h +874 -0
- epyt_flow/EPANET/EPANET-MSX/MSX_Updates.txt +53 -0
- epyt_flow/EPANET/EPANET-MSX/Src/dispersion.h +27 -0
- epyt_flow/EPANET/EPANET-MSX/Src/hash.c +107 -0
- epyt_flow/EPANET/EPANET-MSX/Src/hash.h +28 -0
- epyt_flow/EPANET/EPANET-MSX/Src/include/epanetmsx.h +102 -0
- epyt_flow/EPANET/EPANET-MSX/Src/include/epanetmsx_export.h +42 -0
- epyt_flow/EPANET/EPANET-MSX/Src/mathexpr.c +937 -0
- epyt_flow/EPANET/EPANET-MSX/Src/mathexpr.h +39 -0
- epyt_flow/EPANET/EPANET-MSX/Src/mempool.c +204 -0
- epyt_flow/EPANET/EPANET-MSX/Src/mempool.h +24 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxchem.c +1285 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxcompiler.c +368 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxdict.h +42 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxdispersion.c +586 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxerr.c +116 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxfile.c +260 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxfuncs.c +175 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxfuncs.h +35 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxinp.c +1504 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxout.c +401 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxproj.c +791 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxqual.c +2010 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxrpt.c +400 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxtank.c +422 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxtoolkit.c +1164 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxtypes.h +551 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxutils.c +524 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxutils.h +56 -0
- epyt_flow/EPANET/EPANET-MSX/Src/newton.c +158 -0
- epyt_flow/EPANET/EPANET-MSX/Src/newton.h +34 -0
- epyt_flow/EPANET/EPANET-MSX/Src/rk5.c +287 -0
- epyt_flow/EPANET/EPANET-MSX/Src/rk5.h +39 -0
- epyt_flow/EPANET/EPANET-MSX/Src/ros2.c +293 -0
- epyt_flow/EPANET/EPANET-MSX/Src/ros2.h +35 -0
- epyt_flow/EPANET/EPANET-MSX/Src/smatrix.c +816 -0
- epyt_flow/EPANET/EPANET-MSX/Src/smatrix.h +29 -0
- epyt_flow/EPANET/EPANET-MSX/readme.txt +14 -0
- epyt_flow/EPANET/compile.sh +4 -0
- epyt_flow/VERSION +1 -0
- epyt_flow/__init__.py +24 -0
- epyt_flow/data/__init__.py +0 -0
- epyt_flow/data/benchmarks/__init__.py +11 -0
- epyt_flow/data/benchmarks/batadal.py +257 -0
- epyt_flow/data/benchmarks/batadal_data.py +28 -0
- epyt_flow/data/benchmarks/battledim.py +473 -0
- epyt_flow/data/benchmarks/battledim_data.py +51 -0
- epyt_flow/data/benchmarks/gecco_water_quality.py +267 -0
- epyt_flow/data/benchmarks/leakdb.py +592 -0
- epyt_flow/data/benchmarks/leakdb_data.py +18923 -0
- epyt_flow/data/benchmarks/water_usage.py +123 -0
- epyt_flow/data/networks.py +650 -0
- epyt_flow/gym/__init__.py +4 -0
- epyt_flow/gym/control_gyms.py +47 -0
- epyt_flow/gym/scenario_control_env.py +101 -0
- epyt_flow/metrics.py +404 -0
- epyt_flow/models/__init__.py +2 -0
- epyt_flow/models/event_detector.py +31 -0
- epyt_flow/models/sensor_interpolation_detector.py +118 -0
- epyt_flow/rest_api/__init__.py +4 -0
- epyt_flow/rest_api/base_handler.py +70 -0
- epyt_flow/rest_api/res_manager.py +95 -0
- epyt_flow/rest_api/scada_data_handler.py +476 -0
- epyt_flow/rest_api/scenario_handler.py +352 -0
- epyt_flow/rest_api/server.py +106 -0
- epyt_flow/serialization.py +438 -0
- epyt_flow/simulation/__init__.py +5 -0
- epyt_flow/simulation/events/__init__.py +6 -0
- epyt_flow/simulation/events/actuator_events.py +259 -0
- epyt_flow/simulation/events/event.py +81 -0
- epyt_flow/simulation/events/leakages.py +404 -0
- epyt_flow/simulation/events/sensor_faults.py +267 -0
- epyt_flow/simulation/events/sensor_reading_attack.py +185 -0
- epyt_flow/simulation/events/sensor_reading_event.py +170 -0
- epyt_flow/simulation/events/system_event.py +88 -0
- epyt_flow/simulation/parallel_simulation.py +147 -0
- epyt_flow/simulation/scada/__init__.py +3 -0
- epyt_flow/simulation/scada/advanced_control.py +134 -0
- epyt_flow/simulation/scada/scada_data.py +1589 -0
- epyt_flow/simulation/scada/scada_data_export.py +255 -0
- epyt_flow/simulation/scenario_config.py +608 -0
- epyt_flow/simulation/scenario_simulator.py +1897 -0
- epyt_flow/simulation/scenario_visualizer.py +61 -0
- epyt_flow/simulation/sensor_config.py +1289 -0
- epyt_flow/topology.py +290 -0
- epyt_flow/uncertainty/__init__.py +3 -0
- epyt_flow/uncertainty/model_uncertainty.py +302 -0
- epyt_flow/uncertainty/sensor_noise.py +73 -0
- epyt_flow/uncertainty/uncertainties.py +555 -0
- epyt_flow/uncertainty/utils.py +206 -0
- epyt_flow/utils.py +306 -0
- epyt_flow-0.1.0.dist-info/LICENSE +21 -0
- epyt_flow-0.1.0.dist-info/METADATA +139 -0
- epyt_flow-0.1.0.dist-info/RECORD +131 -0
- epyt_flow-0.1.0.dist-info/WHEEL +5 -0
- epyt_flow-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module provides classes for exporting SCADA data stored in
|
|
3
|
+
:class:`~epyt_flow.simulation.scada.scada_data.ScadaData`.
|
|
4
|
+
"""
|
|
5
|
+
from abc import abstractmethod
|
|
6
|
+
import numpy as np
|
|
7
|
+
from scipy.io import savemat
|
|
8
|
+
import pandas as pd
|
|
9
|
+
|
|
10
|
+
from .scada_data import ScadaData
|
|
11
|
+
from ..sensor_config import SensorConfig
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ScadaDataExport():
|
|
15
|
+
"""
|
|
16
|
+
Base class for exporting SCADA data stored in
|
|
17
|
+
:class:`~epyt_flow.simulation.scada.scada_data.ScadaData`.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
f_out : `str`
|
|
22
|
+
Path to the file to which the SCADA data will be exported.
|
|
23
|
+
export_raw_data : `bool`, optional
|
|
24
|
+
If True, the raw measurements (i.e. sensor reading without any noise or faults)
|
|
25
|
+
are exported instead of the final sensor readings.
|
|
26
|
+
|
|
27
|
+
The default is False.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, f_out: str, export_raw_data: bool = False, **kwds):
|
|
31
|
+
self.__f_out = f_out
|
|
32
|
+
self.__export_raw_data = export_raw_data
|
|
33
|
+
|
|
34
|
+
super().__init__(**kwds)
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def f_out(self) -> str:
|
|
38
|
+
"""
|
|
39
|
+
Gets the path to the file to which the SCADA data will be exported.
|
|
40
|
+
|
|
41
|
+
Returns
|
|
42
|
+
-------
|
|
43
|
+
`str`
|
|
44
|
+
Path to the file to which the SCADA data will be exported.
|
|
45
|
+
"""
|
|
46
|
+
return self.__f_out
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def export_raw_data(self) -> bool:
|
|
50
|
+
"""
|
|
51
|
+
True if the raw measurements instead of the final sensor readings are requested.
|
|
52
|
+
|
|
53
|
+
Returns
|
|
54
|
+
-------
|
|
55
|
+
`bool`
|
|
56
|
+
True if the raw measurements instead of the final sensor readings are requested.
|
|
57
|
+
"""
|
|
58
|
+
return self.__export_raw_data
|
|
59
|
+
|
|
60
|
+
def create_global_sensor_config(self, scada_data: ScadaData) -> SensorConfig:
|
|
61
|
+
"""
|
|
62
|
+
Creates a global sensor configuration with sensors placed everywhere.
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`
|
|
67
|
+
SCADA data for which the global sensor configuration is to be created.
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
:class:`~epyt_flow.simulation.sensor_config.SensorConfig`
|
|
72
|
+
Global sensor configuration.
|
|
73
|
+
"""
|
|
74
|
+
old_sensor_config = scada_data.sensor_config
|
|
75
|
+
|
|
76
|
+
sensor_config = SensorConfig(nodes=old_sensor_config.nodes,
|
|
77
|
+
links=old_sensor_config.links,
|
|
78
|
+
valves=old_sensor_config.valves,
|
|
79
|
+
pumps=old_sensor_config.pumps,
|
|
80
|
+
tanks=old_sensor_config.tanks,
|
|
81
|
+
bulk_species=old_sensor_config.bulk_species,
|
|
82
|
+
surface_species=old_sensor_config.surface_species)
|
|
83
|
+
sensor_config.pressure_sensors = sensor_config.nodes
|
|
84
|
+
sensor_config.flow_sensors = sensor_config.links
|
|
85
|
+
sensor_config.demand_sensors = sensor_config.nodes
|
|
86
|
+
sensor_config.quality_node_sensors = sensor_config.nodes
|
|
87
|
+
sensor_config.quality_link_sensors = sensor_config.links
|
|
88
|
+
sensor_config.valve_state_sensors = sensor_config.valves
|
|
89
|
+
sensor_config.tank_level_sensors = sensor_config.tanks
|
|
90
|
+
sensor_config.pump_state_sensors = sensor_config.pumps
|
|
91
|
+
sensor_config.bulk_species_node_sensors = sensor_config.bulk_species_node_sensors
|
|
92
|
+
sensor_config.bulk_species_link_sensors = sensor_config.bulk_species_link_sensors
|
|
93
|
+
sensor_config.surface_species_sensors = sensor_config.surface_species_sensors
|
|
94
|
+
|
|
95
|
+
return sensor_config
|
|
96
|
+
|
|
97
|
+
def create_column_desc(self, scada_data: ScadaData) -> np.ndarray:
|
|
98
|
+
"""
|
|
99
|
+
Creates column descriptions -- i.e. sensor type and location for each column
|
|
100
|
+
|
|
101
|
+
Parameters
|
|
102
|
+
----------
|
|
103
|
+
scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`
|
|
104
|
+
SCADA data to be described.
|
|
105
|
+
|
|
106
|
+
Returns
|
|
107
|
+
-------
|
|
108
|
+
`numpy.ndarray`
|
|
109
|
+
2-dimensional array describing all columns of the sensor readings:
|
|
110
|
+
The first dimension describes the sensor type, and the second dimension
|
|
111
|
+
describes the sensor location.
|
|
112
|
+
"""
|
|
113
|
+
sensor_readings = scada_data.get_data()
|
|
114
|
+
|
|
115
|
+
col_desc = [None for _ in range(sensor_readings.shape[1])]
|
|
116
|
+
sensor_config = scada_data.sensor_config
|
|
117
|
+
sensors_id_to_idx = sensor_config.sensors_id_to_idx
|
|
118
|
+
for sensor_type in sensors_id_to_idx:
|
|
119
|
+
for item_id in sensors_id_to_idx[sensor_type]:
|
|
120
|
+
col_id = sensors_id_to_idx[sensor_type][item_id]
|
|
121
|
+
col_desc[col_id] = [sensor_type, item_id]
|
|
122
|
+
|
|
123
|
+
return np.array(col_desc, dtype=object)
|
|
124
|
+
|
|
125
|
+
@abstractmethod
|
|
126
|
+
def export(self, scada_data: ScadaData) -> None:
|
|
127
|
+
"""
|
|
128
|
+
Exports given SCADA data.
|
|
129
|
+
|
|
130
|
+
Parameters
|
|
131
|
+
----------
|
|
132
|
+
scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`
|
|
133
|
+
SCADA data to be exported.
|
|
134
|
+
"""
|
|
135
|
+
raise NotImplementedError()
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class ScadaDataNumpyExport(ScadaDataExport):
|
|
139
|
+
"""
|
|
140
|
+
Class for exporting SCADA data to numpy (.npz file).
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
def export(self, scada_data: ScadaData) -> None:
|
|
144
|
+
"""
|
|
145
|
+
Exports given SCADA data.
|
|
146
|
+
|
|
147
|
+
Parameters
|
|
148
|
+
----------
|
|
149
|
+
scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`
|
|
150
|
+
SCADA data to be exported.
|
|
151
|
+
"""
|
|
152
|
+
if not isinstance(scada_data, ScadaData):
|
|
153
|
+
raise TypeError("'scada_data' must be an instance of " +
|
|
154
|
+
"'epyt_flow.simulation.scada_data.ScadaData' and not of " +
|
|
155
|
+
f"'{type(scada_data)}'")
|
|
156
|
+
|
|
157
|
+
old_sensor_config = None
|
|
158
|
+
if self.export_raw_data is True:
|
|
159
|
+
# Backup old sensor config and set a new one with sensors everywhere
|
|
160
|
+
old_sensor_config = scada_data.sensor_config
|
|
161
|
+
scada_data.change_sensor_config(self.create_global_sensor_config(scada_data))
|
|
162
|
+
|
|
163
|
+
sensor_readings = scada_data.get_data()
|
|
164
|
+
col_desc = self.create_column_desc(scada_data)
|
|
165
|
+
sensor_readings_time = scada_data.sensor_readings_time
|
|
166
|
+
|
|
167
|
+
if self.export_raw_data is True:
|
|
168
|
+
# Restore old sensor config
|
|
169
|
+
scada_data.change_sensor_config(old_sensor_config)
|
|
170
|
+
|
|
171
|
+
np.savez(self.f_out, sensor_readings=sensor_readings, col_desc=col_desc,
|
|
172
|
+
sensor_readings_time=sensor_readings_time)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class ScadaDataXlsxExport(ScadaDataExport):
|
|
176
|
+
"""
|
|
177
|
+
Class for exporting SCADA data to Excel (.xlsx file).
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
def export(self, scada_data: ScadaData) -> None:
|
|
181
|
+
"""
|
|
182
|
+
Exports given SCADA data.
|
|
183
|
+
|
|
184
|
+
Parameters
|
|
185
|
+
----------
|
|
186
|
+
scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`
|
|
187
|
+
SCADA data to be exported.
|
|
188
|
+
"""
|
|
189
|
+
if not isinstance(scada_data, ScadaData):
|
|
190
|
+
raise TypeError("'scada_data' must be an instance of " +
|
|
191
|
+
"'epyt_flow.simulation.scada_data.ScadaData' and not of " +
|
|
192
|
+
f"'{type(scada_data)}'")
|
|
193
|
+
|
|
194
|
+
old_sensor_config = None
|
|
195
|
+
if self.export_raw_data is True:
|
|
196
|
+
# Backup old sensor config and set a new one with sensors everywhere
|
|
197
|
+
old_sensor_config = scada_data.sensor_config
|
|
198
|
+
scada_data.change_sensor_config(self.create_global_sensor_config(scada_data))
|
|
199
|
+
|
|
200
|
+
sensor_readings = scada_data.get_data()
|
|
201
|
+
sensor_readings_time = scada_data.sensor_readings_time
|
|
202
|
+
col_desc = self.create_column_desc(scada_data)
|
|
203
|
+
sensors_name = np.array([f"Sensor {i}" for i in range(1, sensor_readings.shape[1] + 1)],
|
|
204
|
+
dtype=object).reshape(-1, 1)
|
|
205
|
+
col_desc = np.concatenate((sensors_name, col_desc), axis=1)
|
|
206
|
+
|
|
207
|
+
if self.export_raw_data is True:
|
|
208
|
+
# Restore old sensor config
|
|
209
|
+
scada_data.change_sensor_config(old_sensor_config)
|
|
210
|
+
|
|
211
|
+
with pd.ExcelWriter(self.f_out) as writer:
|
|
212
|
+
pd.DataFrame(sensor_readings, columns=[f"Sensor {i}" for i in
|
|
213
|
+
range(1, sensor_readings.shape[1] + 1)]). \
|
|
214
|
+
to_excel(writer, sheet_name="Sensor readings", index=False)
|
|
215
|
+
pd.DataFrame(sensor_readings_time, columns=["Time (s)"]). \
|
|
216
|
+
to_excel(writer, sheet_name="Sensor readings time", index=False)
|
|
217
|
+
pd.DataFrame(col_desc, columns=["Name", "Type", "Location"]). \
|
|
218
|
+
to_excel(writer, sheet_name="Sensors description", index=False)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class ScadaDataMatlabExport(ScadaDataExport):
|
|
222
|
+
"""
|
|
223
|
+
Class for exporting SCADA data to MATLAB (.mat file).
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
def export(self, scada_data: ScadaData) -> None:
|
|
227
|
+
"""
|
|
228
|
+
Exports given SCADA data.
|
|
229
|
+
|
|
230
|
+
Parameters
|
|
231
|
+
----------
|
|
232
|
+
scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`
|
|
233
|
+
SCADA data to be exported.
|
|
234
|
+
"""
|
|
235
|
+
if not isinstance(scada_data, ScadaData):
|
|
236
|
+
raise TypeError("'scada_data' must be an instance of " +
|
|
237
|
+
"'epyt_flow.simulation.scada_data.ScadaData' and not of " +
|
|
238
|
+
f"'{type(scada_data)}'")
|
|
239
|
+
|
|
240
|
+
old_sensor_config = None
|
|
241
|
+
if self.export_raw_data is True:
|
|
242
|
+
# Backup old sensor config and set a new one with sensors everywhere
|
|
243
|
+
old_sensor_config = scada_data.sensor_config
|
|
244
|
+
scada_data.change_sensor_config(self.create_global_sensor_config(scada_data))
|
|
245
|
+
|
|
246
|
+
sensor_readings = scada_data.get_data()
|
|
247
|
+
sensor_readings_time = scada_data.sensor_readings_time
|
|
248
|
+
col_desc = self.create_column_desc(scada_data)
|
|
249
|
+
|
|
250
|
+
if self.export_raw_data is True:
|
|
251
|
+
# Restore old sensor config
|
|
252
|
+
scada_data.change_sensor_config(old_sensor_config)
|
|
253
|
+
|
|
254
|
+
savemat(self.f_out, {"sensor_readings": sensor_readings,
|
|
255
|
+
"sensor_readings_time": sensor_readings_time, "col_desc": col_desc})
|