Source code for pybmodes.elastodyn.writer

# Copyright 2024-2026 Jae Hoon Seo
# Marine Structural Mechanics and Integrity Lab (SMI Lab), Inha University
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Patch ElastoDyn .dat files with computed polynomial coefficients.

Each coefficient occupies a separate line of the form
``<value>   <ParamName(k)>   - comment text``. The writer finds each
line by matching the parameter name token, then replaces the leading
value in-place, preserving indentation and all trailing text.
"""

from __future__ import annotations

import pathlib
import re

from pybmodes.elastodyn.params import BladeElastoDynParams, TowerElastoDynParams


[docs] def patch_dat( path: str | pathlib.Path, params: BladeElastoDynParams | TowerElastoDynParams, ) -> None: """Patch named mode-shape coefficient lines in an ElastoDyn .dat file. Each parameter in *params* is located by searching for its exact name as a whitespace-delimited token. The value on the same line (the token before the name) is replaced with the computed coefficient. All other content (indentation, comment text) is left unchanged. Parameters ---------- path : path to the ElastoDyn .dat file (modified in place). params : BladeElastoDynParams or TowerElastoDynParams. Raises ------ KeyError if a required parameter name is not found in the file. """ path = pathlib.Path(path) # newline='' disables Python's universal-newline translation on read so we # see the file's original \n / \r\n / \r endings; we restore them verbatim # below. Without this, on Windows both the read and write sides would # silently coerce LF into CRLF. (pathlib's read_text/write_text only got # the ``newline`` argument in 3.13, so we use open() for compatibility # with 3.11.) with path.open(encoding='utf-8', newline='') as f: text = f.read() lines = text.splitlines(keepends=True) replacements = params.as_dict() missing = list(replacements.keys()) # Precompile one pattern per parameter name (each depends only on the # name, not the line) so the regex isn't rebuilt for every # line × remaining-parameter combination. patterns = { name: re.compile(r'^(\s*)\S+(\s+' + re.escape(name) + r')(\s.*)?$') for name in missing } for i, line in enumerate(lines): # Capture the original line ending (\n, \r\n, \r, or none for EOF) so # we preserve the file's existing convention — important on Windows # OpenFAST files where mixing \n and \r\n in the same file confuses # downstream tooling. body = line.rstrip('\n\r') ending = line[len(body):] for name in list(missing): # Match: optional whitespace, any value, whitespace, then the exact # parameter name as a whole token (word boundary or end-of-field). m = patterns[name].match(body) if m: value = replacements[name] tail = m.group(3) if m.group(3) is not None else '' new_line = f"{m.group(1)}{value: .7E}{m.group(2)}{tail}{ending}" lines[i] = new_line missing.remove(name) break if missing: raise KeyError( f"The following parameter names were not found in {path}:\n" + "\n".join(f" {n}" for n in missing) ) with path.open('w', encoding='utf-8', newline='') as f: f.write(''.join(lines))