Example #1
0
    def _evaluate(self,
                  x,  #
                  out,
                  *args,
                  **kwargs):
        """
        This method iterate over an Individual, execute the refactoring operation sequentially,
        and compute quality attributes for the refactored version of the program, as objectives of the search

        params:
        x[0] (Individual): x[0] is an instance of Individual (i.e., a list of refactoring operations)

        """
        # Stage 0: Git restore
        logger.debug("Executing git restore.")
        git_restore(config.PROJECT_PATH)
        update_understand_database(config.UDB_PATH)
        # Stage 1: Execute all refactoring operations in the sequence x
        logger.debug(f"Reached Individual with Size {len(x[0])}")
        for refactoring_operation in x[0]:
            refactoring_operation.do_refactoring()
            # Update Understand DB
            update_understand_database(config.UDB_PATH)
        # Stage 2: Computing quality attributes
        score = testability_main(config.UDB_PATH)
        logger.info(f"Testability Score: {score}")
        # Stage 3: Marshal objectives into vector
        out["F"] = np.array([-1 * score], dtype=float)
Example #2
0
 def do_refactoring(self):
     logger.info(f"Running {self.name}")
     logger.info(f"Parameters {self.params}")
     try:
         self.main(**self.params)
     except Exception as e:
         logger.error(f"Error in executing refactoring:\n {e}")
Example #3
0
 def do_refactoring(self):
     """ Check preconditions and apply refactoring operation to source code"""
     logger.info(f"Running {self.name}")
     logger.info(f"Parameters {self.params}")
     try:
         self.main(**self.params)
     except Exception as e:
         logger.error(f"Error in executing refactoring:\n {e}")
Example #4
0
    def _do(self, problem, X, **kwargs):
        """
        For population X
        """
        # The input of has the following shape (n_parents, n_matings, n_var)
        _, n_matings, n_var = X.shape

        # The output will be with the shape (n_offsprings, n_matings, n_var)
        # Because there the number of parents and offsprings are equal it keeps the shape of X
        Y = np.full_like(X, None, dtype=object)

        # print(X.shape)
        # print(X)

        # for each mating provided
        for k in range(n_matings):
            # get the first and the second parent (a and b are instance of individuals)
            a, b = X[0, k, 0], X[1, k, 0]
            # print('### a', a)
            # print('### b', b)
            # print('len a', len(a))
            # print('len b', len(b))

            len_min = min(len(a), len(b))
            cross_point_1 = random.randint(1, int(len_min * 0.30))
            cross_point_2 = random.randint(int(len_min * 0.70), len_min - 1)
            if random.random() < 0.5:
                cross_point_final = cross_point_1
            else:
                cross_point_final = cross_point_2
            logger.info(f'cross_point_final: {cross_point_final}')
            offspring_a = []
            offspring_b = []
            for i in range(0, cross_point_final):
                offspring_a.append(deepcopy(a[i]))
                offspring_b.append(deepcopy(b[i]))

            for i in range(cross_point_final, len_min):
                offspring_a.append(deepcopy(b[i]))
                offspring_b.append(deepcopy(a[i]))

            if len(b) > len(a):
                for i in range(len(a), len(b)):
                    offspring_a.append(deepcopy(b[i]))
            else:
                for i in range(len(b), len(a)):
                    offspring_b.append(deepcopy(a[i]))

            # print('$$$ offspring_a', offspring_a)
            # print('$$$ offspring_b', offspring_b)
            # print('len offspring_a', len(offspring_a))
            # print('len offspring_b', len(offspring_b))

            # Join offsprings to offspring population Y
            Y[0, k, 0], Y[1, k, 0] = offspring_a, offspring_b
            # quit()

        return Y
