Esempio n. 1
0
def GetExpectedIncome(settings, console_output=None, logs_on=None):
    """
    Either loading expected income if it was already calculated for these 
    settings, or calling the function to calculate the expected income. 

    Parameters
    ----------
    settings : dict
        Dictionary of settings as given by DefaultSettingsExcept().
    console_output : boolean, optional
        Specifying whether the progress should be documented thorugh console 
        outputs. The default is defined in ModelCode/GeneralSettings.
    logs_on : boolean, optional
        Specifying whether the progress should be documented in a log file.
        The default is defined in ModelCode/GeneralSettings.

    Returns
    -------
    expected_incomes :  np.array of size (T, len(k_using))
        The expected income of farmers in a scenario where the government is
        not involved.

    """

    # not all settings affect the expected income (as no government is
    # included)
    SettingsAffectingGuaranteedIncome = "k" + str(settings["k"]) + \
                "Using" +  '_'.join(str(n) for n in settings["k_using"]) + \
                "Crops" + str(settings["num_crops"]) + \
                "Start" + str(settings["sim_start"]) + \
                "N" + str(settings["N"])

    # open dict with all expected incomes that were calculated so far
    with open("PenaltiesAndIncome/ExpectedIncomes.txt", "rb") as fp:
        dict_incomes = pickle.load(fp)

    # if expected income was already calculated for these settings, fetch it
    if SettingsAffectingGuaranteedIncome in dict_incomes.keys():
        _printing("\nFetching expected income",
                  console_output=console_output,
                  logs_on=logs_on)
        expected_incomes = dict_incomes[SettingsAffectingGuaranteedIncome]
    # else calculate (and save) it
    else:
        expected_incomes = _CalcExpectedIncome(settings, \
                        SettingsAffectingGuaranteedIncome, console_output = console_output, logs_on = logs_on)
        dict_incomes[SettingsAffectingGuaranteedIncome] = expected_incomes
        with open("PenaltiesAndIncome/ExpectedIncomes.txt", "wb") as fp:
            pickle.dump(dict_incomes, fp)

    # should not happen
    if np.sum(expected_incomes < 0) > 0:
        sys.exit("Negative expected income")

    _printing("     Expected income per cluster in " + \
             str(settings["sim_start"] - 1) + ": " + \
             str(np.round(expected_incomes, 3)),
             console_output = console_output, logs_on = logs_on)

    return (expected_incomes)
Esempio n. 2
0
def CropAreasDependingOnColaboration(panda_file="current_panda",
                                     groupAim="Dissimilar",
                                     groupMetric="medoids",
                                     adjacent=False,
                                     figsize=None,
                                     cols=None,
                                     console_output=None,
                                     close_plots=None,
                                     **kwargs):
    """
    For each group size used for grouping, this makes plots to compare the 
    crop areas depending on the level of cooperation (i.e. group sizes)
    

    Parameters
    ----------
    panda_file : str, optional
        Filename of panda csv to use for results.
    groupAim : str, optional
        The aim in grouping clusters, either "Similar" or "Dissimilar".
    groupMetric : str, optional
        The metric on which the grouping is based. The default is "medoids".
    adjacent : boolean, optional
        Whether clusters in a cluster group need to be adjacent. The default is False.
    figsize : tuple, optional
        The figure size. If None, the default as defined in ModelCode/GeneralSettings is used.
    cols : list, optional
        List of colors to use for plotting. If None, default values will
        be used. The default is None.
    close_plots : boolean or None
        Whether plots should be closed after plotting (and saving). If None, 
        the default as defined in ModelCode/GeneralSettings is used.
    **kwargs : 
        Settings specifiying for which model run results shall be returned

    Returns
    -------
    None.

    """

    # settings
    if close_plots is None:
        from ModelCode.GeneralSettings import close_plots

    if console_output is None:
        from ModelCode.GeneralSettings import console_output

    if cols is None:
        cols = ["royalblue", "darkred", "grey", "gold", \
                "limegreen", "darkturquoise", "darkorchid", "seagreen",
                "indigo"]

    if adjacent:
        add = "Adj"
        add_title = ", adjacent"
    else:
        add = ""
        add_title = ""

    foldername = groupAim
    if adjacent:
        foldername = foldername + "Adjacent"
    else:
        foldername = foldername + "NonAdjacent"

    # get cluster grouping for size 1 (i.e. all independent)
    _printing("\nGroup size " + str(1),
              console_output=console_output,
              logs_on=False)
    with open(
            "InputData/Clusters/ClusterGroups/Grouping" +
            groupMetric.capitalize() + "Size" + str(1) + groupAim + add +
            ".txt", "rb") as fp:
        BestGrouping = pickle.load(fp)

    # get results for the different cluster groups in this grouping
    CropAllocs, MaxAreas, labels, Ns, Ms = _GetResultsToCompare(ResType="k_using",\
                                            panda_file = panda_file,
                                            console_output = console_output,
                                            k_using = BestGrouping,
                                            **kwargs)
    total_areas = [sum([np.sum(cr, axis=(1, 2)) for cr in CropAllocs])]

    # more settings
    kwargs["N"] = min(Ns)
    kwargs["validation_size"] = min(Ms)
    settingsIterate = DefaultSettingsExcept(k_using=BestGrouping, **kwargs)
    fnPlot = GetFilename(settingsIterate, groupSize = 1, groupAim = groupAim, \
                      adjacent = adjacent)
    kwargs["N"] = None
    kwargs["validation_size"] = None

    # plot the crop areas for this grouping
    _printing("  Plotting", console_output=console_output, logs_on=False)
    _CompareCropAllocs(CropAllocs=CropAllocs,
                       MaxAreas=MaxAreas,
                       labels=labels,
                       title="Groups of size " + str(1) + " (" +
                       groupAim.lower() + add_title + ")",
                       legend_title="Cluster: ",
                       comparing="clusters",
                       filename=fnPlot,
                       foldername=foldername,
                       subplots=(3, 3),
                       close_plots=close_plots)

    for size in [2, 3, 5, 9]:
        # get cluster grouping for given group size
        _printing("\nGroup size " + str(size),
                  console_output=console_output,
                  logs_on=False)
        with open(
                "InputData/Clusters/ClusterGroups/Grouping" +
                groupMetric.capitalize() + "Size" + str(size) + groupAim +
                add + ".txt", "rb") as fp:
            BestGrouping = pickle.load(fp)

        # settings
        settingsIterate = DefaultSettingsExcept(k_using=BestGrouping, **kwargs)
        fnPlot = GetFilename(settingsIterate, groupSize = str(size), groupAim = groupAim, \
                          adjacent = adjacent)

        # get results for the different cluster groups in this grouping
        CropAllocs_pooling, MaxAreas_pooling, labels_pooling, Ns, Ms = \
                _GetResultsToCompare(ResType="k_using",\
                                    panda_file = panda_file,
                                    console_output = console_output,
                                    k_using = BestGrouping,
                                    **kwargs)
        total_areas.append(
            sum([np.sum(cr, axis=(1, 2)) for cr in CropAllocs_pooling]))

        # more settings
        kwargs["N"] = min(Ns)
        kwargs["validation_size"] = min(Ms)
        settingsIterate = DefaultSettingsExcept(k_using=BestGrouping, **kwargs)
        fnPlot = GetFilename(settingsIterate, groupSize = str(size), groupAim = groupAim, \
                          adjacent = adjacent)
        kwargs["N"] = None
        kwargs["validation_size"] = None

        # plot the crop areas for this grouping
        _printing("  Plotting", console_output=console_output, logs_on=False)
        _CompareCropAllocs(CropAllocs=CropAllocs_pooling,
                           MaxAreas=MaxAreas_pooling,
                           labels=labels_pooling,
                           title="Groups of size " + str(size) + " (" +
                           groupAim.lower() + add_title + ")",
                           legend_title="Cluster: ",
                           comparing="clusters",
                           filename=fnPlot,
                           foldername=foldername,
                           subplots=(3, 3),
                           close_plots=close_plots)

        # plot the crop area comparison between independent and grouped for this grouping
        _CompareCropAllocRiskPooling(CropAllocs_pooling,
                                     CropAllocs,
                                     MaxAreas_pooling,
                                     MaxAreas,
                                     labels_pooling,
                                     labels,
                                     filename=fnPlot,
                                     foldername=foldername,
                                     title=str(BestGrouping),
                                     subplots=False,
                                     close_plots=close_plots)

    # plot total areas
    settingsIterate["N"] = ""
    settingsIterate["validation_size"] = ""
    fnPlot = GetFilename(settingsIterate, groupAim=groupAim, adjacent=adjacent)
    _PlotTotalAreas(total_areas,
                    groupAim,
                    adjacent,
                    fnPlot,
                    foldername=foldername,
                    close_plots=close_plots)

    return (None)
