Skip to content

Commit 87d3d62

Browse files
committed
Implemented accelerated version of Example 2b
1 parent eafbfe0 commit 87d3d62

File tree

6 files changed

+640
-4
lines changed

6 files changed

+640
-4
lines changed

examples/Example_2b.py

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
""" Script to run Experiment 2 b) of the paper
2+
3+
This experiment consist of 3 curves with non-constant speeds.
4+
a) correspond to the noiseless case.
5+
b) correspond to the 20% noise case.
6+
c) correspond to the 60% noise case.
7+
d) correspond to the 60% noise case wth strong regularization.
8+
"""
9+
# Standard imports
10+
import sys
11+
import os
12+
import itertools
13+
import pickle
14+
import numpy as np
15+
16+
# Import package from sibling folder
17+
sys.path.insert(0, os.path.abspath('..'))
18+
from src import DGCG
19+
20+
# General simulation parameters
21+
ALPHA = 0.1
22+
BETA = 0.1
23+
T = 51
24+
TIME_SAMPLES = np.linspace(0, 1, T)
25+
26+
MAX_FREQUENCY = 15
27+
MAX_ANGLES = 5
28+
ANGLES = np.linspace(0, np.pi, MAX_ANGLES)[:-1]
29+
SPACING = 1
30+
31+
32+
def sample_line(num_samples, angle, spacing):
33+
""" Obtain equally spaced points on lines crossing the origin."""
34+
rotation_mat = np.array([[np.cos(angle), np.sin(angle)],
35+
[-np.sin(angle), np.cos(angle)]])
36+
horizontal_samples = [-spacing*(i+1) for i in range(num_samples//2)]
37+
horizontal_samples.extend([spacing*(i+1)
38+
for i in range(num_samples - num_samples//2)])
39+
horizontal_samples = [np.array([x, 0]) for x in horizontal_samples]
40+
rot_samples = [samp@rotation_mat for samp in horizontal_samples]
41+
return rot_samples
42+
43+
44+
def available_frequencies(angle):
45+
""" Avaiable frequencies at angle.
46+
47+
At each time we cicle through different angles"""
48+
av_samps = [np.array([0, 0])]
49+
av_samps.extend(sample_line(MAX_FREQUENCY-1, angle, SPACING))
50+
return np.array(av_samps)
51+
52+
53+
angle_cycler = itertools.cycle(ANGLES)
54+
FREQUENCIES = [available_frequencies(next(angle_cycler))
55+
for t in range(T)]
56+
FREQ_DIMENSION = np.ones(T, dtype=int)*MAX_FREQUENCY
57+
58+
# Some helper functions
59+
def cut_off(s):
60+
""" One-dimensiona twice-differentiable monotonous cut-off function.
61+
62+
This function is applied to the forward measurements such that the
63+
boundary of [0,1]x[0,1] is never reached.
64+
cutoff_threshold define the intervals [0,h], [1-h, h] where the
65+
cut_off values are neither 0 or 1.
66+
67+
Parameters
68+
----------
69+
s : numpy.ndarray
70+
1-dimensional array with values in [0,1]
71+
72+
Returns
73+
-------
74+
numpy.ndarray
75+
1-dimensional array with values in [0,1]
76+
77+
"""
78+
cutoff_threshold = 0.1
79+
transition = lambda s: 10*s**3 - 15*s**4 + 6*s**5
80+
val = np.zeros(s.shape)
81+
for idx, s_i in enumerate(s):
82+
if cutoff_threshold <= s_i <= 1-cutoff_threshold:
83+
val[idx] = 1
84+
elif 0 <= s_i < cutoff_threshold:
85+
val[idx] = transition(s_i/cutoff_threshold)
86+
elif 1-cutoff_threshold < s_i <= 1:
87+
val[idx] = transition(((1-s_i))/cutoff_threshold)
88+
else:
89+
val[idx] = 0
90+
return val
91+
92+
93+
def D_cut_off(s):
94+
""" Derivative of the one-dimensional cut-off functionk
95+
96+
Parameters
97+
----------
98+
s : numpy.ndarray
99+
1-dimensional array with values in [0,1]
100+
101+
Returns
102+
-------
103+
numpy.ndarray of dimension 1.
104+
"""
105+
cutoff_threshold = 0.1
106+
D_transition = lambda s: 30*s**2 - 60*s**3 + 30*s**4
107+
val = np.zeros(s.shape)
108+
for idx, s_i in enumerate(s):
109+
if 0 <= s_i < cutoff_threshold:
110+
val[idx] = D_transition(s_i/cutoff_threshold)/cutoff_threshold
111+
elif 1-cutoff_threshold < s_i <= 1:
112+
val[idx] = -D_transition((1-s_i)/cutoff_threshold)/cutoff_threshold
113+
else:
114+
val[idx] = 0
115+
return val
116+
117+
118+
# Kernels to define the forward measurements
119+
def TEST_FUNC(t, x): # φ_t(x)
120+
""" Kernel that define the forward measurements
121+
122+
Fourier frequency measurements sampled at the pre-set `FREQUENCIES`
123+
and cut-off at the boundary of [0,1]x[0,1].
124+
125+
Parameters
126+
----------
127+
t : int
128+
The time sample of evaluation, ranges from 0 to T-1
129+
x : numpy.ndarray of size (N,2)
130+
represents a list of N 2-dimensional spatial points.
131+
132+
Returns
133+
-------
134+
numpy.ndarray of size (N,K)
135+
The evaluations of the test function in the set of N spatial
136+
points. K corresponds to the number of FREQUENCIES at the input
137+
time.
138+
139+
Notes
140+
-----
141+
The output of this function is an element of the Hilbert space H_t
142+
as described in the theory.
143+
"""
144+
input_fourier = x@FREQUENCIES[t].T # size (N,K)
145+
fourier_evals = np.exp(-2*np.pi*1j*input_fourier)
146+
cutoff = cut_off(x[:, 0:1])*cut_off(x[:, 1:2])
147+
return fourier_evals*cutoff
148+
149+
def GRAD_TEST_FUNC(t, x): # ∇φ_t(x)
150+
""" Gradient of kernel that define the forward measurements
151+
152+
Gradient of Fourier frequency measurements sampled at the pre-set
153+
`FREQUENCIES` and cut-off at the boundary of [0,1]x[0,1].
154+
155+
Parameters
156+
----------
157+
t : int
158+
The time sample of evaluation, ranges from 0 to T-1
159+
x : numpy.ndarray of size (N,2)
160+
represents a list of N 2-dimensional spatial points.
161+
162+
Returns
163+
-------
164+
numpy.ndarray of size (2,N,K)
165+
The evaluations of the gradient of the kernel in the set of N
166+
spatial points. K corresponds to the number of FREQUENCIES at the
167+
input time. The first variable references to the gradient on
168+
the first dimension, and the second dimensions respectively.
169+
170+
Notes
171+
-----
172+
The output of this function are two elements of the Hilbert space H_t
173+
as described in the theory.
174+
"""
175+
freq_t = FREQUENCIES[t]
176+
input_fourier = x@freq_t.T # size (N,K)
177+
fourier_evals = np.exp(-2*np.pi*1j*input_fourier)
178+
# cutoffs
179+
cutoff_1 = cut_off(x[:, 0:1])
180+
cutoff_2 = cut_off(x[:, 1:2])
181+
D_cutoff_1 = D_cut_off(x[:, 0:1])
182+
D_cutoff_2 = D_cut_off(x[:, 1:2])
183+
D_constant_1 = -2*np.pi*1j*cutoff_1@freq_t[:, 0:1].T + D_cutoff_1
184+
D_constant_2 = -2*np.pi*1j*cutoff_2@freq_t[:, 1:2].T + D_cutoff_2
185+
# wrap it up
186+
output = np.zeros((2, x.shape[0], FREQ_DIMENSION[t]), dtype='complex')
187+
output[0] = fourier_evals*cutoff_2*D_constant_1
188+
output[1] = fourier_evals*cutoff_1*D_constant_2
189+
return output
190+
191+
192+
if __name__ == "__main__":
193+
# Input of parameters into model.
194+
# This has to be done first since these values fix the set of extremal
195+
# points and their generated measurement data
196+
DGCG.set_model_parameters(ALPHA, BETA, TIME_SAMPLES, FREQ_DIMENSION,
197+
TEST_FUNC, GRAD_TEST_FUNC)
198+
199+
# Generate data. The curves with different velocities and trajectories.
200+
# For this we use the DGCG.curves.curve and DGCG.curves.measure classes.
201+
202+
# middle curve: straight curve with constant speed.
203+
start_pos_1 = [0.1, 0.1]
204+
end_pos_1 = [0.7, 0.8]
205+
curve_1 = DGCG.curves.curve(np.linspace(0, 1, 2),
206+
np.array([start_pos_1, end_pos_1]))
207+
# top curve: a circle segment
208+
times_2 = np.linspace(0, 1, T)
209+
center = np.array([0.1, 0.9])
210+
radius = np.linalg.norm(center-np.array([0.5, 0.5]))-0.1
211+
x_2 = radius*np.cos(3*np.pi/2 + times_2*np.pi/2) + center[0]
212+
y_2 = radius*np.sin(3*np.pi/2 + times_2*np.pi/2) + center[1]
213+
positions_2 = np.array([x_2, y_2]).T
214+
curve_2 = DGCG.curves.curve(times_2, positions_2)
215+
# bottom curve: circular segment + straight segment and non-constant speed.
216+
tangent_time = 0.5
217+
tangent_point = curve_1.eval(tangent_time)
218+
tangent_direction = curve_1.eval(tangent_time)-curve_1.eval(0)
219+
normal_direction = np.array([tangent_direction[0, 1],
220+
-tangent_direction[0, 0]])
221+
normal_direction = normal_direction/np.linalg.norm(tangent_direction)
222+
radius = 0.3
223+
init_angle = 4.5*np.pi/4
224+
end_angle = np.pi*6/16/2
225+
diff_angle = init_angle - end_angle
226+
middle_time = 0.8
227+
center_circle = tangent_point + radius*normal_direction
228+
increase_factor = 1
229+
increase = lambda t: increase_factor*t**2 + (1-increase_factor)*t
230+
times_3 = np.arange(0, middle_time, 0.01)
231+
times_3 = np.append(times_3, middle_time)
232+
x_3 = np.cos(init_angle - increase(times_3/middle_time)*diff_angle)
233+
x_3 = radius*x_3 + center_circle[0, 0]
234+
y_3 = np.sin(init_angle - increase(times_3/middle_time)*diff_angle)
235+
y_3 = radius*y_3 + center_circle[0, 1]
236+
# # # straight line
237+
times_3 = np.append(times_3, 1)
238+
middle_position = np.array([x_3[-1], y_3[-1]])
239+
last_speed = 1
240+
last_position = middle_position*(1-last_speed) + last_speed*center_circle
241+
x_3 = np.append(x_3, last_position[0, 0])
242+
y_3 = np.append(y_3, last_position[0, 1])
243+
positions_3 = np.array([x_3, y_3]).T
244+
curve_3 = DGCG.curves.curve(times_3, positions_3)
245+
246+
# Include these curves inside a measure, with respective intensities
247+
measure = DGCG.curves.measure()
248+
intensity_1 = 1
249+
weight_1 = intensity_1*curve_1.energy()
250+
measure.add(curve_1, weight_1)
251+
intensity_2 = 1
252+
weight_2 = intensity_2*curve_2.energy()
253+
measure.add(curve_2, weight_2)
254+
intensity_3 = 1
255+
weight_3 = intensity_3*curve_3.energy()
256+
measure.add(curve_3, weight_3)
257+
# uncomment the next line see the animated curve
258+
# measure.animate()
259+
260+
# Simulate the measurements generated by this curve
261+
data = DGCG.operators.K_t_star_full(measure)
262+
# uncomment the next line to see the backprojected data
263+
# dual_variable = DGCG.operators.w_t(DGCG.curves.measure())
264+
# dual_variable.data = -data
265+
# ani_1 = dual_variable.animate(measure = measure, block = True)
266+
267+
# Add noise to the measurements. The noise vector is saved in ./annex
268+
noise_level = 0.2
269+
noise_vector = pickle.load(open('annex/noise_vector.pickle', 'rb'))
270+
nois_norm = DGCG.operators.int_time_H_t_product(noise_vector, noise_vector)
271+
noise_vector = noise_vector/np.sqrt(nois_norm)
272+
data_H_norm = np.sqrt(DGCG.operators.int_time_H_t_product(data, data))
273+
data_noise = data + noise_vector*noise_level*data_H_norm
274+
275+
# uncomment to see the noisy backprojected data
276+
# dual_variable = DGCG.operators.w_t(DGCG.curves.measure())
277+
# dual_variable.data = -data
278+
# ani_2 = dual_variable.animate(measure = measure, block = True)
279+
280+
# settings to speed up the convergence.
281+
simulation_parameters = {
282+
'insertion_max_restarts': 1000,
283+
'insertion_min_restarts': 100,
284+
'results_folder': 'results_Exercise_2b',
285+
'multistart_pooling_num': 1000,
286+
}
287+
# Compute the solution
288+
solution_measure = DGCG.solve(data_noise, **simulation_parameters)
289+

0 commit comments

Comments
 (0)