Beispiel #1
0
def remove_simulationsWithoutOutncFile(input_files):
    ''' Analysis can only be performed if *out.nc file or *out.h5 file is present.

    Returns
    -------       
    input_files : list of PosixPaths
        List of input_files of which the existence of *out.nc or *out.h5 files is checked.
    '''

    # Select the cases without an *out.nc file
    printv("Checking existence of *out.nc file corresponding inputs.")
    count = 0
    not_valid = []
    for input_file in input_files:
        if os.path.isfile(input_file.with_suffix('.out.nc')):
            count = count + 1
        elif os.path.isfile(input_file.with_suffix('.out.h5')):
            count = count + 1
        else:
            printv("    File " + input_file.name + " removed from input list.")
            not_valid.append(input_file)
    printv(
        "    Number of found input files with corresponding output *.nc = " +
        str(count))

    # Remove the selected cases
    for input_file in not_valid:
        i = input_files.index(input_file)
        input_files.pop(i)

    return input_files
Beispiel #2
0
def read_vmecgeo(input_file):
    ''' Read the "*.vmec_geo" file and return the a_ref and B_ref. '''
    
    # Find the omega file corresponding to the input file
    vmec_file = input_file.parent / "vmec_geo"
    geometry_file = input_file.with_suffix(".geometry")

    # Read the *vmec_geo file and get the first two lines (header + values)
    if os.path.isfile(vmec_file):
        geo_data = read_vmecgeoFromVmecgeoFile(vmec_file)

    # Read the *.geometry file and get the first two lines (header + values)
    elif os.path.isfile(geometry_file):
        geo_data = read_vmecgeoFromGeometryFile(geometry_file)

    # Data is saved to the reduced "wout*.h5" file
    elif get_filesInFolder(input_file.parent, start="wout", end=".h5"): 
        geo_data = read_vmecgeoFromH5File(input_file)

    # ERROR IF BOTH FILES DON'T EXIST
    else: printv("No '*.vmec_geo' or '*.geometry' or 'wout*.h5' file found."); return {}
    
    # Return the data
    return geo_data  
Beispiel #3
0
    def print_research(self):

        # Print some information
        printv("")
        printv("##############################")
        printv("            RESEARCH     ")
        printv("##############################")

        for experiment in self.experiments:
            printv("")
            printv(" Experiment " +
                   str(self.experiments.index(experiment) + 1) + ": " +
                   experiment.id)
            printv(" --------------------------------")
            if self.number_variedVariables == -1:
                printv(
                    "   Each simulation is assumed to be its own experiment at the following radial position:  "
                )
            if self.number_variedVariables == 1:
                printv(
                    "   There is 1 varied parameter with the following values:  "
                )
            if self.number_variedVariables > 1:
                printv("   There are " + str(self.number_variedVariables) +
                       " varied parameters with the following values:  ")
            for varied_value in experiment.variedValues:
                printv("          " + varied_value)
            printv("   The simulations associated to this experiment are:  ")
            for simulation in experiment.simulations:
                printv("          " + '"' + simulation.id)
            printv("")

        # Dont collapse header on the next line
        if True: return