Esempio n. 3
0
def _GetResultsToCompare(ResType = "k_using", panda_file = "current_panda", \
                        console_output = None, **kwargs):
    """
    Function that loads results from different model runs, with one setting 
    changing while the others stay the same (e.g. different clusters for same
    settings).

    Parameters
    ----------
    ResType : str, optional
        Which setting will be changed to compare different results. Needs to 
        be the exact name of that setting. The default is "k_using".
    panda_file : str, optional
        Filename of panda csv to use for results.
    console_output : boolean, optional
        Specifying whether the progress should be documented thorugh console 
        outputs. The default is defined in ModelCode/GeneralSettings.
        
    **kwargs : 
        Settings specifiying for which model run results shall be returned

    Returns
    -------
    CropAllocs : list
         List of crop allocations for the different settings.
    MaxAreas : list
         List of maximum available agricultural areas for the different 
         settings.
    labels : list
        List of labels for plots (given information on the setting that is 
        changing).
    Ns :
        sample size of each model run for which results are returned
    Ms : 
        validation sample size of each model run for which results are returned
        

    """

    if console_output is None:
        from ModelCode.GeneralSettings import console_output

    if "PenMet" not in kwargs.keys():
        kwargs["PenMet"] = "prob"
    if kwargs["PenMet"] == "penalties":
        kwargs["probF"] = None
        kwargs["probS"] = None

    # prepare cluster groups
    if "k_using" in kwargs.keys():
        k_using = kwargs["k_using"]
        if ResType == "k_using":
            if (type(k_using) is list) and (type(k_using[0]) is tuple):
                k_using = [
                    sorted(list(k_using_tmp)) for k_using_tmp in k_using
                ]
            elif (type(k_using) is list) and (type(k_using[0]) is int):
                k_using = [[k_using_tmp] for k_using_tmp in k_using]
        else:
            if type(k_using) is tuple:
                k_using = sorted(list(k_using))
            elif type(k_using) is int:
                k_using = [k_using]
        kwargs["k_using"] = k_using

    ToIterate = kwargs[ResType]

    if type(ToIterate) is not list:
        ToIterate = [ToIterate]

    kwargs_tmp = kwargs.copy()
    del kwargs_tmp["PenMet"]

    # get filnenames of full results from panda
    panda_filenames = pd.DataFrame()
    for it in ToIterate:
        kwargs_tmp[ResType] = it
        panda_filenames = panda_filenames.append(\
                        _ReadFromPandaSingleClusterGroup(file = panda_file,
                                          output_var = "Filename for full results",
                                          **kwargs_tmp))

    panda_filenames = panda_filenames.reset_index()

    # get data
    CropAllocs = []
    MaxAreas = []
    labels = []
    Ns = []
    Ms = []

    _printing("  Fetching data", console_output=console_output, logs_on=False)
    for idx, val in enumerate(ToIterate):
        _printing("     " + ResType + ": " + str(val),
                  console_output=console_output,
                  logs_on=False)

        settings, args, yield_information, population_information, \
        status, all_durations, exp_incomes, crop_alloc, meta_sol, \
        crop_allocF, meta_solF, crop_allocS, meta_solS, \
        crop_alloc_vss, meta_sol_vss, VSS_value, validation_values = \
            LoadModelResults(panda_filenames.at[idx, "Filename for full results"])

        CropAllocs.append(crop_alloc)
        MaxAreas.append(args["max_areas"])
        labels.append(val)
        Ns.append(settings["N"])
        Ms.append(settings["validation_size"])

    return (CropAllocs, MaxAreas, labels, Ns, Ms)
Esempio n. 4
0
def _CalcExpectedIncome(settings,
                        SettingsAffectingGuaranteedIncome,
                        console_output=None,
                        logs_on=None):
    """
    Calculating the expected income in the scenario corresponding to the 
    settings but without government.

    Parameters
    ----------
    settings : dict
        Dictionary of settings as given by DefaultSettingsExcept().
    SettingsAffectingGuaranteedIncome : str
        Combining all settings that influence the expected income, used to 
        save the result for further runs.
    console_output : boolean, optional
        Specifying whether the progress should be documented thorugh console 
        outputs. The default is defined in ModelCode/GeneralSettings.
    logs_on : boolean, optional
        Specifying whether the progress should be documented in a log file.
        The default is defined in ModelCode/GeneralSettings.

    Returns
    -------
    expected_incomes :  np.array of size (len(k_using),)
        The expected income of farmers in a scenario where the government is
        not involved.

    """

    _printing("\nCalculating expected income ",
              console_output=console_output,
              logs_on=logs_on)
    settings_ExpIn = settings.copy()

    # change some settings: we are interested in the expected income in 2016
    # (no need to change start year, as we set scenarios to fixed)
    settings_ExpIn["yield_projection"] = "fixed"
    settings_ExpIn["pop_scenario"] = "fixed"
    settings_ExpIn["T"] = 1
    probF = 0.99

    # settings affecting the food demand penalty
    SettingsBasics = "k" + str(settings_ExpIn["k"]) + \
                "Using" +  '_'.join(str(n) for n in settings_ExpIn["k_using"]) + \
                "Crops" + str(settings_ExpIn["num_crops"]) + \
                "Yield" + str(settings_ExpIn["yield_projection"]).capitalize() + \
                "Start" + str(settings_ExpIn["sim_start"]) + \
                "Pop" + str(settings_ExpIn["pop_scenario"]).capitalize() + \
                "T" + str(settings_ExpIn["T"])
    SettingsFirstGuess = SettingsBasics + "ProbF" + str(probF)
    SettingsAffectingRhoF = SettingsFirstGuess + "N" + str(settings_ExpIn["N"])

    # first guess
    with open("PenaltiesAndIncome/RhoFs.txt", "rb") as fp:
        dict_rhoFs = pickle.load(fp)
    with open("PenaltiesAndIncome/crop_allocF.txt", "rb") as fp:
        dict_crop_allocF = pickle.load(fp)

    rhoFini, checkedGuess = _GetInitialGuess(dict_rhoFs, SettingsFirstGuess,
                                             settings["N"])

    # we assume that without government farmers aim for 99% probability of
    # food security, therefore we find the right penalty for probF = 99%.
    # As we want the income in a scenario without government, the resulting run
    # of GetRhoF (with rohS = 0) automatically is the right run
    args, yield_information, population_information = \
        SetParameters(settings_ExpIn, console_output = False, logs_on = False)

    rhoF, meta_solF, crop_allocF = _GetRhoWrapper(args,
                                                  probF,
                                                  rhoFini,
                                                  checkedGuess,
                                                  "F",
                                                  SettingsAffectingRhoF,
                                                  console_output=False,
                                                  logs_on=False)

    dict_rhoFs[SettingsAffectingRhoF] = rhoF
    dict_crop_allocF[SettingsAffectingRhoF] = crop_allocF

    # saving updated dicts
    with open("PenaltiesAndIncome/RhoFs.txt", "wb") as fp:
        pickle.dump(dict_rhoFs, fp)
    with open("PenaltiesAndIncome/crop_allocF.txt", "wb") as fp:
        pickle.dump(dict_crop_allocF, fp)

    return (meta_solF["avg_profits_preTax"].flatten())
Esempio n. 5
0
def _OptimizeModel(settings, panda_file, console_output = None, logs_on = None, \
                  save = True, plotTitle = None):
    """
    Function combines setting up and solving the model, calculating additional
    information, and saving the results.

    Parameters
    ----------
    settings : dict
        All input settings for the model framework.
    panda_file : str or None
        Name of the csv file used to append the results of the model run for
        plotting and easy accessibility. If None, results are not saved to 
        panda csv. Default is "current_panda"
    console_output : boolean, optional
        Specifying whether the progress should be documented thorugh console 
        outputs. If None, the default as defined in ModelCode/GeneralSettings 
        is used.
    logs_on : boolean, optional
        Specifying whether the progress should be documented in a log file.
        If None, the default as defined in ModelCode/GeneralSettings is used.
    save : boolean, optional
        whether the results should be saved (does not refer to the panda results).
        The default is True.
    plotTitle : str or None
        If not None, a plot of the resulting crop allocations will be made 
        with that title and saved to Figures/CropAllocs.
        
    Returns
    -------
    settings : dict
        The model input settings that were given by user. 
    args : dict
        Dictionary of arguments needed as direct model input.
    yield_information : dict
        Information on the yield distributions for the considered clusters.
    population_information : dict
        Information on the population in the considered area.
    status : int
        status of solver (optimal: 2)
    all_durations :  dict
        Information on the duration of different steps of the model framework.
    exp_incomes : dict
        Expected income (on which guaranteed income is based) for the stochastic
        and the deterministic setting.
    crop_alloc :  np.array
        gives the optimal crop areas for all years, crops, clusters
    meta_sol : dict 
        additional information about the model output ('exp_tot_costs', 
        'fix_costs', 'yearly_fixed_costs', 'fd_penalty', 'avg_fd_penalty', 
        'sol_penalty', 'shortcomings', 'exp_shortcomings', 'avg_profits_preTax', 
        'avg_profits_afterTax', 'food_supply', 'profits_preTax', 
        'profits_afterTax', 'num_years_with_losses', 'payouts', 'final_fund', 
        'probF', 'probS', 'avg_nec_import', 'avg_nec_debt', 'guaranteed_income')
    crop_allocF : np.array
        optimal crop allocation for scenario with only food security objective
    meta_solF : dict
        additional information on model output for scenario with only food
        security objective
    crop_allocS : np.array
        optimal crop allocation for scenario with only solvency objective
    meta_solS : dict
        additional information on model output for scenario with only solvency
        objective
    crop_alloc_vss : np.array
        deterministic solution for optimal crop areas    
    meta_sol_vss : dict
        additional information on the deterministic solution  
    VSS_value : float
        VSS calculated as the difference between total costs using 
        deterministic solution for crop allocation and stochastic solution
        for crop allocation       
    validation_values : dict
        total costs and penalty costs for the resulted crop areas but a higher 
        sample size of crop yields for validation ("sample_size", 
        "total_costs", "total_costs_val", "fd_penalty", "fd_penalty_val", 
        "sol_penalty", "sol_penalty_val", "total_penalties", 
        "total_penalties_val", "deviation_penalties")
    """

    # timing
    all_start = tm.time()
    all_durations = {}

    # file name used for log file and saving results
    fn = GetFilename(settings)

    # initialize log file
    if logs_on is None:
        from ModelCode.GeneralSettings import logs_on

    if logs_on:
        import ModelCode.GeneralSettings as GS
        GS.fn_log = fn

        if os.path.exists("ModelLogs/" + GS.fn_log + ".txt"):
            i = 1
            while os.path.exists("ModelLogs/" + GS.fn_log + "_" + str(i) +
                                 ".txt"):
                i += 1
            GS.fn_log = GS.fn_log + "_" + str(i)

        log = open("ModelLogs/" + GS.fn_log + ".txt", "a")
        log.write("Model started " +
                  str(datetime.now().strftime("%B %d, %Y, at %H:%M")) + "\n")
        log.write("\nModel Settings: ")
        for key in settings.keys():
            log.write(key + " = " + str(settings[key]) + "\n                ")
        log.write("save = " + str(save) + "\n                ")
        log.write("plotTitle = " + str(plotTitle))
        log.close()

    # get parameters for the given settings
    ex_income_start = tm.time()
    exp_incomes = GetExpectedIncome(settings, console_output = console_output, \
                                    logs_on = logs_on)
    exp_incomes = {"sto. setting": exp_incomes}
    ex_income_end = tm.time()
    all_durations["GetExpectedIncome"] = ex_income_end - ex_income_start
    _printing("\nGetting parameters", console_output = console_output, \
              logs_on = logs_on)
    args, yield_information, population_information = \
                    SetParameters(settings, exp_incomes["sto. setting"])

    # get the right penalties
    penalties_start = tm.time()
    if settings["PenMet"] == "prob":
        rhoF, rhoS, meta_solF, meta_solS, crop_allocF, crop_allocS = \
            GetPenalties(settings, args, console_output = console_output, \
                         logs_on = logs_on)
        args["rhoF"] = rhoF
        args["rhoS"] = rhoS
    else:
        args["rhoF"] = settings["rhoF"]
        if settings["solv_const"] == "on":
            args["rhoS"] = settings["rhoS"]
        elif settings["solv_const"] == "off":
            args["rhoS"] = 0
        meta_solF = None
        meta_solS = None
        crop_allocF = None
        crop_allocS = None

    penalties_end = tm.time()
    all_durations["GetPenalties"] = penalties_end - penalties_start

    # run the optimization
    status, crop_alloc, meta_sol, prob, durations = \
        SolveReducedLinearProblemGurobiPy(args, \
                                        console_output = console_output, \
                                        logs_on = logs_on)
    all_durations["MainModelRun"] = durations[2]

    _printing("\nResulting probabilities:\n" + \
            "     probF: " + str(np.round(meta_sol["probF"]*100, 2)) + "%\n" + \
            "     probS: " + str(np.round(meta_sol["probS"]*100, 2)) + "%",
            console_output = console_output,
            logs_on = logs_on)

    # VSS
    vss_start = tm.time()
    _printing("\nCalculating VSS", console_output = console_output, \
              logs_on = logs_on)
    crop_alloc_vss, expected_incomes_vss, meta_sol_vss = VSS(settings, args)
    exp_incomes["det. setting"] = expected_incomes_vss
    VSS_value = meta_sol_vss["exp_tot_costs"] - meta_sol["exp_tot_costs"]
    vss_end = tm.time()
    all_durations["VSS"] = vss_end - vss_start

    # out of sample validation
    validation_start = tm.time()
    if settings["validation_size"] is not None:
        _printing("\nOut of sample validation", console_output = console_output, \
                  logs_on = logs_on)
        validation_values = OutOfSampleVal(crop_alloc, settings,
                    exp_incomes["sto. setting"], args["rhoF"], args["rhoS"], \
                    meta_sol, console_output, logs_on = logs_on)
    validation_end = tm.time()
    all_durations["Validation"] = validation_end - validation_start

    # add results to pandas overview
    if panda_file is not None:
        _WriteToPandas(settings, args, yield_information, population_information, \
                       status, all_durations, exp_incomes, crop_alloc, meta_sol, \
                       crop_allocF, meta_solF, crop_allocS, meta_solS, \
                       crop_alloc_vss, meta_sol_vss, VSS_value, validation_values, \
                       fn, console_output, logs_on, panda_file)

    # timing
    all_end = tm.time()
    full_time = all_end - all_start
    _printing("\nTotal time: " + str(np.round(full_time, 2)) + "s",
              console_output=console_output,
              logs_on=logs_on)
    all_durations["TotalTime"] = full_time

    # saving results
    if save:
        info = ["settings", "args", "yield_information", "population_information", \
                "status", "durations", "crop_alloc", "crop_allocF", "crop_allocS", \
                "crop_alloc_vss", "VSS_value", "validation_values"]

        with open("ModelOutput/SavedRuns/" + fn + ".txt", "wb") as fp:
            pickle.dump(info, fp)
            pickle.dump(settings, fp)
            pickle.dump(args, fp)
            pickle.dump(yield_information, fp)
            pickle.dump(population_information, fp)
            pickle.dump(status, fp)
            pickle.dump(all_durations, fp)
            pickle.dump(exp_incomes, fp)
            pickle.dump(crop_alloc, fp)
            pickle.dump(crop_allocF, fp)
            pickle.dump(crop_allocS, fp)
            pickle.dump(crop_alloc_vss, fp)
            pickle.dump(VSS_value, fp)
            pickle.dump(validation_values, fp)

    # remove the global variable fn_log
    if logs_on:
        del GS.fn_log

    return(settings, args, yield_information, population_information, \
           status, all_durations, exp_incomes, crop_alloc, meta_sol, \
           crop_allocF, meta_solF, crop_allocS, meta_solS, \
           crop_alloc_vss, meta_sol_vss, VSS_value, validation_values)
Esempio n. 6
0
def FoodSecurityProblem(console_output = None, logs_on = None, \
                        save = True, plotTitle = None, close_plots = None,
                        panda_file = "current_panda", **kwargs):
    """
    Setting up and solving the food security problem. Returns model output
    and additional information on the solution, as well as the VSS and a 
    validation of the model output. The results are also saved. If the model
    has already been solved for the exact settings, results are loaded instead
    of recalculated.
    

    Parameters
    ----------
    console_output : boolean, optional
        Specifying whether the progress should be documented thorugh console 
        outputs. If None, the default as defined in ModelCode/GeneralSettings 
        is used.
    logs_on : boolean, optional
        Specifying whether the progress should be documented in a log file.
        If None, the default as defined in ModelCode/GeneralSettings is used.
    save : boolean, optional
        whether the results should be saved. The default is True.
    plotTitle : str or None
        If not None, a plot of the resulting crop allocations will be made 
        with that title and saved to Figures/CropAllocs/numclusters/.
    close_plots : boolean or None
        Whether plots should be closed after plotting (and saving). If None, 
        the default as defined in ModelCode/GeneralSettings is used.
    panda_file : str
        Name of the csv file used to append the results of the model run for
        plotting and easy accessibility. If None, results are not saved to 
        panda csv. Default is "current_panda"
    **kwargs
        settings for the model, passed to DefaultSettingsExcept()
        
    Returns
    -------
    settings : dict
        The model input settings that were given by user. 
    args : dict
        Dictionary of arguments needed as direct model input.
    yield_information : dict
        Information on the yield distributions for the considered clusters.
    population_information : dict
        Information on the population in the considered area.
    status : int
        status of solver (optimal: 2)
    all_durations :  dict
        Information on the duration of different steps of the model framework.
    exp_incomes : dict
        Expected income (on which guaranteed income is based) for the stochastic
        and the deterministic setting.
    crop_alloc :  np.array
        gives the optimal crop areas for all years, crops, clusters
    meta_sol : dict 
        additional information about the model output ('exp_tot_costs', 
        'fix_costs', 'yearly_fixed_costs', 'fd_penalty', 'avg_fd_penalty', 
        'sol_penalty', 'shortcomings', 'exp_shortcomings', 'avg_profits_preTax', 
        'avg_profits_afterTax', 'food_supply', 'profits_preTax', 
        'profits_afterTax', 'num_years_with_losses', 'payouts', 'final_fund', 
        'probF', 'probS', 'avg_nec_import', 'avg_nec_debt', 'guaranteed_income')
    crop_allocF : np.array
        optimal crop allocation for scenario with only food security objective
    meta_solF : dict
        additional information on model output for scenario with only food
        security objective
    crop_allocS : np.array
        optimal crop allocation for scenario with only solvency objective
    meta_solS : dict
        additional information on model output for scenario with only solvency
        objective
    crop_alloc_vss : np.array
        deterministic solution for optimal crop areas    
    meta_sol_vss : dict
        additional information on the deterministic solution  
    VSS_value : float
        VSS calculated as the difference between total costs using 
        deterministic solution for crop allocation and stochastic solution
        for crop allocation       
    validation_values : dict
        total costs and penalty costs for the resulted crop areas but a higher 
        sample size of crop yields for validation ("sample_size", 
        "total_costs", "total_costs_val", "fd_penalty", "fd_penalty_val", 
        "sol_penalty", "sol_penalty_val", "total_penalties", 
        "total_penalties_val", "deviation_penalties")
    fn : str
        all settings combined to a single file name to save/load results and 
        for log file
    """

    # set up folder structure (if not already done)
    CheckFolderStructure()

    # defining settings
    settings = DefaultSettingsExcept(**kwargs)

    # get filename for model results
    fn = GetFilename(settings)

    # if model output does not exist yet it is calculated
    if not os.path.isfile("ModelOutput/SavedRuns/" + fn + ".txt"):
        try:
            settings, args, yield_information, population_information, \
            status, all_durations, exp_incomes, crop_alloc, meta_sol, \
            crop_allocF, meta_solF, crop_allocS, meta_solS, \
            crop_alloc_vss, meta_sol_vss, VSS_value, validation_values = \
                                 _OptimizeModel(settings,
                                               console_output = console_output,
                                               save = save,
                                               logs_on = logs_on,
                                               plotTitle = plotTitle,
                                               panda_file = panda_file)
        except KeyboardInterrupt:
            print(colored("\nThe model run was interrupted by the user.",
                          "red"),
                  flush=True)
            import ModelCode.GeneralSettings as GS
            if logs_on or (logs_on is None and GS.logs_on):
                log = open("ModelLogs/" + GS.fn_log + ".txt", "a")
                log.write("\n\nThe model run was interupted by the user.")
                log.close()
                del GS.fn_log

    # if it does, it is loaded from output file
    else:
        _printing("Loading results",
                  console_output=console_output,
                  logs_on=False)

        settings, args, yield_information, population_information, \
        status, all_durations, exp_incomes, crop_alloc, meta_sol, \
        crop_allocF, meta_solF, crop_allocS, meta_solS, \
        crop_alloc_vss, meta_sol_vss, VSS_value, validation_values = \
            LoadModelResults(fn)

    # if a plottitle is provided, crop allocations over time are plotted
    if plotTitle is not None:
        _PlotCropAlloc(crop_alloc = crop_alloc, k = settings["k"], \
                       k_using = settings["k_using"],  max_areas = args["max_areas"], \
                       close_plots = close_plots, title = plotTitle, file = fn)

    return(settings, args, yield_information, population_information, \
           status, all_durations, exp_incomes, crop_alloc, meta_sol, \
           crop_allocF, meta_solF, crop_allocS, meta_solS, \
           crop_alloc_vss, meta_sol_vss, VSS_value, validation_values, fn)
