From 23715364c9229521acb242b510cf08fe8dd29878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= <96138805+ArneVoss@users.noreply.github.com> Date: Wed, 28 Jan 2026 13:33:02 +0100 Subject: [PATCH 01/63] Potential fix for code scanning alert no. 5: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/regression-tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/regression-tests.yml b/.github/workflows/regression-tests.yml index 73fae95..91cd97d 100644 --- a/.github/workflows/regression-tests.yml +++ b/.github/workflows/regression-tests.yml @@ -106,6 +106,8 @@ jobs: if-no-files-found: ignore combine-pages: + permissions: + contents: read runs-on: ubuntu-latest # Add a dependency to the build job needs: [Jupyter, Pytest] From 1890dcb08d8e70dd91b62883b3be8f3b3f3a1726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Wed, 28 Jan 2026 13:50:18 +0100 Subject: [PATCH 02/63] Move permissions block to top-level --- .github/workflows/regression-tests.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/regression-tests.yml b/.github/workflows/regression-tests.yml index 91cd97d..34970fa 100644 --- a/.github/workflows/regression-tests.yml +++ b/.github/workflows/regression-tests.yml @@ -1,6 +1,11 @@ # This workflow will run some regression tests. name: Regression Tests +# Restrict permissions at top-level to 'read' as a minimal explicit permission. +# This will apply to all jobs that do not define their own permissions. +permissions: + contents: read + on: push: branches: ['master', 'devel'] @@ -69,8 +74,8 @@ jobs: if-no-files-found: ignore Jupyter: - # Building the Jupyter book is not really a regression test. However, it has to be in this workflow due to the handling of - # the artifacts. + # Building the Jupyter book is not really a regression test, but it makes sure that the notebooks are still working. + # In addition, it has to be in this workflow due to the handling of the artifacts. runs-on: ubuntu-latest strategy: matrix: @@ -106,8 +111,6 @@ jobs: if-no-files-found: ignore combine-pages: - permissions: - contents: read runs-on: ubuntu-latest # Add a dependency to the build job needs: [Jupyter, Pytest] From 28bda98351eb55994ac1a3f12a9ac64432098623 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Wed, 28 Jan 2026 13:52:34 +0100 Subject: [PATCH 03/63] Apply restriction to build.yml, too --- .github/workflows/build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 969be2d..5cffabf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,6 +2,10 @@ name: Build and installation tests +# Restrict permissions at top-level to 'read' as a minimal explicit permission. +permissions: + contents: read + on: push: branches: ['master', 'devel'] From 6ce69564c064f7a4aef19c8c7451ca251f9f9797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= <96138805+ArneVoss@users.noreply.github.com> Date: Wed, 28 Jan 2026 14:09:10 +0100 Subject: [PATCH 04/63] Apply suggested fix to setup.py from Copilot Autofix Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index b957c3c..96e02ce 100644 --- a/setup.py +++ b/setup.py @@ -14,9 +14,9 @@ def my_setup(): setup(name='LoadsKernel', version='2026.01.1', - description="""The Loads Kernel Software allows for the calculation of quasi-steady and dynamic maneuver loads, - unsteady gust loads in the time and frequency domain as well as dynamic landing loads based on a generic landing - gear module.""", + description=("The Loads Kernel Software allows for the calculation of quasi-steady and dynamic maneuver loads, " + "unsteady gust loads in the time and frequency domain as well as dynamic landing loads based on a " + "generic landing gear module."), long_description=open('README.md', encoding='utf8').read(), long_description_content_type='text/markdown', url='https://github.com/DLR-AE/LoadsKernel', From 51dff3e3f7ba16a571046e869e72cabe083de33d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= <96138805+ArneVoss@users.noreply.github.com> Date: Wed, 28 Jan 2026 14:09:10 +0100 Subject: [PATCH 05/63] Apply suggested fix to setup.py from Copilot Autofix Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 96e02ce..ed372df 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ Setup file Install Loads Kernel with core dependencies via: - pip install -e -To use the graphical tools and other features, optional libraries definded as extras are necessary: +To use the graphical tools and other features, optional libraries defined as extras are necessary: - pip install -e [extras] Especially with mpi or the graphical libraries, pip frequently fails. In that case, try to install the packages using a package manager such as conda. From 2e036de8966401e95c0bc6600881d4b844a8de57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Wed, 28 Jan 2026 15:13:40 +0100 Subject: [PATCH 06/63] Aplly the remaining AI suggestions manually --- setup.py | 102 +++++++++++++++++++++++++++---------------------------- 1 file changed, 50 insertions(+), 52 deletions(-) diff --git a/setup.py b/setup.py index ed372df..2803395 100644 --- a/setup.py +++ b/setup.py @@ -10,56 +10,54 @@ from setuptools import setup, find_packages +with open('README.md', encoding='utf8') as f: + readme = f.read() -def my_setup(): - setup(name='LoadsKernel', - version='2026.01.1', - description=("The Loads Kernel Software allows for the calculation of quasi-steady and dynamic maneuver loads, " - "unsteady gust loads in the time and frequency domain as well as dynamic landing loads based on a " - "generic landing gear module."), - long_description=open('README.md', encoding='utf8').read(), - long_description_content_type='text/markdown', - url='https://github.com/DLR-AE/LoadsKernel', - author='Arne Voß', - author_email='arne.voss@dlr.de', - license='BSD 3-Clause License', - packages=find_packages(), - entry_points={'console_scripts': ['loads-kernel=loadskernel.program_flow:command_line_interface', - 'model-viewer=modelviewer.view:command_line_interface', - 'loads-compare=loadscompare.compare:command_line_interface']}, - include_package_data=True, - package_data={'loadskernel': ['graphics/*.*'], - 'loadscompare': ['graphics/*.*'], }, - # Remember to update the requirements also in the conda feedstock (./recipe/meta.yml) when changing them here! - python_requires='>=3.10', - install_requires=['PanelAero', - 'matplotlib', - 'numpy<2.4.0', # Mayavi / VTK does not support numpy >= 2.4.0, wait for release of VTK 9.6 - 'scipy', - 'h5py', - 'tables', - 'pyyaml', - # Pandas 3.0.0 comes with changes that are not yet supported, - # see https://github.com/DLR-AE/LoadsKernel/issues/86 - 'pandas<3.0.0', - ], - extras_require={'extras': ['mpi4py', - 'mayavi', - 'traits', - 'traitsui', - 'jupyter', - 'pyiges', # only available with pip, not with conda - 'pyfmi', - 'pyside6' - ], - 'test': ['pytest', - 'pytest-cov', - 'jupyter-book', - 'flake8', - 'pylint', - ]}, - ) - - -if __name__ == '__main__': - my_setup() +setup( + name='LoadsKernel', + version='2026.01.1', + description=("The Loads Kernel Software allows for the calculation of quasi-steady and dynamic maneuver loads, " + "unsteady gust loads in the time and frequency domain as well as dynamic landing loads based on a " + "generic landing gear module."), + long_description=readme, + long_description_content_type='text/markdown', + url='https://github.com/DLR-AE/LoadsKernel', + author='Arne Voß', + author_email='arne.voss@dlr.de', + license='BSD 3-Clause License', + packages=find_packages(), + entry_points={'console_scripts': ['loads-kernel=loadskernel.program_flow:command_line_interface', + 'model-viewer=modelviewer.view:command_line_interface', + 'loads-compare=loadscompare.compare:command_line_interface']}, + include_package_data=True, + package_data={'loadskernel': ['graphics/*.*'], + 'loadscompare': ['graphics/*.*'], }, + # Remember to update the requirements also in the conda feedstock (./recipe/meta.yml) when changing them here! + python_requires='>=3.10', + install_requires=['PanelAero', + 'matplotlib', + 'numpy<2.4.0', # Mayavi / VTK does not support numpy >= 2.4.0, wait for release of VTK 9.6 + 'scipy', + 'h5py', + 'tables', + 'pyyaml', + # Pandas 3.0.0 comes with changes that are not yet supported, + # see https://github.com/DLR-AE/LoadsKernel/issues/86 + 'pandas<3.0.0', + ], + extras_require={'extras': ['mpi4py', + 'mayavi', + 'traits', + 'traitsui', + 'jupyter', + 'pyiges', # only available with pip, not with conda + 'pyfmi', + 'pyside6' + ], + 'test': ['pytest', + 'pytest-cov', + 'jupyter-book', + 'flake8', + 'pylint', + ]}, +) From c29e954c749d1494454c1ff1e9f51e2c622f0066 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Tue, 3 Feb 2026 14:01:47 +0100 Subject: [PATCH 07/63] Dropped support for Python versions lower than 3.12 --- CHANGELOG.md | 3 +++ setup.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2714e07..229527d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Note New releases are marked in the repository using tags. Simply checkout the master branch for the lastest version or use git checkout if you require a specific release, for example 'git checkout 2022.10'. +# Next Release +- Dropped support for Python versions lower than 3.12 + # Release 2026.01 - Added gust and flutter simulation in the frequency domain based of GAFs obtained from linearized CFD (using SU2 and via pulse simulations). This is work is still very new and therefore needs more testing and/or applications. - Refactoring of 'equations' diff --git a/setup.py b/setup.py index 2803395..996d7ad 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ package_data={'loadskernel': ['graphics/*.*'], 'loadscompare': ['graphics/*.*'], }, # Remember to update the requirements also in the conda feedstock (./recipe/meta.yml) when changing them here! - python_requires='>=3.10', + python_requires='>=3.12', install_requires=['PanelAero', 'matplotlib', 'numpy<2.4.0', # Mayavi / VTK does not support numpy >= 2.4.0, wait for release of VTK 9.6 From aa49d045f5171c3eab441798f55177fc647c4746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Wed, 11 Feb 2026 10:25:45 +0100 Subject: [PATCH 08/63] Add calculation of reynolds number and dynamic viscosity --- loadskernel/units.py | 37 +++++++++++++++++++++++++++++++++++++ tests/test_unittests.py | 14 ++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/loadskernel/units.py b/loadskernel/units.py index c1e45b6..b1b2110 100644 --- a/loadskernel/units.py +++ b/loadskernel/units.py @@ -75,3 +75,40 @@ def tas2Ma(tas, h): def eas2Ma(eas, h): tas = eas2tas(eas, h) return tas2Ma(tas, h) + + +def reynolds_number(rho, v, l_ref, T): + # Calculate Reynolds number, https://en.wikipedia.org/wiki/Reynolds_number + # Inputs: + # - rho: fluid density [kg/m^3] + # - v: fluid velocity [m/s] + # - l_ref: reference length [m] + # - T: temperature [K] + # + # Output: + # - Re: Reynolds number (dimensionless) + + mu = dynamic_viscosity_of_air(T) + Re = (rho * v * l_ref) / mu + return Re + + +def dynamic_viscosity_of_air(T): + # Calculate dynamic viscosity of air according to Sutherland's formula. + # The constants used here are from https://www.cfd-online.com/Wiki/Sutherland%27s_law and in line with the + # values used in SU2. Different sources (e.g. https://de.wikipedia.org/wiki/Sutherland-Modell) give slightly + # different values. + # Inputs: + # - T: temperature [K] + # + # Output: + # - mu: dynamic viscosity [Pa*s = kg/(m*s)] + + # reference viscosity at T_ref [kg/(m*s)] + mu_ref = 1.716e-5 + # reference temperature [K] + T_ref = 273.15 + # Sutherland's constant for air [K] + S = 110.4 + mu = mu_ref * (T_ref + S) / (T + S) * (T / T_ref) ** (3 / 2) + return mu diff --git a/tests/test_unittests.py b/tests/test_unittests.py index 3f9dfa9..7b18841 100644 --- a/tests/test_unittests.py +++ b/tests/test_unittests.py @@ -4,6 +4,8 @@ from loadskernel.io_functions import read_bdf from loadskernel.fem_interfaces import fem_helper +from loadskernel import atmosphere +from loadskernel import units @pytest.fixture(name='tmp_output', scope='class') @@ -69,3 +71,15 @@ def test_hyperbolic_distance_metric(): HDM = fem_helper.calc_HDM(lam1, lam2) # Check for numerical similarity with reference values. assert np.allclose(HDM, HDM_ref, rtol=1e-4, atol=1e-4), "Hyperbolic distance metric (HDM) does NOT match reference" + +def test_reynoldsnumber(): + # Test Reynolds number calculation at sea level. + # This test also covers the dynamic viscosity calculation and parts of the isa atmosphere. + p, rho, T, a = atmosphere.isa(0.0) + v = 70.0 # m/s + l_ref = 1.0 # m + Re = units.reynolds_number(rho, v, l_ref, T) + # Reference value, checked with external tool. + Re_ref = 4792379.49652419 + # Check for numerical similarity with reference values. + assert np.allclose(Re, Re_ref, rtol=1e-4, atol=1e-4), "Reynolds number does NOT match reference" From 75389827b129c3178f4bb96a066cd4baf00357fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Wed, 11 Feb 2026 15:11:58 +0100 Subject: [PATCH 09/63] Fixing the CFD mesh reader to handle quadrilaterals --- loadskernel/io_functions/read_cfdgrids.py | 257 +++++++++------------- loadskernel/model.py | 9 +- modelviewer/cfdgrid.py | 11 +- modelviewer/plotting.py | 2 +- 4 files changed, 107 insertions(+), 172 deletions(-) diff --git a/loadskernel/io_functions/read_cfdgrids.py b/loadskernel/io_functions/read_cfdgrids.py index 941a8ca..42a874c 100644 --- a/loadskernel/io_functions/read_cfdgrids.py +++ b/loadskernel/io_functions/read_cfdgrids.py @@ -1,4 +1,4 @@ -import h5py +from itertools import chain import logging import numpy as np @@ -7,86 +7,30 @@ class ReadCfdgrids: - def __init__(self, jcl): - self.jcl = jcl + def __init__(self): + self.cfdgrids = {} + self.cfdgrid = {} + + def read_surface(self, jcl): + # Pick up the filename of the grid and the markers from the jcl. + # The markers are needed to extract the points that belong to the surface(s) of interest. if 'surface' in jcl.meshdefo: - self.filename_grid = self.jcl.meshdefo['surface']['filename_grid'] - self.markers = self.jcl.meshdefo['surface']['markers'] + filename_grid = jcl.meshdefo['surface']['filename_grid'] + markers = jcl.meshdefo['surface']['markers'] + # Swich between different file formats as specified in the jcl. + if 'fileformat' in jcl.meshdefo['surface'] and jcl.meshdefo['surface']['fileformat'].lower() == 'netcdf': + self.read_netcdf(filename_grid, markers) + elif 'fileformat' in jcl.meshdefo['surface'] and jcl.meshdefo['surface']['fileformat'].lower() == 'su2': + self.read_su2(filename_grid, markers) + else: + logging.error('jcl.meshdefo["surface"]["fileformat"] must be "netcdf" or "su2"') else: logging.error('jcl.meshdefo has no key "surface"') - def read_surface(self, merge_domains=False): - if 'fileformat' in self.jcl.meshdefo['surface'] and self.jcl.meshdefo['surface']['fileformat'].lower() == 'cgns': - self.read_cfdmesh_cgns(merge_domains) - elif 'fileformat' in self.jcl.meshdefo['surface'] and self.jcl.meshdefo['surface']['fileformat'].lower() == 'netcdf': - self.read_cfdmesh_netcdf(merge_domains) - elif 'fileformat' in self.jcl.meshdefo['surface'] and self.jcl.meshdefo['surface']['fileformat'].lower() == 'su2': - self.read_cfdmesh_su2(merge_domains) - else: - logging.error('jcl.meshdefo["surface"]["fileformat"] must be "netcdf", "cgns" or "su2"') - return - - def read_cfdmesh_cgns(self, merge_domains=False): - logging.info('Extracting all points from grid {}'.format(self.filename_grid)) - f = h5py.File(self.filename_grid, 'r') - f_scale = 1.0 / 1000.0 # convert to SI units: 0.001 if mesh is given in [mm], 1.0 if given in [m] - markers = f['Base'].keys() - markers.sort() - if merge_domains: - x = np.array([]) - y = np.array([]) - z = np.array([]) - for marker in markers: - # loop over domains - if marker[:4] == 'dom-': - logging.info(' - {} included'.format(marker)) - domain = f['Base'][marker] - x = np.concatenate((x, domain['GridCoordinates']['CoordinateX'][' data'][:].reshape(-1) * f_scale)) - y = np.concatenate((y, domain['GridCoordinates']['CoordinateY'][' data'][:].reshape(-1) * f_scale)) - z = np.concatenate((z, domain['GridCoordinates']['CoordinateZ'][' data'][:].reshape(-1) * f_scale)) - else: - logging.info(' - {} skipped'.format(marker)) - f.close() - n = len(x) - # build cfdgrid - self.cfdgrid = {} - self.cfdgrid['ID'] = np.arange(n) + 1 - self.cfdgrid['CP'] = np.zeros(n) - self.cfdgrid['CD'] = np.zeros(n) - self.cfdgrid['n'] = n - self.cfdgrid['offset'] = np.vstack((x, y, z)).T - self.cfdgrid['set'] = np.arange(6 * self.cfdgrid['n']).reshape(-1, 6) - self.cfdgrid['desc'] = 'all domains' - else: - self.cfdgrids = {} - for marker in markers: - # loop over domains - if marker[:4] == 'dom-': - logging.info(' - {} included'.format(marker)) - domain = f['Base'][marker] - x = domain['GridCoordinates']['CoordinateX'][' data'][:].reshape(-1) * f_scale - y = domain['GridCoordinates']['CoordinateY'][' data'][:].reshape(-1) * f_scale - z = domain['GridCoordinates']['CoordinateZ'][' data'][:].reshape(-1) * f_scale - n = len(x) - # build cfdgrid - cfdgrid = {} - cfdgrid['ID'] = np.arange(n) + 1 - cfdgrid['CP'] = np.zeros(n) - cfdgrid['CD'] = np.zeros(n) - cfdgrid['n'] = n - cfdgrid['offset'] = np.vstack((x, y, z)).T - cfdgrid['set'] = np.arange(6 * cfdgrid['n']).reshape(-1, 6) - cfdgrid['desc'] = marker - self.cfdgrids[marker] = cfdgrid - else: - logging.info(' - {} skipped'.format(marker)) - f.close() - - def read_cfdmesh_netcdf(self, merge_domains=False): - markers = self.markers - logging.info('Extracting points belonging to marker(s) {} from grid {}'.format(str(markers), self.filename_grid)) - # --- get all points on surfaces --- - ncfile_grid = netcdf.NetCDFFile(self.filename_grid, 'r') + def read_netcdf(self, filename_grid, markers): + logging.info('Extracting points belonging to marker(s) %s from grid %s', markers, filename_grid) + # get all points on surfaces + ncfile_grid = netcdf.NetCDFFile(filename_grid, 'r') boundarymarker_surfaces = ncfile_grid.variables['boundarymarker_of_surfaces'][:] points_of_surface = [] # merge triangles with quadrilaterals @@ -95,70 +39,67 @@ def read_cfdmesh_netcdf(self, merge_domains=False): if 'points_of_surfacequadrilaterals' in ncfile_grid.variables: points_of_surface += ncfile_grid.variables['points_of_surfacequadrilaterals'][:].tolist() - if merge_domains: + # Merge all markers into one cfdgrid + surfaces = np.array([], dtype=int) + for marker in markers: + surfaces = np.hstack((surfaces, np.where(boundarymarker_surfaces == marker)[0])) + points = np.unique([points_of_surface[s] for s in surfaces]) + self.cfdgrid['ID'] = points + self.cfdgrid['CP'] = np.zeros(self.cfdgrid['ID'].shape) + self.cfdgrid['CD'] = np.zeros(self.cfdgrid['ID'].shape) + self.cfdgrid['n'] = len(self.cfdgrid['ID']) + self.cfdgrid['offset'] = np.vstack((ncfile_grid.variables['points_xc'][:][points].copy(), + ncfile_grid.variables['points_yc'][:][points].copy(), + ncfile_grid.variables['points_zc'][:][points].copy())).T + self.cfdgrid['set'] = np.arange(6 * self.cfdgrid['n']).reshape(-1, 6) + self.cfdgrid['desc'] = markers + self.cfdgrid['points_of_surface'] = [points_of_surface[s] for s in surfaces] + + # Assemble the cfdgrids, one grid for each marker + for marker in markers: # --- get points on surfaces according to marker --- - surfaces = np.array([], dtype=int) - for marker in markers: - surfaces = np.hstack((surfaces, np.where(boundarymarker_surfaces == marker)[0])) + surfaces = np.where(boundarymarker_surfaces == marker)[0] points = np.unique([points_of_surface[s] for s in surfaces]) # build cfdgrid - self.cfdgrid = {} - self.cfdgrid['ID'] = points - self.cfdgrid['CP'] = np.zeros(self.cfdgrid['ID'].shape) - self.cfdgrid['CD'] = np.zeros(self.cfdgrid['ID'].shape) - self.cfdgrid['n'] = len(self.cfdgrid['ID']) - self.cfdgrid['offset'] = np.vstack((ncfile_grid.variables['points_xc'][:][points].copy(), - ncfile_grid.variables['points_yc'][:][points].copy(), - ncfile_grid.variables['points_zc'][:][points].copy())).T - self.cfdgrid['set'] = np.arange(6 * self.cfdgrid['n']).reshape(-1, 6) - self.cfdgrid['desc'] = markers - self.cfdgrid['points_of_surface'] = [points_of_surface[s] for s in surfaces] - else: - self.cfdgrids = {} - for marker in markers: - # --- get points on surfaces according to marker --- - surfaces = np.where(boundarymarker_surfaces == marker)[0] - points = np.unique([points_of_surface[s] for s in surfaces]) - # build cfdgrid - cfdgrid = {} - cfdgrid['ID'] = points - cfdgrid['CP'] = np.zeros(cfdgrid['ID'].shape) - cfdgrid['CD'] = np.zeros(cfdgrid['ID'].shape) - cfdgrid['n'] = len(cfdgrid['ID']) - cfdgrid['offset'] = np.vstack((ncfile_grid.variables['points_xc'][:][points].copy(), - ncfile_grid.variables['points_yc'][:][points].copy(), - ncfile_grid.variables['points_zc'][:][points].copy())).T - cfdgrid['set'] = np.arange(6 * cfdgrid['n']).reshape(-1, 6) - cfdgrid['desc'] = str(marker) - cfdgrid['points_of_surface'] = [points_of_surface[s] for s in surfaces] - self.cfdgrids[str(marker)] = cfdgrid + cfdgrid = {} + cfdgrid['ID'] = points + cfdgrid['CP'] = np.zeros(cfdgrid['ID'].shape) + cfdgrid['CD'] = np.zeros(cfdgrid['ID'].shape) + cfdgrid['n'] = len(cfdgrid['ID']) + cfdgrid['offset'] = np.vstack((ncfile_grid.variables['points_xc'][:][points].copy(), + ncfile_grid.variables['points_yc'][:][points].copy(), + ncfile_grid.variables['points_zc'][:][points].copy())).T + cfdgrid['set'] = np.arange(6 * cfdgrid['n']).reshape(-1, 6) + cfdgrid['desc'] = str(marker) + cfdgrid['points_of_surface'] = [points_of_surface[s] for s in surfaces] + self.cfdgrids[str(marker)] = cfdgrid ncfile_grid.close() - def read_cfdmesh_su2(self, merge_domains=False): + def read_su2(self, filename_grid, markers=None): """ The description of the SU2 mesh file format is given here: https://su2code.github.io/docs/Mesh-File/ - - Splitting lines into parameters and values: According to the mesh specification (and like in all SU2 config files), - a '=' is always followed by a space, for example 'PARAMETER_XY= value'. Some mesh generators use a more relaxed - syntax like 'PARAMETER_XY=value', meaning that a line needs to be split at the '=' and not the space. """ - logging.info('Extracting points belonging to surface marker(s) from grid {}'.format(self.filename_grid)) + logging.info('Extracting points belonging to surface marker(s) from grid %s', filename_grid) # Open the ascii file and read all lines. - with open(self.filename_grid, 'r') as fid: + with open(filename_grid, 'r', encoding='utf-8') as fid: lines = fid.readlines() # Loop over all lines, if a keyword such as NELEM, NPOIN or MARKER_TAG is found, # this announces a new section in the file. surface_points = {} n_lines = len(lines) i = 0 + all_points = None while i < n_lines: + # Splitting lines into parameters and values: According to the mesh specification (and like in all SU2 config + # files), a '=' is always followed by a space, for example 'PARAMETER_XY= value'. Some mesh generators use a + # more relaxed syntax like 'PARAMETER_XY=value', meaning that a line needs to be split at the '=' and not the + # space. if str.find(lines[i], 'NELEM') != -1: - n_elem = int(lines[i].split('=')[1]) # There is nothing we need to do with the volume element connectivity, so we can skip this section. # Skipping all lines (at once) saves a lot of time, since there are many volume elements... + n_elem = int(lines[i].split('=')[1]) i += n_elem elif str.find(lines[i], 'NPOIN') != -1: - # New section found. # Here, the coordinates of all points are given, including surface and volume points. n_points = int(lines[i].split('=')[1]) i += 1 @@ -166,10 +107,9 @@ def read_cfdmesh_su2(self, merge_domains=False): tmp = [] for x in range(n_points): tmp.append(lines[i + x].split()) - points = np.array(tmp, dtype=float) + all_points = np.array(tmp, dtype=float) i += x elif str.find(lines[i], 'MARKER_TAG') != -1: - # New section found. # Here, all points are listed that belong to one marker. # In addition, the connectivity is given, which we need e.g. for plotting. marker = lines[i].split('=')[1].strip() @@ -188,44 +128,47 @@ def read_cfdmesh_su2(self, merge_domains=False): # Quadrilateral elements are identified with a 9 quadrilaterals.append([int(id) for id in split_line[1:]]) else: - logging.error('Surface elements of type "{}" are not implemented!'.format(split_line[0])) - + logging.error('Surface elements of type "%s" are not implemented!', split_line[0]) + points_of_surface = list(chain.from_iterable(triangles + quadrilaterals)) i += x # Store everything surface_points[marker] = {} - surface_points[marker]['points'] = np.unique(triangles + quadrilaterals) - surface_points[marker]['points_of_surface'] = triangles + quadrilaterals + surface_points[marker]['triangles'] = triangles + surface_points[marker]['quadrilaterals'] = quadrilaterals + surface_points[marker]['points_of_surface'] = np.unique(points_of_surface) i += 1 - if merge_domains: - # First we need to do some merging... - tmp_points = np.array([], dtype=int) - tmp_surfaces = [] - for marker in self.markers: - tmp_points = np.hstack((tmp_points, surface_points[marker]['points'])) - tmp_surfaces += surface_points[marker]['points_of_surface'] + # Assemble the cfdgrids, one grid for each marker + for marker, item in surface_points.items(): + cfdgrid = {} + cfdgrid['ID'] = item['points_of_surface'] + cfdgrid['CP'] = np.zeros(cfdgrid['ID'].shape) + cfdgrid['CD'] = np.zeros(cfdgrid['ID'].shape) + cfdgrid['n'] = len(cfdgrid['ID']) + cfdgrid['offset'] = all_points[cfdgrid['ID'], :3] + cfdgrid['set'] = np.arange(6 * cfdgrid['n']).reshape(-1, 6) + cfdgrid['desc'] = marker + cfdgrid['triangles'] = item['triangles'] + cfdgrid['quadrilaterals'] = item['quadrilaterals'] + self.cfdgrids[marker] = cfdgrid - # Assemble the cfdgrid - self.cfdgrid = {} - self.cfdgrid['ID'] = np.unique(tmp_points) - self.cfdgrid['CP'] = np.zeros(self.cfdgrid['ID'].shape) - self.cfdgrid['CD'] = np.zeros(self.cfdgrid['ID'].shape) - self.cfdgrid['n'] = len(self.cfdgrid['ID']) - self.cfdgrid['offset'] = points[self.cfdgrid['ID'], :3] - self.cfdgrid['set'] = np.arange(6 * self.cfdgrid['n']).reshape(-1, 6) - self.cfdgrid['desc'] = self.markers - self.cfdgrid['points_of_surface'] = tmp_surfaces - else: - self.cfdgrids = {} - for marker in surface_points.keys(): - # Assemble the cfdgrid - cfdgrid = {} - cfdgrid['ID'] = surface_points[marker]['points'] - cfdgrid['CP'] = np.zeros(cfdgrid['ID'].shape) - cfdgrid['CD'] = np.zeros(cfdgrid['ID'].shape) - cfdgrid['n'] = len(cfdgrid['ID']) - cfdgrid['offset'] = points[cfdgrid['ID'], :3] - cfdgrid['set'] = np.arange(6 * cfdgrid['n']).reshape(-1, 6) - cfdgrid['desc'] = marker - cfdgrid['points_of_surface'] = surface_points[marker]['points_of_surface'] - self.cfdgrids[marker] = cfdgrid + # Merge all desired markers into one cfdgrid + points_of_all_surfaces = [] + all_triangles = [] + all_quadrilaterals = [] + # If no markers are specified, all markers are merged into one cfdgrid. + if markers is None: + markers = surface_points.keys() + for marker in markers: + points_of_all_surfaces += surface_points[marker]['points_of_surface'].tolist() + all_triangles += surface_points[marker]['triangles'] + all_quadrilaterals += surface_points[marker]['quadrilaterals'] + self.cfdgrid['ID'] = np.unique(points_of_all_surfaces) + self.cfdgrid['CP'] = np.zeros(self.cfdgrid['ID'].shape) + self.cfdgrid['CD'] = np.zeros(self.cfdgrid['ID'].shape) + self.cfdgrid['n'] = len(self.cfdgrid['ID']) + self.cfdgrid['offset'] = all_points[self.cfdgrid['ID'], :3] + self.cfdgrid['set'] = np.arange(6 * self.cfdgrid['n']).reshape(-1, 6) + self.cfdgrid['desc'] = markers + self.cfdgrid['triangles'] = all_triangles + self.cfdgrid['quadrilaterals'] = all_quadrilaterals diff --git a/loadskernel/model.py b/loadskernel/model.py index 52f8a56..5febe70 100644 --- a/loadskernel/model.py +++ b/loadskernel/model.py @@ -502,13 +502,12 @@ def build_cfdgrid(self): # ---- mesh defo --- # ------------------- if self.jcl.aero['method'] in ['cfd_steady', 'cfd_unsteady']: - cfdgrids = read_cfdgrids.ReadCfdgrids(self.jcl) - cfdgrids.read_surface(merge_domains=True) - cfdgrids.read_surface(merge_domains=False) + cfdgrids = read_cfdgrids.ReadCfdgrids() + cfdgrids.read_surface(self.jcl) self.cfdgrid = cfdgrids.cfdgrid self.cfdgrids = cfdgrids.cfdgrids - logging.info('The CFD surface grid consists of {} grid points and {} boundary markers.'.format( - self.cfdgrid['n'], self.cfdgrids.__len__())) + logging.info('The CFD surface grid consists of %s grid points and %s boundary markers.', + self.cfdgrid['n'], len(self.cfdgrids)) # Option A: CFD forces are transferred to the aerogrid. # This allows a direct integration into existing procedures and a comparison to VLM forces. diff --git a/modelviewer/cfdgrid.py b/modelviewer/cfdgrid.py index 6423233..3195e0e 100644 --- a/modelviewer/cfdgrid.py +++ b/modelviewer/cfdgrid.py @@ -4,13 +4,10 @@ class TauGrid(loadskernel.io_functions.read_cfdgrids.ReadCfdgrids): - def __init__(self): - pass - def load_file(self, filename): self.filename_grid = filename self.get_markers() - self.read_cfdmesh_netcdf() + self.read_netcdf(self.filename_grid, self.markers) def get_markers(self): ncfile_grid = netcdf.NetCDFFile(self.filename_grid, 'r') @@ -19,9 +16,5 @@ def get_markers(self): class SU2Grid(loadskernel.io_functions.read_cfdgrids.ReadCfdgrids): - def __init__(self): - pass - def load_file(self, filename): - self.filename_grid = filename - self.read_cfdmesh_su2() + self.read_su2(filename) diff --git a/modelviewer/plotting.py b/modelviewer/plotting.py index c33189a..ca486af 100644 --- a/modelviewer/plotting.py +++ b/modelviewer/plotting.py @@ -275,7 +275,7 @@ def setup_cfdgrid_display(self, grid, color, scalars): ug = tvtk.UnstructuredGrid(points=grid['offset']) # ug.point_data.scalars = scalars shells = [] - for shell in grid['points_of_surface']: + for shell in grid['triangles'] + grid['quadrilaterals']: shells.append([np.where(grid['ID'] == id)[0][0] for id in shell]) shell_type = tvtk.Polygon().cell_type ug.set_cells(shell_type, shells) From 31896d36c75c50ac237457de5383f9e37575ef85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Wed, 11 Feb 2026 15:15:05 +0100 Subject: [PATCH 10/63] Update free-stream initialization for SU2, apply multiple coding style suggestions --- loadskernel/cfd_interfaces/su2_interface.py | 92 ++++++++++----------- 1 file changed, 42 insertions(+), 50 deletions(-) diff --git a/loadskernel/cfd_interfaces/su2_interface.py b/loadskernel/cfd_interfaces/su2_interface.py index b6e8580..f453a38 100644 --- a/loadskernel/cfd_interfaces/su2_interface.py +++ b/loadskernel/cfd_interfaces/su2_interface.py @@ -75,10 +75,10 @@ def __init__(self, solution): if "pysu2" in sys.modules and "SU2" in sys.modules: # make sure that all processes are at the same stage self.comm.barrier() - logging.info('Init CFD interface of type "{}" on MPI process {}.'.format(self.__class__.__name__, self.myid)) + logging.info('Init CFD interface of type "%s" on MPI process %s.', self.__class__.__name__, self.myid) else: - logging.error('pysu2 was/could NOT be imported! Model equations of type "{}" will NOT work.'.format( - self.jcl.aero['method'])) + logging.error('pysu2 was/could NOT be imported! Model equations of type "%s" will NOT work.', + self.jcl.aero['method']) self.FluidSolver = None # Set-up file system structure @@ -86,11 +86,13 @@ def __init__(self, solution): check_cfd_folders(self.jcl) check_para_path(self.jcl) copy_para_file(self.jcl, self.trimcase) - self.para_filename = self.jcl.aero['para_path'] + 'para_subcase_{}'.format(self.trimcase['subcase']) + self.para_filename = self.jcl.aero['para_path'] + f'para_subcase_{self.trimcase["subcase"]}' - # Storage for the euler transofmation of the unsteady interface + # Init some variables self.XYZ = None self.PhiThetaPsi = None + self.Ucfd = None + self.local_mesh = None def prepare_meshdefo(self, Uf, Ux2): """ @@ -143,17 +145,16 @@ def update_general_para(self): # set general parameters, which don't change over the course of the CFD simulation, so they are only updated # for the first execution config['MESH_FILENAME'] = self.jcl.meshdefo['surface']['filename_grid'] - config['RESTART_FILENAME'] = self.jcl.aero['para_path'] + 'sol/restart_subcase_{}.dat'.format( - self.trimcase['subcase']) - config['SOLUTION_FILENAME'] = self.jcl.aero['para_path'] + 'sol/restart_subcase_{}.dat'.format( - self.trimcase['subcase']) - config['SURFACE_FILENAME'] = self.jcl.aero['para_path'] + 'sol/surface_subcase_{}'.format( - self.trimcase['subcase']) - config['VOLUME_FILENAME'] = self.jcl.aero['para_path'] + 'sol/volume_subcase_{}'.format( - self.trimcase['subcase']) - config['CONV_FILENAME'] = self.jcl.aero['para_path'] + 'sol/history_subcase_{}'.format( - self.trimcase['subcase']) - # free stream definition + config['RESTART_FILENAME'] = self.jcl.aero['para_path'] + f'sol/restart_subcase_{self.trimcase["subcase"]}.dat' + config['SOLUTION_FILENAME'] = self.jcl.aero['para_path'] + f'sol/restart_subcase_{self.trimcase["subcase"]}.dat' + config['SURFACE_FILENAME'] = self.jcl.aero['para_path'] + f'sol/surface_subcase_{self.trimcase["subcase"]}' + config['VOLUME_FILENAME'] = self.jcl.aero['para_path'] + f'sol/volume_subcase_{self.trimcase["subcase"]}' + config['CONV_FILENAME'] = self.jcl.aero['para_path'] + f'sol/history_subcase_{self.trimcase["subcase"]}' + # Set density-based free-stream initialization and use the thermodynamics quantities instead of + # the reynolds number. + config['FREESTREAM_OPTION'] = 'DENSITY_FS' + config['INIT_OPTION'] = 'TD_CONDITIONS' + # Free-stream definition config['FREESTREAM_TEMPERATURE'] = self.atmo['T'] config['FREESTREAM_DENSITY'] = self.atmo['rho'] config['FREESTREAM_PRESSURE'] = self.atmo['p'] @@ -172,9 +173,8 @@ def update_general_para(self): config['MARKER_DEFORM_MESH'] = '( ' + ', '.join(self.jcl.meshdefo['surface']['markers']) + ' )' # activate grid movement config['GRID_MOVEMENT'] = 'ROTATING_FRAME' - config['MOTION_ORIGIN'] = '{} {} {}'.format(self.cggrid['offset'][0, 0], - self.cggrid['offset'][0, 1], - self.cggrid['offset'][0, 2]) + config['MOTION_ORIGIN'] = \ + f'{self.cggrid["offset"][0, 0]} {self.cggrid["offset"][0, 1]} {self.cggrid["offset"][0, 2]}' config['MACH_MOTION'] = self.trimcase['Ma'] # there is no restart for the first execution config['RESTART_SOL'] = 'NO' @@ -201,7 +201,7 @@ def init_solver(self): def run_solver(self, i_timestep=0): logging.debug('Waiting until all processes are ready to perform a coordinated start...') self.comm.barrier() - logging.info('Launch SU2 for time step {}.'.format(i_timestep)) + logging.info('Launch SU2 for time step %s.', i_timestep) # start timer t_start = time.time() # initialize SU2 if this is the first run. @@ -218,7 +218,7 @@ def run_solver(self, i_timestep=0): self.FluidSolver.Output(i_timestep) self.comm.barrier() - logging.debug('CFD computation performed in {:.2f} seconds.'.format(time.time() - t_start)) + logging.debug('CFD computation performed in %.2f seconds.', time.time() - t_start) def get_last_solution(self): return self.Pcfd_global() @@ -234,7 +234,7 @@ def Pcfd_global(self): self.comm.barrier() self.comm.Allgatherv(Pcfd_send, Pcfd_rcv) Pcfd = Pcfd_rcv.sum(axis=0) - logging.debug('All nodal loads recovered, sorted and gathered in {:.2f} sec.'.format(time.time() - t_start)) + logging.debug('All nodal loads recovered, sorted and gathered in %.2f sec.', time.time() - t_start) return Pcfd def prepare_initial_solution(self): @@ -299,7 +299,7 @@ def get_local_mesh(self): 'set_global': np.array(tmp_set_global).squeeze(), 'n': n } - logging.debug('This is process {} and my local mesh has a size of {}'.format(self.myid, self.local_mesh['n'])) + logging.debug('This is process %s and my local mesh has a size of %s', self.myid, self.local_mesh['n']) def transfer_deformations(self, grid_i, U_i, set_i, rbf_type, surface_spline, support_radius=2.0): """ @@ -307,7 +307,7 @@ def transfer_deformations(self, grid_i, U_i, set_i, rbf_type, surface_spline, su This version works on the local mesh of a mpi partition, making the calculation of the mesh deformations faster. """ - logging.info('Transferring deformations to the local CFD surface with {} nodes.'.format(self.local_mesh['n'])) + logging.info('Transferring deformations to the local CFD surface with %s nodes.', self.local_mesh['n']) # build spline matrix PHIi_d = spline_functions.spline_rbf(grid_i, set_i, self.local_mesh, '', rbf_type=rbf_type, surface_spline=surface_spline, @@ -419,17 +419,16 @@ def update_general_para(self): # set general parameters, which don't change over the course of the CFD simulation, so they are only updated # for the first execution config['MESH_FILENAME'] = self.jcl.meshdefo['surface']['filename_grid'] - config['RESTART_FILENAME'] = self.jcl.aero['para_path'] + 'sol/restart_subcase_{}.dat'.format( - self.trimcase['subcase']) - config['SOLUTION_FILENAME'] = self.jcl.aero['para_path'] + 'sol/restart_subcase_{}.dat'.format( - self.trimcase['subcase']) - config['SURFACE_FILENAME'] = self.jcl.aero['para_path'] + 'sol/surface_subcase_{}'.format( - self.trimcase['subcase']) - config['VOLUME_FILENAME'] = self.jcl.aero['para_path'] + 'sol/volume_subcase_{}'.format( - self.trimcase['subcase']) - config['CONV_FILENAME'] = self.jcl.aero['para_path'] + 'sol/history_subcase_{}'.format( - self.trimcase['subcase']) - # free stream definition + config['RESTART_FILENAME'] = self.jcl.aero['para_path'] + f'sol/restart_subcase_{self.trimcase["subcase"]}.dat' + config['SOLUTION_FILENAME'] = self.jcl.aero['para_path'] + f'sol/restart_subcase_{self.trimcase["subcase"]}.dat' + config['SURFACE_FILENAME'] = self.jcl.aero['para_path'] + f'sol/surface_subcase_{self.trimcase["subcase"]}' + config['VOLUME_FILENAME'] = self.jcl.aero['para_path'] + f'sol/volume_subcase_{self.trimcase["subcase"]}' + config['CONV_FILENAME'] = self.jcl.aero['para_path'] + f'sol/history_subcase_{self.trimcase["subcase"]}' + # Set density-based free-stream initialization and use the thermodynamics quantities instead of + # the reynolds number. + config['FREESTREAM_OPTION'] = 'DENSITY_FS' + config['INIT_OPTION'] = 'TD_CONDITIONS' + # Free-stream definition config['FREESTREAM_TEMPERATURE'] = self.atmo['T'] config['FREESTREAM_DENSITY'] = self.atmo['rho'] config['FREESTREAM_PRESSURE'] = self.atmo['p'] @@ -486,12 +485,9 @@ def update_timedom_para(self): config['RESTART_SOL'] = 'YES' config['RESTART_ITER'] = 2 # create links for the .dat files... - filename_steady = self.jcl.aero['para_path'] + 'sol/restart_subcase_{}.dat'.format( - self.trimcase['subcase']) - filename_unsteady0 = self.jcl.aero['para_path'] + 'sol/restart_subcase_{}_00000.dat'.format( - self.trimcase['subcase']) - filename_unsteady1 = self.jcl.aero['para_path'] + 'sol/restart_subcase_{}_00001.dat'.format( - self.trimcase['subcase']) + filename_steady = self.jcl.aero['para_path'] + f'sol/restart_subcase_{self.trimcase["subcase"]}.dat' + filename_unsteady0 = self.jcl.aero['para_path'] + f'sol/restart_subcase_{self.trimcase["subcase"]}_00000.dat' + filename_unsteady1 = self.jcl.aero['para_path'] + f'sol/restart_subcase_{self.trimcase["subcase"]}_00001.dat' try: os.symlink(filename_steady, filename_unsteady0) os.symlink(filename_steady, filename_unsteady1) @@ -499,12 +495,9 @@ def update_timedom_para(self): # Do nothing, the most likely cause is that the file already exists. pass # ...and for the .csv files - filename_steady = self.jcl.aero['para_path'] + 'sol/restart_subcase_{}.csv'.format( - self.trimcase['subcase']) - filename_unsteady0 = self.jcl.aero['para_path'] + 'sol/restart_subcase_{}_00000.csv'.format( - self.trimcase['subcase']) - filename_unsteady1 = self.jcl.aero['para_path'] + 'sol/restart_subcase_{}_00001.csv'.format( - self.trimcase['subcase']) + filename_steady = self.jcl.aero['para_path'] + f'sol/restart_subcase_{self.trimcase["subcase"]}.csv' + filename_unsteady0 = self.jcl.aero['para_path'] + f'sol/restart_subcase_{self.trimcase["subcase"]}_00000.csv' + filename_unsteady1 = self.jcl.aero['para_path'] + f'sol/restart_subcase_{self.trimcase["subcase"]}_00001.csv' try: os.symlink(filename_steady, filename_unsteady0) os.symlink(filename_steady, filename_unsteady1) @@ -562,9 +555,8 @@ def update_gust_para(self, Vtas, Vgust): config['GUST_DIR'] = 'Y_DIR' config['GUST_AMPL'] = -Vgust else: - logging.error('Gust orientation {} currently NOT supported by SU2. \ - Possible values: 0.0, 90.0, 180.0, 270.0 or 360.0 degrees.'.format( - self.simcase['gust_orientation'])) + logging.error('Gust orientation %s currently NOT supported by SU2. \ + Possible values: 0.0, 90.0, 180.0, 270.0 or 360.0 degrees.', self.simcase['gust_orientation']) # Note: In SU2 this is the full gust length, not the gust gradient H (half gust length). config['GUST_WAVELENGTH'] = 2.0 * self.simcase['gust_gradient'] config['GUST_PERIODS'] = 1.0 From b0783e0f40f682458964bd1a6f48de541fc84ece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Fri, 13 Feb 2026 14:19:25 +0100 Subject: [PATCH 11/63] Clean-up logger and avoid duplicate outputs with mpi --- loadskernel/program_flow.py | 50 ++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/loadskernel/program_flow.py b/loadskernel/program_flow.py index 2e83c6c..f7465ee 100755 --- a/loadskernel/program_flow.py +++ b/loadskernel/program_flow.py @@ -75,56 +75,48 @@ def print_logo(self): def setup_logger_cluster(self, i): # Generate a separate filename for each subcase path_log = data_handling.check_path(self.path_output + 'log/') - filename = path_log + 'log_' + self.job_name + '_subcase_' + str(self.jcl.trimcase[i]['subcase']) \ - + '.txt.' + str(self.myid) + filename = path_log + f'log_{self.job_name}_subcase_{self.jcl.trimcase[i]['subcase']}.txt.{self.myid}' # Then create the logger and console output self.create_logfile_and_console_output(filename) def setup_logger(self): # Generate a generic name for the log file path_log = data_handling.check_path(self.path_output + 'log/') - filename = path_log + 'log_' + self.job_name + '.txt.' + str(self.myid) + filename = path_log + f'log_{self.job_name}.txt.{self.myid}' # Then create the logger and console output self.create_logfile_and_console_output(filename) def create_logfile_and_console_output(self, filename): logger = logging.getLogger() + # Disable propagation to avoid duplicate outputs in case of multiple handlers (e.g. console and file handler). + logger.propagate = False + # Clear previous handlers. + if logger.hasHandlers(): + logger.handlers.clear() # Set logging level. if self.debug: logger.setLevel(logging.DEBUG) else: logger.setLevel(logging.INFO) - # Get the names of all existing loggers. - existing_handlers = [hdlr.get_name() for hdlr in logger.handlers] - if 'lk_logfile' in existing_handlers: - # Make sure that the filename is still correct. - hdlr = logger.handlers[existing_handlers.index('lk_logfile')] - if not hdlr.baseFilename == filename: - # In case the filename is incorrect, remove the handler completely from the logger. - logger.removeHandler(hdlr) - # Update the list of all existing loggers. - existing_handlers = [hdlr.get_name() for hdlr in logger.handlers] - - # Add the following handlers only if they don't exist. This avoid duplicate lines/log entries. - if 'lk_logfile' not in existing_handlers: - # define a Handler which writes messages to a log file - logfile = logging.FileHandler(filename, mode='a') - logfile.set_name('lk_logfile') - formatter = logging.Formatter(fmt='%(asctime)s %(processName)-14s %(levelname)s: %(message)s', - datefmt='%d/%m/%Y %H:%M:%S') - logfile.setFormatter(formatter) - logger.addHandler(logfile) - + # Define a Handler which writes messages to a log file + logfile = logging.FileHandler(filename, mode='a') + logfile.set_name('lk_logfile') + logfile.propagate = False + formatter = logging.Formatter(fmt='%(asctime)s %(processName)-14s %(levelname)s: %(message)s', + datefmt='%d/%m/%Y %H:%M:%S') + logfile.setFormatter(formatter) + # Add the handler to the root logger + logger.addHandler(logfile) # For convinience, the first rank writes console outputs, too. - if (self.myid == 0) and ('lk_console' not in existing_handlers): - # define a Handler which writes messages to the sys.stout + if self.myid == 0: + # Define a Handler which writes messages to the sys.stout console = logging.StreamHandler(sys.stdout) console.set_name('lk_console') - # set a format which is simpler for console use + console.propagate = False + # Set a format which is simpler for console use and tell the handler to use this format formatter = logging.Formatter(fmt='%(levelname)s: %(message)s') - # tell the handler to use this format console.setFormatter(formatter) - # add the handler(s) to the root logger + # Add the handler to the root logger logger.addHandler(console) logger.info('This is the log for process %s.', self.myid) From 280488afc7c206ffbd439de4b13287c04a7db3d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Fri, 13 Feb 2026 16:45:17 +0100 Subject: [PATCH 12/63] Update reading Tau meshes for quadrilaterals and adjust netcdf interface --- loadskernel/io_functions/read_cfdgrids.py | 77 +++++++++++++---------- modelviewer/cfdgrid.py | 2 +- modelviewer/plotting.py | 3 +- 3 files changed, 45 insertions(+), 37 deletions(-) diff --git a/loadskernel/io_functions/read_cfdgrids.py b/loadskernel/io_functions/read_cfdgrids.py index 42a874c..d31c5b5 100644 --- a/loadskernel/io_functions/read_cfdgrids.py +++ b/loadskernel/io_functions/read_cfdgrids.py @@ -29,50 +29,59 @@ def read_surface(self, jcl): def read_netcdf(self, filename_grid, markers): logging.info('Extracting points belonging to marker(s) %s from grid %s', markers, filename_grid) - # get all points on surfaces - ncfile_grid = netcdf.NetCDFFile(filename_grid, 'r') + # Get all points on surfaces + ncfile_grid = netcdf.netcdf_file(filename_grid, 'r') boundarymarker_surfaces = ncfile_grid.variables['boundarymarker_of_surfaces'][:] - points_of_surface = [] - # merge triangles with quadrilaterals + # Get all surface trianagles and quadrilaterals. + triangles = np.array([]) + quadrilaterals = np.array([]) if 'points_of_surfacetriangles' in ncfile_grid.variables: - points_of_surface += ncfile_grid.variables['points_of_surfacetriangles'][:].tolist() + triangles = ncfile_grid.variables['points_of_surfacetriangles'][:].copy() if 'points_of_surfacequadrilaterals' in ncfile_grid.variables: - points_of_surface += ncfile_grid.variables['points_of_surfacequadrilaterals'][:].tolist() - - # Merge all markers into one cfdgrid - surfaces = np.array([], dtype=int) - for marker in markers: - surfaces = np.hstack((surfaces, np.where(boundarymarker_surfaces == marker)[0])) - points = np.unique([points_of_surface[s] for s in surfaces]) - self.cfdgrid['ID'] = points - self.cfdgrid['CP'] = np.zeros(self.cfdgrid['ID'].shape) - self.cfdgrid['CD'] = np.zeros(self.cfdgrid['ID'].shape) - self.cfdgrid['n'] = len(self.cfdgrid['ID']) - self.cfdgrid['offset'] = np.vstack((ncfile_grid.variables['points_xc'][:][points].copy(), - ncfile_grid.variables['points_yc'][:][points].copy(), - ncfile_grid.variables['points_zc'][:][points].copy())).T - self.cfdgrid['set'] = np.arange(6 * self.cfdgrid['n']).reshape(-1, 6) - self.cfdgrid['desc'] = markers - self.cfdgrid['points_of_surface'] = [points_of_surface[s] for s in surfaces] - + quadrilaterals = ncfile_grid.variables['points_of_surfacequadrilaterals'][:].copy() # Assemble the cfdgrids, one grid for each marker for marker in markers: - # --- get points on surfaces according to marker --- - surfaces = np.where(boundarymarker_surfaces == marker)[0] - points = np.unique([points_of_surface[s] for s in surfaces]) - # build cfdgrid + # Get points on surfaces for this marker + triangles_on_marker = triangles[boundarymarker_surfaces[:len(triangles)] == marker] + quadrilaterals_on_marker = quadrilaterals[boundarymarker_surfaces[len(triangles):] == marker] + points_on_marker = np.unique(np.concatenate((triangles_on_marker.flatten(), + quadrilaterals_on_marker.flatten()))) cfdgrid = {} - cfdgrid['ID'] = points + cfdgrid['ID'] = points_on_marker cfdgrid['CP'] = np.zeros(cfdgrid['ID'].shape) cfdgrid['CD'] = np.zeros(cfdgrid['ID'].shape) cfdgrid['n'] = len(cfdgrid['ID']) - cfdgrid['offset'] = np.vstack((ncfile_grid.variables['points_xc'][:][points].copy(), - ncfile_grid.variables['points_yc'][:][points].copy(), - ncfile_grid.variables['points_zc'][:][points].copy())).T + cfdgrid['offset'] = np.vstack((ncfile_grid.variables['points_xc'][:][points_on_marker].copy(), + ncfile_grid.variables['points_yc'][:][points_on_marker].copy(), + ncfile_grid.variables['points_zc'][:][points_on_marker].copy())).T cfdgrid['set'] = np.arange(6 * cfdgrid['n']).reshape(-1, 6) cfdgrid['desc'] = str(marker) - cfdgrid['points_of_surface'] = [points_of_surface[s] for s in surfaces] + cfdgrid['triangles'] = triangles_on_marker.tolist() + cfdgrid['quadrilaterals'] = quadrilaterals_on_marker.tolist() self.cfdgrids[str(marker)] = cfdgrid + + # Merge all markers into one cfdgrid + points_of_all_markers = np.array([], dtype=int) + triangles_of_all_markers = [] + quadrilaterals_of_all_markers = [] + for marker, cfdgrid in self.cfdgrids.items(): + points_of_all_markers = np.concatenate((points_of_all_markers, cfdgrid['ID'])) + triangles_of_all_markers += cfdgrid['triangles'] + quadrilaterals_of_all_markers += cfdgrid['quadrilaterals'] + points_of_all_markers = np.unique(points_of_all_markers) + + self.cfdgrid['ID'] = points_of_all_markers + self.cfdgrid['CP'] = np.zeros(self.cfdgrid['ID'].shape) + self.cfdgrid['CD'] = np.zeros(self.cfdgrid['ID'].shape) + self.cfdgrid['n'] = len(self.cfdgrid['ID']) + self.cfdgrid['offset'] = np.vstack((ncfile_grid.variables['points_xc'][:][points_of_all_markers].copy(), + ncfile_grid.variables['points_yc'][:][points_of_all_markers].copy(), + ncfile_grid.variables['points_zc'][:][points_of_all_markers].copy())).T + self.cfdgrid['set'] = np.arange(6 * self.cfdgrid['n']).reshape(-1, 6) + self.cfdgrid['desc'] = markers + self.cfdgrid['triangles'] = triangles_of_all_markers + self.cfdgrid['quadrilaterals'] = quadrilaterals_of_all_markers + ncfile_grid.close() def read_su2(self, filename_grid, markers=None): @@ -129,13 +138,13 @@ def read_su2(self, filename_grid, markers=None): quadrilaterals.append([int(id) for id in split_line[1:]]) else: logging.error('Surface elements of type "%s" are not implemented!', split_line[0]) - points_of_surface = list(chain.from_iterable(triangles + quadrilaterals)) + points_on_surface = list(chain.from_iterable(triangles + quadrilaterals)) i += x # Store everything surface_points[marker] = {} surface_points[marker]['triangles'] = triangles surface_points[marker]['quadrilaterals'] = quadrilaterals - surface_points[marker]['points_of_surface'] = np.unique(points_of_surface) + surface_points[marker]['points_of_surface'] = np.unique(points_on_surface) i += 1 # Assemble the cfdgrids, one grid for each marker diff --git a/modelviewer/cfdgrid.py b/modelviewer/cfdgrid.py index 3195e0e..165405e 100644 --- a/modelviewer/cfdgrid.py +++ b/modelviewer/cfdgrid.py @@ -10,7 +10,7 @@ def load_file(self, filename): self.read_netcdf(self.filename_grid, self.markers) def get_markers(self): - ncfile_grid = netcdf.NetCDFFile(self.filename_grid, 'r') + ncfile_grid = netcdf.netcdf_file(self.filename_grid, 'r') self.markers = ncfile_grid.variables['marker'][:].tolist() diff --git a/modelviewer/plotting.py b/modelviewer/plotting.py index ca486af..fb7d8a4 100644 --- a/modelviewer/plotting.py +++ b/modelviewer/plotting.py @@ -266,8 +266,7 @@ def plot_cfdgrids(self, markers): self.src_cfdgrids = [] for marker in self.cfdgrids: if marker in markers: - self.setup_cfdgrid_display( - grid=self.cfdgrids[marker], color=(1, 1, 1), scalars=None) + self.setup_cfdgrid_display(grid=self.cfdgrids[marker], color=(1, 1, 1), scalars=None) self.show_cfdgrids = True mlab.draw(self.fig) From 8937830222bddbe3ee699050d3ca3c2c456c9031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Mon, 16 Feb 2026 11:30:38 +0100 Subject: [PATCH 13/63] Fix line continuation in model viewer mass tab --- modelviewer/view.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/modelviewer/view.py b/modelviewer/view.py index 77763da..57f4f33 100644 --- a/modelviewer/view.py +++ b/modelviewer/view.py @@ -497,13 +497,12 @@ def get_mass_data_for_plotting(self): Mb = self.model['mass'][key]['Mb'][()] cggrid = load_hdf5_dict(self.model['mass'][key]['cggrid']) self.plotting.plot_masses(Mgg, Mb, cggrid, rho) - self.lb_cg.setText(f"CG: x={cggrid['offset'][0, 0]:0.4f}, \ - y={cggrid['offset'][0, 1]:0.4f}, \ - z={cggrid['offset'][0, 2]:0.4f} m") + self.lb_cg.setText(f"CG: x={cggrid['offset'][0, 0]:0.4f}," + f"y={cggrid['offset'][0, 1]:0.4f}," + f"z={cggrid['offset'][0, 2]:0.4f} m") # cg_mac = (x_cg - x_mac)*c_ref * 100 [%] # negativ bedeutet Vorlage --> stabil - cg_mac = (cggrid['offset'][0, 0] - self.MAC[0]) / \ - self.model['macgrid']['c_ref'][()] * 100.0 + cg_mac = (cggrid['offset'][0, 0] - self.MAC[0]) / self.model['macgrid']['c_ref'][()] * 100.0 if cg_mac < 0.0: rating = 'stable' elif cg_mac > 0.0: From c06497d95755478d69ffe5faa918923e89184b67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Tue, 17 Feb 2026 10:40:35 +0100 Subject: [PATCH 14/63] Fix hard-coded surface spline with 'wendland2' weighting function --- loadskernel/spline_functions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/loadskernel/spline_functions.py b/loadskernel/spline_functions.py index 2e2b5bb..095a495 100644 --- a/loadskernel/spline_functions.py +++ b/loadskernel/spline_functions.py @@ -104,7 +104,6 @@ class SplineRadialBasisFunctions: def __init__(self, nodes_fe, nodes_cfd, rbf_type, surface_spline, support_radius): self.surface_spline = surface_spline self.rbf_type = rbf_type - self.rbf_type = 'wendland2' self.R = support_radius if self.surface_spline: logging.debug('Using surface formulation (2D xy surface)') From 841aa7fbb8ea8e1cabbcf5eecd93a6f4290f6079 Mon Sep 17 00:00:00 2001 From: "Chang.Xu" Date: Wed, 25 Feb 2026 16:09:58 +0100 Subject: [PATCH 15/63] add support for nastran 95 generated matrix op2 file --- loadskernel/fem_interfaces/nastran_interface.py | 13 +++++++++++++ loadskernel/io_functions/read_op2.py | 5 +++++ 2 files changed, 18 insertions(+) diff --git a/loadskernel/fem_interfaces/nastran_interface.py b/loadskernel/fem_interfaces/nastran_interface.py index f9fd0f2..ee3dd44 100644 --- a/loadskernel/fem_interfaces/nastran_interface.py +++ b/loadskernel/fem_interfaces/nastran_interface.py @@ -3,6 +3,8 @@ import scipy import numpy as np +from pyNastran.op2.op2 import OP2 +from scipy.sparse import csc_matrix from loadskernel.io_functions import read_op2, read_op4, read_h5, read_mona from loadskernel import spline_functions, spline_rules @@ -22,6 +24,12 @@ def get_stiffness_matrix(self): elif 'filename_KGG' in self.jcl.geom and 'filename_GM' in self.jcl.geom: self.KGG = read_op4.load_matrix(self.jcl.geom['filename_KGG'], sparse_output=True, sparse_format=True) self.GM = read_op4.load_matrix(self.jcl.geom['filename_GM'], sparse_output=True, sparse_format=True) + elif 'filename_op2' in self.jcl.geom: + op2_model = OP2() + op2_model.mode = 'nasa95' + op2_model.read_op2(self.jcl.geom['filename_op2']) + self.KGG = csc_matrix(op2_model.matrices['KGG'].data) + self.GM = csc_matrix(op2_model.matrices['GM'].data).T else: logging.error('Please provide filename(s) of .h5 or .OP4 files.') @@ -31,6 +39,11 @@ def get_mass_matrix(self, i_mass): self.MGG = read_h5.load_matrix(self.jcl.mass['filename_h5'][self.i_mass], name='MGG') elif 'filename_MGG' in self.jcl.mass: self.MGG = read_op4.load_matrix(self.jcl.mass['filename_MGG'][self.i_mass], sparse_output=True, sparse_format=True) + elif 'filename_op2' in self.jcl.mass: + op2_model = OP2() + op2_model.mode = 'nasa95' + op2_model.read_op2(self.jcl.geom['filename_op2']) + self.MGG = csc_matrix(op2_model.matrices['MGG'].data) else: logging.error('Please provide filename(s) of .h5 or .OP4 files.') return self.MGG diff --git a/loadskernel/io_functions/read_op2.py b/loadskernel/io_functions/read_op2.py index f83f2de..e6d0a53 100644 --- a/loadskernel/io_functions/read_op2.py +++ b/loadskernel/io_functions/read_op2.py @@ -522,6 +522,11 @@ def read_op2_record(self, form=None, N=0): f.read(4) # endrec key = self._get_key() self._skip_key(2) + # TODO: IDK why does this work for Nast95 generated op2 file ? + if max(data) > 1: + print("[warning]: .op2 file from Nast95 detected, converting the data to binary") + data[data < 20] = 1 + data[data > 20] = 2 return data def skip_op2_record(self): From 40593908dfc2c22dbd91daf0552f57cfeec9e66f Mon Sep 17 00:00:00 2001 From: "Chang.Xu" Date: Mon, 2 Mar 2026 16:23:26 +0100 Subject: [PATCH 16/63] restore to original code --- loadskernel/fem_interfaces/nastran_interface.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/loadskernel/fem_interfaces/nastran_interface.py b/loadskernel/fem_interfaces/nastran_interface.py index ee3dd44..f9fd0f2 100644 --- a/loadskernel/fem_interfaces/nastran_interface.py +++ b/loadskernel/fem_interfaces/nastran_interface.py @@ -3,8 +3,6 @@ import scipy import numpy as np -from pyNastran.op2.op2 import OP2 -from scipy.sparse import csc_matrix from loadskernel.io_functions import read_op2, read_op4, read_h5, read_mona from loadskernel import spline_functions, spline_rules @@ -24,12 +22,6 @@ def get_stiffness_matrix(self): elif 'filename_KGG' in self.jcl.geom and 'filename_GM' in self.jcl.geom: self.KGG = read_op4.load_matrix(self.jcl.geom['filename_KGG'], sparse_output=True, sparse_format=True) self.GM = read_op4.load_matrix(self.jcl.geom['filename_GM'], sparse_output=True, sparse_format=True) - elif 'filename_op2' in self.jcl.geom: - op2_model = OP2() - op2_model.mode = 'nasa95' - op2_model.read_op2(self.jcl.geom['filename_op2']) - self.KGG = csc_matrix(op2_model.matrices['KGG'].data) - self.GM = csc_matrix(op2_model.matrices['GM'].data).T else: logging.error('Please provide filename(s) of .h5 or .OP4 files.') @@ -39,11 +31,6 @@ def get_mass_matrix(self, i_mass): self.MGG = read_h5.load_matrix(self.jcl.mass['filename_h5'][self.i_mass], name='MGG') elif 'filename_MGG' in self.jcl.mass: self.MGG = read_op4.load_matrix(self.jcl.mass['filename_MGG'][self.i_mass], sparse_output=True, sparse_format=True) - elif 'filename_op2' in self.jcl.mass: - op2_model = OP2() - op2_model.mode = 'nasa95' - op2_model.read_op2(self.jcl.geom['filename_op2']) - self.MGG = csc_matrix(op2_model.matrices['MGG'].data) else: logging.error('Please provide filename(s) of .h5 or .OP4 files.') return self.MGG From 9a2d1a042e2ab8852b015df0d72768315c2bede5 Mon Sep 17 00:00:00 2001 From: "Chang.Xu" Date: Mon, 2 Mar 2026 16:25:16 +0100 Subject: [PATCH 17/63] added pynastran requirement in the setup file --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 996d7ad..752caa2 100644 --- a/setup.py +++ b/setup.py @@ -44,6 +44,7 @@ # Pandas 3.0.0 comes with changes that are not yet supported, # see https://github.com/DLR-AE/LoadsKernel/issues/86 'pandas<3.0.0', + 'pyNastran' ], extras_require={'extras': ['mpi4py', 'mayavi', From 6745b49b73b5091c34b8e7d0c57de86219eb30a7 Mon Sep 17 00:00:00 2001 From: "Chang.Xu" Date: Mon, 2 Mar 2026 16:57:09 +0100 Subject: [PATCH 18/63] nastran95_interface.py added --- .../fem_interfaces/nastran95_interface.py | 343 ++++++++++++++++++ loadskernel/model.py | 16 +- 2 files changed, 352 insertions(+), 7 deletions(-) create mode 100644 loadskernel/fem_interfaces/nastran95_interface.py diff --git a/loadskernel/fem_interfaces/nastran95_interface.py b/loadskernel/fem_interfaces/nastran95_interface.py new file mode 100644 index 0000000..6cef613 --- /dev/null +++ b/loadskernel/fem_interfaces/nastran95_interface.py @@ -0,0 +1,343 @@ +import copy +import logging + +import scipy +import numpy as np +from pyNastran.op2.op2 import OP2 +from scipy.sparse import csc_matrix + +from loadskernel.io_functions import read_op2, read_mona +from loadskernel import spline_functions, spline_rules + + +class Nastran95Interface(object): + + def __init__(self, jcl, strcgrid, coord): + self.jcl = jcl + self.strcgrid = strcgrid + self.coord = coord + + def get_stiffness_matrix(self): + if 'filename_op2' in self.jcl.geom: + op2_model = OP2() + try: + op2_model.read_op2(self.jcl.geom['filename_op2']) + except Exception as e: + print("Warning: an error occurred during OP2 read but was ignored.") + self.KGG = csc_matrix(op2_model.matrices['KGG'].data) + self.GM = csc_matrix(op2_model.matrices['GM'].data).T + else: + logging.error('Please provide filename(s) of .h5 or .OP4 files.') + + def get_mass_matrix(self, i_mass): + self.i_mass = i_mass + if 'filename_op2' in self.jcl.mass: + op2_model = OP2() + try: + op2_model.read_op2(self.jcl.geom['filename_op2']) + except Exception as e: + print("Warning: an error occurred during OP2 read but was ignored.") + self.MGG = csc_matrix(op2_model.matrices['MGG'].data) + else: + logging.error('Please provide filename(s) of .h5 or .OP4 files.') + return self.MGG + + def get_sets_from_bitposes(self, x_dec): + """ + Reference: + National Aeronautics and Space Administration, The Nastran Programmer's Manual, NASA SP-223(01). Washington, D.C., + COSMIC, 1972. + Section 2.3.13.3 USET (TABLE), page 2.3-61 + Assumption: There is (only) the f-, s- & m-set + + Bit positons | decimal notation | old sets | new set + ------------------------------------------------------------- + 31, 30 and 25 | 2, 4 and 128 | 'S', 'O' and 'A' | g-set + 22 | 1024 | 'SB' | s-set + 32 | 1 | 'M' | m-set + """ + + logging.info('Extracting bit positions from USET to determine DoFs') + # The DoFs of f-, s- and m-set are indexed with respect to g-set + self.pos_m = [i for i, x in enumerate(x_dec) if x == 1] + self.pos_f = [i for i, x in enumerate(x_dec) if x in [2, 4, 128]] + # Not (yet) sure if this is a bug, but in some models there are IDs labled with bit positions 22 and 24, + # resulting in a decimal notation of 1280. So far, they were treated like the s-set. + self.pos_s = [i for i, x in enumerate(x_dec) if x in [1024, 1280]] + # The n-set is the sum of s-set and f-set + self.pos_n = self.pos_s + self.pos_f + # Sort the n-set by the DoFs + sorting = np.argsort(self.pos_n) + self.pos_n = [self.pos_n[i] for i in sorting] + # Free DoFs (f-set) indexed with respect to n-set + self.pos_fn = list(np.where(sorting >= len(self.pos_s))[0]) + + def get_dofs(self): + # Prepare some data required for modal analysis which is not mass case dependent. + # The uset is actually geometry dependent and should go into the geometry section. + # However, it is only required for modal analysis... + logging.info('Read USET from OP2-file {} ...'.format(self.jcl.geom['filename_uset'])) + op2_data = read_op2.read_post_op2(self.jcl.geom['filename_uset'], verbose=False) + if op2_data['uset'] is None: + logging.error('No USET found in OP2-file {} !'.format(self.jcl.geom['filename_uset'])) + self.get_sets_from_bitposes(op2_data['uset']) + + def prepare_stiffness_matrices(self): + logging.info('Prepare stiffness matrices for independent and free DoFs (f-set)') + # K_Gnn = K_G(n,n) + K_G(n,m)*Gm + Gm.'* K_G(n,m).' + Gm.'* K_G(m,m)*Gm; + Knn = self.KGG[self.pos_n, :][:, self.pos_n] \ + + self.KGG[self.pos_n, :][:, self.pos_m].dot(self.GM.T) \ + + self.GM.dot(self.KGG[self.pos_m, :][:, self.pos_n]) \ + + self.GM.dot(self.KGG[self.pos_m, :][:, self.pos_m].dot(self.GM.T)) + self.KFF = Knn[self.pos_fn, :][:, self.pos_fn] + + def get_aset(self, bdf_reader): + logging.info('Preparing a- and o-set...') + asets = read_mona.add_SET1(bdf_reader.cards['ASET1']) + # Simplification: take only those GRIDs where all 6 components belong to the a-set + aset_values = asets['values'][asets['ID'].index(123456)] + # Then, take the set of the a-set because IDs might be given repeatedly + aset_grids = np.unique(aset_values) + # Convert to ndarray and then use list comprehension. This is the fastest way of finding indices. + gset_grids = np.array(self.strcgrid['ID']) + id_g2a = [np.where(gset_grids == x)[0][0] for x in aset_grids] + pos_a = self.strcgrid['set'][id_g2a, :].reshape((1, -1))[0] + # Make sure DoFs of a-set are really in f-set (e.g. due to faulty user input) + self.pos_a = pos_a[np.in1d(pos_a, self.pos_f)] + # The remainders will be omitted + self.pos_o = np.setdiff1d(self.pos_f, self.pos_a) + # Convert to ndarray and then use list comprehension. This is the fastest way of finding indices. + pos_f_ndarray = np.array(self.pos_f) + self.pos_f2a = [np.where(pos_f_ndarray == x)[0][0] for x in self.pos_a] + self.pos_f2o = [np.where(pos_f_ndarray == x)[0][0] for x in self.pos_o] + logging.info('The a-set has {} DoFs and the o-set has {} DoFs'.format(len(self.pos_a), len(self.pos_o))) + + def prepare_stiffness_matrices_for_guyan(self): + # In a first step, the positions of the a- and o-set DoFs are prepared. + # Then the equations are solved for the stiffness matrix. + # References: + # R. Guyan, "Reduction of Stiffness and Mass Matrices," AIAA Journal, vol. 3, no. 2, p. 280, 1964. + # MSC.Software Corporation, "Matrix Operations," in MSC Nastran Linear Static Analysis User's Guide, vol. 2003, + # D. M. McLean, Ed. 2003, p. 473. + # MSC.Software Corporation, "Theoretical Basis for Reduction Methods," in MSC.Nastran Version 70 Advanced Dynamic + # Analysis User's Guide, Version 70., H. David N., Ed. p. 63. + + logging.info("Guyan reduction of stiffness matrix Kff --> Kaa ...") + logging.info(' - partitioning') + K = {} + K['A'] = self.KFF[self.pos_f2a, :][:, self.pos_f2a] + K['B'] = self.KFF[self.pos_f2a, :][:, self.pos_f2o] + K['B_trans'] = self.KFF[self.pos_f2o, :][:, self.pos_f2a] + K['C'] = self.KFF[self.pos_f2o, :][:, self.pos_f2o] + # nach Nastran + # Anstelle die Inverse zu bilden, wird ein Gleichungssystem geloest. Das ist schneller! + logging.info(" - solving") + self.Goa = -scipy.sparse.linalg.spsolve(K['C'], K['B_trans']) + self.Goa = self.Goa.toarray() # Sparse format is no longer required as Goa is dense anyway! + self.Kaa = K['A'].toarray() + K['B'].dot(self.Goa) # make sure the output is an ndarray + + def prepare_mass_matrices(self): + logging.info('Prepare mass matrices for independent and free DoFs (f-set)') + Mnn = self.MGG[self.pos_n, :][:, self.pos_n] \ + + self.MGG[self.pos_n, :][:, self.pos_m].dot(self.GM.T) \ + + self.GM.dot(self.MGG[self.pos_m, :][:, self.pos_n]) \ + + self.GM.dot(self.MGG[self.pos_m, :][:, self.pos_m].dot(self.GM.T)) + self.MFF = Mnn[self.pos_fn, :][:, self.pos_fn] + + def guyanreduction(self): + # First, Guyan's equations are solved for the current mass matrix. + # In a second step, the eigenvalue-eigenvector problem is solved. According to Guyan, the solution is closely but + # not exactly preserved. + # Next, the eigenvector for the g-set/strcgrid is reconstructed. + # In a final step, the generalized mass and stiffness matrices are calculated. + # The nomenclature might be a little confusing at this point, because the flexible mode shapes and Nastran's free + # DoFs (f-set) are both labeled with the subscript 'f'... + logging.info("Guyan reduction of mass matrix Mff --> Maa ...") + logging.info(' - partitioning') + M = {} + M['A'] = self.MFF[self.pos_f2a, :][:, self.pos_f2a] + M['B'] = self.MFF[self.pos_f2a, :][:, self.pos_f2o] + M['B_trans'] = self.MFF[self.pos_f2o, :][:, self.pos_f2a] + M['C'] = self.MFF[self.pos_f2o, :][:, self.pos_f2o] + logging.info(" - solving") + # a) original formulation according to R. Guyan + # Maa = M['A'] - M['B'].dot(self.Goa) - self.Goa.T.dot( M['B_trans'] - M['C'].dot(self.Goa) ) + + # b) General Dynamic Reduction as implemented in Nastran (signs are switched!) + Maa = M['A'].toarray() + M['B'].dot(self.Goa) + self.Goa.T.dot(M['B_trans'].toarray() + M['C'].dot(self.Goa)) + + modes_selection = copy.deepcopy(self.jcl.mass['modes'][self.i_mass]) + if self.jcl.mass['omit_rb_modes']: + modes_selection += 6 + eigenvalue, eigenvector = self.calc_elastic_modes(self.Kaa, Maa, modes_selection.max()) + logging.info('From these {} modes, the following {} modes are selected: {}'.format( + modes_selection.max(), len(modes_selection), modes_selection)) + self.eigenvalues_f = eigenvalue[modes_selection - 1] + # reconstruct modal matrix for g-set / strcgrid + self.PHIstrc_f = np.zeros((6 * self.strcgrid['n'], len(modes_selection))) + i = 0 # counter selected modes + for i_mode in modes_selection - 1: + # deformation of a-set due to i_mode is the ith column of the eigenvector + Ua = eigenvector[:, i_mode].real.reshape((-1, 1)) + Uf = self.project_aset_to_fset(Ua) + Ug = self.project_fset_to_gset(Uf) + # store vector in modal matrix + self.PHIstrc_f[:, i] = Ug.squeeze() + i += 1 + return + + def project_aset_to_fset(self, Ua): + # calc ommitted grids with Guyan + Uo = self.Goa.dot(Ua) + # assemble deflections of a and o to f + Uf = np.zeros((len(self.pos_f), 1)) + Uf[self.pos_f2a] = Ua + Uf[self.pos_f2o] = Uo + return Uf + + def project_fset_to_gset(self, Uf): + # deflection of s-set is zero, because that's the sense of an SPC ... + Us = np.zeros((len(self.pos_s), 1)) + # assemble deflections of s and f to n + Un = np.zeros((6 * self.strcgrid['n'], 1)) + Un[self.pos_f] = Uf + Un[self.pos_s] = Us + Un = Un[self.pos_n] + # calc remaining deflections with GM + Um = self.GM.T.dot(Un) + # assemble everything to Ug + Ug = np.zeros((6 * self.strcgrid['n'], 1)) + Ug[self.pos_f] = Uf + Ug[self.pos_s] = Us + Ug[self.pos_m] = Um + return Ug + + def modalanalysis(self): + modes_selection = copy.deepcopy(self.jcl.mass['modes'][self.i_mass]) + if self.jcl.mass['omit_rb_modes']: + modes_selection += 6 + eigenvalue, eigenvector = self.calc_elastic_modes(self.KFF, self.MFF, modes_selection.max()) + logging.info('From these {} modes, the following {} modes are selected: {}'.format( + modes_selection.max(), len(modes_selection), modes_selection)) + self.eigenvalues_f = eigenvalue[modes_selection - 1] + # reconstruct modal matrix for g-set / strcgrid + self.PHIstrc_f = np.zeros((6 * self.strcgrid['n'], len(modes_selection))) + i = 0 # counter selected modes + for i_mode in modes_selection - 1: + # deformation of f-set due to i_mode is the ith column of the eigenvector + Uf = eigenvector[:, i_mode].real.reshape((-1, 1)) + Ug = self.project_fset_to_gset(Uf) + # store vector in modal matrix + self.PHIstrc_f[:, i] = Ug.squeeze() + i += 1 + return + + def calc_modal_matrices(self): + # calc modal mass and stiffness + logging.info('Working on f-set') + Mff = self.PHIstrc_f.T.dot(self.MGG.dot(self.PHIstrc_f)) + Kff = self.PHIstrc_f.T.dot(self.KGG.dot(self.PHIstrc_f)) + Dff = self.calc_damping(self.eigenvalues_f) + + logging.info('Working on h-set') + # add synthetic modes for rigid body motion + eigenvalues_rb, PHIb_strc = self.calc_rbm_modes() + PHIstrc_h = np.concatenate((PHIb_strc, self.PHIstrc_f), axis=1) + Mhh = PHIstrc_h.T.dot(self.MGG.dot(PHIstrc_h)) + # switch signs of off-diagonal terms in rb mass matrix + # Mhh[:6, :6] = self.Mb + Khh = PHIstrc_h.T.dot(self.KGG.dot(PHIstrc_h)) + # set rigid body stiffness explicitly to zero + Khh[np.diag_indices(len(eigenvalues_rb))] = eigenvalues_rb + Dhh = self.calc_damping(np.concatenate((eigenvalues_rb, self.eigenvalues_f))) + return Mff, Kff, Dff, self.PHIstrc_f.T, Mhh, Khh, Dhh, PHIstrc_h.T + + def calc_elastic_modes(self, K, M, n_modes): + # perform modal analysis on a-set + logging.info('Modal analysis for first {} modes...'.format(n_modes)) + eigenvalue, eigenvector = scipy.sparse.linalg.eigs(A=K, M=M, k=n_modes, sigma=0) + idx_sort = np.argsort(eigenvalue) # sort result by eigenvalue + eigenvalue = eigenvalue[idx_sort] + eigenvector = eigenvector[:, idx_sort] + frequencies = np.real(eigenvalue) ** 0.5 / 2 / np.pi + logging.info('Found {} modes with the following frequencies [Hz]:'.format(len(eigenvalue))) + logging.info(frequencies) + n_rbm_estimate = np.sum(np.isnan(frequencies) + np.less(frequencies, 0.1)) + if all([n_rbm_estimate < 6, self.jcl.mass['omit_rb_modes']]): + logging.warning('There are only {} modes < 0.1 Hz! Is the number of rigid body modes correct ??'.format( + n_rbm_estimate)) + return eigenvalue.real, eigenvector.real + + def calc_rbm_modes(self): + eigenvalues = np.zeros(5) + rules = spline_rules.rules_point(self.cggrid, self.strcgrid) + PHIstrc_cg = spline_functions.spline_rb(self.cggrid, '', self.strcgrid, '', rules, self.coord) + return eigenvalues, PHIstrc_cg[:, 1:] + + def calc_damping(self, eigenvalues): + # Currently, only modal damping is implemented. See Bianchi et al., + # "Using modal damping for full model transient analysis. Application to + # pantograph/catenary vibration", presented at the ISMA, 2010. + n = len(eigenvalues) + Dff = np.zeros((n, n)) + if hasattr(self.jcl, 'damping') and 'method' in self.jcl.damping and self.jcl.damping['method'] == 'modal': + logging.info('Damping: modal damping of {}'.format(self.jcl.damping['damping'])) + d = eigenvalues ** 0.5 * 2.0 * self.jcl.damping['damping'] + np.fill_diagonal(Dff, d) # matrix Dff is modified in-place, no return value + elif hasattr(self.jcl, 'damping'): + logging.warning('Damping method not implemented: {}'.format(str(self.jcl.damping['method']))) + logging.warning('Damping: assuming zero damping.') + else: + logging.info('Damping: assuming zero damping.') + return Dff + + def calc_cg(self): + logging.info('Calculate center of gravity, mass and inertia (GRDPNT)...') + # First step: calculate M0 + m0grid = {"ID": np.array([999999]), + "offset": np.array([[0, 0, 0]]), + "set": np.array([[0, 1, 2, 3, 4, 5]]), + 'CD': np.array([0]), + 'CP': np.array([0]), + } + rules = spline_rules.rules_point(m0grid, self.strcgrid) + PHIstrc_m0 = spline_functions.spline_rb(m0grid, '', self.strcgrid, '', rules, self.coord) + m0 = PHIstrc_m0.T.dot(self.MGG.dot(PHIstrc_m0)) + m = m0[0, 0] + # Second step: calculate CG location with the help of the inertia moments + offset_cg = [m0[1, 5], m0[2, 3], m0[0, 4]] / m + + cggrid = {"ID": np.array([9000 + self.i_mass]), + "offset": np.array([offset_cg]), + "set": np.array([[0, 1, 2, 3, 4, 5]]), + 'CD': np.array([0]), + 'CP': np.array([0]), + 'coord_desc': 'bodyfixed', + } + cggrid_norm = {"ID": np.array([9300 + self.i_mass]), + "offset": np.array([[-offset_cg[0], offset_cg[1], -offset_cg[2]]]), + "set": np.array([[0, 1, 2, 3, 4, 5]]), + 'CD': np.array([9300]), + 'CP': np.array([9300]), + 'coord_desc': 'bodyfixed_DIN9300', + } + # Third step: calculate Mb + rules = spline_rules.rules_point(cggrid, self.strcgrid) + PHIstrc_mb = spline_functions.spline_rb(cggrid, '', self.strcgrid, '', rules, self.coord) + mb = PHIstrc_mb.T.dot(self.MGG.dot(PHIstrc_mb)) + # switch signs for coupling terms of I to suite EoMs + Mb = np.zeros((6, 6)) + Mb[0, 0] = m + Mb[1, 1] = m + Mb[2, 2] = m + Mb[3:6, 3:6] = np.array([[1, -1, -1], [-1, 1, -1], [-1, -1, 1]]) * mb[3:6, 3:6] + + logging.info('Mass: {}'.format(Mb[0, 0])) + logging.info('CG at: {}'.format(offset_cg)) + logging.info('Inertia: \n{}'.format(Mb[3:6, 3:6])) + + # store for later internal use + self.cggrid = cggrid + self.cggrid_norm = cggrid_norm + + return Mb, cggrid, cggrid_norm diff --git a/loadskernel/model.py b/loadskernel/model.py index 52f8a56..23328cf 100644 --- a/loadskernel/model.py +++ b/loadskernel/model.py @@ -9,7 +9,7 @@ from panelaero import VLM, DLM -from loadskernel.fem_interfaces import nastran_interface, nastran_f06_interface, cofe_interface, b2000_interface +from loadskernel.fem_interfaces import nastran_interface, nastran_f06_interface, cofe_interface, b2000_interface, nastran95_interface from loadskernel import build_aero_functions from loadskernel import spline_rules from loadskernel import spline_functions @@ -62,7 +62,7 @@ def build_coord(self): def build_strc(self): logging.info('Building structural model...') - if self.jcl.geom['method'] == 'mona': + if self.jcl.geom['method'] in ['mona', 'Nastran95']: # parse given bdf files self.bdf_reader.process_deck(self.jcl.geom['filename_grid']) # assemble strcgrid, sort grids to be in accordance with matricies such as Mgg from Nastran @@ -106,7 +106,7 @@ def build_strcshell(self): self.bdf_reader.cards['CTRIA3']], ignore_index=True)) def build_mongrid(self): - if self.jcl.geom['method'] in ['mona', 'CoFE']: + if self.jcl.geom['method'] in ['mona', 'CoFE', 'Nastran95']: if 'filename_mongrid' in self.jcl.geom and not self.jcl.geom['filename_mongrid'] == '': logging.info('Building Monitoring Stations from GRID data...') self.mongrid = read_mona.Modgen_GRID(self.jcl.geom['filename_mongrid']) @@ -531,7 +531,7 @@ def build_cfdgrid(self): def build_structural_dynamics(self): logging.info('Building stiffness and mass model...') self.mass = {} - if self.jcl.mass['method'] in ['mona', 'f06', 'modalanalysis', 'guyan', 'CoFE', 'B2000']: + if self.jcl.mass['method'] in ['mona', 'f06', 'modalanalysis', 'guyan', 'CoFE', 'B2000', 'Nastran95']: # select the fem interface if self.jcl.mass['method'] in ['modalanalysis', 'guyan']: @@ -542,6 +542,8 @@ def build_structural_dynamics(self): fem_interface = b2000_interface.B2000Interface(self.jcl, self.strcgrid, self.coord) elif self.jcl.mass['method'] in ['CoFE']: fem_interface = cofe_interface.CoFEInterface(self.jcl, self.strcgrid, self.coord) + elif self.jcl.mass['method'] in ['Nastran95']: + fem_interface = nastran95_interface.Nastran95Interface(self.jcl, self.strcgrid, self.coord) # the stiffness matrix is needed for all methods / fem interfaces fem_interface.get_stiffness_matrix() @@ -552,7 +554,7 @@ def build_structural_dynamics(self): logging.warning('Stiffness matrix Kgg is NOT symmetric.') # do further processing of the stiffness matrix - if self.jcl.mass['method'] in ['modalanalysis', 'guyan', 'CoFE', 'B2000']: + if self.jcl.mass['method'] in ['modalanalysis', 'guyan', 'CoFE', 'B2000', 'Nastran95']: fem_interface.get_dofs() fem_interface.prepare_stiffness_matrices() if self.jcl.mass['method'] in ['guyan']: @@ -577,10 +579,10 @@ def build_mass_matrices(self, fem_interface, i_mass): logging.warning('Mass matrix Mgg is NOT symmetric.') # getting the eigenvalues and -vectors depends on the method / fem solver - if self.jcl.mass['method'] in ['modalanalysis', 'guyan', 'CoFE', 'B2000']: + if self.jcl.mass['method'] in ['modalanalysis', 'guyan', 'CoFE', 'B2000', 'Nastran95']: Mb, cggrid, cggrid_norm = fem_interface.calc_cg() fem_interface.prepare_mass_matrices() - if self.jcl.mass['method'] in ['modalanalysis', 'CoFE', 'B2000']: + if self.jcl.mass['method'] in ['modalanalysis', 'CoFE', 'B2000', 'Nastran95']: fem_interface.modalanalysis() elif self.jcl.mass['method'] in ['guyan']: fem_interface.guyanreduction() From 731ab87048149ee598c6618ee299542465f45c0c Mon Sep 17 00:00:00 2001 From: "Chang.Xu" Date: Mon, 2 Mar 2026 16:59:20 +0100 Subject: [PATCH 19/63] minor twick --- .../fem_interfaces/nastran95_interface.py | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/loadskernel/fem_interfaces/nastran95_interface.py b/loadskernel/fem_interfaces/nastran95_interface.py index 6cef613..3af7057 100644 --- a/loadskernel/fem_interfaces/nastran95_interface.py +++ b/loadskernel/fem_interfaces/nastran95_interface.py @@ -18,28 +18,24 @@ def __init__(self, jcl, strcgrid, coord): self.coord = coord def get_stiffness_matrix(self): - if 'filename_op2' in self.jcl.geom: - op2_model = OP2() - try: - op2_model.read_op2(self.jcl.geom['filename_op2']) - except Exception as e: - print("Warning: an error occurred during OP2 read but was ignored.") - self.KGG = csc_matrix(op2_model.matrices['KGG'].data) - self.GM = csc_matrix(op2_model.matrices['GM'].data).T - else: - logging.error('Please provide filename(s) of .h5 or .OP4 files.') + op2_model = OP2() + try: + op2_model.read_op2(self.jcl.geom['filename_op2']) + except Exception as e: + print("Warning: an error occurred during OP2 read but was ignored.") + self.KGG = csc_matrix(op2_model.matrices['KGG'].data) + self.GM = csc_matrix(op2_model.matrices['GM'].data).T def get_mass_matrix(self, i_mass): self.i_mass = i_mass - if 'filename_op2' in self.jcl.mass: - op2_model = OP2() - try: - op2_model.read_op2(self.jcl.geom['filename_op2']) - except Exception as e: - print("Warning: an error occurred during OP2 read but was ignored.") - self.MGG = csc_matrix(op2_model.matrices['MGG'].data) - else: - logging.error('Please provide filename(s) of .h5 or .OP4 files.') + + op2_model = OP2() + try: + op2_model.read_op2(self.jcl.geom['filename_op2']) + except Exception as e: + print("Warning: an error occurred during OP2 read but was ignored.") + self.MGG = csc_matrix(op2_model.matrices['MGG'].data) + return self.MGG def get_sets_from_bitposes(self, x_dec): From f4bf9dcb1b876b333e2ba54080752eb7b6c93f98 Mon Sep 17 00:00:00 2001 From: "Chang.Xu" Date: Mon, 2 Mar 2026 18:15:51 +0100 Subject: [PATCH 20/63] minor twick --- .../fem_interfaces/nastran95_interface.py | 324 +----------------- 1 file changed, 10 insertions(+), 314 deletions(-) diff --git a/loadskernel/fem_interfaces/nastran95_interface.py b/loadskernel/fem_interfaces/nastran95_interface.py index 3af7057..c8eb7cb 100644 --- a/loadskernel/fem_interfaces/nastran95_interface.py +++ b/loadskernel/fem_interfaces/nastran95_interface.py @@ -1,28 +1,24 @@ -import copy -import logging - -import scipy -import numpy as np from pyNastran.op2.op2 import OP2 from scipy.sparse import csc_matrix -from loadskernel.io_functions import read_op2, read_mona -from loadskernel import spline_functions, spline_rules +from loadskernel.fem_interfaces.nastran_interface import NastranInterface -class Nastran95Interface(object): +class Nastran95Interface(NastranInterface): - def __init__(self, jcl, strcgrid, coord): - self.jcl = jcl - self.strcgrid = strcgrid - self.coord = coord + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self.KGG: csc_matrix = csc_matrix((0, 0)) + self.GM: csc_matrix = csc_matrix((0, 0)) + self.MGG: csc_matrix = csc_matrix((0, 0)) + self.i_mass: int = 0 def get_stiffness_matrix(self): op2_model = OP2() try: op2_model.read_op2(self.jcl.geom['filename_op2']) except Exception as e: - print("Warning: an error occurred during OP2 read but was ignored.") + print(f"Warning: an error occurred during OP2 read but was ignored.\n{e}") self.KGG = csc_matrix(op2_model.matrices['KGG'].data) self.GM = csc_matrix(op2_model.matrices['GM'].data).T @@ -33,307 +29,7 @@ def get_mass_matrix(self, i_mass): try: op2_model.read_op2(self.jcl.geom['filename_op2']) except Exception as e: - print("Warning: an error occurred during OP2 read but was ignored.") + print(f"Warning: an error occurred during OP2 read but was ignored.\n{e}") self.MGG = csc_matrix(op2_model.matrices['MGG'].data) return self.MGG - - def get_sets_from_bitposes(self, x_dec): - """ - Reference: - National Aeronautics and Space Administration, The Nastran Programmer's Manual, NASA SP-223(01). Washington, D.C., - COSMIC, 1972. - Section 2.3.13.3 USET (TABLE), page 2.3-61 - Assumption: There is (only) the f-, s- & m-set - - Bit positons | decimal notation | old sets | new set - ------------------------------------------------------------- - 31, 30 and 25 | 2, 4 and 128 | 'S', 'O' and 'A' | g-set - 22 | 1024 | 'SB' | s-set - 32 | 1 | 'M' | m-set - """ - - logging.info('Extracting bit positions from USET to determine DoFs') - # The DoFs of f-, s- and m-set are indexed with respect to g-set - self.pos_m = [i for i, x in enumerate(x_dec) if x == 1] - self.pos_f = [i for i, x in enumerate(x_dec) if x in [2, 4, 128]] - # Not (yet) sure if this is a bug, but in some models there are IDs labled with bit positions 22 and 24, - # resulting in a decimal notation of 1280. So far, they were treated like the s-set. - self.pos_s = [i for i, x in enumerate(x_dec) if x in [1024, 1280]] - # The n-set is the sum of s-set and f-set - self.pos_n = self.pos_s + self.pos_f - # Sort the n-set by the DoFs - sorting = np.argsort(self.pos_n) - self.pos_n = [self.pos_n[i] for i in sorting] - # Free DoFs (f-set) indexed with respect to n-set - self.pos_fn = list(np.where(sorting >= len(self.pos_s))[0]) - - def get_dofs(self): - # Prepare some data required for modal analysis which is not mass case dependent. - # The uset is actually geometry dependent and should go into the geometry section. - # However, it is only required for modal analysis... - logging.info('Read USET from OP2-file {} ...'.format(self.jcl.geom['filename_uset'])) - op2_data = read_op2.read_post_op2(self.jcl.geom['filename_uset'], verbose=False) - if op2_data['uset'] is None: - logging.error('No USET found in OP2-file {} !'.format(self.jcl.geom['filename_uset'])) - self.get_sets_from_bitposes(op2_data['uset']) - - def prepare_stiffness_matrices(self): - logging.info('Prepare stiffness matrices for independent and free DoFs (f-set)') - # K_Gnn = K_G(n,n) + K_G(n,m)*Gm + Gm.'* K_G(n,m).' + Gm.'* K_G(m,m)*Gm; - Knn = self.KGG[self.pos_n, :][:, self.pos_n] \ - + self.KGG[self.pos_n, :][:, self.pos_m].dot(self.GM.T) \ - + self.GM.dot(self.KGG[self.pos_m, :][:, self.pos_n]) \ - + self.GM.dot(self.KGG[self.pos_m, :][:, self.pos_m].dot(self.GM.T)) - self.KFF = Knn[self.pos_fn, :][:, self.pos_fn] - - def get_aset(self, bdf_reader): - logging.info('Preparing a- and o-set...') - asets = read_mona.add_SET1(bdf_reader.cards['ASET1']) - # Simplification: take only those GRIDs where all 6 components belong to the a-set - aset_values = asets['values'][asets['ID'].index(123456)] - # Then, take the set of the a-set because IDs might be given repeatedly - aset_grids = np.unique(aset_values) - # Convert to ndarray and then use list comprehension. This is the fastest way of finding indices. - gset_grids = np.array(self.strcgrid['ID']) - id_g2a = [np.where(gset_grids == x)[0][0] for x in aset_grids] - pos_a = self.strcgrid['set'][id_g2a, :].reshape((1, -1))[0] - # Make sure DoFs of a-set are really in f-set (e.g. due to faulty user input) - self.pos_a = pos_a[np.in1d(pos_a, self.pos_f)] - # The remainders will be omitted - self.pos_o = np.setdiff1d(self.pos_f, self.pos_a) - # Convert to ndarray and then use list comprehension. This is the fastest way of finding indices. - pos_f_ndarray = np.array(self.pos_f) - self.pos_f2a = [np.where(pos_f_ndarray == x)[0][0] for x in self.pos_a] - self.pos_f2o = [np.where(pos_f_ndarray == x)[0][0] for x in self.pos_o] - logging.info('The a-set has {} DoFs and the o-set has {} DoFs'.format(len(self.pos_a), len(self.pos_o))) - - def prepare_stiffness_matrices_for_guyan(self): - # In a first step, the positions of the a- and o-set DoFs are prepared. - # Then the equations are solved for the stiffness matrix. - # References: - # R. Guyan, "Reduction of Stiffness and Mass Matrices," AIAA Journal, vol. 3, no. 2, p. 280, 1964. - # MSC.Software Corporation, "Matrix Operations," in MSC Nastran Linear Static Analysis User's Guide, vol. 2003, - # D. M. McLean, Ed. 2003, p. 473. - # MSC.Software Corporation, "Theoretical Basis for Reduction Methods," in MSC.Nastran Version 70 Advanced Dynamic - # Analysis User's Guide, Version 70., H. David N., Ed. p. 63. - - logging.info("Guyan reduction of stiffness matrix Kff --> Kaa ...") - logging.info(' - partitioning') - K = {} - K['A'] = self.KFF[self.pos_f2a, :][:, self.pos_f2a] - K['B'] = self.KFF[self.pos_f2a, :][:, self.pos_f2o] - K['B_trans'] = self.KFF[self.pos_f2o, :][:, self.pos_f2a] - K['C'] = self.KFF[self.pos_f2o, :][:, self.pos_f2o] - # nach Nastran - # Anstelle die Inverse zu bilden, wird ein Gleichungssystem geloest. Das ist schneller! - logging.info(" - solving") - self.Goa = -scipy.sparse.linalg.spsolve(K['C'], K['B_trans']) - self.Goa = self.Goa.toarray() # Sparse format is no longer required as Goa is dense anyway! - self.Kaa = K['A'].toarray() + K['B'].dot(self.Goa) # make sure the output is an ndarray - - def prepare_mass_matrices(self): - logging.info('Prepare mass matrices for independent and free DoFs (f-set)') - Mnn = self.MGG[self.pos_n, :][:, self.pos_n] \ - + self.MGG[self.pos_n, :][:, self.pos_m].dot(self.GM.T) \ - + self.GM.dot(self.MGG[self.pos_m, :][:, self.pos_n]) \ - + self.GM.dot(self.MGG[self.pos_m, :][:, self.pos_m].dot(self.GM.T)) - self.MFF = Mnn[self.pos_fn, :][:, self.pos_fn] - - def guyanreduction(self): - # First, Guyan's equations are solved for the current mass matrix. - # In a second step, the eigenvalue-eigenvector problem is solved. According to Guyan, the solution is closely but - # not exactly preserved. - # Next, the eigenvector for the g-set/strcgrid is reconstructed. - # In a final step, the generalized mass and stiffness matrices are calculated. - # The nomenclature might be a little confusing at this point, because the flexible mode shapes and Nastran's free - # DoFs (f-set) are both labeled with the subscript 'f'... - logging.info("Guyan reduction of mass matrix Mff --> Maa ...") - logging.info(' - partitioning') - M = {} - M['A'] = self.MFF[self.pos_f2a, :][:, self.pos_f2a] - M['B'] = self.MFF[self.pos_f2a, :][:, self.pos_f2o] - M['B_trans'] = self.MFF[self.pos_f2o, :][:, self.pos_f2a] - M['C'] = self.MFF[self.pos_f2o, :][:, self.pos_f2o] - logging.info(" - solving") - # a) original formulation according to R. Guyan - # Maa = M['A'] - M['B'].dot(self.Goa) - self.Goa.T.dot( M['B_trans'] - M['C'].dot(self.Goa) ) - - # b) General Dynamic Reduction as implemented in Nastran (signs are switched!) - Maa = M['A'].toarray() + M['B'].dot(self.Goa) + self.Goa.T.dot(M['B_trans'].toarray() + M['C'].dot(self.Goa)) - - modes_selection = copy.deepcopy(self.jcl.mass['modes'][self.i_mass]) - if self.jcl.mass['omit_rb_modes']: - modes_selection += 6 - eigenvalue, eigenvector = self.calc_elastic_modes(self.Kaa, Maa, modes_selection.max()) - logging.info('From these {} modes, the following {} modes are selected: {}'.format( - modes_selection.max(), len(modes_selection), modes_selection)) - self.eigenvalues_f = eigenvalue[modes_selection - 1] - # reconstruct modal matrix for g-set / strcgrid - self.PHIstrc_f = np.zeros((6 * self.strcgrid['n'], len(modes_selection))) - i = 0 # counter selected modes - for i_mode in modes_selection - 1: - # deformation of a-set due to i_mode is the ith column of the eigenvector - Ua = eigenvector[:, i_mode].real.reshape((-1, 1)) - Uf = self.project_aset_to_fset(Ua) - Ug = self.project_fset_to_gset(Uf) - # store vector in modal matrix - self.PHIstrc_f[:, i] = Ug.squeeze() - i += 1 - return - - def project_aset_to_fset(self, Ua): - # calc ommitted grids with Guyan - Uo = self.Goa.dot(Ua) - # assemble deflections of a and o to f - Uf = np.zeros((len(self.pos_f), 1)) - Uf[self.pos_f2a] = Ua - Uf[self.pos_f2o] = Uo - return Uf - - def project_fset_to_gset(self, Uf): - # deflection of s-set is zero, because that's the sense of an SPC ... - Us = np.zeros((len(self.pos_s), 1)) - # assemble deflections of s and f to n - Un = np.zeros((6 * self.strcgrid['n'], 1)) - Un[self.pos_f] = Uf - Un[self.pos_s] = Us - Un = Un[self.pos_n] - # calc remaining deflections with GM - Um = self.GM.T.dot(Un) - # assemble everything to Ug - Ug = np.zeros((6 * self.strcgrid['n'], 1)) - Ug[self.pos_f] = Uf - Ug[self.pos_s] = Us - Ug[self.pos_m] = Um - return Ug - - def modalanalysis(self): - modes_selection = copy.deepcopy(self.jcl.mass['modes'][self.i_mass]) - if self.jcl.mass['omit_rb_modes']: - modes_selection += 6 - eigenvalue, eigenvector = self.calc_elastic_modes(self.KFF, self.MFF, modes_selection.max()) - logging.info('From these {} modes, the following {} modes are selected: {}'.format( - modes_selection.max(), len(modes_selection), modes_selection)) - self.eigenvalues_f = eigenvalue[modes_selection - 1] - # reconstruct modal matrix for g-set / strcgrid - self.PHIstrc_f = np.zeros((6 * self.strcgrid['n'], len(modes_selection))) - i = 0 # counter selected modes - for i_mode in modes_selection - 1: - # deformation of f-set due to i_mode is the ith column of the eigenvector - Uf = eigenvector[:, i_mode].real.reshape((-1, 1)) - Ug = self.project_fset_to_gset(Uf) - # store vector in modal matrix - self.PHIstrc_f[:, i] = Ug.squeeze() - i += 1 - return - - def calc_modal_matrices(self): - # calc modal mass and stiffness - logging.info('Working on f-set') - Mff = self.PHIstrc_f.T.dot(self.MGG.dot(self.PHIstrc_f)) - Kff = self.PHIstrc_f.T.dot(self.KGG.dot(self.PHIstrc_f)) - Dff = self.calc_damping(self.eigenvalues_f) - - logging.info('Working on h-set') - # add synthetic modes for rigid body motion - eigenvalues_rb, PHIb_strc = self.calc_rbm_modes() - PHIstrc_h = np.concatenate((PHIb_strc, self.PHIstrc_f), axis=1) - Mhh = PHIstrc_h.T.dot(self.MGG.dot(PHIstrc_h)) - # switch signs of off-diagonal terms in rb mass matrix - # Mhh[:6, :6] = self.Mb - Khh = PHIstrc_h.T.dot(self.KGG.dot(PHIstrc_h)) - # set rigid body stiffness explicitly to zero - Khh[np.diag_indices(len(eigenvalues_rb))] = eigenvalues_rb - Dhh = self.calc_damping(np.concatenate((eigenvalues_rb, self.eigenvalues_f))) - return Mff, Kff, Dff, self.PHIstrc_f.T, Mhh, Khh, Dhh, PHIstrc_h.T - - def calc_elastic_modes(self, K, M, n_modes): - # perform modal analysis on a-set - logging.info('Modal analysis for first {} modes...'.format(n_modes)) - eigenvalue, eigenvector = scipy.sparse.linalg.eigs(A=K, M=M, k=n_modes, sigma=0) - idx_sort = np.argsort(eigenvalue) # sort result by eigenvalue - eigenvalue = eigenvalue[idx_sort] - eigenvector = eigenvector[:, idx_sort] - frequencies = np.real(eigenvalue) ** 0.5 / 2 / np.pi - logging.info('Found {} modes with the following frequencies [Hz]:'.format(len(eigenvalue))) - logging.info(frequencies) - n_rbm_estimate = np.sum(np.isnan(frequencies) + np.less(frequencies, 0.1)) - if all([n_rbm_estimate < 6, self.jcl.mass['omit_rb_modes']]): - logging.warning('There are only {} modes < 0.1 Hz! Is the number of rigid body modes correct ??'.format( - n_rbm_estimate)) - return eigenvalue.real, eigenvector.real - - def calc_rbm_modes(self): - eigenvalues = np.zeros(5) - rules = spline_rules.rules_point(self.cggrid, self.strcgrid) - PHIstrc_cg = spline_functions.spline_rb(self.cggrid, '', self.strcgrid, '', rules, self.coord) - return eigenvalues, PHIstrc_cg[:, 1:] - - def calc_damping(self, eigenvalues): - # Currently, only modal damping is implemented. See Bianchi et al., - # "Using modal damping for full model transient analysis. Application to - # pantograph/catenary vibration", presented at the ISMA, 2010. - n = len(eigenvalues) - Dff = np.zeros((n, n)) - if hasattr(self.jcl, 'damping') and 'method' in self.jcl.damping and self.jcl.damping['method'] == 'modal': - logging.info('Damping: modal damping of {}'.format(self.jcl.damping['damping'])) - d = eigenvalues ** 0.5 * 2.0 * self.jcl.damping['damping'] - np.fill_diagonal(Dff, d) # matrix Dff is modified in-place, no return value - elif hasattr(self.jcl, 'damping'): - logging.warning('Damping method not implemented: {}'.format(str(self.jcl.damping['method']))) - logging.warning('Damping: assuming zero damping.') - else: - logging.info('Damping: assuming zero damping.') - return Dff - - def calc_cg(self): - logging.info('Calculate center of gravity, mass and inertia (GRDPNT)...') - # First step: calculate M0 - m0grid = {"ID": np.array([999999]), - "offset": np.array([[0, 0, 0]]), - "set": np.array([[0, 1, 2, 3, 4, 5]]), - 'CD': np.array([0]), - 'CP': np.array([0]), - } - rules = spline_rules.rules_point(m0grid, self.strcgrid) - PHIstrc_m0 = spline_functions.spline_rb(m0grid, '', self.strcgrid, '', rules, self.coord) - m0 = PHIstrc_m0.T.dot(self.MGG.dot(PHIstrc_m0)) - m = m0[0, 0] - # Second step: calculate CG location with the help of the inertia moments - offset_cg = [m0[1, 5], m0[2, 3], m0[0, 4]] / m - - cggrid = {"ID": np.array([9000 + self.i_mass]), - "offset": np.array([offset_cg]), - "set": np.array([[0, 1, 2, 3, 4, 5]]), - 'CD': np.array([0]), - 'CP': np.array([0]), - 'coord_desc': 'bodyfixed', - } - cggrid_norm = {"ID": np.array([9300 + self.i_mass]), - "offset": np.array([[-offset_cg[0], offset_cg[1], -offset_cg[2]]]), - "set": np.array([[0, 1, 2, 3, 4, 5]]), - 'CD': np.array([9300]), - 'CP': np.array([9300]), - 'coord_desc': 'bodyfixed_DIN9300', - } - # Third step: calculate Mb - rules = spline_rules.rules_point(cggrid, self.strcgrid) - PHIstrc_mb = spline_functions.spline_rb(cggrid, '', self.strcgrid, '', rules, self.coord) - mb = PHIstrc_mb.T.dot(self.MGG.dot(PHIstrc_mb)) - # switch signs for coupling terms of I to suite EoMs - Mb = np.zeros((6, 6)) - Mb[0, 0] = m - Mb[1, 1] = m - Mb[2, 2] = m - Mb[3:6, 3:6] = np.array([[1, -1, -1], [-1, 1, -1], [-1, -1, 1]]) * mb[3:6, 3:6] - - logging.info('Mass: {}'.format(Mb[0, 0])) - logging.info('CG at: {}'.format(offset_cg)) - logging.info('Inertia: \n{}'.format(Mb[3:6, 3:6])) - - # store for later internal use - self.cggrid = cggrid - self.cggrid_norm = cggrid_norm - - return Mb, cggrid, cggrid_norm From bfd9ad6e7028f08f48cfb8a5e0e642814bbe6f61 Mon Sep 17 00:00:00 2001 From: "Chang.Xu" Date: Wed, 4 Mar 2026 16:55:26 +0100 Subject: [PATCH 21/63] avoid a line to be too long --- loadskernel/model.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/loadskernel/model.py b/loadskernel/model.py index 23328cf..957bcad 100644 --- a/loadskernel/model.py +++ b/loadskernel/model.py @@ -9,7 +9,8 @@ from panelaero import VLM, DLM -from loadskernel.fem_interfaces import nastran_interface, nastran_f06_interface, cofe_interface, b2000_interface, nastran95_interface +from loadskernel.fem_interfaces import (nastran_interface, nastran_f06_interface, cofe_interface, b2000_interface, + nastran95_interface) from loadskernel import build_aero_functions from loadskernel import spline_rules from loadskernel import spline_functions From 0a9eb561f3423da30efd177b8b516ce5cc94d9bd Mon Sep 17 00:00:00 2001 From: "Chang.Xu" Date: Wed, 4 Mar 2026 16:55:34 +0100 Subject: [PATCH 22/63] fix bug --- loadskernel/io_functions/read_op2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loadskernel/io_functions/read_op2.py b/loadskernel/io_functions/read_op2.py index e6d0a53..141402b 100644 --- a/loadskernel/io_functions/read_op2.py +++ b/loadskernel/io_functions/read_op2.py @@ -523,7 +523,7 @@ def read_op2_record(self, form=None, N=0): key = self._get_key() self._skip_key(2) # TODO: IDK why does this work for Nast95 generated op2 file ? - if max(data) > 1: + if max(data) > 3: print("[warning]: .op2 file from Nast95 detected, converting the data to binary") data[data < 20] = 1 data[data > 20] = 2 From db4cda151db7823ffbf8663a2c3af2823fb51c9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Wed, 4 Mar 2026 17:07:46 +0100 Subject: [PATCH 23/63] Move pyNastran package to extras --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 752caa2..3659a3b 100644 --- a/setup.py +++ b/setup.py @@ -43,8 +43,7 @@ 'pyyaml', # Pandas 3.0.0 comes with changes that are not yet supported, # see https://github.com/DLR-AE/LoadsKernel/issues/86 - 'pandas<3.0.0', - 'pyNastran' + 'pandas<3.0.0' ], extras_require={'extras': ['mpi4py', 'mayavi', @@ -53,7 +52,8 @@ 'jupyter', 'pyiges', # only available with pip, not with conda 'pyfmi', - 'pyside6' + 'pyside6', + 'pyNastran' # only available with pip, not with conda ], 'test': ['pytest', 'pytest-cov', From 0f27adeb2c3c6024a08e10e146665d00f8e9f300 Mon Sep 17 00:00:00 2001 From: "Chang.Xu" Date: Wed, 4 Mar 2026 17:30:39 +0100 Subject: [PATCH 24/63] changed to logging message print --- loadskernel/fem_interfaces/nastran95_interface.py | 9 +++++++-- loadskernel/io_functions/read_op2.py | 3 ++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/loadskernel/fem_interfaces/nastran95_interface.py b/loadskernel/fem_interfaces/nastran95_interface.py index c8eb7cb..a0576da 100644 --- a/loadskernel/fem_interfaces/nastran95_interface.py +++ b/loadskernel/fem_interfaces/nastran95_interface.py @@ -1,6 +1,11 @@ +# Built-ins +import logging + +# Libs from pyNastran.op2.op2 import OP2 from scipy.sparse import csc_matrix +# Own modules from loadskernel.fem_interfaces.nastran_interface import NastranInterface @@ -18,7 +23,7 @@ def get_stiffness_matrix(self): try: op2_model.read_op2(self.jcl.geom['filename_op2']) except Exception as e: - print(f"Warning: an error occurred during OP2 read but was ignored.\n{e}") + logging.warning(f"An error occurred during OP2 read but was ignored.\n{e}") self.KGG = csc_matrix(op2_model.matrices['KGG'].data) self.GM = csc_matrix(op2_model.matrices['GM'].data).T @@ -29,7 +34,7 @@ def get_mass_matrix(self, i_mass): try: op2_model.read_op2(self.jcl.geom['filename_op2']) except Exception as e: - print(f"Warning: an error occurred during OP2 read but was ignored.\n{e}") + logging.warning(f"An error occurred during OP2 read but was ignored.\n{e}") self.MGG = csc_matrix(op2_model.matrices['MGG'].data) return self.MGG diff --git a/loadskernel/io_functions/read_op2.py b/loadskernel/io_functions/read_op2.py index 141402b..400618b 100644 --- a/loadskernel/io_functions/read_op2.py +++ b/loadskernel/io_functions/read_op2.py @@ -34,6 +34,7 @@ import sys import struct +import logging import numpy as np # Notes on the op2 format. @@ -524,7 +525,7 @@ def read_op2_record(self, form=None, N=0): self._skip_key(2) # TODO: IDK why does this work for Nast95 generated op2 file ? if max(data) > 3: - print("[warning]: .op2 file from Nast95 detected, converting the data to binary") + logging.warning(".op2 file from Nast95 detected, converting the data to binary") data[data < 20] = 1 data[data > 20] = 2 return data From 7fe5e6d0751d269871a06761e5ddd9abb08d51df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Thu, 5 Mar 2026 10:26:30 +0100 Subject: [PATCH 25/63] Add try/except staement for pyNastran Import --- .../fem_interfaces/nastran95_interface.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/loadskernel/fem_interfaces/nastran95_interface.py b/loadskernel/fem_interfaces/nastran95_interface.py index a0576da..b8aa741 100644 --- a/loadskernel/fem_interfaces/nastran95_interface.py +++ b/loadskernel/fem_interfaces/nastran95_interface.py @@ -1,13 +1,21 @@ # Built-ins import logging +import sys # Libs -from pyNastran.op2.op2 import OP2 from scipy.sparse import csc_matrix # Own modules from loadskernel.fem_interfaces.nastran_interface import NastranInterface +# In case a user has only the core packages and no extras installed, we want to avoid an import error when importing the +# Nastran95Interface class. The OP2 read is only performed when the stiffness or mass matrix is requested, so the error +# is not relevant until then. In that case, we catch the error and issue an error message. +try: + from pyNastran.op2.op2 import OP2 +except ImportError: + pass + class Nastran95Interface(NastranInterface): @@ -17,6 +25,13 @@ def __init__(self, *args, **kwargs) -> None: self.GM: csc_matrix = csc_matrix((0, 0)) self.MGG: csc_matrix = csc_matrix((0, 0)) self.i_mass: int = 0 + # Check if OP2 from pyNastran was imported successfully, see try/except statement in the import section. + if "pyNastran" not in sys.modules: + logging.error( + 'pyNastran was/could NOT be imported!' + 'The Nastran95Interface will not be able to read the stiffness and mass matrices from the OP2 file. ' + 'Please install pyNastran or the Loads Kernel with the extras to use this feature.' + ) def get_stiffness_matrix(self): op2_model = OP2() From c12b3b2b4d7b673bdf50988e944b66f093b2f387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Thu, 5 Mar 2026 15:03:27 +0100 Subject: [PATCH 26/63] Require numpy > 2.0 and < 2.4.0 --- setup.py | 6 +++--- tests/list_of_packages.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 3659a3b..4e65196 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ python_requires='>=3.12', install_requires=['PanelAero', 'matplotlib', - 'numpy<2.4.0', # Mayavi / VTK does not support numpy >= 2.4.0, wait for release of VTK 9.6 + 'numpy>2.0,<2.4.0', # Mayavi / VTK does not support numpy >= 2.4.0, wait for release of VTK 9.6 'scipy', 'h5py', 'tables', @@ -49,10 +49,10 @@ 'mayavi', 'traits', 'traitsui', + 'pyside6', 'jupyter', - 'pyiges', # only available with pip, not with conda 'pyfmi', - 'pyside6', + 'pyiges', # only available with pip, not with conda 'pyNastran' # only available with pip, not with conda ], 'test': ['pytest', diff --git a/tests/list_of_packages.txt b/tests/list_of_packages.txt index 723abaf..b1d0bbd 100644 --- a/tests/list_of_packages.txt +++ b/tests/list_of_packages.txt @@ -3,7 +3,7 @@ # conda install -y -c conda-forge --file ./tests/list_of_packages.txt # Lodas Kernel core dependencies matplotlib -numpy<2.4.0 +numpy>2.0,<2.4.0 scipy h5py pytables From 712f647482243a1c3853cbcbbb109dc1b81422c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Thu, 5 Mar 2026 15:21:07 +0100 Subject: [PATCH 27/63] Apply pylint coding style suggestion --- loadskernel/fem_interfaces/nastran95_interface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/loadskernel/fem_interfaces/nastran95_interface.py b/loadskernel/fem_interfaces/nastran95_interface.py index b8aa741..ad51427 100644 --- a/loadskernel/fem_interfaces/nastran95_interface.py +++ b/loadskernel/fem_interfaces/nastran95_interface.py @@ -38,7 +38,7 @@ def get_stiffness_matrix(self): try: op2_model.read_op2(self.jcl.geom['filename_op2']) except Exception as e: - logging.warning(f"An error occurred during OP2 read but was ignored.\n{e}") + logging.warning("An error occurred during OP2 read but was ignored.\n%s", e) self.KGG = csc_matrix(op2_model.matrices['KGG'].data) self.GM = csc_matrix(op2_model.matrices['GM'].data).T @@ -49,7 +49,7 @@ def get_mass_matrix(self, i_mass): try: op2_model.read_op2(self.jcl.geom['filename_op2']) except Exception as e: - logging.warning(f"An error occurred during OP2 read but was ignored.\n{e}") + logging.warning("An error occurred during OP2 read but was ignored.\n%s", e) self.MGG = csc_matrix(op2_model.matrices['MGG'].data) return self.MGG From 3a9f9536ff7a28a773c78da7682b91a238497a6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Thu, 5 Mar 2026 15:32:18 +0100 Subject: [PATCH 28/63] Apply flake8 formatting style suggestion --- tests/test_unittests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_unittests.py b/tests/test_unittests.py index 7b18841..24c3d16 100644 --- a/tests/test_unittests.py +++ b/tests/test_unittests.py @@ -72,6 +72,7 @@ def test_hyperbolic_distance_metric(): # Check for numerical similarity with reference values. assert np.allclose(HDM, HDM_ref, rtol=1e-4, atol=1e-4), "Hyperbolic distance metric (HDM) does NOT match reference" + def test_reynoldsnumber(): # Test Reynolds number calculation at sea level. # This test also covers the dynamic viscosity calculation and parts of the isa atmosphere. From 11e3800ef6b9c666ff98069f0431d77bd869445d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Mon, 9 Mar 2026 17:39:35 +0100 Subject: [PATCH 29/63] Added functionality to read matrices to OP2 reader, which I removed previously --- .../fem_interfaces/nastran_interface.py | 6 +- loadskernel/io_functions/read_op2.py | 109 ++++++++++++------ 2 files changed, 74 insertions(+), 41 deletions(-) diff --git a/loadskernel/fem_interfaces/nastran_interface.py b/loadskernel/fem_interfaces/nastran_interface.py index f9fd0f2..92cabdf 100644 --- a/loadskernel/fem_interfaces/nastran_interface.py +++ b/loadskernel/fem_interfaces/nastran_interface.py @@ -69,10 +69,10 @@ def get_dofs(self): # Prepare some data required for modal analysis which is not mass case dependent. # The uset is actually geometry dependent and should go into the geometry section. # However, it is only required for modal analysis... - logging.info('Read USET from OP2-file {} ...'.format(self.jcl.geom['filename_uset'])) - op2_data = read_op2.read_post_op2(self.jcl.geom['filename_uset'], verbose=False) + logging.info('Read USET from OP2-file %s ...', self.jcl.geom['filename_uset']) + op2_data = read_op2.read_op2(self.jcl.geom['filename_uset']) if op2_data['uset'] is None: - logging.error('No USET found in OP2-file {} !'.format(self.jcl.geom['filename_uset'])) + logging.error('No USET found in OP2-file %s !', self.jcl.geom['filename_uset']) self.get_sets_from_bitposes(op2_data['uset']) def prepare_stiffness_matrices(self): diff --git a/loadskernel/io_functions/read_op2.py b/loadskernel/io_functions/read_op2.py index 400618b..ca29a08 100644 --- a/loadskernel/io_functions/read_op2.py +++ b/loadskernel/io_functions/read_op2.py @@ -310,6 +310,54 @@ def _read_op2_name_trailer(self): rec_type = self._get_key() return db_name, trailer, rec_type + def read_op2_matrix(self, name, trailer): + """ + Read and return Nastran op2 matrix at current file position. + + It is assumed that the name has already been read in via + :func:`_read_op2_name_trailer`. + + The size of the matrix is read from trailer: + nrows = trailer[2] + ncols = trailer[1] + """ + dtype = 1 + nrows = trailer[2] + ncols = trailer[1] + # print(' %s (%s, %s)' % (name, nrows, ncols)) + matrix = np.zeros((nrows, ncols), order='F') + if self._bit64: + intsize = 8 + else: + intsize = 4 + col = 0 + frm = self._endian + '%dd' + # print('frm =', frm) + while dtype > 0: # read in matrix columns + # key is number of elements in next record (row # followed + # by key-1 real numbers) + key = self._get_key() + # read column + while key > 0: + reclen = self._Str4.unpack(self._fileh.read(4))[0] + r = self._Str.unpack(self._fileh.read(self._ibytes))[0] - 1 + n = (reclen - intsize) // 8 + if n < self._rowsCutoff: + matrix[r:r + n, col] = struct.unpack( + frm % n, self._fileh.read(n * 8)) + else: + matrix[r:r + n, col] = np.fromfile( + self._fileh, np.float64, n) + self._fileh.read(4) # endrec + key = self._get_key() + col += 1 + self._get_key() + dtype = self._get_key() + self._read_op2_end_of_table() + if self._swap: + matrix = matrix.byteswap() + return matrix + def skip_op2_matrix(self, trailer): """ Skip Nastran op2 matrix at current position. @@ -523,11 +571,6 @@ def read_op2_record(self, form=None, N=0): f.read(4) # endrec key = self._get_key() self._skip_key(2) - # TODO: IDK why does this work for Nast95 generated op2 file ? - if max(data) > 3: - logging.warning(".op2 file from Nast95 detected, converting the data to binary") - data[data < 20] = 1 - data[data > 20] = 2 return data def skip_op2_record(self): @@ -541,7 +584,7 @@ def skip_op2_record(self): key = self._get_key() self._skip_key(2) - def _read_op2_uset(self): + def read_op2_uset(self): """ Read the USET data block. @@ -556,51 +599,41 @@ def _read_op2_uset(self): if any(sset): uset[sset] = uset[sset] & ~2 self._read_op2_end_of_table() + # We don't know why, but the USET exported from Nastran 95 is not zeros and ones but 17 and another large number + # (e.g. 496 or 1074, possibly depending on the operating system). + if max(uset) > 3: + logging.warning("USET from Nastran 95 detected, converting the data to binary") + uset[uset < 20] = 1 + uset[uset > 20] = 2 return uset -def read_post_op2(op2_filename, verbose=False): +def read_op2(op2_filename): """ - Reads PARAM,POST,-1 op2 file and returns dictionary of data. - - Parameters - ---------- - op2_filename : string - Name of op2 file. - verbose : bool - If true, echo names of tables and matrices to screen - - Returns dictionary with following members - ----------------------------------------- - 'uset' : array + Reads op2 file and returns dictionary of data. """ - # read op2 file: + # Open op2 file with OP2(op2_filename) as o2: - uset = None + data = {} o2._fileh.seek(o2._postheaderpos) while 1: name, trailer, dbtype = o2._read_op2_name_trailer() - # print('name = %r' % name) - # print('trailer = %s' % str(trailer)) - # print('dbtype = %r' % dbtype) + # Condition to stop iterating if name is None: break + # Catch error condition if name == '': - raise RuntimeError('name=%r' % name) + raise RuntimeError('name={name}') + # Read matrices, typically stiffness and mass matrices such as Kgg, Mgg and GM if dbtype > 0: - if verbose: - print("Skipping matrix {0}...".format(name)) - o2.skip_op2_matrix(trailer) + logging.info("Reading matrix %s...", name) + data[name] = o2.read_op2_matrix(name, trailer) + # Read USET table but skip other tables + elif name.find('USET') == 0: + logging.info("Reading table %s...", name) + data['uset'] = o2.read_op2_uset() else: - if name.find('USET') == 0: - if verbose: - print("Reading USET table {0}...".format(name)) - uset = o2._read_op2_uset() - continue - else: - if verbose: - print("Skipping table %r..." % name) + logging.info("Skipping table %s...", name) o2.skip_op2_table() - - return {'uset': uset} + return data From 2dd5dbe5552f7032ac438a6cd301a6857e7e7f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Mon, 16 Mar 2026 14:01:15 +0100 Subject: [PATCH 30/63] Removed 'Nastran95' from geom section, added check fro numerical symmetry --- loadskernel/fem_interfaces/fem_helper.py | 7 +++++++ loadskernel/model.py | 17 ++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/loadskernel/fem_interfaces/fem_helper.py b/loadskernel/fem_interfaces/fem_helper.py index ba51e52..0378e77 100644 --- a/loadskernel/fem_interfaces/fem_helper.py +++ b/loadskernel/fem_interfaces/fem_helper.py @@ -89,3 +89,10 @@ def check_matrix_symmetry(matrix): matrix_sym = force_matrix_symmetry(matrix) result = (matrix != matrix_sym).nnz == 0 return result + + +def check_matrix_symmetry_allclose(matrix): + # Check if a matrix is symmetric by forcing symmetry and then compare with the original matrix. + matrix_sym = force_matrix_symmetry(matrix) + result = np.allclose(matrix.toarray(), matrix_sym.toarray()) + return result diff --git a/loadskernel/model.py b/loadskernel/model.py index 957bcad..38bcc4c 100644 --- a/loadskernel/model.py +++ b/loadskernel/model.py @@ -63,7 +63,7 @@ def build_coord(self): def build_strc(self): logging.info('Building structural model...') - if self.jcl.geom['method'] in ['mona', 'Nastran95']: + if self.jcl.geom['method'] in ['mona']: # parse given bdf files self.bdf_reader.process_deck(self.jcl.geom['filename_grid']) # assemble strcgrid, sort grids to be in accordance with matricies such as Mgg from Nastran @@ -107,7 +107,7 @@ def build_strcshell(self): self.bdf_reader.cards['CTRIA3']], ignore_index=True)) def build_mongrid(self): - if self.jcl.geom['method'] in ['mona', 'CoFE', 'Nastran95']: + if self.jcl.geom['method'] in ['mona']: if 'filename_mongrid' in self.jcl.geom and not self.jcl.geom['filename_mongrid'] == '': logging.info('Building Monitoring Stations from GRID data...') self.mongrid = read_mona.Modgen_GRID(self.jcl.geom['filename_mongrid']) @@ -552,7 +552,11 @@ def build_structural_dynamics(self): self.KGG = fem_interface.KGG # Check if matrix is symmetric if not fem_helper.check_matrix_symmetry(self.KGG): - logging.warning('Stiffness matrix Kgg is NOT symmetric.') + if fem_helper.check_matrix_symmetry_allclose(self.KGG): + logging.warning('Stiffness matrix Kgg is only symmetric within numerical tolerances.') + else: + logging.warning('Stiffness matrix Kgg is NOT symmetric. \ + This may lead to problems in the modal analysis and should be checked carefully.') # do further processing of the stiffness matrix if self.jcl.mass['method'] in ['modalanalysis', 'guyan', 'CoFE', 'B2000', 'Nastran95']: @@ -577,8 +581,11 @@ def build_mass_matrices(self, fem_interface, i_mass): MGG = fem_interface.get_mass_matrix(i_mass) # Check if matrix is symmetric if not fem_helper.check_matrix_symmetry(MGG): - logging.warning('Mass matrix Mgg is NOT symmetric.') - + if fem_helper.check_matrix_symmetry_allclose(MGG): + logging.warning('Mass matrix Mgg is only symmetric within numerical tolerances.') + else: + logging.warning('Mass matrix Mgg is NOT symmetric. \ + This may lead to problems in the modal analysis and should be checked carefully.') # getting the eigenvalues and -vectors depends on the method / fem solver if self.jcl.mass['method'] in ['modalanalysis', 'guyan', 'CoFE', 'B2000', 'Nastran95']: Mb, cggrid, cggrid_norm = fem_interface.calc_cg() From 0030ffa132eaa04352ce8f2872c365cf28caf67c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Mon, 16 Mar 2026 14:15:09 +0100 Subject: [PATCH 31/63] Switch to internal op2 reader --- .../fem_interfaces/nastran95_interface.py | 52 ++++++++----------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/loadskernel/fem_interfaces/nastran95_interface.py b/loadskernel/fem_interfaces/nastran95_interface.py index ad51427..1f91098 100644 --- a/loadskernel/fem_interfaces/nastran95_interface.py +++ b/loadskernel/fem_interfaces/nastran95_interface.py @@ -1,20 +1,12 @@ # Built-ins import logging -import sys # Libs from scipy.sparse import csc_matrix # Own modules from loadskernel.fem_interfaces.nastran_interface import NastranInterface - -# In case a user has only the core packages and no extras installed, we want to avoid an import error when importing the -# Nastran95Interface class. The OP2 read is only performed when the stiffness or mass matrix is requested, so the error -# is not relevant until then. In that case, we catch the error and issue an error message. -try: - from pyNastran.op2.op2 import OP2 -except ImportError: - pass +from loadskernel.io_functions import read_op2 class Nastran95Interface(NastranInterface): @@ -25,31 +17,31 @@ def __init__(self, *args, **kwargs) -> None: self.GM: csc_matrix = csc_matrix((0, 0)) self.MGG: csc_matrix = csc_matrix((0, 0)) self.i_mass: int = 0 - # Check if OP2 from pyNastran was imported successfully, see try/except statement in the import section. - if "pyNastran" not in sys.modules: - logging.error( - 'pyNastran was/could NOT be imported!' - 'The Nastran95Interface will not be able to read the stiffness and mass matrices from the OP2 file. ' - 'Please install pyNastran or the Loads Kernel with the extras to use this feature.' - ) + self.op2_geom = None def get_stiffness_matrix(self): - op2_model = OP2() - try: - op2_model.read_op2(self.jcl.geom['filename_op2']) - except Exception as e: - logging.warning("An error occurred during OP2 read but was ignored.\n%s", e) - self.KGG = csc_matrix(op2_model.matrices['KGG'].data) - self.GM = csc_matrix(op2_model.matrices['GM'].data).T + # Use internal op2 reader. The matrices are numerically eqivalent compared with pyNastran's op2 reader (tested with the + # DC3 model using np.allclose). + if 'filename_op2' in self.jcl.geom: + self.op2_geom = read_op2.read_op2(self.jcl.geom['filename_op2']) + # Convert to sparse foramt. + self.KGG = csc_matrix(self.op2_geom['KGG']) + self.GM = csc_matrix(self.op2_geom['GM'].T) + else: + logging.error('Please provide filename of .op2 file containing matrices Kgg and GM.') def get_mass_matrix(self, i_mass): self.i_mass = i_mass - - op2_model = OP2() - try: - op2_model.read_op2(self.jcl.geom['filename_op2']) - except Exception as e: - logging.warning("An error occurred during OP2 read but was ignored.\n%s", e) - self.MGG = csc_matrix(op2_model.matrices['MGG'].data) + if 'filename_op2' in self.jcl.mass: + op2_mass = read_op2.read_op2(self.jcl.mass['filename_op2'][self.i_mass]) + self.MGG = csc_matrix(op2_mass['MGG']) + else: + logging.error('Please provide filename(s) of .op2 files containing the matrices Mgg.') return self.MGG + + def get_dofs(self): + # See if a USET table was included in the OP2 file from the geom setion (along with the stiffness matrix). + if self.op2_geom['uset'] is None: + logging.error('No USET found in OP2-file %s !', self.jcl.geom['filename_op2']) + self.get_sets_from_bitposes(self.op2_geom['uset']) From 0150053a923a2a2c7348dc84f377982b4e8b571d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Mon, 16 Mar 2026 14:15:42 +0100 Subject: [PATCH 32/63] Make op2 reader less talkative --- loadskernel/io_functions/read_op2.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/loadskernel/io_functions/read_op2.py b/loadskernel/io_functions/read_op2.py index ca29a08..d57e8fa 100644 --- a/loadskernel/io_functions/read_op2.py +++ b/loadskernel/io_functions/read_op2.py @@ -201,7 +201,7 @@ def _op2_open(self, filename): self._int32stru = self._endian + '%di' self._read_op2_header() self._postheaderpos = self._fileh.tell() - self.directory(verbose=True) + self.directory(verbose=False) def _get_key(self): """Reads [reclen, key, endrec] triplet and returns key.""" @@ -602,7 +602,7 @@ def read_op2_uset(self): # We don't know why, but the USET exported from Nastran 95 is not zeros and ones but 17 and another large number # (e.g. 496 or 1074, possibly depending on the operating system). if max(uset) > 3: - logging.warning("USET from Nastran 95 detected, converting the data to binary") + logging.info("USET from Nastran 95 detected, attempt conversion of data to binary") uset[uset < 20] = 1 uset[uset > 20] = 2 return uset @@ -627,13 +627,13 @@ def read_op2(op2_filename): raise RuntimeError('name={name}') # Read matrices, typically stiffness and mass matrices such as Kgg, Mgg and GM if dbtype > 0: - logging.info("Reading matrix %s...", name) + logging.debug("Reading matrix %s...", name) data[name] = o2.read_op2_matrix(name, trailer) # Read USET table but skip other tables elif name.find('USET') == 0: - logging.info("Reading table %s...", name) + logging.debug("Reading table %s...", name) data['uset'] = o2.read_op2_uset() else: - logging.info("Skipping table %s...", name) + logging.debug("Skipping table %s...", name) o2.skip_op2_table() return data From 5e28d86d95dfdc83abb216553750cd9fe9390cf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Mon, 16 Mar 2026 14:16:07 +0100 Subject: [PATCH 33/63] Add example for Nastran 95 DMAP --- scripts/DMAPforNastran95.alter | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 scripts/DMAPforNastran95.alter diff --git a/scripts/DMAPforNastran95.alter b/scripts/DMAPforNastran95.alter new file mode 100644 index 0000000..37d36c2 --- /dev/null +++ b/scripts/DMAPforNastran95.alter @@ -0,0 +1,29 @@ +ID My,Aircraft +$ The modal anaylsis is called SOL 3 in Nastran 95. +SOL 3 +$ Print the DMAP code and insert the output command after line 52. +$ I haven't found an option so specify a filename of the op2 file, +$ in my case the resulting file was named 'none'. So remember to keep +$ track of your filenames when working with multiple mass configurations. +DIAG 14,18 +ALTER 52 +OUTPUT2 KGG,MGG,GM,USET,//0/11////*MSC* $ +ENDALTER +CEND +$ +METHOD=100 +ECHO=NONE +$ +BEGIN BULK +PARAM GRDPNT 0 +$ Calculate all modes between 0.1 and 15.0 Hz using the modified Givens method. +$ Because we only want the system matrices, the modal analysis is (stricly speaking) +$ not necessary but allows to compare eigenvalues calculated in Loads Kernel with +$ those found by Nastran 95 as a cross-check. +$------><------><------><------><------><------><------><------><------> +EIGR 100 MGIV 0.1 15.0 +$ +$ Insert structural model below here (include statements are not supported by Nastran 95). +$ +$ Explicitly terminate file. +ENDDATA \ No newline at end of file From ac8a71dc2e4102e8eb7c3f112351dc70ba264e40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Mon, 16 Mar 2026 14:16:52 +0100 Subject: [PATCH 34/63] Expland explanations in jcl_template --- doc/jcl_template.py | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/doc/jcl_template.py b/doc/jcl_template.py index b1e6a9a..b7b07b7 100755 --- a/doc/jcl_template.py +++ b/doc/jcl_template.py @@ -51,20 +51,25 @@ def __init__(self): # bdf file with GRID-cards, 1st file -> 1st monstation 'filename_monstations': ['monstation_MON1.bdf', 'monstation_MON2.bdf'], # The following matrices are required for some mass methods. However, the stiffness is geometry - # and not mass dependent. Overview: - # KGG via DMAP Alter (.op4 or .h5) - required for mass method = 'modalanalysis', 'guyan' or 'B2000' - # GM via DMAP Alter (.op4 or .h5) - required for mass method = 'modalanalysis', 'guyan' - # USET via DMAP Alter and OP2 - required for mass method = 'modalanalysis', 'guyan' - # bdf file(s) with ASET1-card - required for mass method = 'guyan' - # matrix R_trans from B2000 - required for mass method = 'B2000' + # and not mass dependent and is organized here for better overview. You can gerente these matrices + # with a DMAP Alter, see the scripts folder. + # Overview: + # KGG via DMAP Alter (.op4, .h5, .csv) - required for mass method Nastran or B2000 + # GM via DMAP Alter (.op4 or .h5) - required for mass method Nastran + # USET via DMAP Alter (.op2) - required for mass method Nastran + # R_trans from B2000 - required for mass method B2000 + # bdf file(s) with ASET1-card - required Guyan reduction # The HDF5 file format is preferred over OP4 due to better performance and higher precision. Because the # uset is a table, not a matrix, it can't be included in the HDF5 file and still needs to be given as OP2. 'filename_h5': 'SOL103.mtx.h5', 'filename_KGG': 'KGG.dat', 'filename_GM': 'GM.dat', 'filename_uset': 'uset.op2', - 'filename_aset': 'aset.bdf', 'filename_Rtrans': 'Rtrans.csv', + 'filename_aset': 'aset.bdf', + # If you really want to, the matrices Kgg and GM may also be given in OP2 format, e.g. when using + # Nastran 95. Note that the OP2 file is binary so that it is difficult to check its content. + 'filename_op2': 'matrices_from_Nastran95.op2', } # Settings for the aerodynamic model self.aero = {'method': 'mona_steady', @@ -147,15 +152,25 @@ def __init__(self): 'filename_splinegrid': ['splinegrid.bdf'] } # Settings for the structural dynamics. - self.mass = {'method': 'modalanalysis', # Inplemented interfaces: 'f06', 'modalanalysis', 'guyan', 'CoFE', 'B2000' + self.mass = {'method': 'modalanalysis', + # 'modalanalysis', 'guyan' - Eigenvalue / -vector analysis, optionally incl. Guyan reduction, + # based on system matrices obtained from Nastran (MSC or Siemens NX) + # 'f06' - Eigenvalues and -vectors calculated with SOL 103 and parsed from f06-file + # 'CoFE' - Matrices from Nastran Compatible Finite Elements (CoFE) + # 'B2000' - Matrices from DLR's version of B2000++ + # 'Nastran95' - Matrices from open-source NASA Structural Analysis System (Nastran 95) + # Note that the the stiffness is geometry dependent and is therfor given in the geom setion. All mass + # cases share the same geometry and stiffness matrix. 'key': ['M1', 'M2'], - # MGG via DMAP Alter and HDF5 - 'filename_h5': ['SOL103_M1.mtx.h5', 'SOL103_M2.mtx.h5'], # MGG via DMAP Alter and OP4 'filename_MGG': ['MGG_M1.dat', 'MGG_M2.dat'], + # MGG via DMAP Alter and HDF5 + 'filename_h5': ['SOL103_M1.mtx.h5', 'SOL103_M2.mtx.h5'], + # MGG via DMAP Alter and OP2 + 'filename_op2': ['MGG_M1.op2', 'MGG_M2.op2'], # eigenvalues and eigenvectors from .f06-file - required for 'mona' 'filename_S103': ['SOL103_M1.f06', 'SOL103_M1.f06'], - # eigenvalues and eigenvectors from .f06-file - required for 'mona' + # eigenvalues and eigenvectors from .mat-file - required for 'CoFe' 'filename_CoFE': ['M1.mat', 'M2.mat'], # True or False, omits first six modes 'omit_rb_modes': False, From 2428b04b7cdc54a2328849569976643ceee0754a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Mon, 16 Mar 2026 14:19:05 +0100 Subject: [PATCH 35/63] Move DMAPs into separate folder --- {scripts => doc/DMAPs}/DMAPforNastran95.alter | 0 {scripts => doc/DMAPs}/DMAPforSOL103.alter | 0 {scripts => doc/DMAPs}/DMAPforSOL103viaHDF5.alter | 0 {scripts => doc/DMAPs}/DMAPforSOL145_SOL146.alter | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename {scripts => doc/DMAPs}/DMAPforNastran95.alter (100%) rename {scripts => doc/DMAPs}/DMAPforSOL103.alter (100%) rename {scripts => doc/DMAPs}/DMAPforSOL103viaHDF5.alter (100%) rename {scripts => doc/DMAPs}/DMAPforSOL145_SOL146.alter (100%) diff --git a/scripts/DMAPforNastran95.alter b/doc/DMAPs/DMAPforNastran95.alter similarity index 100% rename from scripts/DMAPforNastran95.alter rename to doc/DMAPs/DMAPforNastran95.alter diff --git a/scripts/DMAPforSOL103.alter b/doc/DMAPs/DMAPforSOL103.alter similarity index 100% rename from scripts/DMAPforSOL103.alter rename to doc/DMAPs/DMAPforSOL103.alter diff --git a/scripts/DMAPforSOL103viaHDF5.alter b/doc/DMAPs/DMAPforSOL103viaHDF5.alter similarity index 100% rename from scripts/DMAPforSOL103viaHDF5.alter rename to doc/DMAPs/DMAPforSOL103viaHDF5.alter diff --git a/scripts/DMAPforSOL145_SOL146.alter b/doc/DMAPs/DMAPforSOL145_SOL146.alter similarity index 100% rename from scripts/DMAPforSOL145_SOL146.alter rename to doc/DMAPs/DMAPforSOL145_SOL146.alter From 75b45546bfc1855b39263bc8c90ecea46636e928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Mon, 16 Mar 2026 14:52:32 +0100 Subject: [PATCH 36/63] Add DC3 example with Nastran 95 as well as a SOL 103 for reference. Add files necessary to reproduce results. --- .../DC3_model/JCLs/jcl_dc3_Nastran95.py | 250 ++++++ doc/tutorials/DC3_model/fem/Nast95_M3.op2 | Bin 0 -> 96408 bytes .../DC3_model/fem/Nast95_structure_only.op2 | Bin 0 -> 274728 bytes .../DC3_model/fem/Nastran95/Nast95_M3.bdf | 824 ++++++++++++++++++ .../fem/Nastran95/Nast95_structure_only.bdf | 779 +++++++++++++++++ .../DC3_model/fem/NastranSOL103/SOL103_M3.bdf | 44 + .../NastranSOL103/SOL103_structure_only.bdf | 33 + .../DC3_model/fem/assembly/structure_only.bdf | 72 ++ .../crew_and_equipment-mass.csv | 15 + .../fueltanks_low_mi.SCONM2 | 46 + .../fueltanks_low_mi.SCONM2_mrd | 36 + .../fem/nonstructural_masses/payload.csv | 19 + .../nonstructural_masses/payload_modified.csv | 19 + .../fem/nonstructural_masses/systems-mass.csv | 99 +++ 14 files changed, 2236 insertions(+) create mode 100755 doc/tutorials/DC3_model/JCLs/jcl_dc3_Nastran95.py create mode 100644 doc/tutorials/DC3_model/fem/Nast95_M3.op2 create mode 100644 doc/tutorials/DC3_model/fem/Nast95_structure_only.op2 create mode 100644 doc/tutorials/DC3_model/fem/Nastran95/Nast95_M3.bdf create mode 100644 doc/tutorials/DC3_model/fem/Nastran95/Nast95_structure_only.bdf create mode 100644 doc/tutorials/DC3_model/fem/NastranSOL103/SOL103_M3.bdf create mode 100644 doc/tutorials/DC3_model/fem/NastranSOL103/SOL103_structure_only.bdf create mode 100644 doc/tutorials/DC3_model/fem/assembly/structure_only.bdf create mode 100644 doc/tutorials/DC3_model/fem/nonstructural_masses/crew_and_equipment-mass.csv create mode 100644 doc/tutorials/DC3_model/fem/nonstructural_masses/fueltanks_low_mi.SCONM2 create mode 100644 doc/tutorials/DC3_model/fem/nonstructural_masses/fueltanks_low_mi.SCONM2_mrd create mode 100644 doc/tutorials/DC3_model/fem/nonstructural_masses/payload.csv create mode 100644 doc/tutorials/DC3_model/fem/nonstructural_masses/payload_modified.csv create mode 100644 doc/tutorials/DC3_model/fem/nonstructural_masses/systems-mass.csv diff --git a/doc/tutorials/DC3_model/JCLs/jcl_dc3_Nastran95.py b/doc/tutorials/DC3_model/JCLs/jcl_dc3_Nastran95.py new file mode 100755 index 0000000..008ca9c --- /dev/null +++ b/doc/tutorials/DC3_model/JCLs/jcl_dc3_Nastran95.py @@ -0,0 +1,250 @@ +""" +Job Control File documentation +The Job Control (jcl) is a python class which defines the model and and the simulation and is imported at +the beginning of every simulation. Unlike a conventional parameter file, this allows scripting/programming +of the input, e.g. to convert units, generate mutiple load cases, etc. +Note that this documentation of parameters is comprehensive, but a) not all parameters are necessary for +every kind of simulation and b) some parameters are for experts only --> your JCL might be much smaller. +""" +import numpy as np +import os +from loadskernel.units import ft2m, tas2Ma +from loadskernel import jcl_helper +import pathlib + + +class jcl: + + def __init__(self): + + model_root = pathlib.Path(__file__).parent.parent.resolve() + + # Give your aircraft a name and set some general parameters + self.general = {'aircraft': 'DC3', + # Reference span width (from tip to tip) + 'b_ref': 29.0, + # Reference chord length + 'c_ref': 3.508, + # Reference area + 'A_ref': 91.7, + # Mean aerodynamic center, also used as moments reference point + 'MAC_ref': [8.566, 0.0, 0.0], + } + """ + The electronic flight control system (EFCS) provides the "wireing" of the pilot commands + xi, eta and zeta with the control surface deflections. This is aircraft-specific and needs + to be implemented as a python module. + """ + # Electronic flight control system + self.efcs = {'version': 'efcs_dc3', # Name of the corresponding python module + # Path where to find the EFCS module + 'path': os.path.join(model_root, 'efcs'), + } + # Read the structural geometry + self.geom = {'method': 'mona', # ModGen and/or Nastran (mona) BDFs + # bdf file(s) with GRIDs and CORDs (CORD1R and CORD2R) + 'filename_grid': [os.path.join(model_root, 'fem', 'structure_only.bdf')], + # bdf file(s) with CQUADs and CTRIAs, for visualization only, e.g. outer skin on the aircraft + # 'filename_shell': [], + # bdf file(s) with MONPNT-cards + 'filename_monpnt': os.path.join(model_root, 'fem', 'export_monitoring-stations.csv'), + # Specify the OP2 file containing the matrices Kgg, GM and the uset. + 'filename_op2': os.path.join(model_root, 'fem', 'Nast95_structure_only.op2'), + } + # Settings for the aerodynamic model + self.aero = {'method': 'mona_steady', + # 'mona_steady' - steady trim and quasi-steady time domain simulations + # 'mona_unsteady' - unsteady time domain simulation based on the RFA, e.g. for gust + # 'freq_dom' - frequency domain simulations, e.g. gust, continuous turbulence, flutter, etc + # 'nonlin_steady' - steady trim and quasi-steady time domain simulations with some non-linearities + # 'cfd_steady' - steady trim + # 'cfd_unsteady' - unsteady time domain simulation, e.g. for gust + # + # True or False, aerodynamic feedback of elastic structure on aerodynamics can be deactivated. + # You will still see deformations, but there is no coupling. + 'flex': True, + # aerogrid is given by CAERO1, CAERO7 or by CQUAD4 cards + 'method_caero': 'CAERO1', + # bdf file(s) with CAERO1 or CQUAD4-cards for aerogrid. IDs in ascending order. + 'filename_caero_bdf': [os.path.join(model_root, 'aero', 'vt', 'vt.CAERO1'), + os.path.join(model_root, 'aero', 'left-ht', 'left-ht.CAERO1'), + os.path.join(model_root, 'aero', 'right-ht', 'right-ht.CAERO1'), + os.path.join(model_root, 'aero', 'left-wing', 'left-wing.CAERO1'), + os.path.join(model_root, 'aero', 'right-wing', 'right-wing.CAERO1')], + # DMI Matrix for camber and twist correction. Same order as the aerogrid. + 'filename_DMI_W2GJ': [os.path.join(model_root, 'fem', 'w2gj_list.DMI_merge')], + # bdf file(s) with AESURF-cards + 'filename_aesurf': [os.path.join(model_root, 'aero', 'vt', 'vt.AESURF'), + os.path.join(model_root, 'aero', 'left-ht', 'left-ht.AESURF'), + os.path.join(model_root, 'aero', 'right-ht', 'right-ht.AESURF'), + os.path.join(model_root, 'aero', 'left-wing', 'left-wing.AESURF'), + os.path.join(model_root, 'aero', 'right-wing', 'right-wing.AESURF') + ], + # bdf file(s) with AELIST-cards + 'filename_aelist': [os.path.join(model_root, 'aero', 'vt', 'vt.AELIST'), + os.path.join(model_root, 'aero', 'left-ht', 'left-ht.AELIST'), + os.path.join(model_root, 'aero', 'right-ht', 'right-ht.AELIST'), + os.path.join(model_root, 'aero', 'left-wing', 'left-wing.AELIST'), + os.path.join(model_root, 'aero', 'right-wing', 'right-wing.AELIST') + ], + # The hingeline of a CS is given by a CORD. Either the y- or the z-axis is taken as hingeline. 'y', 'z' + 'hingeline': 'y', + # 'vlm' (panel-aero), 'dlm' (panel-aero) or 'nastran' (external form matrices) + 'method_AIC': 'vlm', + 'key': ['VC', 'VD'], + 'Ma': [0.27, 0.34], + } + # Set the was in which the aerodynamic forces are applied to the structure. + self.spline = {'method': 'nearest_neighbour', # Options: 'nearest_neighbour', 'rbf' or 'nastran' + # Possibility to use only a subset of the structural grid for splining. True or False + 'splinegrid': False, + # bdf file(s) with GRIDs to ne used + 'filename_splinegrid': ['splinegrid.bdf'] + } + # Settings for the structural dynamics. + self.mass = {'method': 'Nastran95', # Use new Nastran 95 interface + 'key': ['M3'], + # MGG via DMAP Alter and OP2 + 'filename_op2': [os.path.join(model_root, 'fem', 'Nast95_M3.op2')], + # True or False, omits first six modes + 'omit_rb_modes': True, + # list(s) of modes to use + 'modes': [np.arange(1, 71), np.arange(1, 71), np.arange(1, 71), np.arange(1, 71)], + } + # Modal damping can be applied as a factor of the stiffness matrix. + self.damping = {'method': 'modal', + 'damping': 0.02, + } + # The international standard atmosphere (ISA) + self.atmo = {'method': 'ISA', + 'key': ['FL000', 'FL055', 'FL075', 'FL210'], + # Altitude in meters + 'h': ft2m([0, 5500, 7500, 21000,]), + } + # Setting of the rigid body equations of motion + self.eom = {'version': 'waszak'} # 'linear' or 'waszak' + + """ + This section controls the automatic plotting and selection of dimensioning load cases. + Simply put a list of names of the monitoring stations (e.g. ['MON1', 'MON2',...]) into the dictionary + of possible load plots listed below. This will generate a pdf document and nastran force and moment + cards for the dimensioning load cases. + """ + self.loadplots = {'potatos_fz_mx': [], + 'potatos_mx_my': ['WL01', 'WL03', 'WL05', 'WL07', 'WL09', 'WL11', 'WL13', 'WL15', 'WL17', + 'WL19', 'WL21', 'WL23', 'WL25', 'WL27', 'WL29', 'WL31', 'WR31', 'WR29', + 'WR27', 'WR25', 'WR23', 'WR21', 'WR19', 'WR17', 'WR15', 'WR13', 'WR11', + 'WR09', 'WR07', 'WR05', 'WR03', 'WR01'], + 'potatos_fz_my': [], + 'potatos_fy_mx': [], + 'potatos_mx_mz': [], + 'potatos_my_mz': [], + 'cuttingforces_wing': ['WL01', 'WL03', 'WL05', 'WL07', 'WL09', 'WL11', 'WL13', 'WL15', + 'WL17', 'WL19', 'WL21', 'WL23', 'WL25', 'WL27', 'WL29', 'WL31', + 'WR31', 'WR29', 'WR27', 'WR25', 'WR23', 'WR21', 'WR19', 'WR17', + 'WR15', 'WR13', 'WR11', 'WR09', 'WR07', 'WR05', 'WR03', 'WR01'], + } + """ + The trimcase defines the maneuver load case, one dictionary per load case. + There may be hundreds or thousands of load cases, so at some point it might be beneficial to script this section or + import an excel sheet. + """ + self.trimcase = [{'desc': 'CC.M3.OVCFL000.level', # Descriptive string of the maneuver case + # Kind of trim condition, blank for trim about all three axes, for more trim conditions see + # trim_conditions.py + 'maneuver': '', + # Subcase ID number, for Nastran in acending order + 'subcase': 1, + # Setting of the operational point + # The flight speed is given by the Mach number + 'Ma': tas2Ma(70.0, 0.0), + # Aero key + 'aero': 'VC', + # Atmo key + 'altitude': 'FL000', + # Mass key + 'mass': 'M3', + # Load factor Nz + 'Nz': 1.0, + # Velocities and accelerations given in ISO 9300 coordinate system (right-handed, forward-right-down) + # Roll rate in rad/s + 'p': 0.0 / 180.0 * np.pi, + # Pitch rate in rad/s + 'q': 0.0 / 180.0 * np.pi, + # Yaw rate in rad/s + 'r': 0.0, + # Roll acceleration in rad/s^2 + 'pdot': 0.0, + # Pitch acceleration in rad/s^2 + 'qdot': 0.0, + # Yaw acceleration in rad/s^2 + 'rdot': 0.0, + }, + {'desc': 'CC.M3.OVCFL000.pushdown', # Descriptive string of the maneuver case + # Kind of trim condition, blank for trim about all three axes, for more trim conditions see + # trim_conditions.py + 'maneuver': '', + # Subcase ID number, for Nastran in acending order + 'subcase': 2, + # Setting of the operational point + # The flight speed is given by the Mach number + 'Ma': tas2Ma(70.0, 0.0), + # Aero key + 'aero': 'VC', + # Atmo key + 'altitude': 'FL000', + # Mass key + 'mass': 'M3', + # Load factor Nz + 'Nz': -1.0, + # Velocities and accelerations given in ISO 9300 coordinate system (right-handed, forward-right-down) + # Roll rate in rad/s + 'p': 0.0 / 180.0 * np.pi, + # Pitch rate in rad/s + 'q': 0.0 / 180.0 * np.pi, + # Yaw rate in rad/s + 'r': 0.0, + # Roll acceleration in rad/s^2 + 'pdot': 0.0, + # Pitch acceleration in rad/s^2 + 'qdot': 0.0, + # Yaw acceleration in rad/s^2 + 'rdot': 0.0, + }, + {'desc': 'CC.M3.OVCFL000.pullup', # Descriptive string of the maneuver case + # Kind of trim condition, blank for trim about all three axes, for more trim conditions see + # trim_conditions.py + 'maneuver': '', + # Subcase ID number, for Nastran in acending order + 'subcase': 3, + # Setting of the operational point + # The flight speed is given by the Mach number + 'Ma': tas2Ma(70.0, 0.0), + # Aero key + 'aero': 'VC', + # Atmo key + 'altitude': 'FL000', + # Mass key + 'mass': 'M3', + # Load factor Nz + 'Nz': 2.5, + # Velocities and accelerations given in ISO 9300 coordinate system (right-handed, forward-right-down) + # Roll rate in rad/s + 'p': 0.0, + # Pitch rate in rad/s + 'q': 0.0, + # Yaw rate in rad/s + 'r': 0.0, + # Roll acceleration in rad/s^2 + 'pdot': 0.0, + # Pitch acceleration in rad/s^2 + 'qdot': 0.0, + # Yaw acceleration in rad/s^2 + 'rdot': 0.0, + }] + """ + For every trimcase, a corresponding simcase is required. For maneuvers, it may be empty self.simcase = [{}]. + A time simulation is triggered if the simcase contains at least 'dt' and 't_final' + """ + self.simcase = jcl_helper.generate_empty_listofdicts(self.trimcase) + # End diff --git a/doc/tutorials/DC3_model/fem/Nast95_M3.op2 b/doc/tutorials/DC3_model/fem/Nast95_M3.op2 new file mode 100644 index 0000000000000000000000000000000000000000..abcb4f83bf56b11d9eb5f51c62560850772ba4bc GIT binary patch literal 96408 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%0A^KVQ39uwPFO&Kn^I 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 zS7vSH3{=iQ<------><------><------><------><------><------><------><------> +EIGR 100 MGIV 0.1 15.0 +$------><------><------><------><------><------><------><------><------> +$ +CBAR 3328001 33280013329000133290002 0.0 1. 0.0 +CBAR 3328002 33280023329000233290003 0.0 1. 0.0 +CBAR 3328003 33280033329000333290004 0.0 1. 0.0 +CBAR 3328004 33280043329000433290005 0.0 1. 0.0 +CBAR 3328005 33280053329000533290006 0.0 1. 0.0 +CBAR 3328006 33280063329000633290007 0.0 1. 0.0 +CBAR 3328007 33280073329000733290008 0.0 1. 0.0 +CBAR 3328008 33280083329000833290009 0.0 1. 0.0 +CBAR 3338001 33380013339000133390002 0.0 0.0 1. +CBAR 3338002 33380023339000233390003 0.0 0.0 1. +CBAR 3338003 33380033339000333390004 0.0 0.0 1. +CBAR 3338004 33380043339000433390005 0.0 0.0 1. +CBAR 3338005 33380053339000533390006 0.0 0.0 1. +CBAR 3338006 33380063339000633390007 0.0 0.0 1. +CBAR 3338007 33380073339000733390008 0.0 0.0 1. +CBAR 3348001 33480013349000133490002 0.0 0.0 1. +CBAR 3348002 33480023349000233490003 0.0 0.0 1. +CBAR 3348003 33480033349000333490004 0.0 0.0 1. +CBAR 3348004 33480043349000433490005 0.0 0.0 1. +CBAR 3348005 33480053349000533490006 0.0 0.0 1. +CBAR 3348006 33480063349000633490007 0.0 0.0 1. +CBAR 3348007 33480073349000733490008 0.0 0.0 1. +CBAR 5408001 54080015409000154090002 0.0 0.0 1. +CBAR 5408002 54080025409000254090003 0.0 0.0 1. +CBAR 5408003 54080035409000354090004 0.0 0.0 1. +CBAR 5408004 54080045409000454090005 0.0 0.0 1. +CBAR 5408005 54080055409000554090006 0.0 0.0 1. +CBAR 5408006 54080065409000654090007 0.0 0.0 1. +CBAR 5408007 54080075409000754090008 0.0 0.0 1. +CBAR 5408008 54080085409000854090009 0.0 0.0 1. +CBAR 5408009 54080095409000954090010 0.0 0.0 1. +CBAR 5408010 54080105409001054090011 0.0 0.0 1. +CBAR 5408011 54080115409001154090012 0.0 0.0 1. +CBAR 5408012 54080125409001254090013 0.0 0.0 1. +CBAR 5408013 54080135409001354090014 0.0 0.0 1. +CBAR 5408014 54080145409001454090015 0.0 0.0 1. +CBAR 5408015 54080155409001554090016 0.0 0.0 1. +CBAR 5408016 54080165409001654090017 0.0 0.0 1. +CBAR 5408017 54080175409001754090018 0.0 0.0 1. +CBAR 5408018 54080185409001854090019 0.0 0.0 1. +CBAR 5408019 54080195409001954090020 0.0 0.0 1. +CBAR 5408020 54080205409002054090021 0.0 0.0 1. +CBAR 5408021 54080215409002154090022 0.0 0.0 1. +CBAR 5408022 54080225409002254090023 0.0 0.0 1. +CBAR 5408023 54080235409002354090024 0.0 0.0 1. +CBAR 5408024 54080245409002454090025 0.0 0.0 1. +CBAR 5408025 54080255409002554090026 0.0 0.0 1. +CBAR 5408026 54080265409002654090027 0.0 0.0 1. +CBAR 5408027 54080275409002754090028 0.0 0.0 1. +CBAR 5408028 54080285409002854090029 0.0 0.0 1. +CBAR 5408029 54080295409002954090030 0.0 0.0 1. +CBAR 5408030 54080305409003054090031 0.0 0.0 1. +CBAR 6408001 64080016409000164090002 0.0 0.0 1. +CBAR 6408002 64080026409000264090003 0.0 0.0 1. +CBAR 6408003 64080036409000364090004 0.0 0.0 1. +CBAR 6408004 64080046409000464090005 0.0 0.0 1. +CBAR 6408005 64080056409000564090006 0.0 0.0 1. +CBAR 6408006 64080066409000664090007 0.0 0.0 1. +CBAR 6408007 64080076409000764090008 0.0 0.0 1. +CBAR 6408008 64080086409000864090009 0.0 0.0 1. +CBAR 6408009 64080096409000964090010 0.0 0.0 1. +CBAR 6408010 64080106409001064090011 0.0 0.0 1. +CBAR 6408011 64080116409001164090012 0.0 0.0 1. +CBAR 6408012 64080126409001264090013 0.0 0.0 1. +CBAR 6408013 64080136409001364090014 0.0 0.0 1. +CBAR 6408014 64080146409001464090015 0.0 0.0 1. +CBAR 6408015 64080156409001564090016 0.0 0.0 1. +CBAR 6408016 64080166409001664090017 0.0 0.0 1. +CBAR 6408017 64080176409001764090018 0.0 0.0 1. +CBAR 6408018 64080186409001864090019 0.0 0.0 1. +CBAR 6408019 64080196409001964090020 0.0 0.0 1. +CBAR 6408020 64080206409002064090021 0.0 0.0 1. +CBAR 6408021 64080216409002164090022 0.0 0.0 1. +CBAR 6408022 64080226409002264090023 0.0 0.0 1. +CBAR 6408023 64080236409002364090024 0.0 0.0 1. +CBAR 6408024 64080246409002464090025 0.0 0.0 1. +CBAR 6408025 64080256409002564090026 0.0 0.0 1. +CBAR 6408026 64080266409002664090027 0.0 0.0 1. +CBAR 6408027 64080276409002764090028 0.0 0.0 1. +CBAR 6408028 64080286409002864090029 0.0 0.0 1. +CBAR 6408029 64080296409002964090030 0.0 0.0 1. +CBAR 6408030 64080306409003064090031 0.0 0.0 1. +CONM2 8000154090001 0 161.473.0145699-.263119-.156335 +1 ++1 14.7777 -5.6-14 92.9288 .30998 4.39-16 100.509 +CONM2 8000254090003 0 161.466.0145699 .263094-.156336 +2 ++2 14.7761 3.98-6 92.9245 .30996 4.37-5 100.502 +CONM2 8000354090003 0 161.473.0145699 -.26311-.156335 +3 ++3 14.7777 7.97-6 92.9291 .30998 8.74-5 100.509 +CONM2 8000454090004 0 160.344.0145699 -.26126-.156335 +4 ++4 14.5198 7.919-6 92.2792 .30781 8.679-5 99.652 +CONM2 9000164090001 0 161.473.0145699 .263119-.156335 +5 ++5 14.7777 5.67-14 92.9288 -.30998 -4.3-16 100.509 +CONM2 9000264090003 0 161.466.0145699 -.26309-.156336 +6 ++6 14.7761 -3.98-6 92.9245 -.30996 -4.37-5 100.502 +CONM2 9000364090003 0 161.473.0145699 .26311-.156335 +7 ++7 14.7777 -7.97-6 92.9291 -.30998 -8.74-5 100.509 +CONM2 9000464090004 0 160.344.0145699 .26126-.156335 +8 ++8 14.5198 -7.91-6 92.2792 -.30781 -8.67-5 99.652 +CONM2 110001 100001 0 19.313 0.0 0.0 0.0 +9 ++9 .358 0.0 0.0 0.0 0.0 0.0 +CONM2 110002 100002 0 141.008 0.0 0.0 0.0 +10 ++10 139.474 0.0 0.0 0.0 0.0 0.0 +CONM2 110003 100003 0 172.413 0.0 0.0 0.0 +11 ++11 254.959 0.0 0.0 0.0 0.0 0.0 +CONM2 110004 100004 0 184.19 0.0 0.0 0.0 +12 ++12 310.855 0.0 0.0 0.0 0.0 0.0 +CONM2 110005 100005 0 180.264 0.0 0.0 0.0 +13 ++13 291.4 0.0 0.0 0.0 0.0 0.0 +CONM2 110006 100006 0 178.301 0.0 0.0 0.0 +14 ++14 281.984 0.0 0.0 0.0 0.0 0.0 +CONM2 110007 100007 0 162.599 0.0 0.0 0.0 +15 ++15 213.852 0.0 0.0 0.0 0.0 0.0 +CONM2 110008 100008 0 146.896 0.0 0.0 0.0 +16 ++16 157.686 0.0 0.0 0.0 0.0 0.0 +CONM2 110009 100009 0 117.454 0.0 0.0 0.0 +17 ++17 80.606 0.0 0.0 0.0 0.0 0.0 +CONM2 110010 100010 0 74.272 0.0 0.0 0.0 +18 ++18 20.382 0.0 0.0 0.0 0.0 0.0 +CONM2 110011 100011 0 31.09 0.0 0.0 0.0 +19 ++19 1.495 0.0 0.0 0.0 0.0 0.0 +CONM2 12000164090006 0 139.815 -.20038 .2463-.197264 +20 ++20 0.0 0.0 0.0 0.0 0.0 0.0 +CONM2 12000254090006 0 139.815 -.20038 -.2463-.197264 +21 ++21 0.0 0.0 0.0 0.0 0.0 0.0 +CONM2 120003 100010 0 31.07 -1.0209 0.0 -.672 +22 ++22 0.0 0.0 0.0 0.0 0.0 0.0 +CONM2 121001 100004 0 425.47 -.1293 0.0 -1.55 +23 ++23 0.0 0.0 0.0 0.0 0.0 0.0 +CONM2 13000164090005 0 103.55 .01962 -.23426-.197264 +24 ++24 0.0 0.0 0.0 0.0 0.0 0.0 +CONM2 13000254090005 0 103.55 .01962 .23426-.197264 +25 ++25 0.0 0.0 0.0 0.0 0.0 0.0 +CONM2 140001 100003 0 241.9 -.7482 0.0 -.123 +26 ++26 0.0 0.0 0.0 0.0 0.0 0.0 +CONM2 150001 100003 0 12.5 -.7482 0.0 -.123 +27 ++27 0.0 0.0 0.0 0.0 0.0 0.0 +CONM2 160001 100002 0 142.3 -.6501 0.0 -1.55 +28 ++28 0.0 0.0 0.0 0.0 0.0 0.0 +CONM2 170001 100002 0 255.9 -.6501 0.0 -1.55 +29 ++29 0.0 0.0 0.0 0.0 0.0 0.0 +CONM2 180001 100004 0 608. -.7173 0.0 -1.55 +30 ++30 0.0 0.0 0.0 0.0 0.0 0.0 +CONM2 190001 100004 0 696.3 .1357 0.0 -1.55 +31 ++31 0.0 0.0 0.0 0.0 0.0 0.0 +CONM2 707001 100003 0 746. -.5842 0.0 -1.55 +32 ++32 0.0 0.0 0.0 0.0 0.0 0.0 +CONM2 707002 100005 0 1543. .3556 0.0 -1.55 +33 ++33 0.0 0.0 0.0 0.0 0.0 0.0 +CONM2 707003 100008 0 231. -.6477 0.0 -1.55 +34 ++34 0.0 0.0 0.0 0.0 0.0 0.0 +CONM2 332810133290001 0 13.375 0.0 0.0 0.0 +35 ++35 0.0 0.0 0.0 0.0 0.0 20.8867 +CONM2 332810233290002 0 23.065 0.0 0.0 0.0 +36 ++36 0.0 0.0 0.0 0.0 0.0 31.0587 +CONM2 332810333290003 0 19.654 0.0 0.0 0.0 +37 ++37 0.0 0.0 0.0 0.0 0.0 22.5508 +CONM2 332810433290004 0 16.515 0.0 0.0 0.0 +38 ++38 0.0 0.0 0.0 0.0 0.0 15.9235 +CONM2 332810533290005 0 13.65 0.0 0.0 0.0 +39 ++39 0.0 0.0 0.0 0.0 0.0 10.8769 +CONM2 332810633290006 0 11.057 0.0 0.0 0.0 +40 ++40 0.0 0.0 0.0 0.0 0.0 7.1371 +CONM2 332810733290007 0 8.737 0.0 0.0 0.0 +41 ++41 0.0 0.0 0.0 0.0 0.0 4.4562 +CONM2 332810833290008 0 6.69 0.0 0.0 0.0 +42 ++42 0.0 0.0 0.0 0.0 0.0 2.6126 +CONM2 332810933290009 0 2.458 0.0 0.0 0.0 +43 ++43 0.0 0.0 0.0 0.0 0.0 .7053 +CONM2 333810133390001 0 10.663 0.0 0.0 0.0 +44 ++44 0.0 0.0 8.6823 0.0 0.0 0.0 +CONM2 333810233390002 0 17.841 0.0 0.0 0.0 +45 ++45 0.0 0.0 12.5584 0.0 0.0 0.0 +CONM2 333810333390003 0 14.466 0.0 0.0 0.0 +46 ++46 0.0 0.0 8.5231 0.0 0.0 0.0 +CONM2 333810433390004 0 11.445 0.0 0.0 0.0 +47 ++47 0.0 0.0 5.3348 0.0 0.0 0.0 +CONM2 333810533390005 0 8.777 0.0 0.0 0.0 +48 ++48 0.0 0.0 3.1377 0.0 0.0 0.0 +CONM2 333810633390006 0 6.463 0.0 0.0 0.0 +49 ++49 0.0 0.0 1.7012 0.0 0.0 0.0 +CONM2 333810733390007 0 4.502 0.0 0.0 0.0 +50 ++50 0.0 0.0 .8284 0.0 0.0 0.0 +CONM2 333810833390008 0 1.442 0.0 0.0 0.0 +51 ++51 0.0 0.0 .1707 0.0 0.0 0.0 +CONM2 334810133490001 0 10.663 0.0 0.0 0.0 +52 ++52 0.0 0.0 8.6823 0.0 0.0 0.0 +CONM2 334810233490002 0 17.841 0.0 0.0 0.0 +53 ++53 0.0 0.0 12.5584 0.0 0.0 0.0 +CONM2 334810333490003 0 14.466 0.0 0.0 0.0 +54 ++54 0.0 0.0 8.5231 0.0 0.0 0.0 +CONM2 334810433490004 0 11.445 0.0 0.0 0.0 +55 ++55 0.0 0.0 5.3348 0.0 0.0 0.0 +CONM2 334810533490005 0 8.777 0.0 0.0 0.0 +56 ++56 0.0 0.0 3.1377 0.0 0.0 0.0 +CONM2 334810633490006 0 6.463 0.0 0.0 0.0 +57 ++57 0.0 0.0 1.7012 0.0 0.0 0.0 +CONM2 334810733490007 0 4.502 0.0 0.0 0.0 +58 ++58 0.0 0.0 .8284 0.0 0.0 0.0 +CONM2 334810833490008 0 1.442 0.0 0.0 0.0 +59 ++59 0.0 0.0 .1707 0.0 0.0 0.0 +CONM2 541000154090001 0 59.72 2.49282 5.97-18-.022564 +60 ++60 0.0 0.0 37.9722 0.0 0.0 0.0 +CONM2 541000254090002 0 56.44 2.49282 3.8-5-.022564 +61 ++61 0.0 0.0 75.9433 0.0 0.0 0.0 +CONM2 541000354090003 0 55.44 2.49282-4.000-5-.022564 +62 ++62 0.0 0.0 75.9433 0.0 0.0 0.0 +CONM2 541000454090004 0 55.44 2.49282 0.0-.022564 +63 ++63 0.0 0.0 75.6792 0.0 0.0 0.0 +CONM2 541000554090005 0 55.44 2.49282-4.000-5-.022564 +64 ++64 0.0 0.0 66.3853 0.0 0.0 0.0 +CONM2 541000654090006 0 55.44 2.49282 0.0-.022564 +65 ++65 0.0 0.0 75.9448 0.0 0.0 0.0 +CONM2 541000754090007 0 50.44 2.49282 4.-5-.022564 +66 ++66 0.0 0.0 85.2402 0.0 0.0 0.0 +CONM2 541000854090008 0 50.44 2.49282 0.0-.022564 +67 ++67 0.0 0.0 69.5011 0.0 0.0 0.0 +CONM2 541000954090009 0 46.711 2.21047-2.000-5-.021837 +68 ++68 0.0 0.0 55.533 0.0 0.0 0.0 +CONM2 541001054090010 0 40.074 2.34741-1.400-4 -.02121 +69 ++69 0.0 0.0 48.7313 0.0 0.0 0.0 +CONM2 541001154090011 0 37.527 2.27475-1.500-4-.020584 +70 ++70 0.0 0.0 42.5973 0.0 0.0 0.0 +CONM2 541001254090012 0 35.07 2.202 -1.7-4-.019957 +71 ++71 0.0 0.0 37.0827 0.0 0.0 0.0 +CONM2 541001354090013 0 22.702 2.12934 -1.9-4 -.01933 +72 ++72 0.0 0.0 32.1402 0.0 0.0 0.0 +CONM2 541001454090014 0 21.422 2.05659 -3.1-4-.018603 +73 ++73 0.0 0.0 27.7267 0.0 0.0 0.0 +CONM2 541001554090015 0 19.231 1.98383 -3.3-4-.017976 +74 ++74 0.0 0.0 23.7991 0.0 0.0 0.0 +CONM2 541001654090016 0 18.126 1.91118-3.500-4-.017349 +75 ++75 0.0 0.0 20.3172 0.0 0.0 0.0 +CONM2 541001754090017 0 16.108 1.83843-3.900-4-.016721 +76 ++76 0.0 0.0 17.2447 0.0 0.0 0.0 +CONM2 541001854090018 0 15.177 1.76578 -5.2-4-.016093 +77 ++77 0.0 0.0 14.5451 0.0 0.0 0.0 +CONM2 541001954090019 0 13.33 1.69303-5.600-4-.015365 +78 ++78 0.0 0.0 12.1843 0.0 0.0 0.0 +CONM2 541002054090020 0 12.569 1.62038-6.000-4-.014736 +79 ++79 0.0 0.0 10.1313 0.0 0.0 0.0 +CONM2 541002154090021 0 10.891 1.54763 -6.6-4-.014106 +80 ++80 0.0 0.0 8.3566 0.0 0.0 0.0 +CONM2 541002254090022 0 10.297 1.47488-8.100-4-.013477 +81 ++81 0.0 0.0 6.8312 0.0 0.0 0.0 +CONM2 541002354090023 0 8.786 1.40225 -9.-4-.012844 +82 ++82 0.0 0.0 5.5302 0.0 0.0 0.0 +CONM2 541002454090024 0 8.357 1.3295 -9.-4-.012113 +83 ++83 0.0 0.0 4.4292 0.0 0.0 0.0 +CONM2 541002554090025 0 7.01 1.25678 -1.1-3-.011473 +84 ++84 0.0 0.0 3.5048 0.0 0.0 0.0 +CONM2 541002654090026 0 6.744 1.18415 -1.2-3-.010841 +85 ++85 0.0 0.0 2.7359 0.0 0.0 0.0 +CONM2 541002754090027 0 5.558 1.11141 -1.2-3-.010206 +86 ++86 0.0 0.0 2.1038 0.0 0.0 0.0 +CONM2 541002854090028 0 4.453 1.03871-1.500-3-9.562-3 +87 ++87 0.0 0.0 1.5901 0.0 0.0 0.0 +CONM2 541002954090029 0 4.426 .96601 -1.7-3-8.915-3 +88 ++88 0.0 0.0 1.1782 0.0 0.0 0.0 +CONM2 541003054090030 0 3.478 .8934-2.000-3-8.155-3 +89 ++89 0.0 0.0 .8524 0.0 0.0 0.0 +CONM2 541003154090031 0 1.304 .8204-1.000-4 -7.69-3 +90 ++90 0.0 0.0 .3002 0.0 0.0 0.0 +CONM2 541100154100001 0 287.8 0.0 0.0 0.0 +91 ++91 73.257 0.0 0.0 0.0 0.0 0.0 +CONM2 541100254100002 0 470.95 0.0 0.0 0.0 +92 ++92 102.107 0.0 0.0 0.0 0.0 0.0 +CONM2 541100354100003 0 183.15 0.0 0.0 0.0 +93 ++93 33.408 0.0 0.0 0.0 0.0 0.0 +CONM2 641000164090001 0 59.72 2.49282-5.97-18-.022564 +94 ++94 0.0 0.0 37.9722 0.0 0.0 0.0 +CONM2 641000264090002 0 56.44 2.49282 -3.8-5-.022564 +95 ++95 0.0 0.0 75.9433 0.0 0.0 0.0 +CONM2 641000364090003 0 55.44 2.492824.0000-5-.022564 +96 ++96 0.0 0.0 75.9433 0.0 0.0 0.0 +CONM2 641000464090004 0 55.44 2.49282 0.0-.022564 +97 ++97 0.0 0.0 75.6792 0.0 0.0 0.0 +CONM2 641000564090005 0 55.44 2.492824.0000-5-.022564 +98 ++98 0.0 0.0 66.3853 0.0 0.0 0.0 +CONM2 641000664090006 0 55.44 2.49282 0.0-.022564 +99 ++99 0.0 0.0 75.9448 0.0 0.0 0.0 +CONM2 641000764090007 0 50.44 2.49282 -4.-5-.022564 +100 ++100 0.0 0.0 85.2402 0.0 0.0 0.0 +CONM2 641000864090008 0 50.44 2.49282 0.0-.022564 +101 ++101 0.0 0.0 69.5011 0.0 0.0 0.0 +CONM2 641000964090009 0 46.711 2.21046 -1.-5-.021839 +102 ++102 0.0 0.0 55.533 0.0 0.0 0.0 +CONM2 641001064090010 0 40.074 2.347397.0000-5-.021215 +103 ++103 0.0 0.0 48.7313 0.0 0.0 0.0 +CONM2 641001164090011 0 37.527 2.274736.0000-5-.020591 +104 ++104 0.0 0.0 42.5973 0.0 0.0 0.0 +CONM2 641001264090012 0 35.07 2.20197 4.-5-.019967 +105 ++105 0.0 0.0 37.0827 0.0 0.0 0.0 +CONM2 641001364090013 0 22.702 2.129312.0000-5-.019343 +106 ++106 0.0 0.0 32.1402 0.0 0.0 0.0 +CONM2 641001464090014 0 21.422 2.05655 1.1-4-.018618 +107 ++107 0.0 0.0 27.7267 0.0 0.0 0.0 +CONM2 641001564090015 0 19.231 1.983789.0000-5-.017994 +108 ++108 0.0 0.0 23.7991 0.0 0.0 0.0 +CONM2 641001664090016 0 18.126 1.911127.0000-5 -.01737 +109 ++109 0.0 0.0 20.3172 0.0 0.0 0.0 +CONM2 641001764090017 0 16.108 1.838367.0000-5-.016744 +110 ++110 0.0 0.0 17.2447 0.0 0.0 0.0 +CONM2 641001864090018 0 15.177 1.76571.6000-4 -.01612 +111 ++111 0.0 0.0 14.5451 0.0 0.0 0.0 +CONM2 641001964090019 0 13.33 1.69294 1.5-4-.015395 +112 ++112 0.0 0.0 12.1843 0.0 0.0 0.0 +CONM2 641002064090020 0 12.569 1.62028 1.5-4-.014769 +113 ++113 0.0 0.0 10.1313 0.0 0.0 0.0 +CONM2 641002164090021 0 10.891 1.54752 1.5-4-.014144 +114 ++114 0.0 0.0 8.3566 0.0 0.0 0.0 +CONM2 641002264090022 0 10.297 1.474762.5000-4-.013518 +115 ++115 0.0 0.0 6.8312 0.0 0.0 0.0 +CONM2 641002364090023 0 8.786 1.402123.0000-4-.012889 +116 ++116 0.0 0.0 5.5302 0.0 0.0 0.0 +CONM2 641002464090024 0 8.357 1.32935 2.-4-.012165 +117 ++117 0.0 0.0 4.4292 0.0 0.0 0.0 +CONM2 641002564090025 0 7.01 1.25661 3.-4-.011533 +118 ++118 0.0 0.0 3.5048 0.0 0.0 0.0 +CONM2 641002764090027 0 5.558 1.11122 3.-4-.010273 +119 ++119 0.0 0.0 2.1038 0.0 0.0 0.0 +CONM2 641002864090028 0 4.453 1.038474.0000-4-9.644-3 +120 ++120 0.0 0.0 1.5901 0.0 0.0 0.0 +CONM2 641002964090029 0 4.426 .965755.0000-4-9.004-3 +121 ++121 0.0 0.0 1.1782 0.0 0.0 0.0 +CONM2 641003064090030 0 3.478 .89315.0000-4-8.267-3 +122 ++122 0.0 0.0 .8524 0.0 0.0 0.0 +CONM2 641003164090031 0 1.304 .82041.0000-4 -7.69-3 +123 ++123 0.0 0.0 .3002 0.0 0.0 0.0 +CONM2 641100164100001 0 287.8 0.0 0.0 0.0 +124 ++124 73.257 0.0 0.0 0.0 0.0 0.0 +CONM2 641100264100002 0 470.95 0.0 0.0 0.0 +125 ++125 102.107 0.0 0.0 0.0 0.0 0.0 +CONM2 641100364100003 0 183.15 0.0 0.0 0.0 +126 ++126 33.408 0.0 0.0 0.0 0.0 0.0 +CONM2 410012664090026 0 6.744 1.18395 3.-4-.010907 +127 ++127 0.0 0.0 2.7359 0.0 0.0 0.0 +CORD2R 3329001 0 18.5153 -2.9-15 1.86699 18.5153 -1. 1.86699+128 ++128 18.9862 -.5 1.69894 +CORD2R 3339001 0 18.9631 -2.9-18 1.86699 18.9631 3.38-17 .866999+129 ++129 19.4415 .145628 1.36699 +CORD2R 3349001 0 18.9631 2.99-18 1.86699 18.9631 6.27-17 2.867+130 ++130 19.4415 -.14562 2.367 +CORD2R 5409001 0 8.01838 -5.9-18 .197264 8.01838 1.56-18 -.80273+131 ++131 8.51838 -4.8-16 -.30273 +CORD2R 6409001 0 8.01838 5.97-18 .197264 8.01838 1.35-17 1.19726+132 ++132 8.51838 4.92-16 .697264 +GRID 100001 2. 0.0 1.55 +GRID 100002 3.9431 0.0 1.55 +GRID 100003 5.8862 0.0 1.55 +GRID 100004 7.8293 0.0 1.55 +GRID 100005 9.7724 0.0 1.55 +GRID 100006 11.7155 0.0 1.55 +GRID 100007 13.6586 0.0 1.55 +GRID 100008 15.6017 0.0 1.55 +GRID 100009 17.5448 0.0 1.55 +GRID 100010 19.4879 0.0 1.55 +GRID 100011 21.431 0.0 1.55 +GRID 33290001 18.5153-2.95-15 1.86699 +GRID 33290002 18.6428-2.58-15 2.22425 +GRID 33290003 18.7703-2.22-15 2.5815 +GRID 33290004 18.8978-1.85-15 2.93875 +GRID 33290005 19.0253-1.48-15 3.296 +GRID 33290006 19.1528-1.11-15 3.65323 +GRID 33290007 19.2803-7.49-16 4.01048 +GRID 33290008 19.4078-3.82-16 4.36775 +GRID 33290009 19.5352-1.46-17 4.72498 +GRID 33290101 17.416 0.0 1.86699 +GRID 33290102 17.6219 0.0 2.22425 +GRID 33290103 17.8279 0.0 2.5815 +GRID 33290104 18.034 0.0 2.93875 +GRID 33290105 18.2399 0.0 3.296 +GRID 33290106 18.4459 0.0 3.65323 +GRID 33290107 18.6519 0.0 4.01048 +GRID 33290108 18.8579 0.0 4.36775 +GRID 33290109 19.0639 0.0 4.72498 +GRID 33290201 21.26 0.0 1.86699 +GRID 33290202 21.1914 0.0 2.22425 +GRID 33290203 21.1229 0.0 2.5815 +GRID 33290204 21.0545 0.0 2.93875 +GRID 33290205 20.986 0.0 3.296 +GRID 33290206 20.9175 0.0 3.65323 +GRID 33290207 20.849 0.0 4.01048 +GRID 33290208 20.7805 0.0 4.36775 +GRID 33290209 20.712 0.0 4.72498 +GRID 33390001 18.9631-2.99-18 1.86699 +GRID 33390002 19.136-.567768 1.86699 +GRID 33390003 19.3204-1.17339 1.86699 +GRID 33390004 19.5048-1.77901 1.86699 +GRID 33390005 19.6892-2.38462 1.86699 +GRID 33390006 19.8736-2.99025 1.86699 +GRID 33390007 20.0579-3.59587 1.86699 +GRID 33390008 20.2436-4.20568 1.86699 +GRID 33390101 18.02-5.94-18 1.86699 +GRID 33390102 18.2727-.567768 1.86699 +GRID 33390103 18.5422-1.17339 1.86699 +GRID 33390104 18.8118-1.77901 1.86699 +GRID 33390105 19.0814-2.38462 1.86699 +GRID 33390106 19.3509-2.99025 1.86699 +GRID 33390107 19.6205-3.59587 1.86699 +GRID 33390108 19.8919-4.20568 1.86699 +GRID 33390201 21.0049-5.94-18 1.86699 +GRID 33390202 21.0049-.567768 1.86699 +GRID 33390203 21.0049-1.17339 1.86699 +GRID 33390204 21.0049-1.77901 1.86699 +GRID 33390205 21.0049-2.38462 1.86699 +GRID 33390206 21.0049-2.99025 1.86699 +GRID 33390207 21.0049-3.59587 1.86699 +GRID 33390208 21.0049-4.20568 1.86699 +GRID 33490001 18.9631 2.99-18 1.86699 +GRID 33490002 19.136 .567768 1.86699 +GRID 33490003 19.3204 1.17339 1.86699 +GRID 33490004 19.5048 1.77901 1.86699 +GRID 33490005 19.6892 2.38462 1.86699 +GRID 33490006 19.8736 2.99025 1.86699 +GRID 33490007 20.0579 3.59587 1.86699 +GRID 33490008 20.2436 4.20568 1.86699 +GRID 33490101 18.02 5.94-18 1.86699 +GRID 33490102 18.2727 .567768 1.86699 +GRID 33490103 18.5422 1.17339 1.86699 +GRID 33490104 18.8118 1.77901 1.86699 +GRID 33490105 19.0814 2.38462 1.86699 +GRID 33490106 19.3509 2.99025 1.86699 +GRID 33490107 19.6205 3.59587 1.86699 +GRID 33490108 19.8919 4.20568 1.86699 +GRID 33490201 21.0049 5.94-18 1.86699 +GRID 33490202 21.0049 .567768 1.86699 +GRID 33490203 21.0049 1.17339 1.86699 +GRID 33490204 21.0049 1.77901 1.86699 +GRID 33490205 21.0049 2.38462 1.86699 +GRID 33490206 21.0049 2.99025 1.86699 +GRID 33490207 21.0049 3.59587 1.86699 +GRID 33490208 21.0049 4.20568 1.86699 +GRID 54090001 8.01838-5.97-18 .197264 +GRID 54090002 8.01838-.526238 .197264 +GRID 54090003 8.01838-1.05246 .197264 +GRID 54090004 8.01838 -1.5787 .197264 +GRID 54090005 8.01838-2.10126 .197264 +GRID 54090006 8.01838 -2.4987 .197264 +GRID 54090007 8.01838-3.15374 .197264 +GRID 54090008 8.01838 -3.68 .197264 +GRID 54090009 8.11153-4.11688 .229737 +GRID 54090010 8.20469-4.55376 .26221 +GRID 54090011 8.29785-4.99065 .294684 +GRID 54090012 8.391-5.42753 .327157 +GRID 54090013 8.48416-5.86441 .35963 +GRID 54090014 8.57731-6.30129 .392103 +GRID 54090015 8.67047-6.73817 .424576 +GRID 54090016 8.76362-7.17505 .457049 +GRID 54090017 8.85677-7.61191 .489521 +GRID 54090018 8.94992-8.04878 .521993 +GRID 54090019 9.04307-8.48564 .554465 +GRID 54090020 9.13622 -8.9225 .586936 +GRID 54090021 9.22937-9.35934 .619406 +GRID 54090022 9.32252-9.79619 .651877 +GRID 54090023 9.41565-10.2329 .684344 +GRID 54090024 9.5088-10.6698 .716813 +GRID 54090025 9.60192-11.1065 .749273 +GRID 54090026 9.69505-11.5433 .781741 +GRID 54090027 9.78819-11.9801 .814206 +GRID 54090028 9.88129-12.4167 .846662 +GRID 54090029 9.97439-12.8533 .879115 +GRID 54090030 10.0674-13.2898 .911555 +GRID 54090031 10.1607-13.7275 .94409 +GRID 54090101 6.88999-1.11-15 .150999 +GRID 54090102 6.88999-.526238 .150999 +GRID 54090103 6.88999-1.05246 .150999 +GRID 54090104 6.88999 -1.5787 .150999 +GRID 54090105 6.88999-2.10126 .150999 +GRID 54090106 6.88999 -2.4987 .150999 +GRID 54090107 6.88999-3.15374 .150999 +GRID 54090108 6.88999 -3.68 .150999 +GRID 54090109 7.01607 -4.1169 .184822 +GRID 54090110 7.14214-4.55381 .218644 +GRID 54090111 7.26822-4.99072 .252467 +GRID 54090112 7.39429-5.42763 .286289 +GRID 54090113 7.52036-5.86454 .320111 +GRID 54090114 7.64643-6.30144 .353933 +GRID 54090115 7.7725-6.73834 .387755 +GRID 54090116 7.89858-7.17525 .421577 +GRID 54090117 8.02464-7.61214 .455399 +GRID 54090118 8.15072-8.04904 .48922 +GRID 54090119 8.27678-8.48593 .523041 +GRID 54090120 8.40285-8.92283 .556862 +GRID 54090121 8.52892 -9.3597 .590682 +GRID 54090122 8.65498-9.79659 .624503 +GRID 54090123 8.78104-10.2334 .658321 +GRID 54090124 8.9071-10.6703 .69214 +GRID 54090125 9.03313 -11.107 .725952 +GRID 54090126 9.15919-11.5439 .759771 +GRID 54090127 9.28525-11.9808 .793589 +GRID 54090128 9.41128-12.4175 .827399 +GRID 54090129 9.5373-12.8542 .861207 +GRID 54090130 9.66328-13.2908 .895006 +GRID 54090131 9.78999-13.7299 .929 +GRID 54090201 11.21-1.11-15 .150999 +GRID 54090202 11.21-.526238 .150999 +GRID 54090203 11.21-1.05246 .150999 +GRID 54090204 11.21 -1.5787 .150999 +GRID 54090205 11.21-2.10126 .150999 +GRID 54090206 11.21 -2.4987 .150999 +GRID 54090207 11.21-3.15374 .150999 +GRID 54090208 11.21 -3.68 .150999 +GRID 54090209 11.21 -4.1169 .184822 +GRID 54090210 11.21-4.55381 .218644 +GRID 54090211 11.21-4.99072 .252467 +GRID 54090212 11.21-5.42763 .286289 +GRID 54090213 11.21-5.86454 .320111 +GRID 54090214 11.21-6.30144 .353933 +GRID 54090215 11.21-6.73834 .387755 +GRID 54090216 11.21-7.17525 .421577 +GRID 54090217 11.21-7.61214 .455399 +GRID 54090218 11.21-8.04904 .48922 +GRID 54090219 11.21-8.48593 .523041 +GRID 54090220 11.21-8.92283 .556862 +GRID 54090221 11.21 -9.3597 .590682 +GRID 54090222 11.21-9.79659 .624503 +GRID 54090223 11.21-10.2334 .658321 +GRID 54090224 11.21-10.6703 .69214 +GRID 54090225 11.21 -11.107 .725952 +GRID 54090226 11.21-11.5439 .759771 +GRID 54090227 11.21-11.9808 .793589 +GRID 54090228 11.21-12.4175 .827399 +GRID 54090229 11.21-12.8542 .861207 +GRID 54090230 11.21-13.2908 .895006 +GRID 54090231 11.21-13.7254 .92865 +GRID 54100001 5.129 -2.745 0.0 +GRID 54100002 5.897 -2.745 0.0 +GRID 54100003 7.105 -2.745 0.0 +GRID 64090001 8.01838 5.97-18 .197264 +GRID 64090002 8.01838 .526238 .197264 +GRID 64090003 8.01838 1.05246 .197264 +GRID 64090004 8.01838 1.5787 .197264 +GRID 64090005 8.01838 2.10126 .197264 +GRID 64090006 8.01838 2.4987 .197264 +GRID 64090007 8.01838 3.15374 .197264 +GRID 64090008 8.01838 3.68 .197264 +GRID 64090009 8.11154 4.11691 .229739 +GRID 64090010 8.20471 4.55383 .262215 +GRID 64090011 8.29787 4.99074 .294691 +GRID 64090012 8.39103 5.42766 .327167 +GRID 64090013 8.48419 5.86458 .359643 +GRID 64090014 8.57735 6.30149 .392118 +GRID 64090015 8.67052 6.73841 .424594 +GRID 64090016 8.76368 7.17533 .45707 +GRID 64090017 8.85684 7.61223 .489544 +GRID 64090018 8.95 8.04914 .52202 +GRID 64090019 9.04316 8.48605 .554495 +GRID 64090020 9.13632 8.92295 .586969 +GRID 64090021 9.22948 9.35985 .619444 +GRID 64090022 9.32264 9.79675 .651918 +GRID 64090023 9.41578 10.2335 .684389 +GRID 64090024 9.50895 10.6705 .716865 +GRID 64090025 9.60209 11.1073 .749333 +GRID 64090026 9.69525 11.5442 .781807 +GRID 64090027 9.78838 11.981 .814273 +GRID 64090028 9.88153 12.4178 .846744 +GRID 64090029 9.97465 12.8545 .879204 +GRID 64090030 10.0677 13.2913 .911667 +GRID 64090031 10.1607 13.7275 .94409 +GRID 64090101 6.88999 1.11-15 .150999 +GRID 64090102 6.88999 .526238 .150999 +GRID 64090103 6.88999 1.05246 .150999 +GRID 64090104 6.88999 1.5787 .150999 +GRID 64090105 6.88999 2.10126 .150999 +GRID 64090106 6.88999 2.4987 .150999 +GRID 64090107 6.88999 3.15374 .150999 +GRID 64090108 6.88999 3.68 .150999 +GRID 64090109 7.01608 4.11693 .184824 +GRID 64090110 7.14216 4.55388 .218649 +GRID 64090111 7.26824 4.99081 .252474 +GRID 64090112 7.39433 5.42776 .286299 +GRID 64090113 7.52041 5.86471 .320124 +GRID 64090114 7.64649 6.30164 .353949 +GRID 64090115 7.77257 6.73858 .387774 +GRID 64090116 7.89866 7.17553 .421599 +GRID 64090117 8.02474 7.61246 .455423 +GRID 64090118 8.15082 8.0494 .489248 +GRID 64090119 8.2769 8.48635 .523073 +GRID 64090120 8.40298 8.92328 .556897 +GRID 64090121 8.52906 9.36021 .590722 +GRID 64090122 8.65514 9.79715 .624547 +GRID 64090123 8.78121 10.234 .658367 +GRID 64090124 8.9073 10.671 .692194 +GRID 64090125 9.03336 11.1078 .726014 +GRID 64090126 9.15945 11.5448 .75984 +GRID 64090127 9.28551 11.9817 .793659 +GRID 64090128 9.41159 12.4186 .827484 +GRID 64090129 9.53765 12.8554 .8613 +GRID 64090130 9.66372 13.2923 .895122 +GRID 64090131 9.78999 13.7299 .929 +GRID 64090201 11.21 1.11-15 .150999 +GRID 64090202 11.21 .526238 .150999 +GRID 64090203 11.21 1.05246 .150999 +GRID 64090204 11.21 1.5787 .150999 +GRID 64090205 11.21 2.10126 .150999 +GRID 64090206 11.21 2.4987 .150999 +GRID 64090207 11.21 3.15374 .150999 +GRID 64090208 11.21 3.68 .150999 +GRID 64090209 11.21 4.11693 .184824 +GRID 64090210 11.21 4.55388 .218649 +GRID 64090211 11.21 4.99081 .252474 +GRID 64090212 11.21 5.42776 .286299 +GRID 64090213 11.21 5.86471 .320124 +GRID 64090214 11.21 6.30164 .353949 +GRID 64090215 11.21 6.73858 .387774 +GRID 64090216 11.21 7.17553 .421599 +GRID 64090217 11.21 7.61246 .455423 +GRID 64090218 11.21 8.0494 .489248 +GRID 64090219 11.21 8.48635 .523073 +GRID 64090220 11.21 8.92328 .556897 +GRID 64090221 11.21 9.36021 .590722 +GRID 64090222 11.21 9.79715 .624547 +GRID 64090223 11.21 10.234 .658367 +GRID 64090224 11.21 10.671 .692194 +GRID 64090225 11.21 11.1078 .726014 +GRID 64090226 11.21 11.5448 .75984 +GRID 64090227 11.21 11.9817 .793659 +GRID 64090228 11.21 12.4186 .827484 +GRID 64090229 11.21 12.8554 .8613 +GRID 64090230 11.21 13.2923 .895122 +GRID 64090231 11.21 13.7254 .92865 +GRID 64100001 5.129 2.745 0.0 +GRID 64100002 5.897 2.745 0.0 +GRID 64100003 7.105 2.745 0.0 +MAT1 332001 7.+10 2.69+10 .3 0.0 +MAT1 333001 7.+10 2.69+10 .3 0.0 +MAT1 334001 7.+10 2.69+10 .3 0.0 +MAT1 540001 7.+10 2.69+10 .3 0.0 +MAT1 640001 7.+10 2.69+10 .3 0.0 +PBAR 3328001 332001 .02567 .0006 .03703 .0376 0.0 +PBAR 3328002 332001 .02201 .000371 .02726 .02769 0.0 +PBAR 3328003 332001 .01864 2.662-4 .01956 .01987 0.0 +PBAR 3328004 332001 .01554 1.855-4 .01363 .01384 0.0 +PBAR 3328005 332001 .01273 1.247-4 .009158 .009303 0.0 +PBAR 3328006 332001 .0102 8.02-5 .005894 .005987 0.0 +PBAR 3328007 332001 .007948 .0001 .00359 .00365 0.0 +PBAR 3328008 332001 .005979 2.787-5 .002045 .002078 0.0 +PBAR 3338001 333001 .01214 .0002 .00942 .00959 0.0 +PBAR 3338002 333001 .01001 9.633-5 .00643 .00654 0.0 +PBAR 3338003 333001 .008032 6.214-5 .004149 .00422 0.0 +PBAR 3338004 333001 .006268 3.796-5 .002537 .002581 0.0 +PBAR 3338005 333001 .004724 2.164-5 .001449 .001474 0.0 +PBAR 3338006 333001 .003399 1.132-5 .00075 .000769 0.0 +PBAR 3338007 333001 .00229 5.236-6 3.431-4 3.547-4 0.0 +PBAR 3348001 334001 .01214 .0002 .00942 .00959 0.0 +PBAR 3348002 334001 .01001 9.633-5 .00643 .00654 0.0 +PBAR 3348003 334001 .008032 6.214-5 .004149 .00422 0.0 +PBAR 3348004 334001 .006268 3.796-5 .002537 .002581 0.0 +PBAR 3348005 334001 .004724 2.164-5 .001449 .001474 0.0 +PBAR 3348006 334001 .003399 1.132-5 .00075 .000769 0.0 +PBAR 3348007 334001 .00229 5.236-6 3.431-4 3.547-4 0.0 +PBAR 5408001 540001 .03553 4.375-4 .002553 .002663 0.0 +PBAR 5408002 540001 .03553 4.375-4 .002553 .002663 0.0 +PBAR 5408003 540001 .03553 4.375-4 .002553 .002663 0.0 +PBAR 5408004 540001 .03553 4.375-4 .002553 .002663 0.0 +PBAR 5408005 540001 .03553 4.375-4 .002553 .002663 0.0 +PBAR 5408006 540001 .03553 4.376-4 .002553 .002663 0.0 +PBAR 5408007 540001 .03553 4.375-4 .002553 .002663 0.0 +PBAR 5408008 540001 .03446 4.039-4 .002403 .002504 0.0 +PBAR 5408009 540001 .03237 .000341 .002116 .002201 0.0 +PBAR 5408010 540001 .03034 2.866-4 .001857 .001928 0.0 +PBAR 5408011 540001 .02838 2.396-4 .001622 .001682 0.0 +PBAR 5408012 540001 .0265 1.994-4 .001412 .001462 0.0 +PBAR 5408013 540001 .02468 1.649-4 .001223 .001264 0.0 +PBAR 5408014 540001 .02293 1.356-4 .001054 .001088 0.0 +PBAR 5408015 540001 .02125 1.108-4 9.038-4 9.315-4 0.0 +PBAR 5408016 540001 .01964 8.989-5 7.706-4 7.931-4 0.0 +PBAR 5408017 540001 .0181 7.242-5 6.531-4 6.712-4 0.0 +PBAR 5408018 540001 .01662 5.788-5 5.499-4 5.644-4 0.0 +PBAR 5408019 540001 .01521 4.589-5 4.597-4 4.712-4 0.0 +PBAR 5408020 540001 .01386 3.605-5 3.813-4 3.904-4 0.0 +PBAR 5408021 540001 .01258 2.805-5 3.137-4 3.207-4 0.0 +PBAR 5408022 540001 .01137 2.159-5 2.556-4 .000261 0.0 +PBAR 5408023 540001 .01022 1.642-5 2.062-4 2.103-4 0.0 +PBAR 5408024 540001 .009136 1.234-5 1.644-4 1.675-4 0.0 +PBAR 5408025 540001 .008114 9.14-6 1.295-4 1.318-4 0.0 +PBAR 5408026 540001 .007156 6.66-6 1.005-4 1.022-4 0.0 +PBAR 5408027 540001 .00626 4.78-6 7.681-5 7.801-5 0.0 +PBAR 5408028 540001 .005426 3.36-6 5.762-5 5.846-5 0.0 +PBAR 5408029 540001 .004654 2.3-6 4.233-5 4.291-5 0.0 +PBAR 5408030 540001 .003943 1.6-6 3.035-5 3.074-5 0.0 +PBAR 6408001 640001 .03553 4.375-4 .002553 .002663 0.0 +PBAR 6408002 640001 .03553 4.375-4 .002553 .002663 0.0 +PBAR 6408003 640001 .03553 4.375-4 .002553 .002663 0.0 +PBAR 6408004 640001 .03553 4.375-4 .002553 .002663 0.0 +PBAR 6408005 640001 .03553 4.375-4 .002553 .002663 0.0 +PBAR 6408006 640001 .03553 4.376-4 .002553 .002663 0.0 +PBAR 6408007 640001 .03553 4.375-4 .002553 .002663 0.0 +PBAR 6408008 640001 .03446 4.039-4 .002403 .002504 0.0 +PBAR 6408009 640001 .03237 .000341 .002116 .002201 0.0 +PBAR 6408010 640001 .03034 2.866-4 .001857 .001928 0.0 +PBAR 6408011 640001 .02838 2.396-4 .001622 .001682 0.0 +PBAR 6408012 640001 .0265 1.994-4 .001412 .001462 0.0 +PBAR 6408013 640001 .02468 1.649-4 .001223 .001264 0.0 +PBAR 6408014 640001 .02293 1.356-4 .001054 .001088 0.0 +PBAR 6408015 640001 .02125 1.108-4 9.038-4 9.315-4 0.0 +PBAR 6408016 640001 .01964 8.989-5 7.706-4 7.931-4 0.0 +PBAR 6408017 640001 .0181 7.242-5 6.531-4 6.712-4 0.0 +PBAR 6408018 640001 .01662 5.788-5 5.499-4 5.644-4 0.0 +PBAR 6408019 640001 .01521 4.589-5 4.597-4 4.712-4 0.0 +PBAR 6408020 640001 .01386 3.605-5 3.813-4 3.904-4 0.0 +PBAR 6408021 640001 .01258 2.805-5 3.137-4 3.207-4 0.0 +PBAR 6408022 640001 .01137 2.159-5 2.556-4 .000261 0.0 +PBAR 6408023 640001 .01022 1.642-5 2.062-4 2.103-4 0.0 +PBAR 6408024 640001 .009136 1.234-5 1.644-4 1.675-4 0.0 +PBAR 6408025 640001 .008114 9.14-6 1.295-4 1.318-4 0.0 +PBAR 6408026 640001 .007156 6.66-6 1.005-4 1.022-4 0.0 +PBAR 6408027 640001 .00626 4.78-6 7.681-5 7.801-5 0.0 +PBAR 6408028 640001 .005426 3.36-6 5.762-5 5.846-5 0.0 +PBAR 6408029 640001 .004654 2.3-6 4.233-5 4.291-5 0.0 +PBAR 6408030 640001 .003943 1.6-6 3.035-5 3.074-5 0.0 +CRBE2 100000 100004 123456 100001 100002 100003 100005 100006+133 ++133 100007 100008 100009 100010 100011 +CRBE2 200001 100004 1234566409000154090001 +CRBE2 200002 100010 1234563349000133390001 +CRBE2 200003 100010 12345633290001 +CRBE2 3329010133290001 1234563329010133290201 +CRBE2 3329010233290002 1234563329010233290202 +CRBE2 3329010333290003 1234563329010333290203 +CRBE2 3329010433290004 1234563329010433290204 +CRBE2 3329010533290005 1234563329010533290205 +CRBE2 3329010633290006 1234563329010633290206 +CRBE2 3329010733290007 1234563329010733290207 +CRBE2 3329010833290008 1234563329010833290208 +CRBE2 3329010933290009 1234563329010933290209 +CRBE2 3339010133390001 1234563339010133390201 +CRBE2 3339010233390002 1234563339010233390202 +CRBE2 3339010333390003 1234563339010333390203 +CRBE2 3339010433390004 1234563339010433390204 +CRBE2 3339010533390005 1234563339010533390205 +CRBE2 3339010633390006 1234563339010633390206 +CRBE2 3339010733390007 1234563339010733390207 +CRBE2 3339010833390008 1234563339010833390208 +CRBE2 3349010133490001 1234563349010133490201 +CRBE2 3349010233490002 1234563349010233490202 +CRBE2 3349010333490003 1234563349010333490203 +CRBE2 3349010433490004 1234563349010433490204 +CRBE2 3349010533490005 1234563349010533490205 +CRBE2 3349010633490006 1234563349010633490206 +CRBE2 3349010733490007 1234563349010733490207 +CRBE2 3349010833490008 1234563349010833490208 +CRBE2 5409010154090001 1234565409010154090201 +CRBE2 5409010254090002 1234565409010254090202 +CRBE2 5409010354090003 1234565409010354090203 +CRBE2 5409010454090004 1234565409010454090204 +CRBE2 5409010554090005 1234565409010554090205 +CRBE2 5409010654090006 1234565409010654090206 +CRBE2 5409010754090007 1234565409010754090207 +CRBE2 5409010854090008 1234565409010854090208 +CRBE2 5409010954090009 1234565409010954090209 +CRBE2 5409011054090010 1234565409011054090210 +CRBE2 5409011154090011 1234565409011154090211 +CRBE2 5409011254090012 1234565409011254090212 +CRBE2 5409011354090013 1234565409011354090213 +CRBE2 5409011454090014 1234565409011454090214 +CRBE2 5409011554090015 1234565409011554090215 +CRBE2 5409011654090016 1234565409011654090216 +CRBE2 5409011754090017 1234565409011754090217 +CRBE2 5409011854090018 1234565409011854090218 +CRBE2 5409011954090019 1234565409011954090219 +CRBE2 5409012054090020 1234565409012054090220 +CRBE2 5409012154090021 1234565409012154090221 +CRBE2 5409012254090022 1234565409012254090222 +CRBE2 5409012354090023 1234565409012354090223 +CRBE2 5409012454090024 1234565409012454090224 +CRBE2 5409012554090025 1234565409012554090225 +CRBE2 5409012654090026 1234565409012654090226 +CRBE2 5409012754090027 1234565409012754090227 +CRBE2 5409012854090028 1234565409012854090228 +CRBE2 5409012954090029 1234565409012954090229 +CRBE2 5409013054090030 1234565409013054090230 +CRBE2 5409013154090031 1234565409013154090231 +CRBE2 5410000054090006 123456541000015410000254100003 +CRBE2 6409010164090001 1234566409010164090201 +CRBE2 6409010264090002 1234566409010264090202 +CRBE2 6409010364090003 1234566409010364090203 +CRBE2 6409010464090004 1234566409010464090204 +CRBE2 6409010564090005 1234566409010564090205 +CRBE2 6409010664090006 1234566409010664090206 +CRBE2 6409010764090007 1234566409010764090207 +CRBE2 6409010864090008 1234566409010864090208 +CRBE2 6409010964090009 1234566409010964090209 +CRBE2 6409011064090010 1234566409011064090210 +CRBE2 6409011164090011 1234566409011164090211 +CRBE2 6409011264090012 1234566409011264090212 +CRBE2 6409011364090013 1234566409011364090213 +CRBE2 6409011464090014 1234566409011464090214 +CRBE2 6409011564090015 1234566409011564090215 +CRBE2 6409011664090016 1234566409011664090216 +CRBE2 6409011764090017 1234566409011764090217 +CRBE2 6409011864090018 1234566409011864090218 +CRBE2 6409011964090019 1234566409011964090219 +CRBE2 6409012064090020 1234566409012064090220 +CRBE2 6409012164090021 1234566409012164090221 +CRBE2 6409012264090022 1234566409012264090222 +CRBE2 6409012364090023 1234566409012364090223 +CRBE2 6409012464090024 1234566409012464090224 +CRBE2 6409012564090025 1234566409012564090225 +CRBE2 6409012664090026 1234566409012664090226 +CRBE2 6409012764090027 1234566409012764090227 +CRBE2 6409012864090028 1234566409012864090228 +CRBE2 6409012964090029 1234566409012964090229 +CRBE2 6409013064090030 1234566409013064090230 +CRBE2 6409013164090031 1234566409013164090231 +CRBE2 6410000064090006 123456641000016410000264100003 +$ +ENDDATA diff --git a/doc/tutorials/DC3_model/fem/Nastran95/Nast95_structure_only.bdf b/doc/tutorials/DC3_model/fem/Nastran95/Nast95_structure_only.bdf new file mode 100644 index 0000000..2d14b8f --- /dev/null +++ b/doc/tutorials/DC3_model/fem/Nastran95/Nast95_structure_only.bdf @@ -0,0 +1,779 @@ +ID DC3,Aircraft +SOL 3 +DIAG 14,18 +ALTER 52 +OUTPUT2 KGG,GM,USET,,//0/11////*MSC* $ +ENDALTER +CEND +$ +METHOD=100 +ECHO=NONE +$ +BEGIN BULK +PARAM GRDPNT 0 +$------><------><------><------><------><------><------><------><------> +EIGR 100 MGIV 0.1 15.0 +$------><------><------><------><------><------><------><------><------> +$ +CBAR 3328001 33280013329000133290002 0.0 1. 0.0 +CBAR 3328002 33280023329000233290003 0.0 1. 0.0 +CBAR 3328003 33280033329000333290004 0.0 1. 0.0 +CBAR 3328004 33280043329000433290005 0.0 1. 0.0 +CBAR 3328005 33280053329000533290006 0.0 1. 0.0 +CBAR 3328006 33280063329000633290007 0.0 1. 0.0 +CBAR 3328007 33280073329000733290008 0.0 1. 0.0 +CBAR 3328008 33280083329000833290009 0.0 1. 0.0 +CBAR 3338001 33380013339000133390002 0.0 0.0 1. +CBAR 3338002 33380023339000233390003 0.0 0.0 1. +CBAR 3338003 33380033339000333390004 0.0 0.0 1. +CBAR 3338004 33380043339000433390005 0.0 0.0 1. +CBAR 3338005 33380053339000533390006 0.0 0.0 1. +CBAR 3338006 33380063339000633390007 0.0 0.0 1. +CBAR 3338007 33380073339000733390008 0.0 0.0 1. +CBAR 3348001 33480013349000133490002 0.0 0.0 1. +CBAR 3348002 33480023349000233490003 0.0 0.0 1. +CBAR 3348003 33480033349000333490004 0.0 0.0 1. +CBAR 3348004 33480043349000433490005 0.0 0.0 1. +CBAR 3348005 33480053349000533490006 0.0 0.0 1. +CBAR 3348006 33480063349000633490007 0.0 0.0 1. +CBAR 3348007 33480073349000733490008 0.0 0.0 1. +CBAR 5408001 54080015409000154090002 0.0 0.0 1. +CBAR 5408002 54080025409000254090003 0.0 0.0 1. +CBAR 5408003 54080035409000354090004 0.0 0.0 1. +CBAR 5408004 54080045409000454090005 0.0 0.0 1. +CBAR 5408005 54080055409000554090006 0.0 0.0 1. +CBAR 5408006 54080065409000654090007 0.0 0.0 1. +CBAR 5408007 54080075409000754090008 0.0 0.0 1. +CBAR 5408008 54080085409000854090009 0.0 0.0 1. +CBAR 5408009 54080095409000954090010 0.0 0.0 1. +CBAR 5408010 54080105409001054090011 0.0 0.0 1. +CBAR 5408011 54080115409001154090012 0.0 0.0 1. +CBAR 5408012 54080125409001254090013 0.0 0.0 1. +CBAR 5408013 54080135409001354090014 0.0 0.0 1. +CBAR 5408014 54080145409001454090015 0.0 0.0 1. +CBAR 5408015 54080155409001554090016 0.0 0.0 1. +CBAR 5408016 54080165409001654090017 0.0 0.0 1. +CBAR 5408017 54080175409001754090018 0.0 0.0 1. +CBAR 5408018 54080185409001854090019 0.0 0.0 1. +CBAR 5408019 54080195409001954090020 0.0 0.0 1. +CBAR 5408020 54080205409002054090021 0.0 0.0 1. +CBAR 5408021 54080215409002154090022 0.0 0.0 1. +CBAR 5408022 54080225409002254090023 0.0 0.0 1. +CBAR 5408023 54080235409002354090024 0.0 0.0 1. +CBAR 5408024 54080245409002454090025 0.0 0.0 1. +CBAR 5408025 54080255409002554090026 0.0 0.0 1. +CBAR 5408026 54080265409002654090027 0.0 0.0 1. +CBAR 5408027 54080275409002754090028 0.0 0.0 1. +CBAR 5408028 54080285409002854090029 0.0 0.0 1. +CBAR 5408029 54080295409002954090030 0.0 0.0 1. +CBAR 5408030 54080305409003054090031 0.0 0.0 1. +CBAR 6408001 64080016409000164090002 0.0 0.0 1. +CBAR 6408002 64080026409000264090003 0.0 0.0 1. +CBAR 6408003 64080036409000364090004 0.0 0.0 1. +CBAR 6408004 64080046409000464090005 0.0 0.0 1. +CBAR 6408005 64080056409000564090006 0.0 0.0 1. +CBAR 6408006 64080066409000664090007 0.0 0.0 1. +CBAR 6408007 64080076409000764090008 0.0 0.0 1. +CBAR 6408008 64080086409000864090009 0.0 0.0 1. +CBAR 6408009 64080096409000964090010 0.0 0.0 1. +CBAR 6408010 64080106409001064090011 0.0 0.0 1. +CBAR 6408011 64080116409001164090012 0.0 0.0 1. +CBAR 6408012 64080126409001264090013 0.0 0.0 1. +CBAR 6408013 64080136409001364090014 0.0 0.0 1. +CBAR 6408014 64080146409001464090015 0.0 0.0 1. +CBAR 6408015 64080156409001564090016 0.0 0.0 1. +CBAR 6408016 64080166409001664090017 0.0 0.0 1. +CBAR 6408017 64080176409001764090018 0.0 0.0 1. +CBAR 6408018 64080186409001864090019 0.0 0.0 1. +CBAR 6408019 64080196409001964090020 0.0 0.0 1. +CBAR 6408020 64080206409002064090021 0.0 0.0 1. +CBAR 6408021 64080216409002164090022 0.0 0.0 1. +CBAR 6408022 64080226409002264090023 0.0 0.0 1. +CBAR 6408023 64080236409002364090024 0.0 0.0 1. +CBAR 6408024 64080246409002464090025 0.0 0.0 1. +CBAR 6408025 64080256409002564090026 0.0 0.0 1. +CBAR 6408026 64080266409002664090027 0.0 0.0 1. +CBAR 6408027 64080276409002764090028 0.0 0.0 1. +CBAR 6408028 64080286409002864090029 0.0 0.0 1. +CBAR 6408029 64080296409002964090030 0.0 0.0 1. +CBAR 6408030 64080306409003064090031 0.0 0.0 1. +CONM2 110001 100001 0 19.313 0.0 0.0 0.0 +1 ++1 .358 0.0 0.0 0.0 0.0 0.0 +CONM2 110002 100002 0 141.008 0.0 0.0 0.0 +2 ++2 139.474 0.0 0.0 0.0 0.0 0.0 +CONM2 110003 100003 0 172.413 0.0 0.0 0.0 +3 ++3 254.959 0.0 0.0 0.0 0.0 0.0 +CONM2 110004 100004 0 184.19 0.0 0.0 0.0 +4 ++4 310.855 0.0 0.0 0.0 0.0 0.0 +CONM2 110005 100005 0 180.264 0.0 0.0 0.0 +5 ++5 291.4 0.0 0.0 0.0 0.0 0.0 +CONM2 110006 100006 0 178.301 0.0 0.0 0.0 +6 ++6 281.984 0.0 0.0 0.0 0.0 0.0 +CONM2 110007 100007 0 162.599 0.0 0.0 0.0 +7 ++7 213.852 0.0 0.0 0.0 0.0 0.0 +CONM2 110008 100008 0 146.896 0.0 0.0 0.0 +8 ++8 157.686 0.0 0.0 0.0 0.0 0.0 +CONM2 110009 100009 0 117.454 0.0 0.0 0.0 +9 ++9 80.606 0.0 0.0 0.0 0.0 0.0 +CONM2 110010 100010 0 74.272 0.0 0.0 0.0 +10 ++10 20.382 0.0 0.0 0.0 0.0 0.0 +CONM2 110011 100011 0 31.09 0.0 0.0 0.0 +11 ++11 1.495 0.0 0.0 0.0 0.0 0.0 +CONM2 332810133290001 0 13.375 0.0 0.0 0.0 +12 ++12 0.0 0.0 0.0 0.0 0.0 20.8867 +CONM2 332810233290002 0 23.065 0.0 0.0 0.0 +13 ++13 0.0 0.0 0.0 0.0 0.0 31.0587 +CONM2 332810333290003 0 19.654 0.0 0.0 0.0 +14 ++14 0.0 0.0 0.0 0.0 0.0 22.5508 +CONM2 332810433290004 0 16.515 0.0 0.0 0.0 +15 ++15 0.0 0.0 0.0 0.0 0.0 15.9235 +CONM2 332810533290005 0 13.65 0.0 0.0 0.0 +16 ++16 0.0 0.0 0.0 0.0 0.0 10.8769 +CONM2 332810633290006 0 11.057 0.0 0.0 0.0 +17 ++17 0.0 0.0 0.0 0.0 0.0 7.1371 +CONM2 332810733290007 0 8.737 0.0 0.0 0.0 +18 ++18 0.0 0.0 0.0 0.0 0.0 4.4562 +CONM2 332810833290008 0 6.69 0.0 0.0 0.0 +19 ++19 0.0 0.0 0.0 0.0 0.0 2.6126 +CONM2 332810933290009 0 2.458 0.0 0.0 0.0 +20 ++20 0.0 0.0 0.0 0.0 0.0 .7053 +CONM2 333810133390001 0 10.663 0.0 0.0 0.0 +21 ++21 0.0 0.0 8.6823 0.0 0.0 0.0 +CONM2 333810233390002 0 17.841 0.0 0.0 0.0 +22 ++22 0.0 0.0 12.5584 0.0 0.0 0.0 +CONM2 333810333390003 0 14.466 0.0 0.0 0.0 +23 ++23 0.0 0.0 8.5231 0.0 0.0 0.0 +CONM2 333810433390004 0 11.445 0.0 0.0 0.0 +24 ++24 0.0 0.0 5.3348 0.0 0.0 0.0 +CONM2 333810533390005 0 8.777 0.0 0.0 0.0 +25 ++25 0.0 0.0 3.1377 0.0 0.0 0.0 +CONM2 333810633390006 0 6.463 0.0 0.0 0.0 +26 ++26 0.0 0.0 1.7012 0.0 0.0 0.0 +CONM2 333810733390007 0 4.502 0.0 0.0 0.0 +27 ++27 0.0 0.0 .8284 0.0 0.0 0.0 +CONM2 333810833390008 0 1.442 0.0 0.0 0.0 +28 ++28 0.0 0.0 .1707 0.0 0.0 0.0 +CONM2 334810133490001 0 10.663 0.0 0.0 0.0 +29 ++29 0.0 0.0 8.6823 0.0 0.0 0.0 +CONM2 334810233490002 0 17.841 0.0 0.0 0.0 +30 ++30 0.0 0.0 12.5584 0.0 0.0 0.0 +CONM2 334810333490003 0 14.466 0.0 0.0 0.0 +31 ++31 0.0 0.0 8.5231 0.0 0.0 0.0 +CONM2 334810433490004 0 11.445 0.0 0.0 0.0 +32 ++32 0.0 0.0 5.3348 0.0 0.0 0.0 +CONM2 334810533490005 0 8.777 0.0 0.0 0.0 +33 ++33 0.0 0.0 3.1377 0.0 0.0 0.0 +CONM2 334810633490006 0 6.463 0.0 0.0 0.0 +34 ++34 0.0 0.0 1.7012 0.0 0.0 0.0 +CONM2 334810733490007 0 4.502 0.0 0.0 0.0 +35 ++35 0.0 0.0 .8284 0.0 0.0 0.0 +CONM2 334810833490008 0 1.442 0.0 0.0 0.0 +36 ++36 0.0 0.0 .1707 0.0 0.0 0.0 +CONM2 541000154090001 0 59.72 2.49282 5.97-18-.022564 +37 ++37 0.0 0.0 37.9722 0.0 0.0 0.0 +CONM2 541000254090002 0 56.44 2.49282 3.8-5-.022564 +38 ++38 0.0 0.0 75.9433 0.0 0.0 0.0 +CONM2 541000354090003 0 55.44 2.49282-4.000-5-.022564 +39 ++39 0.0 0.0 75.9433 0.0 0.0 0.0 +CONM2 541000454090004 0 55.44 2.49282 0.0-.022564 +40 ++40 0.0 0.0 75.6792 0.0 0.0 0.0 +CONM2 541000554090005 0 55.44 2.49282-4.000-5-.022564 +41 ++41 0.0 0.0 66.3853 0.0 0.0 0.0 +CONM2 541000654090006 0 55.44 2.49282 0.0-.022564 +42 ++42 0.0 0.0 75.9448 0.0 0.0 0.0 +CONM2 541000754090007 0 50.44 2.49282 4.-5-.022564 +43 ++43 0.0 0.0 85.2402 0.0 0.0 0.0 +CONM2 541000854090008 0 50.44 2.49282 0.0-.022564 +44 ++44 0.0 0.0 69.5011 0.0 0.0 0.0 +CONM2 541000954090009 0 46.711 2.21047-2.000-5-.021837 +45 ++45 0.0 0.0 55.533 0.0 0.0 0.0 +CONM2 541001054090010 0 40.074 2.34741-1.400-4 -.02121 +46 ++46 0.0 0.0 48.7313 0.0 0.0 0.0 +CONM2 541001154090011 0 37.527 2.27475-1.500-4-.020584 +47 ++47 0.0 0.0 42.5973 0.0 0.0 0.0 +CONM2 541001254090012 0 35.07 2.202 -1.7-4-.019957 +48 ++48 0.0 0.0 37.0827 0.0 0.0 0.0 +CONM2 541001354090013 0 22.702 2.12934 -1.9-4 -.01933 +49 ++49 0.0 0.0 32.1402 0.0 0.0 0.0 +CONM2 541001454090014 0 21.422 2.05659 -3.1-4-.018603 +50 ++50 0.0 0.0 27.7267 0.0 0.0 0.0 +CONM2 541001554090015 0 19.231 1.98383 -3.3-4-.017976 +51 ++51 0.0 0.0 23.7991 0.0 0.0 0.0 +CONM2 541001654090016 0 18.126 1.91118-3.500-4-.017349 +52 ++52 0.0 0.0 20.3172 0.0 0.0 0.0 +CONM2 541001754090017 0 16.108 1.83843-3.900-4-.016721 +53 ++53 0.0 0.0 17.2447 0.0 0.0 0.0 +CONM2 541001854090018 0 15.177 1.76578 -5.2-4-.016093 +54 ++54 0.0 0.0 14.5451 0.0 0.0 0.0 +CONM2 541001954090019 0 13.33 1.69303-5.600-4-.015365 +55 ++55 0.0 0.0 12.1843 0.0 0.0 0.0 +CONM2 541002054090020 0 12.569 1.62038-6.000-4-.014736 +56 ++56 0.0 0.0 10.1313 0.0 0.0 0.0 +CONM2 541002154090021 0 10.891 1.54763 -6.6-4-.014106 +57 ++57 0.0 0.0 8.3566 0.0 0.0 0.0 +CONM2 541002254090022 0 10.297 1.47488-8.100-4-.013477 +58 ++58 0.0 0.0 6.8312 0.0 0.0 0.0 +CONM2 541002354090023 0 8.786 1.40225 -9.-4-.012844 +59 ++59 0.0 0.0 5.5302 0.0 0.0 0.0 +CONM2 541002454090024 0 8.357 1.3295 -9.-4-.012113 +60 ++60 0.0 0.0 4.4292 0.0 0.0 0.0 +CONM2 541002554090025 0 7.01 1.25678 -1.1-3-.011473 +61 ++61 0.0 0.0 3.5048 0.0 0.0 0.0 +CONM2 541002654090026 0 6.744 1.18415 -1.2-3-.010841 +62 ++62 0.0 0.0 2.7359 0.0 0.0 0.0 +CONM2 541002754090027 0 5.558 1.11141 -1.2-3-.010206 +63 ++63 0.0 0.0 2.1038 0.0 0.0 0.0 +CONM2 541002854090028 0 4.453 1.03871-1.500-3-9.562-3 +64 ++64 0.0 0.0 1.5901 0.0 0.0 0.0 +CONM2 541002954090029 0 4.426 .96601 -1.7-3-8.915-3 +65 ++65 0.0 0.0 1.1782 0.0 0.0 0.0 +CONM2 541003054090030 0 3.478 .8934-2.000-3-8.155-3 +66 ++66 0.0 0.0 .8524 0.0 0.0 0.0 +CONM2 541003154090031 0 1.304 .8204-1.000-4 -7.69-3 +67 ++67 0.0 0.0 .3002 0.0 0.0 0.0 +CONM2 541100154100001 0 287.8 0.0 0.0 0.0 +68 ++68 73.257 0.0 0.0 0.0 0.0 0.0 +CONM2 541100254100002 0 470.95 0.0 0.0 0.0 +69 ++69 102.107 0.0 0.0 0.0 0.0 0.0 +CONM2 541100354100003 0 183.15 0.0 0.0 0.0 +70 ++70 33.408 0.0 0.0 0.0 0.0 0.0 +CONM2 641000164090001 0 59.72 2.49282-5.97-18-.022564 +71 ++71 0.0 0.0 37.9722 0.0 0.0 0.0 +CONM2 641000264090002 0 56.44 2.49282 -3.8-5-.022564 +72 ++72 0.0 0.0 75.9433 0.0 0.0 0.0 +CONM2 641000364090003 0 55.44 2.492824.0000-5-.022564 +73 ++73 0.0 0.0 75.9433 0.0 0.0 0.0 +CONM2 641000464090004 0 55.44 2.49282 0.0-.022564 +74 ++74 0.0 0.0 75.6792 0.0 0.0 0.0 +CONM2 641000564090005 0 55.44 2.492824.0000-5-.022564 +75 ++75 0.0 0.0 66.3853 0.0 0.0 0.0 +CONM2 641000664090006 0 55.44 2.49282 0.0-.022564 +76 ++76 0.0 0.0 75.9448 0.0 0.0 0.0 +CONM2 641000764090007 0 50.44 2.49282 -4.-5-.022564 +77 ++77 0.0 0.0 85.2402 0.0 0.0 0.0 +CONM2 641000864090008 0 50.44 2.49282 0.0-.022564 +78 ++78 0.0 0.0 69.5011 0.0 0.0 0.0 +CONM2 641000964090009 0 46.711 2.21046 -1.-5-.021839 +79 ++79 0.0 0.0 55.533 0.0 0.0 0.0 +CONM2 641001064090010 0 40.074 2.347397.0000-5-.021215 +80 ++80 0.0 0.0 48.7313 0.0 0.0 0.0 +CONM2 641001164090011 0 37.527 2.274736.0000-5-.020591 +81 ++81 0.0 0.0 42.5973 0.0 0.0 0.0 +CONM2 641001264090012 0 35.07 2.20197 4.-5-.019967 +82 ++82 0.0 0.0 37.0827 0.0 0.0 0.0 +CONM2 641001364090013 0 22.702 2.129312.0000-5-.019343 +83 ++83 0.0 0.0 32.1402 0.0 0.0 0.0 +CONM2 641001464090014 0 21.422 2.05655 1.1-4-.018618 +84 ++84 0.0 0.0 27.7267 0.0 0.0 0.0 +CONM2 641001564090015 0 19.231 1.983789.0000-5-.017994 +85 ++85 0.0 0.0 23.7991 0.0 0.0 0.0 +CONM2 641001664090016 0 18.126 1.911127.0000-5 -.01737 +86 ++86 0.0 0.0 20.3172 0.0 0.0 0.0 +CONM2 641001764090017 0 16.108 1.838367.0000-5-.016744 +87 ++87 0.0 0.0 17.2447 0.0 0.0 0.0 +CONM2 641001864090018 0 15.177 1.76571.6000-4 -.01612 +88 ++88 0.0 0.0 14.5451 0.0 0.0 0.0 +CONM2 641001964090019 0 13.33 1.69294 1.5-4-.015395 +89 ++89 0.0 0.0 12.1843 0.0 0.0 0.0 +CONM2 641002064090020 0 12.569 1.62028 1.5-4-.014769 +90 ++90 0.0 0.0 10.1313 0.0 0.0 0.0 +CONM2 641002164090021 0 10.891 1.54752 1.5-4-.014144 +91 ++91 0.0 0.0 8.3566 0.0 0.0 0.0 +CONM2 641002264090022 0 10.297 1.474762.5000-4-.013518 +92 ++92 0.0 0.0 6.8312 0.0 0.0 0.0 +CONM2 641002364090023 0 8.786 1.402123.0000-4-.012889 +93 ++93 0.0 0.0 5.5302 0.0 0.0 0.0 +CONM2 641002464090024 0 8.357 1.32935 2.-4-.012165 +94 ++94 0.0 0.0 4.4292 0.0 0.0 0.0 +CONM2 641002564090025 0 7.01 1.25661 3.-4-.011533 +95 ++95 0.0 0.0 3.5048 0.0 0.0 0.0 +CONM2 641002764090027 0 5.558 1.11122 3.-4-.010273 +96 ++96 0.0 0.0 2.1038 0.0 0.0 0.0 +CONM2 641002864090028 0 4.453 1.038474.0000-4-9.644-3 +97 ++97 0.0 0.0 1.5901 0.0 0.0 0.0 +CONM2 641002964090029 0 4.426 .965755.0000-4-9.004-3 +98 ++98 0.0 0.0 1.1782 0.0 0.0 0.0 +CONM2 641003064090030 0 3.478 .89315.0000-4-8.267-3 +99 ++99 0.0 0.0 .8524 0.0 0.0 0.0 +CONM2 641003164090031 0 1.304 .82041.0000-4 -7.69-3 +100 ++100 0.0 0.0 .3002 0.0 0.0 0.0 +CONM2 641100164100001 0 287.8 0.0 0.0 0.0 +101 ++101 73.257 0.0 0.0 0.0 0.0 0.0 +CONM2 641100264100002 0 470.95 0.0 0.0 0.0 +102 ++102 102.107 0.0 0.0 0.0 0.0 0.0 +CONM2 641100364100003 0 183.15 0.0 0.0 0.0 +103 ++103 33.408 0.0 0.0 0.0 0.0 0.0 +CONM2 410012664090026 0 6.744 1.18395 3.-4-.010907 +104 ++104 0.0 0.0 2.7359 0.0 0.0 0.0 +CORD2R 3329001 0 18.5153 -2.9-15 1.86699 18.5153 -1. 1.86699+105 ++105 18.9862 -.5 1.69894 +CORD2R 3339001 0 18.9631 -2.9-18 1.86699 18.9631 3.38-17 .866999+106 ++106 19.4415 .145628 1.36699 +CORD2R 3349001 0 18.9631 2.99-18 1.86699 18.9631 6.27-17 2.867+107 ++107 19.4415 -.14562 2.367 +CORD2R 5409001 0 8.01838 -5.9-18 .197264 8.01838 1.56-18 -.80273+108 ++108 8.51838 -4.8-16 -.30273 +CORD2R 6409001 0 8.01838 5.97-18 .197264 8.01838 1.35-17 1.19726+109 ++109 8.51838 4.92-16 .697264 +GRID 100001 2. 0.0 1.55 +GRID 100002 3.9431 0.0 1.55 +GRID 100003 5.8862 0.0 1.55 +GRID 100004 7.8293 0.0 1.55 +GRID 100005 9.7724 0.0 1.55 +GRID 100006 11.7155 0.0 1.55 +GRID 100007 13.6586 0.0 1.55 +GRID 100008 15.6017 0.0 1.55 +GRID 100009 17.5448 0.0 1.55 +GRID 100010 19.4879 0.0 1.55 +GRID 100011 21.431 0.0 1.55 +GRID 33290001 18.5153-2.95-15 1.86699 +GRID 33290002 18.6428-2.58-15 2.22425 +GRID 33290003 18.7703-2.22-15 2.5815 +GRID 33290004 18.8978-1.85-15 2.93875 +GRID 33290005 19.0253-1.48-15 3.296 +GRID 33290006 19.1528-1.11-15 3.65323 +GRID 33290007 19.2803-7.49-16 4.01048 +GRID 33290008 19.4078-3.82-16 4.36775 +GRID 33290009 19.5352-1.46-17 4.72498 +GRID 33290101 17.416 0.0 1.86699 +GRID 33290102 17.6219 0.0 2.22425 +GRID 33290103 17.8279 0.0 2.5815 +GRID 33290104 18.034 0.0 2.93875 +GRID 33290105 18.2399 0.0 3.296 +GRID 33290106 18.4459 0.0 3.65323 +GRID 33290107 18.6519 0.0 4.01048 +GRID 33290108 18.8579 0.0 4.36775 +GRID 33290109 19.0639 0.0 4.72498 +GRID 33290201 21.26 0.0 1.86699 +GRID 33290202 21.1914 0.0 2.22425 +GRID 33290203 21.1229 0.0 2.5815 +GRID 33290204 21.0545 0.0 2.93875 +GRID 33290205 20.986 0.0 3.296 +GRID 33290206 20.9175 0.0 3.65323 +GRID 33290207 20.849 0.0 4.01048 +GRID 33290208 20.7805 0.0 4.36775 +GRID 33290209 20.712 0.0 4.72498 +GRID 33390001 18.9631-2.99-18 1.86699 +GRID 33390002 19.136-.567768 1.86699 +GRID 33390003 19.3204-1.17339 1.86699 +GRID 33390004 19.5048-1.77901 1.86699 +GRID 33390005 19.6892-2.38462 1.86699 +GRID 33390006 19.8736-2.99025 1.86699 +GRID 33390007 20.0579-3.59587 1.86699 +GRID 33390008 20.2436-4.20568 1.86699 +GRID 33390101 18.02-5.94-18 1.86699 +GRID 33390102 18.2727-.567768 1.86699 +GRID 33390103 18.5422-1.17339 1.86699 +GRID 33390104 18.8118-1.77901 1.86699 +GRID 33390105 19.0814-2.38462 1.86699 +GRID 33390106 19.3509-2.99025 1.86699 +GRID 33390107 19.6205-3.59587 1.86699 +GRID 33390108 19.8919-4.20568 1.86699 +GRID 33390201 21.0049-5.94-18 1.86699 +GRID 33390202 21.0049-.567768 1.86699 +GRID 33390203 21.0049-1.17339 1.86699 +GRID 33390204 21.0049-1.77901 1.86699 +GRID 33390205 21.0049-2.38462 1.86699 +GRID 33390206 21.0049-2.99025 1.86699 +GRID 33390207 21.0049-3.59587 1.86699 +GRID 33390208 21.0049-4.20568 1.86699 +GRID 33490001 18.9631 2.99-18 1.86699 +GRID 33490002 19.136 .567768 1.86699 +GRID 33490003 19.3204 1.17339 1.86699 +GRID 33490004 19.5048 1.77901 1.86699 +GRID 33490005 19.6892 2.38462 1.86699 +GRID 33490006 19.8736 2.99025 1.86699 +GRID 33490007 20.0579 3.59587 1.86699 +GRID 33490008 20.2436 4.20568 1.86699 +GRID 33490101 18.02 5.94-18 1.86699 +GRID 33490102 18.2727 .567768 1.86699 +GRID 33490103 18.5422 1.17339 1.86699 +GRID 33490104 18.8118 1.77901 1.86699 +GRID 33490105 19.0814 2.38462 1.86699 +GRID 33490106 19.3509 2.99025 1.86699 +GRID 33490107 19.6205 3.59587 1.86699 +GRID 33490108 19.8919 4.20568 1.86699 +GRID 33490201 21.0049 5.94-18 1.86699 +GRID 33490202 21.0049 .567768 1.86699 +GRID 33490203 21.0049 1.17339 1.86699 +GRID 33490204 21.0049 1.77901 1.86699 +GRID 33490205 21.0049 2.38462 1.86699 +GRID 33490206 21.0049 2.99025 1.86699 +GRID 33490207 21.0049 3.59587 1.86699 +GRID 33490208 21.0049 4.20568 1.86699 +GRID 54090001 8.01838-5.97-18 .197264 +GRID 54090002 8.01838-.526238 .197264 +GRID 54090003 8.01838-1.05246 .197264 +GRID 54090004 8.01838 -1.5787 .197264 +GRID 54090005 8.01838-2.10126 .197264 +GRID 54090006 8.01838 -2.4987 .197264 +GRID 54090007 8.01838-3.15374 .197264 +GRID 54090008 8.01838 -3.68 .197264 +GRID 54090009 8.11153-4.11688 .229737 +GRID 54090010 8.20469-4.55376 .26221 +GRID 54090011 8.29785-4.99065 .294684 +GRID 54090012 8.391-5.42753 .327157 +GRID 54090013 8.48416-5.86441 .35963 +GRID 54090014 8.57731-6.30129 .392103 +GRID 54090015 8.67047-6.73817 .424576 +GRID 54090016 8.76362-7.17505 .457049 +GRID 54090017 8.85677-7.61191 .489521 +GRID 54090018 8.94992-8.04878 .521993 +GRID 54090019 9.04307-8.48564 .554465 +GRID 54090020 9.13622 -8.9225 .586936 +GRID 54090021 9.22937-9.35934 .619406 +GRID 54090022 9.32252-9.79619 .651877 +GRID 54090023 9.41565-10.2329 .684344 +GRID 54090024 9.5088-10.6698 .716813 +GRID 54090025 9.60192-11.1065 .749273 +GRID 54090026 9.69505-11.5433 .781741 +GRID 54090027 9.78819-11.9801 .814206 +GRID 54090028 9.88129-12.4167 .846662 +GRID 54090029 9.97439-12.8533 .879115 +GRID 54090030 10.0674-13.2898 .911555 +GRID 54090031 10.1607-13.7275 .94409 +GRID 54090101 6.88999-1.11-15 .150999 +GRID 54090102 6.88999-.526238 .150999 +GRID 54090103 6.88999-1.05246 .150999 +GRID 54090104 6.88999 -1.5787 .150999 +GRID 54090105 6.88999-2.10126 .150999 +GRID 54090106 6.88999 -2.4987 .150999 +GRID 54090107 6.88999-3.15374 .150999 +GRID 54090108 6.88999 -3.68 .150999 +GRID 54090109 7.01607 -4.1169 .184822 +GRID 54090110 7.14214-4.55381 .218644 +GRID 54090111 7.26822-4.99072 .252467 +GRID 54090112 7.39429-5.42763 .286289 +GRID 54090113 7.52036-5.86454 .320111 +GRID 54090114 7.64643-6.30144 .353933 +GRID 54090115 7.7725-6.73834 .387755 +GRID 54090116 7.89858-7.17525 .421577 +GRID 54090117 8.02464-7.61214 .455399 +GRID 54090118 8.15072-8.04904 .48922 +GRID 54090119 8.27678-8.48593 .523041 +GRID 54090120 8.40285-8.92283 .556862 +GRID 54090121 8.52892 -9.3597 .590682 +GRID 54090122 8.65498-9.79659 .624503 +GRID 54090123 8.78104-10.2334 .658321 +GRID 54090124 8.9071-10.6703 .69214 +GRID 54090125 9.03313 -11.107 .725952 +GRID 54090126 9.15919-11.5439 .759771 +GRID 54090127 9.28525-11.9808 .793589 +GRID 54090128 9.41128-12.4175 .827399 +GRID 54090129 9.5373-12.8542 .861207 +GRID 54090130 9.66328-13.2908 .895006 +GRID 54090131 9.78999-13.7299 .929 +GRID 54090201 11.21-1.11-15 .150999 +GRID 54090202 11.21-.526238 .150999 +GRID 54090203 11.21-1.05246 .150999 +GRID 54090204 11.21 -1.5787 .150999 +GRID 54090205 11.21-2.10126 .150999 +GRID 54090206 11.21 -2.4987 .150999 +GRID 54090207 11.21-3.15374 .150999 +GRID 54090208 11.21 -3.68 .150999 +GRID 54090209 11.21 -4.1169 .184822 +GRID 54090210 11.21-4.55381 .218644 +GRID 54090211 11.21-4.99072 .252467 +GRID 54090212 11.21-5.42763 .286289 +GRID 54090213 11.21-5.86454 .320111 +GRID 54090214 11.21-6.30144 .353933 +GRID 54090215 11.21-6.73834 .387755 +GRID 54090216 11.21-7.17525 .421577 +GRID 54090217 11.21-7.61214 .455399 +GRID 54090218 11.21-8.04904 .48922 +GRID 54090219 11.21-8.48593 .523041 +GRID 54090220 11.21-8.92283 .556862 +GRID 54090221 11.21 -9.3597 .590682 +GRID 54090222 11.21-9.79659 .624503 +GRID 54090223 11.21-10.2334 .658321 +GRID 54090224 11.21-10.6703 .69214 +GRID 54090225 11.21 -11.107 .725952 +GRID 54090226 11.21-11.5439 .759771 +GRID 54090227 11.21-11.9808 .793589 +GRID 54090228 11.21-12.4175 .827399 +GRID 54090229 11.21-12.8542 .861207 +GRID 54090230 11.21-13.2908 .895006 +GRID 54090231 11.21-13.7254 .92865 +GRID 54100001 5.129 -2.745 0.0 +GRID 54100002 5.897 -2.745 0.0 +GRID 54100003 7.105 -2.745 0.0 +GRID 64090001 8.01838 5.97-18 .197264 +GRID 64090002 8.01838 .526238 .197264 +GRID 64090003 8.01838 1.05246 .197264 +GRID 64090004 8.01838 1.5787 .197264 +GRID 64090005 8.01838 2.10126 .197264 +GRID 64090006 8.01838 2.4987 .197264 +GRID 64090007 8.01838 3.15374 .197264 +GRID 64090008 8.01838 3.68 .197264 +GRID 64090009 8.11154 4.11691 .229739 +GRID 64090010 8.20471 4.55383 .262215 +GRID 64090011 8.29787 4.99074 .294691 +GRID 64090012 8.39103 5.42766 .327167 +GRID 64090013 8.48419 5.86458 .359643 +GRID 64090014 8.57735 6.30149 .392118 +GRID 64090015 8.67052 6.73841 .424594 +GRID 64090016 8.76368 7.17533 .45707 +GRID 64090017 8.85684 7.61223 .489544 +GRID 64090018 8.95 8.04914 .52202 +GRID 64090019 9.04316 8.48605 .554495 +GRID 64090020 9.13632 8.92295 .586969 +GRID 64090021 9.22948 9.35985 .619444 +GRID 64090022 9.32264 9.79675 .651918 +GRID 64090023 9.41578 10.2335 .684389 +GRID 64090024 9.50895 10.6705 .716865 +GRID 64090025 9.60209 11.1073 .749333 +GRID 64090026 9.69525 11.5442 .781807 +GRID 64090027 9.78838 11.981 .814273 +GRID 64090028 9.88153 12.4178 .846744 +GRID 64090029 9.97465 12.8545 .879204 +GRID 64090030 10.0677 13.2913 .911667 +GRID 64090031 10.1607 13.7275 .94409 +GRID 64090101 6.88999 1.11-15 .150999 +GRID 64090102 6.88999 .526238 .150999 +GRID 64090103 6.88999 1.05246 .150999 +GRID 64090104 6.88999 1.5787 .150999 +GRID 64090105 6.88999 2.10126 .150999 +GRID 64090106 6.88999 2.4987 .150999 +GRID 64090107 6.88999 3.15374 .150999 +GRID 64090108 6.88999 3.68 .150999 +GRID 64090109 7.01608 4.11693 .184824 +GRID 64090110 7.14216 4.55388 .218649 +GRID 64090111 7.26824 4.99081 .252474 +GRID 64090112 7.39433 5.42776 .286299 +GRID 64090113 7.52041 5.86471 .320124 +GRID 64090114 7.64649 6.30164 .353949 +GRID 64090115 7.77257 6.73858 .387774 +GRID 64090116 7.89866 7.17553 .421599 +GRID 64090117 8.02474 7.61246 .455423 +GRID 64090118 8.15082 8.0494 .489248 +GRID 64090119 8.2769 8.48635 .523073 +GRID 64090120 8.40298 8.92328 .556897 +GRID 64090121 8.52906 9.36021 .590722 +GRID 64090122 8.65514 9.79715 .624547 +GRID 64090123 8.78121 10.234 .658367 +GRID 64090124 8.9073 10.671 .692194 +GRID 64090125 9.03336 11.1078 .726014 +GRID 64090126 9.15945 11.5448 .75984 +GRID 64090127 9.28551 11.9817 .793659 +GRID 64090128 9.41159 12.4186 .827484 +GRID 64090129 9.53765 12.8554 .8613 +GRID 64090130 9.66372 13.2923 .895122 +GRID 64090131 9.78999 13.7299 .929 +GRID 64090201 11.21 1.11-15 .150999 +GRID 64090202 11.21 .526238 .150999 +GRID 64090203 11.21 1.05246 .150999 +GRID 64090204 11.21 1.5787 .150999 +GRID 64090205 11.21 2.10126 .150999 +GRID 64090206 11.21 2.4987 .150999 +GRID 64090207 11.21 3.15374 .150999 +GRID 64090208 11.21 3.68 .150999 +GRID 64090209 11.21 4.11693 .184824 +GRID 64090210 11.21 4.55388 .218649 +GRID 64090211 11.21 4.99081 .252474 +GRID 64090212 11.21 5.42776 .286299 +GRID 64090213 11.21 5.86471 .320124 +GRID 64090214 11.21 6.30164 .353949 +GRID 64090215 11.21 6.73858 .387774 +GRID 64090216 11.21 7.17553 .421599 +GRID 64090217 11.21 7.61246 .455423 +GRID 64090218 11.21 8.0494 .489248 +GRID 64090219 11.21 8.48635 .523073 +GRID 64090220 11.21 8.92328 .556897 +GRID 64090221 11.21 9.36021 .590722 +GRID 64090222 11.21 9.79715 .624547 +GRID 64090223 11.21 10.234 .658367 +GRID 64090224 11.21 10.671 .692194 +GRID 64090225 11.21 11.1078 .726014 +GRID 64090226 11.21 11.5448 .75984 +GRID 64090227 11.21 11.9817 .793659 +GRID 64090228 11.21 12.4186 .827484 +GRID 64090229 11.21 12.8554 .8613 +GRID 64090230 11.21 13.2923 .895122 +GRID 64090231 11.21 13.7254 .92865 +GRID 64100001 5.129 2.745 0.0 +GRID 64100002 5.897 2.745 0.0 +GRID 64100003 7.105 2.745 0.0 +MAT1 332001 7.+10 2.69+10 .3 0.0 +MAT1 333001 7.+10 2.69+10 .3 0.0 +MAT1 334001 7.+10 2.69+10 .3 0.0 +MAT1 540001 7.+10 2.69+10 .3 0.0 +MAT1 640001 7.+10 2.69+10 .3 0.0 +PBAR 3328001 332001 .02567 .0006 .03703 .0376 0.0 +PBAR 3328002 332001 .02201 .000371 .02726 .02769 0.0 +PBAR 3328003 332001 .01864 2.662-4 .01956 .01987 0.0 +PBAR 3328004 332001 .01554 1.855-4 .01363 .01384 0.0 +PBAR 3328005 332001 .01273 1.247-4 .009158 .009303 0.0 +PBAR 3328006 332001 .0102 8.02-5 .005894 .005987 0.0 +PBAR 3328007 332001 .007948 .0001 .00359 .00365 0.0 +PBAR 3328008 332001 .005979 2.787-5 .002045 .002078 0.0 +PBAR 3338001 333001 .01214 .0002 .00942 .00959 0.0 +PBAR 3338002 333001 .01001 9.633-5 .00643 .00654 0.0 +PBAR 3338003 333001 .008032 6.214-5 .004149 .00422 0.0 +PBAR 3338004 333001 .006268 3.796-5 .002537 .002581 0.0 +PBAR 3338005 333001 .004724 2.164-5 .001449 .001474 0.0 +PBAR 3338006 333001 .003399 1.132-5 .00075 .000769 0.0 +PBAR 3338007 333001 .00229 5.236-6 3.431-4 3.547-4 0.0 +PBAR 3348001 334001 .01214 .0002 .00942 .00959 0.0 +PBAR 3348002 334001 .01001 9.633-5 .00643 .00654 0.0 +PBAR 3348003 334001 .008032 6.214-5 .004149 .00422 0.0 +PBAR 3348004 334001 .006268 3.796-5 .002537 .002581 0.0 +PBAR 3348005 334001 .004724 2.164-5 .001449 .001474 0.0 +PBAR 3348006 334001 .003399 1.132-5 .00075 .000769 0.0 +PBAR 3348007 334001 .00229 5.236-6 3.431-4 3.547-4 0.0 +PBAR 5408001 540001 .03553 4.375-4 .002553 .002663 0.0 +PBAR 5408002 540001 .03553 4.375-4 .002553 .002663 0.0 +PBAR 5408003 540001 .03553 4.375-4 .002553 .002663 0.0 +PBAR 5408004 540001 .03553 4.375-4 .002553 .002663 0.0 +PBAR 5408005 540001 .03553 4.375-4 .002553 .002663 0.0 +PBAR 5408006 540001 .03553 4.376-4 .002553 .002663 0.0 +PBAR 5408007 540001 .03553 4.375-4 .002553 .002663 0.0 +PBAR 5408008 540001 .03446 4.039-4 .002403 .002504 0.0 +PBAR 5408009 540001 .03237 .000341 .002116 .002201 0.0 +PBAR 5408010 540001 .03034 2.866-4 .001857 .001928 0.0 +PBAR 5408011 540001 .02838 2.396-4 .001622 .001682 0.0 +PBAR 5408012 540001 .0265 1.994-4 .001412 .001462 0.0 +PBAR 5408013 540001 .02468 1.649-4 .001223 .001264 0.0 +PBAR 5408014 540001 .02293 1.356-4 .001054 .001088 0.0 +PBAR 5408015 540001 .02125 1.108-4 9.038-4 9.315-4 0.0 +PBAR 5408016 540001 .01964 8.989-5 7.706-4 7.931-4 0.0 +PBAR 5408017 540001 .0181 7.242-5 6.531-4 6.712-4 0.0 +PBAR 5408018 540001 .01662 5.788-5 5.499-4 5.644-4 0.0 +PBAR 5408019 540001 .01521 4.589-5 4.597-4 4.712-4 0.0 +PBAR 5408020 540001 .01386 3.605-5 3.813-4 3.904-4 0.0 +PBAR 5408021 540001 .01258 2.805-5 3.137-4 3.207-4 0.0 +PBAR 5408022 540001 .01137 2.159-5 2.556-4 .000261 0.0 +PBAR 5408023 540001 .01022 1.642-5 2.062-4 2.103-4 0.0 +PBAR 5408024 540001 .009136 1.234-5 1.644-4 1.675-4 0.0 +PBAR 5408025 540001 .008114 9.14-6 1.295-4 1.318-4 0.0 +PBAR 5408026 540001 .007156 6.66-6 1.005-4 1.022-4 0.0 +PBAR 5408027 540001 .00626 4.78-6 7.681-5 7.801-5 0.0 +PBAR 5408028 540001 .005426 3.36-6 5.762-5 5.846-5 0.0 +PBAR 5408029 540001 .004654 2.3-6 4.233-5 4.291-5 0.0 +PBAR 5408030 540001 .003943 1.6-6 3.035-5 3.074-5 0.0 +PBAR 6408001 640001 .03553 4.375-4 .002553 .002663 0.0 +PBAR 6408002 640001 .03553 4.375-4 .002553 .002663 0.0 +PBAR 6408003 640001 .03553 4.375-4 .002553 .002663 0.0 +PBAR 6408004 640001 .03553 4.375-4 .002553 .002663 0.0 +PBAR 6408005 640001 .03553 4.375-4 .002553 .002663 0.0 +PBAR 6408006 640001 .03553 4.376-4 .002553 .002663 0.0 +PBAR 6408007 640001 .03553 4.375-4 .002553 .002663 0.0 +PBAR 6408008 640001 .03446 4.039-4 .002403 .002504 0.0 +PBAR 6408009 640001 .03237 .000341 .002116 .002201 0.0 +PBAR 6408010 640001 .03034 2.866-4 .001857 .001928 0.0 +PBAR 6408011 640001 .02838 2.396-4 .001622 .001682 0.0 +PBAR 6408012 640001 .0265 1.994-4 .001412 .001462 0.0 +PBAR 6408013 640001 .02468 1.649-4 .001223 .001264 0.0 +PBAR 6408014 640001 .02293 1.356-4 .001054 .001088 0.0 +PBAR 6408015 640001 .02125 1.108-4 9.038-4 9.315-4 0.0 +PBAR 6408016 640001 .01964 8.989-5 7.706-4 7.931-4 0.0 +PBAR 6408017 640001 .0181 7.242-5 6.531-4 6.712-4 0.0 +PBAR 6408018 640001 .01662 5.788-5 5.499-4 5.644-4 0.0 +PBAR 6408019 640001 .01521 4.589-5 4.597-4 4.712-4 0.0 +PBAR 6408020 640001 .01386 3.605-5 3.813-4 3.904-4 0.0 +PBAR 6408021 640001 .01258 2.805-5 3.137-4 3.207-4 0.0 +PBAR 6408022 640001 .01137 2.159-5 2.556-4 .000261 0.0 +PBAR 6408023 640001 .01022 1.642-5 2.062-4 2.103-4 0.0 +PBAR 6408024 640001 .009136 1.234-5 1.644-4 1.675-4 0.0 +PBAR 6408025 640001 .008114 9.14-6 1.295-4 1.318-4 0.0 +PBAR 6408026 640001 .007156 6.66-6 1.005-4 1.022-4 0.0 +PBAR 6408027 640001 .00626 4.78-6 7.681-5 7.801-5 0.0 +PBAR 6408028 640001 .005426 3.36-6 5.762-5 5.846-5 0.0 +PBAR 6408029 640001 .004654 2.3-6 4.233-5 4.291-5 0.0 +PBAR 6408030 640001 .003943 1.6-6 3.035-5 3.074-5 0.0 +CRBE2 100000 100004 123456 100001 100002 100003 100005 100006+110 ++110 100007 100008 100009 100010 100011 +CRBE2 200001 100004 1234566409000154090001 +CRBE2 200002 100010 1234563349000133390001 +CRBE2 200003 100010 12345633290001 +CRBE2 3329010133290001 1234563329010133290201 +CRBE2 3329010233290002 1234563329010233290202 +CRBE2 3329010333290003 1234563329010333290203 +CRBE2 3329010433290004 1234563329010433290204 +CRBE2 3329010533290005 1234563329010533290205 +CRBE2 3329010633290006 1234563329010633290206 +CRBE2 3329010733290007 1234563329010733290207 +CRBE2 3329010833290008 1234563329010833290208 +CRBE2 3329010933290009 1234563329010933290209 +CRBE2 3339010133390001 1234563339010133390201 +CRBE2 3339010233390002 1234563339010233390202 +CRBE2 3339010333390003 1234563339010333390203 +CRBE2 3339010433390004 1234563339010433390204 +CRBE2 3339010533390005 1234563339010533390205 +CRBE2 3339010633390006 1234563339010633390206 +CRBE2 3339010733390007 1234563339010733390207 +CRBE2 3339010833390008 1234563339010833390208 +CRBE2 3349010133490001 1234563349010133490201 +CRBE2 3349010233490002 1234563349010233490202 +CRBE2 3349010333490003 1234563349010333490203 +CRBE2 3349010433490004 1234563349010433490204 +CRBE2 3349010533490005 1234563349010533490205 +CRBE2 3349010633490006 1234563349010633490206 +CRBE2 3349010733490007 1234563349010733490207 +CRBE2 3349010833490008 1234563349010833490208 +CRBE2 5409010154090001 1234565409010154090201 +CRBE2 5409010254090002 1234565409010254090202 +CRBE2 5409010354090003 1234565409010354090203 +CRBE2 5409010454090004 1234565409010454090204 +CRBE2 5409010554090005 1234565409010554090205 +CRBE2 5409010654090006 1234565409010654090206 +CRBE2 5409010754090007 1234565409010754090207 +CRBE2 5409010854090008 1234565409010854090208 +CRBE2 5409010954090009 1234565409010954090209 +CRBE2 5409011054090010 1234565409011054090210 +CRBE2 5409011154090011 1234565409011154090211 +CRBE2 5409011254090012 1234565409011254090212 +CRBE2 5409011354090013 1234565409011354090213 +CRBE2 5409011454090014 1234565409011454090214 +CRBE2 5409011554090015 1234565409011554090215 +CRBE2 5409011654090016 1234565409011654090216 +CRBE2 5409011754090017 1234565409011754090217 +CRBE2 5409011854090018 1234565409011854090218 +CRBE2 5409011954090019 1234565409011954090219 +CRBE2 5409012054090020 1234565409012054090220 +CRBE2 5409012154090021 1234565409012154090221 +CRBE2 5409012254090022 1234565409012254090222 +CRBE2 5409012354090023 1234565409012354090223 +CRBE2 5409012454090024 1234565409012454090224 +CRBE2 5409012554090025 1234565409012554090225 +CRBE2 5409012654090026 1234565409012654090226 +CRBE2 5409012754090027 1234565409012754090227 +CRBE2 5409012854090028 1234565409012854090228 +CRBE2 5409012954090029 1234565409012954090229 +CRBE2 5409013054090030 1234565409013054090230 +CRBE2 5409013154090031 1234565409013154090231 +CRBE2 5410000054090006 123456541000015410000254100003 +CRBE2 6409010164090001 1234566409010164090201 +CRBE2 6409010264090002 1234566409010264090202 +CRBE2 6409010364090003 1234566409010364090203 +CRBE2 6409010464090004 1234566409010464090204 +CRBE2 6409010564090005 1234566409010564090205 +CRBE2 6409010664090006 1234566409010664090206 +CRBE2 6409010764090007 1234566409010764090207 +CRBE2 6409010864090008 1234566409010864090208 +CRBE2 6409010964090009 1234566409010964090209 +CRBE2 6409011064090010 1234566409011064090210 +CRBE2 6409011164090011 1234566409011164090211 +CRBE2 6409011264090012 1234566409011264090212 +CRBE2 6409011364090013 1234566409011364090213 +CRBE2 6409011464090014 1234566409011464090214 +CRBE2 6409011564090015 1234566409011564090215 +CRBE2 6409011664090016 1234566409011664090216 +CRBE2 6409011764090017 1234566409011764090217 +CRBE2 6409011864090018 1234566409011864090218 +CRBE2 6409011964090019 1234566409011964090219 +CRBE2 6409012064090020 1234566409012064090220 +CRBE2 6409012164090021 1234566409012164090221 +CRBE2 6409012264090022 1234566409012264090222 +CRBE2 6409012364090023 1234566409012364090223 +CRBE2 6409012464090024 1234566409012464090224 +CRBE2 6409012564090025 1234566409012564090225 +CRBE2 6409012664090026 1234566409012664090226 +CRBE2 6409012764090027 1234566409012764090227 +CRBE2 6409012864090028 1234566409012864090228 +CRBE2 6409012964090029 1234566409012964090229 +CRBE2 6409013064090030 1234566409013064090230 +CRBE2 6409013164090031 1234566409013164090231 +CRBE2 6410000064090006 123456641000016410000264100003 +$ +ENDDATA +$ diff --git a/doc/tutorials/DC3_model/fem/NastranSOL103/SOL103_M3.bdf b/doc/tutorials/DC3_model/fem/NastranSOL103/SOL103_M3.bdf new file mode 100644 index 0000000..446087a --- /dev/null +++ b/doc/tutorials/DC3_model/fem/NastranSOL103/SOL103_M3.bdf @@ -0,0 +1,44 @@ +NASTRAN OP2NEW=0 +$ Direct Text Input for File Management Section +ASSIGN OUTPUT2 = 'uset.op2', UNIT = 12 +$ Direct Text Input for Executive Control +MALTER 'AFTER UPSTREAM SUPERELEMENT MATRIX AND LOAD ASSEMBLY'$ +CRDB_MTX MGG//'MGG' $ +CRDB_MTX KGG//'KGG' $ +ENDALTER $ +MALTER 'MALTER:AFTER SUPERELEMENT MATRIX AND LOAD REDUCTION TO A-SET' $ +CRDB_MTX GM//'GM' $ +OUTPUT2 USET//0/12///'USET' $ +ENDALTER $ +ECHOON $ +$ +SOL 103 +CEND +$ +METHOD=100 +ECHO=NONE +$ +BEGIN BULK +PARAM GRDPNT 0 +$------><------><------><------><------><------><------><------><------> +EIGRL 100 100 +$------><------><------><------><------><------><------><------><------> +HDF5OUT PRCISION 64 CMPRMTHD NONE MTX YES +$ +$----------------------------------------------------------------------- +$ Structural model +include '../assembly/structure_only.bdf' +$ +$ Non-structural masses +include '../nonstructural_masses/systems-mass.csv' +include '../nonstructural_masses/crew_and_equipment-mass.csv' +$ +$ Low fuel +include '../nonstructural_masses/fueltanks_low_mi.SCONM2' +include '../nonstructural_masses/fueltanks_low_mi.SCONM2_mrd' +$ +$ Payload mass +include '../nonstructural_masses/payload.csv' +$ +ENDDATA +$ diff --git a/doc/tutorials/DC3_model/fem/NastranSOL103/SOL103_structure_only.bdf b/doc/tutorials/DC3_model/fem/NastranSOL103/SOL103_structure_only.bdf new file mode 100644 index 0000000..4d669bb --- /dev/null +++ b/doc/tutorials/DC3_model/fem/NastranSOL103/SOL103_structure_only.bdf @@ -0,0 +1,33 @@ +NASTRAN OP2NEW=0 +$ Direct Text Input for File Management Section +ASSIGN OUTPUT2 = 'uset.op2', UNIT = 12 +$ Direct Text Input for Executive Control +MALTER 'AFTER UPSTREAM SUPERELEMENT MATRIX AND LOAD ASSEMBLY'$ +CRDB_MTX MGG//'MGG' $ +CRDB_MTX KGG//'KGG' $ +ENDALTER $ +MALTER 'MALTER:AFTER SUPERELEMENT MATRIX AND LOAD REDUCTION TO A-SET' $ +CRDB_MTX GM//'GM' $ +OUTPUT2 USET//0/12///'USET' $ +ENDALTER $ +ECHOON $ +$ +SOL 103 +CEND +$ +METHOD=100 +ECHO=NONE +$ +BEGIN BULK +PARAM GRDPNT 0 +$------><------><------><------><------><------><------><------><------> +EIGRL 100 100 +$------><------><------><------><------><------><------><------><------> +HDF5OUT PRCISION 64 CMPRMTHD NONE MTX YES +$ +$----------------------------------------------------------------------- +$ Structural model +include '../assembly/structure_only.bdf' +$ +ENDDATA +$ diff --git a/doc/tutorials/DC3_model/fem/assembly/structure_only.bdf b/doc/tutorials/DC3_model/fem/assembly/structure_only.bdf new file mode 100644 index 0000000..cdc5bac --- /dev/null +++ b/doc/tutorials/DC3_model/fem/assembly/structure_only.bdf @@ -0,0 +1,72 @@ +$ +$----------------------------------------------------------------------- +$ Interface FUS/W-S, FUS/W-P +$------><------><------><------><------><------><------><------><------> +RBE2 200001 100004 1234566409000154090001 +$ +$ Interface FUS/HTP-S, FUS/HTP-P +$------><------><------><------><------><------><------><------><------> +RBE2 200002 100010 1234563349000133390001 +$ +$ Interface FUS/VTP +$------><------><------><------><------><------><------><------><------> +RBE2 200003 100010 12345633290001 +$ +$ +$----------------------------------------------------------------------- +$ Fuselage +include '../export_FUS.csv' +$----------------------------------------------------------------------- +$ Left-wing +$ LRA +include '../left-wing/left-wing.GRID_LREFAX_5400001' +include '../left-wing/left-wing.RBE2_LREFAX_5400001' +include '../left-wing/left-wing.CORD2R_LREFAX' +$ bdf model +include '../left-wing/left-wing.MAT_ZR' +include '../left-wing/export_left-wing.csv' +$----------------------------------------------------------------------- +$ Right-wing +$ LRA +include '../right-wing/right-wing.GRID_LREFAX_6400001' +include '../right-wing/right-wing.RBE2_LREFAX_6400001' +include '../right-wing/right-wing.CORD2R_LREFAX' +$ bdf model +include '../right-wing/right-wing.MAT_ZR' +include '../right-wing/export_right-wing.csv' +$----------------------------------------------------------------------- +$ Left-ht +$ LRA +include '../left-ht/left-ht.GRID_LREFAX_3330001' +include '../left-ht/left-ht.RBE2_LREFAX_3330001' +include '../left-ht/left-ht.CORD2R_LREFAX' +$ bdf model +include '../left-ht/left-ht.MAT_ZR' +include '../left-ht/export_left-ht.csv' +$----------------------------------------------------------------------- +$ Right-ht +$ LRA +include '../right-ht/right-ht.GRID_LREFAX_3340001' +include '../right-ht/right-ht.RBE2_LREFAX_3340001' +include '../right-ht/right-ht.CORD2R_LREFAX' +$ bdf model +include '../right-ht/right-ht.MAT_ZR' +include '../right-ht/export_right-ht.csv' +$----------------------------------------------------------------------- +$ Vt +$ LRA +include '../vt/vt.GRID_LREFAX_3320001' +include '../vt/vt.RBE2_LREFAX_3320001' +include '../vt/vt.CORD2R_LREFAX' +$ bdf model +include '../vt/vt.MAT_ZR' +include '../vt/export_vt.csv' +$----------------------------------------------------------------------- +$ Left-nacell +include '../export_left-nacell.csv' +$----------------------------------------------------------------------- +$ Right-nacell +include '../export_right-nacell.csv' +$----------------------------------------------------------------------- + + diff --git a/doc/tutorials/DC3_model/fem/nonstructural_masses/crew_and_equipment-mass.csv b/doc/tutorials/DC3_model/fem/nonstructural_masses/crew_and_equipment-mass.csv new file mode 100644 index 0000000..219879f --- /dev/null +++ b/doc/tutorials/DC3_model/fem/nonstructural_masses/crew_and_equipment-mass.csv @@ -0,0 +1,15 @@ +$ Mono, 10pt, column width 17mm, no spacing +$2345678 + +$crew eqnodes ref is 2meters in frontof nose +$ GRID ID CP X1 X2 X3 CD +$ GRID 111001 7.700 0.000 0.000 + + + + +$crew eqmass model +$crew eq EID G CID M X1 X2 X3 +$ I11 I21 I22 I31 I32 I33 +CONM2 121001 100004 -1 425.470 7.700 0.000 0.000 + 0.000 0.000 0.000 0.000 0.000 0.000 diff --git a/doc/tutorials/DC3_model/fem/nonstructural_masses/fueltanks_low_mi.SCONM2 b/doc/tutorials/DC3_model/fem/nonstructural_masses/fueltanks_low_mi.SCONM2 new file mode 100644 index 0000000..0fe6934 --- /dev/null +++ b/doc/tutorials/DC3_model/fem/nonstructural_masses/fueltanks_low_mi.SCONM2 @@ -0,0 +1,46 @@ +$ +$*********************************************************************** +$ Created by ModGen (Version 2.2401.01 (latest version)) +$ Operating System: Linux +$ Date & Time: 2024-02-21 15:41:39 +$*********************************************************************** +$ +$ - The file contains CONM2-cards as result of the mass integration +$ task of modgen +$ - SCONM2 mass entities are attached to MGRIDs +$ - The total mass and c.g. is written below the CONM2 cards +$ +$----------------------------------------------------------------------- +$ +CONM2 9000164090001 0 161.473.0145699.2631190-.156335 + ++ 14.7777 5.67-14 92.9288 -.30998 -4.3-16 100.509 +CONM2 9000264090003 0 161.466.0145699 -.26309-.156336 + ++ 14.7761 -3.98-6 92.9245 -.30996 -4.37-5 100.502 +CONM2 9000364090003 0 161.473.0145699.2631100-.156335 + ++ 14.7777 -7.97-6 92.9291 -.30998 -8.74-5 100.509 +CONM2 9000464090004 0 160.344.0145699.2612600-.156335 + ++ 14.5198 -7.91-6 92.2792 -.30781 -8.67-5 99.6520 +$ +$ TOTAL MASS: 644.75600 +$ X-CG: 8.58180 +$ Y-CG: 1.05061 +$ Z-CG: 0.01840 +$ +$ +$ +$*********************************************************************** +$ ModGen is a program to set up Nastran structural, aerodynamic, and +$ optimization models for aircraft structures like wing and fuselage +$ including optional pre-sizing. +$ +$ ModGen is developed by: +$ +$ DLR German Aerospace Center +$ Institute of Aeroelasticity +$ 37073 Goettingen +$ Internet www.dlr.de/ae +$ Contact: thomas.klimmek@dlr.de +$ +$*********************************************************************** +$ +$ diff --git a/doc/tutorials/DC3_model/fem/nonstructural_masses/fueltanks_low_mi.SCONM2_mrd b/doc/tutorials/DC3_model/fem/nonstructural_masses/fueltanks_low_mi.SCONM2_mrd new file mode 100644 index 0000000..815cfd5 --- /dev/null +++ b/doc/tutorials/DC3_model/fem/nonstructural_masses/fueltanks_low_mi.SCONM2_mrd @@ -0,0 +1,36 @@ +$ +$*********************************************************************** +$ Created by ModGen (Version 2.2401.01 (latest version)) +$ Operating System: Linux +$ Date & Time: 2024-02-21 15:41:39 +$*********************************************************************** +$ +$ +$----------------------------------------------------------------------- +$ +CONM2 8000154090001 0 161.473.0145699-.263119-.156335 + ++ 14.7777 -5.6-14 92.9288 .309980 4.39-16 100.509 +CONM2 8000254090003 0 161.466.0145699 .263094-.156336 + ++ 14.7761 3.980-6 92.9245 .309960 4.370-5 100.502 +CONM2 8000354090003 0 161.473.0145699-.263110-.156335 + ++ 14.7777 7.970-6 92.9291 .309980 8.740-5 100.509 +CONM2 8000454090004 0 160.344.0145699-.261260-.156335 + ++ 14.5198 7.919-6 92.2792 .307810 8.679-5 99.6520 +$ +$ +$*********************************************************************** +$ ModGen is a program to set up Nastran structural, aerodynamic, and +$ optimization models for aircraft structures like wing and fuselage +$ including optional pre-sizing. +$ +$ ModGen is developed by: +$ +$ DLR German Aerospace Center +$ Institute of Aeroelasticity +$ 37073 Goettingen +$ Internet www.dlr.de/ae +$ Contact: thomas.klimmek@dlr.de +$ +$*********************************************************************** +$ +$ diff --git a/doc/tutorials/DC3_model/fem/nonstructural_masses/payload.csv b/doc/tutorials/DC3_model/fem/nonstructural_masses/payload.csv new file mode 100644 index 0000000..bd40d5c --- /dev/null +++ b/doc/tutorials/DC3_model/fem/nonstructural_masses/payload.csv @@ -0,0 +1,19 @@ +$ Mono, 10pt, column width 17mm, no spacing +$2345678 + +$payloadnodes ref is 2meters in frontof nose +$ GRID ID CP X1 X2 X3 CD +$ GRID 777001 5.302 0.000 0.000 +$ GRID 777002 10.128 0.000 0.000 +$ GRID 777003 14.954 0.000 0.000 + + +$payloadmass model +$crew eq EID G CID M X1 X2 X3 +$ I11 I21 I22 I31 I32 I33 +CONM2 707001 100003 -1 746.000 5.302 0.000 0.000 + 0.000 0.000 0.000 0.000 0.000 0.000 +CONM2 707002 100005 -11543.000 10.128 0.000 0.000 + 0.000 0.000 0.000 0.000 0.000 0.000 +CONM2 707003 100008 -1 231.000 14.954 0.000 0.000 + 0.000 0.000 0.000 0.000 0.000 0.000 diff --git a/doc/tutorials/DC3_model/fem/nonstructural_masses/payload_modified.csv b/doc/tutorials/DC3_model/fem/nonstructural_masses/payload_modified.csv new file mode 100644 index 0000000..027566e --- /dev/null +++ b/doc/tutorials/DC3_model/fem/nonstructural_masses/payload_modified.csv @@ -0,0 +1,19 @@ +$ Mono, 10pt, column width 17mm, no spacing +$2345678 + +$payloadnodes ref is 2meters in frontof nose +$ GRID ID CP X1 X2 X3 CD +$ GRID 777001 5.302 0.000 0.000 +$ GRID 777002 10.128 0.000 0.000 +$ GRID 777003 14.954 0.000 0.000 + + +$payloadmass model +$crew eq EID G CID M X1 X2 X3 + +$ I11 I21 I22 I31 I32 I33 +CONM2 707001 100003 -1 746.000 10.128 0.000 0.000 + + 0.000 0.000 0.000 0.000 0.000 0.000 +CONM2 707002 100005 -11543.000 14.954 0.000 0.000 + + 0.000 0.000 0.000 0.000 0.000 0.000 +CONM2 707003 100008 -1 231.000 14.954 0.000 0.000 + + 0.000 0.000 0.000 0.000 0.000 0.000 diff --git a/doc/tutorials/DC3_model/fem/nonstructural_masses/systems-mass.csv b/doc/tutorials/DC3_model/fem/nonstructural_masses/systems-mass.csv new file mode 100644 index 0000000..5b72093 --- /dev/null +++ b/doc/tutorials/DC3_model/fem/nonstructural_masses/systems-mass.csv @@ -0,0 +1,99 @@ +$ Mono, 10pt, column width 17mm, no spacing +$2345678 + +$ LG nodes ref is 2meters in frontof nose +$ GRID ID CP X1 X2 X3 CD +$ GRID 101001 7.8180 2.745 0.000 +$ GRID 101002 7.8180 -2.745 0.000 +$ GRID 101003 18.4670 0.000 0.878 + + +$ LG mass model +$ CONM2 EID G CID M X1 X2 X3 +$ I11 I21 I22 I31 I32 I33 +CONM2 12000164090006 -1 139.815 7.8180 2.745 0.000 + 0.000 0.000 0.000 0.000 0.000 0.000 +CONM2 12000254090006 -1 139.815 7.8180 -2.745 0.000 + 0.000 0.000 0.000 0.000 0.000 0.000 +CONM2 120003 100010 -1 31.070 18.4670 0.000 0.878 + 0.000 0.000 0.000 0.000 0.000 0.000 + + +$FuelSysnodes ref is 2meters in frontof nose +$ GRID ID CP X1 X2 X3 CD +$ GRID 102001 8.0380 1.867 0.000 +$ GRID 102002 8.0380 -1.867 0.000 + + +$FuelSysmass model +$ CONM2 EID G CID M X1 X2 X3 +$ I11 I21 I22 I31 I32 I33 +CONM2 13000164090005 -1 103.550 8.0380 1.867 0.000 + 0.000 0.000 0.000 0.000 0.000 0.000 +CONM2 13000254090005 -1 103.550 8.0380 -1.867 0.000 + 0.000 0.000 0.000 0.000 0.000 0.000 + +$FltCtl nodes ref is 2meters in frontof nose +$ GRID ID CP X1 X2 X3 CD +$ GRID 103001 5.138 0.000 1.427 + +$FltCtl mass model +$ CONM2 EID G CID M X1 X2 X3 +$ I11 I21 I22 I31 I32 I33 +CONM2 140001 100003 -1 241.900 5.138 0.000 1.427 + 0.000 0.000 0.000 0.000 0.000 0.000 + + +$HydSys nodes ref is 2meters in frontof nose +$ GRID ID CP X1 X2 X3 CD +$ GRID 104001 5.138 0.000 1.427 + +$HydSys mass model +$ CONM2 EID G CID M X1 X2 X3 +$ I11 I21 I22 I31 I32 I33 +CONM2 150001 100003 -1 12.500 5.138 0.000 1.427 + 0.000 0.000 0.000 0.000 0.000 0.000 + + +$Avioni nodes ref is 2meters in frontof nose +$ GRID ID CP X1 X2 X3 CD +$ GRID 105001 3.293 0.000 0.000 + +$Avioni mass model +$ CONM2 EID G CID M X1 X2 X3 +$ I11 I21 I22 I31 I32 I33 +CONM2 160001 100002 -1 142.300 3.293 0.000 0.000 + 0.000 0.000 0.000 0.000 0.000 0.000 + + +$EleSys nodes ref is 2meters in frontof nose +$ GRID ID CP X1 X2 X3 CD +$ GRID 106001 3.293 0.000 0.000 + +$EleSys mass model +$ CONM2 EID G CID M X1 X2 X3 +$ I11 I21 I22 I31 I32 I33 +CONM2 170001 100002 -1 255.900 3.293 0.000 0.000 + 0.000 0.000 0.000 0.000 0.000 0.000 + + +$AirCon nodes ref is 2meters in frontof nose +$ GRID ID CP X1 X2 X3 CD +$ GRID 107001 7.112 0.000 0.000 + +$AirCon mass model +$ CONM2 EID G CID M X1 X2 X3 +$ I11 I21 I22 I31 I32 I33 +CONM2 180001 100004 -1 608.000 7.112 0.000 0.000 + 0.000 0.000 0.000 0.000 0.000 0.000 + + +$Furnishnodes ref is 2meters in frontof nose +$ GRID ID CP X1 X2 X3 CD +$ GRID 108001 7.965 0.000 0.000 + +$Furnishmass model +$ CONM2 EID G CID M X1 X2 X3 +$ I11 I21 I22 I31 I32 I33 +CONM2 190001 100004 -1 696.300 7.965 0.000 0.000 + 0.000 0.000 0.000 0.000 0.000 0.000 From d873b2ffea8c8f7733d9de4bbddeefce9b517f00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Mon, 16 Mar 2026 14:52:46 +0100 Subject: [PATCH 37/63] Add a test publich case --- tests/test_integration_public.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_integration_public.py b/tests/test_integration_public.py index 032cd8a..4f3c9f1 100644 --- a/tests/test_integration_public.py +++ b/tests/test_integration_public.py @@ -78,3 +78,8 @@ class TestDC3Gust(TestDC3Trim): class TestDC3Flutter(TestDC3Trim): job_name = 'jcl_dc3_flutter' aircraft_name = 'DC3_model' + + +class TestDC3Nastran95(PreMainPostFunctional): + job_name = 'jcl_dc3_Nastran95' + aircraft_name = 'DC3_model' From 3945ee2dfa7c86fbd58d76549595bcd248b757c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Mon, 16 Mar 2026 15:06:38 +0100 Subject: [PATCH 38/63] Remove pyNastran dependency --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 4e65196..62e6e21 100644 --- a/setup.py +++ b/setup.py @@ -53,7 +53,6 @@ 'jupyter', 'pyfmi', 'pyiges', # only available with pip, not with conda - 'pyNastran' # only available with pip, not with conda ], 'test': ['pytest', 'pytest-cov', From b2463cc95312447eb8ed4de994392e86a530ecd0 Mon Sep 17 00:00:00 2001 From: "Chang.Xu" Date: Mon, 16 Mar 2026 17:42:41 +0100 Subject: [PATCH 39/63] added name to contributors list --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index 64480a3..f259075 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -16,6 +16,7 @@ Schulze, Matthias, Institute of Aeroelasticity, Deutsches Zentrum für Luft- und Handojo, Vega, Institute of Aeroelasticity, Deutsches Zentrum für Luft- und Raumfahrt e.V. Baier, Jan, Institute of Aeroelasticity, Deutsches Zentrum für Luft- und Raumfahrt e.V. Carvalho, Francisco, Institute of Aeroelasticity, Deutsches Zentrum für Luft- und Raumfahrt e.V. +Chang Xu, Visonary Aircraft Concepts, Bauhaus Luftfahrt e.V. ``` From e48ce360b7a361e1c41439799a2b669a8704b625 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Mon, 30 Mar 2026 17:39:30 +0200 Subject: [PATCH 40/63] First working version of a response viewer --- loadskernel/program_flow.py | 5 +- responseviewer/__init__.py | 0 responseviewer/graphics/LK_logo2.png | Bin 0 -> 62264 bytes responseviewer/plotting.py | 47 +++++++ responseviewer/view.py | 195 +++++++++++++++++++++++++++ 5 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 responseviewer/__init__.py create mode 100644 responseviewer/graphics/LK_logo2.png create mode 100644 responseviewer/plotting.py create mode 100644 responseviewer/view.py diff --git a/loadskernel/program_flow.py b/loadskernel/program_flow.py index f7465ee..a69ea4c 100755 --- a/loadskernel/program_flow.py +++ b/loadskernel/program_flow.py @@ -204,9 +204,12 @@ def main_core(self, model, jcl, i): solution_i.exec_pulse() # Collect response from solution sequence, then destroy it to free memory. response = solution_i.response - response['i'] = i response['successful'] = solution_i.successful del solution_i + # Add information regarding subcase and description to the response. + response['i'] = i + response['desc'] = jcl.trimcase[i]['desc'] + response['subcase'] = jcl.trimcase[i]['subcase'] return response def run_main_sequential(self): diff --git a/responseviewer/__init__.py b/responseviewer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/responseviewer/graphics/LK_logo2.png b/responseviewer/graphics/LK_logo2.png new file mode 100644 index 0000000000000000000000000000000000000000..34eca6ce8c31da879cc4dd731f89fb31068fd725 GIT binary patch literal 62264 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 literal 0 HcmV?d00001 diff --git a/responseviewer/plotting.py b/responseviewer/plotting.py new file mode 100644 index 0000000..8e1c55e --- /dev/null +++ b/responseviewer/plotting.py @@ -0,0 +1,47 @@ +import os +import matplotlib.pyplot as plt + + +class Plotting(): + + states_avail = ['X [m]', 'Y [m]', 'Z [m]', 'Phi [rad]', 'Theta [rad]', 'Psi [rad]', + 'u [m/s]', 'v [m/s]', 'w [m/s]', 'p [rad]', 'q [rad]', 'r [rad]'] + + def __init__(self, fig): + plt.rcParams.update({'font.size': 16, + 'svg.fonttype': 'none'}) + self.fig = fig + im = plt.imread(os.path.dirname(__file__) + '/graphics/LK_logo2.png') + newax = fig.add_axes([0.04, 0.02, 0.10, 0.08]) + newax.imshow(im, interpolation='hanning') + newax.axis('off') + + self.responses = None + + def plot_nothing(self): + self.fig.clf() + + def add_responses(self, responses): + self.responses = responses + + def timehistories(self, subcases, states,): + self.fig.clf() + # Create subplots for each state to plot, sharing the x-axis (time). + ax = self.fig.subplots(len(states), sharex=True) + # Make sure that ax is always iterable, even if there is only one state to plot. + if len(states) == 1: + ax = [ax] + # Plotting the time histories for each state and subcase. + for a, state in zip(ax, states): + for subcase in subcases: + subcase = str(subcase) + a.plot(self.responses[subcase]['t'][()], self.responses[subcase]['X'][:, state], label=subcase) + # a.ticklabel_format(style='sci', axis='x', scilimits=(-2, 2)) + a.ticklabel_format(style='sci', axis='y', scilimits=(-1, 1)) + a.grid(True) + a.set_ylabel(self.states_avail[state]) + ya = a.get_yaxis() + ya.set_label_coords(x=-0.1, y=0.5) + # Make plots look nice. + ax[0].legend(loc='upper right') + ax[-1].set_xlabel('Time [s]') diff --git a/responseviewer/view.py b/responseviewer/view.py new file mode 100644 index 0000000..1fc8497 --- /dev/null +++ b/responseviewer/view.py @@ -0,0 +1,195 @@ +# -*- coding: utf-8 -*- +import os +import sys + +from PySide6.QtGui import QAction +from PySide6.QtWidgets import ( + QApplication, QWidget, QTabWidget, QSizePolicy, QGridLayout, QMainWindow, QListWidget, + QListWidgetItem, QAbstractItemView, QFileDialog, QMessageBox) +from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg, NavigationToolbar2QT +from matplotlib.figure import Figure + +from responseviewer import plotting +from loadskernel.io_functions import data_handling + + +class ResponseViewer(): + + def __init__(self): + self.responses = None + self.colors = ['cornflowerblue', 'limegreen', 'violet', 'darkviolet', 'turquoise', 'orange', 'tomato', 'darkgrey', + 'black'] + + # define file options + self.file_opt = {} + self.file_opt['filters'] = "HDF5 response files (response*.hdf5);;Pickle response files \ + (response*.pickle);;all files (*.*)" + self.file_opt['initialdir'] = os.getcwd() + self.file_opt['title'] = 'Load Responses' + + def run(self): + # Create the app. + app = self.initApp() + # Init the application's menues, tabs, etc. + self.initGUI() + # Start the main event loop. + app.exec() + + def test(self): + """ + This function is intended for CI testing. To test at least some parts of the code, the app is initialized, but never + started. Instead, all windows are closed again. + """ + app = self.initApp() + self.initGUI() + app.closeAllWindows() + + def initApp(self): + # Init the QApplication in a robust way. + # See https://stackoverflow.com/questions/54281439/pyside2-not-closing-correctly-with-basic-example + app = QApplication.instance() + if app is None: + app = QApplication(sys.argv) + return app + + def initGUI(self): + # Use one Widget as a main container. + self.container = QWidget() + # Init all sub-widgets. + self.initMatplotlibFigure() + self.initTabs() + self.initWindow() + # Arrange the layout inside the container. + layout = QGridLayout(self.container) + # Notation: layout.addWidget(widget, row, column, rowSpan, columnSpan) + layout.addWidget(self.tabs_widget, 0, 0, 2, 1) + layout.addWidget(self.canvas, 1, 1) + layout.addWidget(self.toolbar, 0, 1) + + def initTabs(self): + # Configure tabs widget + self.tabs_widget = QTabWidget() + # configure sizing, limit width of tabs widget in favor of plotting area + sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) + self.tabs_widget.setSizePolicy(sizePolicy) + self.tabs_widget.setMinimumWidth(300) + self.tabs_widget.setMaximumWidth(450) + + # Add tabs + self.initStatesTab() + + def initStatesTab(self): + tab_loads = QWidget() + self.tabs_widget.addTab(tab_loads, 'States') + # Elements of loads tab + self.lb_subcase = QListWidget() + self.lb_subcase.setSelectionMode(QAbstractItemView.ExtendedSelection) + self.lb_subcase.itemSelectionChanged.connect(self.show_choice) + + self.lb_states = QListWidget() + self.lb_states.setSelectionMode(QAbstractItemView.ExtendedSelection) + for item in self.plotting.states_avail: + self.lb_states.addItem(QListWidgetItem(item)) + self.lb_states.setCurrentRow(4) + self.lb_states.itemSelectionChanged.connect(self.show_choice) + + layout = QGridLayout(tab_loads) + # Notation: layout.addWidget(widget, row, column, rowSpan, columnSpan) + layout.addWidget(self.lb_subcase, 0, 0, 1, 2) + layout.addWidget(self.lb_states, 1, 0, 1, 2) + + def initMatplotlibFigure(self): + # init Matplotlib Plot + fig1 = Figure() + # hand over subplot to plotting class + self.plotting = plotting.Plotting(fig1) + # embed figure + self.canvas = FigureCanvasQTAgg(fig1) + self.canvas.draw() + # configure sizing, set minimum size + sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) + self.canvas.setSizePolicy(sizePolicy) + self.canvas.setMinimumWidth(800) + self.canvas.setMinimumHeight(600) + + self.toolbar = NavigationToolbar2QT(self.canvas, self.container) + self.toolbar.update() + + def initWindow(self): + # Set up window and menu + self.window = QMainWindow() + mainMenu = self.window.menuBar() + # Add Menu to window + fileMenu = mainMenu.addMenu('File') + # Add load button + action = QAction('Load Responses', self.window) + action.setShortcut('Ctrl+L') + action.triggered.connect(self.load_response) + fileMenu.addAction(action) + + # Add exit button + action = QAction('Exit', self.window) + action.setShortcut('Ctrl+Q') + action.triggered.connect(self.window.close) + fileMenu.addAction(action) + + self.window.setCentralWidget(self.container) + self.window.setWindowTitle("Response Viewer") + self.window.show() + + def show_choice(self): + # called on change in listbox, combobox, etc + self.update_plot() + + def update_plot(self): + if self.lb_subcase.currentItem() is not None and self.lb_states.currentItem() is not None: + # Get the items selected by the user. + subcases_sel = [item.row() for item in self.lb_subcase.selectedIndexes()] + states_selected = [item.row() for item in self.lb_states.selectedIndexes()] + # Call the plotting function. + self.plotting.timehistories(subcases_sel, states_selected) + else: + self.plotting.plot_nothing() + self.canvas.draw() + + def load_response(self): + # open file dialog + filename, _ = QFileDialog.getOpenFileName( + self.window, + self.file_opt['title'], + self.file_opt['initialdir'], + self.file_opt['filters'] + ) + if filename != '': + dataset = None + if '.pickle' in filename: + with open(filename, 'rb') as f: + dataset = data_handling.load_pickle(f) + elif '.hdf5' in filename: + dataset = data_handling.load_hdf5(filename) + else: + QMessageBox.warning(self.window, "Unsupported File", "Please select a .pickle or .hdf5 file.") + + if dataset is not None: + # Store dataset + self.responses = dataset + # Update fields + self.update_fields() + # Update response in plotting class + self.plotting.add_responses(self.responses) + self.file_opt['initialdir'] = os.path.split(filename)[0] + + def update_fields(self): + if self.responses is not None: + self.lb_subcase.clear() + for i in self.responses: + self.lb_subcase.addItem(QListWidgetItem(i)) + + +def command_line_interface(): + r = ResponseViewer() + r.run() + + +if __name__ == "__main__": + command_line_interface() From 248d686b4f744b32c083ebf63a4659dd0bf24465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Tue, 31 Mar 2026 09:36:33 +0200 Subject: [PATCH 41/63] Undo chages to response as it would change all responses in the long-term continuous intergration tests --- loadskernel/program_flow.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/loadskernel/program_flow.py b/loadskernel/program_flow.py index a69ea4c..98b4965 100755 --- a/loadskernel/program_flow.py +++ b/loadskernel/program_flow.py @@ -205,11 +205,8 @@ def main_core(self, model, jcl, i): # Collect response from solution sequence, then destroy it to free memory. response = solution_i.response response['successful'] = solution_i.successful - del solution_i - # Add information regarding subcase and description to the response. response['i'] = i - response['desc'] = jcl.trimcase[i]['desc'] - response['subcase'] = jcl.trimcase[i]['subcase'] + del solution_i return response def run_main_sequential(self): From 28cf6095ba464d76732ba3fee185ddccbd20882e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Tue, 31 Mar 2026 11:50:03 +0200 Subject: [PATCH 42/63] Extend capabilities --- responseviewer/graphics/LK_logo2.png | Bin 62264 -> 0 bytes responseviewer/plotting.py | 69 ++++++++++++++++++++------- responseviewer/view.py | 10 ++-- 3 files changed, 57 insertions(+), 22 deletions(-) delete mode 100644 responseviewer/graphics/LK_logo2.png diff --git a/responseviewer/graphics/LK_logo2.png b/responseviewer/graphics/LK_logo2.png deleted file mode 100644 index 34eca6ce8c31da879cc4dd731f89fb31068fd725..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62264 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 diff --git a/responseviewer/plotting.py b/responseviewer/plotting.py index 8e1c55e..dae971b 100644 --- a/responseviewer/plotting.py +++ b/responseviewer/plotting.py @@ -1,17 +1,23 @@ import os import matplotlib.pyplot as plt +import numpy as np class Plotting(): - states_avail = ['X [m]', 'Y [m]', 'Z [m]', 'Phi [rad]', 'Theta [rad]', 'Psi [rad]', - 'u [m/s]', 'v [m/s]', 'w [m/s]', 'p [rad]', 'q [rad]', 'r [rad]'] + states_avail = ['x [m]', 'y [m]', 'z [m]', 'Phi [deg]', 'Theta [deg]', 'Psi [deg]', + 'u [m/s]', 'v [m/s]', 'w [m/s]', 'p [deg]', 'q [deg]', 'r [deg]'] + commands_avail = ['Xi [deg]', 'Eta [deg]', 'Zeta [deg]', 'Thrust [N]', 'Stabilizer [deg]', 'Flaps [deg]'] + loadfactors_avail = ['Nx [-]', 'Ny [-]', 'Nz [-]'] + other_avail = ['q_dyn [Pa]', 'alpha [deg]', 'beta [deg]', 'p1 [m]', 'F1 [N]'] + all_quantities = states_avail + commands_avail + loadfactors_avail + other_avail + def __init__(self, fig): plt.rcParams.update({'font.size': 16, 'svg.fonttype': 'none'}) self.fig = fig - im = plt.imread(os.path.dirname(__file__) + '/graphics/LK_logo2.png') + im = plt.imread(os.path.dirname(__file__) + '/../graphics/LK_logo2.png') newax = fig.add_axes([0.04, 0.02, 0.10, 0.08]) newax.imshow(im, interpolation='hanning') newax.axis('off') @@ -24,24 +30,53 @@ def plot_nothing(self): def add_responses(self, responses): self.responses = responses - def timehistories(self, subcases, states,): + def timehistories(self, subcases, quantities,): self.fig.clf() # Create subplots for each state to plot, sharing the x-axis (time). - ax = self.fig.subplots(len(states), sharex=True) + ax = self.fig.subplots(len(quantities), sharex=True) # Make sure that ax is always iterable, even if there is only one state to plot. - if len(states) == 1: + if len(quantities) == 1: ax = [ax] # Plotting the time histories for each state and subcase. - for a, state in zip(ax, states): + for a, quantity in zip(ax, quantities): for subcase in subcases: - subcase = str(subcase) - a.plot(self.responses[subcase]['t'][()], self.responses[subcase]['X'][:, state], label=subcase) - # a.ticklabel_format(style='sci', axis='x', scilimits=(-2, 2)) - a.ticklabel_format(style='sci', axis='y', scilimits=(-1, 1)) - a.grid(True) - a.set_ylabel(self.states_avail[state]) - ya = a.get_yaxis() - ya.set_label_coords(x=-0.1, y=0.5) - # Make plots look nice. - ax[0].legend(loc='upper right') + time = self.responses[subcase]['t'][()] + if quantity in self.states_avail: + # States are stored in 'X' in the same order as in states_avail. + idx = self.states_avail.index(quantity) + data = self.responses[subcase]['X'][:, idx] + elif quantity in self.commands_avail: + # Commands are stored in the last 6 rows of 'X' in the same order as in commands_avail. + idx = self.commands_avail.index(quantity) + commands = self.responses[subcase]['X'][:, -6:] + data = commands[:, idx] + elif quantity in self.loadfactors_avail: + # Load factors are stored in 'Nxyz' in the same order as in loadfactors_avail. + idx = self.loadfactors_avail.index(quantity) + data = self.responses[subcase]['Nxyz'][:, idx] + else: + # For the remaining quantities, we need to check if they are available in the response. + # Most quantities have units in their name, so we split by space to obtain the base name for + # lookup in the response. + q = quantity.split()[0] + if quantity in self.other_avail and q in self.responses[subcase]: + data = self.responses[subcase][q][()] + else: + # In case the quantity is not found, create some dummy data. + subcase = 'Not found' + data = np.zeros_like(time) + if '[deg]' in quantity: + data *= 180.0 / np.pi + # Plot the time history for the current subcase and quantity. + a.plot(time, data, label=subcase) + # Format current axis. + a.ticklabel_format(style='sci', axis='y', scilimits=(-2, 2)) + a.grid(True) + a.set_ylabel(quantity) + # Push left bound to the right to make space for the ylabel. + left, bottom, width, height = a.get_position().bounds + a.set_position([left + 0.05, bottom, width - 0.05, height]) + a.get_yaxis().set_label_coords(x=-0.18, y=0.5) + # Show legend per plot + a.legend(loc='upper right') ax[-1].set_xlabel('Time [s]') diff --git a/responseviewer/view.py b/responseviewer/view.py index 1fc8497..63bb320 100644 --- a/responseviewer/view.py +++ b/responseviewer/view.py @@ -80,7 +80,7 @@ def initTabs(self): def initStatesTab(self): tab_loads = QWidget() - self.tabs_widget.addTab(tab_loads, 'States') + self.tabs_widget.addTab(tab_loads, 'Time Histories') # Elements of loads tab self.lb_subcase = QListWidget() self.lb_subcase.setSelectionMode(QAbstractItemView.ExtendedSelection) @@ -88,7 +88,7 @@ def initStatesTab(self): self.lb_states = QListWidget() self.lb_states.setSelectionMode(QAbstractItemView.ExtendedSelection) - for item in self.plotting.states_avail: + for item in self.plotting.all_quantities: self.lb_states.addItem(QListWidgetItem(item)) self.lb_states.setCurrentRow(4) self.lb_states.itemSelectionChanged.connect(self.show_choice) @@ -144,10 +144,10 @@ def show_choice(self): def update_plot(self): if self.lb_subcase.currentItem() is not None and self.lb_states.currentItem() is not None: # Get the items selected by the user. - subcases_sel = [item.row() for item in self.lb_subcase.selectedIndexes()] - states_selected = [item.row() for item in self.lb_states.selectedIndexes()] + subcases_selected = [item.text() for item in self.lb_subcase.selectedItems()] + quantities_selected = [item.text() for item in self.lb_states.selectedItems()] # Call the plotting function. - self.plotting.timehistories(subcases_sel, states_selected) + self.plotting.timehistories(subcases_selected, quantities_selected) else: self.plotting.plot_nothing() self.canvas.draw() From 263bc7a06ec878516d8a0455219d8b61cd5878ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Tue, 31 Mar 2026 11:50:36 +0200 Subject: [PATCH 43/63] Add as a tool to the installation --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 62e6e21..d7aaaf1 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,9 @@ packages=find_packages(), entry_points={'console_scripts': ['loads-kernel=loadskernel.program_flow:command_line_interface', 'model-viewer=modelviewer.view:command_line_interface', - 'loads-compare=loadscompare.compare:command_line_interface']}, + 'loads-compare=loadscompare.compare:command_line_interface', + 'response-viewer=responseviewer.view:command_line_interface', + ]}, include_package_data=True, package_data={'loadskernel': ['graphics/*.*'], 'loadscompare': ['graphics/*.*'], }, From a3b587b8f3a8f0efd7d8094cb03a586c79cbf1f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Tue, 31 Mar 2026 12:05:21 +0200 Subject: [PATCH 44/63] Remove obsolete code that is now covered by the response viewer --- loadskernel/plotting_extra.py | 142 +--------------------------------- loadskernel/program_flow.py | 12 +-- 2 files changed, 5 insertions(+), 149 deletions(-) diff --git a/loadskernel/plotting_extra.py b/loadskernel/plotting_extra.py index d3d274d..0904820 100755 --- a/loadskernel/plotting_extra.py +++ b/loadskernel/plotting_extra.py @@ -12,8 +12,8 @@ pass from loadskernel import plotting_standard -from modelviewer import plotting as plotting_modelviewer from loadskernel.io_functions.data_handling import load_hdf5_dict +from modelviewer import plotting as plotting_modelviewer plt.rcParams.update({'font.size': 16, 'svg.fonttype': 'none'}) @@ -46,146 +46,6 @@ def plot_pressure_distribution(self): self.set_view_left_above() mlab.show() - def plot_time_data(self): - # Create all plots - _, (ax11, ax12) = plt.subplots(nrows=2, ncols=1, sharex=True,) - _, (ax21, ax22, ax23) = plt.subplots(nrows=3, ncols=1, sharex=True,) - _, (ax31, ax32) = plt.subplots(nrows=2, ncols=1, sharex=True,) - _, (ax41, ax42) = plt.subplots(nrows=2, ncols=1, sharex=True,) - _, (ax51, ax52, ax53) = plt.subplots(nrows=3, ncols=1, sharex=True,) - _, (ax61, ax62) = plt.subplots(nrows=2, ncols=1, sharex=True,) - if hasattr(self.jcl, 'landinggear'): - _, (ax71, ax72) = plt.subplots(nrows=2, ncols=1, sharex=True,) - Dkx1 = self.model['Dkx1'][()] - # Loop over responses and fill plots with data - for response in self.responses: - trimcase = self.jcl.trimcase[response['i'][()]] - logging.info('plotting for simulation {:s}'.format(trimcase['desc'])) - - self.n_modes = self.model['mass'][trimcase['mass']]['n_modes'][()] - - if self.jcl.aero['method'] in ['mona_steady', 'mona_unsteady', 'nonlin_steady']: - Cl = response['Pmac'][:, 2] / response['q_dyn'][:].T / self.jcl.general['A_ref'] - ax11.plot(response['t'], response['Pmac'][:, 2], 'b-') - ax12.plot(response['t'], Cl.T, 'b-') - - ax21.plot(response['t'], response['q_dyn'], 'k-') - ax22.plot(response['t'], response['alpha'][:] / np.pi * 180.0, 'r-') - ax22.plot(response['t'], response['beta'][:] / np.pi * 180.0, 'c-') - ax23.plot(response['t'], response['Nxyz'][:, 1], 'g-') - ax23.plot(response['t'], response['Nxyz'][:, 2], 'b-') - - if self.jcl.aero['method'] in ['mona_unsteady']: - Pb_gust = [] - Pb_unsteady = [] - for i_step in range(len(response['t'])): - Pb_gust.append(np.dot(Dkx1.T, response['Pk_gust'][i_step, :])[2]) - Pb_unsteady.append(np.dot(Dkx1.T, response['Pk_unsteady'][i_step, :])[2]) - ax11.plot(response['t'], Pb_gust, 'k-') - ax11.plot(response['t'], Pb_unsteady, 'r-') - - ax31.plot(response['t'], response['X'][:, 0], 'b-') - ax31.plot(response['t'], response['X'][:, 1], 'g-') - ax31.plot(response['t'], response['X'][:, 2], 'r-') - - ax32.plot(response['t'], response['X'][:, 3] / np.pi * 180.0, 'b-') - ax32.plot(response['t'], response['X'][:, 4] / np.pi * 180.0, 'g-') - ax32.plot(response['t'], response['X'][:, 5] / np.pi * 180.0, 'r-') - - ax41.plot(response['t'], response['X'][:, 6], 'b-') - ax41.plot(response['t'], response['X'][:, 7], 'g-') - ax41.plot(response['t'], response['X'][:, 8], 'r-') - - ax42.plot(response['t'], response['X'][:, 9] / np.pi * 180.0, 'b-') - ax42.plot(response['t'], response['X'][:, 10] / np.pi * 180.0, 'g-') - ax42.plot(response['t'], response['X'][:, 11] / np.pi * 180.0, 'r-') - - ax51.plot(response['t'], response['X'][:, 12 + 2 * self.n_modes + 0] / np.pi * 180.0, 'b-') - ax51.plot(response['t'], response['X'][:, 12 + 2 * self.n_modes + 1] / np.pi * 180.0, 'g-') - ax51.plot(response['t'], response['X'][:, 12 + 2 * self.n_modes + 2] / np.pi * 180.0, 'r-') - - ax52.plot(response['t'], response['X'][:, 12 + 2 * self.n_modes + 3], 'k-') - - ax53.plot(response['t'], response['X'][:, 12 + 2 * self.n_modes + 4], 'b-') - ax53.plot(response['t'], response['X'][:, 12 + 2 * self.n_modes + 5], 'g-') - - ax61.plot(response['t'], response['Uf'], 'b-') - - ax62.plot(response['t'], response['d2Ucg_dt2'][:, 0], 'b-') - ax62.plot(response['t'], response['d2Ucg_dt2'][:, 1], 'g-') - ax62.plot(response['t'], response['d2Ucg_dt2'][:, 2], 'r-') - - if hasattr(self.jcl, 'landinggear'): - ax71.plot(response['t'], response['p1']) - ax72.plot(response['t'], response['F1']) - - # Make plots nice - ax11.set_ylabel('Fz [N]') - ax11.grid(True) - if self.jcl.aero['method'] in ['mona_unsteady']: - ax11.legend(['aero', 'gust', 'unsteady']) - ax12.set_xlabel('t [sec]') - ax12.set_ylabel('Cz [-]') - ax12.grid(True) - ax12.legend(['Cz']) - - ax21.set_ylabel('[Pa]') - ax21.grid(True) - ax21.legend(['q_dyn']) - ax22.legend(['alpha', 'beta']) - ax22.grid(True) - ax22.set_ylabel('[deg]') - ax23.set_xlabel('t [sec]') - ax23.legend(['Ny', 'Nz']) - ax23.grid(True) - ax23.set_ylabel('[-]') - - ax31.set_ylabel('[m]') - ax31.grid(True) - ax31.legend(['x', 'y', 'z']) - ax32.set_xlabel('t [sec]') - ax32.set_ylabel('[deg]') - ax32.grid(True) - ax32.legend(['phi', 'theta', 'psi']) - - ax41.set_ylabel('[m/s]') - ax41.grid(True) - ax41.legend(['u', 'v', 'w']) - ax42.set_xlabel('t [sec]') - ax42.set_ylabel('[deg/s]') - ax42.grid(True) - ax42.legend(['p', 'q', 'r']) - - ax51.set_ylabel('Inputs [deg]') - ax51.grid(True) - ax51.legend(['Xi', 'Eta', 'Zeta']) - ax52.set_ylabel('Inputs [N]') - ax52.grid(True) - ax52.legend(['Thrust']) - ax53.set_xlabel('t [sec]') - ax53.set_ylabel('Inputs [deg]') - ax53.grid(True) - ax53.legend(['stabilizer', 'flap setting']) - - ax61.set_ylabel('Uf') - ax61.grid(True) - ax62.set_xlabel('t [sec]') - ax62.set_ylabel('d2Ucg_dt2 [m/s^2]') - ax62.legend(['du', 'dv', 'dw']) - ax62.grid(True) - - if hasattr(self.jcl, 'landinggear'): - ax71.legend(self.jcl.landinggear['key'], loc='best') - ax71.set_ylabel('p1 [m]') - ax71.grid(True) - ax72.legend(self.jcl.landinggear['key'], loc='best') - ax72.set_xlabel('t [s]') - ax72.set_ylabel('F1 [N]') - ax72.grid(True) - - # Show plots - plt.show() - def plot_forces_deformation_interactive(self): # loop over all responses diff --git a/loadskernel/program_flow.py b/loadskernel/program_flow.py index 98b4965..71120fb 100755 --- a/loadskernel/program_flow.py +++ b/loadskernel/program_flow.py @@ -401,14 +401,6 @@ def run_test(self): logging.info('Drawing some more detailed plots.') plt = plotting_extra.DetailedPlots(self.jcl, model) plt.add_responses(responses) - if 't_final' and 'dt' in self.jcl.simcase[0].keys(): - # show some plots of the time domain data - plt.plot_time_data() - else: - # show some plots of the force vectors, useful to identify model shortcomings - # plt.plot_pressure_distribution() - plt.plot_forces_deformation_interactive() - if 't_final' and 'dt' in self.jcl.simcase[0].keys(): # show a nice animation of the time domain simulation plt = plotting_extra.Animations(self.jcl, model) @@ -416,6 +408,10 @@ def run_test(self): plt.make_animation() # make a video file of the animation # plt.make_movie(self.path_output, speedup_factor=1.0) + else: + # show some plots of the force vectors, useful to identify model shortcomings + # plt.plot_pressure_distribution() + plt.plot_forces_deformation_interactive() """ At the moment, I also use this section for custom analysis scripts. From 82b2bc8c6781fcb64fff45903d1e382b66c16bb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Tue, 31 Mar 2026 13:17:08 +0200 Subject: [PATCH 45/63] Apply style suggestions --- responseviewer/plotting.py | 1 - responseviewer/view.py | 10 ++++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/responseviewer/plotting.py b/responseviewer/plotting.py index dae971b..08b93a8 100644 --- a/responseviewer/plotting.py +++ b/responseviewer/plotting.py @@ -12,7 +12,6 @@ class Plotting(): other_avail = ['q_dyn [Pa]', 'alpha [deg]', 'beta [deg]', 'p1 [m]', 'F1 [N]'] all_quantities = states_avail + commands_avail + loadfactors_avail + other_avail - def __init__(self, fig): plt.rcParams.update({'font.size': 16, 'svg.fonttype': 'none'}) diff --git a/responseviewer/view.py b/responseviewer/view.py index 63bb320..e8e6f34 100644 --- a/responseviewer/view.py +++ b/responseviewer/view.py @@ -27,6 +27,16 @@ def __init__(self): self.file_opt['initialdir'] = os.getcwd() self.file_opt['title'] = 'Load Responses' + # GUI attributes + self.container = None + self.tabs_widget = None + self.canvas = None + self.toolbar = None + self.plotting = None + self.window = None + self.lb_subcase = None + self.lb_states = None + def run(self): # Create the app. app = self.initApp() From 832609cbe127e836326aecc73b7a0f4e0e7f33c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Tue, 31 Mar 2026 13:20:28 +0200 Subject: [PATCH 46/63] Add integration tests --- tests/test_gui.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/test_gui.py b/tests/test_gui.py index 02c51ea..0040a3f 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -2,7 +2,8 @@ try: from loadscompare import compare - from modelviewer import view + from modelviewer import view as modelviewer + from responseviewer import view as responseview except ImportError: pass @@ -19,5 +20,13 @@ class TestModelViewer(): def test_gui(self): logging.info('Testing Model Viewer') - m = view.Modelviewer() + m = modelviewer.Modelviewer() + m.test() + + +class TestResponseViewer(): + + def test_gui(self): + logging.info('Testing Response Viewer') + m = responseview.ResponseViewer() m.test() From be5df1f749466b3e9af615b30dce2c128c39c69c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Tue, 31 Mar 2026 17:07:21 +0200 Subject: [PATCH 47/63] Add basic functionality to plot time histories --- loadscompare/compare.py | 117 ++++++++++++++++++++++++++++++++++++--- loadscompare/plotting.py | 74 ++++++++++++++++++------- 2 files changed, 164 insertions(+), 27 deletions(-) diff --git a/loadscompare/compare.py b/loadscompare/compare.py index 0d4afb1..0e0834e 100644 --- a/loadscompare/compare.py +++ b/loadscompare/compare.py @@ -38,6 +38,31 @@ def __init__(self): self.file_opt['initialdir'] = os.getcwd() self.file_opt['title'] = 'Load Monstations' + # GUI attributes for Loads tab + self.lb_dataset = None + self.lb_mon = None + self.cb_color = None + self.cb_xaxis = None + self.cb_yaxis = None + self.cb_hull = None + self.cb_labels = None + self.cb_minmax = None + self.label_n_loadcases = None + + # GUI attributes for Time tab + self.label_dataset_time = None + self.label_monstation_time = None + self.lb_subcases_time = None + self.lb_dof_time = None + + # Other GUI attributes + self.container = None + self.tabs_widget = None + self.canvas = None + self.toolbar = None + self.plotting = None + self.window = None + def run(self): # Create the app. app = self.initApp() @@ -88,6 +113,10 @@ def initTabs(self): # Add tabs self.initLoadsTab() + self.initTimeTab() + + # Connect tab change signal + self.tabs_widget.currentChanged.connect(self.on_tab_changed) def initLoadsTab(self): tab_loads = QWidget() @@ -143,6 +172,33 @@ def initLoadsTab(self): layout.addWidget(self.cb_minmax, 6, 0, 1, 2) layout.addWidget(self.label_n_loadcases, 7, 0, 1, 2) + def initTimeTab(self): + tab_time = QWidget() + self.tabs_widget.addTab(tab_time, 'Time') + # Elements of time tab + self.label_dataset_time = QLabel() + self.label_dataset_time.setText('Dataset: ') + + self.label_monstation_time = QLabel() + self.label_monstation_time.setText('Monstation: ') + + self.lb_subcases_time = QListWidget() + self.lb_subcases_time.setSelectionMode(QAbstractItemView.ExtendedSelection) + self.lb_subcases_time.itemSelectionChanged.connect(self.update_time_plot) + + self.lb_dof_time = QListWidget() + self.lb_dof_time.setSelectionMode(QAbstractItemView.ExtendedSelection) + self.lb_dof_time.itemSelectionChanged.connect(self.update_time_plot) + self.lb_dof_time.addItems(self.dof) + self.lb_dof_time.setCurrentRow(3) + + layout = QGridLayout(tab_time) + # Notation: layout.addWidget(widget, row, column, rowSpan, columnSpan) + layout.addWidget(self.label_dataset_time, 0, 0, 1, 2) + layout.addWidget(self.label_monstation_time, 1, 0, 1, 2) + layout.addWidget(self.lb_subcases_time, 2, 0, 1, 1) + layout.addWidget(self.lb_dof_time, 3, 0, 1, 1) + def initMatplotlibFigure(self): # init Matplotlib Plot fig1 = Figure() @@ -194,7 +250,7 @@ def initWindow(self): self.window.setWindowTitle("Loads Compare") self.window.show() - def show_choice(self, *args): + def show_choice(self): # called on change in listbox, combobox, etc # discard extra variables if len(self.lb_dataset.selectedItems()) == 1: @@ -202,17 +258,17 @@ def show_choice(self, *args): self.cb_color.setEnabled(True) else: self.cb_color.setDisabled(True) - self.update_plot() + self.update_loads_plot() def update_color(self, color): self.datasets['color'][self.lb_dataset.currentRow()] = self.colors[color] - self.update_plot() + self.update_loads_plot() - def update_desc(self, *args): + def update_desc(self): self.datasets['desc'][self.lb_dataset.currentRow()] = self.lb_dataset.currentItem().text() - self.update_plot() + self.update_loads_plot() - def update_plot(self): + def update_loads_plot(self): if self.lb_dataset.currentItem() is not None and self.lb_mon.currentItem() is not None: # Get the items selected by the user. mon_sel = self.common_monstations[self.lb_mon.currentRow()] @@ -241,7 +297,7 @@ def update_plot(self): n_subcases = [len(dataset[mon_sel]['subcases']) for dataset in datasets] self.label_n_loadcases.setText(f'Selected load case: {np.sum(n_subcases)}') else: - self.plotting.plot_nothing() + self.plotting.clear_figure() self.canvas.draw() def merge_monstation(self): @@ -251,7 +307,7 @@ def merge_monstation(self): for x in [item.row() for item in self.lb_dataset.selectedIndexes()]: print(f'Working on {self.datasets['desc'][x]} ...') for station in self.common_monstations: - if station not in new_dataset.keys(): + if station not in new_dataset: # create (empty) entries for new monstation new_dataset[station] = {'CD': self.datasets['dataset'][x][station]['CD'][()], 'CP': self.datasets['dataset'][x][station]['CP'][()], @@ -332,6 +388,51 @@ def update_fields(self): for x in self.common_monstations: self.lb_mon.addItem(QListWidgetItem(x)) + def update_time_plot(self): + # called on change in listbox + if self.lb_subcases_time.currentItem() is not None and self.lb_dof_time.currentItem() is not None: + # Get the items selected by the user. + mon_sel = self.common_monstations[self.lb_mon.currentRow()] + dataset_idx = self.lb_dataset.currentRow() + dataset_sel = self.datasets['dataset'][dataset_idx] + monstation = dataset_sel[mon_sel] + subcases_sel = [item.text() for item in self.lb_subcases_time.selectedItems()] + dofs_text = [item.text() for item in self.lb_dof_time.selectedItems()] + dofs_idx = [item.row() for item in self.lb_dof_time.selectedIndexes()] + # Call the plotting function. + self.plotting.timehistories(monstation, subcases_sel, dofs_idx, dofs_text) + else: + self.plotting.clear_figure() + self.canvas.draw() + + def on_tab_changed(self, index): + # Called when a tab is changed. + if index == 1: + # Loads tab is at index 0 + # Time tab is at index 1 + self.update_time_tab_fields() + + def update_time_tab_fields(self): + # Update the Time tab fields when it is activated. + if self.lb_dataset.currentItem() is not None and self.lb_mon.currentItem() is not None: + # Get the items selected by the user. + mon_sel = self.common_monstations[self.lb_mon.currentRow()] + dataset_idx = self.lb_dataset.currentRow() + dataset_sel = self.datasets['dataset'][dataset_idx] + + # Update the dataset label + dataset_name = self.datasets['desc'][dataset_idx] + self.label_dataset_time.setText(f'Dataset: {dataset_name}') + + # Update the monstation label + self.label_monstation_time.setText(f'Monstation: {mon_sel}') + + # Populate list of subcases by finding all integers in monstation.keys() + monstation = dataset_sel[mon_sel] + subcase_keys = [key for key in monstation if key.isdigit()] + self.lb_subcases_time.clear() + self.lb_subcases_time.addItems(subcase_keys) + def command_line_interface(): c = Compare() diff --git a/loadscompare/plotting.py b/loadscompare/plotting.py index 43e2e25..595deb1 100644 --- a/loadscompare/plotting.py +++ b/loadscompare/plotting.py @@ -11,30 +11,66 @@ class Plotting(plotting_standard.LoadPlots): def __init__(self, fig): plt.rcParams.update({'font.size': 16, 'svg.fonttype': 'none'}) - self.subplot = fig.add_axes([0.2, 0.15, 0.7, 0.75]) # List is [left, bottom, width, height] + self.fig = fig + self.subplot = None + + def clear_figure(self): + self.fig.clf() + self.subplot = None + # Add the logo in the bottom left corner. im = plt.imread(os.path.dirname(__file__) + '/graphics/LK_logo2.png') - newax = fig.add_axes([0.04, 0.02, 0.10, 0.08]) + newax = self.fig.add_axes([0.04, 0.02, 0.10, 0.08]) newax.imshow(im, interpolation='hanning') newax.axis('off') - def plot_nothing(self): - self.subplot.cla() - - def potato_plots(self, dataset_sel, station, descs, colors, dof_xaxis, dof_yaxis, var_xaxis, var_yaxis, + def potato_plots(self, dataset_sel, station, descs, colors, + dof_xaxis, dof_yaxis, var_xaxis, var_yaxis, show_hull, show_labels, show_minmax): + if self.subplot is None: + # Create a single axes that fills most of the figure, leaving space for the logo and labels. + # Store the axes in the class so that it can be used by potato_plot. + self.clear_figure() + self.subplot = self.fig.add_axes([0.2, 0.15, 0.7, 0.75]) # List is [left, bottom, width, height] + else: + self.subplot.cla() # This function relies on the potato plotting function in LK imported above to avoid code duplications. - # The labels, margins, etc. are adjusted in this function to fit the window space. - self.subplot.cla() - for i_dataset in range(len(dataset_sel)): + for i, dataset in enumerate(dataset_sel): self.crit_trimcases = [] - self.add_monstations(dataset_sel[i_dataset]) - self.potato_plot(station, descs[i_dataset], colors[i_dataset], dof_xaxis, dof_yaxis, + self.add_monstations(dataset) + self.potato_plot(station, descs[i], colors[i], dof_xaxis, dof_yaxis, show_hull, show_labels, show_minmax) - self.subplot.legend(loc='best') - self.subplot.ticklabel_format(style='sci', axis='x', scilimits=(-2, 2)) - self.subplot.ticklabel_format(style='sci', axis='y', scilimits=(-2, 2)) - self.subplot.grid(True) - yax = self.subplot.get_yaxis() - yax.set_label_coords(x=-0.18, y=0.5) - self.subplot.set_xlabel(var_xaxis) - self.subplot.set_ylabel(var_yaxis) + # The labels, margins, etc. are adjusted in this function to fit the window space. + a = self.subplot + a.legend(loc='best') + a.ticklabel_format(style='sci', axis='x', scilimits=(-2, 2)) + a.ticklabel_format(style='sci', axis='y', scilimits=(-2, 2)) + a.grid(True) + a.get_yaxis().set_label_coords(x=-0.18, y=0.5) + a.set_xlabel(var_xaxis) + a.set_ylabel(var_yaxis) + + def timehistories(self, monstation, subcases, dofs_idx, dofs_text): + # Clear the figure, then create subplots for each state to plot, sharing the x-axis (time). + # This is necessary as the number of axes can change, depending on the selection by the user. + self.clear_figure() + ax = self.fig.subplots(len(dofs_idx), sharex=True) + # Make sure that ax is always iterable, even if there is only one state to plot. + if len(dofs_idx) == 1: + ax = [ax] + # Plotting the time histories for each dof and subcase. + for a, dof_idx, dof_text in zip(ax, dofs_idx, dofs_text): + for subcase in subcases: + time = monstation[subcase]['t'][()] + data = monstation[subcase]['loads'][:, dof_idx] + a.plot(time, data, label=subcase) + # Format current axis. + a.ticklabel_format(style='sci', axis='y', scilimits=(-2, 2)) + a.grid(True) + a.set_ylabel(dof_text) + # Push left bound to the right to make space for the ylabel. + left, bottom, width, height = a.get_position().bounds + a.set_position([left + 0.05, bottom, width - 0.05, height]) + a.get_yaxis().set_label_coords(x=-0.18, y=0.5) + # Show legend per plot + a.legend(loc='upper right') + ax[-1].set_xlabel('Time [s]') From a4dd4f074b210222acdc0ad7e43f4e971e735cbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Tue, 31 Mar 2026 17:12:11 +0200 Subject: [PATCH 48/63] Remove Logo file --- loadscompare/graphics/LK_logo2.png | Bin 62264 -> 0 bytes loadscompare/plotting.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 loadscompare/graphics/LK_logo2.png diff --git a/loadscompare/graphics/LK_logo2.png b/loadscompare/graphics/LK_logo2.png deleted file mode 100644 index 34eca6ce8c31da879cc4dd731f89fb31068fd725..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62264 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 diff --git a/loadscompare/plotting.py b/loadscompare/plotting.py index 595deb1..e2610bd 100644 --- a/loadscompare/plotting.py +++ b/loadscompare/plotting.py @@ -18,7 +18,7 @@ def clear_figure(self): self.fig.clf() self.subplot = None # Add the logo in the bottom left corner. - im = plt.imread(os.path.dirname(__file__) + '/graphics/LK_logo2.png') + im = plt.imread(os.path.dirname(__file__) + '/../graphics/LK_logo2.png') newax = self.fig.add_axes([0.04, 0.02, 0.10, 0.08]) newax.imshow(im, interpolation='hanning') newax.axis('off') From 7d19d2ec097db0a666955167388acf3beb462024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Tue, 31 Mar 2026 17:12:53 +0200 Subject: [PATCH 49/63] Init class attribute --- loadscompare/plotting.py | 1 + 1 file changed, 1 insertion(+) diff --git a/loadscompare/plotting.py b/loadscompare/plotting.py index e2610bd..01c0bd5 100644 --- a/loadscompare/plotting.py +++ b/loadscompare/plotting.py @@ -13,6 +13,7 @@ def __init__(self, fig): 'svg.fonttype': 'none'}) self.fig = fig self.subplot = None + self.crit_trimcases = [] def clear_figure(self): self.fig.clf() From 61e2deda5a1942242919f02f8cbc0f0ff1d6f6b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Tue, 31 Mar 2026 17:19:32 +0200 Subject: [PATCH 50/63] More intuitive naming --- loadscompare/compare.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/loadscompare/compare.py b/loadscompare/compare.py index 0e0834e..dbcc5fa 100644 --- a/loadscompare/compare.py +++ b/loadscompare/compare.py @@ -124,11 +124,11 @@ def initLoadsTab(self): # Elements of loads tab self.lb_dataset = QListWidget() self.lb_dataset.setSelectionMode(QAbstractItemView.ExtendedSelection) - self.lb_dataset.itemSelectionChanged.connect(self.show_choice) + self.lb_dataset.itemSelectionChanged.connect(self.on_selection_changed) self.lb_dataset.itemChanged.connect(self.update_desc) self.lb_mon = QListWidget() - self.lb_mon.itemSelectionChanged.connect(self.show_choice) + self.lb_mon.itemSelectionChanged.connect(self.on_selection_changed) self.cb_color = QComboBox() self.cb_color.addItems(self.colors) @@ -137,24 +137,24 @@ def initLoadsTab(self): self.cb_xaxis = QComboBox() self.cb_xaxis.addItems(self.dof) self.cb_xaxis.setCurrentIndex(3) - self.cb_xaxis.activated.connect(self.show_choice) + self.cb_xaxis.activated.connect(self.on_selection_changed) self.cb_yaxis = QComboBox() self.cb_yaxis.addItems(self.dof) self.cb_yaxis.setCurrentIndex(4) - self.cb_yaxis.activated.connect(self.show_choice) + self.cb_yaxis.activated.connect(self.on_selection_changed) self.cb_hull = QCheckBox("show convex hull") self.cb_hull.setChecked(False) - self.cb_hull.stateChanged.connect(self.show_choice) + self.cb_hull.stateChanged.connect(self.on_selection_changed) self.cb_labels = QCheckBox("show labels") self.cb_labels.setChecked(False) - self.cb_labels.stateChanged.connect(self.show_choice) + self.cb_labels.stateChanged.connect(self.on_selection_changed) self.cb_minmax = QCheckBox("show min/max") self.cb_minmax.setChecked(False) - self.cb_minmax.stateChanged.connect(self.show_choice) + self.cb_minmax.stateChanged.connect(self.on_selection_changed) self.label_n_loadcases = QLabel() @@ -250,7 +250,7 @@ def initWindow(self): self.window.setWindowTitle("Loads Compare") self.window.show() - def show_choice(self): + def on_selection_changed(self): # called on change in listbox, combobox, etc # discard extra variables if len(self.lb_dataset.selectedItems()) == 1: @@ -328,7 +328,7 @@ def merge_monstation(self): self.datasets['desc'].append('dataset ' + str(self.datasets['n'])) self.datasets['n'] += 1 # Update fields. - self.update_fields() + self.update_fields_of_loads_tab() def load_monstation(self): # open file dialog @@ -353,7 +353,7 @@ def load_monstation(self): self.datasets['desc'].append('dataset ' + str(self.datasets['n'])) self.datasets['n'] += 1 # update fields - self.update_fields() + self.update_fields_of_loads_tab() self.file_opt['initialdir'] = os.path.split(filename)[0] def save_monstation(self): @@ -373,7 +373,7 @@ def save_monstation(self): if filename != '' and '.hdf5' in filename: data_handling.dump_hdf5(filename, dataset_sel) - def update_fields(self): + def update_fields_of_loads_tab(self): self.lb_dataset.clear() for desc in self.datasets['desc']: item = QListWidgetItem(desc) @@ -410,9 +410,9 @@ def on_tab_changed(self, index): if index == 1: # Loads tab is at index 0 # Time tab is at index 1 - self.update_time_tab_fields() + self.update_fields_of_time_tab() - def update_time_tab_fields(self): + def update_fields_of_time_tab(self): # Update the Time tab fields when it is activated. if self.lb_dataset.currentItem() is not None and self.lb_mon.currentItem() is not None: # Get the items selected by the user. From f99290ce4ecbc38a9e4ee27e1341f631803a6294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Tue, 31 Mar 2026 17:35:48 +0200 Subject: [PATCH 51/63] Apply sorting of subcases to both viewers --- loadscompare/compare.py | 1 + responseviewer/view.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/loadscompare/compare.py b/loadscompare/compare.py index dbcc5fa..b489f70 100644 --- a/loadscompare/compare.py +++ b/loadscompare/compare.py @@ -430,6 +430,7 @@ def update_fields_of_time_tab(self): # Populate list of subcases by finding all integers in monstation.keys() monstation = dataset_sel[mon_sel] subcase_keys = [key for key in monstation if key.isdigit()] + subcase_keys.sort(key=int) self.lb_subcases_time.clear() self.lb_subcases_time.addItems(subcase_keys) diff --git a/responseviewer/view.py b/responseviewer/view.py index e8e6f34..4a4dc7d 100644 --- a/responseviewer/view.py +++ b/responseviewer/view.py @@ -192,8 +192,9 @@ def load_response(self): def update_fields(self): if self.responses is not None: self.lb_subcase.clear() - for i in self.responses: - self.lb_subcase.addItem(QListWidgetItem(i)) + list_of_subcases = list(self.responses) + list_of_subcases.sort(key=int) + self.lb_subcase.addItems(list_of_subcases) def command_line_interface(): From 5dc466e4ebb286032838b0fbdfcc18462f803feb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Wed, 1 Apr 2026 11:13:55 +0200 Subject: [PATCH 52/63] Make an explicit copy using DataFrame.to_numpy().copy() --- loadskernel/io_functions/read_mona.py | 26 +++++++++++++------------- setup.py | 2 +- tests/list_of_packages.txt | 4 ++-- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/loadskernel/io_functions/read_mona.py b/loadskernel/io_functions/read_mona.py index 75c6cd8..40e7185 100644 --- a/loadskernel/io_functions/read_mona.py +++ b/loadskernel/io_functions/read_mona.py @@ -177,12 +177,12 @@ def add_GRIDS(pandas_grids): # This functions relies on the Pandas data frames from the bdf reader. n = pandas_grids.shape[0] strcgrid = {} - strcgrid['ID'] = pandas_grids['ID'].to_numpy(dtype='int') - strcgrid['CD'] = pandas_grids['CD'].to_numpy(dtype='int') - strcgrid['CP'] = pandas_grids['CP'].to_numpy(dtype='int') + strcgrid['ID'] = pandas_grids['ID'].to_numpy(dtype='int').copy() + strcgrid['CD'] = pandas_grids['CD'].to_numpy(dtype='int').copy() + strcgrid['CP'] = pandas_grids['CP'].to_numpy(dtype='int').copy() strcgrid['n'] = n strcgrid['set'] = np.arange(n * 6).reshape((n, 6)) - strcgrid['offset'] = pandas_grids[['X1', 'X2', 'X3']].to_numpy(dtype='float') + strcgrid['offset'] = pandas_grids[['X1', 'X2', 'X3']].to_numpy(dtype='float').copy() return strcgrid @@ -190,7 +190,7 @@ def add_shell_elements(pandas_panels): # This functions relies on the Pandas data frames from the bdf reader. strcshell = {} n = pandas_panels.shape[0] - strcshell['ID'] = pandas_panels['ID'].to_numpy(dtype='int') + strcshell['ID'] = pandas_panels['ID'].to_numpy(dtype='int').copy() strcshell['cornerpoints'] = np.array(pandas_panels[['G1', 'G2', 'G3', 'G4']]) strcshell['CD'] = np.zeros(n) # Assumption: panels are given in global coord system strcshell['CP'] = np.zeros(n) @@ -207,8 +207,8 @@ def add_panels_from_CAERO(pandas_caero, pandas_aefact): panels = {"ID": [], 'CP': [], 'CD': [], "cornerpoints": []} for index, caerocard in pandas_caero.iterrows(): # get the four corner points of the CAERO card - X1 = caerocard[['X1', 'Y1', 'Z1']].to_numpy(dtype='float') - X4 = caerocard[['X4', 'Y4', 'Z4']].to_numpy(dtype='float') + X1 = caerocard[['X1', 'Y1', 'Z1']].to_numpy(dtype='float').copy() + X4 = caerocard[['X4', 'Y4', 'Z4']].to_numpy(dtype='float').copy() X2 = X1 + np.array([caerocard['X12'], 0.0, 0.0]) X3 = X4 + np.array([caerocard['X43'], 0.0, 0.0]) # calculate LE, Root and Tip vectors [x,y,z]^T @@ -362,9 +362,9 @@ def add_CORD2R(pandas_cord2r, coord): for _, row in pandas_cord2r.iterrows(): ID = int(row['ID']) RID = int(row['RID']) - A = row[['A1', 'A2', 'A3']].to_numpy(dtype='float').squeeze() - B = row[['B1', 'B2', 'B3']].to_numpy(dtype='float').squeeze() - C = row[['C1', 'C2', 'C3']].to_numpy(dtype='float').squeeze() + A = row[['A1', 'A2', 'A3']].to_numpy(dtype='float').squeeze().copy() + B = row[['B1', 'B2', 'B3']].to_numpy(dtype='float').squeeze().copy() + C = row[['C1', 'C2', 'C3']].to_numpy(dtype='float').squeeze().copy() # build coord z = B - A y = np.cross(B - A, C - A) @@ -466,9 +466,9 @@ def add_MONPNT1(pandas_monpnts): mongrid['ID'] = np.arange(1, n + 1) mongrid['name'] = pandas_monpnts['NAME'].to_list() mongrid['comp'] = pandas_monpnts['COMP'].to_list() - mongrid['CD'] = pandas_monpnts['CD'].to_numpy(dtype='int') - mongrid['CP'] = pandas_monpnts['CP'].to_numpy(dtype='int') + mongrid['CD'] = pandas_monpnts['CD'].to_numpy(dtype='int').copy() + mongrid['CP'] = pandas_monpnts['CP'].to_numpy(dtype='int').copy() mongrid['n'] = n mongrid['set'] = np.arange(n * 6).reshape((n, 6)) - mongrid['offset'] = pandas_monpnts[['X', 'Y', 'Z']].to_numpy(dtype='float') + mongrid['offset'] = pandas_monpnts[['X', 'Y', 'Z']].to_numpy(dtype='float').copy() return mongrid diff --git a/setup.py b/setup.py index d7aaaf1..328f7c9 100644 --- a/setup.py +++ b/setup.py @@ -45,7 +45,7 @@ 'pyyaml', # Pandas 3.0.0 comes with changes that are not yet supported, # see https://github.com/DLR-AE/LoadsKernel/issues/86 - 'pandas<3.0.0' + 'pandas' ], extras_require={'extras': ['mpi4py', 'mayavi', diff --git a/tests/list_of_packages.txt b/tests/list_of_packages.txt index b1d0bbd..b010ee7 100644 --- a/tests/list_of_packages.txt +++ b/tests/list_of_packages.txt @@ -8,7 +8,7 @@ scipy h5py pytables pyyaml -pandas<3.0.0 +pandas # Extras, optional mpi4py @@ -23,7 +23,7 @@ pyside6 pytest pytest-cov sqlite -jupyter-book==1.0.4 +jupyter-book # Coding style flake8 From 5bf91c32cee7ebbc1af26ecb79e278ec9260917f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Wed, 1 Apr 2026 11:32:29 +0200 Subject: [PATCH 53/63] Change wording from 'cutting loads' to 'section loads' --- loadskernel/model.py | 4 ++-- loadskernel/plotting_standard.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/loadskernel/model.py b/loadskernel/model.py index 6431b6a..6765999 100644 --- a/loadskernel/model.py +++ b/loadskernel/model.py @@ -138,8 +138,8 @@ def build_mongrid(self): logging.warning('No Monitoring Stations are created!') """ This is an empty dummy monitoring stations, which is necessary when no monitoring stations are defined, - because monstations are expected to exist for example for the calculation of cutting forces, which are in - turn expected in the post processing. However, this procedure allows the code to run without any given + because monstations are expected to exist for example for the calculation of internal section forces, which + are in turn expected in the recovery step. However, this procedure allows the code to run without any given monitoring stations, which are not available for all models. """ self.mongrid = {'ID': np.array([0]), diff --git a/loadskernel/plotting_standard.py b/loadskernel/plotting_standard.py index 49d622a..ba62b02 100755 --- a/loadskernel/plotting_standard.py +++ b/loadskernel/plotting_standard.py @@ -77,9 +77,9 @@ def plot_monstations(self, filename_pdf): self.pp = PdfPages(filename_pdf) self.potato_plots() if self.cuttingforces_wing: - self.cuttingforces_along_axis_plots(monstations=self.cuttingforces_wing, axis=1) + self.plot_loads_along_axis(monstations=self.cuttingforces_wing, axis=1) if self.cuttingforces_fuselage: - self.cuttingforces_along_axis_plots(monstations=self.cuttingforces_fuselage, axis=0) + self.plot_loads_along_axis(monstations=self.cuttingforces_fuselage, axis=0) self.pp.close() logging.info('Plots saved as %s', filename_pdf) @@ -196,9 +196,9 @@ def potato_plots(self): self.potato_plot_nicely(station, station, dof_xaxis, dof_yaxis, var_xaxis, var_yaxis) plt.close() - def cuttingforces_along_axis_plots(self, monstations, axis): + def plot_loads_along_axis(self, monstations, axis): assert axis in [0, 1, 2], 'Plotting along an axis only supported for axis 0, 1 or 2!' - logging.info('Start plotting cutting forces along axis %d...', axis) + logging.info('Start plotting internal section loads along axis %d...', axis) # Read the data required for plotting. loads = [] offsets = [] @@ -273,7 +273,7 @@ def cuttingforces_along_axis_plots(self, monstations, axis): plt.close() def plot_monstations_time(self, filename_pdf): - logging.info('start plotting cutting forces over time ...') + logging.info('start plotting internal section loads over time ...') pp = PdfPages(filename_pdf) potato = np.sort(np.unique(self.potatos_fz_mx + self.potatos_mx_my + self.potatos_fz_my + self.potatos_fy_mx + self.potatos_mx_mz + self.potatos_my_mz)) From ccaeeff357df68d3873fd856bbbf979d91b66d1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Wed, 1 Apr 2026 11:33:19 +0200 Subject: [PATCH 54/63] Give class and functions a more specific name --- loadskernel/program_flow.py | 7 ++++--- .../{post_processing.py => recover_loads_and_defo.py} | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) rename loadskernel/{post_processing.py => recover_loads_and_defo.py} (98%) diff --git a/loadskernel/program_flow.py b/loadskernel/program_flow.py index 71120fb..7a91683 100755 --- a/loadskernel/program_flow.py +++ b/loadskernel/program_flow.py @@ -14,7 +14,7 @@ pass import loadskernel -from loadskernel import solution_sequences, post_processing, gather_loads, auxiliary_output, plotting_standard +from loadskernel import recover_loads_and_defo, solution_sequences, gather_loads, auxiliary_output, plotting_standard from loadskernel.io_functions import data_handling import loadskernel.model as model_modul from loadskernel.cfd_interfaces.mpi_helper import setup_mpi @@ -189,10 +189,11 @@ def main_core(self, model, jcl, i): # Also, the name 'post_processing' might be misleading here, as it is not post processing of the entire # job (post=True), but only of the trim / sim solution sequence. if solution_i.successful: - post_processing_i = post_processing.PostProcessing(jcl, model, jcl.trimcase[i], solution_i.response) + post_processing_i = recover_loads_and_defo.RecoverLoadsAndDeformations(jcl, model, jcl.trimcase[i], + solution_i.response) post_processing_i.force_summation_method() post_processing_i.euler_transformation() - post_processing_i.cuttingforces() + post_processing_i.integrate_loads() del post_processing_i # Look if any other special analyses are requested (such as flutter, derivatives, pulses) in the simcase. if 'flutter' in jcl.simcase[i] and jcl.simcase[i]['flutter']: diff --git a/loadskernel/post_processing.py b/loadskernel/recover_loads_and_defo.py similarity index 98% rename from loadskernel/post_processing.py rename to loadskernel/recover_loads_and_defo.py index e8fc54d..18d639c 100755 --- a/loadskernel/post_processing.py +++ b/loadskernel/recover_loads_and_defo.py @@ -7,7 +7,7 @@ from loadskernel.io_functions.data_handling import load_hdf5_sparse_matrix, load_hdf5_dict -class PostProcessing(): +class RecoverLoadsAndDeformations(): """ In this class calculations are made that follow up on every simulation. The functions should be able to handle both trim calculations and time simulations. @@ -153,8 +153,8 @@ def euler_transformation(self): dest_coord=1000000) response['Ug'][i_step, :] = response['Ug_r'][i_step, :] + response['Ug_f'][i_step, :] - def cuttingforces(self): - logging.info('Calculating cutting forces & moments...') + def integrate_loads(self): + logging.info('Calculating section forces & moments...') response = self.response response['Pmon_local'] = np.zeros((len(response['t']), 6 * self.mongrid['n'])) for i_step in range(len(response['t'])): From 3c7e46f48a6e2fd87dc3fcc86dd800cd110871b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Wed, 1 Apr 2026 13:39:00 +0200 Subject: [PATCH 55/63] add latest features to changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 229527d..8e5fdc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,12 @@ New releases are marked in the repository using tags. Simply checkout the master branch for the lastest version or use git checkout if you require a specific release, for example 'git checkout 2022.10'. # Next Release +- New interface for Nastran 95 +- Tested RANS simulations with SU2, found and fixed a bug during splining +- Loads Compare can now plot time histories per subcase +- New tool Response Viewer to plot time histories of aircraft states per subcase (e.g. velocities, rates, angles, alpha, beta, etc.) - Dropped support for Python versions lower than 3.12 +- Dependencies: removed version limitation of Pandas # Release 2026.01 - Added gust and flutter simulation in the frequency domain based of GAFs obtained from linearized CFD (using SU2 and via pulse simulations). This is work is still very new and therefore needs more testing and/or applications. From 5d60c048afd0c015ffdbc779c3e4ea9bd1b775ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Thu, 2 Apr 2026 10:46:42 +0200 Subject: [PATCH 56/63] Derive number of mode shapes from modal deformations 'Uf'. --- responseviewer/plotting.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/responseviewer/plotting.py b/responseviewer/plotting.py index 08b93a8..3738390 100644 --- a/responseviewer/plotting.py +++ b/responseviewer/plotting.py @@ -45,9 +45,13 @@ def timehistories(self, subcases, quantities,): idx = self.states_avail.index(quantity) data = self.responses[subcase]['X'][:, idx] elif quantity in self.commands_avail: - # Commands are stored in the last 6 rows of 'X' in the same order as in commands_avail. + # Derive number of mode shapes from modal deformations 'Uf'. + n_modes = self.responses[subcase]['Uf'].shape[1] + # Find commands in state vector/matrix 'X' with the following sequence: + # X = [12 rigid body states, 2*n_modes, 6 commands, many unsteady lag states] + commands = self.responses[subcase]['X'][:, 12 + 2 * n_modes: 12 + 2 * n_modes + 6] + # Commands are stored in the same sequence as in commands_avail idx = self.commands_avail.index(quantity) - commands = self.responses[subcase]['X'][:, -6:] data = commands[:, idx] elif quantity in self.loadfactors_avail: # Load factors are stored in 'Nxyz' in the same order as in loadfactors_avail. From 069eeb5159b9e3b60e72a7649296cfe9d062f8c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Thu, 2 Apr 2026 12:36:35 +0200 Subject: [PATCH 57/63] Fix unit of rates (deg/s) --- responseviewer/plotting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/responseviewer/plotting.py b/responseviewer/plotting.py index 3738390..6317909 100644 --- a/responseviewer/plotting.py +++ b/responseviewer/plotting.py @@ -6,7 +6,7 @@ class Plotting(): states_avail = ['x [m]', 'y [m]', 'z [m]', 'Phi [deg]', 'Theta [deg]', 'Psi [deg]', - 'u [m/s]', 'v [m/s]', 'w [m/s]', 'p [deg]', 'q [deg]', 'r [deg]'] + 'u [m/s]', 'v [m/s]', 'w [m/s]', 'p [deg/s]', 'q [deg/s]', 'r [deg/s]'] commands_avail = ['Xi [deg]', 'Eta [deg]', 'Zeta [deg]', 'Thrust [N]', 'Stabilizer [deg]', 'Flaps [deg]'] loadfactors_avail = ['Nx [-]', 'Ny [-]', 'Nz [-]'] other_avail = ['q_dyn [Pa]', 'alpha [deg]', 'beta [deg]', 'p1 [m]', 'F1 [N]'] @@ -68,7 +68,7 @@ def timehistories(self, subcases, quantities,): # In case the quantity is not found, create some dummy data. subcase = 'Not found' data = np.zeros_like(time) - if '[deg]' in quantity: + if '[deg]' in quantity or '[deg/s]' in quantity: data *= 180.0 / np.pi # Plot the time history for the current subcase and quantity. a.plot(time, data, label=subcase) From 528781599d8ba1d6de5c126822093e43cce86230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Fri, 29 May 2026 10:34:26 +0200 Subject: [PATCH 58/63] Update all actions used in the GitHub workflows to the latest version --- .github/workflows/build.yml | 8 ++++---- .github/workflows/coding-style.yml | 8 ++++---- .github/workflows/housekeeping.yml | 2 +- .github/workflows/python-publish.yml | 8 ++++---- .github/workflows/regression-tests.yml | 26 +++++++++++++------------- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5cffabf..0f3f70f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,9 +25,9 @@ jobs: python-version: ["3.12", "3.13"] fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Build and install @@ -54,9 +54,9 @@ jobs: env: DISPLAY: ':99.0' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} # Step 2 to make GUIs work diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index 3dbf0c1..72b931c 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -22,9 +22,9 @@ jobs: # Add the Python versions here to run tests on new(er) versions. python-version: ["3.13"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -45,9 +45,9 @@ jobs: # Add multiple Python versions here to run tests on new(er) versions. python-version: ["3.13"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/housekeeping.yml b/.github/workflows/housekeeping.yml index 441ea32..77a7181 100644 --- a/.github/workflows/housekeeping.yml +++ b/.github/workflows/housekeeping.yml @@ -18,7 +18,7 @@ jobs: pull-requests: write steps: - - uses: actions/stale@v5 + - uses: actions/stale@v10 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: > diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 94f23ef..e9e1459 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -18,9 +18,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v6 with: python-version: '3.x' - name: Install dependencies @@ -30,7 +30,7 @@ jobs: - name: Build package run: python -m build - name: Store the distribution packages - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: python-package-distributions path: dist/ @@ -47,7 +47,7 @@ jobs: id-token: write # IMPORTANT: mandatory for trusted publishing steps: - name: Download all the dists - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: python-package-distributions path: dist/ diff --git a/.github/workflows/regression-tests.yml b/.github/workflows/regression-tests.yml index 34970fa..cb06d53 100644 --- a/.github/workflows/regression-tests.yml +++ b/.github/workflows/regression-tests.yml @@ -32,9 +32,9 @@ jobs: env: DISPLAY: ':99.0' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} # Step 2 to make GUIs work @@ -63,8 +63,8 @@ jobs: coverage report coverage xml -o coverage.xml coverage html --directory ./coverage - - name: Upload test restults and coverage as an artifact - uses: actions/upload-artifact@v4 + - name: Upload test results and coverage as an artifact + uses: actions/upload-artifact@v7 with: name: test results and coverage path: | @@ -82,12 +82,12 @@ jobs: # Select Python version to be used for compiling here. python-version: ["3.13"] steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 with: node-version: 18.x - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -95,7 +95,7 @@ jobs: python -m pip install --upgrade pip # Install the package itself to make sure that all imports work. pip install .[test] - - name: Assemble the tutorials to a jupyter book and build htlm pages + - name: Assemble the tutorials to a jupyter book and build html pages run: | cd ./doc/tutorials jupyter-book build --execute --html @@ -104,7 +104,7 @@ jobs: mkdir ./doc/html mv ./doc/tutorials/_build/html ./doc/html/tutorials - name: Upload Jupyter book as an artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: tutorials path: ./doc/html @@ -115,7 +115,7 @@ jobs: # Add a dependency to the build job needs: [Jupyter, Pytest] steps: - - uses: actions/download-artifact@v4.1.7 + - uses: actions/download-artifact@v8 with: merge-multiple: true - name: See what we've got and merge artifacts @@ -126,7 +126,7 @@ jobs: mv ./coverage ./pages/coverage - name: Upload artifact for pages # This is not a normal artifact but one that can be deployed to the GitHub pages in the next step - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v7 with: name: github-pages # This name may not be changed according to the documentation path: ./pages # There must be only one path @@ -147,7 +147,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Setup GitHub Pages - uses: actions/configure-pages@v4 + uses: actions/configure-pages@v6 - name: Deploy to Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 From 31158291bdeba80b7dd96457d35f7509ad6a7ffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Fri, 29 May 2026 11:32:19 +0200 Subject: [PATCH 59/63] Harmonize conda and pip installation --- .github/workflows/regression-tests.yml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/.github/workflows/regression-tests.yml b/.github/workflows/regression-tests.yml index cb06d53..97df9de 100644 --- a/.github/workflows/regression-tests.yml +++ b/.github/workflows/regression-tests.yml @@ -39,23 +39,21 @@ jobs: python-version: ${{ matrix.python-version }} # Step 2 to make GUIs work - uses: tlambert03/setup-qt-libs@v1 - - name: Install dependencies + - name: Build and install run: | # Step 3 to make GUIs work /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1920x1200x24 -ac +extension GLX # Install same requirements as to be used during regression testing source $CONDA/etc/profile.d/conda.sh - conda config --add channels conda-forge - conda config --set channel_priority strict - conda create -n pytest_env python=${{ matrix.python-version }} - conda activate pytest_env - conda install -y -q --file ./tests/list_of_packages.txt || true - # Install the package itself to make sure that all imports work. + conda create -n my_env python=${{ matrix.python-version }} + conda activate my_env + conda install -y -q -c conda-forge --file ./tests/list_of_packages.txt || true + # Install with -e (in editable mode) to allow the tracking of the test coverage pip install -e .[extras,test] - name: Analysing the code with pytest run: | source $CONDA/etc/profile.d/conda.sh - conda activate pytest_env + conda activate my_env # Run the actual testing of the code with pytest # Using python -m pytest is necessary because pytest has the habit of not looking in the site-packages of the venv python -m pytest -v --basetemp=./tmp -k 'test_unittests or test_gui or test_integration_public' --cov=loadskernel --cov=modelviewer --cov=loadscompare --junitxml=testresult.xml @@ -90,7 +88,7 @@ jobs: uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - - name: Install dependencies + - name: Build and install run: | python -m pip install --upgrade pip # Install the package itself to make sure that all imports work. From 743efa311df4a41c8ba9b106fcd29e8f9ef814fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Fri, 29 May 2026 13:13:06 +0200 Subject: [PATCH 60/63] Fixed upload-pages-artifact version to v5 --- .github/workflows/regression-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/regression-tests.yml b/.github/workflows/regression-tests.yml index 97df9de..22821e6 100644 --- a/.github/workflows/regression-tests.yml +++ b/.github/workflows/regression-tests.yml @@ -124,7 +124,7 @@ jobs: mv ./coverage ./pages/coverage - name: Upload artifact for pages # This is not a normal artifact but one that can be deployed to the GitHub pages in the next step - uses: actions/upload-pages-artifact@v7 + uses: actions/upload-pages-artifact@v5 with: name: github-pages # This name may not be changed according to the documentation path: ./pages # There must be only one path From 853b79af293762342e51f7694dd12337fdad0b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Fri, 29 May 2026 13:46:27 +0200 Subject: [PATCH 61/63] Switch to pyvista/setup-headless-display-action@v3 --- .github/workflows/build.yml | 9 ++------- .github/workflows/regression-tests.yml | 9 ++------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0f3f70f..9a98483 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -50,21 +50,16 @@ jobs: # Add multiple Python versions here to run tests on new(er) versions. python-version: ["3.12", "3.13"] fail-fast: false - # Step 1 to make GUIs work, see https://pytest-qt.readthedocs.io/en/latest/troubleshooting.html - env: - DISPLAY: ':99.0' steps: - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - # Step 2 to make GUIs work - - uses: tlambert03/setup-qt-libs@v1 + - name: Setup headless display + uses: pyvista/setup-headless-display-action@v3 - name: Build and install run: | - # Step 3 to make GUIs work - /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1920x1200x24 -ac +extension GLX # Install same requirements as to be used during regression testing source $CONDA/etc/profile.d/conda.sh conda create -n my_env python=${{ matrix.python-version }} diff --git a/.github/workflows/regression-tests.yml b/.github/workflows/regression-tests.yml index 22821e6..c375ba1 100644 --- a/.github/workflows/regression-tests.yml +++ b/.github/workflows/regression-tests.yml @@ -28,21 +28,16 @@ jobs: matrix: # Add multiple Python versions here to run tests on new(er) versions. python-version: ["3.13"] - # Step 1 to make GUIs work, see https://pytest-qt.readthedocs.io/en/latest/troubleshooting.html - env: - DISPLAY: ':99.0' steps: - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - # Step 2 to make GUIs work - - uses: tlambert03/setup-qt-libs@v1 + - name: Setup headless display + uses: pyvista/setup-headless-display-action@v3 - name: Build and install run: | - # Step 3 to make GUIs work - /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1920x1200x24 -ac +extension GLX # Install same requirements as to be used during regression testing source $CONDA/etc/profile.d/conda.sh conda create -n my_env python=${{ matrix.python-version }} From 8a9245eb2a852421a8c43e2f40127c0ac5e6cebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Fri, 29 May 2026 14:53:46 +0200 Subject: [PATCH 62/63] Make Tutorial nicer by hiding the output --- doc/tutorials/first_steps.ipynb | 18 +++++++++++++++--- doc/tutorials/flutter.ipynb | 8 ++++++-- doc/tutorials/gust_time_domain.ipynb | 5 ++++- doc/tutorials/maneuver_loads.ipynb | 6 +++++- doc/tutorials/trim.ipynb | 6 +++++- 5 files changed, 35 insertions(+), 8 deletions(-) diff --git a/doc/tutorials/first_steps.ipynb b/doc/tutorials/first_steps.ipynb index 8e64eaf..9bbc221 100644 --- a/doc/tutorials/first_steps.ipynb +++ b/doc/tutorials/first_steps.ipynb @@ -42,7 +42,11 @@ "cell_type": "code", "execution_count": null, "id": "744e8fab", - "metadata": {}, + "metadata": { + "tags": [ + "hide-output" + ] + }, "outputs": [], "source": [ "k = program_flow.Kernel('jcl_dc3_firststeps', pre=True, main=False, post=False,\n", @@ -118,7 +122,11 @@ "cell_type": "code", "execution_count": null, "id": "3c6bf524", - "metadata": {}, + "metadata": { + "tags": [ + "hide-output" + ] + }, "outputs": [], "source": [ "k = program_flow.Kernel(job_name='jcl_dc3_firststeps', pre=False, main=True, post=False,\n", @@ -176,7 +184,11 @@ "cell_type": "code", "execution_count": null, "id": "1151d9ec", - "metadata": {}, + "metadata": { + "tags": [ + "hide-output" + ] + }, "outputs": [], "source": [ "k = program_flow.Kernel(job_name='jcl_dc3_firststeps', pre=False, main=False, post=True,\n", diff --git a/doc/tutorials/flutter.ipynb b/doc/tutorials/flutter.ipynb index 0e8a222..f6e17bd 100644 --- a/doc/tutorials/flutter.ipynb +++ b/doc/tutorials/flutter.ipynb @@ -153,7 +153,11 @@ "cell_type": "code", "execution_count": null, "id": "a2aab232-5e9d-4260-b07e-8ec160b863c3", - "metadata": {}, + "metadata": { + "tags": [ + "hide-output" + ] + }, "outputs": [], "source": [ "from loadskernel import program_flow\n", @@ -208,7 +212,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.12" + "version": "3.13.11" } }, "nbformat": 4, diff --git a/doc/tutorials/gust_time_domain.ipynb b/doc/tutorials/gust_time_domain.ipynb index b79ea07..9176e0a 100644 --- a/doc/tutorials/gust_time_domain.ipynb +++ b/doc/tutorials/gust_time_domain.ipynb @@ -114,7 +114,10 @@ "execution_count": null, "id": "29800512-fa3e-408d-b698-2c37a048fa55", "metadata": { - "scrolled": true + "scrolled": true, + "tags": [ + "hide-output" + ] }, "outputs": [], "source": [ diff --git a/doc/tutorials/maneuver_loads.ipynb b/doc/tutorials/maneuver_loads.ipynb index c849693..5dc47ef 100644 --- a/doc/tutorials/maneuver_loads.ipynb +++ b/doc/tutorials/maneuver_loads.ipynb @@ -203,7 +203,11 @@ "cell_type": "code", "execution_count": null, "id": "38c976bf-f470-4c0f-b41e-c0c07d739e85", - "metadata": {}, + "metadata": { + "tags": [ + "hide-output" + ] + }, "outputs": [], "source": [ "from loadskernel import program_flow\n", diff --git a/doc/tutorials/trim.ipynb b/doc/tutorials/trim.ipynb index 67e1fa8..30b07eb 100644 --- a/doc/tutorials/trim.ipynb +++ b/doc/tutorials/trim.ipynb @@ -26,7 +26,11 @@ "cell_type": "code", "execution_count": null, "id": "e593fcac-7661-451c-ab04-2e9ed077362c", - "metadata": {}, + "metadata": { + "tags": [ + "hide-output" + ] + }, "outputs": [], "source": [ "from loadskernel import program_flow\n", From dbe716b94792b82e43fb9e1dc8d4a69e9e5361bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20Vo=C3=9F?= Date: Fri, 29 May 2026 15:15:22 +0200 Subject: [PATCH 63/63] Prepare new release --- CHANGELOG.md | 5 +++-- setup.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e5fdc2..3017d2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,14 @@ # Note New releases are marked in the repository using tags. Simply checkout the master branch for the lastest version or use git checkout if you require a specific release, for example 'git checkout 2022.10'. -# Next Release +# Release 2026.05 - New interface for Nastran 95 -- Tested RANS simulations with SU2, found and fixed a bug during splining +- CFD simulations with SU2: tested with RANS, found and fixed a bug during splining - Loads Compare can now plot time histories per subcase - New tool Response Viewer to plot time histories of aircraft states per subcase (e.g. velocities, rates, angles, alpha, beta, etc.) - Dropped support for Python versions lower than 3.12 - Dependencies: removed version limitation of Pandas +- Testing: Maintenance of continuous integration workflows # Release 2026.01 - Added gust and flutter simulation in the frequency domain based of GAFs obtained from linearized CFD (using SU2 and via pulse simulations). This is work is still very new and therefore needs more testing and/or applications. diff --git a/setup.py b/setup.py index 328f7c9..232f4da 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name='LoadsKernel', - version='2026.01.1', + version='2026.05', description=("The Loads Kernel Software allows for the calculation of quasi-steady and dynamic maneuver loads, " "unsteady gust loads in the time and frequency domain as well as dynamic landing loads based on a " "generic landing gear module."),