easysewer 0.0.2__py3-none-any.whl → 0.0.4__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.
easysewer/ModelAPI.py ADDED
@@ -0,0 +1,200 @@
1
+ """
2
+ API extensions for Urban Drainage Modeling system
3
+
4
+ This module enhances the base UrbanDrainageModel with:
5
+ - Advanced simulation control with progress tracking
6
+ - Automated output file management
7
+ - Enhanced error checking and rpting
8
+ """
9
+
10
+ import datetime
11
+ import uuid
12
+ import os
13
+ from .UDM import UrbanDrainageModel
14
+ from .SolverAPI import SWMMSolverAPI
15
+
16
+
17
+ class Model(UrbanDrainageModel):
18
+ """
19
+ Enhanced SWMM model with advanced simulation capabilities.
20
+
21
+ This class extends the base easysewer.Model (UrbanDrainageModel) with additional
22
+ functionality for running simulations with progress tracking and error rpting.
23
+ It provides both a fast simulation mode and a detailed step-by-step simulation
24
+ with visual progress feedback.
25
+
26
+ Attributes:
27
+ Inherits all attributes from easysewer.Model (UrbanDrainageModel)
28
+ """
29
+
30
+ def __init__(self, model_path: str | None = None):
31
+ """
32
+ Initialize a Model instance with optional inp file.
33
+
34
+ Args:
35
+ model_path (str | None): Path to SWMM .inp file to load. If None,
36
+ creates an empty model. Defaults to None.
37
+ """
38
+ super().__init__(model_path)
39
+
40
+ def simulation(
41
+ self,
42
+ inp_file: str | None = None,
43
+ rpt_file: str | None = None,
44
+ out_file: str | None = None,
45
+ mode: str = "normal"
46
+ ) -> tuple[str, str, str]:
47
+ """
48
+ Execute SWMM simulation with progress tracking and error checking.
49
+
50
+ Args:
51
+ inp_file: Path for inp .inp file. Auto-generated if None.
52
+ rpt_file: Path for rpt .rpt file. Auto-generated if None.
53
+ out_file: Path for output .out file. Auto-generated if None.
54
+ mode: Execution mode ("fast" for quick run, "normal" for progress tracking)
55
+
56
+ Returns:
57
+ tuple: Paths to generated (inp_file, rpt_file, out_file)
58
+
59
+ Raises:
60
+ SystemExit: If fatal errors occur during simulation setup/execution
61
+
62
+ """
63
+
64
+ # Get current datetime as a filename-safe string
65
+ now = datetime.datetime.now()
66
+ date_string = now.strftime("%Y%m%d_%H%M%S") # Format: YYYYMMDD_HHMMSS
67
+ # Generate a UUID
68
+ unique_id = str(uuid.uuid4())
69
+ # Define output directory
70
+ output_dir = 'simulation_output'
71
+
72
+ # Combine datetime and UUID for a unique filename
73
+ model_name = f"{date_string}_{unique_id}"
74
+ # Set default file paths if not provided
75
+ if inp_file is None:
76
+ inp_file = os.path.join(output_dir, f"{model_name}.inp")
77
+ # Check if directory exists, create it if not
78
+ if not os.path.exists(output_dir):
79
+ os.makedirs(output_dir)
80
+ # Export model to desired path
81
+ self.to_inp(inp_file)
82
+ if rpt_file is None:
83
+ rpt_file = os.path.join(output_dir, f"{model_name}.rpt")
84
+ # Check if directory exists, create it if not
85
+ if not os.path.exists(output_dir):
86
+ os.makedirs(output_dir)
87
+ if out_file is None:
88
+ out_file = os.path.join(output_dir, f"{model_name}.out")
89
+ # Check if directory exists, create it if not
90
+ if not os.path.exists(output_dir):
91
+ os.makedirs(output_dir)
92
+
93
+ # Initialize swmm solver
94
+ solver = SWMMSolverAPI()
95
+
96
+ # If using fast mode, then use run() method to execute the simulation
97
+ if mode == "fast":
98
+ solver.run(inp_file, rpt_file, out_file)
99
+ solver.close()
100
+ return inp_file, rpt_file, out_file
101
+
102
+ # Open the model
103
+ err = solver.open(inp_file, rpt_file, out_file)
104
+ if err:
105
+ print(f"Error opening SWMM project: {err}")
106
+ print(solver.get_error())
107
+ exit(1)
108
+
109
+ # Start simulation
110
+ err = solver.start(1)
111
+ if err:
112
+ print(f"Error starting SWMM simulation: {err}")
113
+ print(solver.get_error())
114
+ exit(1)
115
+
116
+ # Prepare for simulation progress
117
+ print("Simulation progress:")
118
+ bar_length = 50 # Length of progress bar
119
+ last_progress_int = -1
120
+ start_time = datetime.datetime(
121
+ year=self.calc.simulation_start['year'],
122
+ month=self.calc.simulation_start['month'],
123
+ day=self.calc.simulation_start['day'],
124
+ hour=self.calc.simulation_start['hour'],
125
+ minute=self.calc.simulation_start['minute']
126
+ )
127
+ end_time = datetime.datetime(
128
+ year=self.calc.simulation_end['year'],
129
+ month=self.calc.simulation_end['month'],
130
+ day=self.calc.simulation_end['day'],
131
+ hour=self.calc.simulation_end['hour'],
132
+ minute=self.calc.simulation_end['minute']
133
+ )
134
+ total_seconds = (end_time - start_time).total_seconds()
135
+
136
+ # Simulation iteration
137
+ while True:
138
+ result, elapsed_time = solver.step()
139
+ if elapsed_time <= 0:
140
+ break
141
+
142
+ # Progress bar - Convert elapsed time to percentage of total simulation time
143
+ # Multiply by 24*60*60 to convert days to seconds (SWMM uses days as time unit)
144
+ progress = (elapsed_time * 24 * 60 * 60 / total_seconds) * 100
145
+ progress_int = int(progress)
146
+
147
+ # Only update progress when it changes by at least 1%
148
+ if progress_int > last_progress_int:
149
+ # Calculate the number of characters to fill
150
+ filled_length = int(bar_length * progress / 100)
151
+ bar = '=' * filled_length + '+' * (bar_length - filled_length)
152
+
153
+ # Print the entire progress bar each time (overwriting previous one)
154
+ print(f"\r[{bar}] {progress_int}%", end='', flush=True)
155
+ last_progress_int = progress_int
156
+
157
+ # Complete the progress bar when finished
158
+ print(f"\r[{'=' * bar_length}] 100%")
159
+
160
+ # End the simulation
161
+ err = solver.end()
162
+ if err:
163
+ print(f"Error ending SWMM simulation: {err}")
164
+ print(solver.get_error())
165
+
166
+ # Check simulation mass balance errors (continuity errors)
167
+ # These errors indicate the accuracy of the simulation results
168
+ # Values under 5% are generally acceptable for most applications
169
+ runoff_error_percent, flow_error_percent, quality_error_percent = solver.get_mass_bal_err()
170
+
171
+ def _check_error(error_type: str, error_percent: float) -> None:
172
+ """
173
+ Validate simulation error percentages against acceptability threshold.
174
+
175
+ Args:
176
+ error_type: Category of error being checked from:
177
+ - Runoff: Rainfall runoff calculation errors
178
+ - Flow: Hydraulic flow continuity errors
179
+ - Quality: Water quality simulation errors
180
+ error_percent: Calculated percentage error (positive/negative)
181
+
182
+ Note:
183
+ Prints warning message to stderr when exceeding 5% threshold
184
+ Does not interrupt simulation execution
185
+ """
186
+ ERROR_THRESHOLD = 5
187
+ if abs(error_percent) > ERROR_THRESHOLD:
188
+ print(f"WARNING: {error_type} error percentage ({error_percent:.2f}%) exceeds {ERROR_THRESHOLD}%")
189
+
190
+ # Check for errors over 5%
191
+ _check_error("Runoff", runoff_error_percent)
192
+ _check_error("Flow", flow_error_percent)
193
+ _check_error("Quality", quality_error_percent)
194
+
195
+ # Close the solver
196
+ err = solver.close()
197
+ if err:
198
+ print(f"Error closing SWMM project: {err}")
199
+ print(solver.get_error())
200
+ return inp_file, rpt_file, out_file