def OutOfSampleVal(crop_alloc, settings, expected_incomes, rhoF, rhoS, \
                   meta_sol, console_output = None, logs_on = None):
    """
    For validation, the objective function is re-evaluate, using the optimal
    crop allocation but a higher sample size.    
    
    Parameters
    ----------
    crop_alloc : np.array
        Optimal crop areas for all years, crops, clusters.
    settings : dict
        the model settings that were used     
    expected_incomes : np.array of size (len(k_using),)
        The expected income of farmers in a scenario where the government is
        not involved.
    rhoF : float
        The penalty for shortcomings of the food demand.
    rhoS : float
        The penalty for insolvency.   
    meta_sol : dict 
        additional information about the model output 
    console_output : boolean, optional
        Specifying whether the progress should be documented thorugh console 
        outputs. The default is defined in ModelCode/GeneralSettings.
    logs_on : boolean, optional
        Specifying whether the progress should be documented in a log file.
        The default is defined in ModelCode/GeneralSettings.

    Returns
    ------
    validation_values : dict
        Dictionary of validation values
        
        - sample_size : Validation sample size
        - total_costs : expected total costs in model run
        - total_costs_val : expected total costs in validation run
        - fd_penalty : average total food demand penalty in model run
        - fd_penalty_val : average total food demand penalty in validation run
        - sol_penalty : average total solvency penalty in model run
        - sol_penalty_val : average total solvency penalty in validation run
        - total_penalties : fd_penalty + sol_penalty
        - total_penalties_val : fd_penalty_val + sol_penalty_val
        - deviation_penalties : 1 - (total_penalties / total_penalties_val)

    """

    # higher sample size
    settings_val = settings.copy()
    settings_val["N"] = settings["validation_size"]

    # get yield samples
    _printing("     Getting parameters and yield samples",
              console_output=console_output,
              logs_on=logs_on)
    args, yield_information, population_information = \
                SetParameters(settings_val, expected_incomes, \
                              console_output = False, logs_on = False)

    # run objective function for higher sample size
    _printing("     Objective function",
              console_output=console_output,
              logs_on=logs_on)
    meta_sol_val = GetMetaInformation(crop_alloc, args, rhoF, rhoS)

    # create dictionary with validation information
    validation_values = {
        "sample_size": settings["validation_size"],
        "total_costs": meta_sol["exp_tot_costs"],
        "total_costs_val": meta_sol_val["exp_tot_costs"],
        "fd_penalty": np.nanmean(np.sum(meta_sol["fd_penalty"], axis=1)),
        "fd_penalty_val": np.nanmean(np.sum(meta_sol_val["fd_penalty"],
                                            axis=1)),
        "sol_penalty": np.nanmean(meta_sol["sol_penalty"]),
        "sol_penalty_val": np.nanmean(meta_sol_val["sol_penalty"])
    }
    validation_values["total_penalties"] = validation_values["fd_penalty"] + \
                    validation_values["sol_penalty"]
    validation_values["total_penalties_val"] = validation_values["fd_penalty_val"] + \
                    validation_values["sol_penalty_val"]
    validation_values["deviation_penalties"] = 1 - \
        (validation_values["total_penalties"]/validation_values["total_penalties_val"])

    # add this dictionary to validation file
    with open("ModelOutput/validation.txt", "rb") as fp:
        val_dict = pickle.load(fp)

    val_name = str(len(settings["k_using"])) + "of" + \
                str(settings["k"]) + "_N" + str(settings["N"]) + \
                "_M" + str(settings["validation_size"])

    if val_name in val_dict.keys():
        tmp = val_dict[val_name]
        tmp.append(validation_values["deviation_penalties"])
        val_dict[val_name] = tmp
    else:
        val_dict[val_name] = [validation_values["deviation_penalties"]]
    with open("ModelOutput/validation.txt", "wb") as fp:
        pickle.dump(val_dict, fp)

    return (validation_values)
Esempio n. 8
0
def SolveReducedLinearProblemGurobiPy(args, rhoF = None, rhoS = None, \
                                       console_output = None, logs_on = None):
    """
    Sets up and solves the linear form of the food security problem.

    Parameters
    ----------
    args : dict
        Dictionary of arguments needed as model input (as given by 
        SetParameters()).
    rhoF : float or None
        The penalty for shortcomings of the food demand. If None the values in 
        args are used. (This is used from within the GetPenalties function to
        easily change penalties while keeping other args the same.)
    rhoS : float or None
        The penalty for insolvency. If None the values in 
        args are used. (This is used from within the GetPenalties function to
        easily change penalties while keeping other args the same.)
    console_output : boolean, optional
        Specifying whether the progress should be documented thorugh console 
        outputs. The default is defined in ModelCode/GeneralSettings.
    logs_on : boolean, optional
        Specifying whether the progress should be documented in a log document.
        The default is defined in ModelCode/GeneralSettings.

    Returns
    -------
    status : int
        status of solver (optimal: 2)
    crop_alloc : np.array
        gives the optimal crop areas for all years, crops, clusters
    meta_sol : dict 
        additional information about the model output
    prob : gurobi model
        The food security model that was set up.
    durations : list
        time for setting up the model, time for solving, and total time (in
        sec.)

    """
        
    def _flatten(ListOfLists):
        return(list(it.chain(*ListOfLists)))
    
    if rhoF is None:
        rhoF = args["rhoF"]
    if rhoS is None:
        rhoS = args["rhoS"]
    
    _printing("\nSolving Model", console_output = console_output, logs_on = logs_on)
    
    start = tm.time()
    
    # no output to console from solver
    env = gp.Env(empty = True)    
    env.setParam('OutputFlag', 0)
    env.start()
    
    # intitialize stochastic optimization problem
    prob = gp.Model("SustainableFoodSecurity", env = env)
    
    # get dimensions
    T = args["T"]
    K = len(args["k_using"])
    J = args["num_crops"]
    N = args["N"]
    termyear_p1 = args["terminal_years"] + 1
    termyear_p1[termyear_p1 == 0] = T
    termyear_p1 = termyear_p1.astype(int)
    
    # index tupels for variables and constraints
    indVfood = _flatten([[(t, s) for t in range(0, termyear_p1[s])] \
                        for s in range(0, N)])
    
    indW = _flatten(_flatten([[[(t, k, s) for t in range(0, termyear_p1[s])] \
                             for k in range(0,K)] for s in range(0, N)]))   
            
    indCultCosts = _flatten([[(t, j, k) for (t,j,k) in \
              it.product(range(0,termyear_p1[s]), range(0, J), range(0, K))] \
              for s in range(0, N)])
            
    indMaxArea = list(it.product(range(0, K), range(0, T)))
    indCropsClusters = list(it.product(range(0, J), range(0, K)))
    
    # variables
    x = prob.addVars(range(0, T), range(0, J), range(0, K), name = "x")
    Vfood = prob.addVars(indVfood, name = "Vfood")
    Vsol = prob.addVars(range(0, N), name = "Vsol")
    Wgov = prob.addVars(indW, name = "Wgov")

    # objective function
    obj = gp.quicksum([1/N * x[t,j,k] * args["costs"][j,k] \
                        for (t,j,k) in indCultCosts] + \
                       [1/N * rhoF * Vfood[t, s] for (t, s) in indVfood] + \
                       [1/N * rhoS * Vsol[s] for s in range(0, N)] + \
                          [0 * Wgov[t, k, s] for (t, k, s) in indW])
    prob.setObjective(obj, gp.GRB.MINIMIZE)
            
         
    # constraints 1
    prob.addConstrs((gp.quicksum([x[t, j, k] for j in range(0, J)]) \
                 <= args["max_areas"][k] for (k, t) in indMaxArea), "c_marea")
       
    # constraints 2
    prob.addConstrs((gp.quicksum([Vfood[t, s]] + \
                [args["ylds"][s, t, j, k] * x[t, j, k] * args["crop_cal"][j] \
                          for (j, k) in indCropsClusters]) \
                 >= (args["demand"][t] - args["import"]) \
                                 for (t, s) in indVfood), "c_demand")
    
    # constraints 3
    prob.addConstrs((gp.quicksum([-Vsol[s]] + \
                        [- args["tax"] * (args["ylds"][s, t, j, k] * \
                                x[t, j, k] * args["prices"][j, k] - \
                                x[t, j, k] * args["costs"][j, k]) \
                           for (j, t, k) in it.product(range(0, J), \
                                 range(0, termyear_p1[s]), range(0, K))] + \
                [args["cat_clusters"][s, t, k] * (1 - args["tax"]) * Wgov[t, k, s] \
                     for (t, k) in it.product(range(0, termyear_p1[s]), \
                                range(0, K))]) \
                 <= args["ini_fund"] for s in range(0, N)), "c_sol")
        
    # constraints 4
    prob.addConstrs((gp.quicksum([- Wgov[t, k, s]] + \
            [- args["ylds"][s, t, j, k] * x[t, j, k] * args["prices"][j, k] + \
            x[t, j, k] * args["costs"][j, k] for j in range(0, J)]) \
         <= - args["guaranteed_income"][t, k] for (t, k, s) in indW), "c_gov")

    # solving
    middle = tm.time()
    prob.optimize()
    end = tm.time()
    status = prob.status
    
    # calculate durations
    durationBuild = middle - start
    durationSolve = end - middle
    durationTotal = end - start
    durations = [durationBuild, durationSolve, durationTotal]
    
    # get results
    crop_alloc = np.zeros((T, J, K))
    meta_sol = []
    
    if status != 2:
        warn.warn("Non-optimal status of solver")
        return(status, crop_alloc, meta_sol, prob, durations)
    else:        
        for t in range(0, T):
            for j in range(0, J):
                for k in range(0, K):
                    crop_alloc[t, j, k] = prob.getVarByName("x[" + str(t) + \
                                        "," + str(j) + "," + str(k) + "]").X
                  
        meta_sol = GetMetaInformation(crop_alloc, args, rhoF, rhoS)
            
    _printing("     Time      Setting up model: " + \
            str(np.round(durations[0], 2)) + "s", console_output = console_output, logs_on = logs_on)
    _printing("               Solving model: " + \
            str(np.round(durations[1], 2)) + "s", console_output = console_output, logs_on = logs_on)
    _printing("               Total: " + \
            str(np.round(durations[2], 2)) + "s", console_output = console_output, logs_on = logs_on)       
                
    return(status, crop_alloc, meta_sol, prob, durations)
def RemoveRun(file = "current_panda", **kwargs):
    """
    Removes results of a specific model run (specified by **kwargs) from all
    output files (panda csv, penaltiy dicts, guaranteed income, direct model 
    output).

    Parameters
    ----------
    file : str, optional
        Name of the panda file from which the results should be removed.
        The default is "current_panda".
    **kwargs :
        Settings specifiying for which model run results shall be removed.

    Returns
    -------
    None.

    """
    # get full settings
    settings = DefaultSettingsExcept(**kwargs)
    
    # get filename and name for dicts
    fn, SettingsMaxProbF, SettingsAffectingRhoF, SettingsMaxProbS, \
        SettingsAffectingRhoS = GetFilename(settings, allNames = True)
        
    # remove line from panda
    _printing("Removing from panda", logs_on = False)
    current_panda = pd.read_csv("ModelOutput/Pandas/" + file + ".csv")
    current_panda = current_panda[current_panda["Filename for full results"] != fn]
    current_panda.to_csv("ModelOutput/Pandas/" + file + ".csv", index = False)
    
    # remove from food demand penalty dicts
    _printing("Removing from food security penalty dicts", logs_on = False)
    with open("PenaltiesAndIncome/RhoFs.txt", "rb") as fp:    
        dict_rhoFs = pickle.load(fp)
    with open("PenaltiesAndIncome/crop_allocF.txt", "rb") as fp:    
        dict_crop_allocF = pickle.load(fp)
    
    dict_rhoFs.pop(SettingsAffectingRhoF, None)
    dict_crop_allocF.pop(SettingsAffectingRhoF, None)
    
    with open("PenaltiesAndIncome/RhoFs.txt", "wb") as fp:    
         pickle.dump(dict_rhoFs, fp)
    with open("PenaltiesAndIncome/crop_allocF.txt", "wb") as fp:     
         pickle.dump(dict_crop_allocF, fp)
        
    # remove from solvency penalty dicts
    _printing("Removing from solvency penalty dicts", logs_on = False)
    with open("PenaltiesAndIncome/RhoSs.txt", "rb") as fp:    
        dict_rhoSs = pickle.load(fp)
    with open("PenaltiesAndIncome/crop_allocS.txt", "rb") as fp:    
        dict_crop_allocS = pickle.load(fp)
    
    dict_rhoSs.pop(SettingsAffectingRhoS, None)
    dict_crop_allocS.pop(SettingsAffectingRhoS, None)
    
    with open("PenaltiesAndIncome/RhoSs.txt", "wb") as fp:    
         pickle.dump(dict_rhoSs, fp)
    with open("PenaltiesAndIncome/crop_allocS.txt", "wb") as fp:     
         pickle.dump(dict_crop_allocS, fp)
    
    # remove from income dict
    _printing("Removing from income dict", logs_on = False)
    SettingsAffectingGuaranteedIncome = "k" + str(settings["k"]) + \
                "Using" +  '_'.join(str(n) for n in settings["k_using"]) + \
                "Crops" + str(settings["num_crops"]) + \
                "Start" + str(settings["sim_start"]) + \
                "N" + str(settings["N"])
        
    with open("PenaltiesAndIncome/ExpectedIncomes.txt", "rb") as fp:    
        dict_incomes = pickle.load(fp)
    
    dict_incomes.pop(SettingsAffectingGuaranteedIncome, None)
    
    with open("PenaltiesAndIncome/ExpectedIncomes.txt", "wb") as fp:    
         pickle.dump(dict_incomes, fp)
         
    # remove model outputs
    _printing("Removing direct model outputs", logs_on = False)
    if os.path.isfile("ModelOutput/SavedRuns/" + fn + ".txt"):
        os.remove("ModelOutput/SavedRuns/" + fn + ".txt")
    
    return(None)
    
