Fortran Naming Conventions for SUEWS
March 11, 2026 · View on GitHub
This document establishes the official naming conventions for all Fortran code in SUEWS. These conventions aim to provide consistency, maintainability, and clear organisation as the codebase evolves.
Table of Contents
- Current State and Rationale
- File Naming (Established)
- Module Naming (New Standard)
- Subroutine and Function Naming
- Variable and Constant Naming
- Type Naming
- Migration Strategy
- Validation Tools
Current State and Rationale
What We Have
As of October 2025, the SUEWS Fortran source (src/suews/src/) contains:
- ✅ Files: Perfectly consistent (33 files)
- ❌ Modules: 7+ different naming patterns across ~83 modules
- ⚠️ Subroutines/Functions: 3-4 inconsistent patterns
Why This Matters
- Discoverability: Developers should instantly know which file contains which module
- Maintainability: Consistent patterns reduce cognitive load
- Tooling: Automated checks and refactoring tools work better with conventions
- Onboarding: New contributors learn the codebase faster
Design Principles
- Align module names with file names (one source of truth)
- Maintain categorical organisation (ctrl, phys, util)
- Use lowercase with underscores (match scientific Python conventions)
- Prefer one module per file (simpler mental model)
File Naming (Established)
Status: ✅ Current and enforced
Pattern
suews_<category>_<name>.f95
Categories
ctrl: Control flow, driver, input/output, type definitions, error handlingphys: Physics schemes and models (OHM, LUMPS, ESTM, snow, etc.)util: Utilities (datetime, meteo calculations, string operations, etc.)
Examples
suews_ctrl_const.f95 - Constants and parameters
suews_ctrl_driver.f95 - Main driver and orchestration
suews_ctrl_type.f95 - Type definitions
suews_phys_snow.f95 - Snow physics
suews_phys_lumps.f95 - LUMPS urban scheme
suews_util_datetime.f95 - Date/time utilities
suews_util_meteo.f95 - Meteorological calculations
Rules
- All lowercase
- Underscore-separated (snake_case)
.f95extension (not.f90)- No abbreviations in category names
- Keep
<name>concise but descriptive
Module Naming (New Standard)
Status: 🆕 Adopted for all new code from October 2025
Pattern
MODULE module_<category>_<name>
File vs Module Naming
- Files: Keep
suews_prefix (e.g.,suews_phys_snow.f95) to indicate SUEWS project ownership - Modules: Use
module_prefix (e.g.,MODULE module_phys_snow) for simpler, clearer code
Rationale
- Project ownership: File names with
suews_clearly indicate these belong to SUEWS - Code clarity: Module names with
module_are simpler and more readable in USE statements - Category organisation: Both maintain
<category>for clear structure - One module per scheme: Each physics scheme should have ONE consolidated module
Examples
Single Module per File (Preferred)
! File: suews_phys_snow.f95
MODULE module_phys_snow
IMPLICIT NONE
! Snow physics implementation - all snow-related code in ONE module
CONTAINS
SUBROUTINE update_snow_state(...)
END SUBROUTINE update_snow_state
END MODULE module_phys_snow
Principle: ONE Module Per Physics Scheme
Avoid creating sub-modules unless absolutely necessary. If code logically belongs to the same physics scheme or utility, merge it into ONE module.
Bad example (too many sub-modules):
! File: suews_phys_stebbs.f95
MODULE module_phys_stebbs_precision
MODULE module_phys_stebbs_core
MODULE module_phys_stebbs_func
MODULE module_phys_stebbs_couple
MODULE module_phys_stebbs ! Main interface
Good example (consolidated):
! File: suews_phys_stebbs.f95
MODULE module_phys_stebbs
USE ISO_FORTRAN_ENV, ONLY: REAL64
IMPLICIT NONE
! Precision definitions (merged in)
INTEGER, PARAMETER :: rprc = REAL64
! Data structures (merged in)
TYPE :: LBM
! ... building data structure
END TYPE
CONTAINS
! All functions merged into main module
SUBROUTINE stebbsonlinecouple(...)
FUNCTION ext_conv_coeff(...) RESULT(hc)
END MODULE module_phys_stebbs
Multiple Modules Only When Serving Different Purposes
General-purpose utilities that serve MULTIPLE physics schemes may stay separate:
! File: suews_util_meteo.f95
MODULE module_util_meteo
! Meteorological calculations used by snow, OHM, LUMPS, etc.
END MODULE module_util_meteo
! File: suews_util_time.f95
MODULE module_util_time
! Time utilities used by multiple modules
END MODULE module_util_time
Comparison with Current Codebase
| File | Current Module(s) | New Module |
|---|---|---|
suews_phys_snow.f95 | Snow_module | module_phys_snow |
suews_phys_stebbs.f95 | modulestebbsprecision, modulestebbs, modulestebbsfunc, etc. (5 modules) | module_phys_stebbs (ONE consolidated module) |
suews_util_meteo.f95 | METEO | module_util_meteo |
suews_util_time.f95 | time_module | module_util_time |
Subroutine and Function Naming
Pattern: snake_case for all subroutines and functions
Rationale: Consistent with file names, module names, and variable names. Matches scientific Python conventions (NumPy, SciPy). Simpler - no need to remember scope-based casing rules.
! Subroutines
SUBROUTINE calculate_snow_density(temperature, age, density)
SUBROUTINE update_atmospheric_state(pressure, temperature, humidity)
SUBROUTINE initialise_physics_scheme(config, state)
! Functions
FUNCTION calculate_saturation_vapour_pressure(temp) RESULT(es)
FUNCTION get_surface_resistance(lai, radiation) RESULT(rs)
FUNCTION interpolate_linear(x1, x2, y1, y2, x) RESULT(y)
Common Prefixes
Use clear, descriptive prefixes for clarity:
! Calculation functions
FUNCTION calc_aerodynamic_resistance(...) RESULT(ra)
FUNCTION calc_roughness_length(...) RESULT(z0)
FUNCTION calc_beta_rsl(...) RESULT(beta)
! Initialisation/finalisation
SUBROUTINE init_snow_arrays(nsteps, nlayers)
SUBROUTINE finalise_physics_state(state)
! Update routines
SUBROUTINE update_snow_state(...)
SUBROUTINE update_thermal_conductivity(density, temp, k_eff)
! Get/set accessors (if needed)
FUNCTION get_snow_albedo(...) RESULT(alpha)
SUBROUTINE set_surface_properties(...)
Well-Known Acronyms
For well-established acronyms (≤4 letters), keep uppercase with underscores:
SUBROUTINE ohm_calculate(...) ! Objective Hysteresis Model
SUBROUTINE estm_update(...) ! Element Surface Temperature Method
SUBROUTINE ehc_finalise(...) ! Element Heat Capacity
FUNCTION cbl_height(...) RESULT(h) ! Convective Boundary Layer
Note: These can also be lowercase (e.g., ohm_calculate) - choose one style and be consistent within a module.
Module-Specific Routines
Prefix with module topic for clarity:
MODULE suews_phys_snow
CONTAINS
SUBROUTINE snow_initialise(...) ! Initialisation
SUBROUTINE snow_update(...) ! Main update step
SUBROUTINE snow_finalise(...) ! Cleanup
FUNCTION snow_albedo(...) RESULT(alpha)
END MODULE suews_phys_snow
Summary
| Component | Convention | Example |
|---|---|---|
| All subroutines | snake_case | update_snow_state |
| All functions | snake_case | calc_density |
| Well-known acronyms | lowercase or UPPERCASE | estm_update or ESTM_update |
| Module-specific | <topic>_<action> | snow_initialise |
Key point: One simple rule - everything uses snake_case (except UPPERCASE for constants)
Variable and Constant Naming
Local Variables
Pattern: lowercase_with_underscores
REAL(KIND(1D0)) :: air_temperature ! [K]
REAL(KIND(1D0)) :: surface_resistance ! [s m-1]
REAL(KIND(1D0)) :: latent_heat_flux ! [W m-2]
INTEGER :: time_step_count
INTEGER :: surface_type_id
LOGICAL :: is_snow_covered
Module-Level Variables
Same as local variables, but document scope:
MODULE suews_phys_snow
IMPLICIT NONE
! Module state (should be minimal, prefer passing as arguments)
REAL(KIND(1D0)), SAVE :: prev_snow_depth = 0.0D0 ! [m]
LOGICAL, SAVE :: is_initialised = .FALSE.
Constants and Parameters
Pattern: UPPERCASE_WITH_UNDERSCORES
! Physical constants
REAL(KIND(1D0)), PARAMETER :: STEFAN_BOLTZMANN = 5.67D-8 ! [W m-2 K-4]
REAL(KIND(1D0)), PARAMETER :: GRAVITY_ACCEL = 9.81D0 ! [m s-2]
REAL(KIND(1D0)), PARAMETER :: GAS_CONSTANT_DRY_AIR = 287.04D0 ! [J kg-1 K-1]
! Model constants
REAL(KIND(1D0)), PARAMETER :: SNOW_DENSITY_FRESH = 100.0D0 ! [kg m-3]
INTEGER, PARAMETER :: MAX_ITERATIONS = 100
REAL(KIND(1D0)), PARAMETER :: CONVERGENCE_THRESHOLD = 1.0D-6
Arrays
Include dimensionality information:
! Allocatable arrays
REAL(KIND(1D0)), DIMENSION(:), ALLOCATABLE :: surface_temperature ! [K] (nsurf)
REAL(KIND(1D0)), DIMENSION(:,:), ALLOCATABLE :: soil_moisture ! [m3 m-3] (nsurf, nlayers)
! Fixed-size arrays
REAL(KIND(1D0), DIMENSION(7) :: surface_fraction ! [-] (nsurf)
Physical Units Documentation
Always document units in comments using square brackets:
REAL(KIND(1D0)) :: temperature = 0.0D0 ! [K]
REAL(KIND(1D0)) :: rainfall = 0.0D0 ! [mm h-1]
REAL(KIND(1D0)) :: pressure = 0.0D0 ! [Pa]
REAL(KIND(1D0)) :: wind_speed = 0.0D0 ! [m s-1]
REAL(KIND(1D0)) :: fraction = 0.0D0 ! [-] dimensionless
Type Naming
User-Defined Types (Derived Types)
Pattern: dts_<name> where dts = "derived type"
Rationale:
dtsis proper Fortran terminology for user-defined types- Prefix pattern (not suffix) makes types immediately recognizable
- Consistent with scientific programming conventions
TYPE :: dts_snow_state
REAL(KIND(1D0)) :: depth = 0.0D0 ! [m]
REAL(KIND(1D0)) :: density = 100.0D0 ! [kg m-3]
REAL(KIND(1D0)) :: albedo = 0.8D0 ! [-]
REAL(KIND(1D0)) :: temperature = 273.15D0 ! [K]
INTEGER :: age_days = 0 ! [days]
END TYPE dts_snow_state
TYPE :: dts_forcing
REAL(KIND(1D0)) :: temperature ! [K]
REAL(KIND(1D0)) :: pressure ! [Pa]
REAL(KIND(1D0)) :: humidity ! [kg kg-1]
REAL(KIND(1D0)) :: wind_speed ! [m s-1]
END TYPE dts_forcing
TYPE :: dts_fluxes
REAL(KIND(1D0)) :: sensible_heat ! [W m-2]
REAL(KIND(1D0)) :: latent_heat ! [W m-2]
REAL(KIND(1D0)) :: ground_heat ! [W m-2]
REAL(KIND(1D0)) :: net_radiation ! [W m-2]
END TYPE dts_fluxes
Type Components
Use lowercase_with_underscores (same as variables):
TYPE :: dts_ohm_state
REAL(KIND(1D0)) :: storage_heat_flux ! [W m-2]
REAL(KIND(1D0)) :: hysteresis_coef ! [-]
REAL(KIND(1D0)) :: thermal_time_constant ! [s]
END TYPE dts_ohm_state
Examples of Type Usage
MODULE module_phys_snow
TYPE :: dts_snow_state
REAL(KIND(1D0)) :: depth
REAL(KIND(1D0)) :: density
END TYPE dts_snow_state
CONTAINS
SUBROUTINE update_snow(state)
TYPE(dts_snow_state), INTENT(INOUT) :: state
! Update snow state
END SUBROUTINE update_snow
END MODULE module_phys_snow
Migration Strategy
For New Code (Effective Immediately)
All new Fortran code must follow these conventions:
- New modules:
MODULE module_<category>_<name>(in filesuews_<category>_<name>.f95) - New types:
TYPE :: dts_<name> - One module per physics scheme (consolidate sub-modules)
- New subroutines/functions:
snake_case - New variables:
snake_casewith units in comments
For Existing Code (Gradual Migration)
Phase 1: Module Consolidation and Renaming (2025)
Current status: In progress
- Consolidate sub-modules into ONE module per physics scheme
- Example: STEBBS 5 modules → 1 module
- Rename modules from old patterns to
module_<category>_<name> - Create backward compatibility aliases for all old module names
- Update all USE statements to use new module names
- Test builds after each batch
- Remove aliases after one minor version
Phase 2: Medium-Priority Files (2026-2027)
Files with moderate activity or partial consistency
Phase 3: Low-Priority Files (2027+)
Legacy files rarely touched, migrate as opportunities arise
Backward Compatibility Pattern
! New standard module name
MODULE suews_phys_snow
IMPLICIT NONE
! Implementation
END MODULE suews_phys_snow
! Temporary alias for backward compatibility
! TODO: Remove in version X.Y.0 (deprecated since X.(Y-1).0)
MODULE Snow_module
USE suews_phys_snow
! All functionality is now in suews_phys_snow
END MODULE Snow_module
Documentation Requirements
When renaming modules:
- Add deprecation notice in module header comment
- Update CHANGELOG with
[change]entry - Update user documentation if module is referenced
- Add migration note to release notes
Validation Tools
Automated Naming Checker
Location: scripts/suews/check_naming_conventions.py
Usage:
# Check all Fortran files
python scripts/suews/check_naming_conventions.py
# Check specific files
python scripts/suews/check_naming_conventions.py src/suews/src/suews_phys_snow.f95
# Generate report
python scripts/suews/check_naming_conventions.py --report naming_report.txt
What it checks:
- ✅ File naming matches pattern
- ✅ Module naming matches file
- ✅ Multiple modules use appropriate suffixes
- ⚠️ Public subroutines use PascalCase (warning only)
- ⚠️ Constants use UPPERCASE (warning only)
Pre-commit Hook Integration
Add to .pre-commit-config.yaml:
- repo: local
hooks:
- id: fortran-naming-check
name: Fortran Naming Convention Check
entry: python scripts/suews/check_naming_conventions.py
language: python
files: \.f95$
pass_filenames: true
CI/CD Integration
Add to GitHub Actions (.github/workflows/naming-check.yml):
name: Naming Convention Check
on: [pull_request]
jobs:
check-naming:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check Fortran Naming
run: python scripts/suews/check_naming_conventions.py
Examples and Anti-Patterns
✅ Good Example
! File: suews_phys_resistance.f95
MODULE suews_phys_resistance
IMPLICIT NONE
! Constants
REAL(KIND(1D0)), PARAMETER :: VON_KARMAN = 0.4D0 ! [-]
! Types
TYPE :: resistance_state_t
REAL(KIND(1D0)) :: aerodynamic ! [s m-1]
REAL(KIND(1D0)) :: boundary_layer ! [s m-1]
REAL(KIND(1D0)) :: surface ! [s m-1]
END TYPE resistance_state_t
PRIVATE
PUBLIC :: resistance_state_t
PUBLIC :: calc_aerodynamic_resistance
CONTAINS
! Public API - snake_case for all functions
FUNCTION calc_aerodynamic_resistance(wind_speed, height, roughness) &
RESULT(ra)
REAL(KIND(1D0)), INTENT(IN) :: wind_speed ! [m s-1]
REAL(KIND(1D0)), INTENT(IN) :: height ! [m]
REAL(KIND(1D0)), INTENT(IN) :: roughness ! [m]
REAL(KIND(1D0)) :: ra ! [s m-1]
REAL(KIND(1D0)) :: log_ratio
log_ratio = calc_log_wind_profile(height, roughness)
ra = log_ratio / (VON_KARMAN * wind_speed)
END FUNCTION calc_aerodynamic_resistance
! Private helper - also snake_case (consistent!)
FUNCTION calc_log_wind_profile(z, z0) RESULT(psi)
REAL(KIND(1D0)), INTENT(IN) :: z, z0
REAL(KIND(1D0)) :: psi
psi = LOG(z / z0)
END FUNCTION calc_log_wind_profile
END MODULE suews_phys_resistance
❌ Anti-Patterns to Avoid
! File: snow.f95 ❌ Missing category and suews prefix
MODULE Snow_Module ❌ Doesn't match file, inconsistent case
REAL(KIND(1D0)) :: globalSnowDepth ❌ Module state without SAVE, camelCase
FUNCTION getDensity(T) RESULT(rho) ❌ PascalCase inconsistent with snake_case standard
REAL(KIND(1D0)) :: T ❌ Single-letter non-standard variable
REAL(KIND(1D0)) :: rho
rho = 100 + 0.5*T ❌ Magic numbers without explanation
END FUNCTION
type snowstate ❌ Missing _t suffix, no consistency
real :: d ❌ No precision, single letter, no units
integer :: t
end type
END MODULE
References
- Fortran Naming Best Practices (Fortran-Lang)
- Modern Fortran Style Guide (Goddard Space Flight Center)
- Scientific Software Naming Conventions (NumPy, SciPy)