Download notebook · Download runnable bundle

Dependencies: PySTRA core.

Direct differentiation of the limit state#

Supply an analytical limit-state gradient and compare the evaluation cost with finite differences.

Before you start: the introductory FORM tutorial and partial derivatives.

Problem and approach#

The default FORM gradient uses forward finite differences. When a limit-state gradient is available analytically or from a structural solver, direct differentiation can reduce model evaluations. The example below supplies a gradient for a six-variable limit state.

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

Define the limit-state function and its gradient using elementwise NumPy expressions. For n_points evaluations, each input has shape (n_points,), the returned G has shape (n_points,), and grad_G has shape (6, n_points), with one row per random variable in model order. The current DDM evaluator calls this function one point at a time.

[2]:
def lsf(r, X1, X2, X3, X4, X5, X6):
    """
    Calrel example from FERUM
    """
    G = (
        r
        - X2 / (1000 * X3)
        - (X1 / (200 * X3)) ** 2
        - X5 / (1000 * X6)
        - (X4 / (200 * X6)) ** 2
    )
    grad_G = np.array(
        [
            -X1 / (20000 * X3**2),
            -1 / (1000 * X3),
            (20 * X2 * X3 + X1**2) / (20000 * X3**3),
            -X4 / (20000 * X6**2),
            -1 / (1000 * X6),
            (20 * X5 * X6 + X4**2) / (20000 * X6**3),
        ]
    )
    return G, grad_G

Set up a generic function that establishes and runs the model according to the differentiation type passed diff_mode:

[3]:
def run(diff_mode):
    limit_state = ra.LimitState(lsf)

    # Set some options (optional)
    options = ra.FORMOptions(differentiation=diff_mode)

    stochastic_model = ra.StochasticModel()

    # Define random variables
    stochastic_model.add_variable(ra.Lognormal("X1", 500, 100))
    stochastic_model.add_variable(ra.Lognormal("X2", 2000, 400))
    stochastic_model.add_variable(ra.Uniform("X3", 5, 0.5))
    stochastic_model.add_variable(ra.Lognormal("X4", 450, 90))
    stochastic_model.add_variable(ra.Lognormal("X5", 1800, 360))
    stochastic_model.add_variable(ra.Uniform("X6", 4.5, 0.45))

    # Define constants
    stochastic_model.add_variable(ra.Constant("r", 1.7))

    stochastic_model.set_correlation(
        ra.CorrelationMatrix(
            [
                [1.0, 0.3, 0.2, 0, 0, 0],
                [0.3, 1.0, 0.2, 0, 0, 0],
                [0.2, 0.2, 1.0, 0, 0, 0],
                [0, 0, 0, 1.0, 0.3, 0.2],
                [0, 0, 0, 0.3, 1.0, 0.2],
                [0, 0, 0, 0.2, 0.2, 1.0],
            ]
        )
    )

    # Set up FORM analysis
    form = ra.FORM(
        options=options,
        model=stochastic_model,
        limit_state=limit_state,
    )
    # Run it and return the record
    return form.run()

Call the default FFD method:

[4]:
ffd_count = 0


def run_ffd():
    global ffd_count
    result = run("ffd")
    ffd_count += result.n_limit_state_evaluations

And the DDM, which uses the grad_G returned from the lsf:

[5]:
ddm_count = 0


def run_ddm():
    global ddm_count
    result = run("ddm")
    ddm_count += result.n_limit_state_evaluations

Finally run both 100 times and compare the execution speed difference:

[6]:
number = 100
time_ffd = timeit.timeit(stmt=run_ffd, number=number)
time_ddm = timeit.timeit(stmt=run_ddm, number=number)

print("Total time taken (s):")
print(f"FFD: {time_ffd}; DDM: {time_ddm}")
print("Number of function evaluations:")
print(f"FFD: {ffd_count}; DDM: {ddm_count}")
print("Average time per call (s):")
print(f"FFD: {time_ffd/number}; DDM: {time_ddm/number}")
print(f"DDM speed-up: {time_ffd/time_ddm:.2f}")

Total time taken (s):
FFD: 1.8094270400000028; DDM: 1.7905911950000188
Number of function evaluations:
FFD: 8500; DDM: 4300
Average time per call (s):
FFD: 0.01809427040000003; DDM: 0.01790591195000019
DDM speed-up: 1.01

For the present problem the speed-up is not very significant, but for more complex models, it can be, as may be apparent from the difference in the number of function calls.

Interpretation#

Compare model-evaluation counts as well as elapsed time. Analytical gradients reduce repeated evaluations, but their correctness must be checked independently before using them in an analysis.

Continue: User guide · API reference · Theory