Beispiel #4
0
def create_experiments(\
    # To create the simulations we need their location and whether to ignore the resolution

    folders=None, input_files=None, ignore_resolution=True, \
    # To group them by experiment we need to know how many variables differ between the simulations
    # Or which variable is unique for each experiment


    number_variedVariables=1, experiment_knob="vmec_parameters", experiment_key="vmec_filename",\
    # Give some extra rules

    folderIsExperiment = False):
    ''' Divide the simulations in experiments, based on the number of varied values
    or based on a parameter that differs for each experiment. For example, when we 
    scan multiple radial positions of multiple shots, then each shot is defined by
    its unique magnetic field file. If they are run for the same magnetic field, just
    make sure they have a different symbolic link to differ between the experiments. '''

    # First create the simulations
    simulations = create_simulations(folders, input_files, ignore_resolution,
                                     number_variedVariables,
                                     folderIsExperiment)

    # Create an empty list to hold the experiment objects
    experiments = []

    # Iterate over the simulations objects
    for simulation in simulations:

        # Assume we have a new experiment
        newExperiment = True

        # Go through all the current experiments and see if the simulation belongs to one of these
        for experiment in experiments:

            # If the simulations are in a different folder its a new experiment.
            if not (folderIsExperiment == True
                    and simulation.parent != experiment.simulations[0].parent):

                # Look at the difference in the input parameters of the experiment and the simulation
                dict_difference = get_differenceInDictionaries(
                    experiment.inputParameters, simulation.inputParameters)

                # Sometimes we rerun the exact same simulation with different modes: we have the same experiment
                # However if we want this simulation to be a new simulation, force this with number_variedValues=-1
                # If number_variedVariables==-2 then we want to sort by "1 folder = 1 experiment"
                # If number_variedVariables==-1 then we want to sort by "1 folder = 1 simulation"
                if len(list(dict_difference.keys())) == 0:
                    if number_variedVariables == -2:
                        newExperiment = True
                    if number_variedVariables != -2:
                        newExperiment = False
                        experiment.simulations.append(simulation)

                # The simulation belongs to the current experiment if
                # inputParameters[experiment_knob][experiment_key] is the same.
                # For example if the radial position is varied then different experiments are characterized
                # by having a different vmec_filename in the knob "vmec_parameters".
                # Therefore if inputParameters[experiment_knob][experiment_key]
                # Is the same in the simulation and experiment, add the simulation to this experiment.
                # This rule is overriden if less than or equal to <number_variedVariables> are varied.
                elif experiment_knob in list(dict_difference.keys()):
                    if experiment_key not in dict_difference[experiment_knob]:
                        newExperiment = False
                        experiment.simulations.append(simulation)
                        varied_variable = experiment_knob + ": " + dict_difference[
                            experiment_knob][0]
                        if varied_variable not in experiment.variedVariables:
                            experiment.variedVariables.append(varied_variable)

                # The simulation is part of the experiment if up to <number_variedVariables> input variables are different
                elif len(list(
                        dict_difference.keys())) <= number_variedVariables:
                    varied_values = 0
                    for knob in list(dict_difference.keys()):
                        for parameter in dict_difference[knob]:
                            varied_values += 1
                    if varied_values <= number_variedVariables:
                        newExperiment = False
                        experiment.simulations.append(simulation)
                        for knob in list(dict_difference.keys()):
                            for parameter in dict_difference[knob]:
                                varied_variable = knob + ": " + parameter
                                if varied_variable not in experiment.variedVariables:
                                    experiment.variedVariables.append(
                                        varied_variable)

        # If more than <number_variedVariables> variables are different we have a new experiment
        # If inputParameters[experiment_knob][experiment_key] is different we have a new experiment
        # If the experiments list is empty we have a new experiment
        # In this case create an <Experiment> object and add it to the list
        if newExperiment == True:
            experiments.append(Experiment(simulation))

    # For the variables that are varied, get their exact values for the labels in the graph
    # For example if experiment.variedVariables = ["vmec_parameter: torflux", "knobs: delt"]
    # then experiment.variedValues = ["rho=0.5; delt=0.1", "rho=0.5; delt=0.01"; "rho=0.6; delt=0.1"]
    for experiment in experiments:
        for simulation in experiment.simulations:
            varied_values = ""  # This will be used as the label in the figure
            for variable in experiment.variedVariables:
                knob = variable.split(":")[0]
                variable = variable.split(": ")[-1]
                if knob != experiment_knob and variable != experiment_key:
                    value = simulation.inputParameters[knob][variable]
                    variable = variable.replace("_", " ")
                    if variable == "torflux":
                        value = str(round(np.sqrt(float(value)), 2))
                    if variable == "torflux": variable = "$\\rho$"
                    if variable == "rho": variable = "$\\rho$"
                    if variable == "delt": variable = "$\\Delta t$"
                    if knob == 'species_parameters_1':
                        if variable == "tprim": variable = "$a/L_{Ti}$"
                        if variable == "fprim": variable = "$a/L_{ne}$"
                    if knob == 'species_parameters_2':
                        if variable == "tprim": variable = "$a/L_{Te}$"
                        if variable == "fprim": variable = "$a/L_{ni}$"
                    varied_values = varied_values + variable + "$\,=\,$" + str(
                        value) + "; "

            # If a/Lne = a/Lni replace it by a/Ln
            if "$a/L_{ni}$" in varied_values and "$a/L_{ne}$" in varied_values:
                _elements = varied_values.split("; ")
                _elements = [e for e in _elements if e != '']
                _ionDensityGrad = float([
                    e for e in _elements if "$a/L_{ni}$" in e
                ][0].split("$\,=\,$")[-1])
                _others = [
                    e for e in _elements
                    if ("$a/L_{ni}$" not in e) and ("$a/L_{ne}$" not in e)
                ]
                if len(_others) != 0:
                    varied_values = "; ".join(
                        _others) + "; " + "$a/L_{n}$" + "$\,=\,$" + str(
                            _ionDensityGrad) + "; "
                if len(_others) == 0:
                    varied_values = "$a/L_{n}$" + "$\,=\,$" + str(
                        _ionDensityGrad) + "; "

            # If nothing was different because we only have one experiment, use the radial position as the label
            if varied_values == "":
                varied_values = "$\\rho$" + "$\,=\,$" + str(
                    experiment.inputParameters['vmec_parameters']
                    ['rho']) + "; "

            # The time step in the input_file isn't the actual time step
            if "$\\Delta t$" in varied_values and False:
                netcdf_data = read_netcdf(simulation.input_files[0])
                try:
                    delt = (netcdf_data['vec_time'][5] -
                            netcdf_data['vec_time'][4]
                            ) / simulation.inputParameters[
                                'stella_diagnostics_knobs']['nwrite']
                except:
                    delt = (netcdf_data['vec_time'][5] -
                            netcdf_data['vec_time'][4]
                            ) / simulation.inputs[simulation.input_files[0]][
                                'stella_diagnostics_knobs']['nwrite']
                simulation.inputParameters['knobs']['delt'] = round(delt, 4)
                varied_values = varied_values + "$\\delta t$$\,=\,$" + str(
                    round(delt, 4)) + "; "

            # Add the string of varied values to the dictionary
            experiment.variedValues.append(varied_values[0:-2])

    # Make sure we don't have the same label for each simulation
    for experiment in experiments:
        if len(set(experiment.variedValues)) == 1 and len(
                experiment.variedValues) != 1:
            simulation_ids = [
                simulation.id for simulation in experiment.simulations
            ]
            common_prefix = ''.join(c[0] for c in takewhile(
                lambda x: all(x[0] == y for y in x), zip(*simulation_ids)))
            common_prefix = common_prefix if common_prefix.endswith(
                "_") else '_'.join(common_prefix.split('_')[0:-1]) + "_"
            simulation_ids2 = [
                simulation.id.split("__")[0]
                for simulation in experiment.simulations
            ]
            common_prefix2 = ''.join(c[0] for c in takewhile(
                lambda x: all(x[0] == y for y in x), zip(*simulation_ids2)))
            common_prefix2 = common_prefix if common_prefix2.endswith(
                "_") else '_'.join(common_prefix2.split('_')[0:-1]) + "_"
            for i in range(len(experiment.variedValues)):
                if "__" in common_prefix:
                    experiment.variedValues[i] = str(
                        experiment.simulations[i].id).split("__")[-1]
                if "__" not in common_prefix:
                    experiment.variedValues[i] = str(
                        experiment.simulations[i].id).split("__")[0].split(
                            common_prefix2)[1]
            # Add the delt t for now
            if False:
                for i in range(len(experiment.variedValues)):
                    simulation = experiment.simulations[i]
                    for input_file in simulation.input_files:
                        netcdf_data = read_netcdf(input_file)
                        delt1 = (netcdf_data['vec_time'][5] -
                                 netcdf_data['vec_time'][4]
                                 ) / simulation.inputParameters[
                                     'stella_diagnostics_knobs']['nwrite']
                        delt2 = (netcdf_data['vec_time'][-4] -
                                 netcdf_data['vec_time'][-5]
                                 ) / simulation.inputParameters[
                                     'stella_diagnostics_knobs']['nwrite']
                        print("   Time step input:",
                              simulation.inputParameters['knobs']['delt'])
                        print("   Time step start:", delt1)
                        print("   Time step end:", delt2)
                    experiment.variedValues[i] = experiment.variedValues[
                        i] + "; $\\delta t$$\,=\,$" + str(round(delt1, 4))
                    #experiment.variedValues[i] =  experiment.variedValues[i] + "; $\\delta_e t$$\,=\,$" + str(round(delt2,4))

