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 → StandardStep1DInterface

  • culverts_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 solver

  • culverts/ - Culvert analysis module

Data Flow

Typical execution flow:

  1. User Input → GUI collects parameters

  2. Interface → std_step_1d_gui.py receives input

  3. Adapter → standard_step_1d_adapter.py formats data

  4. Solver → std_step_1d module runs analysis

  5. Adapter → Converts results back to Python objects

  6. Interface → Updates GUI displays

  7. GUI → Shows results to user

Module Integration

To add a new module (e.g., unsteady flow):

  1. Create Integration Layer

    • Copy std_step_1d_gui.py template

    • Rename to unsteady_gui.py

    • Adapt methods for unsteady workflow

    • Implement UnsteadyInterface class

  2. Create Data Adapter (if needed)

    • New file: unsteady_adapter.py

    • Handle model serialization

    • Call core unsteady solver

  3. Register in qt_gui.py

    • Import new interface

    • Instantiate in BackwaterWidget

    • Create tab for unsteady analysis

    • Wire button clicks

  4. 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.