Download notebook · Download runnable bundle

Dependencies: PySTRA core.

Calibrating partial and combination factors#

Derive candidate safety and combination factors from representative load cases, then check the reliability of designs made with the selected factors. This is part of the code-calibration workflow: factor derivation supplies candidates; reliability assessment checks their consequences.

Before you start: load combinations and code calibration using normalized reliability. The core PySTRA installation is sufficient.

Problem and assumptions#

The numerical inputs follow Sørensen’s Example 4 and the Caprani–Khan load-combination examples. Maximum and companion distributions are supplied explicitly for their stated reference periods.

The coefficient method derives factors from design-value/characteristic-value ratios. The matrix method accounts for the action effects associated with each leading case. Both use a separable resistance-scale design rule, which can include multiplicative model errors; they do not decompose an arbitrary nonlinear limit state.

The operations below solve representative designs to a target, derive factors, select a common set, and verify the resulting designs. See code-calibration theory for the shared formulation and references.

[1]:
import pystra as ra
import numpy as np


def limit_state(z, R, G, Q1, Q2, cg):
    return z * R - (cg * G + 0.6 * Q1 + 0.3 * Q2)


maxima = {"Q1": ra.Gumbel("Q1", 1, 0.2), "Q2": ra.Gumbel("Q2", 1, 0.4)}
companions = {"Q1": ra.Gumbel("Q1", 0.89, 0.2), "Q2": ra.Gumbel("Q2", 0.77, 0.4)}
resistance = ra.Lognormal("R", 1, 0.15)
permanent = ra.Normal("G", 1, 0.1)
cases = ra.LoadCombination.from_actions(
    maxima=maxima,
    companions=companions,
    resistance=[resistance],
    other=[permanent],
    constants=[ra.Constant("z", 1), ra.Constant("cg", 0.4)],
    limit_state=limit_state,
)
nominal_values = {
    "R": resistance.ppf(0.05),
    "G": permanent.ppf(0.5),
    "Q1": maxima["Q1"].ppf(0.98),
    "Q2": maxima["Q2"].ppf(0.98),
}
problem = ra.calibration.FactorCalibrationProblem(
    cases, nominal_values=nominal_values, design_parameter="z"
)
target_beta = 4.3

Solve designs to the target#

root records the outer root-solver diagnostics and checks inner FORM convergence and the final target residual. Supply bracket=(lower, upper) to use a bracketed solve. The separate alpha method projects in independent normal space. Failed solves remain in solutions.designs; factor derivation refuses them.

[2]:
solutions = ra.calibration.solve_designs(problem, target_beta=target_beta, method="root")
assert solutions.converged
solutions.to_frame()

[2]:
R G Q1 Q2 z
Q1_max 0.655286 1.037132 1.623560 2.017120 3.043135
Q2_max 0.655030 1.037126 1.512911 2.245823 3.047713

Derive factors#

Factor sets store immutable numerical rows with named case and variable axes. Reporting tables are independent copies. Each variable action must have exactly one leading case; the method rejects unsupported case topologies.

[3]:
factors = ra.calibration.derive_factors(solutions, method="coeff")
for kind in ("resistance", "loads", "combinations"):
    display(factors.to_frame(kind))

R
Q1_max 0.846876
Q2_max 0.846545
G Q1 Q2
Q1_max 1.037132 1.069218 1.102563
Q2_max 1.037126 1.069218 1.102563
G Q1 Q2
Q1_max 1.0 1.000000 0.898165
Q2_max 1.0 0.931848 1.000000

Select factors, design, and verify#

Selection is explicit. Leading-action combination factors remain one; companion maxima are selected by name. Governing-case provenance is retained. The largest required resistance scale is explicitly chosen for the common design and rechecked across all cases.

[4]:
selected = ra.calibration.select_factors(
    factors, resistance="minimum", loads="maximum", combinations="maximum"
)
designs = ra.calibration.design_with_factors(problem, selected)
checks = ra.calibration.verify_designs(problem, max(designs.values), target_beta=target_beta)
for check in checks:
    print(
        check.case_name, check.design_value, check.reliability.beta, check.target_margin
    )
assert all(check.reliability.converged for check in checks)
selected.governing

