Monte Carlo in Python¶
This tutorial builds a Monte Carlo simulation of the bolt fatigue damage with
the random samplers of pyflange.stats. It does the same
as the sample command, but in a script, so you can
change any distribution, add random variables, or post-process the results
as you like.
Download the full script: montecarlo.py
Samplers¶
A sampler is a Python
generator: an
object that returns a new random value every time you call next() on it.
from pyflange.stats import norm_sampler
preload = norm_sampler(2876e3, 0.03) # normal, mean 2876 kN, CoV 3%
next(preload) # a random preload, e.g. 2.91e6
next(preload) # another one, e.g. 2.84e6
PyFlange has generic samplers (norm_sampler, lognorm_sampler,
weibull_sampler, beta_sampler, sampler for any scipy distribution) and
samplers of PyFlange objects, which combine other samplers:
| Sampler | Yields |
|---|---|
standard_gap_sampler |
Random Gap (AMD1 gap height distribution) |
standard_gap_sampler_ED2 |
Random Gap (ED2 distributions) |
standard_PolynomialLFlangeSegment_sampler |
Random L-flange segments |
standard_markov_matrix_sampler |
Markov matrices with random load ranges |
standard_bolt_fatigue_curve_sampler |
Random bolt S-N curves |
fatigue_case_sampler |
Random BoltFatigueAnalysis objects |
You can write your own samplers too: any generator that yields objects of the right type works.
1. Reproducibility¶
The samplers use the global numpy random generator. Seed it to get the same sample at every run:
2. Flange segment sampler¶
import pyflange.stats as stats
from pyflange.bolts import StandardMetricBolt, RoundNut, standard_hole_diameter
D = 8000*mm
n_bolts = 156
bolt = StandardMetricBolt("M80", "10.9", shank_length=160*mm,
shank_diameter_ratio="pitch", stud=True)
fseg_sampler = stats.standard_PolynomialLFlangeSegment_sampler(
a = 150*mm, b = 122*mm, s = 54*mm, t = 172*mm, R = D/2,
central_angle = 2*pi / n_bolts,
Zg = -18044*kN / n_bolts,
bolt = bolt,
Do = standard_hole_diameter("M80"),
washer = None,
nut = RoundNut("M80"),
# Random preload before losses: mean 2876 kN, CoV 3%
preload_sampler = stats.norm_sampler(2876*kN, 0.03),
# Random preload losses (ref. [5], section 6.4.1)
ppl_sampler = stats.norm_sampler(0.03, 1/3), # plastic loss: mean 3%, std 1%
dft_sampler = stats.norm_sampler(250*um, 0.12), # coating: mean 250 um, std 30 um
# Random gap: AMD1 gap height distribution with random angle and shape
gap_sampler = stats.standard_gap_sampler(
flange_diameter = D,
flange_flatness_tolerance = 0.0014,
gap_angle_sampler = stats.lognorm_sampler(100*deg, 1.0),
gap_shape_factor_sampler = stats.norm_sampler(1.0, 0.15)),
# Random inside flange inclination: LN(0.1 deg, CoV 0.5)
tilt_sampler = stats.lognorm_sampler(0.1*deg, 0.5),
settlement_factor = 0.5)
For each realization, the sampler draws a gap and a flange inclination, then
draws the preload and its losses for half of the bolts within the gap and
averages them (ref. [5], 4.4). The yielded segment has the averaged preload
after losses as Fv, and PPL = DFT = 0.
The distributions are those of the stochastic model of ref. [5], Table 8, except the gap angle, which the reference does not give.
3. Loads, S-N curve and Miner's rule¶
from pyflange.fatigue import MarkovMatrix
design_mkvm = MarkovMatrix(
range = np.array([ 25, 50, 100, 150, 200, 250, 300, 375, 500, 625]) * 1e6,
mean = np.array([100, 100, 100, 100, 100, 100, 100, 100, 100, 100]) * 1e6,
cycles = np.array([3e8, 1e8, 3e7, 8e6, 2e6, 5e5, 1.5e5, 4e4, 8e3, 1e3]),
duration = 30)
# Load ranges scaled by X_FLS ~ LN(1.0, 0.12)
mkvm_sampler = stats.standard_markov_matrix_sampler(design_mkvm, 1.0, 0.12)
# Bolt S-N curve with DS_ref = X_FAT ~ N(62 MPa, 0.10)
sn_sampler = stats.standard_bolt_fatigue_curve_sampler(bolt.nominal_diameter, 1.0, 0.10, 62*MPa)
# Miner's rule uncertainty: allowable damage ~ LN(1.0, 0.30)
allowable_damage_sampler = stats.lognorm_sampler(1.0, 0.30)
4. Fatigue case sampler¶
The four samplers are combined into a sampler of complete fatigue analyses:
fcase_sampler = stats.fatigue_case_sampler(fseg_sampler,
mkvm_sampler,
sn_sampler,
allowable_damage_sampler)
fcase = next(fcase_sampler) # one random BoltFatigueAnalysis
print(f"One realization: gap angle {fcase.fseg.gap.angle/deg:.0f} deg, "
f"gap height {fcase.fseg.gap.height/mm:.2f} mm, damage {fcase.damage:.3g}")
Each realization is a BoltFatigueAnalysis:
fcase.fseg is the random segment, fcase.flange_mkvm the random Markov
matrix, fcase.fatigue_curve the random S-N curve,
fcase.allowable_damage the random allowable damage, and fcase.damage
and fcase.fatigue_life the results.
5. Generate the sample¶
Collect the values of interest in a pandas table:
import pandas as pd
rows = []
for i in range(SAMPLE_SIZE):
fcase = next(fcase_sampler)
rows.append({
"GapAngle": fcase.fseg.gap.angle,
"GapHeight": fcase.fseg.gap.height,
"BoltPreload": fcase.fseg.bolt_preload,
"Damage": fcase.damage,
"AllowableDamage": fcase.allowable_damage,
"FatigueLife": fcase.fatigue_life,
})
if (i+1) % 500 == 0:
print(f"... {i+1} realizations")
sample = pd.DataFrame(rows)
6. Results¶
utilization = sample.Damage / sample.AllowableDamage
pf = np.mean(utilization > 1) # fraction of failed realizations
print(sample.describe(percentiles=[0.05, 0.5, 0.95]).transpose().to_string())
print(f"Probability of failure over {design_mkvm.duration} years: {pf:.2e}")
sample.to_csv("montecarlo-sample.csv") # for further processing, e.g. in Excel
count mean std min 5% 50% 95% max
GapAngle 2000.0 1.236226e+00 0.723843 6.096265e-02 2.983545e-01 1.087546e+00 2.675680e+00 3.140577e+00
GapHeight 2000.0 1.143269e-03 0.001238 9.070261e-08 4.384932e-05 7.442454e-04 3.596016e-03 1.128285e-02
BoltPreload 2000.0 2.732521e+06 27989.062233 2.600771e+06 2.688598e+06 2.732288e+06 2.777531e+06 2.916772e+06
Damage 2000.0 1.432734e-01 0.231241 1.589216e-03 1.232016e-02 7.530805e-02 4.771642e-01 4.232725e+00
AllowableDamage 2000.0 9.917610e-01 0.294008 2.884288e-01 5.983693e-01 9.423795e-01 1.534146e+00 2.654757e+00
FatigueLife 2000.0 7.505789e+02 1285.993299 8.399722e+00 5.808260e+01 3.838157e+02 2.471174e+03 2.216393e+04
Probability of failure over 30 years: 1.55e-02
Sample size
With 2000 realizations the estimate of a small probability is very uncertain: 31 failures out of 2000 give a coefficient of variation of about 18% on the estimate. For a target probability of failure of 10-4, use at least 106 realizations, or a variance reduction technique.
This simulation does not apply a model uncertainty factor (stress
multiplication factor) to the stress ranges. Pass a constant or a sampler as
the SMF argument of fatigue_case_sampler to include it.