SWATGenXSWATGenX
Watershed ExplorerExample modelsCloud calibrationDocsAccess
SWATGenXDocs

PFAS fate and transport

Applies to SWATGenX engine extensions · Updated August 2026

pfas.dat activation, per-HRU soil IC and sorption in pfas_hru.ini, in-stream pfas_cha.dat and pfas_calib.dat, and the output and mass-balance files.

Surface-water PFAS fate & transport turns on when the SWAT+ working directory (Scenarios/Default/TxtInOut) contains pfas.dat. Without that file the engine skips the PFAS module entirely. This chapter covers the file schemas and the edit workflow.

Ordering a build with PFAS observation extraction only places ambient monitoring under <model>/pfas_data/ (for comparison). It does not write pfas.dat / pfas_hru.ini. Those engine inputs are hand-edited, supplied in research/example PFAS packages, or produced offline from soils.sol + hru-data.hru (see below). You need a PFAS-enabled SWAT+ binary from the public fork.

Where the files go

Scenarios/Default/TxtInOut/          # run directory for swatplus
├── pfas.dat                         # REQUIRED to enable PFAS (compounds + chemistry)
├── pfas_hru.ini                     # REQUIRED for soil IC + sorption (HRU order)
├── pfas_calib.dat                   # optional: soil_scale koc_scale
├── pfas_cha.dat                     # optional: in-stream koc/settle/resus/bury + IC
├── pfas_leach.map                   # research daily MF6 coupling only (with mf6.con)
├── soils.sol                        # layer thickness, bd, OC, texture (generator input)
├── hru-data.hru                     # soil + lu_mgt names (generator input)
└── … standard SWAT+ inputs …

<model_root>/                        # SWAT_MODEL_Web_Application (sibling of TxtInOut tree)
└── pfas_data/                       # OBSERVATIONS only (optional order / add-on snap)
    ├── pfas_stations_assignment.csv
    ├── stations.shp
    ├── <channel>_<site_id>.csv
    └── README.md

Produce the soil inputs

pfas_hru.ini is required whenever pfas.dat is present. You can hand-edit it, start from a research/example package that already includes it, or derive a background PFOS initial condition from the soils and HRU tables already in TxtInOut (soils.sol + hru-data.hru). A typical offline derivation writes PFOS only (num_pconta=1, enr=1.0), maps each layer’s OC/clay/sand to the nearest of six reference soils S1–S6 for (kf, n, d50), and seeds sol_pfas from land-use background µg/kg × bulk density × thickness × 1e4 with surface-weighted depth decay 0.5^k. Field-level detail is under pfas_hru.ini below.

Loadings / sources — what you edit

  • Initial soil mass: edit sol_pfas columns in pfas_hru.ini (µg ha⁻¹), or raise land-use / site concentrations and regenerate the background IC from soils.sol + hru-data.hru.
  • Chemistry / AWI sorption: edit mw, sol, kl, lm, percop in pfas.dat.
  • Solid sorption: edit kf / nf per layer in pfas_hru.ini (or change soils so the S1–S6 match shifts).
  • In-stream partition / bed exchange: pfas_cha.dat (or rely on defaults).
  • Global scale during cal: pfas_calib.dat.
  • Point / channel initial concentrations: trailing water_ppt / benthic_ppt on pfas_cha.dat rows.

Observation package (not engine IC)

  • Optional order / dashboard add-on places monitoring under pfas_data/ at the model root. Use those CSVs to compare against channel_pfas_day.txt — they are not read as model IC.
  • Public PFAS inventory APIs and MCP tools serve monitoring data; they do not write pfas.dat / pfas_hru.ini into TxtInOut.

Outputs to check after a run

  • pfas_hru_aa.txtper-HRU budget (kg ha⁻¹): init/final, surq, latq, perc (leach toward GW), sed, resid.
  • channel_pfas_day.txtdaily reach mass terms + conc_ngL.
  • pfas_balance.out / pfas_cha_balance.outland-phase and channel mass-balance checks.
  • Coupled research path (mf6.con + pfas_leach.map): mf6/pfas.ucn, mf6/pfas.lst — see the MODFLOW coupling chapter. Ordinary MODFLOW_250m packages do not include these.