Example #5
0
def main(source_class: str, source_package: str, target_class: str, target_package: str, method_name: str,
         udb_path: str, *args, **kwargs):
    """


    """

    import_statement = None
    if source_package != target_package:
        import_statement = f"\nimport {target_package}.{target_class};"
    instance_name = target_class.lower() + "ByCodArt"
    db = und.open(udb_path)
    method_map, class_ent = get_source_class_map(db, source_class)
    if class_ent is None:
        logger.error("Class entity is None")
        return False

    # Strong overlay precondition
    # if class_ent.refs("Extend ~Implicit, ExtendBy, Implement"):
    #     logger.error("Class is in inheritance or implements an interface.")
    #     db.close()
    #     return False

    # Check if method is static
    method_ent = db.lookup(f"{source_package}.{source_class}.{method_name}", "Method")
    if len(method_ent) >= 1:
        method_ent = method_ent[0]
    else:
        logger.error("Entity not found.")
        db.close()
        return False

    if method_ent.simplename() != method_name:
        logger.error("Can not move method duo to duplicated entities.")
        logger.info(f"{method_ent}, {method_ent.kindname()}")
        db.close()
        return False

    if source_package == target_package and source_class == target_class:
        logger.error("Can not move to self.")
        db.close()
        return False

    is_static = STATIC in method_ent.kindname()
    # Find usages
    usages = {}

    for ref in method_ent.refs("Callby"):
        file = ref.file().longname()
        if file in usages:
            usages[file].append(ref.line())
        else:
            usages[file] = [ref.line(), ]

    try:
        src_class_file = db.lookup(f"{source_package}.{source_class}.java", "File")[0].longname()
        target_class_file = db.lookup(f"{target_package}.{target_class}.java", "File")[0].longname()
    except IndexError:
        logger.error("This is a nested method.")
        logger.info(f"{source_package}.{source_class}.java")
        logger.info(f"{target_package}.{target_class}.java")
        db.close()
        return False

    db.close()

    # Check if there is an cycle
    listener = parse_and_walk(
        file_path=target_class_file,
        listener_class=CheckCycleListener,
        class_name=source_class
    )

    if not listener.is_valid:
        logger.error(f"Can not move method because there is a cycle between {source_class}, {target_class}")
        # db.close()
        return False

    # Propagate Changes
    for file in usages.keys():
        public_class_name = os.path.basename(file).split(".")[0]
        is_in_target_class = public_class_name == target_class
        parse_and_walk(
            file_path=file,
            listener_class=PropagateListener,
            has_write=True,
            method_name=method_name,
            new_name=f"{instance_name}.{method_name}",
            lines=usages[file],
            is_in_target_class=is_in_target_class,
            method_map=method_map,
        )
    # exit(-1)
    # Do the cut and paste!
    # Cut
    listener = parse_and_walk(
        file_path=src_class_file,
        listener_class=CutMethodListener,
        has_write=True,
        class_name=target_class,
        instance_name=instance_name,
        method_name=method_name,
        is_static=is_static,
        import_statement=import_statement,
    )

    method_text = listener.method_text

    # Paste
    listener = parse_and_walk(
        file_path=target_class_file,
        listener_class=PasteMethodListener,
        has_write=True,
        method_text=method_text,
        source_class=source_class,
        method_map=method_map,
        imports=listener.imports,
    )

    # Post-Paste: Reference Injection
    parse_and_walk(
        file_path=target_class_file,
        listener_class=ReferenceInjectorAndConstructorListener,
        has_write=True,
        method_text=method_text,
        source_class=source_class,
        method_map=method_map,
        imports=None,
        has_empty_cons=listener.has_empty_cons,
    )
    # db.close()
    return True
Example #6
0
def main():
    # Define search algorithms
    algorithms = list()
    # 1: GA
    algorithm = GA(
        pop_size=config.POPULATION_SIZE,
        sampling=SudoRandomInitialization(),
        # crossover=AdaptiveSinglePointCrossover(prob=0.8),
        crossover=get_crossover("real_k_point", n_points=2),
        mutation=BitStringMutation(),
        eliminate_duplicates=RefactoringSequenceDuplicateElimination()
    )
    algorithms.append(algorithm)

    # 2: NSGA II
    algorithm = NSGA2(pop_size=config.POPULATION_SIZE,
                      sampling=SudoRandomInitialization(),
                      # crossover=AdaptiveSinglePointCrossover(prob=0.8),
                      crossover=get_crossover("real_k_point", n_points=2),
                      mutation=BitStringMutation(),
                      eliminate_duplicates=RefactoringSequenceDuplicateElimination()
                      )
    algorithms.append(algorithm)

    # 3: NSGA III
    # Todo: Ask for best practices in determining ref_dirs
    ref_dirs = get_reference_directions("energy", 8, 90, seed=1)
    algorithm = NSGA3(ref_dirs=ref_dirs,
                      pop_size=config.POPULATION_SIZE,
                      sampling=SudoRandomInitialization(),
                      # crossover=AdaptiveSinglePointCrossover(prob=0.8),
                      crossover=get_crossover("real_k_point", n_points=2),
                      mutation=BitStringMutation(),
                      eliminate_duplicates=RefactoringSequenceDuplicateElimination()
                      )
    algorithms.append(algorithm)

    # Define problems
    problems = list()
    problems.append(
        ProblemSingleObjective(n_refactorings_lowerbound=config.LOWER_BAND, n_refactorings_upperbound=config.UPPER_BAND)
    )
    problems.append(
        ProblemMultiObjective(n_refactorings_lowerbound=config.LOWER_BAND, n_refactorings_upperbound=config.UPPER_BAND)
    )
    problems.append(
        ProblemManyObjective(n_refactorings_lowerbound=config.LOWER_BAND, n_refactorings_upperbound=config.UPPER_BAND)
    )

    # Do optimization for various problems with various algorithms
    res = minimize(problem=problems[2],
                   algorithm=algorithms[2],
                   termination=('n_gen', config.MAX_ITERATIONS),
                   seed=1,
                   verbose=True)
    logger.info("** FINISHED **")

    logger.info("Best Individual:")
    logger.info(res.X)
    logger.info("Objective Values:")
    logger.info(res.F)

    logger.info("==================")
    logger.info("Other Solutions:")
    for ind in res.opt:
        logger.info(ind.X)
        logger.info(ind.F)
        logger.info("==================")

    logger.info(f"Start Time: {res.start_time}")
    logger.info(f"End Time: {res.end_time}")
    logger.info(f"Execution Time in Seconds: {res.exec_time}")
Example #7
0
    def _evaluate(self,
                  x,  #
                  out,
                  *args,
                  **kwargs):
        """
        This method iterate over an Individual, execute the refactoring operation sequentially,
        and compute quality attributes for the refactored version of the program, as objectives of the search

        params:
        x (Individual): x is an instance of Individual (i.e., a list of refactoring operations)

        """
        # Git restore`
        logger.debug("Executing git restore.")
        git_restore(config.PROJECT_PATH)
        update_understand_database(config.UDB_PATH)
        # Stage 1: Execute all refactoring operations in the sequence x
        logger.debug(f"Reached Individual with Size {len(x[0])}")
        for refactoring_operation in x[0]:
            refactoring_operation.do_refactoring()
            # Update Understand DB
            update_understand_database(config.UDB_PATH)

        # Stage 2: Computing quality attributes
        qmood = Objectives(udb_path=config.UDB_PATH)
        o1 = qmood.reusability
        o2 = qmood.understandability
        o3 = qmood.flexibility
        o4 = qmood.functionality
        o5 = qmood.effectiveness
        o6 = qmood.extendability
        del qmood
        o7 = testability_main(config.UDB_PATH)
        o8 = modularity_main(config.UDB_PATH)

        logger.info(f"Reusability Score: {o1}")
        logger.info(f"Understandability Score: {o2}")
        logger.info(f"Flexibility Score: {o3}")
        logger.info(f"Functionality Score: {o4}")
        logger.info(f"Effectiveness Score: {o5}")
        logger.info(f"Extendability Score: {o6}")
        logger.info(f"Testability Score: {o7}")
        logger.info(f"Modularity Score: {o8}")

        # Stage 3: Marshal objectives into vector
        out["F"] = np.array([-1 * o1, -1 * o2, -1 * o3, -1 * o4, -1 * o5, -1 * o6, -1 * o7, -1 * o8, ], dtype=float)