#     # Sort the simulations and varied values
#     numbers = [ int("".join([s for s in value if s.isdigit()])) for value in experiment.variedValues ]
#     sorted_indexes = list(np.array(numbers).argsort())
#     experiment.variedValues = [experiment.variedValues[i] for i in sorted_indexes]
#     experiment.simulations  = [experiment.simulations[i] for i in sorted_indexes]

# Now that all the simulations are added to experiments, we an add labels and markers and write config
    for experiment in experiments:
        experiment.finish_initialization()

    # Print some information
    for experiment in experiments:
        printv("")
        printv("Experiment " + str(experiments.index(experiment) + 1) + ":")
        printv("     Experiment ID:  " + experiment.id)
        printv("     Varied Values:  " + '["' +
               ('", "').join(experiment.variedValues) + '"]')
        printv("     Simulation IDs: " + "[" +
               (",").join([s.id for s in experiment.simulations]) + "]")
        printv("")

    return experiments
def read_inputParameters(input_file):
    ''' Read "*.in" file and return all the input parameters.
    
    Parameters
    ----------
    input_file : PosixPath
        Absolute path of the *.in file that needs to be read.
    
    Returns
    -------
    inputParameters = dict[knobs][variable]
        Returns the namelists from the stella code with their values.
    
    
    Knobs and variables
    -------------------
    zgrid_parameters:  
        nzed; nperiod; ntubes; boundary_option; zed_equal_arc; shat_zero; nzgrid"
        
    geo_knobs:
       geo_option; overwrite_bmag; overwrite_gradpar; overwrite_gds2; overwrite_gds21; overwrite_gds22; 
       overwrite_gds23; overwrite_gds24; overwrite_gbdrift; overwrite_cvdrift; overwrite_gbdrift0; geo_file
       
    vmec_parameters
        vmec_filename; alpha0; zeta_center; nfield_periods; torflux; surface_option;
        verbose; zgrid_scalefac; zgrid_refinement_factor 
        
    parameters
        beta; vnew_ref; rhostar; zeff; tite; nine 
        
    vpamu_grids_parameters
        nvgrid; vpa_max; nmu; vperp_max; equally_spaced_mu_grid 
        
    dist_fn_knobs
        adiabatic_option 
        
    time_advance_knobs
        explicit_option; xdriftknob; ydriftknob; wstarknob; flip_flo

    kt_grids_knobs
        grid_option 
        
    kt_grids_box_parameters
        nx; ny; dkx; dky; jtwist; y0; naky; nakx
        
    kt_grids_range_parameters
        nalpha; naky; nakx; aky_min; aky_max; akx_min; akx_max; theta0_min; theta0_max
        
    physics_flags
        full_flux_surface; include_mirror; nonlinear; include_parallel_nonlinearity; include_parallel_streaming

    init_g_knobs
        tstart; scale; ginit_option; width0; refac; imfac; den0; par0; tperp0; den1; upar1; tpar1; tperp1; 
        den2; upar2; tpar2; tperp2; phiinit; zf_init; chop_side; left; even; restart_file; restart_dir; read_many
        
    knobs
        nstep; delt; fapar; fbpar; delt_option; zed_upwind; vpa_upwind; time_upwind; avail_cpu_time; cfl_cushion
        delt_adjust; mat_gen; mat_read; fields_kxkyz; stream_implicit; mirror_implicit; driftkinetic_implicit
        mirror_semi_lagrange; mirror_linear_interp; maxwellian_inside_zed_derivative; stream_matrix_inversion 

    species_knobs
        nspec; species_option 

    species_parameters_1; species_parameters_2; species_parameters_3; ...
        z; mass; dens; temp; tprim; fprim; d2ndr2; d2Tdr2; type 

    stella_diagnostics_knobs
        nwrite; navg; nmovie; nsave; save_for_restart; write_omega; write_phi_vs_time; write_gvmus
        write_gzvs; write_kspectra; write_moments; flux_norm; write_fluxes_kxky 

    millergeo_parameters
        rhoc; rmaj; shift; qinp; shat; kappa; kapprim; tri; triprim; rgeo; betaprim; betadbprim
        d2qdr2; d2psidr2; nzed_local; read_profile_variation; write_profile_variation 

    layouts_knobs
        xyzs_layout; vms_layout 

    neoclassical_input
        include_neoclassical_terms; nradii; drho; neo_option
        
    sfincs_input
        read_sfincs_output_from_file; nproc_sfincs; irad_min; irad_max; calculate_radial_electric_field
        includeXDotTerm; includeElectricFieldTermInXiDot; magneticDriftScheme; includePhi1
        includePhi1InKineticEquation; geometryScheme; VMECRadialOption; equilibriumFile
        coordinateSystem; inputRadialCoordinate; inputRadialCoordinateForGradients; aHat
        psiAHat; Delta; nu_n; dPhiHatdrN; Er_window; nxi; nx; Ntheta; Nzeta
    '''

    # Read the *in file and get the parameters for species_1
    input_data = open(input_file, 'r')
    input_text = input_data.read().replace(' ', '')

    # Initiate the dictionary: load the default parameters
    input_parameters = load_defaultInputParameters()

    # Get the number of species
    nspec = read_integerInput(input_text, 'nspec')
    if nspec == "USE DEFAULT": nspec = 1

    # Add more default species if nspec>2
    for i in range(2, nspec + 1):
        knob = "species_parameters_" + str(i)
        keys = list(input_parameters["species_parameters_1"].keys())
        input_parameters[knob] = {}
        for key in keys:
            input_parameters[knob][key] = input_parameters[
                "species_parameters_1"][key]

    # Overwrite the default values if they have been changed in the <input files>
    knobs = list(input_parameters.keys())
    knobs.remove(" ")
    for knob in knobs:
        try:  # Only look at the text of the specific knob
            input_text_knob = input_text.split("&" + knob)[1].split("/")[0]
            parameters = list(input_parameters[knob].keys())
            for parameter in parameters:
                default_value = input_parameters[knob][parameter]
                input_parameters[knob][
                    parameter] = read_parameterFromInputFile(
                        input_text_knob, parameter, default_value)
        except:  # If the knob is not present just pass
            pass

    # Fill in the species know for the adiabatic electrons: custom knob for the GUI
    if input_parameters["species_knobs"]["nspec"] == 1:
        input_parameters["species_parameters_a"]["nine"] = input_parameters[
            "parameters"]["nine"]
        input_parameters["species_parameters_a"]["tite"] = input_parameters[
            "parameters"]["tite"]
        input_parameters["species_parameters_a"]["dens"] = round(
            1 / input_parameters["parameters"]["nine"], 4)
        input_parameters["species_parameters_a"]["temp"] = round(
            1 / input_parameters["parameters"]["tite"], 4)

    # Calculate some extra variables
    input_parameters["vmec_parameters"]["rho"] = np.sqrt(
        input_parameters["vmec_parameters"]["torflux"])

    # Now calculate some values manually
    if input_parameters["sfincs_input"]["irad_min"] == "-nradii/2":
        nradii = input_parameters["neoclassical_input"]["nradii"]
        input_parameters["sfincs_input"]["irad_min"] = -nradii / 2
        input_parameters["sfincs_input"]["irad_max"] = nradii / 2
    else:
        printv(
            "WARNING: <irad_min> was set by the input file but it should be calculated indirectly through <nradii>."
        )

    if input_parameters["zgrid_parameters"][
            "nzgrid"] == "nzed/2 + (nperiod-1)*nzed":
        nzed = input_parameters["zgrid_parameters"]["nzed"]
        nperiod = input_parameters["zgrid_parameters"]["nperiod"]
        input_parameters["zgrid_parameters"]["nzgrid"] = nzed / 2 + (nperiod -
                                                                     1) * nzed
    else:
        printv(
            "WARNING: <nzgrid> was set by the input file but it should be calculated indirectly through <nzed> and <nperiod>."
        )

    if input_parameters["vmec_parameters"][
            "zgrid_scalefac"] == "2.0 if zed_equal_arc else 1.0":
        zed_equal_arc = input_parameters["zgrid_parameters"]["zed_equal_arc"]
        input_parameters["vmec_parameters"][
            "zgrid_scalefac"] = 2.0 if zed_equal_arc else 1.0
        input_parameters["vmec_parameters"][
            "zgrid_refinement_factor"] = 4 if zed_equal_arc else 1
    else:
        printv(
            "WARNING: <zgrid_scalefac> was set by the input file but it should be calculated indirectly through <zed_equal_arc>."
        )

    if input_parameters["physics_flags"]["nonlinear"] == True:
        if input_parameters["kt_grids_box_parameters"][
                "naky"] == "(ny-1)/3 + 1":
            ny = input_parameters["kt_grids_box_parameters"]["ny"]
            nx = input_parameters["kt_grids_box_parameters"]["nx"]
            input_parameters["kt_grids_box_parameters"]["naky"] = get_naky(ny)
            input_parameters["kt_grids_box_parameters"]["nakx"] = get_nakx(nx)
        if input_parameters["kt_grids_box_parameters"]["y0"] == -1.0:
            if input_parameters["physics_flags"]["full_flux_surface"] == False:
                print(
                    "WARNING: When simulating a flux tube, y0 needs to be set in the input file."
                )
            if input_parameters["physics_flags"]["full_flux_surface"] == True:
                input_parameters["kt_grids_box_parameters"][
                    "y0"] = "1./(rhostar*geo_surf%rhotor)"

        # Calculate some extra variables for nonlinear runs, note that svalue=psitor=torflux??
        nfield = input_parameters["vmec_parameters"]["nfield_periods"]
        y0 = input_parameters["kt_grids_box_parameters"]["y0"]
        vmec_file = input_parameters["vmec_parameters"]["vmec_filename"]
        psitor = input_parameters["vmec_parameters"]["torflux"]
        nx = input_parameters["kt_grids_box_parameters"]["nx"]
        input_parameters["kt_grids_box_parameters"]["dky"] = round(
            1. / y0, 4
        )  # get the grid spacing in ky and then in kx using twist-and-shift BC
        try:
            vmec_file = input_file.parent + "/" + vmec_file
            if input_parameters["kt_grids_box_parameters"]["jtwist"] == -1:
                input_parameters["kt_grids_box_parameters"][
                    "jtwist"] = get_jtwist(vmec_file, psitor, nfield)
            input_parameters["kt_grids_box_parameters"]["dkx"] = get_dkx(
                vmec_file, psitor, nfield, y0)
            input_parameters["kt_grids_box_parameters"]["shat"] = get_shat(
                vmec_file, psitor)
        except:
            try:
                vmec_file = str(vmec_file).split(".nc")[0] + ".h5"
                if input_parameters["kt_grids_box_parameters"]["jtwist"] == -1:
                    input_parameters["kt_grids_box_parameters"][
                        "jtwist"] = round(
                            get_jtwist(vmec_file, psitor, nfield), 4)
                input_parameters["kt_grids_box_parameters"]["dkx"] = round(
                    get_dkx(vmec_file, psitor, nfield, y0), 4)
                input_parameters["kt_grids_box_parameters"]["shat"] = round(
                    get_shat(vmec_file, psitor), 4)
            except:
                printv('WARNING: vmec file was not found to calculate jtwist.')

    if input_parameters["physics_flags"]["nonlinear"] == False:
        for key in input_parameters["kt_grids_box_parameters"].keys():
            input_parameters["kt_grids_box_parameters"][
                key] = "Linear simulation"

    input_data.close()

    # Return the input_parameters
    return input_parameters
    if True: return
