Write linearization (.lin) output at full precision, independent of OutFmt#3393
Conversation
…less of OutFmt The .lin Jacobian matrices (A/B/C/D, dUdu/dUdy/dXdy/J and the module-level blocks), the operating-point column, and the header values (incl. Rotor Speed, consumed by MBC3 tools) were formatted with the deck's tabular OutFmt, whose default "ES10.3E2" carries ~4 significant digits. Assembled Jacobians of coupled-section BeamDyn blades span ~15 orders of magnitude, so the write itself corrupted downstream eigenanalysis: physically impossible results (spurious positive real eigenvalues, negative damping ratios, merged or missing tower modes) for parked, passive structures on IEA-15/3.4/10/22 class models, while near-diagonal-section models (NREL 5-MW) escaped. Fix: a fixed module-level format LinFmt='ES24.15E3' (full double precision) for all numeric .lin output in FAST_ModGlue; tab-stop width updated to match. Tabular time-series output keeps honoring OutFmt, unchanged. Verification: IEA-15-240-RWT structure-only standstill case with default OutFmt, patched dev build: A-matrix eigenvalues go from 1 spurious positive real eigenvalue + merged tower mode (0.2163 Hz) to 0 unstable and a correctly split tower pair (0.2369/0.2386 Hz vs ElastoDyn 0.2350/0.2366); identical to the ES18.9E3-deck control on the unpatched binary. Rounding a clean A to 4 significant digits in numpy reproduces the corrupted eigenvalues exactly, confirming the mechanism. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR improves the fidelity of OpenFAST linearization (.lin) outputs by switching all numeric .lin formatting to a fixed, full double-precision scientific format, independent of the user’s tabular OutFmt, to avoid precision-loss that can corrupt downstream eigenanalysis results.
Changes:
- Introduces a fixed
.linnumeric format (LinFmt = ES24.15E3) and applies it to.linheader scalars, operating-point tables, and all written Jacobian/state matrices. - Updates the operating-point table tab-stop calculation to match the new fixed field width.
- Modifies the Apache 2.0
LICENSEappendix boilerplate line.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| modules/openfast-library/src/FAST_ModGlue.f90 | Writes all .lin numeric output using a fixed full-precision format to prevent loss of significant digits in linearization artifacts. |
| LICENSE | Updates the appendix boilerplate copyright placeholder line. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
andrew-platt
left a comment
There was a problem hiding this comment.
Looks good, other than the license file change. I suspect we should leave that as it was.
andrew-platt
left a comment
There was a problem hiding this comment.
Revert the LICENSE.
Per Gemini:
In a root-level LICENSE file for the Apache 2.0 license, the text should be kept exactly as is, including the template brackets. [1]
The text located at the very bottom of the OpenFAST LICENSE file is the official Appendix. It serves as an instructional boilerplate meant to show developers how to apply the license to their individual source code files. [2, 3, 4, 5, 6]
Why You Keep the Brackets in the Root LICENSE
- Standardization: Keeping the root text unaltered allows automated open-source compliance scanners (like those used by GitHub) to quickly detect, parse, and verify that your project uses the standard, unmodified [Apache License 2.0](https://github.com/apache/.github/blob/main/LICENSE). [1, 7, 8]
- It is an Appendix: The bracketed line is part of a section titled "APPENDIX: How to apply the Apache License to your work". It is a template tutorial, not the copyright declaration of the root repository itself. [4, 9]
Where You Actually Fill in the Copyright Details
To properly declare your project's copyright ownership under Apache 2.0, you apply those details in two specific locations:
- Individual Source Code Headers: You copy that exact appendix block, fill in the year and your name, and paste it as a comment at the top of every source file you create.
Copyright 2026 National Renewable Energy Laboratory
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License...
[1, 2, 10, 11, 12]
2. A Separate NOTICE File: If you want a centralized copyright statement at the root level, create a file named NOTICE alongside your LICENSE file. Place your filled-out copyright lines inside that file. [4, 13]
If you are currently setting up a new repository, I can help you format a standard NOTICE file or write a GitHub Actions workflow to automatically check for license headers across your source code. Let me know what you need next!
[1] [https://softwareengineering.stackexchange.com](https://softwareengineering.stackexchange.com/questions/307702/looking-for-an-example-of-using-code-with-apache-license-version-2-0)
[2] [https://github.com](CycloneDX/cyclonedx-node-module#148)
[3] [https://github.com](https://github.com/OpenFAST/openfast)
[4] [https://vladimirgorej.com](https://vladimirgorej.com/blog/how-to-apply-apache2-license-to-your-open-source-software-project/)
[5] [https://choosealicense.com](https://choosealicense.com/licenses/apache-2.0/)
[6] [https://www.linkedin.com](https://www.linkedin.com/pulse/how-apply-apache-20-license-your-open-source-software-vladim%C3%ADr-gorej)
[7] [https://docs.github.com](https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions/adding-a-license-to-a-repository)
[8] [https://github.com](https://github.com/apache/.github/blob/main/LICENSE)
[9] [https://www.reddit.com](https://www.reddit.com/r/opensource/comments/1utl4pn/unfilled_license_copyright_holders/)
[10] [https://www.reddit.com](https://www.reddit.com/r/opensource/comments/y1behw/is_it_necessary_to_mention_license_details_in/)
[11] [https://opensource.stackexchange.com](https://opensource.stackexchange.com/questions/9774/how-to-fill-the-name-of-copyright-owner-in-the-license-if-there-are-multiple)
[12] [https://softwareengineering.stackexchange.com](https://softwareengineering.stackexchange.com/questions/270091/is-a-license-header-in-source-files-required-when-applying-the-apache-software-l)
[13] [https://www.linkedin.com](https://www.linkedin.com/pulse/how-apply-apache-20-license-your-open-source-software-vladim%C3%ADr-gorej)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Feature or improvement description
Write linearization (
.lin) output at full precision, independent of theOutFmtset in the OpenFAST input file.The glue code currently formats everything in the
.linfiles — the Jacobian matrices(A/B/C/D,
dUdu/dUdy, module blocks), the operating-point column, and the header scalars(including Rotor Speed, which MBC3 postprocessors consume) — with the OpenFAST input's tabular
OutFmt, whose documented default"ES10.3E2"carries only ~4 significant digits.For models whose state matrices span a wide dynamic range (e.g. BeamDyn blades with fully
populated sectional mass/stiffness matrices, where the assembled A spans ~15 orders of
magnitude), 4 digits is not enough to round-trip the matrix through disk: every downstream
eigenanalysis (openfast_toolbox / pyFAST, MBC3 / Campbell workflows, ACDC) then reports
physically impossible results for a parked, passive structure — spurious positive
eigenvalues, negative damping ratios, and merged or missing modes. The corruption is
invisible in the
.linfile itself and easily misread as a physics or solver defect.This PR introduces a fixed full-double-precision format for all numeric
.linoutput:Tabular time-series output continues to honor the user's
OutFmt, unchanged. Workaround onreleased versions: set
OutFmt "ES18.9E3"in any OpenFAST input file used for linearization.Related issue, if one exists
Fixes #3395. Discovered alongside the independent BeamDyn rotating-frame defect #3394 (fix: #3396); the two PRs are independent.
Impacted areas of the software
modules/openfast-library/src/FAST_ModGlue.f90only: all matrix writes, theoperating-point column formats, and the header-scalar format now use
LinFmt; theoperating-point tab stop is sized from the new width. No change to what is computed —
only to how it is written.
.linfiles grow ~2.4× per numeric field. They are analysis artifacts, not time series,so the size cost is negligible against the correctness gain.
ES24.15E3is standard Fortran scientific notation; verifiedwith openfast_toolbox
ReadFASTLinear)..linbaselines will differ textually (wider fields, moredigits); values agree with the old files to the old files' precision, so baselines need
a regeneration pass, not tolerance changes.
Generative AI usage
Developed with Anthropic Claude (Claude Code) under human direction using OAK's workflows.
Co-authored-by: Anthropic Claude claude@anthropic.com
Test results, if applicable
uses the default
OutFmt: before the patch, eigenanalysis of the written A matrixyields a spurious positive real eigenvalue and a merged tower pair; after the patch,
the same input yields 0 unstable eigenvalues and the correctly split tower pair,
matching a control run whose input sets a wide
OutFmtexplicitly.