Skip to content

Commit 8d8f9e1

Browse files
Chnaged from the scipy Cholesky decomposition used in the covariance method of generating spatially correlated noise.
Also improved the speed of this by moving part of the distance calculation outside the loop that makes atmosphere.
1 parent c848ed4 commit 8d8f9e1

File tree

1 file changed

+58
-20
lines changed

1 file changed

+58
-20
lines changed

lib/syinterferopy_functions.py

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ def atmosphere_turb(n_atms, lons_mg, lats_mg, method = 'fft', mean_m = 0.02,
420420
421421
422422
Outputs:
423-
ph_turb | r3 array | n_atms x n_pixs x n_pixs, UNITS ARE M. Note that if a water_mask is provided, this is applied and a masked array is returned.
423+
ph_turbs | r3 array | n_atms x n_pixs x n_pixs, UNITS ARE M. Note that if a water_mask is provided, this is applied and a masked array is returned.
424424
425425
2019/09/13 | MEG | adapted extensively from a simple script
426426
2020/10/02 | MEG | Change so that a water mask is optional.
@@ -454,8 +454,8 @@ def generate_correlated_noise_cov(pixel_distances, cov_Lc, shape):
454454
nx = shape[0]
455455
ny = shape[1]
456456
Cd = np.exp((-1 * pixel_distances)/cov_Lc) # from the matrix of distances, convert to covariances using exponential equation
457-
#Cd_L = np.linalg.cholesky(Cd) # ie Cd = CD_L @ CD_L.T
458-
Cd_L = scipy.linalg.cholesky(Cd, lower=True) # better error messages than the numpy version.
457+
Cd_L = np.linalg.cholesky(Cd) # ie Cd = CD_L @ CD_L.T Worse error messages, so best called in a try/except form.
458+
#Cd_L = scipy.linalg.cholesky(Cd, lower=True) # better error messages than the numpy versio, but can cause crashes on some machines
459459
x = np.random.randn((ny*nx)) # Parsons 2007 syntax - x for uncorrelated noise
460460
y = Cd_L @ x # y for correlated noise
461461
y_2d = np.reshape(y, (ny, nx)) # turn back to rank 2
@@ -574,7 +574,7 @@ def rescale_atmosphere(atm, atm_mean = 0.02, atm_sigma = 0.005):
574574
lats_mg_ds = lats_mg
575575

576576
#2: calculate distance between points
577-
ph_turb = np.zeros((n_atms, ny_generate, nx_generate)) # initiate output as a rank 3 (ie n_images x ny x nx)
577+
ph_turbs = np.zeros((n_atms, ny_generate, nx_generate)) # initiate output as a rank 3 (ie n_images x ny x nx)
578578
xyz_m, pixel_spacing = lon_lat_to_ijk(lons_mg_ds, lats_mg_ds) # get pixel positions in metres from origin in lower left corner (and also their size in x and y direction)
579579
xy = xyz_m[0:2].T # just get the x and y positions (ie discard z), and make lots x 2 (ie two columns)
580580

@@ -585,45 +585,83 @@ def rescale_atmosphere(atm, atm_mean = 0.02, atm_sigma = 0.005):
585585

586586
if method == 'fft':
587587
for i in range(n_atms):
588-
ph_turb[i,:,:] = generate_correlated_noise_fft(nx_generate, ny_generate, std_long=1,
588+
ph_turbs[i,:,:] = generate_correlated_noise_fft(nx_generate, ny_generate, std_long=1,
589589
sp = 0.001 * np.mean((pixel_spacing['x'], pixel_spacing['y'])) ) # generate noise using fft method. pixel spacing is average in x and y direction (and m converted to km)
590590
if verbose:
591591
print(f'Generated {i+1} of {n_atms} single acquisition atmospheres. ')
592592

593593
else:
594594
pixel_distances = sp_distance.cdist(xy,xy, 'euclidean') # calcaulte all pixelwise pairs - slow as (pixels x pixels)
595-
for i in range(n_atms):
596-
ph_turb[i,:,:] = generate_correlated_noise_cov(pixel_distances, cov_Lc, (nx_generate,ny_generate)) # generate noise
597-
if verbose:
598-
print(f'Generated {i+1} of {n_atms} single acquisition atmospheres. ')
595+
Cd = np.exp((-1 * pixel_distances)/cov_Lc) # from the matrix of distances, convert to covariances using exponential equation
596+
success = False
597+
while not success:
598+
try:
599+
Cd_L = np.linalg.cholesky(Cd) # ie Cd = CD_L @ CD_L.T Worse error messages, so best called in a try/except form.
600+
#Cd_L = scipy.linalg.cholesky(Cd, lower=True) # better error messages than the numpy versio, but can cause crashes on some machines
601+
success = True
602+
except:
603+
success = False
604+
for n_atm in range(n_atms):
605+
x = np.random.randn((ny_generate*nx_generate)) # Parsons 2007 syntax - x for uncorrelated noise
606+
y = Cd_L @ x # y for correlated noise
607+
ph_turb = np.reshape(y, (ny_generate, nx_generate)) # turn back to rank 2
608+
ph_turbs[n_atm,:,:] = ph_turb
609+
print(f'Generated {n_atm} of {n_atms} single acquisition atmospheres. ')
610+
611+
612+
# nx = shape[0]
613+
# ny = shape[1]
614+
615+
616+
617+
# return y_2d
618+
619+
# success = 0
620+
# fail = 0
621+
# while success < n_atms:
622+
# #for i in range(n_atms):
623+
# try:
624+
# ph_turb = generate_correlated_noise_cov(pixel_distances, cov_Lc, (nx_generate,ny_generate)) # generate noise
625+
# ph_turbs[success,:,:] = ph_turb
626+
# success += 1
627+
# if verbose:
628+
# print(f'Generated {success} of {n_atms} single acquisition atmospheres (with {fail} failures). ')
629+
# except:
630+
# fail += 0
631+
# if verbose:
632+
# print(f"'generate_correlated_noise_cov' failed, which is usually due to errors in the cholesky decomposition that Numpy is performing. The odd failure is normal. ")
633+
634+
# # ph_turbs[i,:,:] = generate_correlated_noise_cov(pixel_distances, cov_Lc, (nx_generate,ny_generate)) # generate noise
635+
# # if verbose:
636+
599637

600638

601639
#3: possibly interplate to bigger size
602640
if interpolate:
603641
if verbose:
604642
print('Interpolating to the larger size...', end = '')
605-
ph_turb_output = np.zeros((n_atms, ny, nx)) # initiate output at the upscaled size (ie the same as the original lons_mg shape)
606-
for atm_n, atm in enumerate(ph_turb): # loop through the 1st dimension of the rank 3 atmospheres.
643+
ph_turbs_output = np.zeros((n_atms, ny, nx)) # initiate output at the upscaled size (ie the same as the original lons_mg shape)
644+
for atm_n, atm in enumerate(ph_turbs): # loop through the 1st dimension of the rank 3 atmospheres.
607645
f = scipy_interpolate.interp2d(np.arange(0,nx_generate), np.arange(0,ny_generate), atm, kind='linear') # and interpolate them to a larger size. First we give it meshgrids and values for each point
608-
ph_turb_output[atm_n,:,:] = f(np.linspace(0, nx_generate, nx), np.linspace(0, ny_generate, ny)) # then new meshgrids at the original (full) resolution.
646+
ph_turbs_output[atm_n,:,:] = f(np.linspace(0, nx_generate, nx), np.linspace(0, ny_generate, ny)) # then new meshgrids at the original (full) resolution.
609647
if verbose:
610648
print('Done!')
611649
else:
612-
ph_turb_output = ph_turb # if we're not interpolating, no change needed
650+
ph_turbs_output = ph_turbs # if we're not interpolating, no change needed
613651

614652
# 4: rescale to correct range (i.e. a couple of cm)
615-
ph_turb_m = np.zeros(ph_turb_output.shape)
616-
for atm_n, atm in enumerate(ph_turb_output):
617-
ph_turb_m[atm_n,] = rescale_atmosphere(atm, mean_m)
653+
ph_turbs_m = np.zeros(ph_turbs_output.shape)
654+
for atm_n, atm in enumerate(ph_turbs_output):
655+
ph_turbs_m[atm_n,] = rescale_atmosphere(atm, mean_m)
618656

619657
# 5: return back to the shape given, which can be a rectangle:
620-
ph_turb_m = ph_turb_m[:,:lons_mg.shape[0],:lons_mg.shape[1]]
658+
ph_turbs_m = ph_turbs_m[:,:lons_mg.shape[0],:lons_mg.shape[1]]
621659

622660
if water_mask is not None:
623-
water_mask_r3 = ma.repeat(water_mask[np.newaxis,], ph_turb_m.shape[0], axis = 0)
624-
ph_turb_m = ma.array(ph_turb_m, mask = water_mask_r3)
661+
water_mask_r3 = ma.repeat(water_mask[np.newaxis,], ph_turbs_m.shape[0], axis = 0)
662+
ph_turbs_m = ma.array(ph_turbs_m, mask = water_mask_r3)
625663

626-
return ph_turb_m
664+
return ph_turbs_m
627665

628666
#%%
629667

0 commit comments

Comments
 (0)