def _WriteToPandas(settings, args, yield_information, population_information, \
                status, all_durations, exp_incomes, crop_alloc, \
                meta_sol, crop_allocF, meta_solF, crop_allocS, meta_solS, \
                crop_alloc_vss, meta_sol_vss, VSS_value, validation_values, \
                fn_fullresults, console_output = None, logs_on = None,
                file = "current_panda"):
    """
    Adds information on the model run to the given pandas csv.
    
    !! If additional variables are included in the _WriteToPandas function,
    they need to be added to all three dictionaries in _SetUpPandaDicts as well !!
    
    Parameters
    ---------- 
    settings : dict
        The model input settings that were given by user. 
    args : dict
        Dictionary of arguments needed as direct model input.
    yield_information : dict
        Information on the yield distributions for the considered clusters.
    population_information : dict
        Information on the population in the considered area.
    status : int
        status of solver (optimal: 2)
    all_durations :  dict
        Information on the duration of different steps of the model framework.
    exp_incomes : dict
        Expected income (on which guaranteed income is based) for the stochastic
        and the deterministic setting.
    crop_alloc :  np.array
        gives the optimal crop areas for all years, crops, clusters
    meta_sol : dict 
        additional information about the model output ('exp_tot_costs', 
        'fix_costs', 'yearly_fixed_costs', 'fd_penalty', 'avg_fd_penalty', 
        'sol_penalty', 'shortcomings', 'exp_shortcomings', 'avg_profits_preTax', 
        'avg_profits_afterTax', 'food_supply', 'profits_preTax', 
        'profits_afterTax', 'num_years_with_losses', 'payouts', 'final_fund', 
        'probF', 'probS', 'avg_nec_import', 'avg_nec_debt', 'guaranteed_income')
    crop_allocF : np.array
        optimal crop allocation for scenario with only food security objective
    meta_solF : dict
        additional information on model output for scenario with only food
        security objective
    crop_allocS : np.array
        optimal crop allocation for scenario with only solvency objective
    meta_solS : dict
        additional information on model output for scenario with only solvency
        objective
    crop_alloc_vss : np.array
        deterministic solution for optimal crop areas    
    meta_sol_vss : dict
        additional information on the deterministic solution  
    VSS_value : float
        VSS calculated as the difference between total costs using 
        deterministic solution for crop allocation and stochastic solution
        for crop allocation       
    validation_values : dict
        total costs and penalty costs for the resulted crop areas but a higher 
        sample size of crop yields for validation ("sample_size", 
        "total_costs", "total_costs_val", "fd_penalty", "fd_penalty_val", 
        "sol_penalty", "sol_penalty_val", "total_penalties", 
        "total_penalties_val", "deviation_penalties")
    fn_fullresults : str
        Filename used to save the full model results.
    console_output : boolean, optional
        Specifying whether the progress should be documented thorugh console 
        outputs. The default is defined in ModelCode/GeneralSettings.
    logs_on : boolean, optional
        Specifying whether the progress should be documented in a log file.
        The default is defined in ModelCode/GeneralSettings.
    file : str
        filename of panda csv to which the information is to be added. The
        default is "current_panda".
    Returns
    -------
    None.

    """

    _printing("\nAdding results to pandas",
              console_output=console_output,
              logs_on=logs_on)

    # some pre-calculations
    pop_per_cluster = np.outer(population_information["total_pop_scen"],
                               population_information["pop_cluster_ratio2015"])
    pop_of_area = population_information["population"]
    food_shortage_capita = meta_sol["shortcomings"] / pop_of_area
    # meta_sol["shortcimings"] reports shortcomings as positive values, and
    # surplus as zero
    food_shortage_capita_only_shortage = food_shortage_capita.copy()
    food_shortage_capita_only_shortage[food_shortage_capita_only_shortage ==
                                       0] = np.nan
    # to avoid getting nan as average: set years that never have shortcomings back to zero
    food_shortage_capita_only_shortage[:,
                                       np.sum(np.isnan(
                                           food_shortage_capita_only_shortage),
                                              axis=0) == settings["N"]] = 0

    wh_neg_fund = np.where(meta_sol["final_fund"] < 0)[0]
    ff_debt_neg = -meta_sol["final_fund"][wh_neg_fund]
    ter_years_neg = args["terminal_years"][wh_neg_fund].astype(int)

    wh_catastrophe = np.where(args["terminal_years"] != -1)[0]
    ff_debt_cat = -meta_sol["final_fund"][wh_catastrophe]
    ff_debt_cat[ff_debt_cat < 0] = 0
    ter_years_cat = args["terminal_years"][wh_catastrophe].astype(int)

    ff_debt_all = -meta_sol["final_fund"].copy()
    ff_debt_all[ff_debt_all < 0] = 0
    ter_years_all = args["terminal_years"].astype(int)

    # finding cultivation costs by taking a sample w/o catastrophic yields
    sample_no_cat = np.where(args["terminal_years"] == -1)[0][0]
    cultivation_costs = np.sum(
        meta_sol["yearly_fixed_costs"][sample_no_cat, :, :])
    cultivation_costs_det = np.sum(
        meta_sol_vss["yearly_fixed_costs"][sample_no_cat, :, :])

    # setting up dictionary of parameters to add to the panda object as new row:

    # 1 settings
    panda = {
        "Penalty method": settings["PenMet"],
        "Input probability food security": settings["probF"],
        "Input probability solvency": settings["probS"],
        "Including solvency constraint": settings["solv_const"],
        "Number of crops": settings["num_crops"],
        "Number of clusters": settings["k"],
        "Used clusters": settings["k_using"],
        "Yield projection": settings["yield_projection"],
        "Simulation start": settings["sim_start"],
        "Population scenario": settings["pop_scenario"],
        "Risk level covered": settings["risk"],
        "Tax rate": settings["tax"],
        "Share of income that is guaranteed": settings["perc_guaranteed"],
        "Initial fund size": settings["ini_fund"],
        "Sample size": settings["N"],
        "Sample size for validation": settings["validation_size"],
        "Number of covered years": settings["T"]
    }

    # 2 resulting penalties and probabilities
    panda["Penalty for food shortage"] = args["rhoF"]
    panda["Penalty for insolvency"] = args["rhoS"]
    panda["Resulting probability for food security"] = meta_sol["probF"]
    panda["Resulting probability for solvency"] = meta_sol["probS"]
    if meta_solF is not None:
        panda["Max. possible probability for food security (excluding solvency constraint)"] \
            = meta_solF["probF"]
    else:
        panda["Max. possible probability for food security (excluding solvency constraint)"] \
            = np.nan
    if meta_solS is not None:
        panda["Max. possible probability for solvency (excluding food security constraint)"] \
            = meta_solS["probS"]
    else:
        panda["Max. possible probability for solvency (excluding food security constraint)"] \
            = np.nan

    # 3 yield information
    panda["Probability for a catastrophic year"] = yield_information[
        "prob_cat_year"]
    panda["Share of samples with no catastrophe"] = yield_information[
        "share_no_cat"]
    panda[
        "Share of years/clusters with unprofitable rice yields"] = yield_information[
            "share_rice_np"]
    panda[
        "Share of years/clusters with unprofitable maize yields"] = yield_information[
            "share_maize_np"]

    # 4 population information
    panda["Share of West Africa's population that is living in total considered region (2015)"] \
            = np.sum(population_information["pop_cluster_ratio2015"])
    panda["Share of West Africa's population that is living in considered clusters (2015)"] \
            = list(population_information["pop_cluster_ratio2015"])

    # 5 crop areas
    panda["Available arable area"] = np.sum(args["max_areas"])
    panda["On average cultivated area per cluster"] = list(
        np.nanmean(crop_alloc, axis=(0, 1)))
    panda["Average yearly total cultivated area"] = np.nanmean(
        np.nansum(crop_alloc, axis=(1, 2)))
    panda["Total cultivation costs (sto. solution)"] = cultivation_costs

    # 6 food demand and needed import
    panda["Import (given as model input)"] = args["import"]
    panda["Average food demand"] = np.mean(args["demand"])
    panda["Food demand per capita"] = args["demand"][
        0] / population_information["population"][0]

    panda["Average aggregate food shortage (without taking into account imports)"] \
        = args["import"] + meta_sol["avg_nec_import"]
    panda["Average aggregate food shortage"] \
        = meta_sol["avg_nec_import"] # includes also caes that don't need import as zero
    if meta_solF is not None:
        panda["Average aggregate food shortage excluding solvency constraint"] \
            = meta_solF["avg_nec_import"]
    else:
        panda["Average aggregate food shortage excluding solvency constraint"] \
            = np.nan
    panda["Average aggregate food shortage per capita"] \
        = np.nanmean(np.nanmean(food_shortage_capita, axis = 0))*1e9
    panda["Average aggregate food shortage per capita (including only samples that have shortage)"] \
        = np.nanmean(np.nanmean(food_shortage_capita_only_shortage, axis = 0))*1e9

    # 7 a priori expected income, resulting average income
    panda["Expected income (stochastic setting)"] \
        = list(exp_incomes["sto. setting"])
    panda["Expected income (deterministic setting)"] \
        = list(exp_incomes["det. setting"])

    panda["Number of occurrences per cluster where farmers make losses"] \
        = list(meta_sol["num_years_with_losses"])
    panda["Average profits (pre tax) per cluster in final run (over samples and then years)"] \
        = list(np.nanmean(np.nanmean(meta_sol["profits_preTax"], axis = 0), axis = 0))  # profits include both actual profits and losses
    panda["Average profits (after tax) per cluster in final run (over samples and then years)"] \
        = list(np.nanmean(np.nanmean(meta_sol["profits_afterTax"], axis = 0), axis = 0))  # profits include both actual profits and losses
    panda["Average profits (pre tax) per cluster in final run scaled with capita (over samples and then years)"] \
        = list(np.nanmean(np.nanmean(meta_sol["profits_preTax"], axis = 0)/(pop_per_cluster/1e9), axis = 0))
    panda["Average profits (after tax) per cluster in final run scaled with capita (over samples and then years)"] \
        = list(np.nanmean(np.nanmean(meta_sol["profits_afterTax"], axis = 0)/(pop_per_cluster/1e9), axis = 0))
    panda["Aggregated average government payouts per cluster (over samples)"] \
        = list(np.nansum(np.nanmean(meta_sol["payouts"], axis = 0), axis = 0))

    # 8 final fund and needed debt
    panda["Number of samples with negative final fund"] \
        = np.nansum(meta_sol["final_fund"] < 0)
    panda["Average final fund (over all samples)"] \
        = np.nanmean(meta_sol["final_fund"])
    panda["Average final fund (over samples with catastrophe)"] \
        = np.nanmean(meta_sol["final_fund"][args["terminal_years"] != -1])

    if meta_solS is not None:
        panda["Average aggregate debt after payout (excluding food security constraint)"] \
            = meta_solS["avg_nec_debt"]
    else:
        panda["Average aggregate debt after payout (excluding food security constraint)"] \
            = np.nan
    panda["Average aggregate debt after payout"] \
        = meta_sol["avg_nec_debt"] # includes cases that don't need debt as zero
    panda["Average aggregate debt after payout (including only samples with negative final fund)"] \
        = np.nanmean(ff_debt_neg)
    panda["Average aggregate debt after payout (including only samples with catastrophe)"] \
        = np.nanmean(ff_debt_cat)
    panda["Average aggregate debt after payout per capita (including only samples with catastrophe)"] \
        = np.nanmean(ff_debt_cat / (pop_of_area[ter_years_cat]/1e9))
    panda["Average aggregate debt after payout per capita (including only samples with negative final fund)"] \
        = np.nanmean(ff_debt_neg / (pop_of_area[ter_years_neg]/1e9))
    panda["Average aggregate debt after payout per capita"] \
        = np.nanmean(ff_debt_all / (pop_of_area[ter_years_all]/1e9)) # negative debt set to zero

    # 9 different cost items in objective function
    panda["Average food demand penalty (over samples and then years)"] \
        = np.nanmean(np.nanmean(meta_sol["fd_penalty"], axis = 0))
    panda["Average total food demand penalty (over samples)"] \
        =np.nanmean(np.nansum(meta_sol["fd_penalty"], axis = 1))
    panda["Average solvency penalty (over samples)"] \
        = np.mean(meta_sol["sol_penalty"])
    panda["Average total cultivation costs"] \
        = np.nanmean(np.nansum(meta_sol["yearly_fixed_costs"], axis = (1,2)))
    panda["Expected total costs"] \
        = meta_sol["exp_tot_costs"] # this includes varying cultivation costs (depending on catastrophic year)

    # 10 VSS
    panda[
        "Value of stochastic solution"] = VSS_value  # diff of total costs using det. solution and using
    panda["Total cultivation costs (det. solution)"] = cultivation_costs_det
    if meta_sol["exp_tot_costs"] == 0:
        panda["VSS as share of total costs (sto. solution)"] = 0
    else:
        panda[
            "VSS as share of total costs (sto. solution)"] = VSS_value / meta_sol[
                "exp_tot_costs"]
    panda[
        "VSS as share of total costs (det. solution)"] = VSS_value / meta_sol_vss[
            "exp_tot_costs"]
    panda["VSS in terms of avg. nec. debt"] \
            = meta_sol_vss["avg_nec_debt"] - meta_sol["avg_nec_debt"]
    if meta_sol_vss["avg_nec_debt"] == 0:
        panda[
            "VSS in terms of avg. nec. debt as share of avg. nec. debt of det. solution"] = 0
    else:
        panda["VSS in terms of avg. nec. debt as share of avg. nec. debt of det. solution"] \
                = (meta_sol_vss["avg_nec_debt"] - meta_sol["avg_nec_debt"])/meta_sol_vss["avg_nec_debt"]
    panda["VSS in terms of avg. nec. debt as share of avg. nec. debt of sto. solution"] \
            =(meta_sol_vss["avg_nec_debt"] - meta_sol["avg_nec_debt"])/meta_sol["avg_nec_debt"]
    panda["VSS in terms of avg. nec. import"] \
            = meta_sol_vss["avg_nec_import"] - meta_sol["avg_nec_import"]
    panda["VSS in terms of avg. nec. import as share of avg. nec. import of det. solution"] \
            = (meta_sol_vss["avg_nec_import"] - meta_sol["avg_nec_import"])/meta_sol_vss["avg_nec_import"]
    if meta_sol["avg_nec_import"] == 0:
        panda["VSS in terms of avg. nec. import as share of avg. nec. import of sto. solution"] \
            = 0
    else:
        panda["VSS in terms of avg. nec. import as share of avg. nec. import of sto. solution"] \
            = (meta_sol_vss["avg_nec_import"] - meta_sol["avg_nec_import"])/meta_sol["avg_nec_import"]

    panda["Resulting probability for food security for VSS"] = meta_sol_vss[
        "probF"]
    panda["Resulting probability for solvency for VSS"] = meta_sol_vss["probS"]

    # 11 technincal variables
    panda[
        "Validation value (deviation of total penalty costs)"] = validation_values[
            "deviation_penalties"]
    panda["Seed (for yield generation)"] = settings["seed"]
    panda["Filename for full results"] = fn_fullresults

    # load panda object
    if not os.path.exists("ModelOutput/Pandas/" + file + ".csv"):
        CreateEmptyPanda(file)

    # add new row
    current_panda = pd.read_csv("ModelOutput/Pandas/" + file + ".csv")
    current_panda = current_panda.append(panda, ignore_index=True)

    # try to sve updated panda object as csv
    saved = False
    while saved == False:
        try:
            current_panda.to_csv("ModelOutput/Pandas/" + file + ".csv",
                                 index=False)
        except PermissionError:
            print(colored("Could not save updated panda.", "cyan"))
            print(
                colored(
                    "Please close the corresponding csv if currently open.",
                    "cyan"))
            continue
        saved = True

    return (None)
