|
| 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