Product vs research scope

  • Standard distribution: SWAT+ package download; optional pfas_data observation snap; hand-edited or offline-derived pfas.dat / pfas_hru.ini for engine IC.
  • Requires a PFAS-enabled engine binary locally (see Compile the SWAT+ fork), or a research package that already includes the pfas.* inputs. Standard hosted builds do not auto-emit pfas.dat.
  • Research scope (not the default order): daily BMI PFAS leach maps, MF6 groundwater transport outputs (pfas.ucn), vadose-zone pathways, multi-analyte setups, and joint SW–GW calibration.

pfas.dat — compound database

PFAS compound database. Its presence in TxtInOut enables the PFAS fate & transport module; absent file is a clean no-op.

Two header lines, then one free-format row per compound; a row with id ≤ 0 terminates the list. The compound order defines the compound index used by every other PFAS file.

pfas.dat: PFAS compound database (PFOS example)
 id  name        mw         sol        kl        lm        percop
  1  PFOS    0.50013    680.0      0.137     2500.0     0.20
  0  END     0.0        0.0        0.0       0.0        0.0
FieldDescriptionTypeUnits
idSequential compound index (1…npfas); 0 / negative terminates the listinteger
nameCompound label, up to 16 characterscharacter(16)
mwMolecular weightrealkg mol⁻¹
solMaximum aqueous solubilityrealmg L⁻¹
klLangmuir K_L for air–water interface sorptionrealL nmol⁻¹
lmLangmuir Γ_max at the air–water interfacerealnmol m⁻²
percopFraction of mobile PFAS partitioned to percolation vs runoffrealfraction 0–1
There is no half-life / first-order decay column on this surface — retention is sorption (solid Freundlich + air–water Langmuir) and hydrologic routing.

pfas_hru.ini — soil initial condition

Per-HRU soil initial condition and Freundlich sorption parameters for every compound in pfas.dat. Required when pfas.dat is present.

One block per HRU in engine HRU order (record k → HRU k). The block declares its own layer count (nly in the header); the reader stores layers 1…min(file nly, soil layer count from soils.sol) and derives the working layer count from the soil, not the file.

hru <id> nly <nlayers>
<num_pconta>                         # contaminated-site count (0 coerced to 1)
<d50_L1> … <d50_Lnly>                # median grain diameter per layer (mm)
<pid> <sol_pfas_L1> … <sol_pfas_Ln>  # initial soil PFAS mass per layer
<pid> <kf_L1> … <kf_Ln>              # Freundlich kf per layer
<pid> <nf_L1> … <nf_Ln>              # Freundlich n per layer
<pid> <enr>                          # sediment enrichment ratio
# repeat the four <pid>… lines once per compound in pfas.dat (ip = 1..npfas)
# pid ≤ 0 → no PFAS in this HRU (remaining PFAS rows for that HRU skipped)
FieldDescriptionTypeUnits
hru <id> nly <n>Block header: HRU id and file layer count (nly < 1 coerced to 1; stored layers capped at the soils.sol layer count)integers
num_pcontaContaminated-site count for the HRU, used as a mass divisor (≤ 0 coerced to 1)integer
d50Median grain diameter per layer; sets AWI area A_aw = 6(1−porosity)(1−saturation)/d50real per layermm
pidCompound index into pfas.dat on the sol_pfas line; ≤ 0 marks the HRU PFAS-free and skips its remaining PFAS rowsinteger
sol_pfasInitial soil PFAS mass per layer; stored as pfas_soil_scale × 1e-9 × value (kg ha⁻¹ internally)real per layerµg ha⁻¹ (file)
kfFreundlich sorption coefficient per layer (leading id token on the line is discarded)real per layer(nmol kg⁻¹)/(nM)ⁿ
nfFreundlich exponent per layerreal per layerdimensionless
enrEnrichment ratio for PFAS on eroded sediment; single value broadcast to all layersrealdimensionless
FILE UNIT for sol_pfas is micrograms per hectare (µg ha⁻¹). The reader multiplies by 1×10⁻⁹ to store kg ha⁻¹ internally (byte-faithful to the SWAT2012 readchm.f convention). Entering kg ha⁻¹ in the file understates mass by 10⁹.

How a background IC is typically produced

  • Inputs already in the package: soils.sol (layer thickness, bulk density, OC, texture) and hru-data.hru (soil + land-use names).
  • Background template commonly writes PFOS only: num_pconta=1, enr=1.0.
  • Sorption (kf, n, d50) per layer: nearest of six reference soils S1–S6 by OC/clay/sand.
  • Initial mass: land-use background µg/kg × bulk density × layer thickness × 1e4, with surface-weighted depth decay 0.5^k.

