Project: s41467-024-53200-w | Function: do_unc_calc (python)

system: You are an expert Python programmer implementing algorithms from scientific papers.
Complete the masked function based on the algorithm description.
Follow Python best practices and maintain consistent code style.
Return **only** the complete Python function code.

user: Complete this Python function:

**DESCRIPTION:** Performs uncertainty calculation and sensitivity analysis for tropical cyclone displacement forecast using Monte Carlo sampling and uncertainty quantification methods

**KEY TERMS:** {
  "calc_imp": "impact calculator",
  "make_sample": "Monte Carlo sampling",
  "uncertainty": "uncertainty quantification",
  "sensitivity": "sensitivity analysis",
  "N": "number of samples"
}

**CONTEXT CODE:**                     'OC1': [32.2, 33.3, 37.9, 41.4, 41.5, 42.6, 45. , 45.4, 45.9, 46.9, 46.9,
                            47.3, 52.5, 53.3, 54.3, 56.6, 57.5, 59.3, 65.3, 68.9, 69.5, 76.8,
                            78.6, 82.3, 83.4, 85. ],
                    'OC2': [ 29.9,  40.2,  42.8,  47. ,  47. ,  54.3,  54.6,  62.9,  65.6,
                            68.9,  89.9, 108.9],
                    'SI': [33.8, 39.3, 40.4, 40.7, 46.2, 48.7, 49.3, 56.9, 58. , 58.2, 63.7,
                           68. ],
                    'WP1': [31.5, 38.6, 42.1, 43.6, 43.8, 51.6, 51.8, 53. , 54. , 54.9, 60.9,
                            69.8, 72.2, 78.1],
                    'WP2': [ 37.4,  38.8,  40.7,  42.5,  43.8,  43.8,  45.4,  49.2,  49.3,
                            49.5,  49.8,  51.6,  52. ,  52.3,  56. ,  56.7,  58.2,  60.6,
                            63.9,  65.1,  66.8,  73.6,  76.8,  83.9,  92.7,  97.5, 103.5],
                    'WP3': [25.8, 25.8, 25.8, 25.8, 25.8, 25.8, 28.1, 29.4, 30. , 31.9, 33.7,
                            33.8, 34. , 34.3, 37.5, 38.2, 40.9, 41.6, 42.7, 43.4, 44.3, 44.6,
                            45.3, 47.2, 47.3, 51.9, 56.1, 56.1, 57. , 59.4, 66.3, 67.2, 71.7,
                            73.3, 77. , 88.1],
                    'WP4': [ 37.4,  38.6,  40.4,  41.5,  41.6,  41.6,  43.2,  44.8,  49.2,
                            50.1,  52.5,  58.3,  62.7,  64.7,  67.1,  72.2,  85. ,  86.2,
                            87.9,  87.9,  97.2, 103.5, 105.6, 113.8, 118.1, 125.4, 127. ]}

leadtime_str2float = {'0_5d': 0.5, '1d': 1, '1_5d': 1.5, '2d': 2, '2_5d': 2.5, '3d': 3}

###### functions ######

def impf_func(v_half=45.6):
    """Creates impact function for tropical cyclone displacement using Emanuel model, relating wind speed to displacement probability with specified v_half parameter"""
    raise NotImplementedError("Function implementation has been masked")

# Functions
def extent_buffer(extent, buffer=5):
    extent = latlon_bounds(np.array([extent[2], extent[3]]), np.array([extent[0], extent[1]]), buffer=buffer)
    
    return (extent[0], extent[2], extent[1], extent[3])

def get_impf_distr(country):
    """
    Get impf_distr according to selected country (country_iso3)
    Argument:
        country (string): selected country (variable is "country_iso3")
    """
    # Get basin in which country_iso3 lies
    basin = [key
            for key, list_of_values in iso3_to_basin.items()
            if country in list_of_values]
    
    # Get impf_distr corresponding to basin
    impf_distribution = impf_set_region[basin[0]]
    
    return impf_distribution

def do_unc_calc(exp_iv, impf_iv, haz_iv, N):
    """Performs uncertainty calculation and sensitivity analysis for tropical cyclone displacement forecast using Monte Carlo sampling and uncertainty quantification methods"""
    raise NotImplementedError("Function implementation has been masked")

##### Uncertainty parameters #####
N_SAMPLE = 1024

# uncertanty parameters
## hazard sub-sampling with replacement
n_ev_haz = 1

