Skip to content

Tensile Strength Evaluation as part of Steady State Analysis - #34

Merged
pillowbeast merged 8 commits into
mainfrom
feature/tensile-strength
Dec 10, 2025
Merged

Tensile Strength Evaluation as part of Steady State Analysis#34
pillowbeast merged 8 commits into
mainfrom
feature/tensile-strength

Conversation

@pillowbeast

@pillowbeast pillowbeast commented Dec 8, 2025

Copy link
Copy Markdown
Collaborator

This pull request refactors and improves the stress and steady-state evaluation logic in the weac.analysis package. It introduces new result data classes and fixes plotting issues related to stress calculations and visualization.

Refactoring and Data Model Improvements

  • Introduced MaximalStressResult and SteadyStateResult data classes, replacing the old SSERRResult for clearer and more comprehensive result encapsulation. The new classes include normalized stress fields and system state at touchdown. ([[1]](https://github.com/2phi/weac/pull/34/files#diff-cd31ff029b0e336203154640e70d582b1554ca04f41c94048eaec4dee7daab6dL98-R129), [[2]](https://github.com/2phi/weac/pull/34/files#diff-cd31ff029b0e336203154640e70d582b1554ca04f41c94048eaec4dee7daab6dL110-R153), [[3]](https://github.com/2phi/weac/pull/34/files#diff-1b897e0700621752c15b1115da95c4b2a2d61aa57dde50d6b780bf490269b16cL11-R11), [[4]](https://github.com/2phi/weac/pull/34/files#diff-1b897e0700621752c15b1115da95c4b2a2d61aa57dde50d6b780bf490269b16cL21-R21))
  • Updated method names and references from SSERR to SteadyState for consistency and clarity, including renaming the evaluation method and result types. ([[1]](https://github.com/2phi/weac/pull/34/files#diff-cd31ff029b0e336203154640e70d582b1554ca04f41c94048eaec4dee7daab6dL644-R684), [[2]](https://github.com/2phi/weac/pull/34/files#diff-cd31ff029b0e336203154640e70d582b1554ca04f41c94048eaec4dee7daab6dL691-R734), [[3]](https://github.com/2phi/weac/pull/34/files#diff-1b897e0700621752c15b1115da95c4b2a2d61aa57dde50d6b780bf490269b16cL11-R11), [[4]](https://github.com/2phi/weac/pull/34/files#diff-1b897e0700621752c15b1115da95c4b2a2d61aa57dde50d6b780bf490269b16cL21-R21))

Stress Calculation Enhancements

  • Refactored the Sxx method in analyzer.py to support normalization of axial normal stress to tensile strength, improving numerical stability and result interpretability. ([[1]](https://github.com/2phi/weac/pull/34/files#diff-8468e03357e32957d46ec295646154a4a64e750fa249655a80c6620d9ad32440L228-R228), [[2]](https://github.com/2phi/weac/pull/34/files#diff-8468e03357e32957d46ec295646154a4a64e750fa249655a80c6620d9ad32440R242-R243), [[3]](https://github.com/2phi/weac/pull/34/files#diff-8468e03357e32957d46ec295646154a4a64e750fa249655a80c6620d9ad32440L261-R269), [[4]](https://github.com/2phi/weac/pull/34/files#diff-8468e03357e32957d46ec295646154a4a64e750fa249655a80c6620d9ad32440L277-R291))
  • Improved normalization logic in principal_stress_slab to correctly convert tensile strength units and normalize principal stresses. ([[1]](https://github.com/2phi/weac/pull/34/files#diff-8468e03357e32957d46ec295646154a4a64e750fa249655a80c6620d9ad32440R451-R452), [[2]](https://github.com/2phi/weac/pull/34/files#diff-8468e03357e32957d46ec295646154a4a64e750fa249655a80c6620d9ad32440L463-R478))
  • Added _calculate_maximal_stresses helper method to centralize maximal stress computation for steady-state evaluation. ([src/weac/analysis/criteria_evaluator.pyR1211-R1241](https://github.com/2phi/weac/pull/34/files#diff-cd31ff029b0e336203154640e70d582b1554ca04f41c94048eaec4dee7daab6dR1211-R1241))

Plotting and Visualization Fixes

  • Updated plot_deformed in plotter.py to robustly handle NaN values in weak-layer coordinates, ensuring correct plotting and preventing errors when visualizing stress/displacement fields. ([[1]](https://github.com/2phi/weac/pull/34/files#diff-737cdaa926ddb7c8e3803d8568adddbe44745a23baca2d6bd39fc27e8a41a1dcL888-R934), [[2]](https://github.com/2phi/weac/pull/34/files#diff-737cdaa926ddb7c8e3803d8568adddbe44745a23baca2d6bd39fc27e8a41a1dcL971-R978))

These changes collectively enhance the clarity of stress analysis and steady-state evaluation in the codebase.

Summary by CodeRabbit

  • New Features

    • Per-layer stress normalization option and a new deformation visualization routine with selectable fields and normalization.
  • Bug Fixes

    • Robust handling and masking of NaN/invalid weak-layer coordinates in deformation plotting.
  • Refactor

    • Steady‑state evaluation payload reorganized and renamed: includes energy-release metric, detailed maximal-stress metrics, and system info.
  • Tests

    • Added tests for stress unit conversion and normalization consistency.
  • Chores

    • Gitignore reinstates .weac-reference and adds a dev/ entry.

✏️ Tip: You can customize this high-level summary in your review settings.

Copilot AI review requested due to automatic review settings December 8, 2025 13:12
@coderabbitai

coderabbitai Bot commented Dec 8, 2025

Copy link
Copy Markdown
Contributor

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Renames steady‑state API and result types (evaluate_SSERR → evaluate_SteadyState; SSERRResult → MaximalStressResult + new SteadyStateResult), adds per‑layer stress normalization options in Analyzer, refactors NaN/finite handling in plotting, adds plot_visualize_deformation, updates tests, and updates .gitignore.

Changes

Cohort / File(s) Summary
API & Result types
src/weac/analysis/__init__.py, src/weac/analysis/criteria_evaluator.py
Replaces SSERRResult with MaximalStressResult, introduces SteadyStateResult, renames evaluate_SSERRevaluate_SteadyState, adds _calculate_maximal_stresses, and updates exports and result payload fields (SSERRenergy_release_rate, adds maximal_stress_result and system).
Stress calculations (normalization + units)
src/weac/analysis/analyzer.py
Adds normalize: bool = False parameter to Sxx, Txz, Szz, and principal_stress_slab; computes MPa intermediates, applies per‑layer normalization to tensile strength when requested, short‑circuits to return normalized (dimensionless) arrays, and preserves existing unit conversion when not normalizing.
Plotting / visualization
src/weac/analysis/plotter.py
Updates plot_deformed to filter NaN/finite weak‑layer coordinates and use masked selections for weak‑layer fields; removes per‑field invalid‑input fallback; adds new plot_visualize_deformation(...) API supporting multiple fields and normalization for standalone deformation visualization.
Tests
tests/analysis/test_analyzer.py, tests/analysis/test_criteria_evaluator.py, tests/test_regression_simulation.py
Adds test_stress_fields_unit_conversion; updates tests to call evaluate_SteadyState and assert on SteadyStateResult / energy_release_rate; validates unit consistency and normalization behavior and updates type/assert expectations to new dataclasses/fields.
Git configuration
.gitignore
Reinstates ignoring .weac-reference/ and adds dev/ to ignore list.

Sequence Diagram(s)

sequenceDiagram
    participant Test as Tests/CLI
    participant CE as CriteriaEvaluator
    participant Sys as SystemModel
    participant AN as Analyzer
    participant PL as Plotter

    Test->>CE: evaluate_SteadyState(system=Sys, ...)
    CE->>Sys: read system state
    CE->>AN: _calculate_maximal_stresses(system)
    AN->>AN: compute Sxx/Txz/Szz (optionally normalize)
    AN-->>CE: MaximalStressResult (kPa arrays + norm arrays + maxima)
    CE->>CE: compute energy_release_rate
    CE-->>Test: SteadyStateResult(..., energy_release_rate, maximal_stress_result, system)

    Test->>PL: plot_visualize_deformation(..., analyzer=AN, normalize=...)
    PL->>AN: request stresses/displacements (respecting normalize)
    AN-->>PL: stress/displacement arrays (finite-masked or normalized)
    PL-->>Test: Figure / saved file
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Areas needing focused review:
    • Analyzer normalization branches, MPa intermediate handling, and unit conversion consistency.
    • New/renamed dataclasses in criteria_evaluator.py and all call sites (tests and other modules).
    • _calculate_maximal_stresses implementation and kPa vs normalized field semantics.
    • plot_deformed finite/mask handling and the new plot_visualize_deformation API (indexing, masking, and colormap/normalization).

Possibly related PRs

Suggested reviewers

  • zacczakk

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main focus of the PR: adding tensile strength evaluation within the steady-state analysis framework, which aligns with the core refactoring of result classes, normalization features, and consistent renaming throughout the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/tensile-strength

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/weac/analysis/criteria_evaluator.py (1)

708-711: Potential AttributeError if original system has touchdown=False.

Line 711 accesses system.slab_touchdown.l_BC on the original system before the copy. If system.config.touchdown is False, then system.slab_touchdown is None (per SystemModel.slab_touchdown cached property), causing an AttributeError.

         system_copy = copy.deepcopy(system)
         system_copy.config.touchdown = True
         system_copy.update_scenario(scenario_config=ScenarioConfig(phi=0.0))
-        l_BC = system.slab_touchdown.l_BC
+        # Ensure touchdown is computed on the copy, not the original
+        if system_copy.slab_touchdown is None:
+            raise ValueError("Touchdown analysis failed to initialize.")
+        l_BC = system_copy.slab_touchdown.l_BC
tests/analysis/test_criteria_evaluator.py (1)

186-211: New SteadyState test provides good coverage of the expanded result payload

This test drives evaluate_SteadyState through the recommended (vertical=False) path and validates key invariants on energy_release_rate, touchdown distance, and the normalized maximal stresses, which is a solid sanity/regression check for the new SteadyStateResult/MaximalStressResult API. If you later want stronger regression guarantees, you could extend this to assert approximate expected magnitudes or array shapes of the underlying stress fields, but the current level looks appropriate for now.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2235508 and b4b72ad.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • .gitignore (1 hunks)
  • src/weac/analysis/__init__.py (2 hunks)
  • src/weac/analysis/analyzer.py (11 hunks)
  • src/weac/analysis/criteria_evaluator.py (5 hunks)
  • src/weac/analysis/plotter.py (3 hunks)
  • tests/analysis/test_analyzer.py (1 hunks)
  • tests/analysis/test_criteria_evaluator.py (3 hunks)
  • tests/test_regression_simulation.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
tests/test_regression_simulation.py (1)
src/weac/analysis/criteria_evaluator.py (1)
  • evaluate_SteadyState (679-735)
src/weac/analysis/__init__.py (1)
src/weac/analysis/criteria_evaluator.py (1)
  • SteadyStateResult (127-153)
tests/analysis/test_analyzer.py (1)
src/weac/analysis/analyzer.py (3)
  • rasterize_solution (95-176)
  • Sxx (228-291)
  • principal_stress_slab (432-502)
tests/analysis/test_criteria_evaluator.py (1)
src/weac/analysis/criteria_evaluator.py (2)
  • SteadyStateResult (127-153)
  • evaluate_SteadyState (679-735)
src/weac/analysis/analyzer.py (2)
src/weac/core/system_model.py (2)
  • z (365-412)
  • fq (146-148)
src/weac/core/field_quantities.py (1)
  • du_dx (64-66)
src/weac/analysis/plotter.py (2)
src/weac/analysis/analyzer.py (6)
  • Sxx (228-291)
  • Txz (294-359)
  • Szz (362-429)
  • principal_stress_slab (432-502)
  • principal_stress_weaklayer (505-564)
  • get_zmesh (179-225)
src/weac/core/field_quantities.py (4)
  • u (55-62)
  • w (68-70)
  • sig (101-103)
  • tau (105-114)
src/weac/analysis/criteria_evaluator.py (2)
src/weac/core/system_model.py (1)
  • SystemModel (37-412)
src/weac/analysis/analyzer.py (5)
  • differential_ERR (638-690)
  • Analyzer (50-823)
  • rasterize_solution (95-176)
  • Sxx (228-291)
  • principal_stress_slab (432-502)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Agent
🔇 Additional comments (10)
src/weac/analysis/__init__.py (1)

11-11: LGTM! Public API export updated correctly.

The import and __all__ export are properly updated to expose SteadyStateResult instead of the deprecated SSERRResult, consistent with the renamed dataclass in criteria_evaluator.py.

Also applies to: 21-21

src/weac/analysis/analyzer.py (2)

350-356: Same division-by-zero risk applies here.

The same guard for positive tensile_strength should be applied consistently across Sxx, Txz, and Szz normalization paths.


493-499: LGTM! Unit conversion logic is correct for normalization.

The conversion chain properly handles both kPa and MPa units: the tensile strength is converted to match Ps's unit before division, yielding a correct dimensionless ratio.

tests/analysis/test_analyzer.py (1)

82-116: LGTM! Good test coverage for unit conversion and normalization.

The test properly validates:

  1. Unit conversion relationship (kPa = MPa * 1e3)
  2. Normalization produces unit-independent (dimensionless) results

This aligns well with the implementation changes in analyzer.py.

src/weac/analysis/plotter.py (2)

888-897: LGTM! NaN filtering correctly applied to weak-layer coordinates.

The nanmask is properly used to filter xwl before meshgrid creation and to slice displacement fields, ensuring consistent array shapes for plotting.


900-949: Consistent NaN masking across all field types.

All field computation cases correctly apply nanmask or use xwl_finite dimensions, ensuring array shape consistency for contour plotting.

src/weac/analysis/criteria_evaluator.py (3)

97-124: LGTM! Well-structured dataclass for maximal stress results.

The MaximalStressResult dataclass clearly separates raw stress values (kPa) from normalized values, and provides convenient scalar maxima. Good design.


126-153: LGTM! Comprehensive steady-state result structure.

The SteadyStateResult dataclass provides a complete evaluation payload including convergence status, energy metrics, stress results, and the modified system model for further analysis.


1212-1241: LGTM! Clean implementation of maximal stress calculation.

The helper method correctly computes both raw (kPa) and normalized stress fields, extracts scalar maxima, and packages everything into a MaximalStressResult. The implementation is consistent with the Analyzer API.

tests/analysis/test_criteria_evaluator.py (1)

12-17: SteadyStateResult import matches updated evaluator API

Importing SteadyStateResult alongside the other result types keeps the tests aligned with the new public API and enables precise type annotations; no issues here.

Comment thread .gitignore
Comment thread src/weac/analysis/analyzer.py
Comment thread src/weac/analysis/plotter.py
Comment thread src/weac/analysis/plotter.py Outdated
Comment thread src/weac/analysis/plotter.py
Comment thread tests/test_regression_simulation.py

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the stress and steady-state evaluation logic by introducing clearer data models and adding tensile strength normalization capabilities. The changes rename SSERR to SteadyState for better clarity and introduce new result classes to encapsulate maximal stress information at touchdown.

Key Changes

  • Introduced MaximalStressResult and SteadyStateResult dataclasses to replace SSERRResult, providing better structure for stress evaluation results
  • Added normalization support to Sxx, Txz, Szz, and principal_stress_slab methods, enabling stress values to be expressed as ratios of tensile strength
  • Fixed NaN handling in plotting functions to prevent errors when visualizing systems with partially cracked weak layers

Reviewed changes

Copilot reviewed 7 out of 10 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
uv.lock Version bump from 3.0.1 to 3.0.2
tests/test_regression_simulation.py Updated test to use renamed evaluate_SteadyState method and energy_release_rate attribute
tests/analysis/test_criteria_evaluator.py Updated test names, imports, and assertions for renamed classes/methods; added tests for new maximal stress fields
tests/analysis/test_analyzer.py Added comprehensive tests for stress field unit conversion and normalization behavior
src/weac/analysis/criteria_evaluator.py Added MaximalStressResult dataclass; renamed SSERRResult to SteadyStateResult with enhanced fields; added _calculate_maximal_stresses helper method
src/weac/analysis/analyzer.py Added normalize parameter to Sxx, Txz, Szz methods; fixed unit conversion in principal_stress_slab normalization
src/weac/analysis/plotter.py Improved NaN handling in plot_deformed; added new plot_visualize_deformation method with enhanced coordinate system management
src/weac/analysis/init.py Updated exports to use SteadyStateResult instead of SSERRResult
.gitignore Added dev/ folder to ignore list
Comments suppressed due to low confidence (3)

src/weac/analysis/analyzer.py:383

  • The return type documentation should specify that when normalize=True, the returned values are dimensionless ratios (stress/tensile_strength) rather than values in the specified unit. For example: "Transverse normal stress at grid points in the slab in specified unit, or dimensionless ratio if normalized."
        Returns
        -------
        ndarray, float
            Transverse normal stress at grid points in the slab in
            specified unit.

src/weac/analysis/analyzer.py:248

  • The return type documentation should specify that when normalize=True, the returned values are dimensionless ratios (stress/tensile_strength) rather than values in the specified unit. For example: "Axial slab normal stress in specified unit, or dimensionless ratio if normalized."
        Returns
        -------
        ndarray, float
            Axial slab normal stress in specified unit.

src/weac/analysis/analyzer.py:314

  • The return type documentation should specify that when normalize=True, the returned values are dimensionless ratios (stress/tensile_strength) rather than values in the specified unit. For example: "Shear stress at grid points in the slab in specified unit, or dimensionless ratio if normalized."
        Returns
        -------
        ndarray
            Shear stress at grid points in the slab in specified unit.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/weac/analysis/criteria_evaluator.py Outdated
Comment thread src/weac/analysis/analyzer.py Outdated
Comment thread src/weac/analysis/analyzer.py Outdated
Comment thread src/weac/analysis/plotter.py Outdated
Comment thread src/weac/analysis/analyzer.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/weac/analysis/analyzer.py (1)

284-290: Division by zero risk when tensile_strength is zero or near-zero.

This issue was previously flagged. If any layer has zero or very small tensile_strength, dividing by tensile_strength_MPa will produce inf or numerically unstable results. The same pattern exists in Txz and Szz normalization blocks.

Consider adding validation before normalization:

         # Normalize tensile stresses to tensile strength
         if normalize:
             tensile_strength_kPa = zmesh["tensile_strength"]
             tensile_strength_MPa = tensile_strength_kPa / 1e3
+            if np.any(tensile_strength_MPa <= 0):
+                raise ValueError(
+                    "Cannot normalize: tensile_strength must be positive for all layers."
+                )
             # Normalize axial normal stress to layers' tensile strength
             normalized_Sxx = Sxx_MPa / tensile_strength_MPa[:, None]
             return normalized_Sxx
src/weac/analysis/plotter.py (1)

911-925: Bug: normalize parameter not passed to Sxx, Txz, Szz methods.

The normalize parameter is accepted by plot_deformed but is only used for the "principal" case. For "Sxx", "Txz", and "Szz" fields, normalization is never applied, and labels always show (kPa) regardless of the normalize flag.

Compare with plot_visualize_deformation (lines 1184, 1191, 1196) where normalize is correctly passed.

Apply this diff to fix:

             # Axial normal stresses (kPa)
             case "Sxx":
-                slab = analyzer.Sxx(z, phi, dz=dz, unit="kPa")
+                slab = analyzer.Sxx(z, phi, dz=dz, unit="kPa", normalize=normalize)
                 weak = np.zeros(xwl_finite.shape[0])
-                label = r"$\sigma_{xx}$ (kPa)"
+                label = r"$\sigma_{xx}/\sigma_+$" if normalize else r"$\sigma_{xx}$ (kPa)"
             # Shear stresses (kPa)
             case "Txz":
-                slab = analyzer.Txz(z, phi, dz=dz, unit="kPa")
+                slab = analyzer.Txz(z, phi, dz=dz, unit="kPa", normalize=normalize)
                 weak = Tauwl[nanmask]
-                label = r"$\tau_{xz}$ (kPa)"
+                label = r"$\tau_{xz}/\sigma_+$" if normalize else r"$\tau_{xz}$ (kPa)"
             # Transverse normal stresses (kPa)
             case "Szz":
-                slab = analyzer.Szz(z, phi, dz=dz, unit="kPa")
+                slab = analyzer.Szz(z, phi, dz=dz, unit="kPa", normalize=normalize)
                 weak = Sigmawl[nanmask]
-                label = r"$\sigma_{zz}$ (kPa)"
+                label = r"$\sigma_{zz}/\sigma_+$" if normalize else r"$\sigma_{zz}$ (kPa)"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b4b72ad and ab27c4a.

📒 Files selected for processing (3)
  • src/weac/analysis/analyzer.py (11 hunks)
  • src/weac/analysis/criteria_evaluator.py (5 hunks)
  • src/weac/analysis/plotter.py (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/weac/analysis/analyzer.py (2)
src/weac/core/system_model.py (2)
  • z (365-412)
  • fq (146-148)
src/weac/core/field_quantities.py (1)
  • du_dx (64-66)
src/weac/analysis/criteria_evaluator.py (1)
src/weac/analysis/analyzer.py (4)
  • differential_ERR (643-695)
  • rasterize_solution (95-176)
  • Sxx (228-293)
  • principal_stress_slab (437-507)
🔇 Additional comments (9)
src/weac/analysis/criteria_evaluator.py (3)

97-124: LGTM! Well-structured dataclass for stress results.

The MaximalStressResult dataclass clearly separates absolute values (kPa) from normalized values, and the docstring adequately documents each field's purpose.


126-154: LGTM! Clean API design for steady-state evaluation results.

The SteadyStateResult dataclass provides a comprehensive result structure including the computed stresses and the system state at touchdown.


726-735: LGTM! Proper integration of maximal stress calculation into steady-state evaluation.

The method correctly unpacks the differential ERR and delegates stress computation to the helper method.

src/weac/analysis/analyzer.py (2)

228-246: LGTM! Comprehensive docstring for the normalize parameter.

The updated documentation clearly explains that when normalize=True, values are returned as dimensionless ratios and the unit parameter is ignored.


476-507: LGTM! Correct unit conversion for principal stress normalization.

The conversion logic properly handles both kPa and MPa units:

  • tensile_strength_kPa / 1e3 converts to MPa
  • Multiplying by convert[unit] scales back appropriately

The principal stress is computed in the requested unit first, then normalized by the tensile strength in the same unit—this maintains dimensional consistency.

src/weac/analysis/plotter.py (4)

888-897: LGTM! Correct NaN handling for weak-layer coordinates.

The finite-mask approach properly filters out NaN values from xwl before constructing the mesh grids, preventing plotting errors when weak-layer regions are not fully defined.


1040-1081: LGTM! Well-structured new visualization method.

The plot_visualize_deformation method provides a clean normalized coordinate system for visualization, with proper handling of slab and weak-layer proportions. The docstring adequately describes the parameters and return value.


1182-1200: LGTM! Correct label handling for normalized stress fields.

Unlike plot_deformed, this method correctly passes normalize to the stress computation methods and updates labels to show dimensionless ratios when normalization is enabled.


1245-1250: xsl[~nanmask] is correct—both arrays have identical shapes by construction.

The nanmask derived from xwl can safely be applied to xsl because both originate from a single rasterize_solution() call at line 1698 and are initialized with size ni.sum() in the function definition. Using xsl (not xwl) is necessary because xwl contains NaN values at cracked positions, while xsl preserves the coordinate values at those positions for the cracked weak-layer outline plot.

Comment thread src/weac/analysis/criteria_evaluator.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/weac/analysis/criteria_evaluator.py (1)

126-153: SteadyStateResult extension matches new steady-state API; consider clarifying units

The added fields (energy_release_rate, maximal_stress_result, system) make the result richer and consistent with the new evaluation flow. For future readers, it would help to explicitly document the physical units for touchdown_distance and energy_release_rate in the docstring.

♻️ Duplicate comments (2)
src/weac/analysis/plotter.py (2)

1243-1248: Bug: nanmask derived from xwl is incorrectly applied to xsl.

nanmask is computed from xwl (line 1145), but here it's applied to xsl. If xsl and xwl have different shapes or NaN patterns, this will produce incorrect cracked region coordinates or raise an IndexError.

Apply this diff to fix:

         # Plot cracked weak-layer outline (where there is no weak layer)
-        xwl_cracked = xsl[~nanmask]
+        xwl_cracked = xwl[~nanmask]
         Xwl_cracked, Zwl_cracked_plot = np.meshgrid(
             1e-1 * xwl_cracked,
             [slab_proportion + cracked_proportion, total_height_plot],
         )

1168-1217: Consider extracting shared field computation logic.

The match field block is nearly identical between plot_deformed and plot_visualize_deformation. Extracting this into a helper method would reduce ~50 lines of duplication and improve maintainability.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ab27c4a and d25fa03.

📒 Files selected for processing (2)
  • src/weac/analysis/criteria_evaluator.py (5 hunks)
  • src/weac/analysis/plotter.py (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/weac/analysis/criteria_evaluator.py (2)
src/weac/core/system_model.py (1)
  • SystemModel (37-412)
src/weac/analysis/analyzer.py (5)
  • differential_ERR (643-695)
  • Analyzer (50-828)
  • rasterize_solution (95-176)
  • Sxx (228-293)
  • principal_stress_slab (437-507)
src/weac/analysis/plotter.py (3)
src/weac/analysis/analyzer.py (6)
  • Sxx (228-293)
  • Txz (296-362)
  • Szz (365-434)
  • principal_stress_slab (437-507)
  • principal_stress_weaklayer (510-569)
  • get_zmesh (179-225)
src/weac/core/system_model.py (2)
  • z (365-412)
  • fq (146-148)
src/weac/core/field_quantities.py (4)
  • u (55-62)
  • w (68-70)
  • sig (101-103)
  • tau (105-114)
🔇 Additional comments (7)
src/weac/analysis/plotter.py (5)

888-897: LGTM!

The NaN filtering approach is correct. Filtering xwl to finite values before constructing the weak-layer grid and displacement fields prevents NaN propagation issues.


902-910: LGTM!

The nanmask indexing on Usl[-1, :] and Wsl[-1, :] correctly extracts values corresponding to finite xwl positions, maintaining consistency with the filtered weak-layer grid.


911-928: LGTM!

The labels now correctly reflect the normalization state: dimensionless ratios (σxx/σ+) when normalize=True, and units (kPa) when False. This addresses the previous review feedback about incorrect labels.


930-947: LGTM!

The principal stress handling correctly filters weak_full using nanmask to align with finite weak-layer coordinates, and the labels appropriately reflect the normalization state.


972-979: LGTM!

The deformed weak-layer outline now uses the consistently-sized Xwl, Zwl, Uwl, and Wwl arrays built from finite xwl values.

src/weac/analysis/criteria_evaluator.py (2)

97-124: MaximalStressResult dataclass and documentation look consistent

The fields and docstring align with how _calculate_maximal_stresses populates principal and axial stresses (both absolute and normalized); no issues from my side here.


151-152: Verify energy_release_rate type hint matches differential_ERR output

SteadyStateResult.energy_release_rate is annotated as float, while it is populated via:

energy_release_rate, _, _ = analyzer.differential_ERR(unit="J/m^2")

In other parts of the code, differential_ERR appears to return arrays that are then indexed (e.g., diff_energy[1], diff_energy[2]). Please double-check that the first return value here is indeed a scalar steady-state value rather than an array; if it is an array, either:

  • adjust the type hint for energy_release_rate, or
  • reduce the array to the intended scalar summary before assigning.

Also applies to: 726-727

Comment thread src/weac/analysis/criteria_evaluator.py
Comment thread src/weac/analysis/criteria_evaluator.py Outdated
Comment thread src/weac/analysis/plotter.py
Comment thread src/weac/analysis/plotter.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
src/weac/analysis/plotter.py (2)

1116-1120: Consider adding a lower bound for weaklayer_proportion.

When max_w_displacement is very small but non-zero, the calculated weaklayer_proportion could become extremely small (close to 0), making the weak layer region invisible. Consider adding a minimum threshold:

         if weaklayer_proportion is None:
             if max_w_displacement > 0:
-                weaklayer_proportion = min(0.3, (h_cm / max_w_displacement) * 0.1)
+                weaklayer_proportion = max(0.05, min(0.3, (h_cm / max_w_displacement) * 0.1))
             else:
                 weaklayer_proportion = 0.3

1170-1219: Significant code duplication with plot_deformed.

The match field block (lines 1171-1219) is nearly identical to the one in plot_deformed (lines 900-947). Consider extracting this into a helper method to reduce duplication:

def _compute_field_values(self, field, Usl, Wsl, Sigmawl, Tauwl, nanmask, 
                          xwl_finite, analyzer, z, phi, dz, normalize):
    """Compute field values for slab and weak layer plotting."""
    match field:
        case "u":
            slab = 1e4 * Usl
            weak = 1e4 * Usl[-1, nanmask]
            label = r"$u$ ($\mu$m)"
        # ... other cases
    return slab, weak, label

This would make both plotting methods call the shared helper.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d25fa03 and 50797e9.

📒 Files selected for processing (2)
  • src/weac/analysis/criteria_evaluator.py (5 hunks)
  • src/weac/analysis/plotter.py (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/weac/analysis/plotter.py (1)
src/weac/analysis/analyzer.py (6)
  • Sxx (228-293)
  • Txz (296-362)
  • Szz (365-434)
  • principal_stress_slab (437-507)
  • principal_stress_weaklayer (510-569)
  • get_zmesh (179-225)
src/weac/analysis/criteria_evaluator.py (2)
src/weac/core/system_model.py (1)
  • SystemModel (37-412)
src/weac/analysis/analyzer.py (2)
  • differential_ERR (643-695)
  • Analyzer (50-828)
🔇 Additional comments (8)
src/weac/analysis/plotter.py (4)

888-897: LGTM! NaN handling for weak layer coordinates is well-implemented.

The filtering logic correctly:

  1. Creates nanmask from xwl using np.isfinite
  2. Extracts only finite coordinates for meshgrid construction
  3. Applies the mask consistently to displacement fields

This prevents plotting errors when weak layer coordinates contain NaN values.


911-928: Labels correctly updated for normalization-aware display.

The labels now properly reflect dimensionless ratios when normalize=True (e.g., r"$\sigma_{xx}/\sigma_+$") and include units when normalize=False (e.g., r"$\sigma_{xx}$ (kPa)"). This addresses the inconsistency previously noted.


1038-1081: Docstring is complete and well-documented.

The levels parameter is now properly documented (line 1068-1069), addressing the previous review comment about the missing parameter documentation.


1245-1258: No defensive check needed for array shape alignment.

xsl and xwl are guaranteed to have identical shapes since rasterize_solution returns both as arrays of size ni.sum(). The shape alignment is by design, not an assumption, so adding an assertion is unnecessary.

Likely an incorrect or invalid review comment.

src/weac/analysis/criteria_evaluator.py (4)

98-124: Well-documented dataclass with clear field descriptions.

The MaximalStressResult dataclass properly documents all fields, including both absolute (kPa) and normalized stress values. The naming convention clearly distinguishes between the two forms.


126-154: New SteadyStateResult dataclass provides comprehensive result encapsulation.

The dataclass properly includes:

  • Convergence status and message
  • Touchdown distance
  • Energy release rate (renamed from SSERR for clarity)
  • Maximal stress results
  • The modified system model

Including the system field is a good design choice as it allows callers to inspect or reuse the modified system state.


726-739: print_call_stats is now correctly honored.

The implementation now:

  1. Forwards print_call_stats to _calculate_maximal_stresses (line 728)
  2. Calls analyzer.print_call_stats() when the flag is True (lines 730-731)

This addresses the previous review comment about the flag having no visible effect.


1216-1262: Well-implemented helper with proper documentation and explicit mode.

The _calculate_maximal_stresses method:

  1. Has complete docstring with parameters and return type documented
  2. Explicitly specifies mode="cracked" in rasterize_solution (line 1238)
  3. Properly invokes analyzer.print_call_stats() when the flag is True (lines 1251-1254)

All previous review concerns have been addressed.

Comment thread src/weac/analysis/criteria_evaluator.py
Comment thread src/weac/analysis/plotter.py
Comment thread src/weac/analysis/analyzer.py Outdated
@pillowbeast
pillowbeast dismissed zacczakk’s stale review December 10, 2025 15:12

I made a small change and created the issue, so we will remember more in-depth for the future.
Not sure if you will have time, so I will just dismiss the review for now.

@pillowbeast
pillowbeast merged commit 8f52770 into main Dec 10, 2025
3 of 4 checks passed
@pillowbeast
pillowbeast deleted the feature/tensile-strength branch December 10, 2025 15:12

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (4)
src/weac/analysis/analyzer.py (2)

284-290: Add guard against division by zero in normalization.

The past review comment about division by zero remains unaddressed. If any layer has tensile_strength ≤ 0, the normalization will produce inf or numerically unstable results.

Apply a guard before normalization:

         # Normalize tensile stresses to tensile strength
         if normalize:
             tensile_strength_kPa = zmesh["tensile_strength"]
             tensile_strength_MPa = tensile_strength_kPa / 1e3
+            if np.any(tensile_strength_MPa <= 0):
+                raise ValueError("Cannot normalize: tensile_strength must be positive for all layers.")
             # Normalize axial normal stress to layers' tensile strength
             normalized_Sxx = Sxx_MPa / tensile_strength_MPa[:, None]
             return normalized_Sxx

425-431: Add guard against division by zero in normalization.

Same issue as Sxx and Txz: dividing by tensile_strength_MPa without checking for zero/negative values.

Apply the same guard:

         # Normalize tensile stresses to tensile strength
         if normalize:
             tensile_strength_kPa = zmesh["tensile_strength"]
             tensile_strength_MPa = tensile_strength_kPa / 1e3
+            if np.any(tensile_strength_MPa <= 0):
+                raise ValueError("Cannot normalize: tensile_strength must be positive for all layers.")
             # Normalize transverse normal stress to layers' tensile strength
             normalized_Szz = Szz_MPa / tensile_strength_MPa[:, None]
             return normalized_Szz
src/weac/analysis/plotter.py (2)

1170-1219: Consider extracting shared field computation logic.

This match field block is nearly identical to the one in plot_deformed (lines 900-947). Extract this into a helper method to reduce duplication and improve maintainability.

Example helper method:

def _compute_field_values(
    self, 
    field: str,
    z: np.ndarray,
    analyzer: Analyzer,
    phi: float,
    dz: int,
    normalize: bool,
    Usl: np.ndarray,
    Wsl: np.ndarray,
    Sigmawl: np.ndarray,
    Tauwl: np.ndarray,
    nanmask: np.ndarray,
    xwl_finite_shape: int,
) -> tuple[np.ndarray, np.ndarray, str]:
    """Compute field values for slab and weak layer."""
    # ... shared field computation logic
    return slab, weak, label

1245-1250: Bug: nanmask applied to wrong array.

nanmask is derived from xwl (line 1147), but line 1246 applies it to xsl. Since xsl and xwl may have different shapes or NaN patterns, this produces incorrect cracked-region coordinates or dimension misalignment.

Fix:

         # Plot cracked weak-layer outline (where there is no weak layer)
-        xwl_cracked = xsl[~nanmask]
+        xwl_cracked = xwl[~nanmask]
         Xwl_cracked, Zwl_cracked_plot = np.meshgrid(
             1e-1 * xwl_cracked,
             [slab_proportion + cracked_proportion, total_height_plot],
         )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 50797e9 and 59dd3a7.

📒 Files selected for processing (2)
  • src/weac/analysis/analyzer.py (11 hunks)
  • src/weac/analysis/plotter.py (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/weac/analysis/analyzer.py (1)
src/weac/core/field_quantities.py (1)
  • du_dx (64-66)
src/weac/analysis/plotter.py (2)
src/weac/analysis/analyzer.py (6)
  • Sxx (228-293)
  • Txz (296-362)
  • Szz (365-434)
  • principal_stress_slab (437-507)
  • principal_stress_weaklayer (510-569)
  • get_zmesh (179-225)
src/weac/core/field_quantities.py (4)
  • u (55-62)
  • w (68-70)
  • sig (101-103)
  • tau (105-114)
🔇 Additional comments (3)
src/weac/analysis/analyzer.py (1)

215-217: LGTM: Helpful unit annotation.

The comment clarifying that tensile_strength is in kPa improves code readability and aligns with the subsequent normalization logic that converts to MPa.

src/weac/analysis/plotter.py (2)

888-897: LGTM: Robust NaN handling for weak-layer coordinates.

The filtering of NaN values from xwl before creating the meshgrid prevents plotting errors and ensures correct visualization. The displacement fields Uwl and Wwl are properly assembled using the nanmask.


911-928: LGTM: Correctly updated labels for normalized fields.

The labels now properly reflect whether normalization is enabled, showing dimensionless ratios (e.g., σ_xx/σ_+) when normalize=True or units (e.g., σ_xx (kPa)) when normalize=False. This addresses a past review comment.

Comment on lines +353 to +359
# Normalize shear stresses to tensile strength
if normalize:
tensile_strength_kPa = zmesh["tensile_strength"]
tensile_strength_MPa = tensile_strength_kPa / 1e3
# Normalize shear stress to layers' tensile strength
normalized_Txz = Txz_MPa / tensile_strength_MPa[:, None]
return normalized_Txz

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add guard against division by zero and clarify normalization approach.

Two concerns:

  1. Division by zero: Same issue as Sxx - dividing by tensile_strength_MPa without checking for zero/negative values.

  2. Normalization approach: Normalizing shear stress by tensile strength (rather than shear strength) is unconventional. If this is intentional for a specific physical reason, please document why in the docstring or add a reference.

Add the same guard as suggested for Sxx:

         # Normalize shear stresses to tensile strength
         if normalize:
             tensile_strength_kPa = zmesh["tensile_strength"]
             tensile_strength_MPa = tensile_strength_kPa / 1e3
+            if np.any(tensile_strength_MPa <= 0):
+                raise ValueError("Cannot normalize: tensile_strength must be positive for all layers.")
             # Normalize shear stress to layers' tensile strength
             normalized_Txz = Txz_MPa / tensile_strength_MPa[:, None]
             return normalized_Txz
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Normalize shear stresses to tensile strength
if normalize:
tensile_strength_kPa = zmesh["tensile_strength"]
tensile_strength_MPa = tensile_strength_kPa / 1e3
# Normalize shear stress to layers' tensile strength
normalized_Txz = Txz_MPa / tensile_strength_MPa[:, None]
return normalized_Txz
# Normalize shear stresses to tensile strength
if normalize:
tensile_strength_kPa = zmesh["tensile_strength"]
tensile_strength_MPa = tensile_strength_kPa / 1e3
if np.any(tensile_strength_MPa <= 0):
raise ValueError("Cannot normalize: tensile_strength must be positive for all layers.")
# Normalize shear stress to layers' tensile strength
normalized_Txz = Txz_MPa / tensile_strength_MPa[:, None]
return normalized_Txz
🤖 Prompt for AI Agents
In src/weac/analysis/analyzer.py around lines 353 to 359, the code normalizes
shear stresses by tensile_strength_MPa without guarding against zero or
non-positive values and uses tensile strength for shear normalization without
explanation; add a guard that identifies non-positive tensile strength entries
and handles them (e.g., raise a clear ValueError, skip/NaN those layers, or
substitute a small epsilon) to avoid division-by-zero, and either change the
normalization to use shear strength if that was intended or add a brief
docstring comment and a reference explaining why tensile strength is used for
normalizing shear stresses so the choice is explicit.

Comment on lines 498 to 504
if normalize and val == "max":
zmesh = self.get_zmesh(dz=dz)
tensile_strength = zmesh["tensile_strength"]
tensile_strength_kPa = zmesh["tensile_strength"]
tensile_strength_converted = tensile_strength_kPa / 1e3 * convert[unit]
# Normalize maximum principal stress to layers' tensile strength
normalized_Ps = Ps / tensile_strength[:, None]
normalized_Ps = Ps / tensile_strength_converted[:, None]
return normalized_Ps

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add division by zero guard and consider simplifying normalization.

Two issues:

  1. Division by zero: Missing guard for tensile_strength_converted <= 0.

  2. Inconsistent approach: This method computes stress in the requested unit first (lines 486-491), then normalizes. In contrast, Sxx, Txz, and Szz compute in MPa, normalize if requested, then convert units. Consider aligning the approaches for consistency.

Add guard:

         # Normalize tensile stresses to tensile strength
         if normalize and val == "max":
             zmesh = self.get_zmesh(dz=dz)
             tensile_strength_kPa = zmesh["tensile_strength"]
             tensile_strength_converted = tensile_strength_kPa / 1e3 * convert[unit]
+            if np.any(tensile_strength_converted <= 0):
+                raise ValueError("Cannot normalize: tensile_strength must be positive for all layers.")
             # Normalize maximum principal stress to layers' tensile strength
             normalized_Ps = Ps / tensile_strength_converted[:, None]
             return normalized_Ps
🤖 Prompt for AI Agents
In src/weac/analysis/analyzer.py around lines 498 to 504, the code normalizes Ps
by tensile_strength_converted but lacks a division-by-zero guard and uses a
different unit-conversion order than Sxx/Txz/Szz; change the logic to compute
tensile strength in MPa first (matching the other methods), apply normalization
against tensile_strength_MPa with a safe guard (e.g., mask or clamp values <= 0
to raise or set to np.inf/np.nan) to avoid division by zero, and then convert
the normalized result to the requested unit—or alternatively clamp
tensile_strength_converted to a small positive epsilon (and log/raise on
non-positive values) before dividing so the normalization is consistent and
safe.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants