From e2231209f8da82f15b0af663a4f89de8c04fa009 Mon Sep 17 00:00:00 2001 From: Morten Engen Date: Wed, 25 Mar 2026 08:46:53 +0100 Subject: [PATCH 1/3] Update iterative strain before convergence check --- structuralcodes/sections/_generic.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/structuralcodes/sections/_generic.py b/structuralcodes/sections/_generic.py index 764f3e71..bb8a29eb 100644 --- a/structuralcodes/sections/_generic.py +++ b/structuralcodes/sections/_generic.py @@ -1829,14 +1829,14 @@ def calculate_strain_profile( # Solve using the current tangent stiffness delta_strain = np.linalg.solve(stiffness_tangent, residual) + # Update the strain + strain += delta_strain + # Check for convergence: if np.linalg.norm(delta_strain) < tol: converged = True break - # Update the strain - strain += delta_strain - num_iter += 1 # Create the results object From d62d0c8c5c761e6fbd6d9d8b4143f5d470f22fe9 Mon Sep 17 00:00:00 2001 From: Morten Engen Date: Wed, 25 Mar 2026 21:22:51 +0100 Subject: [PATCH 2/3] Move residual calculation so that the complete residual history is captured --- structuralcodes/sections/_generic.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/structuralcodes/sections/_generic.py b/structuralcodes/sections/_generic.py index bb8a29eb..9bd009f2 100644 --- a/structuralcodes/sections/_generic.py +++ b/structuralcodes/sections/_generic.py @@ -1773,7 +1773,7 @@ def calculate_strain_profile( geom = self.section.geometry # Collect loads in a numpy array - loads = np.array([n, my, mz]) + loads = np.array([n, my, mz], dtype=float) # Compute initial tangent stiffness matrix stiffness_tangent, integration_data = ( @@ -1791,7 +1791,9 @@ def calculate_strain_profile( # Calculate strain plane with Newton Rhapson Iterative method num_iter = 0 strain = np.zeros(3, dtype=float) - residual = np.zeros(3, dtype=float) + + # The initial residual is equal to the loads + residual = loads.copy() converged = False residual_history = [] @@ -1809,14 +1811,6 @@ def calculate_strain_profile( if num_iter > max_iter: break - # Calculate response and residuals - response = self.integrate_strain_profile(strain=strain).asarray() - residual = loads - response - - # Append to history variables - residual_history.append(residual.copy()) - strain_history.append(strain.copy()) - if initial: # Solve using the decomposed matrix delta_strain = lu_solve((lu, piv), residual) @@ -1832,6 +1826,14 @@ def calculate_strain_profile( # Update the strain strain += delta_strain + # Calculate response and residuals + response = self.integrate_strain_profile(strain=strain).asarray() + residual = loads - response + + # Append to history variables + residual_history.append(residual.copy()) + strain_history.append(strain.copy()) + # Check for convergence: if np.linalg.norm(delta_strain) < tol: converged = True From c398a07f88664fb7b6aeb7469ae161453a22331e Mon Sep 17 00:00:00 2001 From: Morten Engen Date: Wed, 25 Mar 2026 21:31:16 +0100 Subject: [PATCH 3/3] Calculate the initial residual since it may be different from the loads if initial strain is present --- structuralcodes/sections/_generic.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/structuralcodes/sections/_generic.py b/structuralcodes/sections/_generic.py index 9bd009f2..de78169b 100644 --- a/structuralcodes/sections/_generic.py +++ b/structuralcodes/sections/_generic.py @@ -1790,11 +1790,14 @@ def calculate_strain_profile( # Calculate strain plane with Newton Rhapson Iterative method num_iter = 0 + converged = False strain = np.zeros(3, dtype=float) - # The initial residual is equal to the loads - residual = loads.copy() - converged = False + # Calculate the initial response and residuals. Note that the initial + # residual might be different from the applied loads if any initial + # strain is present + response = self.integrate_strain_profile(strain=strain).asarray() + residual = loads - response residual_history = [] strain_history = []