diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 262462e..fd92d0a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,25 +8,25 @@ Thank you for considering contributing to FatPy, a Python package for fatigue li Get started contributing to FatPy in a few simple steps: -1. **Fork the Repository** +1. **Fork the Repository** Create a personal copy on GitHub. -2. **Clone Your Fork** +2. **Clone Your Fork** ```bash git clone https://github.com/your-username/FatPy.git # Clone your fork ``` -3. **Create a Branch** +3. **Create a Branch** ```bash git checkout -b my-feature-branch # Create a new branch for your feature ``` -4. **Implement the Feature** +4. **Implement the Feature** Write code to pass your tests. -5. **Write Tests for Your Feature** +5. **Write Tests for Your Feature** Define expected behavior first. -6. **Submit a Pull Request** +6. **Submit a Pull Request** Share your changes for review. ## :hammer_and_wrench: Development Setup @@ -63,7 +63,7 @@ FatPy emphasizes comprehensive testing to ensure code reliability and quality. A ### Testing Guidelines - **Write comprehensive tests** - Cover all functionality with unit and integration tests -- **Test edge cases** - Include boundary conditions and error scenarios +- **Test edge cases** - Include boundary conditions and error scenarios - **Maintain test coverage** - Aim for high coverage to ensure reliability - **Keep tests updated** - Update tests when modifying existing functionality - **Use clear test names** - Write descriptive test names and docstrings @@ -92,7 +92,7 @@ FatPy aims for high code quality utilizing these tools: - **MyPy** - Static type checking for reliability. - **Pre-commit** - Automated checks before commits. -Follow our coding standards for contributions, see **[Code Style Guide :arrow_right:](https://faberorg.github.io/FatPy/development/code_style/)** +Follow our coding standards for contributions, see **[Code Style Guide :arrow_right:](https://faberorg.github.io/FatPy/development/code_style/)** ### Running Code Quality Checks @@ -117,11 +117,11 @@ pre-commit run --all-files Keep FatPy’s documentation clear and up-to-date with these guidelines: -- **API Changes** +- **API Changes** Update documentation for any API modifications. -- **Docstrings** +- **Docstrings** Add docstrings following Google style to all new code. -- **Examples** +- **Examples** Include examples and mathematical formulas where helpful to aid users. Learn best practices and guidelines for documentation, see **[Documentation Guide :arrow_right:](https://faberorg.github.io/FatPy/development/documentation/)** @@ -140,15 +140,15 @@ mkdocs serve Submit a high-quality pull request with these steps: -1. **Run Tests and Checks** +1. **Run Tests and Checks** Ensure tests pass and code quality checks succeed. -2. **Update Documentation** +2. **Update Documentation** Reflect changes in relevant docs. -3. **Link Issues** +3. **Link Issues** Reference related GitHub issues. -4. **Follow guidelines** +4. **Follow guidelines** Make sure your code follows the project's style guidelines. -5. **Await Review** +5. **Await Review** Respond to feedback from maintainers. ## :book: Code of Conduct @@ -161,11 +161,11 @@ Understand our expectations for collaboration, see **[Code of Conduct :arrow_rig Join discussion, create an issue or reach out to maintainers: -- :speech_balloon: **[GitHub Discussions :arrow_right:](https://github.com/faberorg/FatPy/discussions)** +- :speech_balloon: **[GitHub Discussions :arrow_right:](https://github.com/faberorg/FatPy/discussions)** Join for community support. -- :beetle: **[Report an Issue :arrow_right:](https://github.com/faberorg/fatpy/issues)** +- :beetle: **[Report an Issue :arrow_right:](https://github.com/faberorg/fatpy/issues)** Create an issue on our GitHub repository for bugs or questions. -- :envelope: **[Contact our Team :arrow_right:](https://faberorg.github.io/FatPy/contact/)** +- :envelope: **[Contact our Team :arrow_right:](https://faberorg.github.io/FatPy/contact/)** Visit our contact page. Thank you for contributing to FatPy! diff --git a/docs/contact.md b/docs/contact.md index c1165bd..b783000 100644 --- a/docs/contact.md +++ b/docs/contact.md @@ -28,13 +28,13 @@ Check out these resources: Use GitHub as a main way to collaborate, discuss and improve the FatPy package. -- **Explore the Repository**: +- **Explore the Repository**: Visit our [:fontawesome-brands-github: GitHub repository](https://github.com/faberorg/fatpy) to access the source code. -- **Contribute**: +- **Contribute**: Follow our [:fontawesome-solid-handshake: Contribution Guidelines](development/contributing.md) to help improve FatPy. -- **Collaborate**: +- **Collaborate**: Join our [:fontawesome-solid-comment-dots: Discussions](https://github.com/faberorg/fatpy/discussions) or [:fontawesome-solid-bug: Report issues](https://github.com/faberorg/fatpy/issues). --- @@ -48,6 +48,6 @@ You can also contact our team directly! Here is a list of active members: --- -Thank you for reaching out and contributing your ideas! -Your feedback and suggestions help us grow and improve. +Thank you for reaching out and contributing your ideas! +Your feedback and suggestions help us grow and improve. We appreciate your involvement in the FatPy and FABER communities! diff --git a/docs/development/ci_cd.md b/docs/development/ci_cd.md index 3b5d045..a5c58fa 100644 --- a/docs/development/ci_cd.md +++ b/docs/development/ci_cd.md @@ -120,7 +120,7 @@ Status badges are displayed in the README.md: - Check Python version differences - Check dependency versions - Review test logs for environment-specific issues - + === "**Type Errors**" - **Issue**: Mypy reports type errors diff --git a/docs/development/documentation.md b/docs/development/documentation.md index eece616..b783cd1 100644 --- a/docs/development/documentation.md +++ b/docs/development/documentation.md @@ -108,15 +108,15 @@ Follow these guidelines to create clear and effective documentation for FatPy. ### **General Guidelines** -- **Clear Language** +- **Clear Language** Use concise, straightforward wording. -- **Examples** +- **Examples** Include practical code or use-case examples, where possible. -- **Related Links** +- **Related Links** Reference related documentation. -- **Headers** +- **Headers** Organize content with clear section headings. -- **Mathematical Formulas** +- **Mathematical Formulas** Use LaTeX for mathematical notation where appropriate. --- @@ -174,15 +174,15 @@ Consistent naming is crucial for code readability and maintainability. Follow th - **`fat`**: fatigue (e.g., `fat_limit` for fatigue limit) - **`frac`**: fracture (e.g., `frac_toughness` for fracture toughness) - Common names and material parameters: - - `first_principal_stress`, `second_principal_stress` and `third_principal_stress` - - `elastic_modulus`, `shear_modulus` and `poisson_ratio` - - `ultimate_tensile_strength` and `yield_strength` + Common names and material parameters: + - `first_principal_stress`, `second_principal_stress` and `third_principal_stress` + - `elastic_modulus`, `shear_modulus` and `poisson_ratio` + - `ultimate_tensile_strength` and `yield_strength` !!! warning "Clarity First" If an abbreviation makes the code harder to understand for someone new to the specific module or fatigue in general, prefer the full name. Document any project-specific abbreviations clearly. -Learn more about the naming conventions form our discussion page: +Learn more about the naming conventions form our discussion page: [:simple-github::fontawesome-solid-comment-dots: Naming Conventions for FatPy Functions & Parameters #18](https://github.com/faberorg/FatPy/discussions/18) @@ -209,7 +209,7 @@ Use LaTeX for mathematical formulas. This allows for clear rendering of equation Hooke's Law can be expressed as $\sigma = E \cdot \epsilon$. ``` - **Rendered output:** + **Rendered output:** Hooke's Law can be expressed as $\sigma = E \cdot \epsilon$. === "Block Syntax" @@ -218,7 +218,7 @@ Use LaTeX for mathematical formulas. This allows for clear rendering of equation **How to write it:** ``` latex title="Block syntax" % This line is empty - $$ + $$ \sigma_{eq} = \sqrt{3J_2} = \sqrt{\frac{3}{2}s_{ij}s_{ij}} $$ % This line is empty @@ -231,7 +231,7 @@ Use LaTeX for mathematical formulas. This allows for clear rendering of equation $$ === "Display Complex Expression" - For more complex expressions, like summations, matrices, or a sequence of aligned equations. + For more complex expressions, like summations, matrices, or a sequence of aligned equations. This example shows Miner's rule for damage accumulation: @@ -289,7 +289,7 @@ API documentation is automatically generated from docstrings using mkdocstrings. 1. All public functions, classes, and modules must have Google-style docstrings. 2. Type hints should be used for all function parameters and return values. 3. Examples should be included in docstrings where appropriate. -4. Mathematical formulas should use LaTeX syntax within docstrings, e.g.: +4. Mathematical formulas should use LaTeX syntax within docstrings, e.g.: `$$ \sigma = \sqrt{x^2} $$` View the generated API documentation: **[:fontawesome-solid-book-open: API Reference](../api/index.md)** @@ -298,11 +298,11 @@ View the generated API documentation: **[:fontawesome-solid-book-open: API Refer To add new documentation pages, follow these steps: -- **Create File** +- **Create File** Add a new Markdown file in the appropriate directory (e.g., `docs/`). -- **Update Navigation** +- **Update Navigation** Include the file in `mkdocs.yml` under the `nav` section. -- **Format Content** +- **Format Content** Use headers, code blocks, and consistent Markdown formatting. ??? note "Documentation Deployment" diff --git a/docs/development/index.md b/docs/development/index.md index 0ec87d3..4cff1fc 100644 --- a/docs/development/index.md +++ b/docs/development/index.md @@ -15,24 +15,24 @@ This section provides comprehensive resources to help you contribute to FatPy, a New to FatPy? Start with these resources to set up your environment and understand how to contribute effectively: -- :fontawesome-solid-download: **[Installation Guide](install.md)** +- :fontawesome-solid-download: **[Installation Guide](install.md)** Set up your development environment for FatPy. -- :fontawesome-solid-handshake: **[Contributing Guide](contributing.md)** +- :fontawesome-solid-handshake: **[Contributing Guide](contributing.md)** Learn how to contribute code, documentation, or ideas to the project. -- :fontawesome-solid-book: **[Code of Conduct](code_of_conduct.md)** +- :fontawesome-solid-book: **[Code of Conduct](code_of_conduct.md)** Understand our community standards for respectful collaboration. ## :fontawesome-solid-code: Development Resources Deepen your understanding of FatPy development with these resources: -- :fontawesome-solid-code: **[Code Style](code_style.md)** +- :fontawesome-solid-code: **[Code Style](code_style.md)** Follow our coding standards for consistent, high-quality code. -- :fontawesome-solid-file-pen: **[Documentation](documentation.md)** +- :fontawesome-solid-file-pen: **[Documentation](documentation.md)** Learn best practices for writing and maintaining FatPy documentation. -- :fontawesome-solid-vial: **[Testing](testing.md)** +- :fontawesome-solid-vial: **[Testing](testing.md)** Understand how to write and run tests to ensure code reliability. -- :material-cogs: **[CI/CD Process](ci_cd.md)** +- :material-cogs: **[CI/CD Process](ci_cd.md)** Explore our continuous integration and deployment workflow. ??? tip "Additional resources" @@ -45,32 +45,32 @@ Get a detailed overview of FatPy’s modules and functions, visit **[API Referen Follow our development workflow: -1. **Set Up Your Environment** +1. **Set Up Your Environment** :fontawesome-solid-download: [Installation Guide](install.md) -2. **Create a Feature Branch** +2. **Create a Feature Branch** ```bash git checkout -b feature-name ``` -3. **Implement the Feature** +3. **Implement the Feature** Follow the [Code Style Guide](code_style.md). -4. **Write and Run Tests** +4. **Write and Run Tests** :fontawesome-solid-vial: [Testing Guide](testing.md) -5. **Document your Changes** +5. **Document your Changes** Use [Documentation](documentation.md) resources! -6. **Submit a pull request** +6. **Submit a pull request** :fontawesome-solid-handshake: [Contributing Guide](contributing.md) ## :material-help-box-outline: Getting Help Need assistance with FatPy development? Reach out through these channels: -- :simple-github: **[GitHub Discussions](https://github.com/faberorg/FatPy/discussions)** +- :simple-github: **[GitHub Discussions](https://github.com/faberorg/FatPy/discussions)** Join for community support. -- :fontawesome-solid-bug: **[Report an Issue](https://github.com/faberorg/fatpy/issues)** +- :fontawesome-solid-bug: **[Report an Issue](https://github.com/faberorg/fatpy/issues)** Create an issue on our GitHub repository for bugs or questions. -- :fontawesome-solid-envelope: **[Contact our Team](../contact.md)** +- :fontawesome-solid-envelope: **[Contact our Team](../contact.md)** Visit our contact page. Thank you for contributing to FatPy! diff --git a/docs/development/install.md b/docs/development/install.md index a09eb22..346b811 100644 --- a/docs/development/install.md +++ b/docs/development/install.md @@ -65,7 +65,7 @@ The easiest way to install FatPy is from PyPI: To install package and add dependency to your project .toml file use: ```bash - uv add fatpy # Install from PyPI and add dependency + uv add fatpy # Install from PyPI and add dependency ``` This installs the latest stable release with all dependencies. diff --git a/docs/development/testing.md b/docs/development/testing.md index 3f8f4b7..009efb8 100644 --- a/docs/development/testing.md +++ b/docs/development/testing.md @@ -10,17 +10,17 @@ This guide outlines the testing approach and best practices for the FatPy projec FatPy emphasizes comprehensive testing to ensure code reliability and quality: -1. **Write comprehensive tests** +1. **Write comprehensive tests** Cover all functionality with appropriate unit and integration tests -2. **Test edge cases** +2. **Test edge cases** Include boundary conditions and error scenarios -3. **Maintain test coverage** +3. **Maintain test coverage** Aim for high test coverage to ensure code reliability -4. **Verify functionality** +4. **Verify functionality** Ensure all tests pass before submitting changes -5. **Keep tests updated** +5. **Keep tests updated** Update tests when modifying existing functionality -6. **Document test cases** +6. **Document test cases** Use clear test names and docstrings to explain test purpose ## :simple-pytest: Testing Framework diff --git a/docs/index.md b/docs/index.md index f0ae914..9218e7d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -17,26 +17,26 @@ material science. #### Core Modules -- **Stress-Life** +- **Stress-Life** Stress-based fatigue assessment methods for estimating fatigue damage with corrections for various effects (stress concentration, size, surface quality, etc.) -- **Strain-Life** +- **Strain-Life** Fatigue analysis using strain amplitude and cycles to failure relationships (ε-N approaches) such as Coffin-Manson and Basquin laws, suited for low-cycle and transitional fatigue regimes -- **Energy-Life** +- **Energy-Life** Fatigue analysis methods based on the relationship between strain energy density and number of cycles to failure -- **Damage Cumulation** +- **Damage Cumulation** Various fatigue damage accumulation rules for variable amplitude loading, including linear models (Palmgren-Miner) and advanced non-linear approaches accounting for load sequence effects -- **Decompositions** +- **Decompositions** Methods for breaking down complex load signals into cycles, containing both uniaxial and multiaxial procedures -- **Plane-Based Methods** +- **Plane-Based Methods** Methods for processing stress tensor paths on material planes, providing infrastructure for critical-plane and integral prediction approaches #### Supporting Modules -- **Material Laws** +- **Material Laws** Functions dealing with various material constitutive laws and behavior models -- **Structural Mechanics** +- **Structural Mechanics** Stress analysis and transformation utilities -- **Utils** +- **Utils** General utilities for mesh handling, signal processing, and data manipulation ## :fontawesome-solid-download: Installation @@ -60,7 +60,7 @@ material science. To install package and add dependency to your project .toml file use: ```bash - uv add fatpy # Install from PyPI and add dependency + uv add fatpy # Install from PyPI and add dependency ``` For detailed installation options, see the [Installation Guide :octicons-arrow-right-24:](development/install.md) @@ -99,13 +99,13 @@ interested in coding, testing, documentation, or providing feedback, your partic ### Getting Involved -- **Explore the Repository**: +- **Explore the Repository**: Visit our [:fontawesome-brands-github: GitHub repository](https://github.com/faberorg/fatpy) to access the source code. -- **Contribute**: +- **Contribute**: Follow our [:fontawesome-solid-handshake: Contribution Guidelines](development/contributing.md) to help improve FatPy. -- **Collaborate**: +- **Collaborate**: Join our [:fontawesome-solid-comment-dots: Discussions](https://github.com/faberorg/fatpy/discussions) or [:fontawesome-solid-bug: Report issues](https://github.com/faberorg/fatpy/issues). By participating, you'll be part of a collaborative effort to advance the field of material fatigue analysis. diff --git a/docs/overrides/main.html b/docs/overrides/main.html index 9b8b639..d059509 100644 --- a/docs/overrides/main.html +++ b/docs/overrides/main.html @@ -5,4 +5,4 @@ {{ super() }} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index b8b362d..d5c6092 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -17,11 +17,11 @@ --fatpy-heading2-color: var(--md-primary-fg-color--light); /* Defined custom colors for admonitions in light mode */ ---custom-note-color: #98b8d8; ---custom-abstract-color: #6490bc; ---custom-info-color: #296595; ---custom-tip-color: #034670; ---custom-example-color: #6a5acd; +--custom-note-color: #98b8d8; +--custom-abstract-color: #6490bc; +--custom-info-color: #296595; +--custom-tip-color: #034670; +--custom-example-color: #6a5acd; --custom-api-color: #98b8d8; /* Used for API doc section headers */ /* Define custom icons for admonitions - set to existing standard icons */ @@ -139,7 +139,7 @@ h2.doc-heading { /* Modified admonition styles for Abstract */ .md-typeset .admonition.abstract, .md-typeset details.abstract { - border-color: var(--custom-abstract-color); + border-color: var(--custom-abstract-color); } .md-typeset .abstract > .admonition-title, @@ -149,20 +149,20 @@ h2.doc-heading { .md-typeset .abstract > .admonition-title::before, .md-typeset .abstract > summary::before { - background-color: var(--custom-abstract-color); + background-color: var(--custom-abstract-color); /* -webkit-mask-image: var(--md-admonition-icon--custom-abstract); mask-image: var(--md-admonition-icon--custom-abstract); */ } .md-typeset .abstract > .admonition-title::after, .md-typeset .abstract > summary::after { - background-color: var(--custom-abstract-color); + background-color: var(--custom-abstract-color); } /* Modified admonition styles for Note */ .md-typeset .admonition.note, .md-typeset details.note { - border-color: var(--custom-note-color); + border-color: var(--custom-note-color); } .md-typeset .note > .admonition-title, @@ -172,18 +172,18 @@ h2.doc-heading { .md-typeset .note > .admonition-title::before, .md-typeset .note > summary::before { - background-color: var(--custom-note-color); + background-color: var(--custom-note-color); } .md-typeset .note > .admonition-title::after, .md-typeset .note > summary::after { - background-color: var(--custom-note-color); + background-color: var(--custom-note-color); } /* Modified admonition styles for Info */ .md-typeset .admonition.info, .md-typeset details.info { - border-color: var(--custom-info-color); + border-color: var(--custom-info-color); } .md-typeset .info > .admonition-title, @@ -193,18 +193,18 @@ h2.doc-heading { .md-typeset .info > .admonition-title::before, .md-typeset .info > summary::before { - background-color: var(--custom-info-color); + background-color: var(--custom-info-color); } .md-typeset .info > .admonition-title::after, .md-typeset .info > summary::after { - background-color: var(--custom-info-color); + background-color: var(--custom-info-color); } /* Modified admonition styles for Tip */ .md-typeset .admonition.tip, .md-typeset details.tip { - border-color: var(--custom-tip-color); + border-color: var(--custom-tip-color); } .md-typeset .tip > .admonition-title, @@ -214,18 +214,18 @@ h2.doc-heading { .md-typeset .tip > .admonition-title::before, .md-typeset .tip > summary::before { - background-color: var(--custom-tip-color); + background-color: var(--custom-tip-color); } .md-typeset .tip > .admonition-title::after, .md-typeset .tip > summary::after { - background-color: var(--custom-tip-color); + background-color: var(--custom-tip-color); } /* Modified admonition styles for Example */ .md-typeset .admonition.example, .md-typeset details.example { - border-color: var(--custom-example-color); + border-color: var(--custom-example-color); } .md-typeset .example > .admonition-title, @@ -235,12 +235,12 @@ h2.doc-heading { .md-typeset .example > .admonition-title::before, .md-typeset .example > summary::before { - background-color: var(--custom-example-color); + background-color: var(--custom-example-color); } .md-typeset .example > .admonition-title::after, .md-typeset .example > summary::after { - background-color: var(--custom-example-color); + background-color: var(--custom-example-color); } /* API Documentation styles */ @@ -261,7 +261,7 @@ div.doc-contents:not(.first) { .md-typeset .doc-contents .doc-function > h4, .md-typeset .doc-contents .doc-function > h5, .md-typeset .doc-contents .doc-function > h6 { - font-size: 1rem; + font-size: 1rem; font-weight: 600; color: var(--md-primary-fg-color--dark); margin-top: 0.6em; diff --git a/src/fatpy/core/stress_life/damage_params/s_eqa_goodman.py b/src/fatpy/core/stress_life/damage_params/s_eqa_goodman.py new file mode 100644 index 0000000..967141d --- /dev/null +++ b/src/fatpy/core/stress_life/damage_params/s_eqa_goodman.py @@ -0,0 +1,115 @@ +"""Goodman mean-stress correction for uniaxial S–N life. + +This module exposes :func:`s_eqa_goodman`, which computes the Goodman +equivalent stress amplitude and the corresponding cycles to failure: + + sigma_aeq = sigma_a / (1 - sigma_m / sigma_UTS) + N = 0.5 * (sigma_aeq / sigma_f) ** (1 / b) + +The implementation is vectorized (NumPy broadcasting) and unit-agnostic +(provided all inputs use consistent units). It returns `(N, sigma_aeq)`, +where `N` is rounded to the nearest integer and clipped to a minimum of 1. + +""" + +from __future__ import annotations + +from typing import Union +import warnings + +import numpy as np +from numpy.typing import ArrayLike, NDArray + + +Number = Union[int, float] + + +def s_eqa_goodman( + stress_amp: ArrayLike, + mean_stress: ArrayLike, + fat_strength_coef: ArrayLike | Number, + fat_strength_exp: ArrayLike | Number, + ult_stress: ArrayLike | Number, +) -> tuple[NDArray[np.int64], NDArray[np.float64]]: + """Stress-life (S–N) prediction using the Goodman mean-stress correction. + + Implements the Goodman mean-stress correction (Suresh, 1998): + + σ_a,eq = σ_a / (1 - σ_m / σ_UTS) + N = 0.5 * (σ_a,eq / σ_f) ** (1 / b) + + where `b < 0` is the Basquin exponent. + + Args: + stress_amp: σ_a. Stress amplitude(s). Must be > 0. + mean_stress: σ_m. Mean stress(es). + fat_strength_coef: σ_f. Basquin fatigue strength coefficient(s). Must be > 0. + fat_strength_exp: b. Basquin fatigue strength exponent(s). Must be < 0. + ult_stress: σ_UTS. Ultimate tensile strength(s). Must be > 0. + + Returns: + (N, sigma_aeq): + N: integer ndarray of predicted cycles to failure (rounded to nearest int, + clipped to a minimum of 1). + sigma_aeq: float ndarray of equivalent stress amplitude(s) per Goodman. + + Raises: + ValueError: If inputs violate constraints (e.g., b >= 0, σ_f <= 0, σ_UTS <= 0, + σ_a <= 0) or if any (σ_m / σ_UTS) >= 1, which would make the Goodman + denominator non-positive. + + Notes: + * Vectorized: all arguments broadcast via NumPy rules. + * If σ_a,eq > σ_f anywhere (which would give N < 1), a warning is issued. + In that case, N is clipped to a minimum of 1. + * Units must be consistent throughout (e.g., MPa or Pa). + """ + # Convert to arrays (float64) for safe broadcasting / math + sa = np.asarray(stress_amp, dtype=np.float64) + sm = np.asarray(mean_stress, dtype=np.float64) + sf = np.asarray(fat_strength_coef, dtype=np.float64) + b = np.asarray(fat_strength_exp, dtype=np.float64) + uts = np.asarray(ult_stress, dtype=np.float64) + + # Basic validation (constraints) + if np.any(sa <= 0): + raise ValueError("stress_amp (σ_a) must be > 0.") + if np.any(sf <= 0): + raise ValueError("fat_strength_coef (σ_f) must be > 0.") + if np.any(uts <= 0): + raise ValueError("ult_stress (σ_UTS) must be > 0.") + if np.any(b >= 0): + raise ValueError("fat_strength_exp (b) must be < 0 for Basquin.") + + # Goodman denominator + denom = 1.0 - (sm / uts) + if np.any(denom <= 0.0): + raise ValueError( + "Invalid mean stress: (σ_m / σ_UTS) must be < 1. " + "Goodman denominator became non-positive." + ) + + # Equivalent stress amplitude + sigma_aeq = sa / denom + + # Warn if σ_a,eq exceeds σ_f (implies N < 1) + if np.any(sigma_aeq > sf): + warnings.warn( + "Equivalent stress amplitude exceeds σ_f; " + "predicted life would be < 1 cycle. Clipping N to a minimum of 1.", + RuntimeWarning, + stacklevel=2, + ) + + # Basquin inversion: N = 0.5 * (σ_a,eq / σ_f) ** (1 / b) + with np.errstate(divide="raise", invalid="raise"): + N_float = 0.5 * (sigma_aeq / sf) ** (1.0 / b) + + # Enforce a minimum of 1 cycle to avoid N < 1.0 + N_float = np.maximum(N_float, 1.0) + + # Integer cycles as in your original function + N = np.rint(N_float).astype(np.int64) + + # Return both N and σ_a,eq + return N, sigma_aeq diff --git a/tests/stress_life/test_s_eqa_goodman.py b/tests/stress_life/test_s_eqa_goodman.py new file mode 100644 index 0000000..51e81fe --- /dev/null +++ b/tests/stress_life/test_s_eqa_goodman.py @@ -0,0 +1,123 @@ +# tests/stress_life/test_s_eqa_goodman.py +import numpy as np +import pytest + +# Import the function (not the module); alias to avoid name shadowing +from fatpy.core.stress_life.damage_params.s_eqa_goodman import ( + s_eqa_goodman as goodman_fn, +) + + +def test_returns_tuple_and_dtypes() -> None: + N, sigma_aeq = goodman_fn( + stress_amp=150.0, + mean_stress=25.0, + fat_strength_coef=400.0, + fat_strength_exp=-0.1, + ult_stress=700.0, + ) + # Accept NumPy scalars or arrays: + N_arr = np.asarray(N) + sig_arr = np.asarray(sigma_aeq) + + assert N_arr.dtype == np.int64 + assert sig_arr.dtype == np.float64 + + # For scalar inputs, function returns 0-D; this documents the behavior. + assert N_arr.ndim == 0 + assert sig_arr.ndim == 0 + + +def test_example_numeric_values() -> None: + """Concrete example to check both outputs.""" + sa = 180.0 + sm = 100.0 + sf = 475.4 + b = -0.078 + uts = 700.0 + + N, sigma_aeq = goodman_fn( + stress_amp=sa, + mean_stress=sm, + fat_strength_coef=sf, + fat_strength_exp=b, + ult_stress=uts, + ) + + # sigma_aeq = 180 / (1 - 100/700) = 210 + assert np.isclose(float(sigma_aeq), 210.0, rtol=0.0, atol=1e-12) + # N ≈ 17709 after rounding + assert int(N) == 17709 + + +def test_vectorization_and_broadcast() -> None: + sa = np.array([150.0, 180.0, 210.0]) + sm = np.array([0.0, 50.0, 100.0]) + + N, sigma_aeq = goodman_fn( + stress_amp=sa, + mean_stress=sm, + fat_strength_coef=475.4, + fat_strength_exp=-0.078, + ult_stress=700.0, + ) + + assert N.shape == (3,) + assert sigma_aeq.shape == (3,) + # More severe loading should not increase life + assert N[0] >= N[1] >= N[2] + # sigma_aeq increases with mean stress under Goodman + assert sigma_aeq[0] <= sigma_aeq[1] <= sigma_aeq[2] + + +def test_warns_and_clips_minimum_one_cycle() -> None: + """If σ_a,eq > σ_f, warn and clip N to at least 1.""" + with pytest.warns(RuntimeWarning, match="exceeds σ_f"): + N, sigma_aeq = goodman_fn( + stress_amp=300.0, + mean_stress=0.0, + fat_strength_coef=200.0, # small σ_f -> σ_a,eq > σ_f + fat_strength_exp=-0.08, + ult_stress=700.0, + ) + assert int(N) >= 1 + assert float(sigma_aeq) > 200.0 + + +def test_invalid_denominator_raises() -> None: + """Denominator must be positive: (σ_m / σ_UTS) < 1.""" + with pytest.raises(ValueError, match="denominator became non-positive"): + _ = goodman_fn( + stress_amp=100.0, + mean_stress=700.0, # equals UTS -> denom = 0 + fat_strength_coef=400.0, + fat_strength_exp=-0.1, + ult_stress=700.0, + ) + + +def test_invalid_inputs_raise() -> None: + with pytest.raises(ValueError): + _ = goodman_fn( + stress_amp=0.0, # must be > 0 + mean_stress=0.0, + fat_strength_coef=400.0, + fat_strength_exp=-0.1, + ult_stress=700.0, + ) + with pytest.raises(ValueError): + _ = goodman_fn( + stress_amp=100.0, + mean_stress=0.0, + fat_strength_coef=-1.0, # must be > 0 + fat_strength_exp=-0.1, + ult_stress=700.0, + ) + with pytest.raises(ValueError): + _ = goodman_fn( + stress_amp=100.0, + mean_stress=0.0, + fat_strength_coef=400.0, + fat_strength_exp=0.0, # b must be < 0 + ult_stress=700.0, + )