Skip to content

Conversation

@KarasTomas
Copy link
Collaborator

Pull request following Issues #56 and #57 implementing S-N curve models

@KarasTomas KarasTomas requested a review from Vybornak2 November 24, 2025 15:19
@KarasTomas KarasTomas added the WIP Work In Progress label Nov 24, 2025
Copy link
Collaborator

@Vybornak2 Vybornak2 left a comment

Choose a reason for hiding this comment

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

some variable names should be adjusted according to nomenclature faber table, that is being created at the time of this review (dec 2025).

"""Abstract base class for stress-life (S-N) curve models."""

@abstractmethod
def stress_amp(self, life: ArrayLike) -> ArrayLike:
Copy link
Collaborator

Choose a reason for hiding this comment

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

n_cycles could be more understandable to broader audience?
with variable names adjustment wait until confirmation please

class WholerPowerLaw(SN_Curve):
"""Wöhler (S-N) curve model using a power law relationship."""

def __init__(self, SN_C: float, SN_w: float):
Copy link
Collaborator

Choose a reason for hiding this comment

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

SN_C, SN_w are compared to rest of the library shortcuts, which is not desirable
at the architecture meeting longer names such as power_law_coef, power_law_exp has been suggested

class WholerPowerLaw(SN_Curve):
"""Wöhler (S-N) curve model using a power law relationship."""

def __init__(self, SN_C: float, SN_w: float):
Copy link
Collaborator

Choose a reason for hiding this comment

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

It should be thought out, if we might want to scale this to multiple dimensions
that would allow assembly of multiple SN curves, without using for cycle

user might want to assess multiple welds for example, utilizing different coefficients
therefore it is quite desirable to vectorize such function, what do you think?

Copy link
Collaborator

Choose a reason for hiding this comment

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

this would mean that type of both coefs would be ArrayLike

if np.any(life_array <= 0):
raise ValueError("life must contain only positive values")

return np.power((self.SN_C) / life_array, 1 / self.SN_w)
Copy link
Collaborator

Choose a reason for hiding this comment

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

unless you need to use additional arguments of np.power, use ** for better readability as both have same performance

if np.any(stress_amp_array <= 0):
raise ValueError("stress_amp must contain only positive values")

return self.SN_C / np.power(stress_amp, self.SN_w)
Copy link
Collaborator

Choose a reason for hiding this comment

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

use ** instead of np.power

derivative_constant = self.A * self.beta * np.power(self.C, self.beta)

converged = False
for _ in range(max_iterations):
Copy link
Collaborator

Choose a reason for hiding this comment

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

For better understanding I would recommend recording iterations, and add logging that would be enabled via kw param

this would allow user to debug the process
we can discuss this on the meeting (please bring it up, if I don't)

Copy link
Collaborator

Choose a reason for hiding this comment

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

this could be potentially be implemented in second iteration - issue could be created
two things could take place:

  1. include logging process
  2. batch processing for newton method as the data could be potentially vectorized
    both things would be nice to have, but might not be necessary immediately

for _ in range(max_iterations):
# Calculate function value f(N) = A * (C*(N+B)/(N+C))^β - σ_a
f_N = (
self.A * np.power((self.C * (N + self.B)) / (N + self.C), self.beta)
Copy link
Collaborator

Choose a reason for hiding this comment

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

use ** instead of np.power


# Calculate derivative f'(N) = A*β*C^β*(N+B)^(β-1)*(C-B)/(N+C)^(β+1)
f_prime_N = derivative_constant * (
(np.power(N + self.B, self.beta - 1) * (self.C - self.B))
Copy link
Collaborator

Choose a reason for hiding this comment

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

use ** instead of np.power

)

# Avoid division by zero
f_prime_N = np.where(np.abs(f_prime_N) < 1e-15, 1e-15, f_prime_N)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could these hardcoded values lead to bug?
Would dtype affect this?
Is this the optimal way how to solve this problem?

N = N_new

# Issue warning if Newton solver did not converge
if not converged:
Copy link
Collaborator

Choose a reason for hiding this comment

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

for else pattern can be used here instead
but this is up to you, some may consider it being antipattern for it not being too well known

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

Labels

WIP Work In Progress

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants