Module pybeepop.tools

Functions

def StringList2CPA(theList)

Utility function to convert a list of strings to bytes readble by the C++ library

Classes

class BeePopModel (library_file, verbose=False)

Class of background functions to interface with the BeePop+ shared library using CTypes.

In most cases users would interact with a PyBeePop object instead of this class.

Initialize the connection to the BeePop+ shared library.

Args

library_file : str
Path to the BeePop+ shared library.
verbose : bool, optional
Print debugging messages? Defaults to False.

Raises

RuntimeError
If BeePop+ passes an error code on initialization.
Expand source code
class BeePopModel:
    """Class of background functions to interface with the BeePop+ shared library using CTypes.

    In most cases users would interact with a PyBeePop object instead of this class.
    """

    def __init__(self, library_file, verbose=False):
        """Initialize the connection to the BeePop+ shared library.

        Args:
            library_file (str): Path to the BeePop+ shared library.
            verbose (bool, optional): Print debugging messages? Defaults to False.

        Raises:
            RuntimeError: If BeePop+ passes an error code on initialization.
        """
        self.parameters = dict()
        self.parent = os.path.dirname(os.path.abspath(__file__))
        self.valid_parameters = pd.read_csv(
            os.path.join(self.parent, "data/BeePop_exposed_parameters.csv"), skiprows=1
        )["Exposed Variable Name"].tolist()
        self.weather_file = None
        self.contam_file = None
        self.verbose = verbose
        self.results = None
        self.lib = ctypes.CDLL(library_file)
        self.parent_dir = os.path.dirname(os.path.abspath(__file__))
        self.lib_status = None
        if self.lib.InitializeModel():  # Initialize model
            if self.verbose:
                print("Model initialized.")
        else:
            raise RuntimeError("BeePop+ could not be initialized.")
        self.clear_buffers()
        self.send_pars_to_beepop(
            ["NecPolFileEnable=false"], silent=True
        )  # disable residue input until given

    def clear_buffers(self):
        """Clear C++ buffers in BeePop+"""
        if not self.lib.ClearResultsBuffer():  # Clear Results and weather lists
            raise RuntimeError("Error clearing results buffer.")
        if not self.lib.ClearErrorList():
            raise RuntimeError("Error clearing error list")
        if not self.lib.ClearInfoList():
            raise RuntimeError("Error clearing info")

    def load_input_file(self, in_file):
        """Load txt file of BeePop+ parameters."""
        self.input_file = in_file
        icf = open(self.input_file)
        inputs = icf.readlines()
        icf.close()
        input_d = dict(x.replace(" ", "").replace("\n", "").split("=") for x in inputs)
        self.parameter_list_update(input_d)  # update parameter dictionary
        inputlist = []
        for k, v in self.parameters.items():
            inputlist.append("{}={}".format(k, v))
        self.send_pars_to_beepop(inputlist)
        return self.parameters

    def parameter_list_update(self, parameters):
        """Update the internal tracking of set parameters with a dict of
        parameters: values."""
        to_add = dict((k.lower(), v) for k, v in parameters.items())
        self.parameters.update(to_add)

    def set_parameters(self, parameters=None):
        """Set BeePop+ parameters based on a dict of parameters: values"""
        if parameters is not None:
            self.parameter_list_update(parameters)
        else:
            if len(self.parameters) < 1:
                return
        inputlist = []
        for k, v in self.parameters.items():
            inputlist.append("{}={}".format(k, v))
        self.send_pars_to_beepop(inputlist)
        return self.parameters

    def send_pars_to_beepop(self, parameter_list, silent=False):
        """Call the BeePop+ interface function to set parameters from a list of
        parameter=value strings"""
        for par in parameter_list:  # check for invalid parameters
            par_name = par.split("=")[0].lower()
            if par_name not in [x.lower() for x in self.valid_parameters]:
                raise ValueError("{} is not a valid parameter.".format(par_name))
        CPA = (ctypes.c_char_p * len(parameter_list))()
        inputlist_bytes = StringList2CPA(parameter_list)
        CPA[:] = inputlist_bytes
        if self.lib.SetICVariablesCPA(CPA, len(parameter_list)):
            if self.verbose and not silent:
                print("Updated parameters")
        else:
            raise RuntimeError("Error setting parameters")

    def get_parameters(self):
        """Return the current dict of user defined parameters"""
        return self.parameters

    def load_weather(self, weather_file=None):
        """Load a csv or comma separated txt weather file into BeePop+ using the library interface."""
        if weather_file is not None:
            try:
                wf = open(weather_file)
                weatherlines = wf.readlines()
                wf.close()
            except:
                raise OSError("Weather file is invalid.")
            self.weather_file = weather_file
            CPA = (ctypes.c_char_p * len(weatherlines))()
            weatherline_bytes = StringList2CPA(weatherlines)
            CPA[:] = weatherline_bytes
            if self.lib.SetWeatherCPA(CPA, len(weatherlines)):
                if self.verbose:
                    print("Loaded Weather")
            else:
                raise RuntimeError("Error Loading Weather")
        else:
            raise TypeError("Cannot set weather file to None")

    def load_contam_file(self, contam_file):
        """Load a csv or comma separated txt of pesticide residues in pollen/nectar using the library interface."""
        try:
            ct = open(contam_file)
            contamlines = ct.readlines()
            ct.close()
            self.contam_file = contam_file
        except:
            raise OSError("Residue file is invalid.")
        CPA = (ctypes.c_char_p * len(contamlines))()
        contamlines_bytes = StringList2CPA(contamlines)
        CPA[:] = contamlines_bytes
        if self.lib.SetContaminationTableCPA(CPA, len(contamlines)):
            if self.verbose:
                print("Loaded residue file")
        else:
            raise RuntimeError("Error loading residue file")
        self.send_pars_to_beepop(["NecPolFileEnable=true"], silent=True)  # enable residue files

    def set_latitude(self, latitude):
        """Set the latitude for calculation of day length using the library interface."""
        c_double_lat = ctypes.c_double(latitude)
        if self.lib.SetLatitude(c_double_lat):
            if self.verbose:
                print("Set Latitude to: {}".format(latitude))
        else:
            print("Error setting latitude")

    def run_beepop(self):
        """Run the BeePop+ model once using the previously set parameters and weather.

        Raises:
            RuntimeError: If BeePop+ passes an error code when running the simulation.

        Returns:
            DataFrame: A pandas DataFrame of daily BeePop+ outputs.
        """
        if self.lib.RunSimulation():
            self.lib_status = 1
        else:
            self.lib_status = 2
            raise RuntimeError("Error running BeePop+ simulation.")
        # fetch results
        theCount = ctypes.c_int(0)
        p_Results = ctypes.POINTER(ctypes.c_char_p)()
        if self.lib.GetResultsCPA(ctypes.byref(p_Results), ctypes.byref(theCount)):
            # store results
            n_result_lines = int(theCount.value)
            self.lib.ClearResultsBuffer()
            out_lines = []
            for j in range(0, n_result_lines - 1):
                out_lines.append(p_Results[j].decode("utf-8", errors="strict"))
            out_str = io.StringIO("\n".join(out_lines))
            out_df = pd.read_csv(
                out_str, sep="\\s+", skiprows=3, names=colnames, dtype={"Date": str}
            )
            self.results = out_df
        else:
            print("Error running BeePop+ and fetching results.")
        self.clear_buffers()
        return self.results

    def write_results(self, file_path):
        """Write previously generated BeePop+ outputs to a csv file."""
        results_file = file_path
        if self.results is None:
            raise RuntimeError("There are no results to write. Please run the model first")
        self.results.to_csv(results_file, index=False)
        if self.verbose():
            print("Wrote results to file")

    def get_errors(self):
        """Return the BeePop+ error log as a string using the library interface."""
        p_Errors = ctypes.POINTER(ctypes.c_char_p)()
        count = ctypes.c_int()
        if self.lib.GetErrorListCPA(ctypes.byref(p_Errors), ctypes.byref(count)):
            error_lines = []
            for j in range(count.value):
                error_lines.append(p_Errors[j].decode("utf-8", errors="replace"))
            # self.lib.ClearErrorList()
        else:
            raise RuntimeError("Failed to get error log")
        return "\n".join(error_lines)

    def get_info(self):
        """Return the BeePop+ info log as a string using the library interface."""
        p_Info = ctypes.POINTER(ctypes.c_char_p)()
        count = ctypes.c_int()
        if self.lib.GetInfoListCPA(ctypes.byref(p_Info), ctypes.byref(count)):
            info_lines = []
            for j in range(count.value):
                info_lines.append(p_Info[j].decode("utf-8", errors="replace"))
            # self.lib.ClearInfoList()
        return "\n".join(info_lines)

    def get_version(self):
        """Return the BeePop+ version as a string using the library interface."""
        buffsize = 16
        version_buffer = ctypes.create_string_buffer(buffsize)
        result = self.lib.GetLibVersionCP(version_buffer, buffsize)
        if result:
            return version_buffer.value.decode("utf-8")
        else:
            raise RuntimeError("Failed to get library version")

    def close_library(self):
        """Close connection to the library using CTypes."""
        dlclose_func = ctypes.CDLL(None).dlclose
        dlclose_func.argtypes = [ctypes.c_void_p]
        handle = self.lib._handle
        self.lib = None
        del self.lib

Methods

def clear_buffers(self)

Clear C++ buffers in BeePop+

def close_library(self)

Close connection to the library using CTypes.

def get_errors(self)

Return the BeePop+ error log as a string using the library interface.

def get_info(self)

Return the BeePop+ info log as a string using the library interface.

def get_parameters(self)

Return the current dict of user defined parameters

def get_version(self)

Return the BeePop+ version as a string using the library interface.

def load_contam_file(self, contam_file)

Load a csv or comma separated txt of pesticide residues in pollen/nectar using the library interface.

def load_input_file(self, in_file)

Load txt file of BeePop+ parameters.

def load_weather(self, weather_file=None)

Load a csv or comma separated txt weather file into BeePop+ using the library interface.

def parameter_list_update(self, parameters)

Update the internal tracking of set parameters with a dict of parameters: values.

def run_beepop(self)

Run the BeePop+ model once using the previously set parameters and weather.

Raises

RuntimeError
If BeePop+ passes an error code when running the simulation.

Returns

DataFrame
A pandas DataFrame of daily BeePop+ outputs.
def send_pars_to_beepop(self, parameter_list, silent=False)

Call the BeePop+ interface function to set parameters from a list of parameter=value strings

def set_latitude(self, latitude)

Set the latitude for calculation of day length using the library interface.

def set_parameters(self, parameters=None)

Set BeePop+ parameters based on a dict of parameters: values

def write_results(self, file_path)

Write previously generated BeePop+ outputs to a csv file.