Example #8
0
def main(source_class: str, source_package: str, target_class: str,
         target_package: str, field_name: str, udb_path: str, *args, **kwargs):
    """

    Move filed main API

    """

    import_statement = None
    if source_package != target_package:
        import_statement = f"\nimport {target_package}.{target_class};"
    instance_name = target_class.lower() + "ByCodArt"
    db = und.open(udb_path)

    # Check if field is static
    field_ent = db.lookup(f"{source_package}.{source_class}.{field_name}",
                          "Variable")
    if len(field_ent) == 0:
        logger.error(
            f"Entity not found with query: {source_package}.{source_class}.{field_name}."
        )
        db.close()
        return False

    if source_package == target_package and source_class == target_class:
        logger.error("Can not move to self.")
        db.close()
        return False

    field_ent = field_ent[0]
    is_static = field_ent.kindname() == STATIC

    if is_static:
        logger.warning("Field is static!")

    # Find usages
    usages = {}

    for ref in field_ent.refs("Setby, Useby"):
        file = ref.file().longname()
        if file in usages:
            usages[file].append(ref.line())
        else:
            usages[file] = [
                ref.line(),
            ]
    try:
        src_class_file = db.lookup(
            f"{source_package}.{source_class}.java")[0].longname()
        target_class_file = db.lookup(
            f"{target_package}.{target_class}.java")[0].longname()
    except IndexError:
        logger.error("This is a nested class.")
        logger.info(f"{source_package}.{source_class}.java")
        logger.info(f"{target_package}.{target_class}.java")
        db.close()
        return False

    db.close()

    # Check if there is an cycle
    listener = parse_and_walk(
        file_path=target_class_file,
        listener_class=CheckCycleListener,
        class_name=source_class,
    )

    if not listener.is_valid:
        logger.error(
            f"Can not move field because there is a cycle between {source_class}, {target_class}"
        )
        # db.close()
        return False

    # Propagate Changes
    for file in usages.keys():
        parse_and_walk(
            file_path=file,
            listener_class=PropagateListener,
            has_write=True,
            field_name=field_name,
            new_name=f"{instance_name}.{field_name}",
            lines=usages[file],
        )

    # Do the cut and paste!
    # Cut
    listener = parse_and_walk(file_path=src_class_file,
                              listener_class=CutFieldListener,
                              has_write=True,
                              class_name=target_class,
                              instance_name=instance_name,
                              field_name=field_name,
                              is_static=is_static,
                              import_statement=import_statement)

    field_text = listener.field_text

    # Paste
    parse_and_walk(
        file_path=target_class_file,
        listener_class=PasteFieldListener,
        has_write=True,
        field_text=field_text,
    )

    # db.close()
    return True
Example #9
0
def main():
    # Define search algorithms
    algorithms = list()
    # 1: GA
    algorithm = GA(
        pop_size=config.POPULATION_SIZE,
        sampling=PureRandomInitialization(),
        crossover=AdaptiveSinglePointCrossover(prob=0.9),
        # crossover=get_crossover("real_k_point", n_points=2),
        mutation=BitStringMutation(prob=0.1),
        eliminate_duplicates=ElementwiseDuplicateElimination(
            cmp_func=is_equal_2_refactorings_list))
    algorithms.append(algorithm)

    # 2: NSGA II
    algorithm = NSGA2(
        pop_size=config.POPULATION_SIZE,
        sampling=PureRandomInitialization(),
        crossover=AdaptiveSinglePointCrossover(prob=0.9),
        # crossover=get_crossover("real_k_point", n_points=2),
        mutation=BitStringMutation(prob=0.1),
        eliminate_duplicates=ElementwiseDuplicateElimination(
            cmp_func=is_equal_2_refactorings_list))
    algorithms.append(algorithm)

    # 3: NSGA III
    # pop_size must be equal or larger than the number of reference directions
    number_of_references_points = config.POPULATION_SIZE - int(
        config.POPULATION_SIZE * 0.20)
    ref_dirs = get_reference_directions(
        'energy',  # algorithm
        8,  # number of objectives
        number_of_references_points,  # number of reference directions
        seed=1)
    algorithm = NSGA3(
        ref_dirs=ref_dirs,
        pop_size=config.POPULATION_SIZE,  # 200
        sampling=PureRandomInitialization(),
        selection=TournamentSelection(func_comp=binary_tournament),
        crossover=AdaptiveSinglePointCrossover(prob=0.8),
        # crossover=get_crossover("real_k_point", n_points=2),
        mutation=BitStringMutation(prob=0.1),
        eliminate_duplicates=ElementwiseDuplicateElimination(
            cmp_func=is_equal_2_refactorings_list))
    algorithms.append(algorithm)

    # -------------------------------------------
    # Define problems
    problems = list()
    problems.append(
        ProblemSingleObjective(n_refactorings_lowerbound=config.LOWER_BAND,
                               n_refactorings_upperbound=config.UPPER_BAND))
    problems.append(
        ProblemMultiObjective(n_refactorings_lowerbound=config.LOWER_BAND,
                              n_refactorings_upperbound=config.UPPER_BAND))
    problems.append(
        ProblemManyObjective(n_refactorings_lowerbound=config.LOWER_BAND,
                             n_refactorings_upperbound=config.UPPER_BAND,
                             evaluate_in_parallel=True))

    # Termination of algorithms
    my_termination = MultiObjectiveDefaultTermination(
        x_tol=None,
        cv_tol=None,
        f_tol=0.0015,
        nth_gen=10,
        n_last=20,
        n_max_gen=config.MAX_ITERATIONS,  # about 1000 - 1400
        n_max_evals=1e6)

    # Do optimization for various problems with various algorithms
    res = minimize(
        problem=problems[2],
        algorithm=algorithms[2],
        termination=my_termination,
        seed=1,
        verbose=False,
        copy_algorithm=True,
        copy_termination=True,
        save_history=False,
    )
    # np.save('checkpoint', res.algorithm)

    # Log results
    logger.info("\n** FINISHED **\n")
    logger.info(
        "Best refactoring sequences (a set of non-dominated solutions):")
    logger.info(res.X)
    logger.info("Best objective values (a set of non-dominated solutions):")
    logger.info(res.F)

    logger.info("=" * 75)
    logger.info("Other solutions:")
    for ind in res.opt:
        logger.info(ind.X)
        logger.info(ind.F)
        logger.info("-" * 50)
    logger.info("=" * 75)

    logger.info(f"Start time: {res.start_time}")
    logger.info(f"End time: {res.end_time}")
    logger.info(f"Execution time in seconds: {res.exec_time}")
    logger.info(f"Execution time in minutes: {res.exec_time / 60}")
    logger.info(f"Execution time in hours: {res.exec_time / (60 * 60)}")
    logger.info(f"Number of generations: {res.algorithm.n_gen}")
    # logger.info(f"Number of generations", res.algorithm.termination)

    pf = res.F
    # dm = HighTradeoffPoints()
    dm = get_decision_making("high-tradeoff")
    try:
        I = dm.do(pf)
        logger.info(f"High tradeoff points: {pf[I][0]}")
        logger.info(
            f"High tradeoff points corresponding refactorings: {res.X[I]}")
        logger.info(
            f"The mean improvement of quality attributes: {np.mean(pf[I][0], axis=0)}"
        )
        logger.info(
            f"The median improvement of quality attributes: {np.median(pf[I][0], axis=0)}"
        )
    except:
        logger.info(
            "No multi optimal solutions (error in computing high tradeoff points)!"
        )
Example #10
0
    def _evaluate(self, x, out, *args, **kwargs):
        """
        By default, elementwise_evaluation is set to False, which implies the _evaluate retrieves a set of solutions.

        params:
            x (Population): x is a matrix where each row is an individual, and each column a variable.
            We have one variable of type list (Individual) ==> x.shape = (len(Population), 1)

        """

        objective_values = []
        for k, individual_ in enumerate(x):
            # Stage 0: Git restore
            logger.debug("Executing git restore.")
            git_restore(config.PROJECT_PATH)
            logger.debug("Updating understand database after git restore.")
            update_understand_database(config.UDB_PATH)

            # Stage 1: Execute all refactoring operations in the sequence x
            logger.debug(
                f"Reached an Individual with size {len(individual_[0])}")
            for refactoring_operation in individual_[0]:
                refactoring_operation.do_refactoring()
                # Update Understand DB
                logger.debug(
                    f"Updating understand database after {refactoring_operation.name}."
                )
                update_understand_database(config.UDB_PATH)

            # Stage 2:
            arr = Array('d', range(8))
            if self.evaluate_in_parallel:
                # Stage 2 (parallel mood): Computing quality attributes
                p1 = Process(target=calc_qmood_objectives, args=(arr, ))
                p2 = Process(target=calc_testability_objective,
                             args=(
                                 config.UDB_PATH,
                                 arr,
                             ))
                p3 = Process(target=calc_modularity_objective,
                             args=(
                                 config.UDB_PATH,
                                 arr,
                             ))
                p1.start(), p2.start(), p3.start()
                p1.join(), p2.join(), p3.join()
            else:
                # Stage 2 (sequential mood): Computing quality attributes
                qmood = Objectives(udb_path=config.UDB_PATH)
                arr[0] = qmood.reusability
                arr[1] = qmood.understandability
                arr[2] = qmood.flexibility
                arr[3] = qmood.functionality
                arr[4] = qmood.effectiveness
                arr[5] = qmood.extendability
                arr[6] = testability_main(
                    config.UDB_PATH,
                    initial_value=config.CURRENT_METRICS.get("TEST", 1.0))
                arr[7] = modularity_main(
                    config.UDB_PATH,
                    initial_value=config.CURRENT_METRICS.get("MODULE", 1.0))
                del qmood

            # Stage 3: Marshal objectives into vector
            objective_values.append([-1 * i for i in arr])
            logger.info(
                f"Objective values for individual {k}: {[i for i in arr]}")

        # Stage 4: Marshal all objectives into out dictionary
        out['F'] = np.array(objective_values, dtype=float)
Example #11
0
    def _evaluate(
            self,
            x,  #
            out,
            *args,
            **kwargs):
        """
        This method iterate over an Individual, execute the refactoring operation sequentially,
        and compute quality attributes for the refactored version of the program, as objectives of the search

        params:
        x (Population): x is a matrix where each row is an individual, and each column a variable.
            We have one variable of type list (Individual) ==> x.shape = (len(Population), 1)

        """
        objective_values = []
        for k, individual_ in enumerate(x):
            # Stage 0: Git restore
            logger.debug("Executing git restore.")
            git_restore(config.PROJECT_PATH)
            logger.debug("Updating understand database after git restore.")
            update_understand_database(config.UDB_PATH)

            # Stage 1: Execute all refactoring operations in the sequence x
            logger.debug(f"Reached Individual with Size {len(individual_[0])}")
            for refactoring_operation in individual_[0]:
                refactoring_operation.do_refactoring()
                # Update Understand DB
                logger.debug(
                    f"Updating understand database after {refactoring_operation.name}."
                )
                update_understand_database(config.UDB_PATH)

            # Stage 2:
            arr = Array('d', range(8))
            if self.evaluate_in_parallel:
                # Stage 2 (parallel mood): Computing quality attributes
                p1 = Process(target=calc_qmood_objectives, args=(arr, ))
                p2 = Process(target=calc_testability_objective,
                             args=(
                                 config.UDB_PATH,
                                 arr,
                             ))
                p3 = Process(target=calc_modularity_objective,
                             args=(
                                 config.UDB_PATH,
                                 arr,
                             ))
                p1.start(), p2.start(), p3.start()
                p1.join(), p2.join(), p3.join()
                o1 = sum([i for i in arr[:6]]) / 6.
                o2 = arr[7]
                o3 = arr[8]
            else:
                # Stage 2 (sequential mood): Computing quality attributes
                qmoods = Objectives(udb_path=config.UDB_PATH)
                o1 = qmoods.average
                o2 = testability_main(config.UDB_PATH,
                                      initial_value=config.CURRENT_METRICS.get(
                                          "TEST", 1.0))
                o3 = modularity_main(config.UDB_PATH,
                                     initial_value=config.CURRENT_METRICS.get(
                                         "MODULE", 1.0))
                del qmoods

            # Stage 3: Marshal objectives into vector
            objective_values.append([-1 * o1, -1 * o2, -1 * o3])
            logger.info(
                f"Objective values for individual {k}: {[-1 * o1, -1 * o2, -1 * o3]}"
            )

        # Stage 4: Marshal all objectives into out dictionary
        out['F'] = np.array(objective_values, dtype=float)
Example #12
0
# import logging

from dotenv import load_dotenv

# logging.basicConfig(level=logging.DEBUG)
# logger = logging.getLogger(__file__)
from sbse.config import logger

load_dotenv()

# -------------------
# For Linux os
PYTHONPATH = os.environ.get("PYTHONPATH")  # Put your path here

# -------------------
# For Windows os
# https://scitools.com/support/python-api/
# Python 3.8 and newer require the user add a call to os.add_dll_directory(“SciTools/bin/“  # Put your path here
# os.add_dll_directory('C:/Program Files/SciTools/bin/pc-win64')  # Put your path here
sys.path.insert(0, PYTHONPATH)  # Put your path here

# --------------------
# Import understand if available on the path
try:
    import understand as und
    logger.info(f"Loaded understand {und.version()} successfully")
except ModuleNotFoundError:
    raise ModuleNotFoundError('Understand not found.')
except ImportError:
    raise ImportError("Can not import")