-
-
Notifications
You must be signed in to change notification settings - Fork 988
/
inclined_plane.py
152 lines (120 loc) · 5.33 KB
/
inclined_plane.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# Copyright (c) 2017-2019 Uber Technologies, Inc.
# SPDX-License-Identifier: Apache-2.0
import argparse
import numpy as np
import torch
import pyro
from pyro.distributions import Normal, Uniform
from pyro.infer import EmpiricalMarginal, Importance
"""
Samantha really likes physics---but she likes Pyro even more. Instead of using
calculus to do her physics lab homework (which she could easily do), she's going
to use bayesian inference. The problem setup is as follows. In lab she observed
a little box slide down an inclined plane (length of 2 meters and with an incline of
30 degrees) 20 times. Each time she measured and recorded the descent time. The timing
device she used has a known measurement error of 20 milliseconds. Using the observed
data, she wants to infer the coefficient of friction mu between the box and the inclined
plane. She already has (deterministic) python code that can simulate the amount of time
that it takes the little box to slide down the inclined plane as a function of mu. Using
Pyro, she can reverse the simulator and infer mu from the observed descent times.
"""
little_g = 9.8 # m/s/s
mu0 = 0.12 # actual coefficient of friction in the experiment
time_measurement_sigma = 0.02 # observation noise in seconds (known quantity)
# the forward simulator, which does numerical integration of the equations of motion
# in steps of size dt, and optionally includes measurement noise
def simulate(mu, length=2.0, phi=np.pi / 6.0, dt=0.005, noise_sigma=None):
T = torch.zeros(())
velocity = torch.zeros(())
displacement = torch.zeros(())
acceleration = (
torch.tensor(little_g * np.sin(phi)) - torch.tensor(little_g * np.cos(phi)) * mu
)
if (
acceleration.numpy() <= 0.0
): # the box doesn't slide if the friction is too large
return torch.tensor(1.0e5) # return a very large time instead of infinity
while (
displacement.numpy() < length
): # otherwise slide to the end of the inclined plane
displacement += velocity * dt
velocity += acceleration * dt
T += dt
if noise_sigma is None:
return T
else:
return T + noise_sigma * torch.randn(())
# analytic formula that the simulator above is computing via
# numerical integration (no measurement noise)
def analytic_T(mu, length=2.0, phi=np.pi / 6.0):
numerator = 2.0 * length
denominator = little_g * (np.sin(phi) - mu * np.cos(phi))
return np.sqrt(numerator / denominator)
# generate N_obs observations using simulator and the true coefficient of friction mu0
print("generating simulated data using the true coefficient of friction %.3f" % mu0)
N_obs = 20
torch.manual_seed(2)
observed_data = torch.tensor(
[
simulate(torch.tensor(mu0), noise_sigma=time_measurement_sigma)
for _ in range(N_obs)
]
)
observed_mean = np.mean([T.item() for T in observed_data])
# define model with uniform prior on mu and gaussian noise on the descent time
def model(observed_data):
mu_prior = Uniform(0.0, 1.0)
mu = pyro.sample("mu", mu_prior)
def observe_T(T_obs, obs_name):
T_simulated = simulate(mu)
T_obs_dist = Normal(T_simulated, torch.tensor(time_measurement_sigma))
pyro.sample(obs_name, T_obs_dist, obs=T_obs)
for i, T_obs in enumerate(observed_data):
observe_T(T_obs, "obs_%d" % i)
return mu
def main(args):
# create an importance sampler (the prior is used as the proposal distribution)
importance = Importance(model, guide=None, num_samples=args.num_samples)
# get posterior samples of mu (which is the return value of model)
# from the raw execution traces provided by the importance sampler.
print("doing importance sampling...")
emp_marginal = EmpiricalMarginal(importance.run(observed_data))
# calculate statistics over posterior samples
posterior_mean = emp_marginal.mean
posterior_std_dev = emp_marginal.variance.sqrt()
# report results
inferred_mu = posterior_mean.item()
inferred_mu_uncertainty = posterior_std_dev.item()
print(
"the coefficient of friction inferred by pyro is %.3f +- %.3f"
% (inferred_mu, inferred_mu_uncertainty)
)
# note that, given the finite step size in the simulator, the simulated descent times will
# not precisely match the numbers from the analytic result.
# in particular the first two numbers reported below should match each other pretty closely
# but will be systematically off from the third number
print(
"the mean observed descent time in the dataset is: %.4f seconds" % observed_mean
)
print(
"the (forward) simulated descent time for the inferred (mean) mu is: %.4f seconds"
% simulate(posterior_mean).item()
)
print(
(
"disregarding measurement noise, elementary calculus gives the descent time\n"
+ "for the inferred (mean) mu as: %.4f seconds"
)
% analytic_T(posterior_mean.item())
)
"""
################## EXERCISE ###################
# vectorize the computations in this example! #
###############################################
"""
if __name__ == "__main__":
assert pyro.__version__.startswith("1.9.1")
parser = argparse.ArgumentParser(description="parse args")
parser.add_argument("-n", "--num-samples", default=500, type=int)
args = parser.parse_args()
main(args)