easysewer 0.0.2__py3-none-any.whl → 0.0.3__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,194 @@
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
+ # Check if directory exists, create it if not
72
+ if not os.path.exists(output_dir):
73
+ os.makedirs(output_dir)
74
+ # Combine datetime and UUID for a unique filename
75
+ model_name = f"{date_string}_{unique_id}"
76
+ # Set default file paths if not provided
77
+ if inp_file is None:
78
+ inp_file = os.path.join(output_dir, f"{model_name}.inp")
79
+ if rpt_file is None:
80
+ rpt_file = os.path.join(output_dir, f"{model_name}.rpt")
81
+ if out_file is None:
82
+ out_file = os.path.join(output_dir, f"{model_name}.out")
83
+
84
+ # Export model to desired path
85
+ self.to_inp(inp_file)
86
+
87
+ # Initialize swmm solver
88
+ solver = SWMMSolverAPI()
89
+
90
+ # If using fast mode, then use run() method to execute the simulation
91
+ if mode == "fast":
92
+ solver.run(inp_file, rpt_file, out_file)
93
+ solver.close()
94
+ return inp_file, rpt_file, out_file
95
+
96
+ # Open the model
97
+ err = solver.open(inp_file, rpt_file, out_file)
98
+ if err:
99
+ print(f"Error opening SWMM project: {err}")
100
+ print(solver.get_error())
101
+ exit(1)
102
+
103
+ # Start simulation
104
+ err = solver.start(1)
105
+ if err:
106
+ print(f"Error starting SWMM simulation: {err}")
107
+ print(solver.get_error())
108
+ exit(1)
109
+
110
+ # Prepare for simulation progress
111
+ print("Simulation progress:")
112
+ bar_length = 50 # Length of progress bar
113
+ last_progress_int = -1
114
+ start_time = datetime.datetime(
115
+ year=self.calc.simulation_start['year'],
116
+ month=self.calc.simulation_start['month'],
117
+ day=self.calc.simulation_start['day'],
118
+ hour=self.calc.simulation_start['hour'],
119
+ minute=self.calc.simulation_start['minute']
120
+ )
121
+ end_time = datetime.datetime(
122
+ year=self.calc.simulation_end['year'],
123
+ month=self.calc.simulation_end['month'],
124
+ day=self.calc.simulation_end['day'],
125
+ hour=self.calc.simulation_end['hour'],
126
+ minute=self.calc.simulation_end['minute']
127
+ )
128
+ total_seconds = (end_time - start_time).total_seconds()
129
+
130
+ # Simulation iteration
131
+ while True:
132
+ result, elapsed_time = solver.step()
133
+ if elapsed_time <= 0:
134
+ break
135
+
136
+ # Progress bar - Convert elapsed time to percentage of total simulation time
137
+ # Multiply by 24*60*60 to convert days to seconds (SWMM uses days as time unit)
138
+ progress = (elapsed_time * 24 * 60 * 60 / total_seconds) * 100
139
+ progress_int = int(progress)
140
+
141
+ # Only update progress when it changes by at least 1%
142
+ if progress_int > last_progress_int:
143
+ # Calculate the number of characters to fill
144
+ filled_length = int(bar_length * progress / 100)
145
+ bar = '█' * filled_length + '-' * (bar_length - filled_length)
146
+
147
+ # Print the entire progress bar each time (overwriting previous one)
148
+ print(f"\r[{bar}] {progress_int}%", end='', flush=True)
149
+ last_progress_int = progress_int
150
+
151
+ # Complete the progress bar when finished
152
+ print(f"\r[{'█' * bar_length}] 100%")
153
+
154
+ # End the simulation
155
+ err = solver.end()
156
+ if err:
157
+ print(f"Error ending SWMM simulation: {err}")
158
+ print(solver.get_error())
159
+
160
+ # Check simulation mass balance errors (continuity errors)
161
+ # These errors indicate the accuracy of the simulation results
162
+ # Values under 5% are generally acceptable for most applications
163
+ runoff_error_percent, flow_error_percent, quality_error_percent = solver.get_mass_bal_err()
164
+
165
+ def _check_error(error_type: str, error_percent: float) -> None:
166
+ """
167
+ Validate simulation error percentages against acceptability threshold.
168
+
169
+ Args:
170
+ error_type: Category of error being checked from:
171
+ - Runoff: Rainfall runoff calculation errors
172
+ - Flow: Hydraulic flow continuity errors
173
+ - Quality: Water quality simulation errors
174
+ error_percent: Calculated percentage error (positive/negative)
175
+
176
+ Note:
177
+ Prints warning message to stderr when exceeding 5% threshold
178
+ Does not interrupt simulation execution
179
+ """
180
+ ERROR_THRESHOLD = 5
181
+ if abs(error_percent) > ERROR_THRESHOLD:
182
+ print(f"WARNING: {error_type} error percentage ({error_percent:.2f}%) exceeds {ERROR_THRESHOLD}%")
183
+
184
+ # Check for errors over 5%
185
+ _check_error("Runoff", runoff_error_percent)
186
+ _check_error("Flow", flow_error_percent)
187
+ _check_error("Quality", quality_error_percent)
188
+
189
+ # Close the solver
190
+ err = solver.close()
191
+ if err:
192
+ print(f"Error closing SWMM project: {err}")
193
+ print(solver.get_error())
194
+ return inp_file, rpt_file, out_file