Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9ad7abc
feat(openfast_io): add Layer 1 — parsing.py + per-module IO classes
mayankchetan May 25, 2026
0a0e434
feat(openfast_io): add Layer 2 — driver orchestration classes
mayankchetan May 25, 2026
f51be0b
refactor(openfast_io): convert FAST_reader/writer to thin facades
mayankchetan May 25, 2026
a3ef30d
feat(openfast_io): add ancillary subsystems — schema, validation, out…
mayankchetan May 25, 2026
607bb86
test(openfast_io): add comprehensive test suite — 262 tests, 87% cove…
mayankchetan May 25, 2026
c1f268b
docs(openfast_io): add architecture redesign document
mayankchetan May 25, 2026
5731925
docs(openfast_io): add MCP server architecture section to design doc
mayankchetan May 25, 2026
1cfdcb3
fix(outlist): restore OutList read/write — capture+emit helpers + Ela…
mayankchetan Jun 23, 2026
ef2e01c
fix(outlist): wire BeamDyn/InflowWind/ServoDyn read capture via drive…
mayankchetan Jun 23, 2026
9b6d204
fix(outlist): wire read capture for all remaining modules via driver …
mayankchetan Jun 23, 2026
9d2bade
fix(outlist): route AeroDisk/SED/ExtPtfm private _outlist parse into …
mayankchetan Jun 23, 2026
2178266
fix(outlist): read BeamDyn when BDBldFile exists (match baseline) -> …
mayankchetan Jun 23, 2026
5d09b5d
fix(outlist): write-side emit — driver passes outlist to all writes; …
mayankchetan Jun 23, 2026
02aef3b
fix(R2): pass spd_trq to ServoDyn write so VSContrl=3 decks round-trip
mayankchetan Jun 23, 2026
19cd126
fix(beamdyn+extptfm): 4 audit-confirmed bugs
mayankchetan Jun 23, 2026
6b32f24
docs: §10 differential-audit corrections (OutList regression, fmt_fie…
mayankchetan Jun 24, 2026
1ab1824
test(beamdyn): distinct-blade collision regression test in suite (CI,…
mayankchetan Jun 24, 2026
f717971
fix+test: write-side OutList symmetry (AeroDisk/SED/ExtPtfm/MoorDyn) …
mayankchetan Jun 24, 2026
60eb76a
test: consolidate 8 per-module io tests into 2; cut boilerplate; fix …
mayankchetan Jun 24, 2026
7eff569
fix(subdyn): emit SSOutList via emit_outlist (was dropping all member…
mayankchetan Jul 2, 2026
0134605
fix(elastodyn): capture+emit nodal OutList under ElastoDyn_Nodes; add…
mayankchetan Jul 9, 2026
fc09bd0
fix(drivers): preserve OutList in standalone drivers; guard standalon…
mayankchetan Jul 9, 2026
c427871
fix(packaging+validation): declare pyyaml; validate_fst_vt(base_dir=)…
mayankchetan Jul 9, 2026
d91c40b
docs: fold audit corrections into §2–§5 inline; verified counts; §10 …
mayankchetan Jul 9, 2026
8223550
Merge pull request #31 from mayankchetan/fix/outlist-assembly-review
mayankchetan Jul 10, 2026
ac5a9fa
Merge pull request #30 from mayankchetan/fix/outlist-assembly
mayankchetan Jul 10, 2026
34e8897
Merge remote-tracking branch 'origin/main' into openfast_io_arch
mayankchetan Jul 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,231 changes: 1,231 additions & 0 deletions openfast_io/docs/architecture-redesign.md

Large diffs are not rendered by default.

3,924 changes: 114 additions & 3,810 deletions openfast_io/openfast_io/FAST_reader.py

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions openfast_io/openfast_io/FAST_vars_out.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
""" Generated from FAST OutListParameters.xlsx files with openfast_io/openfast_io/create_output_vars.py """

import warnings as _warnings
_warnings.warn(
"FAST_vars_out is deprecated and will be removed in a future version. "
"Use openfast_io.outlist.OutList instead.",
DeprecationWarning,
stacklevel=2,
)


""" AeroDyn """
AeroDyn = {}
Expand Down
3,206 changes: 227 additions & 2,979 deletions openfast_io/openfast_io/FAST_writer.py

Large diffs are not rendered by default.

141 changes: 141 additions & 0 deletions openfast_io/openfast_io/drivers/aerodisk_driver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
"""AeroDisk standalone driver — reads/writes AeroDisk driver input files (.dvr).

Uses right-aligned values and ``@filename.csv`` syntax for timeseries data.
Handles geometry, environment, and case analysis parameters.
"""
from __future__ import annotations

import os
from pathlib import Path
from typing import Any, Dict

from ..io.aerodisk import AeroDiskIO
from ..parsing import bool_read, float_read, int_read, quoted_read


class AeroDiskStandaloneDriver:
"""Reads and writes AeroDisk standalone driver input files (.dvr)."""

def __init__(self) -> None:
self._aerodisk = AeroDiskIO()

# ------------------------------------------------------------------
# READ
# ------------------------------------------------------------------
def read(self, dvr_path: Path) -> dict:
"""Read an AeroDisk driver file and the referenced AeroDisk input.

Returns a dict with keys:
``'AeroDiskDriver'`` — driver-level parameters
``'AeroDisk'`` — from AeroDiskIO
"""
dvr_path = Path(dvr_path)
base_dir = dvr_path.parent
dvr: Dict[str, Any] = {}

with open(dvr_path) as f:
# --- Header ---
f.readline() # title line
dvr['description'] = f.readline().rstrip()

# --- Echo ---
f.readline() # separator
dvr['Echo'] = bool_read(f.readline().split()[0])

# --- Input files ---
f.readline() # separator
dvr['ADskIptFile'] = quoted_read(f.readline().split()[0])
dvr['OutRootName'] = quoted_read(f.readline().split()[0])

# --- Geometry and Environment ---
f.readline() # section header
dvr['AirDens'] = float_read(f.readline().split()[0])
dvr['RotorRad'] = float_read(f.readline().split()[0])
dvr['RotorHeight'] = float_read(f.readline().split()[0])
dvr['ShftTilt'] = float_read(f.readline().split()[0])

# --- Case Analysis ---
f.readline() # section header
dvr['TStart'] = float_read(f.readline().split()[0])
ln = f.readline().split()
dvr['DT'] = ln[0].strip('"') # may be "DEFAULT"
ln = f.readline().split()
dvr['NumTimeSteps'] = ln[0].strip('"') # may be "DEFAULT" or int

# Column headers and units
f.readline() # column headers
f.readline() # column units

# Timeseries: either @filename or inline data
line = f.readline().strip()
if line.startswith('@'):
dvr['TimeseriesFile'] = line[1:] # strip @ prefix
dvr['TimeseriesData'] = []
else:
dvr['TimeseriesFile'] = ''
rows = []
while line and not line.upper().startswith('END'):
parts = line.split()
if parts:
rows.append([float_read(p) for p in parts])
line = f.readline().strip()
dvr['TimeseriesData'] = rows

result: Dict[str, Any] = {'AeroDiskDriver': dvr}

# --- Delegate to AeroDiskIO ---
adsk_file = dvr.get('ADskIptFile', '')
adsk_path = os.path.normpath(os.path.join(str(base_dir), adsk_file))
if adsk_file and os.path.isfile(adsk_path):
try:
adsk_data = self._aerodisk.read(Path(adsk_path), base_dir)
result.update(adsk_data)
except Exception:
pass # module file parse failure — driver data still valid

return result

# ------------------------------------------------------------------
# WRITE
# ------------------------------------------------------------------
def write(self, data: dict, dvr_path: Path) -> None:
"""Write an AeroDisk driver file and the referenced AeroDisk input."""
dvr_path = Path(dvr_path)
base_dir = dvr_path.parent
dvr = data['AeroDiskDriver']

with open(dvr_path, 'w') as f:
f.write('------- AeroDisk driver v1.0 INPUT FILE ----------------------------------------------------------------------\n')
f.write(dvr.get('description', 'Generated by OpenFAST_IO') + '\n')
f.write('--------------------------------------------------------------------------------------------------------------\n')
f.write('{!s:<35} {:<15} {:}\n'.format(dvr['Echo'], 'Echo', '- Echo input data to <RootName>.ech (flag)'))
f.write('--------------------------------------------------------------------------------------------------------------\n')

adsk_name = dvr.get('ADskIptFile', 'adsk_primary.inp')
f.write('{:<35} {:<15} {:}\n'.format('"' + adsk_name + '"', 'ADskIptFile', '- AeroDisk input file'))
f.write('{:<35} {:<15} {:}\n'.format('"' + dvr.get('OutRootName', 'adsk_driver') + '"', 'OutRootName', '- RootName for output files'))
f.write('----- Geometry and Environment -------------------------------------------------------------------------------\n')
f.write('{:<35} {:<15} {:}\n'.format(dvr['AirDens'], 'AirDens', '- Air density (kg/m^3)'))
f.write('{:<35} {:<15} {:}\n'.format(dvr['RotorRad'], 'RotorRad', '- Rotor radius (m)'))
f.write('{:<35} {:<15} {:}\n'.format(dvr['RotorHeight'], 'RotorHeight', '- Rotor height (m)'))
f.write('{:<35} {:<15} {:}\n'.format(dvr['ShftTilt'], 'ShftTilt', '- Shaft tilt (deg)'))
f.write('----- Case Analysis ------------------------------------------------------------------------------------------\n')
f.write('{:<35} {:<15} {:}\n'.format(dvr['TStart'], 'TStart', '- Start time (currently unused)'))
f.write('{:<35} {:<15} {:}\n'.format('"' + str(dvr['DT']) + '"', 'DT', '- Time step (DEFAULT to use smallest DT from input series)'))
f.write('{:<35} {:<15} {:}\n'.format(str(dvr['NumTimeSteps']), 'NumTimeSteps', '- Number of timesteps (DEFAULT to run to end of time)'))
f.write('Time WndSpeed_X WndSpeed_Y WndSpeed_Z RotSpd Pitch Yaw\n')
f.write('(s) (m/s) (m/s) (m/s) (rpm) (deg) (deg)\n')
ts_file = dvr.get('TimeseriesFile', '')
if ts_file:
f.write('@{}\n'.format(ts_file))
else:
for row in dvr.get('TimeseriesData', []):
f.write(' '.join(str(v) for v in row) + '\n')

# --- Delegate to AeroDiskIO ---
if 'AeroDisk' in data:
adsk_path = os.path.normpath(os.path.join(str(base_dir), adsk_name))
try:
self._aerodisk.write({'AeroDisk': data['AeroDisk']}, adsk_path, str(base_dir))
except Exception:
pass
Loading