Esempio n. 11
0
def SetParameters(settings,
                  expected_incomes=None,
                  wo_yields=False,
                  VSS=False,
                  console_output=None,
                  logs_on=None):
    """
    
    Based on the settings, this sets most of the parameters that are needed as
    input to the model.    
    
    Parameters
    ----------
    settings : dict
        Dictionary of settings as given by DefaultSettingsExcept().
    expected_incomes : np.array of size (len(k_using),)
        The expected income of farmers in a scenario where the government is
        not involved.
    wo_yields : boolean, optional
        If True, the function will do everything execept generating the yield
        samples (and return an empty list as placeholder for the ylds 
        parameter). The default is False.
    VSS : boolean, optional
        If True, instead of yield samples only the average yields will be 
        returned and all clusters will be indicated as non-catastrophic by 
        cat_clusters, as needed to calculate the deterministic solution on 
        which the VSS is based. The default is False.
    console_output : boolean, optional
        Specifying whether the progress should be documented thorugh console 
        outputs. The default is defined in ModelCode/GeneralSettings.
    logs_on : boolean, optional
        Specifying whether the progress should be documented in a log document.
        The default is defined in ModelCode/GeneralSettings.
        

    Returns
    -------
    args : dict
        Dictionary of arguments needed as model input. 
        
        - k: number of clusters in which the area is devided.
        - k_using: specifies which of the clusters are to be considered in 
          the model. 
        - num_crops: the number of crops that are used
        - N: number of yield samples to be used to approximate the 
          expected value in the original objective function
        - cat_cluster: np.array of size (N, T, len(k_using)), 
          indicating clusters with yields labeled as catastrophic with 1, 
          clusters with "normal" yields with 0
        - terminal years: np.array of size (N,) indicating the year in 
          which the simulation is terminated (i.e. the first year with a 
          catastrophic cluster) for each sample
        - ylds: np.array of size (N, T, num_crops, len(k_using)) of
          yield samples in 10^6t/10^6ha according to the presence of 
          catastrophes
        - costs: np array of size (num_crops,) giving cultivation costs 
          for each crop in 10^9$/10^6ha
        - demand: np.array of size (T,) giving the total food demand
          for each year in 10^12kcal
        - ini_fund: initial fund size in 10^9$
        - import: given import that will be subtraced from demand in 10^12kcal
        - tax: tax rate to be paied on farmers profits
        - prices: np.array of size (num_crops,) giving farm gate prices 
          farmers earn in 10^9$/10^6t
        - T: number of years covered in model          
        - guaranteed_income: np.array of size (T, len(k_using)) giving 
          the income guaranteed by the government for each year and cluster
          in case of catastrophe in 10^9$, based on expected income
        - crop_cal : np.array of size (num_crops,) giving the calorie content
          of the crops in 10^12kcal/10^6t
        - max_areas: np.array of size (len(k_using),) giving the upper 
          limit of area available for agricultural cultivation in each
          cluster
        - probF : float or None, gigving demanded probability of keeping the
          food demand constraint.
        - probS : float or None, giving demanded probability of keeping the 
          solvency constraint.
    yield_information : dict
        Dictionary giving additional information on the yields
     
        - slopes: slopes of the yield trends
        - constants: constants of the yield trends
        - yld_means: average yields 
        - residual_stds: standard deviations of the residuals of the yield 
          trends
        - prob_cat_year: probability for a catastrophic year given the 
          covered risk level
        - share_no_cat: share of samples that don't have a catastrophe within 
          the considered timeframe
        - y_profit: minimal yield per crop and cluster needed to not have 
          losses
        - share_rice_np: share of cases where rice yields are too low to 
          provide profit
        - share_maize_np: share of cases where maize yields are too low to 
          provide profit
        - exp_profit: expected profits in 10^9$/10^6ha per year, crop, cluster
    population_information: dict
        Dictionary giving additional information on the population size in the
        model.
        
        - population : np.array of size (T,) giving estimates of population 
          in considered clusters from simulation start to end for given population 
          scenario (from UN PopDiv)
        - total_pop_scen : np.array of size (T,) giving estimates of population 
          in West Africa from simulation start to end for given population 
          scenario (from UN PopDiv)
        - pop_cluster_ratio2015 : np.array of size (len(k_using),) giving the
          share of population of a cluster to total population of West Africa
          in 2015.
    """

    crops = ["Rice", "Maize"]

    # 0. extract settings from dictionary
    k = settings["k"]
    k_using = settings["k_using"]
    num_crops = settings["num_crops"]
    yield_projection = settings["yield_projection"]
    sim_start = settings["sim_start"]
    pop_scenario = settings["pop_scenario"]
    risk = settings["risk"]
    if VSS:
        N = 1
    else:
        N = settings["N"]
    T = settings["T"]
    seed = settings["seed"]
    tax = settings["tax"]
    perc_guaranteed = settings["perc_guaranteed"]
    if expected_incomes is None:
        expected_incomes = np.zeros(len(k_using))

    # 1. get cluster information (clusters given by k-Medoids on SPEI data)
    with open("InputData/Clusters/Clustering/kMediods" + \
                        str(k) + "_PearsonDistSPEI.txt", "rb") as fp:
        clusters = pickle.load(fp)
        costs = pickle.load(fp)

    # 2. calculate total area and area proportions of the different clusters
    with open("InputData/Population/land_area.txt", "rb") as fp:
        land_area = pickle.load(fp)
    cluster_areas = np.zeros(len(k_using))
    for i, cl in enumerate(k_using):
        cluster_areas[i] = np.nansum(land_area[clusters == cl])
    cluster_areas = cluster_areas * 100  # convert sq km to ha

    # 3. Share of population in the area we use (in 2015):
    with open("InputData/Population/GPW_WA.txt", "rb") as fp:
        gridded_pop = pickle.load(fp)[3, :, :]
    with open("InputData/Population/" + \
                  "UN_PopTotal_Prospects_WesternAfrica.txt", "rb") as fp:
        total_pop = pickle.load(fp)
        scenarios = pickle.load(fp)
    total_pop_est_past = total_pop[np.where(
        scenarios == "Medium")[0][0], :][0:71]
    if pop_scenario == "fixed":
        total_pop_scen = np.repeat(
            total_pop[np.where(
                scenarios == "Medium")[0][0], :][(sim_start - 1) - 1950], T)
    else:
        total_pop_scen = total_pop[np.where(scenarios == pop_scenario)[0][0],:]\
                                    [(sim_start - 1950):(sim_start + T - 1950)]
        total_pop_year_before = total_pop[np.where(scenarios == pop_scenario)[0][0],:]\
                                    [sim_start - 1 - 1950]
    total_pop_UN_2015 = total_pop_est_past[2015 - 1950]
    cluster_pop = np.zeros(len(k_using))
    for i, cl in enumerate(k_using):
        cluster_pop[i] = np.nansum(gridded_pop[clusters == cl])
    total_pop_GPW = np.sum(cluster_pop)
    cluster_pop_ratio_2015 = cluster_pop / total_pop_UN_2015
    total_pop_ratio_2015 = total_pop_GPW / total_pop_UN_2015
    considered_pop_scen = total_pop_scen * total_pop_ratio_2015  # use 2015 ratio to scale down population scneario to considered area

    # 5. Per person/day demand
    # based on country specific caloric demand from a paper on food waste,
    # averaged based on area. For more detail see DataPreparation_DoNotRun.py
    with open("InputData/Other/AvgCaloricDemand.txt", "rb") as fp:
        ppdemand = pickle.load(fp)

    # 6. cultivation costs of crops
    # average cultivation costs based on literature data for some West
    # African countries (see DataPreparation_DoNotRun for details and
    # sources)
    with open("InputData/Other/CultivationCosts.txt", "rb") as fp:
        costs = pickle.load(fp)
    costs = np.transpose(np.tile(costs, (len(k_using), 1)))

    # 7. Energy value of crops
    with open("InputData/Other/CalorieContentCrops.txt", "rb") as fp:
        crop_cal = pickle.load(fp)

    # 8. Food demand
    # based on the demand per person and day (ppdemand) and assuming no change
    # of per capita daily consumption we use UN population scenarios for West
    # Africa and scale them down to the area we use, using the ratio from 2015
    # (from gridded GPW data)
    demand = ppdemand * 365 * total_pop_scen
    demand = demand * total_pop_ratio_2015
    # in 10^12 kcal
    demand = 1e-12 * demand

    # 9. guaranteed income as share of expected income w/o government
    # if expected income is not given...
    guaranteed_income = np.repeat(expected_incomes[np.newaxis, :], T, axis=0)
    guaranteed_income = perc_guaranteed * guaranteed_income
    # guaraneteed income per person assumed to be constant over time,
    # therefore scale with population size
    if not pop_scenario == "fixed":
        total_pop_ratios = total_pop_scen / total_pop_year_before
        guaranteed_income = (guaranteed_income.swapaxes(0, 1) *
                             total_pop_ratios).swapaxes(0, 1)

    # 10. prices for selling crops, per crop and cluster (but same over all clusters)
    with open("InputData//Prices/RegionFarmGatePrices.txt", "rb") as fp:
        prices = pickle.load(fp)
    prices = np.transpose(np.tile(prices, (len(k_using), 1)))

    # 11. thresholds for yields being profitable
    y_profit = costs / prices

    # 12. Agricultural Areas:
    # Landscapes of West Africa - A Window on a Changing World:
    # "Between 1975 and 2013, the area covered by crops doubled in West
    # Africa, reaching a total of 1,100,000 sq km, or 22.4 percent, of the
    # land surface."
    # (https://eros.usgs.gov/westafrica/agriculture-expansion)
    # => Approximate max. agricultural area by 22.4% of total cluster land
    #   area (assuming agricultural area is evenly spread over West Africa)
    # Their area extends a bit more north, where agricultural land is
    # probably reduced due to proximity to desert, so in our region the
    # percentage of agricultural might be a bit higher in reality. But
    # assuming evenly spread agricultural area over the area we use is
    # already a big simplification, hence this will not be that critical.
    max_areas = cluster_areas * 0.224
    # in 10^6ha
    max_areas = 1e-6 * max_areas

    # 13. generating yield samples
    # using historic yield data from GDHY
    with open("InputData/YieldTrends/DetrYieldAvg_k" + str(k) + ".txt",
              "rb") as fp:
        pickle.load(fp)  # yields_avg
        pickle.load(fp)  # avg_pred
        pickle.load(fp)  # residuals
        pickle.load(fp)  # residual_means
        residual_stds = pickle.load(fp)
        pickle.load(fp)  # fstat
        constants = pickle.load(fp)
        slopes = pickle.load(fp)
        pickle.load(fp)  # crops
        pickle.load(fp)  # years
    residual_stds = residual_stds[:, [i - 1 for i in k_using]]
    constants = constants[:, [i - 1 for i in k_using]]
    slopes = slopes[:, [i - 1 for i in k_using]]

    # get yield realizations:
    # what is the probability of a catastrophic year for given settings?
    _printing("\nOverview on yield samples",
              console_output=console_output,
              logs_on=logs_on)
    prob_cat_year = RiskForCatastrophe(risk, len(k_using))
    _printing("     Prob for catastrophic year: " + \
             str(np.round(prob_cat_year*100, 2)) + "%", \
             console_output = console_output, logs_on = logs_on)
    # create realizations of presence of catastrophic yields and corresponding
    # yield distributions
    np.random.seed(seed)
    cat_clusters, terminal_years, ylds, yld_means = \
          _YieldRealisations(slopes, constants, residual_stds, sim_start, \
                           N, risk, T, len(k_using), num_crops, \
                           yield_projection, VSS, wo_yields)
    # probability to not have a catastrophe
    no_cat = np.sum(terminal_years == -1) / N
    _printing("     Share of samples without catastrophe: " + str(np.round(no_cat*100, 2)), \
              console_output = console_output, logs_on = logs_on)
    # share of non-profitable crops
    if wo_yields:
        share_rice_np = 0
        share_maize_np = 0
    else:
        share_rice_np = np.sum(ylds[:, :, 0, :] < y_profit[0, :]) / np.sum(
            ~np.isnan(ylds[:, :, 0, :]))
        _printing("     Share of cases with rice yields too low to provide profit: " + \
                 str(np.round(share_rice_np * 100, 2)), console_output = console_output, \
                 logs_on = logs_on)
        share_maize_np = np.sum(ylds[:, :, 1, :] < y_profit[1, :]) / np.sum(
            ~np.isnan(ylds[:, :, 1, :]))
        _printing("     Share of cases with maize yields too low to provide profit: " + \
                 str(np.round(share_maize_np * 100, 2)), console_output = console_output, \
                 logs_on = logs_on)
    # in average more profitable crop
    exp_profit = yld_means * prices - costs
    avg_time_profit = np.nanmean(exp_profit, axis=0)
    more_profit = np.argmax(avg_time_profit, axis=0)  # per cluster
    _printing("     On average more profit (per cluster): " + \
             str([crops[i] for i in more_profit]), \
             console_output = console_output, logs_on = logs_on)
    # in average more productive crop
    avg_time_production = np.nanmean(yld_means, axis=0)
    more_food = np.argmax(avg_time_production, axis=0)
    _printing("     On average higher productivity (per cluster): " + \
             str([crops[i] for i in more_food]) + "\n", \
             console_output = console_output, logs_on = logs_on)

    # 14. group output into different dictionaries
    # arguments that are given to the objective function by the solver
    args = {
        "k": k,
        "k_using": k_using,
        "num_crops": num_crops,
        "N": N,
        "cat_clusters": cat_clusters,
        "terminal_years": terminal_years,
        "ylds": ylds,
        "costs": costs,
        "demand": demand,
        "ini_fund": settings["ini_fund"],
        "import": settings["import"],
        "tax": tax,
        "prices": prices,
        "T": T,
        "guaranteed_income": guaranteed_income,
        "crop_cal": crop_cal,
        "max_areas": max_areas,
        "probF": settings["probF"],
        "probS": settings["probS"]
    }

    # information not needed by the solver but potentially interesting
    yield_information = {
        "slopes": slopes,
        "constants": constants,
        "yld_means": yld_means,
        "residual_stds": residual_stds,
        "prob_cat_year": prob_cat_year,
        "share_no_cat": no_cat,
        "y_profit": y_profit,
        "share_rice_np": share_rice_np,
        "share_maize_np": share_maize_np,
        "exp_profit": exp_profit
    }

    population_information = {
        "population": considered_pop_scen,
        "total_pop_scen": total_pop_scen,  # full area WA
        "pop_cluster_ratio2015": cluster_pop_ratio_2015
    }

    return (args, yield_information, population_information)