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