├── src ├── lib │ ├── .gitignore │ ├── minimal │ │ ├── create_minimal_sources.bat │ │ ├── cfg │ │ │ └── unifdef_defile.h │ │ ├── readme.md │ │ ├── rainflow.h │ │ └── rainflow.c │ ├── CMakeLists.txt │ └── config.h.in ├── python │ ├── .gitignore │ ├── tests │ │ ├── .gitignore │ │ ├── __init__.py │ │ ├── examples.py │ │ └── test_rfcnt.py │ ├── requirements.txt │ ├── notebooks │ │ ├── example.xcf │ │ ├── jupyter_screenshot.png │ │ └── example_damage_history.png │ ├── version.py │ ├── MANIFEST.in │ ├── run_examples.py │ ├── prebuilds.json │ ├── cmake │ │ ├── numpy_get_include.py │ │ └── rfcnt_target_functions.cmake │ ├── pyproject.toml │ ├── run_tests.py │ ├── lib │ │ └── config.h │ ├── rfcnt.pyi │ ├── utils.py │ ├── LICENSE │ ├── README.md │ ├── setup.py │ ├── __init__.py │ └── CMakeLists.txt ├── 3rd_party │ └── unifdef │ │ ├── unifdef.exe │ │ └── unifdef.txt ├── CMakeLists.txt └── matlab │ ├── CMakeLists.txt │ └── rfc.c ├── test ├── long_series.h ├── CMakeLists.txt ├── rfc_wrapper_simple.cpp └── rfc_wrapper_advanced.cpp ├── coan_invoke-minimal.sh ├── coan_invoke-minimal.bat ├── .gitmodules ├── .gitattributes ├── .gitignore ├── coan-args-minimal.in ├── tools └── where_to_get_coan.txt ├── LICENSE ├── .github └── workflows │ └── run_test.yml ├── CMakeLists.txt ├── content.txt └── README.md /src/lib/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/python/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/python/tests/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/long_series.h: -------------------------------------------------------------------------------- 1 | #define DATA_LEN 10001 2 | -------------------------------------------------------------------------------- /coan_invoke-minimal.sh: -------------------------------------------------------------------------------- 1 | coan spin --dir coan --prefix $PWD -fcoan-args-minimal.in 2 | -------------------------------------------------------------------------------- /src/python/requirements.txt: -------------------------------------------------------------------------------- 1 | matplotlib>=3.2.2 # for tests and examples 2 | pandas # for tests and examples 3 | -------------------------------------------------------------------------------- /src/python/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from . import examples # noqa F401 2 | from . import test_rfcnt # noqa F401 3 | -------------------------------------------------------------------------------- /coan_invoke-minimal.bat: -------------------------------------------------------------------------------- 1 | tools\coan-6.0.1-x86_64.exe spin --dir coan --prefix %cd% -fcoan-args-minimal.in 2 | pause 3 | -------------------------------------------------------------------------------- /src/3rd_party/unifdef/unifdef.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a-ma72/rainflow/HEAD/src/3rd_party/unifdef/unifdef.exe -------------------------------------------------------------------------------- /src/python/notebooks/example.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a-ma72/rainflow/HEAD/src/python/notebooks/example.xcf -------------------------------------------------------------------------------- /src/python/version.py: -------------------------------------------------------------------------------- 1 | _version = (0, 5, 0, "") 2 | __version__ = "%d.%d.%d%s" % _version 3 | __author__ = "Andreas Martin" -------------------------------------------------------------------------------- /src/python/notebooks/jupyter_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a-ma72/rainflow/HEAD/src/python/notebooks/jupyter_screenshot.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/3rd_party/greatest"] 2 | path = src/3rd_party/greatest 3 | url = https://github.com/silentbicycle/greatest.git 4 | -------------------------------------------------------------------------------- /src/python/notebooks/example_damage_history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a-ma72/rainflow/HEAD/src/python/notebooks/example_damage_history.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.c linguist-detectable=true 2 | *.hpp linguist-detectable=false 3 | *.cpp linguist-detectable=false 4 | *.m linguist-detectable=false 5 | -------------------------------------------------------------------------------- /src/python/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include lib/config.h 2 | include lib/rainflow.c 3 | include lib/rainflow.h 4 | include lib/rainflow.hpp 5 | include src/rfcnt.cpp 6 | include LICENSE -------------------------------------------------------------------------------- /src/python/run_examples.py: -------------------------------------------------------------------------------- 1 | from .tests import examples 2 | 3 | 4 | def main(): 5 | print("Running example 1...") 6 | examples.example_1() 7 | 8 | 9 | if __name__ == '__main__': 10 | main() 11 | -------------------------------------------------------------------------------- /src/lib/minimal/create_minimal_sources.bat: -------------------------------------------------------------------------------- 1 | ..\..\3rd_party\unifdef\unifdef -fcfg\unifdef_defile.h -orainflow.c -B ..\rainflow.c 2 | ..\..\3rd_party\unifdef\unifdef -fcfg\unifdef_defile.h -orainflow.h -B ..\rainflow.h 3 | pause -------------------------------------------------------------------------------- /src/python/prebuilds.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.23.2": {"capi": 16, "target": "rfcnt_npy_1_23_2_api_v16.cp311-win_amd64"} 3 | ,"2.0.0": {"capi": 18, "target": "rfcnt_npy_2_0_0_api_v18.cp311-win_amd64"} 4 | ,"1.23.2": {"capi":16, "target": "root"} 5 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /coan/ 2 | /minimal/ 3 | /sf/ 4 | __pycache__/ 5 | build/ 6 | src/python/_ext 7 | src/python/venv/ 8 | src/python/dist/ 9 | src/python/numpy/ 10 | src/python/MANIFEST 11 | cmake-build-debug/ 12 | cmake-build-release/ 13 | rfcnt.egg-info 14 | .idea/ 15 | .vscode/ 16 | .editorconfig/ 17 | .so 18 | .sublime-workspace 19 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(lib) 2 | 3 | add_library(greatest INTERFACE) 4 | target_include_directories(greatest INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/3rd_party/greatest) 5 | 6 | if (RFC_EXPORT_MEX) 7 | add_subdirectory(matlab) 8 | endif () 9 | if (RFC_EXPORT_PY) 10 | add_subdirectory(python) 11 | endif () 12 | -------------------------------------------------------------------------------- /src/python/cmake/numpy_get_include.py: -------------------------------------------------------------------------------- 1 | import os, numpy 2 | 3 | numpy_include_dir = numpy.get_include() 4 | include_file = os.path.join(numpy_include_dir, "numpy", "arrayobject.h").replace("\\", "/") 5 | with open("numpy_arrayobject.h", "wt") as f: 6 | f.write(f"#include \"{include_file}\"") 7 | print(numpy_include_dir.replace("\\", "/"), end='') -------------------------------------------------------------------------------- /src/python/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=42", 4 | "wheel", 5 | # For target "build_wheel_isolated" enable one numpy package: 6 | # "oldest-supported-numpy", # for Numpy 1.x versions 7 | "numpy==2.0", # for Numpy 2.x versions 8 | ] 9 | build-backend = "setuptools.build_meta" 10 | -------------------------------------------------------------------------------- /src/lib/minimal/cfg/unifdef_defile.h: -------------------------------------------------------------------------------- 1 | // https://dotat.at/prog/unifdef/ 2 | 3 | #define RFC_MINIMAL 1 4 | #undef RFC_USE_INTEGRAL_COUNTS 5 | #undef RFC_USE_DELEGATES 6 | #undef RFC_HCM_SUPPORT 7 | #undef RFC_ASTM_SUPPORT 8 | #undef RFC_TP_SUPPORT 9 | #undef RFC_DH_SUPPORT 10 | #undef RFC_AT_SUPPORT 11 | #undef RFC_AR_SUPPORT 12 | #undef RFC_GLOBAL_EXTREMA 13 | #undef RFC_DAMAGE_FAST 14 | #undef RFC_DEBUG_FLAGS 15 | #undef _DEBUG -------------------------------------------------------------------------------- /src/lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Rainflow core library 2 | 3 | project(rfc_core LANGUAGES C) 4 | 5 | add_library(rfc_core STATIC 6 | ${CMAKE_CURRENT_SOURCE_DIR}/rainflow.c 7 | ${CMAKE_CURRENT_SOURCE_DIR}/rainflow.h 8 | ${CMAKE_CURRENT_SOURCE_DIR}/config.h 9 | ) 10 | target_link_libraries(rfc_core ${LIBM_LIBRARY}) 11 | target_include_directories(rfc_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) 12 | target_compile_definitions(rfc_core PRIVATE -DRFC_HAVE_CONFIG_H) 13 | -------------------------------------------------------------------------------- /coan-args-minimal.in: -------------------------------------------------------------------------------- 1 | -DCOAN_INVOKED=1 2 | -DRFC_MINIMAL=1 3 | -DRFC_TP_SUPPORT=0 4 | -DRFC_HCM_SUPPORT=0 5 | -DRFC_USE_DELEGATES=0 6 | -DRFC_GLOBAL_EXTREMA=0 7 | -DRFC_DAMAGE_FAST=0 8 | -DRFC_DH_SUPPORT=0 9 | -DRFC_AT_SUPPORT=0 10 | -DRFC_DEBUG_FLAGS=0 11 | -DRFC_USE_HYSTERESIS_FILTER=1 12 | -DMATLAB_MEX_FILE=0 13 | -U_DEBUG 14 | -xd 15 | src/lib/rainflow.c 16 | src/lib/rainflow.h 17 | src/lib/config.h 18 | src/lib/config.h.in 19 | test/rfc_test.c 20 | test/long_series.c 21 | test/rfc_wrapper_simple.cpp 22 | -------------------------------------------------------------------------------- /src/lib/minimal/readme.md: -------------------------------------------------------------------------------- 1 | # README.md 2 | 3 | This folder contains extracts of the core source codes for the Rainflow 4 | counting (`rainflow.c` and `rainflow.h`). The extracts were generated 5 | using the tool `unifdef.exe` ([https://dotat.at/prog/unifdef/]) from the 6 | original files. 7 | `unifdef.exe` reduces the code to its essential components without the additional 8 | features, highlighting the Rainflow counting algorithm within the source code. 9 | This makes the code easier to understand. 10 | -------------------------------------------------------------------------------- /src/python/run_tests.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import unittest 3 | from io import StringIO 4 | from pprint import pprint 5 | 6 | from .tests import test_rfcnt 7 | 8 | 9 | def run() -> unittest.result.TestResult: 10 | stream = StringIO() 11 | runner = unittest.TextTestRunner(stream=stream) 12 | test_result = runner.run(unittest.makeSuite(test_rfcnt.TestRainflowCounting)) 13 | print('Tests run ', test_result.testsRun) 14 | print('Errors ', test_result.errors) 15 | pprint(test_result.failures) 16 | stream.seek(0) 17 | print('Test output\n', stream.read()) 18 | return test_result 19 | 20 | 21 | if __name__ == '__main__': 22 | result = run() 23 | if result.wasSuccessful(): 24 | sys.exit(0) 25 | else: 26 | sys.exit(1) 27 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Unit test 2 | 3 | # Regular build 4 | # ============= 5 | #[[ 6 | cmake -S. -Bbuild -G "Visual Studio 16 2019" -DRFC_EXPORT_PY=0 -DRFC_EXPORT_MEX=0 -DRFC_UNIT_TEST=1 7 | cd build 8 | cmake --build . --target rfc_test --config Release 9 | ctest -C Release -V 10 | #]] 11 | 12 | set(rfc_core_sources $) 13 | set(rfc_core_include_dir $) 14 | 15 | add_executable(rfc_test rfc_test.c rfc_wrapper_simple.cpp rfc_wrapper_advanced.cpp) 16 | target_link_libraries(rfc_test PRIVATE rfc_core greatest) 17 | target_compile_definitions(rfc_test PRIVATE -DRFC_HAVE_CONFIG_H) 18 | 19 | target_include_directories(rfc_test PRIVATE greatest) 20 | 21 | if (MSVC) 22 | set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT rfc_test) 23 | endif () -------------------------------------------------------------------------------- /src/python/lib/config.h: -------------------------------------------------------------------------------- 1 | #define ON 1 2 | #define OFF 0 3 | 4 | #ifndef RFC_VERSION_MAJOR 5 | #define RFC_VERSION_MAJOR "0" 6 | #define RFC_VERSION_MINOR "5" 7 | #define RFC_VERSION_PATCH "0" 8 | #define RFC_USE_INTEGRAL_COUNTS OFF 9 | #define RFC_USE_HYSTERESIS_FILTER ON 10 | #define RFC_MINIMAL OFF 11 | #define RFC_TP_SUPPORT ON 12 | #define RFC_HCM_SUPPORT ON 13 | #define RFC_ASTM_SUPPORT ON 14 | #define RFC_USE_DELEGATES ON 15 | #define RFC_GLOBAL_EXTREMA ON 16 | #define RFC_DAMAGE_FAST ON 17 | #define RFC_DH_SUPPORT ON 18 | #define RFC_AT_SUPPORT ON 19 | #define RFC_AR_SUPPORT ON 20 | #define RFC_DEBUG_FLAGS OFF 21 | #define RFC_EXPORT_MEX ON 22 | #endif /*RFC_VERSION_MAJOR*/ 23 | -------------------------------------------------------------------------------- /test/rfc_wrapper_simple.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* Using Rainflow C-Library in a C++ context */ 3 | 4 | #include "rainflow.h" 5 | #include "greatest.h" 6 | 7 | 8 | /* Made extern "C", so that C-Code (rfc_test.c) can access this function (in a C++ module) 9 | C++ functions are usually "name mangeled", that is averted here! */ 10 | TEST wrapper_test_simple( void ) 11 | { 12 | namespace rf = rainflow_C; /* Using a namespace alias to make it short */ 13 | 14 | rf::rfc_ctx_s ctx = { sizeof(ctx) }; 15 | 16 | /* Just init and deinit here */ 17 | ASSERT( 18 | rf::RFC_init( &ctx, /*class_count*/ 10, 19 | /*class_width*/ 1, 20 | /*class_offset*/ 0, 21 | /*hysteresis*/ 1, 22 | /*flags*/ rf::RFC_FLAGS_DEFAULT ) 23 | && 24 | rf::RFC_deinit( &ctx ) ); 25 | 26 | PASS(); 27 | } 28 | 29 | 30 | /* Test suite for rfc_test.c */ 31 | extern "C" 32 | SUITE( RFC_WRAPPER_SUITE_SIMPLE ) 33 | { 34 | RUN_TEST( wrapper_test_simple ); 35 | } 36 | -------------------------------------------------------------------------------- /tools/where_to_get_coan.txt: -------------------------------------------------------------------------------- 1 | 2 | Get "coan" from http://coan2.sourceforge.net 3 | 4 | From the coan website: 5 | ====================== 6 | Coan is a software engineering tool for analysing preprocessor-based 7 | configurations of C or C++ source code. Its principal use is to simplify a 8 | body of source code by eliminating any parts that are redundant with respect 9 | to a specified configuration. Dead code removal is an application of this sort. 10 | 11 | Coan is most useful to developers of constantly evolving products with large 12 | code bases, where preprocessor definitions and #if-directives are used 13 | differentiate progressive releases or parallel variants of the product. 14 | In these settings the upkeep of the product's configuration tree can become 15 | difficult and the incidence of configuration-related defects can become costly. 16 | (The side-panels on these pages illustrate how preprocessor-encrusted 17 | production code can get.) Coan can largely automate the maintenance of 18 | preprocessor-based configurations and the investigation of 19 | configuration-related questions. 20 | -------------------------------------------------------------------------------- /src/python/rfcnt.pyi: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | 3 | from . import ArrayLike, LCMethod, ResidualMethod, SDMethod, RPDamageCalcMethod 4 | 5 | 6 | def rfc( 7 | data: ArrayLike, 8 | class_width: float, 9 | *, 10 | class_count: Optional[int] = 100, 11 | class_offset: Optional[float] = None, 12 | hysteresis: Optional[float] = None, 13 | residual_method: Optional[Union[int, ResidualMethod]] = ResidualMethod.REPEATED, 14 | spread_damage: Optional[Union[int, SDMethod]] = SDMethod.TRANSIENT_23c, 15 | lc_method: Optional[Union[int, LCMethod]] = LCMethod.SLOPES_UP, 16 | use_HCM: Optional[Union[int, bool]] = 0, 17 | use_ASTM: Optional[Union[int, bool]] = 0, 18 | enforce_margin: Optional[Union[int, bool]] = 0, 19 | auto_resize: Optional[Union[int, bool]] = 0, 20 | wl: Optional[dict] = None 21 | ) -> tuple: ... 22 | 23 | 24 | def damage_from_rp( 25 | Sa: ArrayLike, 26 | counts: ArrayLike, 27 | *, 28 | wl: Optional[dict] = None, 29 | method: Optional[RPDamageCalcMethod] = 0 30 | ) -> float: ... 31 | -------------------------------------------------------------------------------- /src/lib/config.h.in: -------------------------------------------------------------------------------- 1 | #define ON 1 2 | #define OFF 0 3 | 4 | #ifndef RFC_VERSION_MAJOR 5 | #define RFC_VERSION_MAJOR "${RFC_VERSION_MAJOR}" 6 | #define RFC_VERSION_MINOR "${RFC_VERSION_MINOR}" 7 | #define RFC_VERSION_PATCH "${RFC_VERSION_PATCH}" 8 | #define RFC_USE_INTEGRAL_COUNTS ${RFC_USE_INTEGRAL_COUNTS} 9 | #define RFC_USE_HYSTERESIS_FILTER ${RFC_USE_HYSTERESIS_FILTER} 10 | #define RFC_MINIMAL ${RFC_MINIMAL} 11 | #define RFC_TP_SUPPORT ${RFC_TP_SUPPORT} 12 | #define RFC_HCM_SUPPORT ${RFC_HCM_SUPPORT} 13 | #define RFC_ASTM_SUPPORT ${RFC_ASTM_SUPPORT} 14 | #define RFC_USE_DELEGATES ${RFC_USE_DELEGATES} 15 | #define RFC_GLOBAL_EXTREMA ${RFC_GLOBAL_EXTREMA} 16 | #define RFC_DAMAGE_FAST ${RFC_DAMAGE_FAST} 17 | #define RFC_DH_SUPPORT ${RFC_DH_SUPPORT} 18 | #define RFC_AT_SUPPORT ${RFC_AT_SUPPORT} 19 | #define RFC_AR_SUPPORT ${RFC_AR_SUPPORT} 20 | #define RFC_DEBUG_FLAGS ${RFC_DEBUG_FLAGS} 21 | #define RFC_EXPORT_MEX ${RFC_EXPORT_MEX} 22 | #endif /*RFC_VERSION_MAJOR*/ 23 | -------------------------------------------------------------------------------- /src/python/utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from . import ArrayLike 3 | 4 | 5 | def rpplot_prepare(sa: ArrayLike, counts: ArrayLike): 6 | """ 7 | Prepare data for range pair plot by sorting the input arrays. 8 | 9 | Parameters 10 | ---------- 11 | sa : ArrayLike 12 | The stress amplitude array. 13 | counts : ArrayLike 14 | The counts array corresponding to the stress amplitudes. 15 | 16 | Returns 17 | ------- 18 | dict 19 | A dictionary with keys "sa" and "counts", containing the sorted and formatted data, 20 | and "sa_max" with the maximum value that "sa" contains. 21 | """ 22 | sa = np.asarray(sa).flatten() 23 | counts = np.asarray(counts).flatten() 24 | 25 | # Filter out elements with non-zero counts 26 | mask = counts > 0 27 | sa, counts = sa[mask], counts[mask] 28 | sa_max = np.nan 29 | if np.any(mask): 30 | # Sort descending by stress amplitude 31 | i = np.argsort(-sa) 32 | sa, counts = sa[i], counts[i] 33 | 34 | # First stress amplitude is the maximum 35 | sa_max = sa[0] 36 | 37 | return {"sa": sa, "counts": counts, "sa_max": sa_max} 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2023, Andreas Martin 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /src/python/LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2025, Andreas Martin 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /.github/workflows/run_test.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | build: 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | os: [windows-latest, macos-latest, ubuntu-latest] 19 | python-version: ["3.7", "3.8", "3.9", "3.10"] 20 | exclude: 21 | - python-version: "3.7" # Python < v3.8 does not support Apple Silicon ARM64. 22 | os: macos-latest 23 | - python-version: "3.7" # Skip 3.7 on Ubuntu as requested 24 | os: ubuntu-latest 25 | include: # Run those macos legacy versions on Intel CPUs. 26 | - python-version: "3.7" 27 | os: macos-13 28 | steps: 29 | - uses: actions/checkout@v3 30 | - name: Set up Python ${{ matrix.python-version }} 31 | uses: actions/setup-python@v3 32 | with: 33 | python-version: ${{ matrix.python-version }} 34 | - name: Install dependencies 35 | run: | 36 | cd src/python 37 | pip install wheel 38 | pip install -r requirements.txt 39 | pip install . --no-build-isolation --no-deps 40 | - name: Run Python tests 41 | run: | 42 | pip install pandas 43 | python -m rfcnt.run_tests 44 | - name: Set up CMake 45 | uses: lukka/get-cmake@latest 46 | - name: Pull & update submodules recursively 47 | run: | 48 | git submodule update --init --recursive 49 | git submodule update --recursive --remote 50 | - name: Build an test rfc core 51 | run: | 52 | cmake -S . -B build -DRFC_EXPORT_PY=0 -DRFC_EXPORT_MEX=0 -DRFC_UNIT_TEST=1 53 | cd build 54 | cmake --build . --target rfc_test --config Release 55 | ctest -C Release -V 56 | -------------------------------------------------------------------------------- /src/python/README.md: -------------------------------------------------------------------------------- 1 | ## **rfcnt** a rainflow counting algorithm Python package 2 | 3 | ### "Rainflow Counting" consists of four main steps: 4 | 5 | 1. Hysteresis Filtering 6 | 2. Peak-Valley Filtering 7 | 3. Discretization 8 | 4. Four Point Counting Method: 9 | 10 | * D 11 | / \ Closed, if min(B,C) >= min(A,D) && max(B,C) <= max(A,D) 12 | B *<--/ Slope B-C is counted and removed from residue 13 | / \ / 14 | / * C 15 | \ / 16 | * A 17 | 18 | These steps are fully documented in standards such as 19 | ASTM E1049 "Standard Practices for Cycle Counting in Fatigue Analysis" [1] 20 | This implementation uses the 4-point algorithm mentioned in [3,4] and the 3-point HCM method proposed in [2] as well as the ASTM E 1049 (2011) standard in [1]. 21 | To take the residue into account, you may implement a custom method or use some 22 | predefined functions. 23 | 24 | ### Install 25 | pip install {packagename}.tar.gz --no-build-isolation --no-deps 26 | where _{packagename}_ is the current package release, for example: 27 | 28 | pip install rfcnt-0.4.7.tar.gz --no-build-isolation --no-deps 29 | 30 | ### Test 31 | _rfcnt_ packages include some unit tests, which can be run: 32 | 33 | python -m rfcnt.run_tests 34 | 35 | ### Examples 36 | For a quick introduction you can run and inspect a small example: 37 | 38 | python -m rfcnt.run_examples 39 | 40 | ![](jupyter_screenshot.png) 41 | 42 | --- 43 | ### References: 44 | [1] "Standard Practices for Cycle Counting in Fatigue Analysis." 45 | ASTM Standard E 1049, 1985 (2011). 46 | West Conshohocken, PA: ASTM International, 2011. 47 | [2] "Rainflow - HCM / Ein Hysteresisschleifen-Zaehlalgorithmus auf werkstoffmechanischer Grundlage" 48 | U.H. Clormann, T. Seeger 49 | 1985 TU Darmstadt, Fachgebiet Werkstoffmechanik 50 | [3] "Zaehlverfahren zur Bildung von Kollektiven und Matrizen aus Zeitfunktionen" 51 | FVA-Richtlinie, 2010. 52 | [https://fva-net.de/fileadmin/content/Richtlinien/FVA-Richtlinie_Zaehlverfahren_2010.pdf] 53 | [4] Siemens Product Lifecycle Management Software Inc., 2018. 54 | [https://community.plm.automation.siemens.com/t5/Testing-Knowledge-Base/Rainflow-Counting/ta-p/383093] 55 | -------------------------------------------------------------------------------- /src/python/tests/examples.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | from .. import ResidualMethod, SDMethod, rfc, utils, ArrayLike 4 | 5 | 6 | def _get_script_path() -> str: 7 | """ 8 | Get the directory path of the current script. 9 | 10 | Returns 11 | ------- 12 | str 13 | The directory path where the current script is located. 14 | """ 15 | return os.path.dirname(os.path.abspath(__file__)) 16 | 17 | 18 | def example_1(): 19 | """ 20 | Example function to demonstrate rainflow cycle counting and data visualization. 21 | 22 | This function reads a time series data from a CSV file, performs rainflow cycle counting, 23 | and generates plots for the rainflow matrix, range pairs, damage history, and the original 24 | time series data. 25 | 26 | Raises 27 | ------ 28 | ImportError 29 | If the required modules 'pandas' and 'matplotlib' are not installed. 30 | """ 31 | try: 32 | import matplotlib.pyplot as plt 33 | import matplotlib.ticker as ticker 34 | import pandas as pd 35 | from matplotlib.gridspec import GridSpec 36 | except ImportError as err: 37 | print("This example requires modules 'pandas' and 'matplotlib'!") 38 | raise err 39 | 40 | # Load data from CSV 41 | data = pd.read_csv(os.path.join(_get_script_path(), "long_series.csv"), header=None) 42 | data = data.to_numpy().squeeze() 43 | 44 | # Set parameters for rainflow counting 45 | class_count = 50 46 | class_range = np.ptp(data) 47 | class_width = class_range / (class_count - 1) 48 | class_offset = data.min() - class_width / 2 49 | 50 | # Perform rainflow counting 51 | res = rfc( 52 | data, 53 | class_count=class_count, 54 | class_offset=class_offset, 55 | class_width=class_width, 56 | hysteresis=class_width, 57 | use_HCM=False, 58 | use_ASTM=False, 59 | spread_damage=SDMethod.TRANSIENT_23c, 60 | residual_method=ResidualMethod.REPEATED, 61 | wl={"sd": 1e3, "nd": 1e7, "k": 5}, 62 | ) 63 | 64 | # Create a figure and gridspec layout 65 | fig = plt.figure(figsize=(14, 10)) 66 | gs = GridSpec(nrows=3, ncols=2, width_ratios=[1, 2]) 67 | 68 | # Rainflow matrix plot 69 | ax1 = fig.add_subplot(gs[0, 0]) 70 | im = ax1.imshow(res["rfm"], cmap="YlOrRd", aspect=0.7) 71 | cb = plt.colorbar(im, ax=ax1, label="Counts") 72 | cb.ax.tick_params(labelsize="x-small") 73 | ax1.yaxis.set_major_locator(ticker.MultipleLocator(2)) 74 | ax1.xaxis.set_major_locator(ticker.MultipleLocator(2)) 75 | ax1.xaxis.set_ticks_position("top") 76 | ax1.xaxis.set_label_position("top") 77 | ax1.tick_params(axis="x", labelsize="x-small", labelrotation=90) 78 | ax1.tick_params(axis="y", labelsize="x-small") 79 | ax1.grid(which="both") 80 | ax1.set_xlabel("Class # (to)") 81 | ax1.set_ylabel("Class # (from)") 82 | 83 | # Range pairs plot 84 | r = utils.rpplot_prepare(sa=res["rp"][:, 0] / 2, counts=res["rp"][:, 1]) 85 | ax2 = fig.add_subplot(gs[0, 1]) 86 | ax2.plot(r["counts"].cumsum(), r["sa"], drawstyle="steps-post") 87 | ax2.set_xscale("log") 88 | ax2.set_ylim(bottom=0, top=2500) 89 | ax2.set_xlim(left=0.9) 90 | ax2.grid(which="both") 91 | ax2.set_xlabel("N (log) [1]") 92 | ax2.set_ylabel("$S_a$") 93 | 94 | # Damage history plot 95 | ax3 = fig.add_subplot(gs[1, :]) 96 | ax3.plot(np.arange(len(res["dh"])), res["dh"].cumsum()) 97 | ax3.grid(which="both") 98 | ax3.set_xlabel("Sample #") 99 | ax3.set_ylabel("Damage (cumulative)") 100 | 101 | # Time series plot 102 | ax4 = fig.add_subplot(gs[2, :]) 103 | ax4.plot(np.arange(len(data)), data) 104 | ax4.grid(which="both") 105 | ax4.set_xlabel("Sample #") 106 | ax4.set_ylabel("Value") 107 | 108 | fig.tight_layout() 109 | plt.show() 110 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CMakeLists for project "rainflow" 2 | # 2025, Andreas Martin 3 | 4 | # See `README.md` for build and installation hints. 5 | 6 | cmake_minimum_required(VERSION 3.16) 7 | cmake_policy(SET CMP0048 NEW) # The project() command manages VERSION variables. 8 | if (POLICY CMP0076) 9 | cmake_policy(SET CMP0076 NEW) # The target_sources() command converts relative paths to absolute. 10 | endif () 11 | cmake_policy(SET CMP0091 NEW) # MSVC runtime library flags are selected by an abstraction. 12 | 13 | # Project name and version 14 | project(rainflow VERSION 0.5.0 LANGUAGES NONE) 15 | 16 | # C++11 17 | enable_language(C) 18 | enable_language(CXX) 19 | set(CMAKE_CXX_STANDARD 11) 20 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 21 | set(CMAKE_CXX_EXTENSIONS ON) 22 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 23 | set(CMAKE_VERBOSE_MAKEFILE OFF) 24 | 25 | if ("${CMAKE_BUILD_TYPE}" STREQUAL "") 26 | set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type" FORCE) 27 | endif () 28 | 29 | if (CMAKE_BUILD_TYPE MATCHES Debug) 30 | message(STATUS "Rainflow: Debug build") 31 | add_definitions(-DDEBUG -D_DEBUG) 32 | endif () 33 | 34 | # Compiler dependencies 35 | if (MSVC) 36 | # Turn off misguided "secure CRT" warnings in MSVC. 37 | # Microsoft wants people to use the MS-specific _s 38 | # versions of certain C functions but this is difficult to do 39 | # in platform-independent code. 40 | add_definitions(-D_CRT_SECURE_NO_WARNINGS) 41 | add_compile_options(-Od) # No optimization 42 | elseif (GNUC) 43 | add_compile_options(-O0) # No optimization 44 | endif () 45 | 46 | # Math library 47 | find_library(LIBM_LIBRARY NAMES m) 48 | if (NOT LIBM_LIBRARY) 49 | set(LIBM_LIBRARY "") 50 | endif () 51 | 52 | # Options valid, if project compiled as standalone only 53 | if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) 54 | message(STATUS "Build ${PROJECT_NAME} as main project") 55 | option(RFC_MINIMAL "Build module with as few features as possible" OFF) 56 | option(RFC_USE_INTEGRAL_COUNTS "Use integral (non-floating) data type for counts" OFF) 57 | option(RFC_USE_HYSTERESIS_FILTER "Use hysteresis filtering" ON) 58 | option(RFC_USE_DELEGATES "Use delegates (functors)" ON) 59 | option(RFC_GLOBAL_EXTREMA "Always calculate global extrema" ON) 60 | option(RFC_HCM_SUPPORT "Support HCM (Clormann/Seeger) algorithm" ON) 61 | option(RFC_ASTM_SUPPORT "Support ASTM E 1049-85 algorithm" ON) 62 | option(RFC_TP_SUPPORT "Support turning points" ON) 63 | option(RFC_DH_SUPPORT "Support \"spread damage\" over turning points and damage history" ON) 64 | option(RFC_AT_SUPPORT "Support amplitude transformation regarding mean load influence on fatigue strength" ON) 65 | option(RFC_AR_SUPPORT "Support automatic growth of counting buffers" ON) 66 | option(RFC_DAMAGE_FAST "Enables fast damage calculation (per look-up table)" ON) 67 | option(RFC_DEBUG_FLAGS "Enables flags for detailed examination" OFF) 68 | option(RFC_EXPORT_MEX "Export a function wrapper for MATLAB(R)" ON) 69 | option(RFC_EXPORT_PY "Export a function wrapper for Python)" ON) 70 | option(RFC_UNIT_TEST "Generate rainflow testing program for unit test" ON) 71 | set(RFC_VALUE_TYPE double CACHE STRING "Value type of input data to be processed") 72 | set(RFC_PYTHON_VERSION "3.9" CACHE STRING "Expected Python version") 73 | set(RFC_NUMPY_VERSION "" CACHE STRING "NumPy version to link to") 74 | else () 75 | message(STATUS "Build ${PROJECT_NAME} as subsequent project") 76 | set(RFC_EXPORT_MEX OFF) 77 | set(RFC_EXPORT_PY OFF) 78 | endif () 79 | 80 | set(RFC_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) 81 | set(RFC_VERSION_MINOR ${PROJECT_VERSION_MINOR}) 82 | set(RFC_VERSION_PATCH ${PROJECT_VERSION_PATCH}) 83 | 84 | # Save options in configuration file 85 | configure_file(${CMAKE_CURRENT_LIST_DIR}/src/lib/config.h.in ${CMAKE_CURRENT_LIST_DIR}/src/lib/config.h) 86 | 87 | # Targets 88 | add_subdirectory(src) 89 | 90 | # Unit tests 91 | if (${RFC_UNIT_TEST}) 92 | enable_testing() 93 | include(CTest) 94 | add_subdirectory(test) # EXCLUDE_FROM_ALL) 95 | add_test(NAME rfc_unit_test COMMAND rfc_test) 96 | endif () 97 | -------------------------------------------------------------------------------- /content.txt: -------------------------------------------------------------------------------- 1 | RAINFLOW 2 | | CMakeLists.txt Makefile (CMake) 3 | | coan-args-minimal.in Input arguments file for coan invocation 4 | | coan_invoke-minimal.bat Invoke coan (cmd shell) 5 | | coan_invoke-minimal.sh Invoke coan (linux shell) 6 | | content.txt This file 7 | | LICENSE License information 8 | | README.MD Readme 9 | | 10 | +---src 11 | | | 12 | | | CMakeLists.txt Makefile (CMake) 13 | | | 14 | | +---3rd_party 15 | | | +---greatest A testing system for C, contained in 1 header file. 16 | | | \---unifdef A tool remove preprocessor conditionals from code. 17 | | | 18 | | +---lib 19 | | | | CMakeLists.txt Makefile (CMake) 20 | | | | config.h Configuration file (Rainflow) 21 | | | | config.h.in Configuration file (Input file for CMake) 22 | | | | rainflow.h Header file for rainflow.c 23 | | | | rainflow.hpp Header file for C++ interface 24 | | | | rainflow.c Rainflow main routines 25 | | | | 26 | | | \---minimal 27 | | | | create_minimal_sources.bat Batch generating `rainflow.c`and `rainflow.h` 28 | | | | readme.md Readme 29 | | | | rainflow.c Generated source code 30 | | | | rainflow.h Generated source code 31 | | | | 32 | | | \---cfg 33 | | | \ unifdef_defile.h 34 | | | 35 | | +---matlab 36 | | | | CMakeLists.txt Makefile (CMake) 37 | | | | rfc.c MATLAB(R) wrapper module `rfc` 38 | | | | make.m Build rfc.m 39 | | | \ validate.m Examples an checks 40 | | | 41 | | +---python 42 | | | __init.py__ Init 43 | | | CMakeLists.txt Makefile (CMake) 44 | | | LICENSE License information 45 | | | MANIFEST.in Manifest file (input file) 46 | | | pyproject.toml TOML file for build requirements 47 | | | README.md Readme 48 | | | requirements.txt Requirements of the rfcnt module 49 | | | rfcnt.pyi rfcnt interface file 50 | | | run_examples.py Python script executes examples from .tests.examples 51 | | | run_tests.py Python script for unit testing 52 | | | setup.py Setup (build, install, deploy) 53 | | | utils.py Utility module 54 | | | version.py Version file 55 | | | 56 | | +---_ext 57 | | | \ Prebuild extensions for various numpy versions 58 | | | 59 | | +---cmake 60 | | | | numpy_get_include.py Python script returning NumPy include directories 61 | | | \ rfcnt_target_functions.cmake Some helper functions for create build targets 62 | | | 63 | | +---lib 64 | | | | rainflow.h (Generated) copies of /src/lib 65 | | | | rainflow.hpp ... 66 | | | | config.h ... 67 | | | \ rainflow.c ... 68 | | | 69 | | +---notebooks 70 | | | | example_damage_history.ipynb Example showing damage growth over time 71 | | | | example_damage_history.png Output from example_damage_history.ipynb 72 | | | \ jupyter_screenshot.png Output from .tests.examples.example_1() 73 | | | 74 | | +---src 75 | | | | numpy_arrayobject.h Include file generated by `numpy_get_include.py` 76 | | | \ rfcnt.cpp Python wrapper module `rfcnt` 77 | | | 78 | | \---tests 79 | | | __init.py__ Init 80 | | | test_rfcnt.py Unit Test 81 | | | examples.py Examples 82 | | | long_series.csv Time series for unit test and examples 83 | | \ test_rfcnt.py Demonstrator and test (Python) 84 | | 85 | +---test 86 | | | CMakeLists.txt Makefile (CMake) 87 | | | long_series.h Header (defining long series data length) 88 | | | long_series.c Time series for tests 89 | | | long_series.csv Time series for tests as CSV file 90 | | | rfc_test.c Main program (windows console application) for comprehensive unit test, uses "greatest" 91 | | | rfc_wrapper_advanced.cpp Example using rainflow in CPP context applying an advanced wrapper 92 | | \ rfc_wrapper_simple.cpp Example using rainflow in CPP context applying a simple wrapper 93 | | 94 | +---tools 95 | \ where_to_get_coan.txt Link 96 | -------------------------------------------------------------------------------- /src/matlab/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Function wrapper for MATLAB(R) 2 | 3 | # Regular build: 4 | # =============== 5 | #[[ 6 | cmake -S. -Bbuild -DRFC_EXPORT_PY=0 -DRFC_EXPORT_MEX=1 7 | cmake --build build/src/matlab --target rfc_mex --config Release 8 | #]] 9 | 10 | # Build using special MATLAB(R) installation: 11 | # =========================================== 12 | #[[ 13 | cmake -S. -Bbuild -DRFC_EXPORT_PY=0 -DRFC_EXPORT_MEX=1 -DMatlab_ROOT_DIR=/appl/matlab/2017b 14 | cmake --build build/src/matlab --target rfc_mex --config Release 15 | #]] 16 | 17 | # Build packages 18 | # ============== 19 | #[[ 20 | cd build 21 | cpack --config CPackConfig.cmake 22 | cpack --config CPackSourceConfig.cmake 23 | #]] 24 | 25 | 26 | cmake_minimum_required(VERSION 3.16) 27 | cmake_policy(SET CMP0048 NEW) # The project() command manages VERSION variables. 28 | if (POLICY CMP0076) 29 | cmake_policy(SET CMP0076 NEW) # The target_sources() command converts relative paths to absolute. 30 | endif () 31 | cmake_policy(SET CMP0091 NEW) # MSVC runtime library flags are selected by an abstraction. 32 | 33 | # Project name 34 | project(rfc_mex LANGUAGES C CXX) 35 | 36 | # find MATLAB 37 | # (https://de.mathworks.com/support/requirements/previous-releases.html) 38 | set(MATLAB_FIND_DEBUG 1) 39 | set(MATLAB_ADDITIONAL_VERSIONS 40 | "R2019b=9.7" 41 | "R2020a=9.8" 42 | "R2020b=9.9" 43 | "R2021a=9.10" 44 | "R2021b=9.11" 45 | "R2022a=9.12" 46 | "R2022b=9.13" 47 | "R2023a=9.14" 48 | "R2023b=9.15" 49 | "R2024a=9.16" 50 | "R2024b=9.17" 51 | ) 52 | set(VERSION_MATLAB "" CACHE STRING "The desired Matlab installation (`9.7` for example), or autodetect if empty.") 53 | if (DEFINED ENV{Matlab_ROOT_DIR}) 54 | set(Matlab_ROOT_DIR $ENV{Matlab_ROOT_DIR}) 55 | endif () 56 | if ("${VERSION_MATLAB}" STREQUAL "") 57 | find_package(Matlab COMPONENTS MAIN_PROGRAM) 58 | else () 59 | find_package(Matlab ${VERSION_MATLAB} EXACT COMPONENTS MAIN_PROGRAM) 60 | endif () 61 | 62 | if (NOT MATLAB_FOUND) 63 | message(WARNING "MATLAB not found, unable to build MEX file.") 64 | else () 65 | message(STATUS "MATLAB found, able to build MEX file.") 66 | if (UNIX) 67 | matlab_get_version_from_matlab_run(${Matlab_MAIN_PROGRAM} matlab_version) 68 | else () 69 | set(matlab_version ${VERSION_MATLAB}) 70 | endif () 71 | 72 | # MEX function (MATLAB) 73 | if (RFC_EXPORT_MEX) 74 | matlab_add_mex( 75 | NAME rfc_mex 76 | SRC ${CMAKE_CURRENT_SOURCE_DIR}/rfc.c 77 | LINK_TO ${LIBM_LIBRARY} 78 | rfc_core 79 | OUTPUT_NAME rfc 80 | ) 81 | else () 82 | matlab_add_mex( 83 | NAME rfc_mex 84 | SRC ${CMAKE_CURRENT_SOURCE_DIR}/rfc.c 85 | ${rfc_core_SOURCE_DIR}/rainflow.c 86 | LINK_TO ${LIBM_LIBRARY} 87 | OUTPUT_NAME rfc 88 | ) 89 | target_include_directories(rfc_mex PRIVATE 90 | ${rfc_core_SOURCE_DIR} 91 | ) 92 | target_compile_definitions(rfc_mex PRIVATE 93 | RFC_VALUE_TYPE=double 94 | # RFC_USE_INTEGRAL_COUNTS 95 | RFC_USE_DELEGATES 96 | RFC_USE_HYSTERESIS_FILTER 97 | RFC_GLOBAL_EXTREMA 98 | RFC_HCM_SUPPORT 99 | RFC_ASTM_SUPPORT 100 | RFC_TP_SUPPORT 101 | RFC_DH_SUPPORT 102 | RFC_AT_SUPPORT 103 | RFC_AR_SUPPORT 104 | RFC_DAMAGE_FAST 105 | # RFC_DEBUG_FLAGS 106 | RFC_EXPORT_MEX 107 | # RFC_UNIT_TEST 108 | ) 109 | endif () 110 | 111 | # install to source root by default 112 | install(TARGETS rfc_mex LIBRARY DESTINATION ${CMAKE_SOURCE_DIR}) 113 | endif () 114 | 115 | 116 | # CPack 117 | include(InstallRequiredSystemLibraries) 118 | 119 | set(CPACK_GENERATOR TGZ ZIP) 120 | set(CPACK_SOURCE_GENERATOR TGZ ZIP) 121 | set(CPACK_PACKAGE_DIRECTORY ${CMAKE_BINARY_DIR}/package) 122 | #set( CPACK_PACKAGE_NAME "rainflow") 123 | set(CPACK_PACKAGE_VERSION_MAJOR ${RFC_VERSION_MAJOR}) 124 | set(CPACK_PACKAGE_VERSION_MINOR ${RFC_VERSION_MINOR}) 125 | set(CPACK_PACKAGE_VERSION_PATCH "") 126 | set(CPACK_PACKAGE_VERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}) 127 | set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../../README.md") 128 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Fast rainflow counting written in C (C99)") 129 | set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/../../LICENSE") 130 | set(CPACK_SOURCE_IGNORE_FILES 131 | # Files specific to version control. 132 | "/\\\\.git/" 133 | "/\\\\.gitattributes$" 134 | "/\\\\.github/" 135 | "/\\\\.gitignore$" 136 | "/\\\\.hooks-config$" 137 | 138 | # Package build. 139 | "/build" 140 | "/.git*" 141 | 142 | # Temporary files. 143 | "\\\\.#" 144 | "/#" 145 | "~$" 146 | ) 147 | set(CPACK_STRIP_FILES TRUE) 148 | set(CPACK_SOURCE_STRIP_FILES TRUE) 149 | 150 | include(CPack) 151 | -------------------------------------------------------------------------------- /src/python/setup.py: -------------------------------------------------------------------------------- 1 | # Source distribution (./dist) 2 | # python setup.py build sdist 3 | # 4 | # Binary distribution (./build) 5 | # python -m build -nwx 6 | # python setup.py bdist_wheel --plat-name=win-amd64 7 | # python setup.py bdist --formats=wininst 8 | # python setup.py bdist_wininst --title= --bitmap= 9 | # pip install --force-reinstall --no-deps --no-build-isolation package.tar 10 | # python setup.py build_clib -f 11 | # python setup.py build_ext -fi # Build pyd modules for folder `_ext` 12 | # python -mbuild -n 13 | 14 | # If build with mingw32 compiler (TDM-GCC64): 15 | # Comment out the get_msvcr() occurrences in PATHONPATH/Lib/distutils/cygwinccompiler.py 16 | # Create a file distutils.cfg in PYTHON_ROOT/Lib/distutils: 17 | # [build] 18 | # compiler=mingw32 19 | 20 | # PyPi 21 | # python3 -m pip install --upgrade build twine 22 | # python3 -m build 23 | # python3 -m twine upload --repository testpypi dist/* 24 | # pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple rfcnt==0.2.0 25 | # python3 -m twine upload --repository pypi dist/* 26 | 27 | 28 | # Install (Jupyter Notebook) 29 | # !export CFLAGS='-std=c++11' && pip install rfcnt 30 | 31 | import re 32 | from os import path 33 | from setuptools import setup, Extension 34 | 35 | 36 | try: 37 | # Check if numpy is installed 38 | from numpy import __version__ as np_version 39 | from numpy import get_include as np_get_include 40 | except ImportError: 41 | np_version = "NUMPY_NOTFOUND" 42 | def np_get_include(): 43 | return "NUMPY_NOTFOUND" 44 | 45 | 46 | def main(): 47 | this_directory = path.abspath(path.dirname(__file__)) 48 | long_description = "" 49 | with open(path.join(this_directory, 'README.md'), encoding='utf-8') as f: 50 | long_description = f.read() 51 | with open(path.join(this_directory, 'lib', 'config.h')) as f: 52 | RFC_VERSION_MAJOR = RFC_VERSION_MINOR = None 53 | for line in f: 54 | match = re.match(r".*#define\s+RFC_VERSION_(MAJOR|MINOR)\s+\"(\d+)\".*", line) 55 | if match: 56 | if match.group(1) == "MAJOR": 57 | RFC_VERSION_MAJOR = match.group(1) 58 | else: 59 | RFC_VERSION_MINOR = match.group(2) 60 | assert RFC_VERSION_MAJOR is not None and RFC_VERSION_MINOR is not None, \ 61 | "Can't locate version signature." 62 | ver_ns = {} 63 | with open(path.join(this_directory, "version.py")) as f: 64 | exec(f.read(), ver_ns) 65 | _version = ver_ns["_version"] 66 | __version__ = ver_ns["__version__"] 67 | __author__ = ver_ns["__author__"] 68 | del ver_ns, f 69 | 70 | define_macros = [ 71 | ('NPY_NO_DEPRECATED_API', 'NPY_1_7_API_VERSION'), 72 | ('RFC_HAVE_CONFIG_H', '0'), 73 | ('RFC_VERSION_MAJOR', RFC_VERSION_MAJOR), 74 | ('RFC_VERSION_MINOR', RFC_VERSION_MINOR), 75 | ('RFC_USE_INTEGRAL_COUNTS', '0'), 76 | ('RFC_USE_HYSTERESIS_FILTER', '1'), 77 | ('RFC_MINIMAL', '0'), 78 | ('RFC_TP_SUPPORT', '1'), 79 | ('RFC_HCM_SUPPORT', '1'), 80 | ('RFC_ASTM_SUPPORT', '1'), 81 | ('RFC_USE_DELEGATES', '1'), 82 | ('RFC_GLOBAL_EXTREMA', '1'), 83 | ('RFC_DAMAGE_FAST', '1'), 84 | ('RFC_DH_SUPPORT', '1'), 85 | ('RFC_AT_SUPPORT', '1'), 86 | ('RFC_AR_SUPPORT', '1'), 87 | ('RFC_DEBUG_FLAGS', '0'), 88 | ('RFC_EXPORT_MEX', '0'), 89 | ('RFC_EXPORT_PY', '0')] 90 | 91 | setup( 92 | name="rfcnt", 93 | version=__version__, 94 | description="Python interface for rainflow counting", 95 | long_description=long_description, 96 | long_description_content_type='text/markdown', 97 | keywords='rainflow counting', 98 | author=__author__, 99 | license='BSD-2-Clause', 100 | url='http://github.com/a-ma72/rainflow', 101 | setup_requires=['wheel'], 102 | install_requires=['numpy'], 103 | packages=["rfcnt", "rfcnt.tests"], 104 | package_dir={"rfcnt": "", "rfcnt.tests": "tests"}, 105 | package_data={ 106 | "rfcnt": ["*.py*", "_ext/*", "prebuilds.json", 107 | "requirements.txt", "README.md", "LICENSE"], 108 | "rfcnt.tests": ["*.py", "long_series.csv"] 109 | }, 110 | libraries=[("rainflow_c", {"sources": ["lib/rainflow.c"], 111 | "macros": define_macros})], 112 | ext_modules=[ 113 | Extension( 114 | "rfcnt.rfcnt", ["src/rfcnt.cpp"], 115 | define_macros=define_macros, 116 | include_dirs=['src', 'lib', np_get_include()], 117 | extra_compile_args=['-std=c++11'], 118 | ) 119 | ], 120 | classifiers=[ 121 | 'Development Status :: 6 - Mature', 122 | 'Environment :: Console', 123 | 'Framework :: Buildout :: Extension', 124 | 'Intended Audience :: Developers', 125 | 'Intended Audience :: Education', 126 | 'Intended Audience :: Information Technology', 127 | 'Intended Audience :: Science/Research', 128 | 'License :: OSI Approved :: BSD License', 129 | 'Natural Language :: English', 130 | 'Operating System :: MacOS :: MacOS X', 131 | 'Operating System :: Microsoft :: Windows', 132 | 'Operating System :: POSIX', 133 | 'Programming Language :: Python :: 3', 134 | 'Programming Language :: C++', 135 | 'Programming Language :: C', 136 | 'Topic :: Scientific/Engineering', 137 | 'Topic :: Scientific/Engineering :: Information Analysis', 138 | 'Topic :: Scientific/Engineering :: Physics', 139 | ] 140 | ) 141 | 142 | 143 | if __name__ == "__main__": 144 | main() 145 | print("numpy: %s" % (np_version,)) 146 | 147 | -------------------------------------------------------------------------------- /test/rfc_wrapper_advanced.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* Using Rainflow C-Library in a C++ context */ 3 | 4 | #include "config.h" 5 | 6 | // Check for correct configuration 7 | #if !RFC_MINIMAL && \ 8 | RFC_TP_SUPPORT && \ 9 | RFC_HCM_SUPPORT && \ 10 | RFC_USE_DELEGATES && \ 11 | RFC_GLOBAL_EXTREMA && \ 12 | RFC_DAMAGE_FAST && \ 13 | RFC_DH_SUPPORT && \ 14 | RFC_AT_SUPPORT && \ 15 | RFC_DEBUG_FLAGS 16 | 17 | // "Cross-platform C++ Utility Library" [https://github.com/i42output/neolib] 18 | #define HAVE_NEOLIB 0 19 | 20 | #include "rainflow.h" 21 | #include "greatest.h" 22 | 23 | #define NUMEL(x) (sizeof(x)/sizeof(*(x))) 24 | 25 | #if HAVE_NEOLIB 26 | #include "../../neolib/include/neolib/segmented_array.hpp" 27 | #else /*!HAVE_NEOLIB*/ 28 | #include 29 | #endif /*HAVE_NEOLIB*/ 30 | 31 | // Declare user defined turning points storage (tp_storage) 32 | namespace RFC_CPP_NAMESPACE 33 | { 34 | typedef struct rfc_value_tuple rfc_value_tuple_s; /**< Tuple of value and index position */ 35 | #if HAVE_NEOLIB 36 | class tp_storage : public neolib::segmented_array 37 | { 38 | public: 39 | inline size_t capacity() const { return size(); } /* Rainflow needs a capacity() method */ 40 | 41 | private: 42 | class notifier 43 | { 44 | public: 45 | notifier() { fprintf( stdout, "\nneolib ctor\n" ); } 46 | ~notifier() { fprintf( stdout, "\nneolib dtor\n" ); } 47 | } m_notifier; 48 | }; 49 | #else /*!HAVE_NEOLIB*/ 50 | typedef std::vector tp_storage; /**< Turning points storage */ 51 | #endif /*HAVE_NEOLIB*/ 52 | } 53 | 54 | 55 | /* If RFC_TP_STORAGE is defined, rainflow.hpp will define the 56 | * class Rainflow supporting external turning points storage 57 | * with given type */ 58 | #define RFC_TP_STORAGE RFC_CPP_NAMESPACE::tp_storage 59 | #include "rainflow.hpp" 60 | 61 | 62 | 63 | 64 | TEST wrapper_test_advanced( void ) 65 | { 66 | /* 67 | | 68 | 8.5 _____________________________________|________________________________________________________________________________ 69 | + o | + o + o 70 | 7.5 _____________________________________|________________________________________________________________________________ 71 | | 72 | 6.5 _____________________________________|________________________________________________________________________________ 73 | + + | + + 74 | 5.5 _____________________________________|________________________________________________________________________________ 75 | | 76 | 4.5 _____________________________________|________________________________________________________________________________ 77 | | 78 | 3.5 _____________________________________|________________________________________________________________________________ 79 | | 80 | 2.5 _____________________________________|________________________________________________________________________________ 81 | + + | + + 82 | 1.5 _____________________________________|________________________________________________________________________________ 83 | + + | + + + + 84 | 0.5 _____________________________________|________________________________________________________________________________ 85 | 1 2 3 4 5 6 7 8 | 1 4 5 6 7 8 1 4 5 6 7 8 86 | 87 | Counts 6-2 (2,3) | 6-2 (6,7) ; 8-1 (4,5) ; 8-1 (8,1) ; 6-2 (6,7) ; 8-1 (4,5) 88 | ( => 3x 6-2 ; 3x 8-1 ) 89 | 90 | TP 91 | 1: 8-1 92 | 2: 6-2 93 | 3: 6-2 94 | 4: 8-1, 8-1 95 | 5: 8-1, 8-1 96 | 6: 6-2, 6-2 97 | 7: 6-2, 6-2 98 | 8: 8-1 99 | */ 100 | Rainflow rf; 101 | Rainflow::rfc_wl_param_s wl_param; 102 | int flags; 103 | 104 | double values[] = { 1,6,2,8 }; 105 | std::vector data( values, values + 4 ); 106 | 107 | ASSERT( rf.init( 10, 1, -0.5, 1 ) ); 108 | 109 | ASSERT( rf.flags_set( (int)Rainflow::RFC_FLAGS_LOG_CLOSED_CYCLES, /*debugging*/ true, /*overwrite*/ false ) ); 110 | 111 | ASSERT( rf.feed( values, NUMEL(values) ) ); 112 | ASSERT( rf.tp_storage().size() == 3 ); 113 | 114 | ASSERT( rf.feed( data ) ); 115 | ASSERT( rf.tp_storage().size() == 7 ); 116 | 117 | ASSERT( rf.finalize( Rainflow::RFC_RES_REPEATED ) ); 118 | ASSERT( rf.tp_storage().size() == 8 ); 119 | 120 | ASSERT_EQ( rf.tp_storage()[0].tp_pos, 0 ); 121 | ASSERT_EQ( rf.tp_storage()[1].tp_pos, 0 ); 122 | ASSERT_EQ( rf.tp_storage()[2].tp_pos, 0 ); 123 | ASSERT_EQ( rf.tp_storage()[3].tp_pos, 0 ); 124 | ASSERT_EQ( rf.tp_storage()[4].tp_pos, 0 ); 125 | ASSERT_EQ( rf.tp_storage()[5].tp_pos, 0 ); 126 | ASSERT_EQ( rf.tp_storage()[6].tp_pos, 0 ); 127 | ASSERT_EQ( rf.tp_storage()[7].tp_pos, 0 ); 128 | 129 | ASSERT_EQ( rf.tp_storage()[0].value, 1 ); 130 | ASSERT_EQ( rf.tp_storage()[1].value, 6 ); 131 | ASSERT_EQ( rf.tp_storage()[2].value, 2 ); 132 | ASSERT_EQ( rf.tp_storage()[3].value, 8 ); 133 | ASSERT_EQ( rf.tp_storage()[4].value, 1 ); 134 | ASSERT_EQ( rf.tp_storage()[5].value, 6 ); 135 | ASSERT_EQ( rf.tp_storage()[6].value, 2 ); 136 | ASSERT_EQ( rf.tp_storage()[7].value, 8 ); 137 | 138 | ASSERT( rf.wl_param_get( wl_param ) ); 139 | double damage_6_2 = pow( ( (6.0-2.0)/2 / wl_param.sx ), fabs(wl_param.k) ) / wl_param.nx; 140 | double damage_8_1 = pow( ( (8.0-1.0)/2 / wl_param.sx ), fabs(wl_param.k) ) / wl_param.nx; 141 | 142 | ASSERT_IN_RANGE( 1.0, rf.tp_storage()[0].damage / ( damage_8_1*1/2 ), 1e-10 ); 143 | ASSERT_IN_RANGE( 1.0, rf.tp_storage()[1].damage / ( damage_6_2*1/2 ), 1e-10 ); 144 | ASSERT_IN_RANGE( 1.0, rf.tp_storage()[2].damage / ( damage_6_2*1/2 ), 1e-10 ); 145 | ASSERT_IN_RANGE( 1.0, rf.tp_storage()[3].damage / ( damage_8_1*1/1 ), 1e-10 ); 146 | ASSERT_IN_RANGE( 1.0, rf.tp_storage()[4].damage / ( damage_8_1*1/1 ), 1e-10 ); 147 | ASSERT_IN_RANGE( 1.0, rf.tp_storage()[5].damage / ( damage_6_2*1/1 ), 1e-10 ); 148 | ASSERT_IN_RANGE( 1.0, rf.tp_storage()[6].damage / ( damage_6_2*1/1 ), 1e-10 ); 149 | ASSERT_IN_RANGE( 1.0, rf.tp_storage()[7].damage / ( damage_8_1*1/2 ), 1e-10 ); 150 | 151 | ASSERT( rf.deinit() ); 152 | 153 | PASS(); 154 | } 155 | 156 | /* Test suite for rfc_test.c */ 157 | extern "C" 158 | SUITE( RFC_WRAPPER_SUITE_ADVANCED ) 159 | { 160 | fprintf( stdout, "\nsizeof(RFC_CPP_NAMESPACE::rfc_ctx_s): %lu\n", sizeof( RFC_CPP_NAMESPACE::rfc_ctx_s ) ); 161 | fprintf( stdout, "\nsizeof(Rainflow::rfc_ctx_s): %lu\n", sizeof( Rainflow::rfc_ctx_s ) ); 162 | RUN_TEST( wrapper_test_advanced ); 163 | } 164 | 165 | #else 166 | #include "greatest.h" 167 | 168 | TEST wrapper_test_advanced( void ) 169 | { 170 | fprintf( stdout, "\nNothing to do in this configuration!" ); 171 | PASS(); 172 | } 173 | 174 | extern "C" 175 | SUITE( RFC_WRAPPER_SUITE_ADVANCED ) 176 | { 177 | RUN_TEST( wrapper_test_advanced ); 178 | } 179 | #endif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![tests](https://github.com/a-ma72/rainflow/actions/workflows/run_test.yml/badge.svg) 2 | 3 | ## Rainflow Counting Algorithm (4-point-methods), C99 compliant 4 | 5 | ### "Rainflow Counting" consists of four main steps: 6 | 7 | 1. Hysteresis Filtering 8 | 2. Peak-Valley Filtering 9 | 3. Discretization 10 | 4. Four Point Counting Method: 11 | 12 | * D 13 | / \ Closed, if min(B,C) >= min(A,D) && max(B,C) <= max(A,D) 14 | B *<--/ Slope B-C is counted and removed from residue 15 | / \ / 16 | / * C 17 | \ / 18 | * A 19 | 20 | These steps are fully documented in standards such as 21 | ASTM E 1049 "Standard Practices for Cycle Counting in Fatigue Analysis" [1]. 22 | This implementation uses the 4-point algorithm mentioned in [3,4] and the 3-point HCM method proposed in [2] as well as the ASTM E 1049 (2011) standard in [1]. 23 | To take the residue into account, you may implement a custom method or use some 24 | predefined functions. 25 | 26 | ### Features of this package 27 | 1. Modular architecture in two layers: 28 | a) Module _rainflow.c_ (with _rainflow.h_) holds all necessary functions for rainflow counting and histogram extraction. You may select multiple optional features at compile time: 29 | `RFC_MINIMAL`: To use core functions for rainflow counting only (for porting to µControllers for example). 30 | `RFC_TP_SUPPORT`: Turning point storage. 31 | `RFC_HCM_SUPPORT`: HCM algorithm (Clormann/Seeger). 32 | `RFC_ASTM_SUPPORT`: ASTM E 1049 (2011) algorithm. 33 | `RFC_AT_SUPPORT`: User defined amplitude transformation (Haigh diagram). 34 | `RFC_DH_SUPPORT`: Damage history storage. 35 | `RFC_USE_DELEGATES`: Delegates for various core functions to implement user defined behavior. 36 | `RFC_GLOBAL_EXTREMA`: Store global data extrema. 37 | `RFC_DAMAGE_FAST`: Using lookup tables for damage and amplitude transformation. 38 | `RFC_EXPORT_MEX`: Export a mexFunction() to use the rainflow counting in MATLAB (R). 39 | `RFC_EXPORT_PY`: Export a Python extension to use the rainflow counting in Python. 40 | `RFC_UNIT_TEST`: Build an executable for unit testing. 41 | Using _COAN_ [[http://coan2.sourceforge.net/]] for example, you can tidy up the code from unwanted features.
42 | (The minimal version of this package is created using _COAN_ with option `RFC_MINMAL`set.) 43 | b) C++ wrapper _rainflow.hpp_ encapsulates functions from rainflow.h in a namespace and offers a template class _Rainflow_ for object oriented access and inheritance. 44 | This class also offers container class based turning point storage. 45 | 2. Streamable: You're able to count your data at once, as data packages or sample-wise. 46 | 3. Class width fit to your needs. Dynamically increase class width, when needed. (Needs turning point storage.) 47 | 4. Four point counting method, optionally HCM counting method (Clormann/Seeger). 48 | 5. Woehler curve with up to two slopes, fatigue limit and omission. 49 | 6. Miners rule for damage accumulation (elementary, original, modified and consistent). 50 | 7. In-time damage indicator (consistent Miner's rule). 51 | 8. In-time histograms: rainflow matrix, level crossing and range pair counting. 52 | 9. Turning points with hysteresis filtering. Turning points involved in a closed hysteresis are marked as pairs, with its partial assigned damage. (Compact history) 53 | 10. Look-up tables for damage calculation and amplitude transformation. 54 | 11. Amplitude transformation (Haigh diagram) according to FKM (symmetrical, non-symmetrical or user defined). 55 | 12. Damage history (uncompressed) 56 | 13. Various methods on residual data: 57 | - According to DIN 45667 58 | - ASTM method (halfcycle, fullcycle) 59 | - Second run 60 | - HCM 61 | 14. Various function pointers to implement user defined behavior. 62 | 15. Conversions supporting RFM->LC, RFM->RP, RFM->Damage and RP->Damage (original, elementar, modified, consistent). 63 | 64 | 65 | ## Build from sources 66 | 67 | Building makefiles from sources root folder in a shell: 68 | 69 | cmake -S. -Bbuild -G "Visual Studio 16 2019" 70 | cmake --build build --config Release 71 | 72 | If you intend to use a special MATLAB(R) installation to build the project 73 | just set an environment variable `Matlab_ROOT_DIR` such as 74 | 75 | export Matlab_ROOT_DIR=/usr/local/MATLAB/R2019b 76 | or use the option to configure the build tree: 77 | 78 | cmake -S. -Bbuild -DMatlab_ROOT_DIR=/usr/local/MATLAB/R2017b -G "Visual Studio 16 2019" 79 | cmake --build build --config Release 80 | 81 | ### MATLAB (only) 82 | cmake -S. -Bbuild -DRFC_EXPORT_PY=0 -DRFC_UNIT_TEST=0 -G "Visual Studio 16 2019" 83 | cmake --build build --target rfc_mex --config Release 84 | 85 | ### Python (only) 86 | cmake -S. -Bbuild -DRFC_EXPORT_MEX=0 -DRFC_UNIT_TEST=0 -G "Visual Studio 16 2019" 87 | cmake --build build --target rfcnt --config Release 88 | 89 | To build a Python package (wheel) you don't need cmake. Use packages `setuptools`, `wheel` and `build` instead: 90 | 91 | cd src/python 92 | pip install setuptools build wheel oldest-supported-numpy 93 | python -mbuild -nw 94 | 95 | #### Run examples 96 | python -m rfcnt.run_examples 97 | 98 | Using rfcnt in Google Colaboratory 99 | https://colab.research.google.com 100 | 101 | !pip install --no-build-isolation --no-deps https://github.com/a-ma72/rainflow/releases/download/rfcnt-0.4.7rc0/rfcnt-0.4.7rc0.tar.gz 102 | 103 | import rfcnt 104 | rfcnt.tests.examples.example_1() 105 | 106 | ### Unit test (only) 107 | Currently, two options are offered to perform a unit test: 108 | 1. Build `rfc_test` and execute in a shell: 109 | 110 | cmake -S. -Bbuild -DRFC_EXPORT_PY=0 -DRFC_EXPORT_MEX=0 -G "Visual Studio 16 2019" 111 | cmake --build build --target rfc_test --config Release 112 | 113 | Then invoke the unit test: 114 | 115 | build/test/Release/rfc_test # Linux 116 | build\test\Release\rfc_test.exe # Windows 117 | 118 | or 119 | 120 | ctest -C Release 121 | 2. Run Python code (shell): 122 | 123 | python -m rfcnt.run_tests 124 | 125 | 126 | 127 | --- 128 | ### References: 129 | [1] "Standard Practices for Cycle Counting in Fatigue Analysis."
130 | ASTM Standard E 1049, 1985 (2011).
131 | West Conshohocken, PA: ASTM International, 2011.
132 | [2] "Rainflow - HCM / Ein Hysteresisschleifen-Zaehlalgorithmus auf werkstoffmechanischer Grundlage"
133 | U.H. Clormann, T. Seeger
134 | 1985 TU Darmstadt, Fachgebiet Werkstoffmechanik
135 | [3] "Zaehlverfahren zur Bildung von Kollektiven und Matrizen aus Zeitfunktionen"
136 | FVA-Richtlinie, 2010.
137 | [https://fva-net.de/fileadmin/content/Richtlinien/FVA-Richtlinie_Zaehlverfahren_2010.pdf]
138 | [4] Siemens Product Lifecycle Management Software Inc., 2018.
139 | [https://community.plm.automation.siemens.com/t5/Testing-Knowledge-Base/Rainflow-Counting/ta-p/383093]
140 | [5] "Review and application of Rainflow residue processing techniques for accurate fatigue damage estimation"
141 | G.Marsh;
142 | International Journal of Fatigue 82 (2016) 757-765,
143 | [https://doi.org/10.1016/j.ijfatigue.2015.10.007]
144 | [6] "Betriebsfestigkeit - Verfahren und Daten zur Bauteilberechnung"
145 | Haibach, Erwin; Springer Verlag
146 | [] "Schaedigungsbasierte Hysteresefilter"; Hack, M, D386 (Diss Univ. Kaiserslautern), Shaker Verlag Aachen, 1998, ISBN 3-8265-3936-2
147 | [] "Hysteresis and Phase Transition"
148 | Brokate, M.; Sprekels, J.; Applied Mathematical Sciences 121; Springer, New York, 1996
149 | [] "Rainflow counting and energy dissipation in elastoplasticity"; Eur. J. Mech. A/Solids 15, pp. 705-737, 1996
150 | Brokate, M.; Dressler, K.; Krejci, P.
151 | [] "Multivariate Density Estimation: Theory, Practice and Visualization". New York, Chichester, Wiley & Sons, 1992
152 | Scott, D.
153 | [] "Werkstoffmechanik - Bauteile sicher beurteilen undWerkstoffe richtig einsetzen";
154 | Ralf Buergel, Hans Albert Richard, Andre Riemer; Springer FachmedienWiesbaden 2005, 2014
155 | [] "Zaehlverfahren und Lastannahme in der Betriebsfestigkeit";
156 | Michael Koehler, Sven Jenne / Kurt Poetter, Harald Zenner; Springer-Verlag Berlin Heidelberg 2012
157 | -------------------------------------------------------------------------------- /src/python/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import json 4 | import os 5 | import warnings 6 | from collections import namedtuple 7 | from enum import IntEnum 8 | from numpy import __version__ as _npy_version 9 | from numpy.lib import NumpyVersion 10 | from .version import __version__ 11 | 12 | 13 | ClassParams = namedtuple("ClassParams", "class_count, class_offset, class_width") 14 | 15 | 16 | class ResidualMethod(IntEnum): 17 | """ 18 | An enumeration representing various methods for handling residuals in data analysis. 19 | 20 | Attributes 21 | ---------- 22 | NONE : int 23 | No residual method applied. 24 | _IGNORE : int 25 | Ignore residuals in the computation. 26 | _NO_FINALIZE : int 27 | Do not finalize the computation with residuals. 28 | DISCARD : int 29 | Discard residuals completely. 30 | HALFCYCLES : int 31 | Apply the half cycles method for residuals. 32 | FULLCYCLES : int 33 | Apply the full cycles method for residuals. 34 | CLORMANN_SEEGER : int 35 | Use the Clormann-Seeger method for residuals. 36 | REPEATED : int 37 | Use repeated application of residuals method. 38 | DIN45667 : int 39 | Apply the DIN 45667 standard method for residuals. 40 | """ 41 | NONE = 0 # No residual method applied. 42 | _IGNORE = 1 # Ignore residuals in the computation. 43 | _NO_FINALIZE = 2 # Do not finalize the computation with residuals. 44 | DISCARD = 3 # Discard residuals completely. 45 | HALFCYCLES = 4 # Apply the half cycles method for residuals. 46 | FULLCYCLES = 5 # Apply the full cycles method for residuals. 47 | CLORMANN_SEEGER = 6 # Use the Clormann-Seeger method for residuals. 48 | REPEATED = 7 # Use repeated application of residuals method. 49 | DIN45667 = 8 # Apply the DIN 45667 standard method for residuals. 50 | 51 | 52 | class SDMethod(IntEnum): 53 | """ 54 | An enumeration representing various methods spreading damage increments over time history. 55 | 56 | Attributes 57 | ---------- 58 | NONE : int 59 | No spread damage calculation. 60 | HALF_23 : int 61 | Equally split damage between P2 and P3. 62 | RAMP_AMPLITUDE_23 : int 63 | Spread damage according to amplitude over points between P2 and P3. 64 | RAMP_DAMAGE_23 : int 65 | Spread damage evenly over points between P2 and P3. 66 | RAMP_AMPLITUDE_24 : int 67 | Spread damage exponentially according to amplitude impact over points between P2 and P4. 68 | RAMP_DAMAGE_24 : int 69 | Spread damage evenly over points between P2 and P4. 70 | FULL_P2 : int 71 | Assign damage to P2. 72 | FULL_P3 : int 73 | Assign damage to P3. 74 | TRANSIENT_23 : int 75 | Spread damage transient according to amplitude over points between P2 and P3. 76 | TRANSIENT_23c : int 77 | Spread damage transient according to amplitude over points between P2 and P4 only until cycle is closed. 78 | """ 79 | NONE = -1 # No spread damage calculation. 80 | HALF_23 = 0 # Equally split damage between P2 and P3. 81 | RAMP_AMPLITUDE_23 = 1 # Spread damage according to amplitude over points between P2 and P3. 82 | RAMP_DAMAGE_23 = 2 # Spread damage evenly over points between P2 and P3. 83 | RAMP_AMPLITUDE_24 = 3 # Spread damage exponentially according to amplitude impact over points between P2 and P4. 84 | RAMP_DAMAGE_24 = 4 # Spread damage evenly over points between P2 and P4. 85 | FULL_P2 = 5 # Assign damage to P2. 86 | FULL_P3 = 6 # Assign damage to P3. 87 | TRANSIENT_23 = 7 # Spread damage transient according to amplitude over points between P2 and P3. 88 | TRANSIENT_23c = 8 # Spread damage transient according to amplitude over points between P2 and P4 only until cycle is closed. 89 | 90 | 91 | class LCMethod(IntEnum): 92 | """ 93 | An enumeration which slopes encounter level crossing counting. 94 | 95 | Attributes 96 | ---------- 97 | SLOPES_UP : int 98 | Count on rising slopes only (default). 99 | SLOPES_DOWN : int 100 | Count on falling slopes only. 101 | SLOPES_ALL : int 102 | Count on rising AND falling slopes. 103 | """ 104 | SLOPES_UP = 0 # Count on rising slopes only (default). 105 | SLOPES_DOWN = 1 # Count on falling slopes only. 106 | SLOPES_ALL = 3 # Count on rising AND falling slopes. 107 | 108 | 109 | class RPDamageCalcMethod(IntEnum): 110 | """ 111 | A method enumeration how `damage_from_rp()` calculates the damage value. 112 | 113 | Attributes 114 | ---------- 115 | DEFAULT : int 116 | Use SN curve params as they are set. 117 | MINER_ELEMENTAR : int 118 | Use SN curve type "Miner elementar". 119 | (Slope `k2` ignored.) 120 | MINER_MODIFIED : int 121 | Use SN curve type "Miner modified". 122 | (Takes slope `k2` into account.) 123 | MINER_CONSISTENT : int 124 | Accumulate according to "Miner consistent". 125 | """ 126 | DEFAULT = 0 # Use SN curve params as they are set. 127 | MINER_ELEMENTAR = 1 # Use SN curve type "Miner elementary". 128 | MINER_MODIFIED = 2 # Use SN curve type "Miner modified". 129 | MINER_CONSISTENT = 3 # Accumulate according to "consistent Miner's rule". 130 | 131 | 132 | def _get_spec_extension_prebuild(): 133 | if os.name == "nt": 134 | import sys 135 | from importlib.util import module_from_spec, spec_from_file_location 136 | 137 | EXT_DIR = "_ext" # Directory containing the extension modules 138 | 139 | # Ensure Python version is 3.8 or higher 140 | if sys.version_info < (3, 8): 141 | warnings.warn("Prebuilds are supported for Python >= 3.8 only.") 142 | return 143 | 144 | # Determine the directory of the current script 145 | package_directory = os.path.dirname(__file__) 146 | 147 | # Try to locate suitable prebuilt modules. 148 | prebuilds = None 149 | if os.path.exists(os.path.join(package_directory, "_ext")): 150 | # Construct the path to the text file 151 | prebuilds_json_path = os.path.join(package_directory, "prebuilds.json") 152 | try: 153 | with open(prebuilds_json_path, "rt") as f: 154 | prebuilds = json.load(f) 155 | except Exception: 156 | pass 157 | 158 | npy_version = NumpyVersion(_npy_version) 159 | candidates = [] 160 | if prebuilds: 161 | for version_str in prebuilds: 162 | version = NumpyVersion(version_str) 163 | if version.major == npy_version.major and version <= npy_version: 164 | candidates.append(prebuilds[version_str]["target"]) 165 | 166 | if candidates and "root" not in candidates: 167 | prebuild = candidates[0] 168 | # TODO: Support other Python tags too? 169 | files = [file for file in os.listdir(os.path.join(package_directory, EXT_DIR)) if file.startswith(prebuild)] 170 | 171 | for file in files: 172 | spec = spec_from_file_location(".rfcnt", os.path.join(package_directory, EXT_DIR, file)) 173 | if spec: 174 | try: 175 | module = module_from_spec(spec) 176 | spec.loader.exec_module(module) 177 | sys.modules[__name__ + spec.name] = module 178 | except: 179 | raise ImportError(f"No suitable rfcnt build found for NumPy {_npy_version}!") 180 | return prebuild 181 | 182 | 183 | if NumpyVersion(_npy_version) >= "1.20.0": 184 | from numpy.typing import ArrayLike 185 | else: 186 | from typing import Any as ArrayLike 187 | 188 | 189 | # Try to import the extension module, compiled from sources. 190 | # When this module is installed from a wheel, there are multiple 191 | # versions depending on the version of NumPy installed. 192 | # In the latter case, the suitable version will be loaded and 193 | # finally imported. 194 | _prebuild = _get_spec_extension_prebuild() 195 | if _prebuild: 196 | print(f"Using prebuild `{_prebuild}`") 197 | 198 | # Import python extension 199 | from . import rfcnt # noqa 402 200 | 201 | # For backward compatibility, supporting both rfcnt.rfc() and rfcnt.rfcnt.rfc() 202 | from .rfcnt import rfc, damage_from_rp # noqa 402 203 | 204 | # from . import tests, utils # noqa F402 205 | del _get_spec_extension_prebuild, annotations, NumpyVersion, namedtuple, os, json, warnings 206 | -------------------------------------------------------------------------------- /src/python/cmake/rfcnt_target_functions.cmake: -------------------------------------------------------------------------------- 1 | # ==================================================== Functions ====================================================== 2 | 3 | # Function to get the NumPy C API version. 4 | # The function takes one argument: 5 | # - out_var: The variable name to store the detected NumPy C API version. 6 | 7 | 8 | if (POLICY CMP0177) 9 | cmake_policy(SET CMP0177 OLD) 10 | endif() 11 | 12 | 13 | function(numpy_get_capi_version out_var) 14 | # Initialize the output variable with "NOTFOUND". 15 | set(${out_var} "NOTFOUND") 16 | 17 | # Execute a Python command to get the NumPy include directory. 18 | execute_process( 19 | COMMAND "${Python3_EXECUTABLE}" -c "import numpy; print(numpy.get_include(), end='')" 20 | OUTPUT_VARIABLE Python3_NumPy_INCLUDE_DIR 21 | RESULT_VARIABLE Python3_NumPy_NOTFOUND 22 | ) 23 | 24 | # Recursive search for the _numpyconfig.h file in the NumPy include directory. 25 | file(GLOB_RECURSE NUMPYCONFIG_H "${Python3_NumPy_INCLUDE_DIR}/_numpyconfig.h") 26 | 27 | # Check if the _numpyconfig.h file was found. 28 | if(NUMPYCONFIG_H) 29 | # Read the contents of the _numpyconfig.h file. 30 | file(STRINGS ${NUMPYCONFIG_H} NUMPYCONFIG) 31 | 32 | # Iterate over each line of the file contents. 33 | foreach(line ${NUMPYCONFIG}) 34 | # Check if the line contains the NPY_API_VERSION definition. 35 | string(REGEX MATCH ".*#define[ \t]+NPY_API_VERSION[ \t]+" NPY_VERSION_FOUND ${line}) 36 | 37 | # If the line contains the NPY_API_VERSION definition, extract the version number. 38 | if(NPY_VERSION_FOUND) 39 | string(REGEX REPLACE ".*#define[ \t]+NPY_API_VERSION[ \t]+0[xX]([0-9a-fA-F]+).*$" "\\1" NPY_VERSION "${line}") 40 | 41 | # If the version number was successfully extracted, convert it from hexadecimal to decimal. 42 | if (NPY_VERSION) 43 | math(EXPR NPY_VERSION_DEC "0x${NPY_VERSION}" OUTPUT_FORMAT DECIMAL) 44 | 45 | # Set the output variable with the converted version number and break the loop. 46 | set(${out_var} ${NPY_VERSION_DEC} PARENT_SCOPE) 47 | break() 48 | endif() 49 | endif() 50 | endforeach() 51 | endif () 52 | endfunction() 53 | 54 | 55 | # Define a function to generate the appropriate pip argument for installing NumPy. 56 | # The function takes two arguments: 57 | # - out_var: The variable name to store the generated pip argument. 58 | # - numpy_version: The specified version of NumPy. 59 | 60 | function(numpy_pip_arg out_var numpy_version) 61 | # Check if the specified version is "oldest-supported-numpy". 62 | if (numpy_version STREQUAL "oldest-supported-numpy") 63 | # If the version is "oldest-supported-numpy", set the PIP_ARG to "oldest-supported-numpy". 64 | set(PIP_ARG "oldest-supported-numpy") 65 | else () 66 | # Use a regular expression to check if the numpy_version is a simple version number without any qualifiers (e.g., "1.18.5"). 67 | string(REGEX MATCH "^[0-9.]+$" NO_QUALIFIER "${numpy_version}") 68 | 69 | # If the numpy_version is a simple version number without qualifiers. 70 | if (NO_QUALIFIER) 71 | # Set the PIP_ARG to "numpy==" to specify an exact version. 72 | set(PIP_ARG "numpy==${numpy_version}") 73 | else () 74 | # Otherwise, set the PIP_ARG to "numpy" to handle version specifiers (e.g., ">1.18"). 75 | set(PIP_ARG "numpy${numpy_version}") 76 | endif () 77 | endif () 78 | # Set the output variable with the generated PIP_ARG and make it available in the parent scope. 79 | set(${out_var} ${PIP_ARG} PARENT_SCOPE) 80 | endfunction() 81 | 82 | 83 | # Define a function to generate a prebuild target name for the NumPy package. 84 | # The function takes three arguments: 85 | # - out_var: The variable name to store the generated prebuild target name. 86 | # - numpy_version: The specified version of NumPy. 87 | # - capi_version: The C API version of NumPy. 88 | 89 | function(prebuild_target_name out_var numpy_version capi_version) 90 | # Replace dots in the NumPy version with underscores to form a tag. 91 | string(REPLACE . _ Numpy_tag ${numpy_version}) 92 | 93 | # Create a Python tag using the major and minor Python version and platform. 94 | set(Python_tag "cp${Python3_VERSION_MAJOR}${Python3_VERSION_MINOR}-${PYTHON_PLATFORM}") 95 | 96 | # Assemble the prebuild name using project name, NumPy tag, and Python tag. 97 | set(prebuild_name ${PROJECT_NAME}_npy_${Numpy_tag}_api_v${capi_version}.${Python_tag}) 98 | 99 | # Set the output variable for the prebuild name, passing it to the parent scope. 100 | set(${out_var} ${prebuild_name} PARENT_SCOPE) 101 | endfunction() 102 | 103 | 104 | 105 | # ================================================== C module rfcnt =================================================== 106 | function (rfcnt_library numpy_version dependencies OUTPUT_NAME OUTPUT_DIRECTORY rfcnt_target) 107 | # Create a new target that builds a rfcnt module (or prebuild) with a specified NumPy version installed 108 | # before build. 109 | 110 | if (NOT TARGET ${rfcnt_target}) 111 | # Check if the target does not already exist. 112 | 113 | set( 114 | INCLUDE_DIRS 115 | ${Python3_INCLUDE_DIRS} 116 | ${rfc_core_SOURCE_DIR} 117 | ${NUMPY_INCLUDE_DIRECTORIES} 118 | ) 119 | # Set the include directories for the target, including Python3 include directories, 120 | # source directory for rfc_core, and NumPy include directories. 121 | 122 | set( 123 | RFCNT_SOURCES 124 | src/rfcnt.cpp 125 | ${CMAKE_CURRENT_SOURCE_DIR}/lib/rainflow.c 126 | ${CMAKE_CURRENT_SOURCE_DIR}/lib/rainflow.h 127 | ${CMAKE_CURRENT_SOURCE_DIR}/lib/rainflow.hpp 128 | ${CMAKE_CURRENT_SOURCE_DIR}/lib/config.h 129 | ) 130 | # Set the source files for the target, including rfcnt.cpp and various rainflow library files. 131 | 132 | add_library(${rfcnt_target} SHARED ${RFCNT_SOURCES}) 133 | # Create a shared library target with the specified source files. 134 | 135 | target_link_libraries(${rfcnt_target} PRIVATE ${Python3_LIBRARIES}) 136 | # Link the Python3 libraries to the target. 137 | 138 | if (MINGW) 139 | # If using MinGW, set specific link options and properties to address compatibility issues. 140 | # See: https://github.com/cython/cython/issues/3213 141 | target_link_options( 142 | ${rfcnt_target} 143 | PRIVATE 144 | -static-libgcc 145 | -static-libstdc++ 146 | -Wl,-Bstatic,--whole-archive 147 | -lwinpthread 148 | ) 149 | set_target_properties( 150 | ${rfcnt_target} 151 | PROPERTIES 152 | PREFIX "" 153 | ) 154 | endif () 155 | 156 | # Ignore rainflow configuration file and use fixed options: 157 | target_compile_definitions( 158 | ${rfcnt_target} 159 | PRIVATE 160 | RFC_VALUE_TYPE=double 161 | # RFC_USE_INTEGRAL_COUNTS 162 | RFC_USE_DELEGATES 163 | RFC_USE_HYSTERESIS_FILTER 164 | RFC_GLOBAL_EXTREMA 165 | RFC_HCM_SUPPORT 166 | RFC_ASTM_SUPPORT 167 | RFC_TP_SUPPORT 168 | RFC_DH_SUPPORT 169 | RFC_AT_SUPPORT 170 | RFC_AR_SUPPORT 171 | RFC_DAMAGE_FAST 172 | # RFC_DEBUG_FLAGS 173 | # RFC_EXPORT_MEX 174 | RFC_UNIT_TEST 175 | ) 176 | # Define preprocessor macros for the target, configuring various options for the rainflow counting library. 177 | 178 | if (WIN32) 179 | set_target_properties( 180 | ${rfcnt_target} 181 | PROPERTIES 182 | SUFFIX ".pyd" 183 | ) 184 | endif () 185 | # Windows python modules have suffix ".pyd" 186 | 187 | if (OUTPUT_DIRECTORY) 188 | set_target_properties( 189 | ${rfcnt_target} 190 | PROPERTIES 191 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${OUTPUT_DIRECTORY} # Output directory 192 | LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${OUTPUT_DIRECTORY} # Output directory 193 | ) 194 | install( 195 | TARGETS ${rfcnt_target} 196 | LIBRARY DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/${OUTPUT_DIRECTORY} 197 | ) 198 | # If an output directory is specified, set the runtime output directory and install 199 | # the target library to that directory. 200 | endif () 201 | 202 | if (OUTPUT_NAME) 203 | set_target_properties( 204 | ${rfcnt_target} 205 | PROPERTIES 206 | OUTPUT_NAME ${OUTPUT_NAME} # New executable name (my_app.exe on Windows) 207 | ) 208 | # If an output name is specified, set the output name of the target. 209 | endif () 210 | 211 | if (numpy_version) 212 | # If no version is given, the module is built in the current environment. 213 | # If a NumPy version string is specified, handle the installation and uninstallation 214 | # of that specific NumPy version. 215 | add_custom_target( 216 | numpy_${rfcnt_target}_uninstall 217 | COMMAND "${Python3_EXECUTABLE}" -mpip uninstall -y oldest-supported-numpy numpy 218 | COMMENT "Remove previous NumPy installations" 219 | DEPENDS ${dependencies} 220 | ) 221 | # Create a custom target to uninstall any existing NumPy versions, 222 | # ensuring main module has been built already. 223 | 224 | add_custom_target( 225 | numpy_${rfcnt_target}_install 226 | COMMAND "${Python3_EXECUTABLE}" -mpip install numpy==${numpy_version} 227 | COMMAND "${Python3_EXECUTABLE}" ${CMAKE_CURRENT_SOURCE_DIR}/cmake/numpy_get_include.py 228 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src 229 | COMMENT "Installing numpy ${numpy_version}" 230 | DEPENDS numpy_${rfcnt_target}_uninstall 231 | ) 232 | # Create a custom target to install the specified NumPy version and get its include directory. 233 | 234 | add_dependencies( 235 | ${rfcnt_target} 236 | numpy_${rfcnt_target}_install 237 | ) 238 | # Set the target to depend on the NumPy installation target. 239 | endif () 240 | 241 | target_include_directories( 242 | ${rfcnt_target} 243 | BEFORE 244 | PRIVATE 245 | ${INCLUDE_DIRS} 246 | ) 247 | # Set the include directories for the target, ensuring they are included before any other directories. 248 | 249 | endif () 250 | endfunction() 251 | -------------------------------------------------------------------------------- /src/3rd_party/unifdef/unifdef.txt: -------------------------------------------------------------------------------- 1 | UNIFDEF(1) Programmer's Manual UNIFDEF(1) 2 | 3 | NAME 4 | unifdef, unifdefall -- remove preprocessor conditionals from code 5 | 6 | SYNOPSIS 7 | unifdef [-bBcdehKkmnsStV] [-Ipath] [-[i]Dsym[=val]] [-[i]Usym] ... 8 | [-f defile] [-x {012}] [-M backext] [-o outfile] [infile ...] 9 | unifdefall [-Ipath] ... file 10 | 11 | DESCRIPTION 12 | The unifdef utility selectively processes conditional cpp(1) directives. 13 | It removes from a file both the directives and any additional text that 14 | they specify should be removed, while otherwise leaving the file alone. 15 | 16 | The unifdef utility acts on #if, #ifdef, #ifndef, #elif, #else, and 17 | #endif lines, using macros specified in -D and -U command line options or 18 | in -f definitions files. A directive is processed if the macro specifi- 19 | cations are sufficient to provide a definite value for its control 20 | expression. If the result is false, the directive and the following 21 | lines under its control are removed. If the result is true, only the 22 | directive is removed. An #ifdef or #ifndef directive is passed through 23 | unchanged if its controlling macro is not specified. Any #if or #elif 24 | control expression that has an unknown value or that unifdef cannot parse 25 | is passed through unchanged. By default, unifdef ignores #if and #elif 26 | lines with constant expressions; it can be told to process them by speci- 27 | fying the -k flag on the command line. 28 | 29 | It understands a commonly-used subset of the expression syntax for #if 30 | and #elif lines: integer constants, integer values of macros defined on 31 | the command line, the defined() operator, the operators !, ~, - (unary), 32 | *, /, %, +, -, <, <=, >, >=, ==, !=, &, ^, |, &&, ||, and parenthesized 33 | expressions. Division by zero is treated as an unknown value. A kind of 34 | ``short circuit'' evaluation is used for the && operator: if either oper- 35 | and is definitely false then the result is false, even if the value of 36 | the other operand is unknown. Similarly, if either operand of || is def- 37 | initely true then the result is true. 38 | 39 | When evaluating an expression, unifdef does not expand macros first. The 40 | value of a macro must be a simple number, not an expression. A limited 41 | form of indirection is allowed, where one macro's value is the name of 42 | another. 43 | 44 | In most cases, unifdef does not distinguish between object-like macros 45 | (without arguments) and function-like macros (with arguments). A func- 46 | tion-like macro invocation can appear in #if and #elif control expres- 47 | sions. If the macro is not explicitly defined, or is defined with the -D 48 | flag on the command-line, or with #define in a -f definitions file, its 49 | arguments are ignored. If a macro is explicitly undefined on the command 50 | line with the -U flag, or with #undef in a -f definitions file, it may 51 | not have any arguments since this leads to a syntax error. 52 | 53 | The unifdef utility understands just enough about C to know when one of 54 | the directives is inactive because it is inside a comment, or cannot be 55 | evaluated because it is split by a backslash-continued line. It spots 56 | unusually-formatted preprocessor directives and passes them through 57 | unchanged when the layout is too odd for it to handle. (See the BUGS 58 | section below.) 59 | 60 | A script called unifdefall can be used to remove all conditional cpp(1) 61 | directives from a file. It uses unifdef -s and cpp -dM to get lists of 62 | all the controlling macros and their definitions (or lack thereof), then 63 | invokes unifdef with appropriate arguments to process the file. 64 | 65 | OPTIONS 66 | -Dsym=val 67 | Specify that a macro is defined to a given value. 68 | 69 | -Dsym Specify that a macro is defined to the value 1. 70 | 71 | -Usym Specify that a macro is undefined. 72 | 73 | If the same macro appears in more than one argument, the last 74 | occurrence dominates. 75 | 76 | -iDsym[=val] 77 | -iUsym C strings, comments, and line continuations are ignored within 78 | #ifdef and #ifndef blocks controlled by macros specified with 79 | these options. 80 | 81 | -f defile 82 | The file defile contains #define and #undef preprocessor direc- 83 | tives, which have the same effect as the corresponding -D and -U 84 | command-line arguments. You can have multiple -f arguments and 85 | mix them with -D and -U arguments; later options override earlier 86 | ones. 87 | 88 | Each directive must be on a single line. Object-like macro defi- 89 | nitions (without arguments) are set to the given value. Func- 90 | tion-like macro definitions (with arguments) are treated as if 91 | they are set to 1. 92 | 93 | Warning: string literals and character constants are not parsed 94 | correctly in -f files. 95 | 96 | -b Replace removed lines with blank lines instead of deleting them. 97 | Mutually exclusive with the -B option. 98 | 99 | -B Compress blank lines around a deleted section. Mutually exclu- 100 | sive with the -b option. 101 | 102 | -c Complement, i.e., lines that would have been removed or blanked 103 | are retained and vice versa. 104 | 105 | -d Turn on printing of debugging messages. 106 | 107 | -e By default, unifdef will report an error if it needs to remove a 108 | preprocessor directive that spans more than one line, for exam- 109 | ple, if it has a multi-line comment hanging off its right hand 110 | end. The -e flag makes it ignore the line instead. 111 | 112 | -h Print help. 113 | 114 | -Ipath Specifies to unifdefall an additional place to look for #include 115 | files. This option is ignored by unifdef for compatibility with 116 | cpp(1) and to simplify the implementation of unifdefall. 117 | 118 | -K Always treat the result of && and || operators as unknown if 119 | either operand is unknown, instead of short-circuiting when 120 | unknown operands can't affect the result. This option is for 121 | compatibility with older versions of unifdef. 122 | 123 | -k Process #if and #elif lines with constant expressions. By 124 | default, sections controlled by such lines are passed through 125 | unchanged because they typically start ``#if 0'' and are used as 126 | a kind of comment to sketch out future or past development. It 127 | would be rude to strip them out, just as it would be for normal 128 | comments. 129 | 130 | -m Modify one or more input files in place. If an input file is not 131 | modified, the original is preserved instead of being overwritten 132 | with an identical copy. 133 | 134 | -M backext 135 | Modify input files in place, and keep backups of the original 136 | files by appending the backext to the input filenames. A zero 137 | length backext behaves the same as the -m option. 138 | 139 | -n Add #line directives to the output following any deleted lines, 140 | so that errors produced when compiling the output file correspond 141 | to line numbers in the input file. 142 | 143 | -o outfile 144 | Write output to the file outfile instead of the standard output 145 | when processing a single file. 146 | 147 | -s Instead of processing an input file as usual, this option causes 148 | unifdef to produce a list of macros that are used in preprocessor 149 | directive controlling expressions. 150 | 151 | -S Like the -s option, but the nesting depth of each macro is also 152 | printed. This is useful for working out the number of possible 153 | combinations of interdependent defined/undefined macros. 154 | 155 | -t Disables parsing for C strings, comments, and line continuations, 156 | which is useful for plain text. This is a blanket version of the 157 | -iD and -iU flags. 158 | 159 | -V Print version details. 160 | 161 | -x {012} 162 | Set exit status mode to zero, one, or two. See the EXIT STATUS 163 | section below for details. 164 | 165 | The unifdef utility takes its input from stdin if there are no file argu- 166 | ments. You must use the -m or -M options if there are multiple input 167 | files. You can specify inut from stdin or output to stdout with '-'. 168 | 169 | The unifdef utility works nicely with the -Dsym option of diff(1). 170 | 171 | EXIT STATUS 172 | In normal usage the unifdef utility's exit status depends on the mode set 173 | using the -x option. 174 | 175 | If the exit mode is zero (the default) then unifdef exits with status 0 176 | if the output is an exact copy of the input, or with status 1 if the out- 177 | put differs. 178 | 179 | If the exit mode is one, unifdef exits with status 1 if the output is 180 | unmodified or 0 if it differs. 181 | 182 | If the exit mode is two, unifdef exits with status zero in both cases. 183 | 184 | In all exit modes, unifdef exits with status 2 if there is an error. 185 | 186 | The exit status is 0 if the -h or -V command line options are given. 187 | 188 | DIAGNOSTICS 189 | EOF in comment 190 | 191 | Inappropriate #elif, #else or #endif 192 | 193 | Missing macro name in #define or #undef 194 | 195 | Obfuscated preprocessor control line 196 | 197 | Premature EOF (with the line number of the most recent unterminated #if) 198 | 199 | Too many levels of nesting 200 | 201 | Unrecognized preprocessor directive 202 | 203 | Unterminated char or string literal 204 | 205 | SEE ALSO 206 | cpp(1), diff(1) 207 | 208 | The unifdef home page is http://dotat.at/prog/unifdef 209 | 210 | HISTORY 211 | The unifdef command appeared in 2.9BSD. ANSI C support was added in 212 | FreeBSD 4.7. 213 | 214 | AUTHORS 215 | The original implementation was written by Dave Yost . 216 | Tony Finch rewrote it to support ANSI C. 217 | 218 | BUGS 219 | o Expression evaluation is very limited. 220 | 221 | o Character constants are not evaluated. String literals and character 222 | constants in -f definition files are ignored rather than parsed as 223 | part of a macro's replacement tokens. 224 | 225 | o Only the basic form of C++ raw string literals is recognized, like 226 | R"(string)" without delimiters as in R"delimiter(string)delimiter". 227 | 228 | o Source files are processed one line at a time, so preprocessor direc- 229 | tives split across more than one physical line (because of comments 230 | or backslash-newline) cannot be handled in every situation. 231 | 232 | o Trigraphs are not recognized. 233 | 234 | o There is no support for macros with different definitions at differ- 235 | ent points in the source file. 236 | 237 | o The text-mode and ignore functionality does not correspond to modern 238 | cpp(1) behaviour. 239 | 240 | Please send bug reports by email to . 241 | 242 | December 3, 2015 243 | -------------------------------------------------------------------------------- /src/python/tests/test_rfcnt.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | import numpy as np 5 | 6 | from .. import rfc, ResidualMethod, SDMethod 7 | 8 | 9 | class TestRainflowCounting(unittest.TestCase): 10 | 11 | def get_script_path(self) -> str: 12 | """ 13 | Get the directory path of the current script. 14 | 15 | Returns 16 | ------- 17 | str 18 | The directory path where the current script is located. 19 | """ 20 | return os.path.dirname(os.path.abspath(__file__)) 21 | 22 | def class_param(self, data: np.ndarray, class_count: int): 23 | """ 24 | Calculate class parameters for rainflow cycle counting. 25 | 26 | This method computes the class width and class offset based on the input data 27 | and the specified number of classes. If the input data is empty, default values 28 | are used. 29 | 30 | Parameters 31 | ---------- 32 | data : np.ndarray 33 | The input array containing the data. 34 | class_count : int 35 | The number of classes for the rainflow cycle counting. Must be greater than 1. 36 | 37 | Returns 38 | ------- 39 | tuple 40 | A tuple containing the class width and class offset. 41 | 42 | Raises 43 | ------ 44 | AssertionError 45 | If `class_count` is not greater than 1. 46 | """ 47 | assert class_count > 1, "class_count must be greater than 1" 48 | 49 | if len(data) == 0: 50 | class_width = 1 # Default class width when data is empty 51 | class_offset = 0 # Default class offset when data is empty 52 | else: 53 | class_width = np.ptp(data) / (class_count - 1) 54 | class_width = np.ceil(class_width * 100) / 100 # Round up to the nearest 0.01 55 | class_offset = np.floor((data.min() - class_width / 2) * 1000) / 1000 # Round down to the nearest 0.001 56 | 57 | return class_width, class_offset 58 | 59 | def test_empty_series(self): 60 | """ 61 | Test the rainflow cycle counting method with an empty data series. 62 | 63 | This test verifies that the rainflow cycle counting method handles an empty 64 | data series correctly, producing an empty rainflow matrix and no residuals. 65 | 66 | Parameters 67 | ---------- 68 | None 69 | 70 | Returns 71 | ------- 72 | None 73 | 74 | Raises 75 | ------ 76 | AssertionError 77 | If the resulting rainflow matrix sum is not 0 or if residuals are present. 78 | """ 79 | class_count = 100 # Number of classes 80 | x = np.array([]) # Empty data array 81 | x_max = 1 # Placeholder for maximum value in data 82 | x_min = -1 # Placeholder for minimum value in data 83 | 84 | # Calculate class parameters 85 | class_width, class_offset = self.class_param(x, class_count) 86 | 87 | hysteresis = class_width # Hysteresis width 88 | enforce_margin = False # Exclude first and last data point in turning points 89 | use_HCM = False # Do not use HCM method 90 | use_ASTM = False # Do not use ASTM method 91 | residual_method = ResidualMethod.NONE # No processing on residue 92 | spread_damage = SDMethod.NONE # No damage spreading 93 | 94 | # Perform rainflow counting 95 | res = rfc( 96 | x, 97 | class_count=class_count, 98 | class_width=class_width, 99 | class_offset=class_offset, 100 | hysteresis=hysteresis, 101 | residual_method=residual_method, 102 | enforce_margin=enforce_margin, 103 | use_HCM=use_HCM, 104 | use_ASTM=use_ASTM, 105 | spread_damage=spread_damage 106 | ) 107 | 108 | # Assert that the rainflow matrix sum is 0 109 | self.assertEqual(res["rfm"].sum(), 0) 110 | # Assert that there are no residuals 111 | self.assertEqual(len(res["res"]), 0) 112 | 113 | def test_single_cycle_up(self): 114 | """ 115 | Test the rainflow cycle counting method with a single upward cycle. 116 | 117 | This test verifies that the rainflow cycle counting method correctly identifies 118 | a single cycle in a small dataset. The test checks that the rainflow matrix 119 | and residuals are accurately computed. 120 | 121 | Parameters 122 | ---------- 123 | None 124 | 125 | Returns 126 | ------- 127 | None 128 | 129 | Raises 130 | ------ 131 | AssertionError 132 | If the resulting rainflow matrix or residuals do not match the expected values. 133 | """ 134 | class_count = 4 # Number of classes 135 | x = np.array([1, 3, 2, 4]) # Data array representing a single cycle 136 | x_max = 4 # Maximum value in data 137 | x_min = 1 # Minimum value in data 138 | 139 | # Calculate class parameters 140 | class_width, class_offset = self.class_param(x, class_count) 141 | 142 | hysteresis = class_width * 0.99 # Hysteresis width slightly less than class width 143 | enforce_margin = False # Exclude first and last data point in turning points 144 | use_HCM = False # Do not use HCM method 145 | use_ASTM = False # Do not use ASTM method 146 | residual_method = ResidualMethod.NONE # No processing on residue 147 | spread_damage = SDMethod.NONE # No damage spreading 148 | 149 | # Perform rainflow counting 150 | res = rfc( 151 | x, 152 | class_count=class_count, 153 | class_width=class_width, 154 | class_offset=class_offset, 155 | hysteresis=hysteresis, 156 | residual_method=residual_method, 157 | enforce_margin=enforce_margin, 158 | use_HCM=use_HCM, 159 | use_ASTM=use_ASTM, 160 | spread_damage=spread_damage 161 | ) 162 | 163 | # Assert that the rainflow matrix sum is 1 164 | self.assertEqual(res["rfm"].sum(), 1) 165 | # Assert that the specific entry in the rainflow matrix is 1 166 | self.assertEqual(res["rfm"][3 - 1, 2 - 1], 1) 167 | # Assert that the residuals match the expected values 168 | self.assertTrue((res["res"].flatten() == [1, 4]).all()) 169 | 170 | def test_one_cycle_down(self): 171 | """ 172 | Test the rainflow cycle counting method with a single downward cycle. 173 | 174 | This test verifies that the rainflow cycle counting method correctly identifies 175 | a single cycle in a small dataset with a downward trend. The test checks that the 176 | rainflow matrix and residuals are accurately computed. 177 | 178 | Parameters 179 | ---------- 180 | None 181 | 182 | Returns 183 | ------- 184 | None 185 | 186 | Raises 187 | ------ 188 | AssertionError 189 | If the resulting rainflow matrix or residuals do not match the expected values. 190 | """ 191 | class_count = 4 # Number of classes 192 | x = np.array([4, 2, 3, 1]) # Data array representing a single cycle with a downward trend 193 | x_max = 4 # Maximum value in data 194 | x_min = 1 # Minimum value in data 195 | 196 | # Calculate class parameters 197 | class_width, class_offset = self.class_param(x, class_count) 198 | 199 | hysteresis = class_width * 0.99 # Hysteresis width slightly less than class width 200 | enforce_margin = False # Exclude first and last data point in turning points 201 | use_HCM = False # Do not use HCM method 202 | use_ASTM = False # Do not use ASTM method 203 | residual_method = ResidualMethod.NONE # No processing on residue 204 | spread_damage = SDMethod.NONE # No damage spreading 205 | 206 | # Perform rainflow counting 207 | res = rfc( 208 | x, 209 | class_count=class_count, 210 | class_width=class_width, 211 | class_offset=class_offset, 212 | hysteresis=hysteresis, 213 | residual_method=residual_method, 214 | enforce_margin=enforce_margin, 215 | use_HCM=use_HCM, 216 | use_ASTM=use_ASTM, 217 | spread_damage=spread_damage 218 | ) 219 | 220 | # Assert that the rainflow matrix sum is 1 221 | self.assertEqual(res["rfm"].sum(), 1) 222 | # Assert that the specific entry in the rainflow matrix is 1 223 | self.assertEqual(res["rfm"][2 - 1, 3 - 1], 1) 224 | # Assert that the residuals match the expected values 225 | self.assertTrue((res["res"].flatten() == [4, 1]).all()) 226 | 227 | def test_small_sample(self): 228 | """ 229 | Test the rainflow cycle counting method with a small sample dataset. 230 | 231 | This test verifies that the rainflow cycle counting method correctly identifies 232 | cycles in a small dataset. The test checks that the rainflow matrix and residuals 233 | are accurately computed. 234 | 235 | Parameters 236 | ---------- 237 | None 238 | 239 | Returns 240 | ------- 241 | None 242 | 243 | Raises 244 | ------ 245 | AssertionError 246 | If the resulting rainflow matrix or residuals do not match the expected values. 247 | """ 248 | class_count = 6 # Number of classes 249 | x = np.array([2, 5, 3, 6, 2, 4, 1, 6, 1, 250 | 4, 1, 5, 3, 6, 3, 6, 1, 5, 2]) # Small sample data array 251 | x_max = x.max() # Maximum value in data 252 | x_min = x.min() # Minimum value in data 253 | 254 | # Calculate class parameters 255 | class_width, class_offset = self.class_param(x, class_count) 256 | 257 | hysteresis = class_width # Hysteresis width 258 | enforce_margin = False # Exclude first and last data point in turning points 259 | use_HCM = False # Do not use HCM method 260 | use_ASTM = False # Do not use ASTM method 261 | residual_method = ResidualMethod.NONE # No processing on residue 262 | spread_damage = SDMethod.NONE # No damage spreading 263 | 264 | # Perform rainflow counting 265 | res = rfc( 266 | x, 267 | class_count=class_count, 268 | class_width=class_width, 269 | class_offset=class_offset, 270 | hysteresis=hysteresis, 271 | residual_method=residual_method, 272 | enforce_margin=enforce_margin, 273 | use_HCM=use_HCM, 274 | use_ASTM=use_ASTM, 275 | spread_damage=spread_damage 276 | ) 277 | 278 | # Assert that the rainflow matrix sum is 7 279 | self.assertEqual(res["rfm"].sum(), 7) 280 | # Assert that specific entries in the rainflow matrix are correct 281 | self.assertEqual(res["rfm"][5 - 1, 3 - 1], 2) 282 | self.assertEqual(res["rfm"][6 - 1, 3 - 1], 1) 283 | self.assertEqual(res["rfm"][1 - 1, 4 - 1], 1) 284 | self.assertEqual(res["rfm"][2 - 1, 4 - 1], 1) 285 | self.assertEqual(res["rfm"][1 - 1, 6 - 1], 2) 286 | 287 | # Assert that the residuals match the expected values 288 | self.assertTrue((res["res"].flatten() == [2, 6, 1, 5, 2]).all()) 289 | 290 | def test_long_series(self): 291 | """ 292 | Test the rainflow cycle counting method with a long data series. 293 | 294 | This test verifies that the rainflow cycle counting method correctly processes 295 | a long dataset read from a CSV file. The test checks the computed damage, rainflow 296 | matrix, and residuals for accuracy. 297 | 298 | Parameters 299 | ---------- 300 | None 301 | 302 | Returns 303 | ------- 304 | None 305 | 306 | Raises 307 | ------ 308 | ImportError 309 | If the required module 'pandas' is not installed. 310 | AssertionError 311 | If the resulting damage, rainflow matrix, or residuals do not match the expected values. 312 | """ 313 | try: 314 | import pandas as pd 315 | except ImportError as err: 316 | print("This test requires module 'pandas'!") 317 | raise err 318 | 319 | class_count = 100 # Number of classes 320 | class_offset = -2025 # Class offset 321 | class_width = 50 # Class width 322 | 323 | # Read the data from CSV 324 | x = pd.read_csv(os.path.join(self.get_script_path(), "long_series.csv"), header=None) 325 | x = x.to_numpy().squeeze() # Convert to numpy array and squeeze 326 | 327 | hysteresis = class_width # Hysteresis width 328 | enforce_margin = True # Exclude first and last data point in turning points 329 | use_HCM = False # Do not use HCM method 330 | use_ASTM = False # Do not use ASTM method 331 | residual_method = ResidualMethod.NONE # No processing on residue 332 | spread_damage = SDMethod.RAMP_AMPLITUDE_23 # Spread damage method 333 | 334 | # Perform rainflow counting 335 | res = rfc( 336 | x, class_count=class_count, 337 | class_width=class_width, 338 | class_offset=class_offset, 339 | hysteresis=hysteresis, 340 | residual_method=residual_method, 341 | enforce_margin=enforce_margin, 342 | use_HCM=use_HCM, 343 | use_ASTM=use_ASTM, 344 | spread_damage=spread_damage 345 | ) 346 | 347 | # With residuum: pd == 9.8934e-06 (repeated) 348 | # Without residuum: pd == 1.1486e-07 349 | self.assertTrue(np.absolute(res["tp"][:, 2].sum() / res["damage"] - 1) < 1e-10) 350 | 351 | # Change spread damage method 352 | spread_damage = SDMethod.TRANSIENT_23c 353 | 354 | # Perform rainflow counting again with the new spread damage method 355 | res = rfc( 356 | x, class_count=class_count, 357 | class_width=class_width, 358 | class_offset=class_offset, 359 | hysteresis=hysteresis, 360 | residual_method=residual_method, 361 | enforce_margin=enforce_margin, 362 | use_HCM=use_HCM, 363 | use_ASTM=use_ASTM, 364 | spread_damage=spread_damage 365 | ) 366 | 367 | self.assertEqual("%.4e" % res["damage"], "1.1486e-07") 368 | self.assertEqual(res["rfm"].sum(), 640) 369 | self.assertEqual(len(res["res"]), 10) 370 | 371 | test = np.absolute(res["res"].flatten() - [ 372 | 0, 142, -609, 2950, -2000, 373 | 2159, 1894, 2101, 1991, 2061 374 | ]) 375 | self.assertTrue(test.sum() < 1e-3) 376 | 377 | 378 | def run(): 379 | unittest.main() 380 | -------------------------------------------------------------------------------- /src/python/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Function wrapper for Python 2 | 3 | # Don't forget to setup `PREBUILD_NUMPY_VERSIONS` in this file! 4 | 5 | # Regular build 6 | # ============= 7 | #[[ 8 | cmake -S. -Bbuild -DRFC_EXPORT_PY=1 -DRFC_EXPORT_MEX=0 9 | cmake --build build --target build_wheel_isolated --config Release 10 | 11 | # MinGW 12 | cmake -G "MinGW Makefiles" -D CMAKE_C_COMPILER=gcc -D CMAKE_CXX_COMPILER=g++ ... 13 | #]] 14 | 15 | # Linux build 16 | # =========== 17 | #[[ 18 | rm -rf build 19 | cmake -G "Unix Makefiles" -D CMAKE_C_COMPILER=gcc -D CMAKE_CXX_COMPILER=g++ ... 20 | cmake --build build --target build_wheel_isolated --config Release 21 | #]] 22 | 23 | # Python Setup 24 | #[[ 25 | pip install --force-reinstall --no-deps package.tar 26 | # mingw32: 27 | # in PYTHONPATH\Lib\distutils, create a file distutils.cfg and add these lines: 28 | [build] 29 | compiler = mingw32 30 | # Or: 31 | pip install --global-option build_ext --global-option --compiler=mingw32 32 | #]] 33 | 34 | 35 | 36 | cmake_minimum_required(VERSION 3.16) 37 | cmake_policy(SET CMP0048 NEW) # The project() command manages VERSION variables. 38 | if (POLICY CMP0076) 39 | cmake_policy(SET CMP0076 NEW) # The target_sources() command converts relative paths to absolute. 40 | endif () 41 | cmake_policy(SET CMP0091 NEW) # MSVC runtime library flags are selected by an abstraction. 42 | cmake_policy(SET CMP0094 NEW) # Modules FindPython3, FindPython2 and FindPython use LOCATION for lookup strategy. 43 | 44 | set (Python3_FIND_REGISTRY "LAST") 45 | # Search the Windows registry for Python installations last 46 | 47 | set (Python3_FIND_VIRTUALENV "FIRST") 48 | # Prefer to find Python in a virtual environment if available 49 | 50 | # set (CMAKE_FIND_DEBUG_MODE TRUE) 51 | # Uncommenting this line enables debug mode for CMake, which provides detailed information about the find process. 52 | 53 | project(rfcnt LANGUAGES C CXX) 54 | # Project name 55 | 56 | # Set prebuild versions depending on Python version: 57 | if (WIN32) 58 | set(PREBUILD_NUMPY_VERSIONS 1.23.2 2.0.0) # Python 3.11 59 | # set(PREBUILD_NUMPY_VERSIONS 1.19.3 2.0.0) # Python 3.9 60 | else () 61 | set(PREBUILD_NUMPY_VERSIONS) 62 | endif () 63 | # Define NumPy versions used as prebuilds (used for MS Windows only), specifiers like ~=1.19.0 allowed. 64 | 65 | # set (RFC_NUMPY_VERSION "1.23.5") 66 | # NumPy version for the main module is defined per `RFC_NUMPY_VERSION` 67 | 68 | if (UNIX) 69 | set(PATH_SEP ":") 70 | set(PYTHON_PLATFORM "linux_x86_64") 71 | else () 72 | set(PATH_SEP "\;") 73 | set(PYTHON_PLATFORM "win_amd64") 74 | endif () 75 | 76 | include (${CMAKE_CURRENT_SOURCE_DIR}/cmake/rfcnt_target_functions.cmake) 77 | 78 | 79 | # =============================================== Find Python 3 & NumPy =============================================== 80 | set(Python3_FIND_ABI "OFF" "ANY" "ANY") 81 | # This setting ensures that CMake does not consider the ABI when finding Python3, making the search less restrictive. 82 | 83 | if (VIRTUAL_ENV) 84 | # Prefer active virtual environment 85 | message(STATUS "Using virtual environment ${VIRTUAL_ENV} (cmake argument preset)") 86 | set(Python3_ROOT_DIR ${VIRTUAL_ENV}) 87 | elseif (DEFINED ENV{VIRTUAL_ENV}) 88 | # Prefer active virtual environment 89 | message(STATUS "Using active virtual environment $ENV{VIRTUAL_ENV}") 90 | set(Python3_ROOT_DIR $ENV{VIRTUAL_ENV}) 91 | else () 92 | message(STATUS "(Re-)create virtual environment") 93 | # If no virtual environment is active or specified, create a new one. 94 | 95 | file(REMOVE_RECURSE "${CMAKE_CURRENT_SOURCE_DIR}/venv") 96 | if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/venv") 97 | message(FATAL_ERROR "Could not delete local virtual environment '${CMAKE_CURRENT_SOURCE_DIR}/venv'") 98 | endif () 99 | # Remove any existing virtual environment in the source directory. 100 | 101 | find_package(Python3 ${RFC_PYTHON_VERSION} REQUIRED COMPONENTS Interpreter) 102 | # Find the Python3 interpreter first. 103 | 104 | execute_process( 105 | COMMAND "${Python3_EXECUTABLE}" -m venv ${CMAKE_CURRENT_SOURCE_DIR}/venv 106 | ) 107 | # Create a new virtual environment in the source directory. 108 | 109 | set(Python3_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/venv") 110 | # Set the root directory for Python3 to the new virtual environment. 111 | if (WIN32) 112 | set(Python3_EXECUTABLE "${Python3_ROOT_DIR}/Scripts/python.exe") 113 | else () 114 | set(Python3_EXECUTABLE "${Python3_ROOT_DIR}/bin/python3") 115 | endif () 116 | # Preliminary redirect to the just created environment 117 | 118 | execute_process( 119 | COMMAND "${Python3_EXECUTABLE}" -m pip install -U pip setuptools wheel build 120 | ) 121 | # Install necessary Python packages in the virtual environment. 122 | 123 | if (RFC_NUMPY_VERSION) 124 | numpy_pip_arg(PIP_ARG ${RFC_NUMPY_VERSION}) 125 | execute_process( 126 | COMMAND "${Python3_EXECUTABLE}" -m pip install ${PIP_ARG} 127 | ) 128 | # Install a specific version of NumPy if RFC_NUMPY_VERSION is defined. 129 | else () 130 | execute_process( 131 | COMMAND "${Python3_EXECUTABLE}" -m pip install oldest-supported-numpy 132 | ) 133 | # Otherwise, install the oldest supported version of NumPy. 134 | endif () 135 | 136 | execute_process( 137 | COMMAND "${Python3_EXECUTABLE}" -c "import numpy; print(numpy.get_include(), end='')" 138 | OUTPUT_VARIABLE Python3_NumPy_INCLUDE_DIR 139 | RESULT_VARIABLE Python3_NumPy_NOTFOUND 140 | ) 141 | # Get the include directory for NumPy. 142 | 143 | unset(Python3_INCLUDE_DIRS CACHE) 144 | unset(Python3_LIBRARIES CACHE) 145 | unset(Python3_NumPy_INCLUDE_DIRS CACHE) 146 | unset(Python3_NumPy_VERSION CACHE) 147 | # Unset variables to ensure a clean slate for the next find_package call. 148 | endif () 149 | find_package(Python3 ${RFC_PYTHON_VERSION} COMPONENTS Development Interpreter Numpy) 150 | # Find the Python3 package with the specified version and required components. 151 | 152 | # Ensure NumPy is installed, with the NumPy C API to build rfcnt 153 | if (NOT Python3_NumPy_FOUND) 154 | message(STATUS "[Python] Didn't find NumPy, trying alternative search...") 155 | execute_process( 156 | COMMAND "${Python3_EXECUTABLE}" ${CMAKE_CURRENT_SOURCE_DIR}/cmake/numpy_get_include.py 157 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src 158 | OUTPUT_VARIABLE Python3_NumPy_INCLUDE_DIRS 159 | RESULT_VARIABLE Python3_NumPy_NOTFOUND 160 | ) 161 | # Try to find NumPy include directories using a Python script if the initial search fails. 162 | 163 | if (Python3_NumPy_NOTFOUND) 164 | message(FATAL_ERROR "[Python] NumPy headers not found") 165 | else () 166 | message(STATUS "[Python] NumPy found.") 167 | endif () 168 | endif () 169 | 170 | # Get the active NumPy version and directories 171 | execute_process( 172 | COMMAND "${Python3_EXECUTABLE}" -c "import numpy; print(numpy.__version__, end='')" 173 | OUTPUT_VARIABLE Python3_NumPy_VERSION 174 | RESULT_VARIABLE Python3_NumPy_NOTFOUND 175 | ) 176 | # Retrieve the version of the installed NumPy package. 177 | 178 | numpy_get_capi_version(Python3_NumPy_CAPI) 179 | 180 | message(STATUS "[Python] Executable: ${Python3_EXECUTABLE}") 181 | message(STATUS "[Python] Include dirs: ${Python3_INCLUDE_DIRS}") 182 | message(STATUS "[Python] Library: ${Python3_LIBRARIES}") 183 | message(STATUS "[Python] NumPy include dirs: ${Python3_NumPy_INCLUDE_DIRS}") 184 | message(STATUS "[Python] NumPy version: ${Python3_NumPy_VERSION}") 185 | message(STATUS "[Python] NumPy API version: ${Python3_NumPy_CAPI}") 186 | # Output status messages with details about the Python3 and NumPy setup. 187 | 188 | 189 | # ======================================== Collect NumPy include directories ========================================== 190 | # Unfortunately, CMake does not provide a way to change the compilers include directories at build time. 191 | # Therefore all packages must be installed at configure time in order to find out the directories to pass them 192 | # to compiler options. 193 | message(STATUS "Collect NumPy include directories") 194 | # Output a status message indicating the start of collecting NumPy include directories. 195 | 196 | file(REMOVE "prebuilds.json") 197 | if (EXISTS "prebuilds.json") 198 | message(FATAL_ERROR "Could not delete 'prebuilds.json'") 199 | endif () 200 | set(PREBUILDS_JSON "") 201 | set(JSON_SEP " ") 202 | 203 | set(NUMPY_VERSIONS_LIST ${PREBUILD_NUMPY_VERSIONS}) 204 | # Collect numpy versions needed into NUMPY_VERSIONS_LIST 205 | 206 | file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/_ext) 207 | # Ensure directory for prebuilds 208 | 209 | # Initialize the list of NumPy versions with the given PREBUILD_NUMPY_VERSIONS. 210 | set(NUMPY_INCLUDE_DIRECTORIES) 211 | # Initialize an empty list to store the NumPy include directories. 212 | 213 | list(APPEND NUMPY_VERSIONS_LIST oldest-supported-numpy ${RFC_NUMPY_VERSION}) 214 | # Append the "oldest-supported-numpy" and RFC_NUMPY_VERSION to the list of NumPy versions. 215 | 216 | foreach (NUMPY_VERSION ${NUMPY_VERSIONS_LIST}) 217 | # Loop through each NumPy version in the list. 218 | 219 | numpy_pip_arg(PIP_ARG ${NUMPY_VERSION}) 220 | 221 | execute_process( 222 | COMMAND "${Python3_EXECUTABLE}" -mpip uninstall -y oldest-supported-numpy numpy 223 | ) 224 | # Uninstall any existing NumPy versions to ensure a clean installation. 225 | 226 | execute_process( 227 | COMMAND "${Python3_EXECUTABLE}" -mpip install ${PIP_ARG} 228 | COMMAND_ECHO STDOUT 229 | ) 230 | # Install the specified version of NumPy. 231 | 232 | execute_process( 233 | COMMAND "${Python3_EXECUTABLE}" ${CMAKE_CURRENT_SOURCE_DIR}/cmake/numpy_get_include.py 234 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src 235 | OUTPUT_VARIABLE Python3_NumPy_INCLUDE_DIR 236 | RESULT_VARIABLE Python3_NumPy_NOTFOUND 237 | ) 238 | # Run a Python script to get the include directory for NumPy and store it in Python3_NumPy_INCLUDE_DIR. 239 | 240 | if (Python3_NumPy_NOTFOUND) 241 | message(FATAL_ERROR "NumPy not found") 242 | # If the include directory was not found, output an error message and terminate the process. 243 | endif () 244 | 245 | # Get the active NumPy version and directories 246 | execute_process( 247 | COMMAND "${Python3_EXECUTABLE}" -c "import numpy; print(numpy.__version__, end='')" 248 | OUTPUT_VARIABLE Python3_NumPy_VERSION 249 | ) 250 | # Retrieve the version of the installed NumPy package. 251 | 252 | numpy_get_capi_version(NUMPY_CAPI_VERSION) 253 | prebuild_target_name(PREBUILD_TARGET ${Python3_NumPy_VERSION} ${NUMPY_CAPI_VERSION}) 254 | string(JSON VERSION_EXISTS ERROR_VARIABLE ERROR_MSG GET "{${PREBUILDS_JSON}}" "${Python3_NumPy_VERSION}") 255 | if (ERROR_MSG) 256 | string(APPEND PREBUILDS_JSON " ${JSON_SEP}\"${Python3_NumPy_VERSION}\": {\"capi\": ${NUMPY_CAPI_VERSION}, \"target\": \"${PREBUILD_TARGET}\"}\n") 257 | set(JSON_SEP ",") 258 | list(APPEND NUMPY_INCLUDE_DIRECTORIES ${Python3_NumPy_INCLUDE_DIR}) 259 | endif () 260 | # Append the found include directory to the list of NumPy include directories. 261 | endforeach () 262 | 263 | string(APPEND PREBUILDS_JSON " ${JSON_SEP}\"${Python3_NumPy_VERSION}\": {\"capi\":${NUMPY_CAPI_VERSION}, \"target\": \"root\"}\n") 264 | file(WRITE "prebuilds.json" "{\n${PREBUILDS_JSON}}") 265 | file(READ "prebuilds.json" PREBUILDS_JSON) 266 | list(REMOVE_DUPLICATES NUMPY_INCLUDE_DIRECTORIES) 267 | # Remove duplicate entries from the list of NumPy include directories. 268 | 269 | execute_process( 270 | COMMAND "${CMAKE_COMMAND}" -E copy 271 | "${CMAKE_CURRENT_SOURCE_DIR}/../lib/rainflow.c" 272 | "${CMAKE_CURRENT_SOURCE_DIR}/../lib/rainflow.h" 273 | "${CMAKE_CURRENT_SOURCE_DIR}/../lib/rainflow.hpp" 274 | "${CMAKE_CURRENT_SOURCE_DIR}/../lib/config.h" 275 | "${CMAKE_CURRENT_SOURCE_DIR}/lib" 276 | ) 277 | # Copy rainflow core sources 278 | 279 | 280 | # ========================================= Create main module and prebuilds ========================================== 281 | 282 | # Add main module with the current installed numpy version. 283 | rfcnt_library("${Python3_NumPy_VERSION}" "" "${PROJECT_NAME}" "." rfcnt_module) 284 | 285 | # Add prebuilds for different NumPy packages 286 | set(RFCNT_PREBUILD_TARGETS rfcnt_module) 287 | # Initialize an empty list to store prebuild targets. 288 | # The list is used as a target dependency for consecutive targets to ensure build order and 289 | # prevent conflicts when concurrent processes would update the virtual environment. 290 | 291 | string(JSON PREBUILDS_COUNT LENGTH ${PREBUILDS_JSON}) 292 | foreach (nr RANGE 1 ${PREBUILDS_COUNT}) 293 | # Loop through each version of NumPy in the list of PREBUILD_NUMPY_VERSIONS. 294 | 295 | math (EXPR member_id "${nr} - 1") 296 | string(JSON VERSION_STRING MEMBER ${PREBUILDS_JSON} ${member_id}) 297 | string(JSON PREBUILD_NAME GET ${PREBUILDS_JSON} ${VERSION_STRING} "target") 298 | 299 | if (NOT ${PREBUILD_NAME} STREQUAL "root") 300 | message(STATUS ${PREBUILD_NAME}) 301 | # Output a status message with the prebuild name. 302 | 303 | set(RFCNT_PREBUILD_TARGET target_${PREBUILD_NAME}) 304 | # Define the prebuild target name based on the prebuild name. 305 | 306 | if (NOT TARGET ${RFCNT_PREBUILD_TARGET}) 307 | # Check if the target does not already exist. 308 | 309 | rfcnt_library(${VERSION_STRING} "${RFCNT_PREBUILD_TARGETS}" ${PREBUILD_NAME} _ext ${RFCNT_PREBUILD_TARGET}) 310 | # Call the rfcnt_library function to create the library target with the specified details. 311 | 312 | list(APPEND RFCNT_PREBUILD_TARGETS ${RFCNT_PREBUILD_TARGET}) 313 | # Append the new prebuild target to the list of prebuild targets. 314 | endif () 315 | endif () 316 | endforeach () 317 | 318 | 319 | add_custom_target( 320 | rfcnt_prebuilds 321 | DEPENDS ${RFCNT_PREBUILD_TARGETS} 322 | COMMENT "Build rfcnt prebuilds" 323 | ) 324 | # Custom target for prebuilds, preserve the build order. 325 | 326 | 327 | add_custom_target( 328 | rfcnt 329 | DEPENDS rfcnt_prebuilds rfcnt_module 330 | COMMENT "Build rfcnt modules" 331 | ) 332 | # Custom target for main module and prebuilds. 333 | 334 | 335 | add_custom_target( 336 | build_wheel_isolated 337 | COMMAND "${CMAKE_COMMAND}" -E remove -f $ 338 | COMMAND "${Python3_EXECUTABLE}" -mbuild 339 | DEPENDS rfcnt 340 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 341 | COMMENT "Build rfcnt wheel (needs a Numpy package defined in pyproject.toml!)" 342 | ) 343 | # Custom target for creating sdist and wheel from sdist for rfcnt with setuptools (prebuilds by CMake). 344 | 345 | 346 | add_custom_target( 347 | build_wheel 348 | COMMAND "${Python3_EXECUTABLE}" -m pip uninstall -y numpy 349 | COMMAND "${Python3_EXECUTABLE}" -m pip install oldest-supported-numpy 350 | COMMAND "${Python3_EXECUTABLE}" -mbuild --no-isolation 351 | DEPENDS rfcnt 352 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 353 | COMMENT "Build rfcnt wheel" 354 | ) 355 | # Custom target for creating wheel with main module and prebuilds created by CMake. 356 | 357 | 358 | add_custom_target( 359 | clean_all 360 | COMMAND "${CMAKE_COMMAND}" -E remove_directory "${CMAKE_CURRENT_SOURCE_DIR}/build" 361 | COMMAND "${CMAKE_COMMAND}" -E remove_directory "${CMAKE_CURRENT_SOURCE_DIR}/dist" 362 | COMMAND "${CMAKE_COMMAND}" -E remove_directory "${CMAKE_CURRENT_SOURCE_DIR}/rfcnt.egg-info" 363 | COMMAND "${CMAKE_COMMAND}" -E remove_directory "${CMAKE_CURRENT_SOURCE_DIR}/venv" 364 | COMMAND "${CMAKE_COMMAND}" -E remove_directory "${CMAKE_CURRENT_SOURCE_DIR}/_ext" 365 | COMMAND "${CMAKE_COMMAND}" -E remove "${CMAKE_CURRENT_SOURCE_DIR}/prebuilds.json" 366 | ) 367 | # Custom target for cleanup directories. 368 | -------------------------------------------------------------------------------- /src/lib/minimal/rainflow.h: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * | .-. 4 | * | / \ 5 | * | .-.===========/ \ .-. 6 | * | / \ / \ / \ 7 | * | / \ / \ / \ .-. 8 | * +--/-------\-----/-----------\---/-------\-------/---\ 9 | * | / \ / '-'=========\ / \ / 10 | * |/ '-' \ / '-' 11 | * | '-' 12 | * ____ ___ _____ __________ ____ _ __ 13 | * / __ \/ | / _/ | / / ____/ / / __ \ | / / 14 | * / /_/ / /| | / // |/ / /_ / / / / / / | /| / / 15 | * / _, _/ ___ |_/ // /| / __/ / /___/ /_/ /| |/ |/ / 16 | * /_/ |_/_/ |_/___/_/ |_/_/ /_____/\____/ |__/|__/ 17 | * 18 | * Rainflow Counting Algorithm (4-point-method), C99 compliant 19 | * 20 | * 21 | * "Rainflow Counting" consists of four main steps: 22 | * 1. Hysteresis Filtering 23 | * 2. Peak-Valley Filtering 24 | * 3. Discretization 25 | * 4. Four Point Counting Method: 26 | * 27 | * * D 28 | * / \ Closed, if min(B,C) >= min(A,D) && max(B,C) <= max(A,D) 29 | * B *<--/ Slope B-C is counted and removed from residue 30 | * / \ / 31 | * / * C 32 | * \ / 33 | * * A 34 | * 35 | * These steps are fully documented in standards such as 36 | * ASTM E1049 "Standard Practices for Cycle Counting in Fatigue Analysis" [1]. 37 | * This implementation uses the 4-point algorithm mentioned in [3,4] and the 3-point HCM method proposed in [2]. 38 | * To take the residue into account, you may implement a custom method or use some 39 | * predefined functions. 40 | * 41 | * References: 42 | * [1] "Standard Practices for Cycle Counting in Fatigue Analysis." 43 | * ASTM Standard E 1049, 1985 (2011). 44 | * West Conshohocken, PA: ASTM International, 2011. 45 | * [2] "Rainflow - HCM / Ein Hysteresisschleifen-Zaehlalgorithmus auf werkstoffmechanischer Grundlage" 46 | * U.H. Clormann, T. Seeger 47 | * 1985 TU Darmstadt, Fachgebiet Werkstoffmechanik 48 | * [3] "Zaehlverfahren zur Bildung von Kollektiven und Matrizen aus Zeitfunktionen" 49 | * FVA-Richtlinie, 2010. 50 | * [https://fva-net.de/fileadmin/content/Richtlinien/FVA-Richtlinie_Zaehlverfahren_2010.pdf] 51 | * [4] Siemens Product Lifecycle Management Software Inc., 2018. 52 | * [https://community.plm.automation.siemens.com/t5/Testing-Knowledge-Base/Rainflow-Counting/ta-p/383093] 53 | * [5] "Review and application of Rainflow residue processing techniques for accurate fatigue damage estimation" 54 | * G.Marsh; 55 | * International Journal of Fatigue 82 (2016) 757-765, 56 | * [https://doi.org/10.1016/j.ijfatigue.2015.10.007] 57 | * [6] "Betriebsfestigkeit - Verfahren und Daten zur Bauteilberechnung" 58 | * Haibach, Erwin; Springer Verlag 59 | * [] "Schaedigungsbasierte Hysteresefilter"; Hack, M, D386 (Diss Univ. Kaiserslautern), Shaker Verlag Aachen, 1998, ISBN 3-8265-3936-2 60 | * [] "Hysteresis and Phase Transition" 61 | * Brokate, M.; Sprekels, J.; Applied Mathematical Sciences 121; Springer, New York, 1996 62 | * [] "Rainflow counting and energy dissipation in elastoplasticity"; Eur. J. Mech. A/Solids 15, pp. 705-737, 1996 63 | * Brokate, M.; Dressler, K.; Krejci, P. 64 | * [] "Multivariate Density Estimation: Theory, Practice and Visualization". New York, Chichester, Wiley & Sons, 1992 65 | * Scott, D. 66 | * [] "Werkstoffmechanik - Bauteile sicher beurteilen undWerkstoffe richtig einsetzen"; 67 | * Ralf Buergel, Hans Albert Richard, Andre Riemer; Springer FachmedienWiesbaden 2005, 2014 68 | * [] "Zaehlverfahren und Lastannahme in der Betriebsfestigkeit"; 69 | * Michael Koehler, Sven Jenne / Kurt Poetter, Harald Zenner; Springer-Verlag Berlin Heidelberg 2012 70 | * 71 | * 72 | *================================================================================ 73 | * BSD 2-Clause License 74 | * 75 | * Copyright (c) 2023, Andras Martin 76 | * All rights reserved. 77 | * 78 | * Redistribution and use in source and binary forms, with or without 79 | * modification, are permitted provided that the following conditions are met: 80 | * 81 | * * Redistributions of source code must retain the above copyright notice, this 82 | * list of conditions and the following disclaimer. 83 | * 84 | * * Redistributions in binary form must reproduce the above copyright notice, 85 | * this list of conditions and the following disclaimer in the documentation 86 | * and/or other materials provided with the distribution. 87 | * 88 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 89 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 90 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 91 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 92 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 93 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 94 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 95 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 96 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 97 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 98 | *================================================================================ 99 | */ 100 | 101 | #ifndef RAINFLOW_H 102 | #define RAINFLOW_H 103 | 104 | #if COAN_INVOKED 105 | /* This version is generated via coan (http://coan2.sourceforge.net/) */ 106 | #endif /*COAN_INVOKED*/ 107 | 108 | #if RFC_HAVE_CONFIG_H 109 | #include "config.h" /* Configuration */ 110 | #endif 111 | 112 | #define RFC_CORE_VERSION "0.8" 113 | 114 | #ifndef ON 115 | #define ON (1) 116 | #endif /*ON*/ 117 | #ifndef OFF 118 | #define OFF (0) 119 | #endif /*OFF*/ 120 | 121 | #define RFC_CLASS_COUNT_MAX (1024) 122 | 123 | #ifndef RFC_VALUE_TYPE 124 | #define RFC_VALUE_TYPE double 125 | #endif /*RFC_VALUE_TYPE*/ 126 | 127 | #define RFC_COUNTS_VALUE_TYPE double 128 | #define RFC_FULL_CYCLE_INCREMENT (1.0) 129 | #define RFC_HALF_CYCLE_INCREMENT (0.5) 130 | #define RFC_COUNTS_LIMIT (4.5e15 - RFC_FULL_CYCLE_INCREMENT) 131 | 132 | #undef RFC_USE_DELEGATES 133 | #define RFC_USE_DELEGATES OFF 134 | #undef RFC_HCM_SUPPORT 135 | #define RFC_HCM_SUPPORT OFF 136 | #undef RFC_ASTM_SUPPORT 137 | #define RFC_ASTM_SUPPORT OFF 138 | #undef RFC_TP_SUPPORT 139 | #define RFC_TP_SUPPORT OFF 140 | #undef RFC_DH_SUPPORT 141 | #define RFC_DH_SUPPORT OFF 142 | #undef RFC_AT_SUPPORT 143 | #define RFC_AT_SUPPORT OFF 144 | #undef RFC_AR_SUPPORT 145 | #define RFC_AR_SUPPORT OFF 146 | #undef RFC_GLOBAL_EXTREMA 147 | #define RFC_GLOBAL_EXTREMA OFF 148 | #undef RFC_DAMAGE_FAST 149 | #define RFC_DAMAGE_FAST OFF 150 | 151 | 152 | /* Notes on mix C and C++ headers: 153 | * https://developers.redhat.com/blog/2016/02/29/why-cstdlib-is-more-complicated-than-you-might-think/ 154 | * Avoid including C standard headers in a C++ namespace! */ 155 | #ifdef __cplusplus 156 | #include /* bool, true, false */ 157 | #include /* ULLONG_MAX */ 158 | #include /* ULLONG_MAX */ 159 | #include /* size_t, NULL */ 160 | #ifndef RFC_CPP_NAMESPACE 161 | #define RFC_CPP_NAMESPACE rainflow_C 162 | #endif /*RFC_CPP_NAMESPACE*/ 163 | namespace RFC_CPP_NAMESPACE { 164 | extern "C" { 165 | #else /*!__cplusplus*/ 166 | #include /* bool, true, false */ 167 | #include /* ULLONG_MAX */ 168 | #include /* ULLONG_MAX */ 169 | #include /* size_t, NULL */ 170 | #endif /*__cplusplus*/ 171 | 172 | #pragma pack(push, 1) 173 | 174 | 175 | /* Memory allocation aim info */ 176 | enum rfc_mem_aim 177 | { 178 | RFC_MEM_AIM_TEMP = 0, /**< Error on accessing memory for temporary storage */ 179 | RFC_MEM_AIM_RESIDUE = 1, /**< Error on accessing memory for residue */ 180 | RFC_MEM_AIM_MATRIX = 2, /**< Error on accessing memory for rf matrix */ 181 | RFC_MEM_AIM_RP = 3, /**< Error on accessing memory for range pair counting */ 182 | RFC_MEM_AIM_LC = 4, /**< Error on accessing memory for level crossing */ 183 | }; 184 | 185 | 186 | /* Flags */ 187 | enum rfc_flags 188 | { 189 | RFC_FLAGS_DEFAULT = -1, 190 | RFC_FLAGS_COUNT_RFM = 1 << 0, /**< Count into rainflow matrix */ 191 | RFC_FLAGS_COUNT_DAMAGE = 1 << 1, /**< Count damage */ 192 | RFC_FLAGS_COUNT_ALL = RFC_FLAGS_COUNT_RFM /**< Count all */ 193 | | RFC_FLAGS_COUNT_DAMAGE 194 | }; 195 | 196 | 197 | enum rfc_debug_flags 198 | { 199 | RFC_FLAGS_LOG_CLOSED_CYCLES = 1 << 0, /**< Log closed cycles */ 200 | }; 201 | 202 | 203 | enum rfc_state 204 | { 205 | RFC_STATE_INIT0, /**< Initialized with zeros */ 206 | RFC_STATE_INIT, /**< Initialized, memory allocated */ 207 | RFC_STATE_BUSY, /**< In counting state */ 208 | RFC_STATE_BUSY_INTERIM, /**< In counting state, having still one interim turning point (not included) */ 209 | RFC_STATE_FINALIZE, /**< Finalizing */ 210 | RFC_STATE_FINISHED, /**< Counting finished, memory still allocated */ 211 | RFC_STATE_ERROR, /**< An error occurred */ 212 | }; 213 | 214 | 215 | enum rfc_error 216 | { 217 | RFC_ERROR_UNEXP = -1, /**< Unexpected error */ 218 | RFC_ERROR_NOERROR = 0, /**< No error */ 219 | RFC_ERROR_INVARG = 1, /**< Invalid arguments passed */ 220 | RFC_ERROR_UNSUPPORTED = 2, /**< Unsupported feature */ 221 | RFC_ERROR_MEMORY = 3, /**< Error on memory allocation */ 222 | RFC_ERROR_DATA_OUT_OF_RANGE = 9, /**< Input data leaves classrange */ 223 | RFC_ERROR_DATA_INCONSISTENT = 10, /**< Processed data is inconsistent (internal error) */ 224 | }; 225 | 226 | 227 | enum rfc_res_method 228 | { 229 | /* Don't change order! */ 230 | RFC_RES_NONE = 0, /**< No residual method */ 231 | RFC_RES_IGNORE = 1, /**< Ignore residue (same as RFC_RES_NONE) */ 232 | RFC_RES_NO_FINALIZE = 2, /**< Don't finalize the data stream */ 233 | RFC_RES_COUNT /**< Number of options */ 234 | }; 235 | 236 | 237 | enum rfc_wl_defaults 238 | { 239 | RFC_WL_SD_DEFAULT = 1000, /**< Fatigue strength amplitude (Miner original) */ 240 | RFC_WL_ND_DEFAULT = 10000000L, /**< Cycles according to wl_sd */ 241 | RFC_WL_K_DEFAULT = -5, /**< Woehler slope, always negative */ 242 | }; 243 | 244 | 245 | /* Typedefs */ 246 | typedef RFC_VALUE_TYPE rfc_value_t; /** Input data value type */ 247 | typedef RFC_COUNTS_VALUE_TYPE rfc_counts_t; /** Type of counting values */ 248 | typedef struct rfc_value_tuple rfc_value_tuple_s; /** Tuple of value and index position */ 249 | typedef struct rfc_ctx rfc_ctx_s; /** Forward declaration (rainflow context) */ 250 | typedef enum rfc_mem_aim rfc_mem_aim_e; /** Memory accessing mode */ 251 | typedef enum rfc_flags rfc_flags_e; /** Flags, see RFC_FLAGS... */ 252 | typedef enum rfc_debug_flags rfc_debug_flags_e; /** Flags, see RFC_DEBUG_FLAGS... */ 253 | typedef enum rfc_state rfc_state_e; /** Counting state, see RFC_STATE... */ 254 | typedef enum rfc_error rfc_error_e; /** Recent error, see RFC_ERROR... */ 255 | typedef enum rfc_res_method rfc_res_method_e; /** Method when count residue into matrix, see RFC_RES... */ 256 | 257 | /* Memory allocation functions typedef */ 258 | typedef void * ( *rfc_mem_alloc_fcn_t ) ( void *, size_t num, size_t size, int aim ); /** Memory allocation functor */ 259 | 260 | /* Core functions */ 261 | bool RFC_init ( void *ctx, unsigned class_count, rfc_value_t class_width, rfc_value_t class_offset, 262 | rfc_value_t hysteresis, rfc_flags_e flags ); 263 | rfc_state_e RFC_state_get ( const void *ctx ); 264 | rfc_error_e RFC_error_get ( const void *ctx ); 265 | bool RFC_wl_init_elementary ( void *ctx, double sx, double nx, double k ); 266 | bool RFC_deinit ( void *ctx ); 267 | bool RFC_feed ( void *ctx, const rfc_value_t* data, size_t count ); 268 | bool RFC_finalize ( void *ctx, rfc_res_method_e residual_method ); 269 | bool RFC_res_get ( const void *ctx, const rfc_value_tuple_s **residue, unsigned *count ); 270 | 271 | 272 | /* Value info struct */ 273 | struct rfc_value_tuple 274 | { 275 | rfc_value_t value; /**< Value. Don't change order, value field must be first! */ 276 | unsigned cls; /**< Class number, base 0 */ 277 | size_t pos; /**< Absolute position in input data stream, base 1 */ 278 | }; 279 | 280 | 281 | /** 282 | * Rainflow context (ctx) 283 | */ 284 | struct rfc_ctx 285 | { 286 | size_t version; /**< Version number as sizeof(struct rfctx..), must be 1st field! */ 287 | 288 | /* State and error information */ 289 | rfc_state_e state; /**< Current counting state */ 290 | rfc_error_e error; /**< Error code */ 291 | 292 | /* Methods */ 293 | rfc_res_method_e residual_method; /**< Used on finalizing */ 294 | 295 | /* Memory allocation functions */ 296 | rfc_mem_alloc_fcn_t mem_alloc; /**< Allocate initialized memory */ 297 | 298 | /* Counter increments */ 299 | rfc_counts_t full_inc; /**< Increment for a full cycle */ 300 | rfc_counts_t half_inc; /**< Increment for a half cycle, used by some residual algorithms */ 301 | rfc_counts_t curr_inc; /**< Current increment, used by counting algorithms (changed by finalize_res_weight_cycles() only) */ 302 | 303 | /* Rainflow class parameters */ 304 | unsigned class_count; /**< Class count */ 305 | rfc_value_t class_width; /**< Class width */ 306 | rfc_value_t class_offset; /**< Lower bound of first class */ 307 | rfc_value_t hysteresis; /**< Hysteresis filtering, slope must exceed hysteresis to be counted! */ 308 | 309 | /* Woehler curve */ 310 | double wl_sx; /**< Sa of any point on the Woehler curve */ 311 | double wl_nx; /**< Cycles for Sa on the Woehler curve */ 312 | double wl_k; /**< Woehler slope, always negative */ 313 | 314 | /* Residue */ 315 | rfc_value_tuple_s *residue; /**< Buffer for residue */ 316 | size_t residue_cap; /**< Buffer capacity in number of elements (max. 2*class_count) */ 317 | size_t residue_cnt; /**< Number of value tuples in buffer */ 318 | 319 | /* Non-sparse storages (optional, may be NULL) */ 320 | rfc_counts_t *rfm; /**< Rainflow matrix, always class_count^2 elements (row-major, row=from, to=col). */ 321 | 322 | /* Damage */ 323 | double damage; /**< Cumulated damage (damage resulting from residue included) */ 324 | double damage_residue; /**< Partial damage in .damage influenced by taking residue into account (after finalizing) */ 325 | 326 | /* Internal usage */ 327 | struct internal 328 | { 329 | int flags; /**< Flags (enum rfc_flags) */ 330 | int slope; /**< Current signal slope */ 331 | rfc_value_tuple_s extrema[2]; /**< Local or global extrema depending on RFC_GLOBAL_EXTREMA */ 332 | size_t pos; /**< Absolute position in data input stream, base 1 */ 333 | size_t pos_offset; /**< Offset for pos */ 334 | rfc_value_tuple_s residue[3]; /**< Static residue (if class_count is zero) */ 335 | size_t residue_cap; /**< Capacity of static residue */ 336 | bool res_static; /**< true, if .residue refers the static residue .internal.residue */ 337 | } 338 | internal; 339 | }; 340 | 341 | #ifdef __cplusplus 342 | } /* extern "C" */ 343 | } /* namespace RFC_CPP_NAMESPACE */ 344 | #endif /*__cplusplus*/ 345 | 346 | #pragma pack(pop) 347 | 348 | #endif /*RAINFLOW_H*/ 349 | -------------------------------------------------------------------------------- /src/matlab/rfc.c: -------------------------------------------------------------------------------- 1 | #include "rainflow.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #define MAT_OFFS( i, j ) ( (i) * class_count + (j) ) 8 | #define CALLOC calloc 9 | #define REALLOC realloc 10 | #define FREE free 11 | 12 | static void * mem_alloc( void *ptr, size_t num, size_t size, int aim ); 13 | 14 | #if !RFC_MINIMAL 15 | #define RFC_MEX_USAGE \ 16 | "\nUsage:\n"\ 17 | "[pd,re,rm,rp,lc,tp,dh] = rfc( 'rfc', data, class_count, class_width, class_offset, hysteresis, residual_method, enforce_margin, use_hcm )\n"\ 18 | " pd = Pseudo damage\n"\ 19 | " re = Residue\n"\ 20 | " rm = Rainflow matrix (from/to)\n"\ 21 | " rp = Range pair counts\n"\ 22 | " lc = Level crossings\n"\ 23 | " tp = Turning points\n"\ 24 | " dh = Damage history\n"\ 25 | "\n"\ 26 | "[Sa] = rfc( 'amptransform', Sa, Sm, M, target, R_pinned )\n"\ 27 | " Sa = Amplitude\n"\ 28 | " Sm = Mean load\n"\ 29 | " M = Mean load sensitivity\n"\ 30 | " target = Mean load or mean load ratio (R)\n"\ 31 | " target_is_R = true, if target is R (otherwise target is test rig mean load)\n" 32 | #else /*RFC_MINIMAL*/ 33 | #define RFC_MEX_USAGE \ 34 | "\nUsage:\n"\ 35 | "[pd,re,rm] = rfc( data, class_count, class_width, class_offset, hysteresis )\n"\ 36 | " pd = Pseudo damage\n"\ 37 | " re = Residue\n"\ 38 | " rm = Rainflow matrix (from/to)\n" 39 | #endif /*!RFC_MINIMAL*/ 40 | #pragma message(RFC_MEX_USAGE) 41 | 42 | 43 | 44 | #if RFC_DEBUG_FLAGS 45 | static 46 | int rfc_vfprintf_fcn( void *ctx, FILE* stream, const char *fmt, va_list arg ) 47 | { 48 | int length; 49 | 50 | /* Get the buffer size needed */ 51 | length = vsnprintf( NULL, 0, fmt, arg ); 52 | if( length > 0 ) 53 | { 54 | char *buffer = CALLOC( ++length, 1 ); 55 | 56 | if( buffer ) 57 | { 58 | buffer[length-1] = 0; 59 | vsnprintf( buffer, length, fmt, arg ); 60 | mexPrintf( "%s", buffer ); 61 | 62 | FREE( buffer ); 63 | } 64 | } 65 | 66 | return length; 67 | } 68 | #endif /*RFC_DEBUG_FLAGS*/ 69 | 70 | /** 71 | * MATLAB wrapper for the rainflow algorithm 72 | */ 73 | static 74 | void mexRainflow( int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[] ) 75 | { 76 | #if !RFC_MINIMAL 77 | if( nrhs != 10 + 1 ) 78 | { 79 | mexErrMsgTxt( "Function needs exact 10 arguments!" ); 80 | #else /*RFC_MINIMAL*/ 81 | if( nrhs != 5 ) 82 | { 83 | if( !nrhs ) 84 | { 85 | mexPrintf( "%s", RFC_MEX_USAGE ); 86 | return; 87 | } 88 | mexErrMsgTxt( "Function needs exact 5 arguments!" ); 89 | 90 | #endif /*!RFC_MINIMAL*/ 91 | } 92 | else 93 | { 94 | rfc_ctx_s rfc_ctx = { sizeof(rfc_ctx_s) }; 95 | 96 | const mxArray *mxData = prhs[0]; 97 | const mxArray *mxClassCount = prhs[1]; 98 | const mxArray *mxClassWidth = prhs[2]; 99 | const mxArray *mxClassOffset = prhs[3]; 100 | const mxArray *mxHysteresis = prhs[4]; 101 | #if !RFC_MINIMAL 102 | const mxArray *mxResidualMethod = prhs[5]; 103 | const mxArray *mxEnforceMargin = prhs[6]; 104 | const mxArray *mxUseHCM = prhs[7]; 105 | const mxArray *mxUseASTM = prhs[8]; 106 | const mxArray *mxSpreadDamage = prhs[9]; 107 | const mxArray *mxAutoResize = prhs[10]; 108 | #endif /*!RFC_MINIMAL*/ 109 | 110 | rfc_value_t *buffer = NULL; 111 | double *data = mxGetPr( mxData ); 112 | size_t data_len = mxGetNumberOfElements( mxData ); 113 | unsigned class_count = (unsigned)( mxGetScalar( mxClassCount ) + 0.5 ); 114 | double class_width = mxGetScalar( mxClassWidth ); 115 | double class_offset = mxGetScalar( mxClassOffset ); 116 | double hysteresis = mxGetScalar( mxHysteresis ); 117 | #if !RFC_MINIMAL 118 | int residual_method = (int)( mxGetScalar( mxResidualMethod ) + 0.5 ); 119 | int enforce_margin = (int)mxGetScalar( mxEnforceMargin ); 120 | int use_hcm = (int)mxGetScalar( mxUseHCM ); 121 | int use_astm = (int)mxGetScalar( mxUseASTM ); 122 | int spread_damage = (int)mxGetScalar( mxSpreadDamage ); 123 | int auto_resize = (int)mxGetScalar( mxAutoResize ); 124 | #else /*RFC_MINIMAL*/ 125 | int residual_method = RFC_RES_NONE; 126 | #endif /*!RFC_MINIMAL*/ 127 | size_t i; 128 | bool ok; 129 | 130 | mxAssert( residual_method >= 0 && residual_method < RFC_RES_COUNT, 131 | "Invalid residual method!" ); 132 | 133 | #if !RFC_MINIMAL 134 | #if RFC_HCM_SUPPORT 135 | mxAssert( use_hcm == 0 || use_hcm == 1, 136 | "Invalid HCM flag, use 0 or 1!" ); 137 | #else /*!RFC_HCM_SUPPORT*/ 138 | mxAssert( use_hcm == 0, 139 | "HCM not supported!" ); 140 | #endif /*RFC_HCM_SUPPORT*/ 141 | 142 | #if RFC_ASTM_SUPPORT 143 | mxAssert( use_astm == 0 || use_astm == 1, 144 | "Invalid ASTM flag, use 0 or 1!" ); 145 | #if RFC_HCM_SUPPORT 146 | mxAssert( use_astm == 0 || use_hcm == 0, 147 | "Invalid ASTM flag, use either HCM or ASTM!" ); 148 | #endif /*RFC_HCM_SUPPORT*/ 149 | #else /*!RFC_ASTM_SUPPORT*/ 150 | mxAssert( use_astm == 0, 151 | "ASTM not supported!" ); 152 | #endif /*RFC_ASTM_SUPPORT*/ 153 | 154 | 155 | #if RFC_DH_SUPPORT 156 | mxAssert( spread_damage >= RFC_SD_NONE && spread_damage < RFC_SD_COUNT, 157 | "Invalid spread damage method!" ); 158 | #else /*!RFC_DH_SUPPORT*/ 159 | if( spread_damage != 0 ) 160 | { 161 | mexErrMsgTxt( "Invalid spread damage method, only 0 accepted!" ); 162 | } 163 | #endif /*RFC_DH_SUPPORT*/ 164 | 165 | #if RFC_AR_SUPPORT 166 | mxAssert( auto_resize == 0 || auto_resize == 1, 167 | "Invalid auto resize flag, use 0 or 1!" ); 168 | #else 169 | if( auto_resize != 0 ) 170 | { 171 | mexErrMsgTxt( "Invalid auto resize flag, only 0 accepted!" ); 172 | } 173 | #endif /*RFC_AR_SUPPORT*/ 174 | #endif /*!RFC_MINIMAL*/ 175 | 176 | ok = RFC_init( &rfc_ctx, 177 | class_count, (rfc_value_t)class_width, (rfc_value_t)class_offset, 178 | (rfc_value_t)hysteresis, RFC_FLAGS_DEFAULT ); 179 | if( !ok ) 180 | { 181 | RFC_deinit( &rfc_ctx ); 182 | mexErrMsgTxt( "Error during initialization!" ); 183 | } 184 | 185 | #if RFC_DEBUG_FLAGS 186 | rfc_ctx.debug_vfprintf_fcn = rfc_vfprintf_fcn; 187 | #endif /*RFC_DEBUG_FLAGS*/ 188 | 189 | #if RFC_TP_SUPPORT 190 | ok = RFC_tp_init( &rfc_ctx, /*tp*/ NULL, /*tp_cap*/ 128, /* is_static */ false ); 191 | 192 | if( !ok ) 193 | { 194 | RFC_deinit( &rfc_ctx ); 195 | mexErrMsgTxt( "Error during initialization (tp)!" ); 196 | } 197 | #endif /*RFC_TP_SUPPORT*/ 198 | 199 | /* Cast values from double type to rfc_value_t */ 200 | if( sizeof( rfc_value_t ) != sizeof(double) && data_len ) /* maybe unsafe! */ 201 | { 202 | buffer = (rfc_value_t *)mem_alloc( NULL, data_len, 203 | sizeof(rfc_value_t), RFC_MEM_AIM_TEMP ); 204 | 205 | if( !buffer ) 206 | { 207 | RFC_deinit( &rfc_ctx ); 208 | mexErrMsgTxt( "Error during initialization (memory)!" ); 209 | } 210 | 211 | for( i = 0; i < data_len; i++ ) 212 | { 213 | buffer[i] = (rfc_value_t)data[i]; 214 | } 215 | } 216 | else buffer = (rfc_value_t*)data; 217 | 218 | #if RFC_DH_SUPPORT 219 | if( spread_damage >= RFC_SD_TRANSIENT_23 ) 220 | { 221 | if( !RFC_dh_init( &rfc_ctx, spread_damage, /*dh*/ NULL, /*dh_cap*/ 1, /*is_static*/ false ) ) 222 | { 223 | ok = false; 224 | } 225 | } 226 | else 227 | { 228 | if( !RFC_dh_init( &rfc_ctx, spread_damage, /*dh*/ NULL, /*dh_cap*/ 0, /*is_static*/ true ) ) 229 | { 230 | ok = false; 231 | } 232 | } 233 | 234 | #endif /*RFC_DH_SUPPORT*/ 235 | 236 | #if RFC_AR_SUPPORT 237 | if( auto_resize ) 238 | { 239 | RFC_flags_set( &rfc_ctx, RFC_FLAGS_AUTORESIZE, /* stack */ 0, /* overwrite */ false ); 240 | } 241 | #endif /*RFC_AR_SUPPORT*/ 242 | 243 | /* Rainflow counting */ 244 | 245 | #if !RFC_MINIMAL 246 | /* Setup */ 247 | rfc_ctx.internal.flags |= enforce_margin ? RFC_FLAGS_ENFORCE_MARGIN : 0; 248 | #endif /*!RFC_MINIMAL*/ 249 | 250 | #if RFC_HCM_SUPPORT && RFC_ASTM_SUPPORT 251 | if( use_hcm ) rfc_ctx.counting_method = RFC_COUNTING_METHOD_HCM; 252 | else if( use_astm ) rfc_ctx.counting_method = RFC_COUNTING_METHOD_ASTM; 253 | else rfc_ctx.counting_method = RFC_COUNTING_METHOD_4PTM; 254 | #elif RFC_HCM_SUPPORT 255 | if( use_hcm ) rfc_ctx.counting_method = RFC_COUNTING_METHOD_HCM; 256 | else rfc_ctx.counting_method = RFC_COUNTING_METHOD_4PTM; 257 | #elif RFC_ASTM_SUPPORT 258 | if( use_astm ) rfc_ctx.counting_method = RFC_COUNTING_METHOD_ASTM; 259 | else rfc_ctx.counting_method = RFC_COUNTING_METHOD_4PTM; 260 | #else /*!(RFC_HCM_SUPPORT || RFC_ASTM_SUPPORT)*/ 261 | #if !RFC_MINIMAL 262 | rfc_ctx.counting_method = RFC_COUNTING_METHOD_4PTM; 263 | #endif /*!RFC_MINIMAL*/ 264 | #endif /*(RFC_HCM_SUPPORT || RFC_ASTM_SUPPORT)*/ 265 | 266 | ok = RFC_feed( &rfc_ctx, buffer, data_len ) && 267 | RFC_finalize( &rfc_ctx, residual_method ); 268 | 269 | /* Free temporary buffer (cast) */ 270 | if( (void*)buffer != (void*)data ) 271 | { 272 | buffer = mem_alloc( buffer, 0, 0, RFC_MEM_AIM_TEMP ); 273 | } 274 | 275 | if( !ok ) 276 | { 277 | int error = rfc_ctx.error; 278 | 279 | RFC_deinit( &rfc_ctx ); 280 | switch( error ) 281 | { 282 | case RFC_ERROR_INVARG: 283 | mexErrMsgTxt( "Invalid argument(s)!" ); 284 | case RFC_ERROR_MEMORY: 285 | mexErrMsgTxt( "Error during memory allocation!" ); 286 | #if RFC_AT_SUPPORT 287 | case RFC_ERROR_AT: 288 | mexErrMsgTxt( "Error during amplitude transformation!" ); 289 | #endif /*RFC_AT_SUPPORT*/ 290 | #if RFC_TP_SUPPORT 291 | case RFC_ERROR_TP: 292 | mexErrMsgTxt( "Error during turning point access!" ); 293 | #endif /*RFC_TP_SUPPORT*/ 294 | #if RFC_DAMAGE_FAST 295 | case RFC_ERROR_LUT: 296 | mexErrMsgTxt( "Error during lookup table access!" ); 297 | #endif /*RFC_DAMAGE_FAST*/ 298 | case RFC_ERROR_UNEXP: 299 | default: 300 | mexErrMsgTxt( "Unexpected error occurred!" ); 301 | } 302 | } 303 | 304 | /* Return results */ 305 | if( plhs ) 306 | { 307 | /* Damage */ 308 | plhs[0] = mxCreateDoubleScalar( rfc_ctx.damage ); 309 | 310 | /* Residue */ 311 | #if RFC_HCM_SUPPORT 312 | if( use_hcm ) 313 | { 314 | if( nlhs > 1 && rfc_ctx.internal.hcm.stack ) 315 | { 316 | mxArray* re = mxCreateDoubleMatrix( rfc_ctx.internal.hcm.IZ, 1, mxREAL ); 317 | if( re ) 318 | { 319 | int i; 320 | double *val = mxGetPr(re); 321 | 322 | for( i = 0; i < rfc_ctx.internal.hcm.IZ; i++ ) 323 | { 324 | *val++ = (double)rfc_ctx.internal.hcm.stack[i].value; 325 | } 326 | plhs[1] = re; 327 | } 328 | } 329 | } 330 | else 331 | #endif /*!RFC_HCM_SUPPORT*/ 332 | { 333 | if( nlhs > 1 && rfc_ctx.residue ) 334 | { 335 | mxArray* re = mxCreateDoubleMatrix( rfc_ctx.residue_cnt, 1, mxREAL ); 336 | if( re ) 337 | { 338 | size_t i; 339 | double *val = mxGetPr(re); 340 | 341 | for( i = 0; i < rfc_ctx.residue_cnt; i++ ) 342 | { 343 | *val++ = (double)rfc_ctx.residue[i].value; 344 | } 345 | plhs[1] = re; 346 | } 347 | } 348 | } 349 | 350 | /* Rainflow matrix (column major order) */ 351 | if( nlhs > 2 && rfc_ctx.rfm ) 352 | { 353 | mxArray* rfm = mxCreateDoubleMatrix( class_count, class_count, mxREAL ); 354 | if( rfm ) 355 | { 356 | double *ptr = mxGetPr(rfm); 357 | size_t from, to; 358 | for( to = 0; to < class_count; to++ ) 359 | { 360 | for( from = 0; from < class_count; from++ ) 361 | { 362 | *ptr++ = (double)rfc_ctx.rfm[ MAT_OFFS( from, to ) ] / rfc_ctx.full_inc; 363 | } 364 | } 365 | plhs[2] = rfm; 366 | } 367 | } 368 | 369 | #if !RFC_MINIMAL 370 | /* Range pair */ 371 | if( nlhs > 3 && rfc_ctx.rp ) 372 | { 373 | mxArray* rp = mxCreateDoubleMatrix( class_count, 1, mxREAL ); 374 | if( rp ) 375 | { 376 | double *ptr = mxGetPr(rp); 377 | size_t i; 378 | for( i = 0; i < class_count; i++ ) 379 | { 380 | *ptr++ = (double)rfc_ctx.rp[i] / rfc_ctx.curr_inc; 381 | } 382 | plhs[3] = rp; 383 | } 384 | } 385 | 386 | /* Level crossing */ 387 | if( nlhs > 4 && rfc_ctx.lc ) 388 | { 389 | mxArray* lc = mxCreateDoubleMatrix( class_count, 1, mxREAL ); 390 | if( lc ) 391 | { 392 | double *ptr = mxGetPr(lc); 393 | size_t i; 394 | for( i = 0; i < class_count; i++ ) 395 | { 396 | *ptr++ = (double)rfc_ctx.lc[i]; 397 | } 398 | plhs[4] = lc; 399 | } 400 | } 401 | #if RFC_TP_SUPPORT 402 | /* Turning points */ 403 | if( nlhs > 5 && rfc_ctx.tp ) 404 | { 405 | #if RFC_DH_SUPPORT 406 | mxArray *tp = mxCreateDoubleMatrix( rfc_ctx.tp_cnt, 3, mxREAL ); 407 | double *dam = tp ? ( mxGetPr(tp) + 2 * rfc_ctx.tp_cnt ) : NULL; 408 | #else /*!RFC_DH_SUPPORT*/ 409 | mxArray* tp = mxCreateDoubleMatrix( rfc_ctx.tp_cnt, 2, mxREAL ); 410 | #endif /*RFC_DH_SUPPORT*/ 411 | 412 | if( tp ) 413 | { 414 | size_t i; 415 | double *idx = mxGetPr(tp) + 0 * rfc_ctx.tp_cnt; 416 | double *val = mxGetPr(tp) + 1 * rfc_ctx.tp_cnt; 417 | double D = 0.0; 418 | 419 | for( i = 0; i < rfc_ctx.tp_cnt; i++ ) 420 | { 421 | *val++ = (double)rfc_ctx.tp[i].value; 422 | *idx++ = (double)rfc_ctx.tp[i].pos; 423 | #if RFC_DH_SUPPORT 424 | *dam++ = (double)rfc_ctx.tp[i].damage; 425 | D += (double)rfc_ctx.tp[i].damage; 426 | #endif /*RFC_DH_SUPPORT*/ 427 | } 428 | /* assert( D == rfc_ctx.damage ); */ 429 | plhs[5] = tp; 430 | } 431 | } 432 | #endif /*RFC_TP_SUPPORT*/ 433 | #if RFC_DH_SUPPORT 434 | /* Turning points */ 435 | if( nlhs > 6 ) 436 | { 437 | if( rfc_ctx.dh ) 438 | { 439 | mxArray *dh = mxCreateDoubleMatrix( rfc_ctx.internal.pos, 1, mxREAL ); 440 | double *dh_ptr = dh ? mxGetPr(dh) : NULL; 441 | 442 | if( dh_ptr ) 443 | { 444 | size_t i; 445 | 446 | for( i = 0; i < rfc_ctx.internal.pos; i++ ) 447 | { 448 | *dh_ptr++ = rfc_ctx.dh[i]; 449 | } 450 | } 451 | 452 | plhs[6] = dh; 453 | } 454 | else 455 | { 456 | plhs[6] = mxCreateDoubleMatrix( 0, 0, mxREAL ); 457 | } 458 | } 459 | #endif /*RFC_DH_SUPPORT*/ 460 | #endif /*!RFC_MINIMAL*/ 461 | } 462 | 463 | /* Deinitialize rainflow context */ 464 | RFC_deinit( &rfc_ctx ); 465 | } 466 | } 467 | 468 | 469 | #if RFC_AT_SUPPORT 470 | /** 471 | * MATLAB wrapper for the amplitude transformation 472 | */ 473 | static 474 | void mexAmpTransform( int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[] ) 475 | { 476 | if( nrhs != 5 ) 477 | { 478 | mexErrMsgTxt( "Function needs exact 5 arguments!" ); 479 | } 480 | else 481 | { 482 | mwSize n, cnt; 483 | mxArray *mxResult = NULL; 484 | 485 | rfc_ctx_s ctx = { sizeof(ctx) }; 486 | 487 | const mxArray *mxSa = prhs[0]; 488 | const mxArray *mxSm = prhs[1]; 489 | const mxArray *mxM = prhs[2]; 490 | const mxArray *mxTarget = prhs[3]; 491 | const mxArray *mxTarget_is_R = prhs[4]; 492 | 493 | double M = mxGetScalar( mxM ); 494 | double target = mxGetScalar( mxTarget ); 495 | bool target_is_R = (int)mxGetScalar( mxTarget_is_R ); 496 | 497 | cnt = mxGetNumberOfElements( mxSa ); 498 | mxAssert( mxGetNumberOfElements( mxSm ) == cnt, "Sa and Sm must have same length!" ); 499 | 500 | mxResult = mxCreateDoubleMatrix( mxGetDimensions( mxSa )[0], mxGetDimensions( mxSa )[1], mxREAL ); 501 | mxAssert( mxResult, "Memory error!" ); 502 | 503 | mxAssert( RFC_init( &ctx, 0 /*class_count*/, 0.0 /*class_width*/, 0.0 /*class_offset*/, 0.0 /*hysteresis*/, RFC_FLAGS_DEFAULT ), 504 | "RFC initialization error!" ); 505 | mxAssert( RFC_at_init( &ctx, NULL /*Sa*/, NULL /*Sm*/, 0 /*count*/, M, target /*Sm_rig*/, target /*R_rig*/, target_is_R, false /*symmetric*/ ), 506 | "RFC initialization error!" ); 507 | 508 | for( n = 0; n < cnt; n++ ) 509 | { 510 | double Sa_n = mxGetPr( mxSa )[n]; 511 | double Sm_n = mxGetPr( mxSm )[n]; 512 | double Sa_t; 513 | 514 | RFC_at_transform( &ctx, Sa_n, Sm_n, &Sa_t ); 515 | mxGetPr( mxResult )[n] = Sa_t; 516 | } 517 | 518 | plhs[0] = mxResult; 519 | } 520 | } 521 | #endif /*RFC_AT_SUPPORT*/ 522 | 523 | 524 | #if RFC_TP_SUPPORT 525 | /** 526 | * MATLAB wrapper calculates turning points from data points 527 | */ 528 | static 529 | void mexTP( int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[] ) 530 | { 531 | if( nrhs != 3 ) 532 | { 533 | mexErrMsgTxt( "Function needs exact 3 arguments!" ); 534 | } 535 | else 536 | { 537 | rfc_value_tuple_s *tp = NULL; 538 | mxArray *mxResult = NULL; 539 | double *ptr; 540 | rfc_ctx_s ctx = { sizeof(ctx) }; 541 | mwSize n; 542 | bool ok = true; 543 | 544 | const mxArray *mxData = prhs[0]; 545 | const mxArray *mxHysteresis = prhs[1]; 546 | const mxArray *mxEnforceMargin = prhs[2]; 547 | 548 | double hysteresis = mxGetScalar( mxHysteresis ); 549 | bool enforce_margin = (int)mxGetScalar( mxEnforceMargin ); 550 | 551 | tp = (rfc_value_tuple_s*)CALLOC( mxGetNumberOfElements( mxData ), sizeof(rfc_value_tuple_s) ); 552 | if( !tp ) 553 | { 554 | mexErrMsgTxt( "Memory allocation error!" ); 555 | } 556 | 557 | if( !RFC_init( &ctx, 0 /*class_count*/, 0.0 /*class_width*/, 0.0 /*class_offset*/, hysteresis, RFC_FLAGS_DEFAULT ) ) 558 | { 559 | FREE(tp); 560 | mexErrMsgTxt( "Error on RFC init!" ); 561 | } 562 | 563 | if( !RFC_tp_init( &ctx, tp, mxGetNumberOfElements( mxData ), true /*tp_is_static*/ ) ) 564 | { 565 | FREE(tp); 566 | mexErrMsgTxt( "Error on RFC tp init!" ); 567 | } 568 | 569 | if( !RFC_feed( &ctx, mxGetPr( mxData ), mxGetNumberOfElements( mxData ) ) ) 570 | { 571 | FREE(tp); 572 | mexErrMsgTxt( "Error on RFC feed!" ); 573 | } 574 | 575 | /*if( !finalize_res_ignore( &ctx, ctx.internal.flags ) )*/ 576 | if( !RFC_finalize( &ctx, RFC_RES_IGNORE ) ) 577 | { 578 | FREE(tp); 579 | mexErrMsgTxt( "Error on RFC finalize!" ); 580 | } 581 | 582 | mxResult = mxCreateDoubleMatrix( 2, ctx.tp_cnt, mxREAL ); 583 | mxAssert( mxResult, "Memory allocation error!" ); 584 | for( ptr = mxGetPr( mxResult ), n = 0; n < ctx.tp_cnt; n++ ) 585 | { 586 | *ptr++ = tp[n].value; 587 | *ptr++ = (double)tp[n].pos; 588 | } 589 | FREE( tp ); 590 | 591 | if( !RFC_deinit( &ctx ) ) 592 | { 593 | mexErrMsgTxt( "Error on RFC deinit!" ); 594 | } 595 | 596 | plhs[0] = mxResult; 597 | } 598 | } 599 | #endif /*RFC_TP_SUPPORT*/ 600 | 601 | 602 | #if !RFC_MINIMAL 603 | /** 604 | * @brief Compare two string case insensitive 605 | * 606 | * @param[in] a First string 607 | * @param[in] b Second string 608 | * 609 | * @return 0 on equality 610 | */ 611 | static 612 | int wal_stricmp( const char *a, const char *b ) 613 | { 614 | int ca, cb; 615 | do 616 | { 617 | ca = (unsigned char) *a++; 618 | cb = (unsigned char) *b++; 619 | ca = tolower( toupper(ca) ); 620 | cb = tolower( toupper(cb) ); 621 | } 622 | while( ca == cb && ca != '\0' ); 623 | 624 | return ca - cb; 625 | } 626 | 627 | 628 | /** 629 | * The MATLAB MEX main function 630 | */ 631 | void mexFunction( int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[] ) 632 | { 633 | char buffer[80]; 634 | 635 | if( !nrhs || !mxIsChar( prhs[0] ) || 0 != mxGetString( prhs[0], buffer, sizeof(buffer) ) ) 636 | { 637 | mexPrintf( "%s", RFC_MEX_USAGE ); 638 | return; 639 | } 640 | 641 | if( 0 == wal_stricmp( buffer, "rfc" ) ) 642 | { 643 | mexRainflow( nlhs, plhs, nrhs - 1, prhs + 1 ); 644 | } 645 | #if RFC_AT_SUPPORT 646 | else if( 0 == wal_stricmp( buffer, "amptransform" ) ) 647 | { 648 | mexAmpTransform( nlhs, plhs, nrhs - 1, prhs + 1 ); 649 | } 650 | #endif /*RFC_AT_SUPPORT*/ 651 | #if RFC_TP_SUPPORT 652 | else if( 0 == wal_stricmp( buffer, "turningpoints" ) ) 653 | { 654 | mexTP( nlhs, plhs, nrhs - 1, prhs + 1 ); 655 | } 656 | #endif /*RFC_TP_SUPPORT*/ 657 | else 658 | { 659 | mexPrintf( "Unknown subfunction \"%s\"!\n", buffer ); 660 | } 661 | } 662 | #else /*RFC_MINIMAL*/ 663 | /** 664 | * The MATLAB MEX main function 665 | */ 666 | void mexFunction( int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[] ) 667 | { 668 | mexRainflow( nlhs, plhs, nrhs, prhs ); 669 | } 670 | #endif /*!RFC_MINIMAL*/ 671 | 672 | 673 | /** 674 | * @brief (Re-)Allocate or free memory 675 | * 676 | * @param ptr Previous data pointer, or NULL, if unset 677 | * @param num The number of elements 678 | * @param size The size of one element in bytes 679 | * @param aim The aim 680 | * 681 | * @return New memory pointer or NULL if either num or size is 0 682 | */ 683 | static 684 | void * mem_alloc( void *ptr, size_t num, size_t size, int aim ) 685 | { 686 | if( !num || !size ) 687 | { 688 | if( ptr ) 689 | { 690 | FREE( ptr ); 691 | } 692 | return NULL; 693 | } 694 | else 695 | { 696 | return ptr ? REALLOC( ptr, num * size ) : CALLOC( num, size ); 697 | } 698 | } 699 | -------------------------------------------------------------------------------- /src/lib/minimal/rainflow.c: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * | .-. 4 | * | / \ 5 | * | .-.===========/ \ .-. 6 | * | / \ / \ / \ 7 | * | / \ / \ / \ .-. 8 | * +--/-------\-----/-----------\---/-------\-------/---\ 9 | * | / \ / '-'=========\ / \ / 10 | * |/ '-' \ / '-' 11 | * | '-' 12 | * ____ ___ _____ __________ ____ _ __ 13 | * / __ \/ | / _/ | / / ____/ / / __ \ | / / 14 | * / /_/ / /| | / // |/ / /_ / / / / / / | /| / / 15 | * / _, _/ ___ |_/ // /| / __/ / /___/ /_/ /| |/ |/ / 16 | * /_/ |_/_/ |_/___/_/ |_/_/ /_____/\____/ |__/|__/ 17 | * 18 | * Rainflow Counting Algorithm (4-point-method), C99 compliant 19 | * 20 | * 21 | * "Rainflow Counting" consists of four main steps: 22 | * 1. Hysteresis Filtering 23 | * 2. Peak-Valley Filtering 24 | * 3. Discretization 25 | * 4. Four Point Counting Method: 26 | * 27 | * * D 28 | * / \ Closed, if min(B,C) >= min(A,D) && max(B,C) <= max(A,D) 29 | * B *<--/ Slope B-C is counted and removed from residue 30 | * / \ / 31 | * / * C 32 | * \ / 33 | * * A 34 | * 35 | * These steps are fully documented in standards such as 36 | * ASTM E1049 "Standard Practices for Cycle Counting in Fatigue Analysis" [1]. 37 | * This implementation uses the 4-point algorithm mentioned in [3,4] and the 3-point HCM method proposed in [2]. 38 | * To take the residue into account, you may implement a custom method or use some 39 | * predefined functions. 40 | * 41 | * References: 42 | * [1] "Standard Practices for Cycle Counting in Fatigue Analysis." 43 | * ASTM Standard E 1049, 1985 (2011). 44 | * West Conshohocken, PA: ASTM International, 2011. 45 | * [2] "Rainflow - HCM / Ein Hysteresisschleifen-Zaehlalgorithmus auf werkstoffmechanischer Grundlage" 46 | * U.H. Clormann, T. Seeger 47 | * 1985 TU Darmstadt, Fachgebiet Werkstoffmechanik 48 | * [3] "Zaehlverfahren zur Bildung von Kollektiven und Matrizen aus Zeitfunktionen" 49 | * FVA-Richtlinie, 2010. 50 | * [https://fva-net.de/fileadmin/content/Richtlinien/FVA-Richtlinie_Zaehlverfahren_2010.pdf] 51 | * [4] Siemens Product Lifecycle Management Software Inc., 2018. 52 | * [https://community.plm.automation.siemens.com/t5/Testing-Knowledge-Base/Rainflow-Counting/ta-p/383093] 53 | * [5] "Review and application of Rainflow residue processing techniques for accurate fatigue damage estimation" 54 | * G.Marsh; 55 | * International Journal of Fatigue 82 (2016) 757-765, 56 | * [https://doi.org/10.1016/j.ijfatigue.2015.10.007] 57 | * [6] "Betriebsfestigkeit - Verfahren und Daten zur Bauteilberechnung" 58 | * Haibach, Erwin; Springer Verlag 59 | * [] "Schaedigungsbasierte Hysteresefilter"; Hack, M, D386 (Diss Univ. Kaiserslautern), Shaker Verlag Aachen, 1998, ISBN 3-8265-3936-2 60 | * [] "Hysteresis and Phase Transition" 61 | * Brokate, M.; Sprekels, J.; Applied Mathematical Sciences 121; Springer, New York, 1996 62 | * [] "Rainflow counting and energy dissipation in elastoplasticity"; Eur. J. Mech. A/Solids 15, pp. 705-737, 1996 63 | * Brokate, M.; Dressler, K.; Krejci, P. 64 | * [] "Multivariate Density Estimation: Theory, Practice and Visualization". New York, Chichester, Wiley & Sons, 1992 65 | * Scott, D. 66 | * [] "Werkstoffmechanik - Bauteile sicher beurteilen undWerkstoffe richtig einsetzen"; 67 | * Ralf Buergel, Hans Albert Richard, Andre Riemer; Springer FachmedienWiesbaden 2005, 2014 68 | * [] "Zaehlverfahren und Lastannahme in der Betriebsfestigkeit"; 69 | * Michael Koehler, Sven Jenne / Kurt Poetter, Harald Zenner; Springer-Verlag Berlin Heidelberg 2012 70 | * 71 | * 72 | *================================================================================ 73 | * BSD 2-Clause License 74 | * 75 | * Copyright (c) 2023, Andras Martin 76 | * All rights reserved. 77 | * 78 | * Redistribution and use in source and binary forms, with or without 79 | * modification, are permitted provided that the following conditions are met: 80 | * 81 | * * Redistributions of source code must retain the above copyright notice, this 82 | * list of conditions and the following disclaimer. 83 | * 84 | * * Redistributions in binary form must reproduce the above copyright notice, 85 | * this list of conditions and the following disclaimer in the documentation 86 | * and/or other materials provided with the distribution. 87 | * 88 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 89 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 90 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 91 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 92 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 93 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 94 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 95 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 96 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 97 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 98 | *================================================================================ 99 | */ 100 | 101 | #if COAN_INVOKED 102 | /* This version is generated via coan (http://coan2.sourceforge.net/) */ 103 | #endif /*COAN_INVOKED*/ 104 | 105 | 106 | #include "rainflow.h" 107 | 108 | #include /* assert() */ 109 | #include /* exp(), log(), fabs() */ 110 | #include /* calloc(), free(), abs() */ 111 | #include /* memset() */ 112 | #include /* DBL_MAX */ 113 | 114 | static char* __rfc_core_version__ = RFC_CORE_VERSION; 115 | 116 | #ifndef CALLOC 117 | #define CALLOC calloc 118 | #endif 119 | #ifndef REALLOC 120 | #define REALLOC realloc 121 | #endif 122 | #ifndef FREE 123 | #define FREE free 124 | #endif 125 | 126 | 127 | /* Core functions */ 128 | #define cycle_find cycle_find_4ptm 129 | static bool feed_once ( rfc_ctx_s *, const rfc_value_tuple_s* tp, rfc_flags_e flags ); 130 | static bool feed_finalize ( rfc_ctx_s * ); 131 | static rfc_value_tuple_s * feed_filter_pt ( rfc_ctx_s *, const rfc_value_tuple_s *pt ); 132 | static void cycle_find_4ptm ( rfc_ctx_s *, rfc_flags_e flags ); 133 | static void cycle_process_counts ( rfc_ctx_s *, rfc_value_tuple_s *from, rfc_value_tuple_s *to, rfc_value_tuple_s *next, rfc_flags_e flags ); 134 | /* Methods on residue */ 135 | static bool finalize_res_ignore ( rfc_ctx_s *, rfc_flags_e flags ); 136 | static bool finalize_res_no_finalize ( rfc_ctx_s *, rfc_flags_e flags ); 137 | static void residue_remove_item ( rfc_ctx_s *, size_t index, size_t count ); 138 | /* Memory allocator */ 139 | static void * mem_alloc ( void *ptr, size_t num, size_t size, int aim ); 140 | /* Other */ 141 | static bool damage_calc_amplitude ( rfc_ctx_s *, double Sa, double *damage ); 142 | static bool damage_calc ( rfc_ctx_s *, unsigned class_from, unsigned class_to, double *damage, double *Sa_ret ); 143 | static bool error_raise ( rfc_ctx_s *, rfc_error_e ); 144 | static rfc_value_t value_delta ( rfc_ctx_s *, const rfc_value_tuple_s* pt_from, const rfc_value_tuple_s* pt_to, int *sign_ptr ); 145 | 146 | 147 | #define QUANTIZE( r, v ) ( (r)->class_count ? (unsigned)( ((v) - (r)->class_offset) / (r)->class_width ) : 0 ) 148 | #define AMPLITUDE( r, i ) ( (r)->class_count ? ( (double)(r)->class_width * (i) / 2 ) : 0.0 ) 149 | #define CLASS_MEAN( r, c ) ( (r)->class_count ? ( (double)(r)->class_width * (0.5 + (c)) + (r)->class_offset ) : 0.0 ) 150 | #define CLASS_UPPER( r, c ) ( (r)->class_count ? ( (double)(r)->class_width * (1.0 + (c)) + (r)->class_offset ) : 0.0 ) 151 | #define NUMEL( x ) ( sizeof(x) / sizeof(*(x)) ) 152 | #define MAT_OFFS( i, j ) ( (i) * class_count + (j) ) 153 | 154 | #define RFC_CTX_CHECK_AND_ASSIGN \ 155 | rfc_ctx_s *rfc_ctx = (rfc_ctx_s*)ctx; \ 156 | \ 157 | if( !rfc_ctx || rfc_ctx->version != sizeof(rfc_ctx_s) ) \ 158 | { \ 159 | return error_raise( rfc_ctx, RFC_ERROR_INVARG ); \ 160 | } \ 161 | 162 | 163 | 164 | /** 165 | * @brief Initialization (rainflow context). 166 | * 167 | * @param ctx The rainflow context 168 | * @param class_count The class count 169 | * @param class_width The class width 170 | * @param class_offset The class offset 171 | * @param hysteresis The hysteresis 172 | * @param flags The flags 173 | * 174 | * @return true on success 175 | */ 176 | bool RFC_init( void *ctx, unsigned class_count, rfc_value_t class_width, rfc_value_t class_offset, 177 | rfc_value_t hysteresis, rfc_flags_e flags ) 178 | { 179 | rfc_value_tuple_s nil = { 0.0 }; /* All other members are zero-initialized, see ISO/IEC 9899:TC3, 6.7.8 (21) */ 180 | RFC_CTX_CHECK_AND_ASSIGN 181 | 182 | if( rfc_ctx->state != RFC_STATE_INIT0 ) 183 | { 184 | return false; 185 | } 186 | 187 | /* Flags */ 188 | if( flags == RFC_FLAGS_DEFAULT ) 189 | { 190 | flags = RFC_FLAGS_COUNT_RFM | 191 | RFC_FLAGS_COUNT_DAMAGE; 192 | } 193 | rfc_ctx->internal.flags = flags; 194 | 195 | /* Counter increments */ 196 | rfc_ctx->full_inc = RFC_FULL_CYCLE_INCREMENT; 197 | rfc_ctx->half_inc = RFC_HALF_CYCLE_INCREMENT; 198 | rfc_ctx->curr_inc = RFC_FULL_CYCLE_INCREMENT; 199 | 200 | if( class_count ) 201 | { 202 | if( class_count > RFC_CLASS_COUNT_MAX || class_width <= 0.0 ) 203 | { 204 | return error_raise( rfc_ctx, RFC_ERROR_INVARG ); 205 | } 206 | } 207 | else 208 | { 209 | class_width = 1.0; 210 | class_offset = 0.0; 211 | } 212 | 213 | /* Rainflow class parameters */ 214 | rfc_ctx->class_count = class_count; 215 | rfc_ctx->class_width = class_width; 216 | rfc_ctx->class_offset = class_offset; 217 | rfc_ctx->hysteresis = hysteresis; 218 | 219 | /* Values for a "pseudo Woehler curve" */ 220 | rfc_ctx->state = RFC_STATE_INIT; /* Bypass sanity check for state in wl_init() */ 221 | RFC_wl_init_elementary( rfc_ctx, /*sx*/ RFC_WL_SD_DEFAULT, /*nx*/ RFC_WL_ND_DEFAULT, /*k*/ RFC_WL_K_DEFAULT ); 222 | rfc_ctx->state = RFC_STATE_INIT0; /* Reset state */ 223 | 224 | /* Memory allocator */ 225 | if( !rfc_ctx->mem_alloc ) 226 | { 227 | rfc_ctx->mem_alloc = mem_alloc; 228 | } 229 | 230 | /* Residue */ 231 | rfc_ctx->internal.residue_cap = NUMEL( rfc_ctx->internal.residue ); 232 | rfc_ctx->residue_cnt = 0; 233 | rfc_ctx->residue_cap = 2 * rfc_ctx->class_count + 1; /* 4pt-method fills max 2*n-2 (+1 candidate), +2 extra points ("enforce margin", "interim point") = 2*n+1 */ 234 | 235 | if( rfc_ctx->residue_cap <= rfc_ctx->internal.residue_cap ) 236 | { 237 | rfc_ctx->residue_cap = rfc_ctx->internal.residue_cap; /* At least 3 elements are needed (two to define a slope and one as interim point) */ 238 | rfc_ctx->residue = rfc_ctx->internal.residue; 239 | rfc_ctx->internal.res_static = true; 240 | } 241 | else 242 | { 243 | rfc_ctx->residue = (rfc_value_tuple_s*)rfc_ctx->mem_alloc( NULL, rfc_ctx->residue_cap, 244 | sizeof(rfc_value_tuple_s), RFC_MEM_AIM_RESIDUE ); 245 | rfc_ctx->internal.res_static = false; 246 | } 247 | 248 | if( rfc_ctx->class_count ) 249 | { 250 | int ok = rfc_ctx->residue != NULL; 251 | 252 | if( ok && ( flags & RFC_FLAGS_COUNT_RFM ) ) 253 | { 254 | /* Non-sparse storages (optional, may be NULL) */ 255 | rfc_ctx->rfm = (rfc_counts_t*)rfc_ctx->mem_alloc( NULL, class_count * class_count, 256 | sizeof(rfc_counts_t), RFC_MEM_AIM_MATRIX ); 257 | if( !rfc_ctx->rfm ) ok = false; 258 | } 259 | if( !ok ) 260 | { 261 | RFC_deinit( rfc_ctx ); 262 | return error_raise( rfc_ctx, RFC_ERROR_INVARG ); 263 | } 264 | } 265 | 266 | /* Damage */ 267 | rfc_ctx->damage = 0.0; 268 | rfc_ctx->damage_residue = 0.0; 269 | 270 | /* Internals */ 271 | rfc_ctx->internal.slope = 0; 272 | rfc_ctx->internal.extrema[0] = nil; /* local minimum */ 273 | rfc_ctx->internal.extrema[1] = nil; /* local maximum */ 274 | 275 | 276 | rfc_ctx->state = RFC_STATE_INIT; 277 | 278 | return true; 279 | } 280 | 281 | /** 282 | * @brief Return state 283 | * 284 | * @param ctx The rfc context 285 | * 286 | * @return state 287 | */ 288 | rfc_state_e RFC_state_get( const void *ctx ) 289 | { 290 | /* RFC_CTX_CHECK_AND_ASSIGN */ 291 | rfc_ctx_s *rfc_ctx = (rfc_ctx_s*)ctx; 292 | 293 | if( !rfc_ctx || rfc_ctx->version != sizeof(rfc_ctx_s) ) 294 | { 295 | return error_raise( rfc_ctx, RFC_ERROR_INVARG ); 296 | } 297 | 298 | return rfc_ctx->state; 299 | } 300 | 301 | 302 | /** 303 | * @brief Return error 304 | * 305 | * @param ctx The rfc context 306 | * 307 | * @return error 308 | */ 309 | rfc_error_e RFC_error_get( const void *ctx ) 310 | { 311 | RFC_CTX_CHECK_AND_ASSIGN 312 | 313 | return rfc_ctx->error; 314 | } 315 | 316 | 317 | /** 318 | * @brief Initialize Woehler parameters to Miners' elementary rule 319 | * 320 | * @param ctx The rfc context 321 | * @param sx The amplitude "SA" 322 | * @param nx The cycles "N" according to Sa 323 | * @param k The slope "k" 324 | * 325 | * @return true on success 326 | */ 327 | bool RFC_wl_init_elementary( void *ctx, double sx, double nx, double k ) 328 | { 329 | RFC_CTX_CHECK_AND_ASSIGN 330 | 331 | if( rfc_ctx->state != RFC_STATE_INIT ) 332 | { 333 | return false; 334 | } 335 | 336 | /* Woehler curve */ 337 | rfc_ctx->wl_sx = sx; 338 | rfc_ctx->wl_nx = nx; 339 | rfc_ctx->wl_k = -fabs(k); 340 | 341 | return true; 342 | } 343 | 344 | 345 | /** 346 | * @brief Returns the residuum 347 | * 348 | * @param ctx The rainflow context 349 | * @param[out] residue The residue (last point is interim, if its tp_pos is zero) 350 | * @param[out] count The residue count 351 | * 352 | * @return true on success 353 | */ 354 | bool RFC_res_get( const void *ctx, const rfc_value_tuple_s **residue, unsigned *count ) 355 | { 356 | RFC_CTX_CHECK_AND_ASSIGN 357 | 358 | if( rfc_ctx->state < RFC_STATE_INIT ) 359 | { 360 | return false; 361 | } 362 | 363 | if( residue ) 364 | { 365 | *residue = rfc_ctx->residue; 366 | } 367 | 368 | if( count ) 369 | { 370 | *count = (unsigned)rfc_ctx->residue_cnt + (rfc_ctx->state == RFC_STATE_BUSY_INTERIM); 371 | } 372 | 373 | return true; 374 | } 375 | 376 | 377 | /** 378 | * @brief De-initialization (freeing memory). 379 | * 380 | * @param ctx The rainflow context 381 | * 382 | * @return true on success 383 | */ 384 | bool RFC_deinit( void *ctx ) 385 | { 386 | rfc_value_tuple_s nil = { 0.0 }; /* All other members are zero-initialized, see ISO/IEC 9899:TC3, 6.7.8 (21) */ 387 | RFC_CTX_CHECK_AND_ASSIGN 388 | 389 | if( rfc_ctx->state < RFC_STATE_INIT ) 390 | { 391 | return false; 392 | } 393 | 394 | if( !rfc_ctx->internal.res_static && 395 | rfc_ctx->residue ) rfc_ctx->mem_alloc( rfc_ctx->residue, 0, 0, RFC_MEM_AIM_RESIDUE ); 396 | if( rfc_ctx->rfm ) rfc_ctx->mem_alloc( rfc_ctx->rfm, 0, 0, RFC_MEM_AIM_MATRIX ); 397 | 398 | rfc_ctx->residue = NULL; 399 | rfc_ctx->residue_cap = 0; 400 | rfc_ctx->residue_cnt = 0; 401 | 402 | rfc_ctx->rfm = NULL; 403 | 404 | rfc_ctx->internal.slope = 0; 405 | rfc_ctx->internal.extrema[0] = nil; /* local minimum */ 406 | rfc_ctx->internal.extrema[1] = nil; /* local maximum */ 407 | rfc_ctx->internal.pos = 0; 408 | rfc_ctx->internal.pos_offset = 0; 409 | 410 | rfc_ctx->state = RFC_STATE_INIT0; 411 | 412 | return true; 413 | } 414 | 415 | 416 | /** 417 | * @brief "Feed" counting algorithm with data samples (consecutive calls 418 | * allowed). 419 | * 420 | * @param ctx The rainflow context 421 | * @param[in] data The data 422 | * @param data_count The data count 423 | * 424 | * @return true on success 425 | */ 426 | bool RFC_feed( void *ctx, const rfc_value_t * data, size_t data_count ) 427 | { 428 | RFC_CTX_CHECK_AND_ASSIGN 429 | 430 | if( !data ) return !data_count; 431 | 432 | if( rfc_ctx->state < RFC_STATE_INIT || rfc_ctx->state >= RFC_STATE_FINISHED ) 433 | { 434 | return false; 435 | } 436 | 437 | /* Process data */ 438 | while( data_count-- ) 439 | { 440 | rfc_value_tuple_s tp = { *data++ }; /* All other members are zero-initialized, see ISO/IEC 9899:TC3, 6.7.8 (21) */ 441 | 442 | /* Assign class and global position (base 1) */ 443 | tp.pos = ++rfc_ctx->internal.pos; 444 | tp.cls = QUANTIZE( rfc_ctx, tp.value ); 445 | 446 | if( rfc_ctx->class_count && ( tp.cls >= rfc_ctx->class_count || tp.value < rfc_ctx->class_offset ) ) 447 | { 448 | return error_raise( rfc_ctx, RFC_ERROR_DATA_OUT_OF_RANGE ); 449 | } 450 | 451 | if( !feed_once( rfc_ctx, &tp, rfc_ctx->internal.flags ) ) 452 | { 453 | return false; 454 | } 455 | } 456 | 457 | return true; 458 | } 459 | 460 | 461 | /** 462 | * @brief Finalize pending counts and turning point storage. 463 | * 464 | * @param ctx The rainflow context 465 | * @param residual_method The residual method (RFC_RES_...) 466 | * 467 | * @return true on success 468 | */ 469 | bool RFC_finalize( void *ctx, rfc_res_method_e residual_method ) 470 | { 471 | double damage; 472 | bool ok; 473 | RFC_CTX_CHECK_AND_ASSIGN 474 | 475 | if( rfc_ctx->state < RFC_STATE_INIT || rfc_ctx->state >= RFC_STATE_FINISHED ) 476 | { 477 | return false; 478 | } 479 | 480 | damage = rfc_ctx->damage; 481 | 482 | { 483 | int flags = rfc_ctx->internal.flags; 484 | 485 | switch( residual_method ) 486 | { 487 | case RFC_RES_NONE: 488 | /* FALLTHROUGH */ 489 | case RFC_RES_IGNORE: 490 | ok = finalize_res_ignore( rfc_ctx, flags ); 491 | break; 492 | case RFC_RES_NO_FINALIZE: 493 | ok = finalize_res_no_finalize( rfc_ctx, flags ); 494 | break; 495 | default: 496 | assert( false ); 497 | ok = error_raise( rfc_ctx, RFC_ERROR_INVARG ); 498 | } 499 | assert( rfc_ctx->state == RFC_STATE_FINALIZE ); 500 | } 501 | 502 | if( !rfc_ctx->class_count ) 503 | { 504 | rfc_ctx->residue_cnt = 0; 505 | } 506 | 507 | rfc_ctx->damage_residue = rfc_ctx->damage - damage; 508 | rfc_ctx->state = ok ? RFC_STATE_FINISHED : RFC_STATE_ERROR; 509 | 510 | return ok; 511 | } 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | /*** Implementation static functions ***/ 523 | 524 | 525 | /** 526 | * @brief Processing one data point. Find turning points and check for 527 | * closed cycles. 528 | * 529 | * @param rfc_ctx The rainflow context 530 | * @param[in] pt The data tuple 531 | * @param flags The flags 532 | * 533 | * @return true on success 534 | */ 535 | static 536 | bool feed_once( rfc_ctx_s *rfc_ctx, const rfc_value_tuple_s* pt, rfc_flags_e flags ) 537 | { 538 | rfc_value_tuple_s *tp_residue; /* Pointer to residue element */ 539 | 540 | assert( rfc_ctx && pt ); 541 | assert( rfc_ctx->state >= RFC_STATE_INIT && rfc_ctx->state < RFC_STATE_FINISHED ); 542 | 543 | /* Check for next turning point and update residue. tp_residue is NULL, if there is no turning point */ 544 | /* Otherwise tp_residue refers the forelast element in member rfc_ctx->residue */ 545 | tp_residue = feed_filter_pt( rfc_ctx, pt ); 546 | 547 | /* Countings */ 548 | 549 | /* Add turning point and check for closed cycles */ 550 | if( tp_residue ) 551 | { 552 | 553 | if( rfc_ctx->class_count ) 554 | { 555 | /* Check for closed cycles and count. Modifies residue! */ 556 | cycle_find( rfc_ctx, flags ); 557 | } 558 | else 559 | { 560 | if( rfc_ctx->residue_cnt > 1 ) 561 | { 562 | residue_remove_item( rfc_ctx, 0, 1 ); 563 | } 564 | } 565 | } 566 | 567 | return true; 568 | } 569 | 570 | 571 | /** 572 | * @brief Handling interim turning point and margin. If there are still 573 | * unhandled turning point left, "finalizing" takes this into 574 | * account for the rainflow algorithm. 575 | * 576 | * @param rfc_ctx The rainflow context 577 | * 578 | * @return true on success 579 | */ 580 | static 581 | bool feed_finalize( rfc_ctx_s *rfc_ctx ) 582 | { 583 | rfc_value_tuple_s *tp_interim = NULL; 584 | 585 | assert( rfc_ctx ); 586 | assert( rfc_ctx->state >= RFC_STATE_INIT && rfc_ctx->state < RFC_STATE_FINISHED ); 587 | 588 | if( rfc_ctx->state < RFC_STATE_FINALIZE ) 589 | { 590 | /* Adjust residue: Incorporate interim turning point */ 591 | if( rfc_ctx->state == RFC_STATE_BUSY_INTERIM ) 592 | { 593 | tp_interim = &rfc_ctx->residue[rfc_ctx->residue_cnt]; 594 | rfc_ctx->residue_cnt++; 595 | 596 | rfc_ctx->state = RFC_STATE_BUSY; 597 | } 598 | 599 | if( tp_interim ) 600 | { 601 | int flags = rfc_ctx->internal.flags; 602 | 603 | /* Check once more if a new cycle is closed now */ 604 | cycle_find( rfc_ctx, flags ); 605 | } 606 | 607 | rfc_ctx->state = RFC_STATE_FINALIZE; 608 | } 609 | 610 | return true; 611 | } 612 | 613 | 614 | /** 615 | * @brief Finalize pending counts, ignore residue. 616 | * 617 | * @param rfc_ctx The rainflow context 618 | * @param flags The flags 619 | * 620 | * @return true on success 621 | */ 622 | static 623 | bool finalize_res_ignore( rfc_ctx_s *rfc_ctx, rfc_flags_e flags ) 624 | { 625 | assert( rfc_ctx ); 626 | assert( rfc_ctx->state >= RFC_STATE_INIT && rfc_ctx->state < RFC_STATE_FINISHED ); 627 | 628 | /* Include interim turning point */ 629 | return feed_finalize( rfc_ctx ); 630 | } 631 | 632 | 633 | /** 634 | * @brief Finalize pending counts, ignore residue, don't finalize 635 | * 636 | * @param rfc_ctx The rainflow context 637 | * @param flags The flags 638 | * 639 | * @return true on success 640 | */ 641 | static 642 | bool finalize_res_no_finalize( rfc_ctx_s *rfc_ctx, rfc_flags_e flags ) 643 | { 644 | assert( rfc_ctx ); 645 | assert( rfc_ctx->state >= RFC_STATE_INIT && rfc_ctx->state < RFC_STATE_FINISHED ); 646 | 647 | rfc_ctx->state = RFC_STATE_FINALIZE; 648 | 649 | return true; 650 | } 651 | 652 | 653 | /** 654 | * @brief Remove items (points) from the residue 655 | * 656 | * @param rfc_ctx The rainflow context 657 | * @param index The item position in residue, base 0 658 | * @param count The number of elements to remove 659 | */ 660 | static 661 | void residue_remove_item( rfc_ctx_s *rfc_ctx, size_t index, size_t count ) 662 | { 663 | size_t from = index + count, 664 | to = index, 665 | end; 666 | 667 | assert( rfc_ctx ); 668 | assert( rfc_ctx->state >= RFC_STATE_INIT && rfc_ctx->state < RFC_STATE_FINISHED ); 669 | assert( rfc_ctx->residue && index + count <= rfc_ctx->residue_cnt ); 670 | 671 | end = (int)rfc_ctx->residue_cnt; 672 | 673 | /* Example 674 | |cnt(5) 675 | v 676 | |O|O|X|O|O| 677 | ^ 678 | |index(2) 679 | 680 | elements = 2 (5-2-1+0 = 2, no interim turning point) 681 | */ 682 | 683 | if( rfc_ctx->state == RFC_STATE_BUSY_INTERIM ) 684 | { 685 | /* Include interim turning point */ 686 | end++; 687 | } 688 | 689 | /* Shift points */ 690 | while( from < end ) 691 | { 692 | rfc_ctx->residue[to++] = rfc_ctx->residue[from++]; 693 | } 694 | 695 | rfc_ctx->residue_cnt -= count; 696 | } 697 | 698 | 699 | /** 700 | * @brief Calculate damage for one cycle with given amplitude Sa 701 | * 702 | * @param rfc_ctx The rainflow context 703 | * @param Sa The amplitude 704 | * @param[out] damage The damage 705 | * 706 | * @return true on success 707 | */ 708 | static 709 | bool damage_calc_amplitude( rfc_ctx_s *rfc_ctx, double Sa, double *damage ) 710 | { 711 | assert( rfc_ctx ); 712 | assert( rfc_ctx->state >= RFC_STATE_INIT ); 713 | 714 | do { 715 | /* Damage */ 716 | double D = 0.0; 717 | 718 | if( Sa >= 0.0 ) 719 | { 720 | /* D = h / ND * ( Sa / SD) ^ ABS(k) */ 721 | /* D = exp( log(h / ND) + log( Sa / SD) * ABS(k) ) */ 722 | /* D = exp( (log(h)-log(ND)) + (log(Sa)-log(SD)) * ABS(k) ) */ 723 | /* D = exp( 0 -log(ND) + (log(Sa)-log(SD)) * ABS(k) ) */ 724 | 725 | /* If D is integer format count: 726 | * 727 | * D_integer = (double)(int)range ^ ABS(k) // where range is 0..class_count-1 728 | * 729 | * D = D_integer_sum * exp( log( ND * 2*SD/class_width ) * -ABS(k) ) 730 | * 731 | */ 732 | 733 | /* Constants for the Woehler curve */ 734 | const double SX_log = log(rfc_ctx->wl_sx); 735 | const double NX_log = log(rfc_ctx->wl_nx); 736 | const double k = rfc_ctx->wl_k; 737 | 738 | /* Miner original */ 739 | D = exp( fabs(k) * ( log(Sa) - SX_log ) - NX_log ); 740 | } 741 | else 742 | { 743 | assert( false ); 744 | return error_raise( rfc_ctx, RFC_ERROR_INVARG ); 745 | } 746 | 747 | *damage = D; 748 | 749 | } while(0); 750 | 751 | return true; 752 | } 753 | 754 | 755 | /** 756 | * @brief Calculate pseudo damage for one closed (full) cycle. 757 | * 758 | * @param rfc_ctx The rainflow context 759 | * @param class_from The starting class 760 | * @param class_to The ending class 761 | * @param[out] damage The damage value for the closed cycle 762 | * @param[out] Sa_ret The amplitude, may be NULL 763 | * 764 | * @return true on success 765 | */ 766 | static 767 | bool damage_calc( rfc_ctx_s *rfc_ctx, unsigned class_from, unsigned class_to, double *damage, double *Sa_ret ) 768 | { 769 | double Sa = -1.0; /* Negative amplitude states undefined */ 770 | double D = 0.0; 771 | 772 | assert( damage ); 773 | assert( rfc_ctx ); 774 | assert( rfc_ctx->state >= RFC_STATE_INIT ); 775 | 776 | 777 | if( class_from != class_to ) 778 | { 779 | Sa = fabs( (int)class_from - (int)class_to ) / 2.0 * rfc_ctx->class_width; 780 | 781 | if( !damage_calc_amplitude( rfc_ctx, Sa, &D ) ) 782 | { 783 | return false; 784 | } 785 | } 786 | 787 | if( Sa_ret ) 788 | { 789 | *Sa_ret = Sa; 790 | } 791 | 792 | *damage = D; 793 | 794 | return true; 795 | } 796 | 797 | 798 | /** 799 | * @brief Test data sample for a new turning point and add to the residue 800 | * in that case. Update extrema. 801 | * - 1. Hysteresis Filtering 802 | * - 2. Peak-Valley Filtering 803 | * 804 | * @param rfc_ctx The rainflow context 805 | * @param[in] pt The data tuple, must not be NULL 806 | * 807 | * @return Returns pointer to new turning point in residue or NULL 808 | */ 809 | static 810 | rfc_value_tuple_s * feed_filter_pt( rfc_ctx_s *rfc_ctx, const rfc_value_tuple_s *pt ) 811 | { 812 | int slope; 813 | rfc_value_t delta; 814 | rfc_value_tuple_s *new_tp = NULL; 815 | bool do_append = false; 816 | 817 | assert( rfc_ctx ); 818 | assert( rfc_ctx->state >= RFC_STATE_INIT && rfc_ctx->state <= RFC_STATE_BUSY_INTERIM ); 819 | 820 | if( !pt ) return NULL; 821 | 822 | slope = rfc_ctx->internal.slope; 823 | 824 | /* Handle first turning point(s) */ 825 | if( rfc_ctx->state < RFC_STATE_BUSY_INTERIM ) 826 | { 827 | /* Residue is empty, still searching first turning point(s) */ 828 | 829 | if( rfc_ctx->state == RFC_STATE_INIT ) 830 | { 831 | /* Very first point, initialize local min-max search */ 832 | rfc_ctx->internal.extrema[0] = 833 | rfc_ctx->internal.extrema[1] = *pt; 834 | rfc_ctx->state = RFC_STATE_BUSY; 835 | } 836 | else 837 | { 838 | int is_falling_slope = -1; 839 | 840 | assert( rfc_ctx->state == RFC_STATE_BUSY ); 841 | 842 | /* Still searching for first turning point(s) */ 843 | 844 | /* Update local extrema */ 845 | if( pt->value < rfc_ctx->internal.extrema[0].value ) 846 | { 847 | /* Minimum */ 848 | is_falling_slope = 1; 849 | rfc_ctx->internal.extrema[0] = *pt; 850 | } 851 | else if( pt->value > rfc_ctx->internal.extrema[1].value ) 852 | { 853 | /* Maximum */ 854 | is_falling_slope = 0; 855 | rfc_ctx->internal.extrema[1] = *pt; 856 | } 857 | 858 | /* Local hysteresis filtering */ 859 | delta = value_delta( rfc_ctx, &rfc_ctx->internal.extrema[0], &rfc_ctx->internal.extrema[1], NULL /* sign_ptr */ ); 860 | 861 | if( is_falling_slope >= 0 && delta > rfc_ctx->hysteresis ) 862 | { 863 | /* Criteria met, new turning point found. 864 | * Emit maximum on falling slope as first interim turning point, 865 | * minimum as second then (and vice versa) 866 | * 1st point: internal.extrema[ is_falling_slope] 867 | * 2nd point: internal.extrema[!is_falling_slope] ==> which is *pt also 868 | */ 869 | assert( rfc_ctx->residue_cnt < rfc_ctx->residue_cap ); 870 | rfc_ctx->residue[rfc_ctx->residue_cnt] = rfc_ctx->internal.extrema[is_falling_slope]; 871 | 872 | rfc_ctx->internal.slope = is_falling_slope ? -1 : 1; 873 | 874 | /* pt is the new interim turning point */ 875 | rfc_ctx->state = RFC_STATE_BUSY_INTERIM; 876 | do_append = true; 877 | } 878 | } 879 | } 880 | else /* if( rfc_ctx->state < RFC_STATE_BUSY_INTERIM ) */ 881 | { 882 | assert( rfc_ctx->state == RFC_STATE_BUSY_INTERIM ); 883 | 884 | /* Consecutive search for turning points */ 885 | 886 | /* Hysteresis Filtering, check against interim turning point */ 887 | delta = value_delta( rfc_ctx, &rfc_ctx->residue[rfc_ctx->residue_cnt], pt, &slope /* sign_ptr */ ); 888 | 889 | /* There are three scenarios possible here: 890 | * 1. Previous slope is continued 891 | * "delta" is ignored whilst hysteresis is exceeded already. 892 | * Interim turning point has just to be adjusted. 893 | * 2. Slope reversal, slope is greater than hysteresis 894 | * Interim turning point becomes real turning point. 895 | * Current point becomes new interim turning point 896 | * 3. Slope reversal, slope is less than or equal hysteresis 897 | * Nothing to do. 898 | */ 899 | 900 | /* Peak-Valley Filtering */ 901 | /* Justify interim turning point, or add a new one (2) */ 902 | if( slope == rfc_ctx->internal.slope ) 903 | { 904 | /* Scenario (1), Continuous slope */ 905 | 906 | /* Replace interim turning point with new extrema */ 907 | if( rfc_ctx->residue[rfc_ctx->residue_cnt].value != pt->value ) 908 | { 909 | rfc_ctx->residue[rfc_ctx->residue_cnt] = *pt; 910 | } 911 | } 912 | else 913 | { 914 | if( delta > rfc_ctx->hysteresis ) 915 | { 916 | /* Scenario (2), Criteria met: slope != rfc_ctx->internal.slope && delta > rfc_ctx->hysteresis */ 917 | 918 | /* Storage */ 919 | rfc_ctx->internal.slope = slope; 920 | 921 | /* Handle new turning point */ 922 | do_append = true; 923 | } 924 | else 925 | { 926 | /* Scenario (3), Turning point found, but still in hysteresis band, nothing to do */ 927 | } 928 | } 929 | } 930 | 931 | /* Handle new turning point, that is the current last point in residue */ 932 | if( do_append ) 933 | { 934 | assert( rfc_ctx->state == RFC_STATE_BUSY_INTERIM ); 935 | 936 | /* Increment and set new interim turning point */ 937 | assert( rfc_ctx->residue_cnt + 1 < rfc_ctx->residue_cap ); 938 | rfc_ctx->residue[++rfc_ctx->residue_cnt] = *pt; 939 | 940 | /* Return new turning point */ 941 | new_tp = &rfc_ctx->residue[rfc_ctx->residue_cnt - 1]; 942 | } 943 | 944 | return new_tp; 945 | } 946 | 947 | 948 | /** 949 | * @brief Rainflow counting core (4-point-method [3]). 950 | * 951 | * @param rfc_ctx The rainflow context 952 | * @param flags The flags 953 | */ 954 | static 955 | void cycle_find_4ptm( rfc_ctx_s *rfc_ctx, rfc_flags_e flags ) 956 | { 957 | assert( rfc_ctx ); 958 | assert( rfc_ctx->state >= RFC_STATE_INIT && rfc_ctx->state < RFC_STATE_FINISHED ); 959 | 960 | while( rfc_ctx->residue_cnt >= 4 ) 961 | { 962 | size_t idx = rfc_ctx->residue_cnt - 4; 963 | 964 | unsigned A = rfc_ctx->residue[idx+0].cls; 965 | unsigned B = rfc_ctx->residue[idx+1].cls; 966 | unsigned C = rfc_ctx->residue[idx+2].cls; 967 | unsigned D = rfc_ctx->residue[idx+3].cls; 968 | 969 | if( B > C ) 970 | { 971 | unsigned temp = B; 972 | B = C; 973 | C = temp; 974 | } 975 | 976 | if( A > D ) 977 | { 978 | unsigned temp = A; 979 | A = D; 980 | D = temp; 981 | } 982 | 983 | /* Check for closed cycles [3] */ 984 | if( A <= B && C <= D ) 985 | { 986 | rfc_value_tuple_s *from = &rfc_ctx->residue[idx+1]; 987 | rfc_value_tuple_s *to = &rfc_ctx->residue[idx+2]; 988 | 989 | /* Closed cycle found, process countings */ 990 | cycle_process_counts( rfc_ctx, from, to, to + 1, flags ); 991 | 992 | /* Remove two inner turning points (idx+1 and idx+2) */ 993 | /* Move last turning point */ 994 | rfc_ctx->residue[idx+1] = rfc_ctx->residue[idx+3]; 995 | /* Move interim turning point */ 996 | if( rfc_ctx->state == RFC_STATE_BUSY_INTERIM ) 997 | { 998 | rfc_ctx->residue[idx+2] = rfc_ctx->residue[idx+4]; 999 | } 1000 | rfc_ctx->residue_cnt -= 2; 1001 | } 1002 | else break; 1003 | } 1004 | } 1005 | 1006 | 1007 | /** 1008 | * @brief Processes counts on a closing cycle and further modifications 1009 | * due to the new damage value. 1010 | * 1011 | * @param rfc_ctx The rainflow context 1012 | * @param[in,out] from The starting data point 1013 | * @param[in,out] to The ending data point 1014 | * @param[in,out] next The point next after "to" 1015 | * @param flags Control flags 1016 | */ 1017 | static 1018 | void cycle_process_counts( rfc_ctx_s *rfc_ctx, rfc_value_tuple_s *from, rfc_value_tuple_s *to, rfc_value_tuple_s *next, rfc_flags_e flags ) 1019 | { 1020 | unsigned class_from, class_to; 1021 | 1022 | assert( rfc_ctx ); 1023 | assert( rfc_ctx->state >= RFC_STATE_INIT && rfc_ctx->state < RFC_STATE_FINISHED ); 1024 | 1025 | if( !rfc_ctx->class_count || ( from->value >= rfc_ctx->class_offset && to->value >= rfc_ctx->class_offset ) ) 1026 | { 1027 | /* If class_count is zero, no counting is done. Otherwise values must be greater than class_offset */ 1028 | } 1029 | else 1030 | { 1031 | assert( false ); 1032 | } 1033 | 1034 | /* Quantized "from" */ 1035 | class_from = from->cls; 1036 | 1037 | if( class_from >= rfc_ctx->class_count ) class_from = rfc_ctx->class_count - 1; 1038 | 1039 | /* Quantized "to" */ 1040 | class_to = to->cls; 1041 | 1042 | if( class_to >= rfc_ctx->class_count ) class_to = rfc_ctx->class_count - 1; 1043 | 1044 | /* class_from and class_to are base 0 now */ 1045 | 1046 | /* Do several counts, according to "flags" */ 1047 | if( class_from != class_to ) 1048 | { 1049 | 1050 | /* Cumulate damage */ 1051 | if( flags & RFC_FLAGS_COUNT_DAMAGE ) 1052 | { 1053 | double Sa_i; 1054 | double D_i; 1055 | 1056 | if( !damage_calc( rfc_ctx, class_from, class_to, &D_i, &Sa_i ) ) 1057 | { 1058 | return; 1059 | } 1060 | 1061 | /* Adding damage for the current cycle, with its actual weight */ 1062 | rfc_ctx->damage += D_i * rfc_ctx->curr_inc / rfc_ctx->full_inc; 1063 | } 1064 | 1065 | /* Rainflow matrix */ 1066 | if( rfc_ctx->rfm && ( flags & RFC_FLAGS_COUNT_RFM ) ) 1067 | { 1068 | /* Matrix (row-major storage): 1069 | * t o 1070 | * +------------- 1071 | * | 0 1 2 3 4 5 1072 | * f | 6 7 8 9 # # 1073 | * r | # # # # # # 1074 | * o | # # # # # # 1075 | * m | # # # # # # 1076 | * | # # # # # #<-(6x6-1) 1077 | */ 1078 | size_t idx = rfc_ctx->class_count * class_from + class_to; 1079 | 1080 | assert( rfc_ctx->rfm[idx] <= RFC_COUNTS_LIMIT ); 1081 | rfc_ctx->rfm[idx] += rfc_ctx->curr_inc; 1082 | } 1083 | 1084 | } 1085 | } 1086 | 1087 | 1088 | /** 1089 | * @brief Raises an error 1090 | * 1091 | * @param rfc_ctx The rainflow context 1092 | * @param error The error identifier 1093 | * 1094 | * @return false on error 1095 | */ 1096 | static 1097 | bool error_raise( rfc_ctx_s *rfc_ctx, rfc_error_e error ) 1098 | { 1099 | if( error == RFC_ERROR_NOERROR ) return true; 1100 | 1101 | if( rfc_ctx && rfc_ctx->version == sizeof(rfc_ctx_s) ) 1102 | { 1103 | rfc_ctx->state = RFC_STATE_ERROR; 1104 | rfc_ctx->error = error; 1105 | } 1106 | else 1107 | { 1108 | assert( false ); 1109 | } 1110 | 1111 | return false; 1112 | } 1113 | 1114 | 1115 | /** 1116 | * @brief Returns the unsigned difference of two values, sign optionally 1117 | * returned as -1 or 1. 1118 | * 1119 | * @param rfc_ctx The rainflow context 1120 | * @param[in] pt_from The point from 1121 | * @param[in] pt_to The point to 1122 | * @param[out] sign_ptr Pointer to catch sign (may be NULL) 1123 | * 1124 | * @return Returns the absolute difference of given values 1125 | */ 1126 | static 1127 | rfc_value_t value_delta( rfc_ctx_s* rfc_ctx, const rfc_value_tuple_s* pt_from, const rfc_value_tuple_s* pt_to, int *sign_ptr ) 1128 | { 1129 | double delta; 1130 | 1131 | assert( rfc_ctx ); 1132 | assert( pt_from && pt_to ); 1133 | 1134 | #if RFC_USE_HYSTERESIS_FILTER 1135 | delta = (double)pt_to->value - (double)pt_from->value; 1136 | #else /*RFC_USE_HYSTERESIS_FILTER*/ 1137 | delta = rfc_ctx->class_width * ( (int)pt_to->cls - (int)pt_from->cls ); 1138 | #endif /*RFC_USE_HYSTERESIS_FILTER*/ 1139 | 1140 | if( sign_ptr ) 1141 | { 1142 | *sign_ptr = ( delta < 0.0 ) ? -1 : 1; 1143 | } 1144 | 1145 | return (rfc_value_t)fabs( delta ); 1146 | } 1147 | 1148 | 1149 | /** 1150 | * @brief (Re-)Allocate or free memory 1151 | * 1152 | * @param ptr Previous data pointer, or NULL, if unset 1153 | * @param num The number of elements 1154 | * @param size The size of one element in bytes 1155 | * @param aim The aim 1156 | * 1157 | * @return New memory pointer or NULL if either num or size is 0 1158 | */ 1159 | static 1160 | void * mem_alloc( void *ptr, size_t num, size_t size, int aim ) 1161 | { 1162 | if( !num || !size ) 1163 | { 1164 | if( ptr ) 1165 | { 1166 | FREE( ptr ); 1167 | } 1168 | return NULL; 1169 | } 1170 | else 1171 | { 1172 | return ptr ? REALLOC( ptr, num * size ) : CALLOC( num, size ); 1173 | } 1174 | } 1175 | --------------------------------------------------------------------------------