pfas_cha.dat and pfas_calib.dat

Optional in-stream PFAS parameters and initial reach concentrations (pfas_cha.dat) and global calibration multipliers (pfas_calib.dat).

pfas_cha.dat — in-stream parameters

Optional. Absent file → PFOS-like defaults and PFAS-free initial reaches; HRU loads still route through the channel phase. Two header lines, then one row per compound; id ≤ 0 terminates. Compound ids must align with pfas.dat.

FieldDescriptionTypeUnitsDefault
idCompound index (must align with pfas.dat); ≤ 0 ends the list; id > npfas rows are skippedinteger
nameCompound labelcharacter(16)
kocWater–sediment partition coefficient (multiplied by pfas_koc_scale after read)realm³ g⁻¹1e-5
settleSettling velocityrealm d⁻¹1.0
resusResuspension velocityrealm d⁻¹0.05
buryBurial velocityrealm d⁻¹0.001
act_depActive benthic bed depth; values ≤ 1e-6 are ignored (prior value kept)realm0.1
water_pptOptional initial water-column concentration; init pool kg = water_ppt × 1e-9 × reach volumereal (optional)ng L⁻¹0
benthic_pptOptional initial bed concentration; init pool kg = benthic_ppt × 1e-12 × bed massreal (optional)ng g⁻¹0

pfas_calib.dat — calibration multipliers

Optional single line: soil_scale koc_scale (e.g. 0.11 1.0). Missing file → both 1.0.

FieldDescriptionTypeUnitsDefault
soil_scaleScales the initial soil PFAS pool from pfas_hru.inirealdimensionless1.0
koc_scaleScales in-stream sorption (pfas_koc_scale applied to koc)realdimensionless1.0

Output files

The four PFAS output surfaces the engine writes when the PFAS module is active: per-HRU budget, daily channel series, and two mass-balance checks. Written by pfas_output.f90 and cha_pfas_output.f90.

pfas_hru_aa.txt — per-HRU budget (once per run)

Written once at the end of the simulation by pfas_output. Only HRU × compound rows with a nonzero initial pool are emitted.

FieldDescriptionTypeUnits
hruHRU indexinteger
pfasCompound index into pfas.datinteger
init_kghaInitial soil pool (sum of layers at read time)realkg ha⁻¹
final_kghaFinal soil pool (sum of layers)realkg ha⁻¹
surq_kghaRun-cumulative loss to surface runoffrealkg ha⁻¹
latq_kghaRun-cumulative loss to lateral flowrealkg ha⁻¹
perc_kghaRun-cumulative leaching below the profile (toward groundwater)realkg ha⁻¹
sed_kghaRun-cumulative loss on eroded sedimentrealkg ha⁻¹
resid_kgha(init − final) − losses; mass-balance residualrealkg ha⁻¹

channel_pfas_day.txt — daily reach series

Written per reach per day (on the daily print schedule from print.prt) by cha_pfas_output; a twin CSV is written when csvout = y. Mass terms are daily kg per reach.

FieldDescriptionTypeUnits
jday mon day yrSimulation date fieldsintegers
unitReach (chandeg) indexinteger
gis_idGIS channel idinteger
nameObject namecharacter
pfasCompound name from pfas.datcharacter(16)
tot_in_kgTotal mass inflow to the reachrealkg
sol_out_kg / sor_out_kgDissolved / sorbed mass outflowrealkg
settle_kg / resus_kg / diffuse_kg / bury_kgBed exchange terms (settling, resuspension, diffusion, burial)realkg
water_kg / benthic_kgEnd-of-day water-column and benthic storagerealkg
conc_ngLWater-column concentrationrealng L⁻¹

Mass-balance checks (once per run)

  • pfas_balance.outarea-weighted basin totals per compound: init_kg, final_kg, surq_kg, latq_kg, perc_kg, sed_kg, loss_kg, resid_kg, resid_pct; footer reports the worst per-HRU residual (abs/rel) and its HRU index.
  • pfas_cha_balance.outrun-cumulative channel-phase totals (kg): reach-days active, point-source input, cumulative inflow/outflow, cumulative burial, final channel storage.
Coupled research runs (mf6.con + pfas_leach.map) additionally produce mf6/pfas.ucn (groundwater PFAS concentration) and mf6/pfas.lst (transport mass budget) — see mf6.con and exchange maps in the MODFLOW coupling chapter. Ordinary MODFLOW_250m packages do not include these.