Code Style Guide
Comprehensive coding standards and style guidelines for Canopy-App development.
Fortran Coding Standards
General Principles
- Clarity over cleverness - Code should be self-documenting
- Consistency - Follow established patterns throughout the codebase
- Modularity - Use modules to organize functionality
- Documentation - All public interfaces must be documented
File Organization
File Naming
# Main program
canopy_app.F90
# Module files
canopy_<functionality>_mod.F90
# Examples:
canopy_bioemi_mod.F90 # Biogenic emissions
canopy_canmet_mod.F90 # Canopy meteorology
canopy_utils_mod.F90 # Utility functions
File Structure Template
!> \file canopy_example_mod.F90
!> \brief Brief description of module purpose
!!
!! Detailed description of the module functionality,
!! including key algorithms, assumptions, and usage.
!!
!! \author Author Name
!! \date Creation date
!! \version Version number
!> \defgroup example_mod Example Module
!! \brief Example module for demonstration
!!
!! This module demonstrates proper code organization
!! and documentation standards for the Canopy-App project.
!! \{
module canopy_example_mod
! Use statements (alphabetical order)
use canopy_const_mod, only: rk => real_kind, pi, grav
use canopy_utils_mod, only: interpolate_linear, check_bounds
use netcdf
! Always include implicit none
implicit none
! Module accessibility (private by default)
private
! Public interfaces (explicitly list all public items)
public :: init_example_module
public :: calc_example_process
public :: finalize_example_module
public :: example_type
! Module parameters (uppercase with underscores)
real(rk), parameter :: DEFAULT_VALUE = 1.0_rk
real(rk), parameter :: MAX_ITERATIONS = 100
character(len=*), parameter :: MODULE_NAME = 'example_mod'
! Module variables (lowercase with underscores)
logical :: module_initialized = .false.
integer :: debug_level = 0
! Derived types
type :: example_type
real(rk) :: temperature !< Temperature (K)
real(rk) :: pressure !< Pressure (Pa)
real(rk), allocatable :: profile(:) !< Vertical profile
end type example_type
contains
! Module procedures...
end module canopy_example_mod
!! \}
Naming Conventions
Variables
! Variables: lowercase with underscores, descriptive names
real(rk) :: air_temperature_k ! Temperature in Kelvin
real(rk) :: wind_speed_ms ! Wind speed in m/s
real(rk) :: leaf_area_index ! Leaf area index (m²/m²)
integer :: num_canopy_levels ! Number of vertical levels
logical :: use_advanced_physics ! Physics option flag
! Arrays: plural or indicate dimensionality
real(rk) :: height_levels(nlev) ! Height coordinates
real(rk) :: temperature_profile(:) ! Temperature vs height
real(rk) :: emission_rates(:,:) ! 2D emission array
! Loop indices: simple, single letters for short loops
integer :: i, j, k, n
! Descriptive names for complex loops
integer :: level_idx, species_idx, time_step
Constants
! Constants: uppercase with underscores
real(rk), parameter :: PI = 3.14159265358979_rk
real(rk), parameter :: GRAVITATIONAL_ACCEL = 9.80665_rk
real(rk), parameter :: GAS_CONSTANT_DRY_AIR = 287.0_rk ! J/kg/K
real(rk), parameter :: STEFAN_BOLTZMANN = 5.67e-8_rk ! W/m²/K⁴
! Physical constants with units in comments
real(rk), parameter :: AVOGADRO_NUMBER = 6.022e23_rk ! molecules/mol
real(rk), parameter :: BOLTZMANN_CONST = 1.381e-23_rk ! J/K
! Model-specific constants
real(rk), parameter :: DEFAULT_CANOPY_HEIGHT = 20.0_rk ! m
integer, parameter :: MAX_CANOPY_LEVELS = 100
integer, parameter :: MAX_SPECIES = 50
Subroutines and Functions
! Subroutines: verb phrases, lowercase with underscores
subroutine calc_wind_profile()
subroutine read_input_data()
subroutine write_output_files()
subroutine initialize_physics_modules()
! Functions: noun phrases or questions, return type obvious
real(rk) function interpolate_linear(x, y, xi)
logical function file_exists(filename)
integer function count_valid_data_points(data_array)
real(rk) function saturation_vapor_pressure(temperature)
Code Formatting
Indentation
! Use 2 spaces for indentation (no tabs)
module example_mod
implicit none
contains
subroutine example_routine()
integer :: i
real(rk) :: value
do i = 1, 10
if (i > 5) then
value = real(i, rk) * 2.0_rk
else
value = real(i, rk)
end if
end do
end subroutine example_routine
end module example_mod
Line Length
! Maximum 80 characters per line
! Break long lines using continuation characters
subroutine long_parameter_list(temperature, pressure, humidity, &
wind_speed, wind_direction, &
solar_radiation, output_array)
! Break long expressions logically
total_emission = isoprene_emission + monoterpene_emission + &
sesquiterpene_emission + other_voc_emission
! Align continuation lines
if ((temperature > freezing_point) .and. &
(pressure > minimum_pressure) .and. &
(humidity >= 0.0_rk) .and. &
(humidity <= 100.0_rk)) then
! Process valid meteorological data
end if
Spacing
! Spaces around operators
result = a + b * c / d
logical_result = (x > 0.0_rk) .and. (y < 100.0_rk)
! Spaces after commas
call subroutine(arg1, arg2, arg3)
array = [1.0_rk, 2.0_rk, 3.0_rk, 4.0_rk]
! No space before/after parentheses in function calls
value = sqrt(temperature * pressure)
! Space after keywords
if (condition) then
do i = 1, n
while (convergence_check)
Documentation Standards
Module Documentation
!> \brief Calculate biogenic emissions from vegetation
!!
!! This module implements the MEGAN (Model of Emissions of Gases
!! and Aerosols from Nature) algorithms for calculating biogenic
!! volatile organic compound emissions from vegetation.
!!
!! The module includes:
!! - Temperature and light-dependent emission factors
!! - Species-specific emission algorithms
!! - Canopy environment corrections
!! - Integration with canopy physics
!!
!! \author P.C. Campbell, NOAA ARL
!! \date 2024-01-15
!! \version 2.1
!!
!! \see Guenther et al. (2012) for algorithm details
!! \see https://bai.ess.uci.edu/megan for model documentation
!!
module canopy_bioemi_mod
Subroutine Documentation
!> \brief Calculate isoprene emission rates for canopy layers
!!
!! Computes isoprene emission rates using the MEGAN algorithm
!! with temperature and PAR (photosynthetically active radiation)
!! dependencies. Emissions are calculated for each canopy layer
!! based on leaf area density and environmental conditions.
!!
!! \param[in] temperature Layer temperature array (K)
!! \param[in] par_radiation PAR radiation array (μmol/m²/s)
!! \param[in] leaf_area_density LAD profile (m²/m³)
!! \param[in] emission_factor Base emission factor (μg/g/h)
!! \param[out] emission_rate Calculated emission rates (μg/m³/s)
!! \param[out] status Return status (0=success, >0=error)
!!
!! \pre temperature values must be > 0 K
!! \pre par_radiation values must be >= 0
!! \pre leaf_area_density values must be >= 0
!! \post emission_rate contains valid emission rates
!!
!! \note Emission factor should be at standard conditions
!! (30°C, 1000 μmol/m²/s PAR)
!!
!! \warning Large emission factors (>100 μg/g/h) should be verified
!!
!! \author P.C. Campbell
!! \date 2024-01-15
!! \version 1.0
!!
!! \see calc_temperature_activity() for temperature dependence
!! \see calc_light_activity() for PAR dependence
!!
subroutine calc_isoprene_emission(temperature, par_radiation, &
leaf_area_density, emission_factor, &
emission_rate, status)
Variable Documentation
! In-line comments for variable declarations
real(rk) :: temperature_k !< Air temperature (K)
real(rk) :: relative_humidity !< Relative humidity (%)
real(rk) :: wind_speed_ms !< Wind speed (m/s)
integer :: num_iterations !< Number of solver iterations
! Block comments for complex variable groups
!> \defgroup met_inputs Meteorological Input Variables
!! Input meteorological variables required for canopy calculations
!! \{
real(rk) :: air_pressure_pa !< Atmospheric pressure (Pa)
real(rk) :: solar_zenith_angle !< Solar zenith angle (radians)
real(rk) :: cloud_fraction !< Cloud fraction (0-1)
!! \}
Error Handling
Error Checking Patterns
! Input validation with descriptive messages
subroutine validate_meteorological_inputs(temp, press, humid, status)
real(rk), intent(in) :: temp, press, humid
integer, intent(out) :: status
status = 0 ! Success
! Temperature checks
if (temp <= 0.0_rk) then
write(error_unit, '(a,f8.2)') 'Invalid temperature: ', temp
status = 1
return
end if
if (temp > 350.0_rk) then
write(error_unit, '(a,f8.2)') 'Unrealistic temperature: ', temp
status = 2
return
end if
! Pressure checks
if (press < 50000.0_rk .or. press > 110000.0_rk) then
write(error_unit, '(a,f10.1)') 'Invalid pressure: ', press
status = 3
return
end if
! Humidity checks
if (humid < 0.0_rk .or. humid > 100.0_rk) then
write(error_unit, '(a,f8.2)') 'Invalid humidity: ', humid
status = 4
return
end if
end subroutine validate_meteorological_inputs
File I/O Error Handling
! Safe file operations with error checking
subroutine safe_file_read(filename, data_array, status)
character(len=*), intent(in) :: filename
real(rk), intent(out) :: data_array(:)
integer, intent(out) :: status
integer :: file_unit, iostat
logical :: file_exists
! Check file existence
inquire(file=filename, exist=file_exists)
if (.not. file_exists) then
write(error_unit, '(a)') 'File not found: ' // trim(filename)
status = -1
return
end if
! Open file safely
open(newunit=file_unit, file=filename, status='old', &
action='read', iostat=iostat)
if (iostat /= 0) then
write(error_unit, '(a,i0)') 'Error opening file, iostat: ', iostat
status = -2
return
end if
! Read data with error checking
read(file_unit, *, iostat=iostat) data_array
if (iostat /= 0) then
write(error_unit, '(a,i0)') 'Error reading data, iostat: ', iostat
close(file_unit)
status = -3
return
end if
close(file_unit)
status = 0 ! Success
end subroutine safe_file_read
Performance Guidelines
Efficient Array Operations
! Prefer array operations over explicit loops
! Good: vectorized operation
temperature_c = temperature_k - 273.15_rk
! Less efficient: explicit loop
do i = 1, size(temperature_k)
temperature_c(i) = temperature_k(i) - 273.15_rk
end do
! Use intrinsic functions when possible
total_emission = sum(emission_rates)
max_temperature = maxval(temperature_profile)
average_wind = sum(wind_speeds) / real(size(wind_speeds), rk)
Memory Management
! Allocate arrays only when needed
subroutine process_canopy_data(nlevels)
integer, intent(in) :: nlevels
real(rk), allocatable :: work_array(:)
integer :: alloc_stat
! Allocate with error checking
allocate(work_array(nlevels), stat=alloc_stat)
if (alloc_stat /= 0) then
call abort_with_message('Memory allocation failed')
end if
! Use array...
! Always deallocate
deallocate(work_array)
end subroutine process_canopy_data
Avoiding Common Performance Issues
! Avoid repeated function calls in loops
! Bad:
do i = 1, n
result(i) = expensive_function(data(i))
end do
! Good: cache expensive calculations
expensive_value = expensive_function(data(1))
do i = 1, n
if (data(i) /= data(1)) then
expensive_value = expensive_function(data(i))
end if
result(i) = expensive_value
end do
! Use appropriate variable types
! Use single precision for large arrays if appropriate
real(real32) :: large_array(1000000) ! If double precision not needed
! Avoid unnecessary type conversions
integer :: count
real(rk) :: average
average = sum(values) / real(count, rk) ! Explicit conversion
Python Coding Standards
PEP 8 Compliance
Naming Conventions
# Variables and functions: lowercase with underscores
air_temperature = 25.0
wind_speed_ms = 5.2
def calculate_emission_rate():
pass
def read_meteorological_data():
pass
# Constants: uppercase with underscores
GRAVITY = 9.81
MOLECULAR_WEIGHT_AIR = 28.97
# Classes: CapWords (PascalCase)
class CanopyModel:
def __init__(self):
pass
class EmissionCalculator:
def __init__(self):
pass
Function Documentation
def calc_biogenic_emissions(temperature, par, leaf_area, emission_factor):
"""Calculate biogenic emission rates using MEGAN algorithms.
Computes volatile organic compound emission rates from vegetation
based on temperature, photosynthetically active radiation (PAR),
and leaf area density.
Args:
temperature (np.ndarray): Air temperature array (K)
par (np.ndarray): PAR radiation array (μmol/m²/s)
leaf_area (np.ndarray): Leaf area density (m²/m³)
emission_factor (float): Base emission factor (μg/g/h)
Returns:
np.ndarray: Emission rates (μg/m³/s)
Raises:
ValueError: If input arrays have different shapes
ValueError: If temperature values are non-positive
Example:
>>> temp = np.array([298.15, 301.15, 295.15])
>>> par = np.array([800.0, 1200.0, 400.0])
>>> lad = np.array([0.5, 0.8, 0.3])
>>> emissions = calc_biogenic_emissions(temp, par, lad, 24.0)
"""
# Validate inputs
if temperature.shape != par.shape != leaf_area.shape:
raise ValueError("Input arrays must have the same shape")
if np.any(temperature <= 0):
raise ValueError("Temperature values must be positive")
# Calculate emission activity factors
gamma_t = temperature_activity_factor(temperature)
gamma_p = par_activity_factor(par)
# Compute emission rates
emission_rates = emission_factor * gamma_t * gamma_p * leaf_area
return emission_rates
Code Organization
Module Structure
"""
Canopy model utilities for data processing and analysis.
This module provides utility functions for processing meteorological
data, calculating derived variables, and interfacing with the
Fortran canopy model.
Author: Your Name
Date: 2024-01-15
Version: 1.0
"""
import numpy as np
import pandas as pd
import xarray as xr
from pathlib import Path
from typing import Union, Tuple, Optional, Dict, Any
# Module constants
DEFAULT_CANOPY_HEIGHT = 20.0 # meters
STANDARD_TEMPERATURE = 30.0 # Celsius
STANDARD_PAR = 1000.0 # μmol/m²/s
# Type aliases for clarity
Temperature = np.ndarray
Pressure = np.ndarray
EmissionRate = np.ndarray
class CanopyDataProcessor:
"""Process and validate canopy model input/output data."""
def __init__(self, config_file: Optional[Path] = None):
"""Initialize data processor with optional configuration."""
self.config = self._load_config(config_file)
self.logger = self._setup_logging()
def process_meteorological_data(self,
input_file: Path,
output_file: Path) -> Dict[str, Any]:
"""Process meteorological input data for canopy model."""
# Implementation...
pass
Build System Standards
Makefile Organization
# Canopy-App Makefile
# Author: P.C. Campbell
# Date: 2024-01-15
# Compiler settings
FC = gfortran
FFLAGS = -O2 -g -Wall -Wextra -fcheck=bounds
LDFLAGS =
LIBS = -lnetcdff -lnetcdf
# Directories
SRCDIR = src
OBJDIR = obj
BINDIR = bin
# Source files (in dependency order)
SOURCES = canopy_const_mod.F90 \
canopy_utils_mod.F90 \
canopy_coord_mod.F90 \
canopy_files_mod.F90 \
canopy_bioemi_mod.F90 \
canopy_canmet_mod.F90 \
canopy_rad_mod.F90 \
canopy_drydep_mod.F90 \
canopy_phot_mod.F90 \
canopy_ncf_io_mod.F90 \
canopy_txt_io_mod.F90 \
canopy_init.F90 \
canopy_alloc.F90 \
canopy_dealloc.F90 \
canopy_calcs.F90 \
canopy_app.F90
# Object files
OBJECTS = $(SOURCES:%.F90=$(OBJDIR)/%.o)
# Target executable
TARGET = $(BINDIR)/canopy_app
# Default target
all: $(TARGET)
# Build executable
$(TARGET): $(OBJECTS) | $(BINDIR)
$(FC) $(LDFLAGS) -o $@ $(OBJECTS) $(LIBS)
# Compile source files
$(OBJDIR)/%.o: $(SRCDIR)/%.F90 | $(OBJDIR)
$(FC) $(FFLAGS) -c $< -o $@
# Create directories
$(OBJDIR):
mkdir -p $(OBJDIR)
$(BINDIR):
mkdir -p $(BINDIR)
# Clean build files
clean:
rm -rf $(OBJDIR) $(BINDIR)
# Development targets
debug: FFLAGS += -g -O0 -fbacktrace -ffpe-trap=invalid,zero,overflow
debug: $(TARGET)
profile: FFLAGS += -pg
profile: $(TARGET)
# Testing targets
test: $(TARGET)
cd tests && ./run_tests.sh
# Installation
install: $(TARGET)
cp $(TARGET) /usr/local/bin/
.PHONY: all clean debug profile test install
Documentation Standards
README Structure
# Canopy-App
Brief description of the project and its purpose.
## Quick Start
```bash
# Clone and build
git clone https://github.com/canopy-app/canopy-app.git
cd canopy-app
make
# Run example
cd input
../bin/canopy_app
Installation
Detailed installation instructions...
Usage
Basic usage examples...
Documentation
Link to full documentation...
Contributing
How to contribute to the project...
License
License information...
### Changelog Format
```markdown
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/),
and this project adheres to [Semantic Versioning](https://semver.org/).
## [Unreleased]
### Added
- New biogenic emission species
- Enhanced radiation calculations
### Changed
- Improved numerical stability in wind profile calculations
- Updated documentation format
### Fixed
- Memory leak in NetCDF I/O routines
- Boundary condition handling in dry deposition
## [1.2.0] - 2024-01-15
### Added
- Complete Doxygen documentation
- MkDocs documentation site
- Python analysis utilities
### Changed
- Refactored emission calculation algorithms
- Improved error handling throughout codebase
### Fixed
- Division by zero in stability calculations
- Incorrect units in photolysis rate output
Best Practices Summary
Code Quality Checklist
- [ ] Meaningful names for all variables and functions
- [ ] Consistent indentation (2 spaces)
- [ ] Comprehensive documentation for all public interfaces
- [ ] Error checking for all I/O operations
- [ ] Input validation for all subroutines
- [ ] Memory management (proper allocation/deallocation)
- [ ] Performance considerations (avoid unnecessary operations)
- [ ] Modular design (clear separation of concerns)
Review Guidelines
- Readability: Can another developer understand the code?
- Correctness: Does the code do what it's supposed to do?
- Performance: Are there any obvious inefficiencies?
- Maintainability: Will this code be easy to modify later?
- Documentation: Is the code adequately documented?
Next Steps
- Testing Guide - Comprehensive testing procedures
- Contributing Guide - Development workflow
- Architecture Overview - System design
- API Reference - Code documentation