Beispiel #6
0
    def write_unstableModes(self):
        ''' Sort the modes by stable/unstable and converged/unconverged. '''
        
        # The section headers to save the data
        mode_header = 'STABLE MODE; CONVERGED MODE; PHI_END - PHI_START, RELATIVE FLUCTUATION OVER THE LAST 10% OF OMEGA AND GAMMA' 
        
        # If the section already existed, remove it
        if mode_header in self.simulation_file: self.simulation_file.remove_section(mode_header) 
        
        # Create the empty section
        self.simulation_file[mode_header] = {} 
            
        # Build a matrix to hold whether the mode is stable/unstable and converged/unconverged
        self.stable_modes    = np.empty((len(self.vec_kx), len(self.vec_ky))); self.stable_modes[:,:] = np.NaN
        self.converged_modes = np.empty((len(self.vec_kx), len(self.vec_ky))); self.converged_modes[:,:] = np.NaN
        
        # Go through the modes
        for i in range(len(self.vec_kx)):
            for j in range(len(self.vec_ky)):
            
                # Update the progress bar of the GUI
                if self.Progress: c = i*len(self.vec_ky) + j; length = len(self.vec_kx)*len(self.vec_ky)
                if self.Progress: self.Progress.move(c/length*100,"Determining the stability ("+str(c)+"/"+str(length)+")")
                
                # An unstable mode will grow in phi2 while a stable mode will decrease in phi2, therefore:
                # if phi_end < phi_start the mode is stable, if phi_end > phi_start, the mode is unstable.
                phi2_noNaN      = self.phi2_kxky[:,i,j][np.isfinite(self.phi2_kxky[:,i,j])]
                first_10percent = int(np.size(phi2_noNaN)*1/10)
                last_10percent  = int(np.size(phi2_noNaN)*9/10)
                phi_start = np.mean(phi2_noNaN[0:first_10percent])
                phi_end   = np.mean(phi2_noNaN[last_10percent:-1]) 
                 
                # Add <True> to <stable_modes> if the mode is stable, otherwise add <False>
                if phi_end - phi_start < 0: self.stable_modes[i,j] = True
                if phi_end - phi_start > 0: self.stable_modes[i,j] = False
                if len(phi2_noNaN) < 15:    self.stable_modes[i,j] = False
                