## exposures
bounds_totval = [0.8, 1.2] #+- 20% noise on the total exposures value

## impact funcs
impf_set_region = {}
for region in impf_80quantiles:
    impf_set_list = []
    for v_half in impf_80quantiles[region]:
        impf_set = impf_func(v_half=v_half)
        impf_set_list.append(impf_set)
    impf_set_region[region] = impf_set_list

########## read TIGGE files ##########
def main(storm_year=2017, leadtime_str='0_5d'):

    # Read all TIGGE tracks from 2017-2020 into a TCTracks()-object
    tigge_tc_tracks = TCTracks()
    
    for file in glob.glob(DATA_DIR + 'nc_ecmf_{storm_year}_*.nc'.format(storm_year=storm_year)):
        print(file)                                              # to check in console if script is still running
        tigge_tc_tracks.data.extend(read_TIGGE_netcdf(file, matched_ibtracs_only=True).data)
    
    ##### run the uncertainty analysis #####
    for idx_entry in range(len(df_storm)):
       
        # extract the landfall time
        landfall_time = datetime.strptime(df_storm['landfall_time'][idx_entry], '%Y-%m-%dT%H:%M:%S')
        if landfall_time.year != int(storm_year):
            continue
        
        
        init_datetime = landfall_time - timedelta(days=leadtime_str2float[leadtime_str])
        
        # rounding time to 00 or 12 UTC
        if init_datetime.hour == 3 or init_datetime.hour == 15:
            init_datetime = init_datetime - timedelta(hours=3)
        elif init_datetime.hour == 6 or init_datetime.hour == 18:
            init_datetime = init_datetime - timedelta(hours=6)
        elif init_datetime.hour == 9 or init_datetime.hour == 21:
            init_datetime = init_datetime + timedelta(hours=3)
        
        # extract storm from TIGGE
        ibtracs_id = df_storm['ibtracs_id'][idx_entry]
        
        # select all the ens. tracks from 1 storm out of all TIGGE tracks from 2017-2020
        try:
            sel_storm = tigge_tc_tracks.subset({"matched_ibtracs": ibtracs_id,
                                                "is_ensemble": True,
                                                "run_datetime": init_datetime})
            sel_storm.equal_timestep(time_step_h=1)          # interpolate the track to 1 hour interval
            
            extent = sel_storm.get_extent()
            extent_buffered = extent_buffer(extent, buffer=5)
        except ValueError:
            print(ibtracs_id, 'with init datetime', init_datetime.strftime('%Y-%m-%dT%H:%M:%S'), 'does not exist')
            continue
        
        ##### compute TC windfield #####
        # Compute the predicted windfield of the track at a resolution of 150 ArcSec (approx. 5km) on land and 1800 ArcSec (approx. 50km) on sea
        centroids_sel = centroids_global.select(extent = extent_buffered)
        centroids_sel.lon = u_coord.lon_normalize(centroids_sel.lon)         # Lon-coordinates need to be normalized to a common 360 degree range
                    
        tc_fcast = TropCyclone.from_tracks(sel_storm, centroids_sel)
        tc_fcast.frequency = np.ones(len(tc_fcast.event_id))
        
        ##### haz_iv #####
        haz_iv = InputVar.haz([tc_fcast], n_ev=n_ev_haz)
        
        ##### exp_iv #####
        exp_base = LitPop.from_population(countries=df_storm['ISO3'][idx_entry])
        exp_base.assign_centroids(tc_fcast)
        
        exp_iv = InputVar.exp([exp_base], bounds_totval=bounds_totval)
        
        ##### impf_iv ######
        impf_iv = InputVar.impfset(impf_set_list=get_impf_distr(df_storm['ISO3'][idx_entry]))
        
        ##### calculate uncertainty and save outputs ######
        unc_output = do_unc_calc(exp_iv, impf_iv, haz_iv, N=N_SAMPLE)
        
        save_unc = SAVE_UNC_PATTERN.format(leadtime_str=leadtime_str, tc_name=df_storm['TC_name'][idx_entry],
                                           country_iso3=df_storm['ISO3'][idx_entry], ibtracs_id=ibtracs_id,
                                           init_datetime_str=init_datetime.strftime('%Y-%m-%dT%H:%M:%S'))
        print(save_unc)
        unc_output.to_hdf5(save_unc)
        
if __name__ == "__main__":
     main(*sys.argv[1:])

Complete the function implementation replacing "raise NotImplementedError".