Architecture
System Overview
pyhnhtools uses a three-layer architecture to support multiple analysis modules with a shared GUI:
┌────────────────────────────────────────┐
│ qt_gui.py (Generic PyQt5 Framework) │
│ - Layout, widgets, visualization │
│ - No solver-specific logic │
└────────────────┬───────────────────────┘
│
┌───────────┴──────────────┐
│ │
┌────▼─────────────────┐ ┌───▼──────────────────┐
│ std_step_1d_gui.py │ │ culverts_gui.py │
│ (Integration Layer) │ │ (Integration Layer) │
│ Workflow logic │ │ Module-specific UI │
└────┬─────────────────┘ └───┬──────────────────┘
│ │
┌────▼──────────────┐ ┌──────▼────────────────┐
│ standard_step_1d_ │ │ culverts_adapter.py │
│ adapter.py │ │ (Data Adapter) │
│ (Data Adapter) │ │ │
└────┬──────────────┘ └──────┬────────────────┘
│ │
┌────▼──────────────┐ ┌──────▼────────────────┐
│ std_step_1d/ │ │ culverts/ │
│ (Core Solver) │ │ (Core Solver) │
└───────────────────┘ └───────────────────────┘
Layer Descriptions
Layer 1: qt_gui.py (Generic GUI Framework)
The core GUI framework handles:
Window layout and organization
Tab and widget management
User interaction routing
Common visualization patterns
Key principle: No solver-specific logic here
Layer 2: {module}_gui.py (Integration Interface)
Module-specific integration layer handles:
Module workflow orchestration
Converting GUI inputs to solver inputs
Converting solver outputs to GUI display
Plotting module-specific results
Examples:
std_step_1d_gui.py→ StandardStep1DInterfaceculverts_gui.py→ CulvertsInterface
Layer 3: {module}_adapter.py (Data Adapter)
Bridges module API and GUI:
Serializes/deserializes data (JSON, CSV)
Transforms data between formats
Handles file I/O
Layer 4: Core Solver
Pure computation with no GUI knowledge:
std_step_1d/- Standard step 1D solverculverts/- Culvert analysis module
Data Flow
Typical execution flow:
User Input → GUI collects parameters
Interface → std_step_1d_gui.py receives input
Adapter → standard_step_1d_adapter.py formats data
Solver → std_step_1d module runs analysis
Adapter → Converts results back to Python objects
Interface → Updates GUI displays
GUI → Shows results to user
Module Integration
To add a new module (e.g., unsteady flow):
Create Integration Layer
Copy
std_step_1d_gui.pytemplateRename to
unsteady_gui.pyAdapt methods for unsteady workflow
Implement
UnsteadyInterfaceclass
Create Data Adapter (if needed)
New file:
unsteady_adapter.pyHandle model serialization
Call core unsteady solver
Register in qt_gui.py
Import new interface
Instantiate in BackwaterWidget
Create tab for unsteady analysis
Wire button clicks
Benefits
One GUI serves all modules
Each module has independent logic
Easy to add new modules
No code duplication
Package Structure
pyhnhtools/
├── __init__.py # Package initialization
├── gui/
│ ├── qt_gui.py # Core GUI framework (1,799 lines)
│ ├── std_step_1d_gui.py # Template & working example
│ ├── culverts_gui.py # Template for different workflows
│ ├── app.py # Application launcher
│ └── __init__.py
├── standard_step_1d_adapter.py # Data adapter
├── std_step_1d/ # Core solver
│ ├── __init__.py
│ ├── solver.py
│ └── ...
├── culverts/ # Culvert analysis module
│ ├── __init__.py
│ └── ...
├── parsers/ # HEC-RAS parsers
│ ├── __init__.py
│ └── ...
└── ...
Design Principles
Separation of Concerns
Each layer has single responsibility:
GUI manages interface, not computation
Interfaces manage workflow, not data format
Adapters manage serialization, not solving
Solvers manage computation, not UI
No Circular Dependencies
Data flows downward only:
GUI → Interfaces → Adapters → Solvers
Solvers never import GUI
Adapters never import interfaces
Extensibility
Add new solvers without modifying GUI:
New solver = new interface class
qt_gui.py just gets one new reference
Minimal changes to existing code
Maintainability
Clear boundaries make debugging easier:
GUI issues isolated to qt_gui.py
Workflow issues in {module}_gui.py
Data issues in {module}_adapter.py
Computation issues in {module}/
API Consistency
All module interfaces follow same pattern:
class {Module}Interface:
def __init__(self, gui_widget)
def setup() # Initialize
def load_model(filepath) # Load data
def new_model() # Create empty
def run_solver() # Execute
def validate_model() # Pre-checks
def save_model(filepath) # Save data
This consistency makes code predictable.
Technology Stack
GUI Framework: PyQt5 (desktop application)
Plotting: Plotly (interactive visualization)
Data: NumPy, SciPy (numerical computation)
Serialization: JSON (human-readable configs)
Build: Setuptools (package distribution)
Documentation: Sphinx + Read the Docs
Performance Considerations
Geometry Processing
Cross-section interpolation scales with point count:
<100 points: Instant
100-1000 points: <1 second
>1000 points: May slow GUI
Solver Execution
Standard step solver scales with section count:
5-20 sections: Milliseconds
20-100 sections: <1 second
>100 sections: Variable by geometry
GUI Responsiveness
For large models, consider:
Run solver in background thread
Reduce plot complexity for preview
Use command-line API for batch jobs
Future Expansion
The architecture supports:
Unsteady Flow Module (time-varying boundary conditions)
Hydrology Module (basin and routing analysis)
Multiple Backends (different solvers for same problem type)
Plugin System (user-defined modules)
Cloud Integration (REST API frontend)
See Module Integration for details.