Q1_max 3.0477172739193117 4.306471480121069 0.006471480121069462
Q2_max 3.0477172739193117 4.30000560315918 5.603159180189721e-06
[4]:
(GoverningFactor(kind='resistance', variable='R', case_names=('Q2_max',)),
 GoverningFactor(kind='loads', variable='G', case_names=('Q1_max',)),
 GoverningFactor(kind='loads', variable='Q1', case_names=('Q1_max', 'Q2_max')),
 GoverningFactor(kind='loads', variable='Q2', case_names=('Q1_max', 'Q2_max')),
 GoverningFactor(kind='combinations', variable='G', case_names=('Q1_max', 'Q2_max')),
 GoverningFactor(kind='combinations', variable='Q1', case_names=('Q2_max',)),
 GoverningFactor(kind='combinations', variable='Q2', case_names=('Q1_max',)))

Compare the supported method combinations#

[5]:
for design_method, factor_method in [
    ("root", "coeff"),
    ("root", "matrix"),
    ("alpha", "matrix"),
]:
    solved = ra.calibration.solve_designs(
        problem, target_beta=target_beta, method=design_method
    )
    assert solved.converged
    estimated = ra.calibration.derive_factors(solved, method=factor_method)
    selected = ra.calibration.select_factors(
        estimated, resistance="minimum", loads="maximum", combinations="maximum"
    )
    design = ra.calibration.design_with_factors(problem, selected)
    verified = ra.calibration.verify_designs(
        problem, max(design.values), target_beta=target_beta
    )
    print(design_method, factor_method, [c.reliability.beta for c in verified])
    assert all(c.reliability.converged for c in verified), [
        c.reliability.message for c in verified if not c.reliability.converged
    ]

root coeff [4.306471480121069, 4.30000560315918]
root matrix [4.3064711133200655, 4.30000523595273]
alpha matrix [4.30646064767464, 4.29999475873901]

Multiplicative resistance and load model errors#

The following reference example includes nonlinear products. Roles explicitly associate each model error with its side of the limit state. The matrix method evaluates each action coefficient in its own leading case, preserving the original formulation.

[6]:
def nonlinear_limit_state(scale, wR, R, wS, Q1, Q2):
    return scale * wR * R - wS * (Q1 + Q2)


resistance = ra.Normal("R", 60, 6)
maxima = {"Q1": ra.Normal("Q1", 30, 3), "Q2": ra.Normal("Q2", 20, 2)}
companions = {"Q1": ra.Normal("Q1", 15, 3), "Q2": ra.Normal("Q2", 10, 2)}
cases = ra.LoadCombination.from_actions(
    maxima=maxima,
    companions=companions,
    resistance=[resistance, ra.Lognormal("wR", 1, 0.05)],
    other=[ra.Lognormal("wS", 1, 0.1)],
    constants=[ra.Constant("scale", 1)],
    limit_state=nonlinear_limit_state,
)
problem = ra.calibration.FactorCalibrationProblem(
    cases,
    design_parameter="scale",
    nominal_values={
        "wR": 1,
        "wS": 1,
        "R": resistance.ppf(0.05),
        "Q1": maxima["Q1"].ppf(0.95),
        "Q2": maxima["Q2"].ppf(0.95),
    },
)
solutions = ra.calibration.solve_designs(problem, target_beta=3.7)
assert solutions.converged
factors = ra.calibration.derive_factors(solutions, method="matrix")
selected = ra.calibration.select_factors(factors, loads="maximum")
designs = ra.calibration.design_with_factors(problem, selected)
checks = ra.calibration.verify_designs(problem, max(designs.values), target_beta=3.7)
for check in checks:
    print(check.case_name, check.reliability.beta, check.target_margin)

assert all(check.reliability.converged for check in checks)

Q1_max 3.7039102295177426 0.003910229517742447
Q2_max 4.28712854669924 0.5871285466992395

Interpretation#

Factor derivation and factor selection are distinct steps. Verify a common code-conforming design against every representative case after selecting factors; meeting the target in the original solved cases alone is insufficient.

Keep unsuccessful target solves visible with solutions.reliability_frame(). Factor derivation needs normal-space design points; an estimate-only evaluator can serve root solving and verification. Always verify the designs produced by the selected factors.

Continue: User guide · API reference · Theory