#                 # The above criteria doesn't always work correctly: stable modes have noise of gamma around zero!
#                 gamma_noNaN     = self.gamma_kxky[:,i,j][np.isfinite(self.gamma_kxky[:,i,j])] 
#                 last_10percent  = int(np.size(gamma_noNaN)*9/10) 
#                 gamma_10percent = gamma_noNaN[last_10percent:-1]
#                 sign_changes    = np.where(np.diff(np.sign(gamma_10percent)))[0]
#                 if len(sign_changes) >= : self.stable_modes[i,j] = True
                
                # Only if the mode is unstable, look if it is converged
                fluctuation_o = np.NaN; fluctuation_g = np.NaN
                if self.stable_modes[i,j] == False:
    
                    # Look at omega and gamma to determine whether the mode has converged
                    omega  = abs(self.omega_kxky)[:,i,j]
                    gamma  = abs(self.gamma_kxky)[:,i,j]
            
                    # Remove the nan and inifinite values
                    omega  = omega[np.isfinite(gamma)]
                    gamma  = gamma[np.isfinite(gamma)]
                    
                    # If the mode has converged it approaches a fixed value in time
                    # Consinder the mode converged if gamma/omega doesn't change more than 2% over the last 10% of time
                    did_yConverge = np.all( np.isclose(omega[int(len(omega)*0.90):], omega[-1], rtol=0.02) )
                    did_yConverge = np.all( np.isclose(gamma[int(len(gamma)*0.90):], gamma[-1], rtol=0.02) ) & did_yConverge
                    
                    # Add <True> to <converged_modes> if the mode has converged, otherwise add <False>
                    if did_yConverge == True:  self.converged_modes[i,j] = True
                    if did_yConverge == False: self.converged_modes[i,j] = False 
                    
                    # Add some information to the command prompt
                    if did_yConverge == False: 
                        kx = self.vec_kx[i]; ky = self.vec_ky[j]
                        fluctuation_o = (max(omega[int(len(omega)*0.90):])-min(omega[int(len(omega)*0.90):]))/omega[-1]*100
                        fluctuation_g = (max(gamma[int(len(gamma)*0.90):])-min(gamma[int(len(gamma)*0.90):]))/gamma[-1]*100
                        printv("\nThe simulation for (kx,kx) = ("+str(kx)+", "+str(ky)+") has not converged yet:")
                        printv("    The relative fluctuation of omega between (0.9*t, t) is:  "+str(fluctuation_o)+"%.")
                        printv("    The relative fluctuation of gamma between (0.9*t, t) is:  "+str(fluctuation_g)+"%.")
        
                # Save the linear data: [omega_last, omega_avg, omega_min, omega_max]
                kx = self.vec_kx[i];  ky = self.vec_ky[j]; 
                mode_indices = '(' + str(kx) + ", " + str(ky) + ')'
                mode_data = [ str(k) for k in [self.stable_modes[i,j], self.converged_modes[i,j], phi_end - phi_start, fluctuation_o, fluctuation_g] ] 
                self.simulation_file[mode_header][mode_indices] = "[" + ", ".join(mode_data) + "]"
    
        # Write the simulation file
        self.simulation_file.write(open(self.simulation_path, 'w'))
        return