Scanning Mobility Particle Sizer (SMPS)
The SMPS is an instrument used for measuring particle size distributions in the nanometer range.
AeroViz.rawDataReader.script.SMPS.Reader
Bases: AbstractReader
SMPS (Scanning Mobility Particle Sizer) Data Reader
A specialized reader for SMPS data files, which measure particle size distributions in the range of 11.8-593.5 nm.
See docs/api/instruments/particle-sizers/SMPS.md for usage and
docs/guide/reader-reference.md for the
file layout, status codes and QC rules.
Attributes
STATUS_SPECS
class-attribute
instance-attribute
STATUS_SPECS = (('Status Flag', 'Normal Scan', ()), ('Instrument Errors', '', ('Normal Scan',)), ('Detector Status', 'Normal Scan', ()), ('Classifier Errors', '', ('Normal Scan',)), ('Communication Status', '0', ()), ('Neutralizer Status', 'ON', ()))
DETECTOR_FIELDS
class-attribute
instance-attribute
CPC_MAX_CONC
class-attribute
instance-attribute
CPC_MAX_CONC = {'3750': 100000.0, '3772': 10000.0, '3787': 250000.0, '3788': 400000.0, '3022': 10000000.0, '3022A': 10000000.0}
SECONDARY_STATUS_COLUMN
class-attribute
instance-attribute
METADATA_ALIASES
class-attribute
instance-attribute
METADATA_ALIASES = {'Sheath Flow(lpm)': 'Sheath Flow (L/min)', 'Aerosol Flow(lpm)': 'Aerosol Flow (L/min)', 'Bypass Flow(lpm)': 'Bypass Flow (L/min)', 'CPC Inlet Flow(lpm)': 'Detector Inlet Flow (L/min)', 'CPC Sample Flow(lpm)': 'Detector Sample Flow (L/min)', 'Retrace Time(s)': 'Retrace Time (s)', 'Low Voltage': 'Low Voltage (V)', 'High Voltage': 'High Voltage (V)', 'Lower Size(nm)': 'Lower Size (nm)', 'Upper Size(nm)': 'Upper Size (nm)', 'Density(g/cc)': 'Density (g/cm)', 'tf(s)': 'tf (s)', 'D50(nm)': 'D50 (nm)', 'Median(nm)': 'Median (nm)', 'Mean(nm)': 'Mean (nm)', 'Geo. Mean(nm)': 'Geo. Mean (nm)', 'Mode(nm)': 'Mode (nm)', 'Total Concentration (#/cm³)': 'Total Conc. (#/cm)', 'Aerosol Temperature (C)': 'Sample Temp (C)', 'Aerosol Humidity (%)': 'Relative Humidity (%)', 'Aerosol Density (g/cm³)': 'Density (g/cm)', 'Impactor D50 (nm)': 'D50 (nm)', 'Test Name': 'Title', 'Geo. Std. Dev': 'Geo. Std. Dev.', 'DMA Column transit time Tf (s)': 'tf (s)', 'DMA Exit to Optical Detector Td (s)': 'td + 0.5 (s)'}
METADATA_PATTERNS
class-attribute
instance-attribute
METADATA_PATTERNS = ((re.compile('^Total Conc(?:\\.|entration)\\s*\\(#/cm.?\\)?$'), 'Total Conc. (#/cm)'), (re.compile('^Density\\s*\\(g/c[mc].?\\)?$'), 'Density (g/cm)'))
AEROSOL_FLOW_COLUMNS
class-attribute
instance-attribute
Methods:
_inventory_sniff
classmethod
AIM version, export layout and bin grid from the header block only.
Row-oriented exports (one scan per line) are what the parser reads; a column-oriented export (AIM 10.x "transposed", one scan per column) is skipped by it, so flagging the layout here is what makes a folder of unreadable scans visible before a read.
__call__
Return the dN/dlogDp distribution; write S/V + a stats sidecar.
The parent pipeline produces the QC-applied, resampled dN/dlogDp frame
(diameters in nm as columns) and stamps df.attrs. We then write the
number / surface / volume distributions and a QC-aligned statistics file
next to the main output. Pass append_stats=True to also append the
statistics columns to the returned frame (default keeps it a clean PSD
matrix for psd_stats / merge_psd / SizeDist).
_raw_reader
Read and parse raw SMPS data files.
Returns all columns from the raw file. Column selection is deferred to _QC() and _process() stages.
Supported formats: - S80 TXT (AIM old): tab-separated, header at 'Sample #' - S82 TXT (AIM 10.3): tab-separated, header at 'Sample #' - CSV (AIM 11.x): comma-separated, header at 'Scan Number'
_bin_signature
staticmethod
Stable fingerprint of a file's size-bin grid (sorted tuple of diameter columns, rounded to 2 decimals so trivial float jitter doesn't split otherwise-identical scans into different groups).
_partition_compatible_scans
Keep files whose size-bin grid matches the dominant group; drop the rest so the concat sees one consistent schema.
The grouping fingerprint is the file's sorted size-bin tuple. The
"dominant" group is picked by total row count, not file count —
this stops a swarm of tiny files from outvoting one large-but-typical
file. The minority files are not silently discarded: every dropped
file is named in a warning, so the user can re-run them in isolation
(different folder, or with size_range=) if both grids are wanted.
_flow_ratio
Per-scan aerosol/sheath flow ratio β, or None if the flows are absent.
Flows are read under their canonical (AIM 10.3) names — _raw_reader
renames the older and newer AIM spellings. This used to look up
Aerosol Flow(lpm) only, which left the CPC over-range rule inert on
every AIM 10.3 / 11.x export.
_rated_max_conc
classmethod
Look up a counter's rated maximum from a free-text model string.
The field is whatever the operator's software wrote — 3772,
3788 Low Flow, 3022A — so match on the longest known key contained
in it rather than requiring equality. Longest-first matters: 3022A
must not be answered by the 3022 entry when both are listed.
Returns None when nothing matches, which leaves the rule inert.
_cpc_bin_ceiling
Highest credible dN/dlogDp per bin before the CPC is over-range.
The DMA passes one narrow mobility band at a time, so the counter sees
N_counted(Dp) ~ dN/dlogDp(Dp) x dlog10(Dp)_transfer
and the rated maximum applies to that, not to the integrated total.
Inverting gives the ceiling. The transfer function's width in mobility
is beta = q_aerosol / q_sheath; converting to diameter needs the
local slope |dlnZ/dlnDp|, which runs from ~2 in the free-molecular
regime to ~1 in the continuum — so the ceiling is a curve, not a
constant, about 40% lower at 600 nm than at 12 nm.
A higher sheath ratio narrows the transfer function and therefore raises the ceiling: the same counter tolerates a denser aerosol when it only ever sees a thinner slice of it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dp
|
ndarray
|
Bin midpoints (nm). |
required |
beta
|
float or ndarray
|
Aerosol/sheath flow ratio. Scalar, or one value per scan. |
required |
cpc_max
|
float
|
Rated maximum concentration of the counter (#/cm³). |
required |
mean_free_path_nm
|
float
|
Defaults to |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Ceiling per bin — shape |
_truncated_scan_mask
staticmethod
True where a scan has counts but nothing above smallest bin x factor.
First seen at FS from mid-2026-08: 11.8-14 nm bins carrying 1.6e4-1.2e5 dN/dlogDp while every bin from 20 nm up reads exactly 0, total about 60 % of the neighbouring scans. That is a DMA ramp that never ran (or a scan cut short), not a nucleation burst -- a real burst still has particles above 20 nm. It passed every other rule: the total is inside the plausible range and no single bin exceeds the CPC ceiling, so on a heat map it is a red bar pinned to the floor and the GMD trace drops from ~66 nm to ~13 nm for one scan. NULL bins above count as zero: the instrument writes nothing there when the ramp is missing.
_QC
Perform quality control on SMPS particle size distribution data.
QC Rules Applied
- Status Error : Non-empty status flag indicates instrument error
- Insufficient : Less than 50% hourly data completeness (WARNING, not dropped)
- Invalid Number Conc : Total number concentration outside plausible range (10-1e6 #/cm³); total = sum(dN/dlogDp x dlogDp) with per-bin widths
- CPC Over-range : A bin exceeds what the counter can count (per-instrument, from the file header)
- DMA Water Ingress : Bins >400nm with concentration > 4000 dN/dlogDp (indicates water in DMA)
- Truncated Scan : Counts only in the smallest bins, every bin above exactly zero (the voltage ramp never completed; not a nucleation event)
_process
Return the QC'd dN/dlogDp size bins (plus QC_Flag).
The size distribution itself is the canonical SMPS product. Summary
statistics (total / GMD / GSD / mode, mode fractions) and the surface
and volume distributions are derived quantities — compute them on
demand with :func:AeroViz.psd_stats / :func:AeroViz.psd_distributions
rather than baking them into the reader output. This keeps the reader's
return type a plain dN/dlogDp DataFrame (diameters as columns), which is
exactly what psd_stats / merge_psd / SizeDist consume.
Raw format
- File pattern:
*.txtor*.csv.txtfiles (tab-delimited) from AIM 8.x / 9.x / 10.3.csvfiles (comma-delimited) from AIM 11.x
- Native frequency:
6min(config fallback; the grid actually used is detected per file and reported asdf.attrs['raw_freq']) - Encoding: opened with
encoding='utf-8', errors='ignore'— real files contain non-UTF-8 bytes in the sample-path metadata - Header layout: a metadata block (which names the CPC, see below) followed by
the data header row. The header row is found by scanning for a first cell of
Sample #(TXT / AIM 10.3, ~row 25) orScan Number(CSV / AIM 11.x, ~row 52). - Default size grid: 11.8–593.5 nm (110 bins) on AIM 10.3; 11.34–615.27 nm (112 bins) on AIM 11.x. A folder mixing both versions cannot be outer-joined safely — see "Mixed AIM versions" below.
Parse recipe
- Delimiter: tab for
.txt, comma for.csv. - Time from
Date+Start Time, or fromDateTime Sample Start. Date formats are tried in order until one parses:- TXT:
%m/%d/%y %X(mm/dd/yy, older versions),%m/%d/%Y %X(mm/dd/yyyy, newer versions),%Y/%m/%d %X(AIM 10.3+ exports such as2021/1/3) - CSV:
%d/%m/%Y %X(dd/mm/yyyy, EU format)
- TXT:
- Some exports are transposed — if no date column is found the frame is rotated and re-parsed.
- Columns whose name contains a comma are taken as a comma-decimal export;
the comma is rewritten to
.with a warning. - Size bins are the numeric column names, converted to float and sorted
numerically. Expected range 11.8–593.5 nm: a mismatch warns (so older
instrument configs such as 18.8–914 nm still parse for coverage purposes);
the file is only rejected when the caller passed an explicit
size_range=. - The consumed index columns (
Date,Start Time,DateTime Sample Start,Sample #,Scan Number,Diameter Midpoint) are dropped; every other source column is kept through L1.
Export units
Everything after parsing treats the size bins as dN/dlogDp. AIM can export
the same scans in other forms, named in the metadata block by Units and
Weight. Each file is checked and, where the conversion is exact, converted
to dN/dlogDp while it is parsed:
Units |
Weight |
Read as | Handling |
|---|---|---|---|
dw/dlogDp / dW/dlogDp |
Number |
dN/dlogDp | as is |
Concentration (dW) |
Number |
dN per channel | ÷ dlogDp (1 / Channels/Decade from the header) |
dW/dlogDp |
Surface |
dS/dlogDp | ÷ π·Dp² |
dW/dlogDp |
Volume |
dV/dlogDp | ÷ (π/6)·Dp³ |
Concentration (dW) |
Surface / Volume |
dS, dV per channel | both steps |
% Concentration |
any | share of the total | skipped — cannot be undone |
| any | Mass |
dM | skipped — no SMPS mass export was available to verify AIM's mass unit |
A preamble without Units / Weight is taken as dN/dlogDp. Skipped files
are named in the log and in the terminal summary.
The conversions were checked on TCLab data: surface / volume exports against
the number export of the same scans agree to 0.9998–0.9999 (bin by bin,
median), and per-channel dN exports integrate to the instrument's own
Total Conc. within 0.1 %. A file whose surface/volume export was processed
with different corrections (e.g. Multiple Charge Correction off) will not
match its number export — that is the export, not the conversion.
Each file's export unit is recorded; the returned frame says what any converted files were in (see Output).
Mixed AIM versions
The same physical SMPS exports at different size-bin grids and re-labels many metadata columns depending on the host software. The reader treats each scan grid as its own logical instrument — concatenating mismatched grids would create NaN-only columns that downstream completeness checks read as 100 % Insufficient. Two independent problems, two mechanisms:
1. Partition by bin grid (auto-applied): _partition_compatible_scans
buckets files by their sorted size-bin tuple. The bucket with the most rows
wins; the others are dropped before concat with a warning naming every skipped
file. The picked-by-row-count rule means a swarm of tiny minority files cannot
outvote one large export just by file count.
To process the dropped bucket explicitly, either move those files to a
separate folder, or pass size_range= to force _raw_reader to reject
files outside that exact range:
# Process only AIM 11.x scans in a mixed folder:
df = RawDataReader('SMPS', path=mixed_folder, size_range=(11.34, 615.27))
2. Metadata column aliases: older AIM (≤ 10.2, e.g. Sheath Flow(lpm))
and AIM 11.x both name many metadata columns differently from AIM 10.3 for the
same physical quantity. METADATA_ALIASES rewrites both to the AIM 10.3
canonical form on every parsed file, so a folder of any version (or a
partitioned-down folder) produces one schema downstream — and code that reads
a metadata column (such as the CPC over-range rule's flows) works on every
version.
| AIM ≤ 10.2 | → | AIM 10.3 canonical |
|---|---|---|
Sheath Flow(lpm) / Aerosol Flow(lpm) / Bypass Flow(lpm) |
→ | Sheath Flow (L/min) / Aerosol Flow (L/min) / Bypass Flow (L/min) |
CPC Inlet Flow(lpm) / CPC Sample Flow(lpm) |
→ | Detector Inlet Flow (L/min) / Detector Sample Flow (L/min) |
Retrace Time(s) |
→ | Retrace Time (s) |
Low Voltage / High Voltage |
→ | Low Voltage (V) / High Voltage (V) |
Lower Size(nm) / Upper Size(nm) |
→ | Lower Size (nm) / Upper Size (nm) |
Density(g/cc) |
→ | Density (g/cm) |
tf(s) / D50(nm) |
→ | tf (s) / D50 (nm) |
Median(nm) / Mean(nm) / Geo. Mean(nm) / Mode(nm) |
→ | Median (nm) / Mean (nm) / Geo. Mean (nm) / Mode (nm) |
Left under their own names on purpose: old td(s) (AIM 10.3 writes both
td (s) and td + 0.5 (s), so which one it matches is not certain), old
Scan Up Time(s) vs 10.3 Scan Time (s), Impactor Type(cm) and the old
Status Flag (the status rules read it directly).
The ³ in (#/cm³) / (g/cm³) does not survive every export's encoding —
it is dropped, written as ? or a space, and sometimes takes the closing
parenthesis with it. METADATA_PATTERNS maps every spelling seen in the field
(Total Conc.(#/cm), Total Conc.(#/cm?), Total Conc. (#/cm?,
Total Concentration(#/cm), Density (g/cm? …) to Total Conc. (#/cm) /
Density (g/cm).
| AIM 11.x | → | AIM 10.3 canonical |
|---|---|---|
Total Concentration (#/cm³) |
→ | Total Conc. (#/cm) |
Aerosol Temperature (C) |
→ | Sample Temp (C) |
Aerosol Humidity (%) |
→ | Relative Humidity (%) |
Aerosol Density (g/cm³) |
→ | Density (g/cm) |
Impactor D50 (nm) |
→ | D50 (nm) |
Test Name |
→ | Title |
Geo. Std. Dev |
→ | Geo. Std. Dev. |
DMA Column transit time Tf (s) |
→ | tf (s) |
DMA Exit to Optical Detector Td (s) |
→ | td + 0.5 (s) |
AIM 11.x columns that have no AIM 10.3 equivalent — the 4-way error split
(Classifier Errors / Detector Status / Communication Status /
Neutralizer Status), granular DMA timings (THIGH / TLOW / TUP /
TDOWN), Sheath Pressure/Temp/Humidity, etc. — are intentionally kept
under their AIM 11.x names. Collapsing them onto AIM 10.3's coarser
Instrument Errors / Scan Time would lose information. If a file already
carries the canonical AIM 10.3 name, the 10.3 column is kept and the AIM 11.x
duplicate dropped.
CPC metadata
The metadata block above the header names the counter that did the counting
(Detector Model — spelled CPC Model on AIM 10.3 — plus Detector S/N and
Nano Enhancer). These are captured while the header row is located and
reported in df.attrs as cpc_detector_model, cpc_cpc_model,
cpc_detector_s_n, cpc_nano_enhancer (key = field name lower-cased, spaces
and / replaced by _). The CPC's cut-off decides how far the lowest
channels under-report — see
Counting Efficiency. Not corrected
for. The same model string feeds the CPC Over-range rule below.
Measurement parameters
The SMPS provides particle size distribution measurements:
| Parameter | Value | Description |
|---|---|---|
| Size range | 11.8-593.5 nm | Default particle diameter range |
| Output | dN/dlogDp | Number concentration per size bin |
| Unit | #/cm³ | Particle number concentration |
Status & error codes
Mode text (see
status modes),
across six columns; the error masks of whichever columns are present are
OR'd. The same information is reported under different names per
host-software version:
Status Flag(AIM 10.3) ↔Detector Status(AIM 11.x) — positive sentinel, OK whenNormal Scan(e.g.Conditioner Temperature Erroris an error)Instrument Errors(AIM 10.3) ↔Classifier Errors(AIM 11.x) — error tokens, OK when empty (e.g.Low aerosol flow,Neutralizer not active)Communication Status(AIM 11.x only) — OK when0. The column parses as a number and, once the time grid pads in NaN rows, as float, so it is compared as text the way the instrument wrote it (0, not0.0). Before that, every real AIM 11.x scan read asStatus Error(TCLab TP 2024-01/02: 0 h kept → 630 h).Neutralizer Status(AIM 11.x only) — OK whenON
There are no numeric codes: the values are free-text tokens, so no status
register table exists and df.attrs['status_conditions'] is not produced for
SMPS.
Not errors:
- Empty cells (
'','nan', and the Python-None-stringified'None'that appears when a column is missing from some files of a multi-file concat) are never errors. Normal Scanis auto-whitelisted on the error-token columns too, because some sites (e.g. FS) write the positive sentinel there instead of leaving the cell blank.- Tokens listed in the
ignored_status_errorskwarg are exempted, with comma-split semantics:"Low aerosol flow,Neutralizer not active"passes when both tokens are whitelisted.
Until all six columns were listed an AIM 11.x export had neither 10.3
name, so Status Error could never fire on it. The reader now calls
check_status_columns first and logs which dialect's columns it found; an
export with none of the six leaves the rule inert with a warning.
Whitelisting benign status warnings
If an instrument runs in a known-low-aerosol mode and reports "Low aerosol
flow" on every scan, every row trips Status Error. Pass
ignored_status_errors=[...] to RawDataReader to suppress those tokens:
df = RawDataReader(
instrument='SMPS', path='/data/TP_SMPS',
start='2026-01-01', end='2026-05-31',
ignored_status_errors=['Low aerosol flow', 'Neutralizer not active'],
)
Token-level matching: a row passes when EVERY comma-split token is either
the OK sentinel or in the whitelist. "Low aerosol flow,Sheath flow
error" still fails because Sheath flow error is not whitelisted.
QC rules
Before the rules run, the bins are restricted to size_range (default
11.8–593.5 nm) and the total number concentration is computed as
Σ (dN/dlogDp × dlogDp) over the per-bin widths (see "Total concentration"
below).
| Rule | Condition | Severity |
|---|---|---|
| Status Error | any of the six status columns reports a non-whitelisted token (see above) | error |
| Insufficient | an hour holds < 50 % of the scans it could have held at the detected frequency (edge hours scaled by coverage) | advisory (WARNING) — recorded, data kept |
| Invalid Number Conc | total < 10 or > 1e6 #/cm³; a NaN total is also flagged. Override per run with min_total_conc= / max_total_conc= |
error |
| CPC Over-range | any bin's dN/dlogDp exceeds the per-bin ceiling derived from the CPC's rated maximum and the recorded sheath ratio; inert (logged) when the detector model is unknown or the flow columns are absent. Override with cpc_max_conc= (or cpc_model=) |
error |
| DMA Water Ingress | any bin ≥ 400 nm (LARGE_BIN_THRESHOLD) exceeds 4 000 dN/dlogDp (MAX_LARGE_BIN_CONC) — water contamination in the DMA column |
error |
| Truncated Scan | the scan has counts, but every bin at or above 2 × the smallest bin (TRUNCATED_SCAN_FACTOR, 23.6 nm on the standard grid) is exactly zero — the DMA voltage ramp never completed, not a nucleation burst (a real burst still has particles above 20 nm). Seen at FS from 2026-08 at ~5 % of scans; passes every other rule, so it needs its own |
error |
Thresholds live on the reader class: MIN_TOTAL_CONC = 10,
MAX_TOTAL_CONC = 1e6, MAX_LARGE_BIN_CONC = 4000, LARGE_BIN_THRESHOLD = 400.
Total concentration
The total is Σ (dN/dlogDp × dlogDp) using the per-bin widths from
bin_widths, which is also correct on a non-uniform grid. Until 2026-09-09
(v0.4.6) two bugs made the Invalid Number Conc thresholds mean something
other than they said: a natural log was used where the data is dN/dlog₁₀Dp,
inflating every total by ln 10 = 2.3× (so MAX_TOTAL_CONC = 1e7 was really
4.34e6 and MIN_TOTAL_CONC = 2000 was 868), and one column was dropped
before diffing so the mean step averaged n−2 gaps instead of n−1. The fixed
total is verified against the instrument's own Total Conc. column at
0.996–0.999.
The thresholds changed in the same fix:
MIN_TOTAL_CONCwas 2000, which made the reader unusable at any clean site: EBAS background stations sit at 50–300 /cm³ (Zeppelin's median is 95), so 99–100 % of their record was rejected. A minimum is there to catch "instrument off / no flow", which reads ~0, not to encode an urban site's typical loading.MAX_TOTAL_CONCwas 1e7, which caught 19 % of the scans a five-station TCLab audit found implausible; 1e6 catches 30 % and is still above any credible ambient total (roadside peaks reach 2–5e5). Implausible shapes are left to the CPC and water-ingress rules.
CPC Over-range
The DMA passes one narrow mobility band at a time, so the CPC only ever sees
a slice of the distribution — the rated maximum is a per-bin limit, not a
limit on the total (comparing it against the total would be a category
error). The reader takes the detector model from the file header
(Detector Model / CPC Model; cpc_model= overrides), looks up its rated
maximum in CPC_MAX_CONC, and converts it to a dN/dlogDp ceiling per bin
using the sheath ratio β = aerosol flow / Sheath Flow (L/min) from the data
rows (NaN ratios filled with the median) and the file's own
Mean Free Path (m) when present (otherwise 67.3 nm, the reference state
296.15 K / 101.3 kPa). A scan with any bin above its ceiling is flagged.
The aerosol flow is Aerosol Flow (L/min) (AIM ≤ 10.2 and 10.3, after the
aliases above); AIM 11.x has no such column, and on the 3082 classifier the
same flow is the one measured at the impactor, Impactor Flow (L/min).
Before the aliases covered the older spellings the rule looked the flows up
as Aerosol Flow(lpm) only, so it never ran on AIM 10.3 / 11.x exports.
| CPC model | Rated maximum (#/cm³) |
|---|---|
3750 |
1e5 (single-count with live coincidence correction) |
3772 |
1e4 (single-count only, no photometric mode) |
3787 |
2.5e5 |
3788 |
4e5 |
3022 / 3022A |
1e7 (photometric above ~1e4; effectively unconstrained) |
The model string is matched on the longest known key it contains (3788 Low
Flow → 3788; 3022A is not answered by 3022). The ceiling is a curve,
not a constant: the transfer-function width in diameter depends on the local
slope |dlnZ/dlnDp| (≈2 in the free-molecular regime, ≈1 in the continuum),
so it is about 40 % lower at 600 nm than at 12 nm, and a higher sheath ratio
raises it. Unknown models leave the rule inert with a debug log entry; pass
cpc_max_conc= (a number, or a dict such as {'3022': 1e7} to extend the
table) to enable or override it.
Output
The L2 frame holds the dN/dlogDp matrix only (diameters in nm as
columns) plus the QC bookkeeping; all metadata and status columns are dropped
in _process. Statistics are not in the frame.
| Column | Unit | Description |
|---|---|---|
| Size bins (float diameters, nm) | dN/dlogDp | Number concentration for each particle size |
At L3 the size-distribution sidecars are written next to the main output
(all four by default; choose a subset with size_dist_outputs=, e.g.
('number', 'stats') — surface and volume derive from number):
{prefix}_dNdlogDp.csv— number distribution{prefix}_dSdlogDp.csv— surface distribution,π·d²·dN{prefix}_dVdlogDp.csv— volume distribution,π·d³/6·dN{prefix}_stats.csv— QC-aligned summary statistics frompsd_stats(total / GMD / GSD / mode, mode fractions); a failure here is logged and never fails the read
Pass append_stats=True to also append the statistics columns to the
returned frame; the default keeps it a clean PSD matrix that psd_stats /
merge_psd / SizeDist consume directly. The returned frame's df.attrs
additionally carries the cpc_* metadata described above, and:
size_dist_unit— always'dN/dlogDp';SizeDist,psd_statsandmerge_psdcheck it before computing (see Size Distribution);source_units—{export unit: [files]}for the files that had to be converted (e.g.{'dN': ['2024-01-12_100755_SMPS.csv', …]}); absent when every file was already dN/dlogDp.
Files written per read are listed in RawDataReader Reference §1.
Notes
- Different AIM software versions may produce different file formats — see "Mixed AIM versions" above for how the reader isolates and reconciles them
- Size range validation ensures data quality
- DMA water ingress detection: High concentrations in bins ≥400nm indicate water contamination in the DMA column
- Automatic format detection and parsing