import math
from abc import ABC, abstractmethod
from dataclasses import dataclass
from hvac import Quantity
from hvac.fluids import Fluid
from hvac.refrigerant_piping.copper_tubing import CopperTube
from hvac.fluid_flow import Pipe, Circular

Q_ = Quantity

@dataclass
class RefrigerantCycleData:
    rfg: Fluid
    T_eva: Quantity  # saturated suction temperature = evaporating temperature
    T_con: Quantity  # saturated condensing temperature
    ESH: Quantity    # evaporator superheat
    CSC: Quantity    # condensor subcooling
    CSH: Quantity    # compressor superheat

    def __post_init__(self):
        self.T_eva_o = self.T_eva + self.ESH
        self.T_con_i = self.T_con + self.CSH
        self.T_con_o = self.T_con - self.CSC
        self.P_eva = self.rfg(T=self.T_eva, x=Q_(0, 'frac')).P
        self.P_con = self.rfg(T=self.T_con, x=Q_(0, 'frac')).P
        self.rfg_con_i = self.rfg(P=self.P_con, T=self.T_con_i)
        self.rfg_con_o = self.rfg(P=self.P_con, T=self.T_con_o)
        self.rfg_eva_i = self.rfg(P=self.P_eva, h=self.rfg_con_o.h)
        self.rfg_eva_o = self.rfg(P=self.P_eva, T=self.T_eva_o)
        self.q_eva = self.rfg_eva_o.h - self.rfg_eva_i.h

class RefrigerantLine(ABC):
    vr_allow_max: Quantity = None
    vr_allow_min: dict[str, Quantity] | None = None

    def __init__(
        self,
        rfg_cycle_data: RefrigerantCycleData,
        Q_eva_max: Quantity,
        Q_eva_min: Quantity | None = None,
    ):
        """
        Parameters
        ----------
        rfg_cycle_data:
            Instance of dataclass RefrigerantCycleData containing the
            specifications of the refrigerant cycle.
        Q_eva_max:
            Maximum evaporator capacity. This will determine the maximum
            flow velocity of the refrigerant.
        Q_eva_min: optional, default None
            Minimum evaporator capacity. This will determine the minimum
            flow velocity of the refrigerant. Leave to default `None` in case of
            an ON/OFF-controlled compressor.
        """
        self.rcd = rfg_cycle_data
        self.Q_eva_max = Q_eva_max
        self.Q_eva_min = Q_eva_min or Q_eva_max
        # if Q_eva_min is None, set self.Q_eva_min equal to Q_eva_max
        self.mr_max = self._get_mr(self.Q_eva_max)
        self.Vr_max = self._get_Vr(self.mr_max)
        self.mr_min = self._get_mr(self.Q_eva_min)
        self.Vr_min = self._get_Vr(self.mr_min)

    def _get_mr(self, Q) -> Quantity:
        """Get mass flow rate of refrigerant in the system."""
        return Q / self.rcd.q_eva

    @abstractmethod
    def _get_Vr(self, mr) -> Quantity:
        """Get volume flow rate of refrigerant."""
        ...

    @abstractmethod
    def get_vr(self, *args, **kwargs) -> tuple:
        """Get refrigerant velocity."""
        ...

    def _check_vr_max(self, vr_max):
        if vr_max.to('feet / min') < self.vr_allow_max:
            r = 'OK'
        else:
            r = 'TOO HIGH'
        return r

    def _check_vr_min(self, vr_min, copper_tube, riser):
        ...

    @abstractmethod
    def get_dp(self, copper_tube: CopperTube, Leq: Quantity) -> Quantity:
        """Get pressure drop across refrigeration line."""
        ...

class VaporLine(RefrigerantLine):

    @abstractmethod
    def _get_Vr(self, mr) -> Quantity:
        ...

    def get_vr(self, copper_tube: CopperTube, riser: bool = True) -> tuple:
        """
        Get maximum and minimum flow velocity of refrigerant.
        This method also checks if maximum and minimum velocity are within the
        allowable upper and lower limits to avoid noise on one hand and to
        ensure proper oil return on the other hand.

        Parameters
        ----------
        copper_tube:
            Instance of dataclass CopperTube containing copper tube specs.
        riser: default True
            Indicate if the pipe is a vertical riser (True) or not (False).

        Returns
        -------
        Tuple with 4 elements:
        1. the flow velocity at maximum capacity
        2. string that indicates if maximum flow velocity is 'OK' or 'TOO HIGH'
        3. the flow velocity at minimum capacity
        4. string that indicates if minimum flow velocity is 'OK', 'TOO LOW', or
        'TOO HIGH'
        """
        A = math.pi * (copper_tube.di ** 2) / 4
        vr_max = self.Vr_max / A
        r_max = self._check_vr_max(vr_max)
        vr_min = self.Vr_min / A
        r_min = self._check_vr_min(vr_min, copper_tube, riser)
        return vr_max, r_max, vr_min, r_min

    def _check_vr_max(self, vr_max):
        if vr_max.to('feet / min') < self.vr_allow_max:
            r = 'OK'
        else:
            r = 'TOO HIGH'
        return r

    def _check_vr_min(self, vr_min, copper_tube, riser):
        vr_min = vr_min.to('feet / min')
        vr_allow_min = self.vr_allow_min[copper_tube.dn]
        # minimum allowable flow velocity to ensure oil return
        if not riser:
            vr_allow_min *= 0.75
        if vr_allow_min <= vr_min < self.vr_allow_max:
            r = 'OK'
        elif vr_min < vr_allow_min:
            r = 'TOO LOW'
        else:  # vr_min >= self.vr_allow_max:
            r = 'TOO HIGH'
        return r

    @abstractmethod
    def get_dp(self, copper_tube: CopperTube, Leq: Quantity) -> Quantity:
        ...

class SuctionLine(VaporLine):
    vr_allow_max = Q_(4000, 'ft / min')
    vr_allow_min = {
        # minimum allowable flow velocity for proper oil return in riser @
        # saturated suction temperature of 20 °F
        k: Q_(v, 'feet / min') for k, v in
        [
            ('3/8', 370), ('1/2', 460),
            ('5/8', 520), ('3/4', 560),
            ('7/8', 600), ('1 1/8', 700),
            ('1 3/8', 780), ('1 5/8', 840),
            ('2 1/8', 980), ('2 5/8', 1080),
            ('3 1/8', 1180), ('3 5/8', 1270),
            ('4 1/8', 1360)
        ]
    }

    def _get_Vr(self, mr):
        # volume flow rate of refrigerant at evaporator outlet
        return mr / self.rcd.rfg_eva_o.rho

    def get_dp(self, copper_tube: CopperTube, Leq: Quantity) -> Quantity:
        """
        Get pressure drop across suction line.

        Parameters
        ----------
        copper_tube:
            Instance of dataclass CopperTube containing copper tube specs.
        Leq:
            Equivalent length of suction line (including equivalent length
            of fittings).
        """
        pipe = Pipe.create(
            length=Leq,
            wall_roughness=Q_(0.0015, 'mm'),
            fluid=self.rcd.rfg(P=self.rcd.P_eva, x=Q_(1, 'frac')),
            cross_section=Circular.create(copper_tube.di),
            volume_flow_rate=self.Vr_max
        )
        return pipe.pressure_drop

class DischargeLine(VaporLine):
    vr_allow_max = Q_(3500, 'ft / min')
    vr_allow_min = {
        # minimum allowable flow velocity for proper oil return in riser @
        # saturated condensing temperature of 80 °F
        k: Q_(v, 'feet / min') for k, v in
        [
            ('5/16', 220), ('3/8', 250),
            ('1/2', 285), ('5/8', 315),
            ('3/4', 345), ('7/8', 375),
            ('1 1/8', 430), ('1 3/8', 480),
            ('1 5/8', 520), ('2 1/8', 600),
            ('2 5/8', 665), ('3 1/8', 730)
        ]
    }

    def _get_Vr(self, mr):
        # volume flow rate of refrigerant at condenser inlet
        return mr / self.rcd.rfg_con_i.rho

    def get_dp(self, copper_tube: CopperTube, Leq: Quantity) -> Quantity:
        """
        Get pressure drop across discharge line.

        Parameters
        ----------
        copper_tube:
            Instance of dataclass CopperTube containing copper tube specs.
        Leq:
            Equivalent length of discharge line (including equivalent length
            of fittings).
        """
        pipe = Pipe.create(
            length=Leq,
            wall_roughness=Q_(0.0015, 'mm'),
            fluid=self.rcd.rfg(P=self.rcd.P_con, x=Q_(1, 'frac')),
            cross_section=Circular.create(copper_tube.di),
            volume_flow_rate=self.Vr_max
        )
        return pipe.pressure_drop

class LiquidLine(RefrigerantLine):
    vr_allow_max = Q_(600, 'ft / min')

    def _get_Vr(self, mr) -> Quantity:
        return mr / self.rcd.rfg_con_o.rho

    def get_vr(self, copper_tube: CopperTube) -> tuple:
        A = math.pi * (copper_tube.di ** 2) / 4
        vr_max = self.Vr_max / A
        r_max = self._check_vr_max(vr_max)
        return vr_max, r_max

    def get_dp(self, copper_tube: CopperTube, Leq: Quantity) -> Quantity:
        """
        Get pressure drop across liquid line.

        Parameters
        ----------
        copper_tube:
            Instance of dataclass CopperTube containing copper tube specs.
        Leq:
            Equivalent length of discharge line (including equivalent length
            of fittings).
        """
        pipe = Pipe.create(
            length=Leq,
            wall_roughness=Q_(0.0015, 'mm'),
            fluid=self.rcd.rfg(P=self.rcd.P_con, x=Q_(0, 'frac')),
            cross_section=Circular.create(copper_tube.di),
            volume_flow_rate=self.Vr_max
        )
        return pipe.pressure_drop

    def get_dT(self, dP: Quantity, H: Quantity) -> Quantity:
        """
        Get equivalent change in saturated temperature.

        Parameters
        ----------
        dP:
            Total pressure drop across liquid line including fittings
            and accessoires.
        H:
            The elevation height between the outlet and inlet of the liquid
            line (if the outlet is below the inlet, `H` is negative).
        """
        rho = self.rcd.rfg_con_o.rho
        g = Q_(9.81, 'm / s ** 2')
        dP_elev = rho * g * H
        dP_tot = dP + dP_elev
        P_out = self.rcd.P_con - dP_tot  # pressure at liquid line outlet = TXV inlet
        T_sat = self.rcd.rfg(P=P_out, x=Q_(0, 'frac')).T  # saturation temperature @ P_out
        dT = self.rcd.T_con.to('K') - T_sat.to('K')
        return dT


from pandas import read_csv
from sklearn.metrics import mean_absolute_percentage_error
from math import sqrt
from matplotlib import pyplot as plt
from pandas import concat
import numpy as np
import scipy.stats as stats
import pandas as pd

def persistence_one_step_ln(train_log, teste_log,
    show_results=False, plot_result=False):
    # Prepare data
    teste_log = teste_log.values
    train_log = [x for x in train_log]

    # Walk-forward validation
    predictions = list()

    for i in range(len(teste_log)):
        # Predict
        yhat = train_log[-1]
        # Store forecast in list of predictions
        predictions.append(yhat)
        # Add actual observation to train for the next loop
        obs = teste_log[i]
        train_log.append(obs)

        if show_results:
            print('>Predicted=%.3f, Expected=%.3f' % (yhat, obs))

    # Report performance
    mape = mean_absolute_percentage_error(np.exp(teste_log), np.exp(predictions))
    print('MAPE: %.3f' % mape)
    # Plot predicted vs expected values
    if plot_result:
        plt.plot(np.exp(teste_log))
        plt.plot(np.exp(predictions), color='red')
        plt.show()

# Load data
train_log = pd.read_csv('../timeserie_log_train.csv',
                     header=0, index_col=0, parse_dates=True, squeeze=True)
teste_log = pd.read_csv('../timeserie_log_test.csv',
                     header=0, index_col=0, parse_dates=True, squeeze=True)

persistence_one_step_ln(train_log, teste_log, plot_result=True)


import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

import plotly.graph_objects as go
import pandas as pd

import plotly.express as px

# Read data from a csv
z_data = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/api_docs/mt_bruno_elevation.csv')

fig = go.Figure(data=[go.Surface(z=z_data.values)])
fig.update_traces(contours_z=dict(show=True, usecolormap=True,
                                  highlightcolor="limegreen", project_z=True))
fig.update_layout(title='Mt Bruno Elevation', autosize=False,
                  scene_camera_eye=dict(x=1.87, y=0.88, z=-0.64),
                  width=500, height=500,
                  margin=dict(l=65, r=50, b=65, t=90))

cont = go.Figure(go.Surface(
    contours = {
        "x": {"show": True, "start": 1.5, "end": 2, "size": 0.04, "color":"white"},
        "z": {"show": True, "start": 0.5, "end": 0.8, "size": 0.05}
    },
    x = [1,2,3,4,5],
    y = [1,2,3,4,5],
    z = [
        [0, 1, 0, 1, 0],
        [1, 0, 1, 0, 1],
        [0, 1, 0, 1, 0],
        [1, 0, 1, 0, 1],
        [0, 1, 0, 1, 0]
    ]))
cont.update_layout(
        scene = {
            "xaxis": {"nticks": 20},
            "zaxis": {"nticks": 4},
            'camera_eye': {"x": 0, "y": -1, "z": 0.5},
            "aspectratio": {"x": 1, "y": 1, "z": 0.2}
        })

df = px.data.iris()
sepal = px.scatter_3d(df, x='sepal_length', y='sepal_width', z='petal_width',color='species')

app = dash.Dash(__name__)

app.layout = html.Div(
    children = [
        html.Div([
            dcc.Graph(id="3d-surface-plot", figure=fig),
            dcc.Graph(id="contour-surface-plot", figure=cont)
        ],style={'columnCount': 2}),
        html.Div([
            dcc.Graph(id="3d-scatter-plot", figure=sepal)
            ], style={'columnCount': 1})
            ])


from uuid import uuid4
from typing import Tuple, List
from asendia_us_lib.shipping_request import ShippingRequest, Item
from asendia_us_lib.shipping_response import PackageLabel
from purplship.core.units import CustomsInfo, Packages, Options, Weight
from purplship.core.utils import Serializable, DP
from purplship.core.models import (
    Documents,
    ShipmentRequest,
    ShipmentDetails,
    Message,
    Customs,
)

from purplship.providers.asendia_us.units import (
    Service,
    Option,
    LabelType,
    ProcessingLocation,
)
from purplship.providers.asendia_us.error import parse_error_response
from purplship.providers.asendia_us.utils import Settings

def parse_shipment_response(
    responses: Tuple[str, dict], settings: Settings
) -> Tuple[ShipmentDetails, List[Message]]:
    _, response = responses
    errors = parse_error_response(response, settings)
    details = (
        _extract_details(responses, settings)
        if response.get("packageLabel") is not None
        else None
    )

    return details, errors

def _extract_details(response: Tuple[str, dict], settings: Settings) -> ShipmentDetails:
    label, details = response
    shipment = DP.to_object(PackageLabel, details)

    return ShipmentDetails(
        carrier_name=settings.carrier_name,
        carrier_id=settings.carrier_id,
        tracking_number=shipment.trackingNumber,
        shipment_identifier=shipment.packageId,
        docs=Documents(label=label),
    )

def shipment_request(
    payload: ShipmentRequest, settings: Settings
) -> Serializable[ShippingRequest]:
    package = Packages(payload.parcels).single
    options = Options(payload.options, Option)
    product_code = Service.map(payload.service).value_or_key
    unique_id = getattr(payload, "id", uuid4().hex)
    customs = CustomsInfo(payload.customs or Customs(commodities=[]))

    request = ShippingRequest(
        accountNumber=settings.account_number,
        subAccountNumber=options.asendia_sub_account_number,
        processingLocation=ProcessingLocation.map(
            options.asendia_processing_location or "SFO"
        ).name,
        includeRate=True,
        labelType=LabelType.map(payload.label_type or "PDF").name_or_key,
        orderNumber=unique_id,
        dispatchNumber=unique_id,
        packageID=unique_id,
        recipientTaxID=payload.recipient.state_tax_id,
        returnFirstName=payload.shipper.person_name,
        returnLastName=payload.shipper.person_name,
        returnCompanyName=payload.shipper.company_name,
        returnAddressLine1=payload.shipper.address_line1,
        returnAddressLine2=payload.shipper.address_line2,
        returnAddressLine3=None,
        returnProvince=payload.shipper.state_code,
        returnPostalCode=payload.shipper.postal_code,
        returnCountryCode=payload.shipper.country_code,
        returnPhone=payload.shipper.phone_number,
        returnEmail=payload.shipper.email,
        recipientFirstName=payload.recipient.person_name,
        recipientLastName=payload.recipient.person_name,
        recipientBusinessName=payload.recipient.company_name,
        recipientAddressLine1=payload.recipient.address_line1,
        recipientAddressLine2=payload.recipient.address_line2,
        recipientAddressLine3=None,
        recipientCity=payload.recipient.city,
        recipientProvince=payload.recipient.state_code,
        recipientPostalCode=payload.recipient.postal_code,
        recipientPhone=payload.recipient.phone_number,
        recipientEmail=payload.recipient.email,
        totalPackageWeight=package.weight.value,
        weightUnit=package.weight_unit.value.lower(),
        dimLength=package.length.value,
        dimWidth=package.width.value,
        dimHeight=package.height.value,
        dimUnit=package.dimension_unit.value,
        totalPackageValue=options.declared_value,
        currencyType=options.currency,
        productCode=product_code,
        customerReferenceNumber1=payload.reference,
        customerReferenceNumber2=None,
        customerReferenceNumber3=None,
        contentType=("D" if package.parcel.is_document else "M"),
        packageContentDescription=package.parcel.description,
        vatNumber=None,
        sellerName=payload.shipper.person_name,
        sellerAddressLine1=payload.shipper.address_line1,
        sellerAddressLine2=payload.shipper.address_line2,
        sellerAddressLine3=None,
        sellerProvince=payload.shipper.state_code,
        sellerPostalCode=payload.shipper.postal_code,
        sellerPhone=payload.shipper.phone_number,
        sellerEmail=payload.shipper.email,
        items=[
            Item(
                sku=item.sku,
                itemDescription=item.description,
                unitPrice=item.value_amount,
                quantity=item.quantity,
                unitWeight=Weight(item.weight, package.weight_unit).value,
                countryOfOrigin=item.origin_country,
                htsNumber=None,
            )
            for item in customs.commodities
        ],
    )

    return Serializable(request)


#!/usr/bin/env python3
#
# File: tidal_perturbation_in_circular_binary.py
# Author: Timothy Van Reeth <timothy.vanreeth@kuleuven.be>
# License: GPL-3+
# Description: Calculating the observed flux variations of a tidally
#              distorted g-mode pulsation in a circular, synchronised
#              binary system

import os
import sys
import glob
import yaml
import numpy as np
import subprocess as sp
import astropy.units as au

from progress.bar import Bar
from binary import circular_binary
from pulsations import pmode_pulsation

def distort_pulsation(star, orb_phase=0., distortion_factor=1.):
    """
        Routine to scale the g-mode visibility as a function of the location on
        the stellar surface

        Parameters:
            star:               stellar_model object
                                the star in which the pulsation is distorted
            orb_phase:          float; optional
                                current phase of the orbital cycle
                                (default = 0.)
            distortion_factor:  float; optional
                                the scaling factor of the mode visibility
                                across the stellar surface (default = 1)

        Returns:
            puls_scaling:       numpy array

    """

    basefun = 0.5 * (3.*(np.sin(star.theta)*np.cos(star.phi - 2.*np.pi*orb_phase))**2. - 1.)
    puls_scaling = ( 2. * np.ones(star.theta.shape) / ( distortion_factor + 1. ) ) + ( (basefun - np.nanmin(basefun)) / (np.nanmax(basefun) - np.nanmin(basefun)) * 2. * ( (distortion_factor - 1.) / (distortion_factor + 1.) ) )

    return puls_scaling

def calculate_flux(binary, pulsation, distortion_factor=1., puls_phase=0., orb_phase=0.):
    """
        Calculate the observed flux of a binary star with a perturbed g-mode pulsation at a
        given pulsation and orbital phase, and indicate if the data point was simulated
        during an eclipse.

        Parameters:
            binary:            circular_binary object
                               the studied circular, synchronised binary system
            pulsation:         gmode_pulsation object
                               the g-mode pulsation that will be perturbed and evaluated
            distortion_factor: float; optional
                               the scaling factor of the mode visibility
                                across the stellar surface (default = 1)
            puls_phase:         float; optional
                                current phase of the pulsation cycle (in the inertial frame)
                                (default = 0.)
            orb_phase:          float; optional
                                current phase of the orbital cycle
                                (default = 0.)

        Returns:
            bin_iflux:          float
                                the simulated flux caused by (solely) the binarity
            tot_iflux:          float
                                the total simulated flux at the provided pulsational and
                                orbital phases
            puls_iflux:         float
                                the simulated flux caused by (solely) the distorted pulsation
            ecl_iflag:          int
                                flag indicating if the calculated data point occurs during an
                                eclipse
    """

    ecl_maps = binary.eclipse_masks(orb_phase)
    primary_vissurf_bool = binary.primary.theta_incl < np.pi/2.
    secondary_vissurf_bool = binary.secondary.theta_incl < np.pi/2.

    primary_mask = np.array(ecl_maps[0] & primary_vissurf_bool, dtype=float)
    secondary_mask = np.array(ecl_maps[1] & secondary_vissurf_bool, dtype=float)

    primary_vissurf = np.array(primary_vissurf_bool, dtype=float)
    secondary_vissurf = np.array(secondary_vissurf_bool, dtype=float)

    if(binary.primary.pulsating):
        norm_puls = calculate_normalised_pulsations(binary.primary, pulsation, puls_phase)
        # puls_scaling = distort_pulsation(binary.primary, orb_phase=orb_phase, distortion_factor=distortion_factor)
        puls_scaling = 1.
        primary_puls = 10.**(-0.0004 * pulsation.amplitude * norm_puls * puls_scaling)
        secondary_puls = np.ones(binary.secondary.theta.shape)
    else:
        norm_puls = calculate_normalised_pulsations(binary.secondary, pulsation, puls_phase)
        # puls_scaling = distort_pulsation(binary.secondary, orb_phase=orb_phase, distortion_factor=distortion_factor)
        puls_scaling = 1.
        secondary_puls = 10.**(-0.0004 * pulsation.amplitude * norm_puls * puls_scaling)
        primary_puls = np.ones(binary.primary.theta.shape)

    primary_totflux   = np.nansum(2. * np.cos(binary.primary.theta_incl)   * binary.primary.cell_weight   * binary.primary.limb_darkening()   * primary_puls   * primary_mask)
    secondary_totflux = np.nansum(2. * np.cos(binary.secondary.theta_incl) * binary.secondary.cell_weight * binary.secondary.limb_darkening() * secondary_puls * secondary_mask)
    primary_binflux   = np.nansum(2. * np.cos(binary.primary.theta_incl)   * binary.primary.cell_weight   * binary.primary.limb_darkening()   * primary_mask)
    secondary_binflux = np.nansum(2. * np.cos(binary.secondary.theta_incl) * binary.secondary.cell_weight * binary.secondary.limb_darkening() * secondary_mask)
    primary_refflux   = np.nansum(2. * np.cos(binary.primary.theta_incl)   * binary.primary.cell_weight   * binary.primary.limb_darkening()   * primary_vissurf)
    secondary_refflux = np.nansum(2. * np.cos(binary.secondary.theta_incl) * binary.secondary.cell_weight * binary.secondary.limb_darkening() * secondary_vissurf)

    tot_iflux = -2500.*np.log10( (binary.light_contribution1*primary_totflux/primary_refflux) + (binary.light_contribution2*secondary_totflux/secondary_refflux))
    bin_iflux = -2500.*np.log10( (binary.light_contribution1*primary_binflux/primary_refflux) + (binary.light_contribution2*secondary_binflux/secondary_refflux))
    puls_iflux = tot_iflux - bin_iflux

    if(ecl_maps[0].all() & ecl_maps[1].all()):
        ecl_iflag = 0
    else:
        ecl_iflag = 1

    return bin_iflux, tot_iflux, puls_iflux, ecl_iflag

def calculate_normalised_pulsations(star, pulsation, puls_phase):
    if pulsation.mode_type == 'p':
        return calculate_normalised_pmode_pulsations(star, pulsation, puls_phase)
    else:
        print('g modes are currently unsupported')
        sys.exit()

def calculate_normalised_pmode_pulsations(star, pulsation, puls_phase):
    """
        Converting the geomety of the calculated g-mode pulsation to temperature
        variations, Lagrangian displacements and the associated velocity field

        Parameters:
            star:           stellar_model object
                            the pulsating star
            pulsation:      gmode_pulsation object
                            the simulated g-mode pulsation
            puls_phase:     float
                            the current phase of the studied pulsation (as seen
                            by the observer)

        Returns:
            norm_puls:      numpy array
                            normalised pulsation variability of the g-mode at the
                            stellar surface at the phase puls_phase
    """

    if(pulsation.m < 0.):
        sign = 1.
    else:
        sign = -1.

    norm_puls = (pulsation.Lr * np.cos(pulsation.m*star.phi + 2.*np.pi*sign*puls_phase)) / np.nanmax(pulsation.Lr * np.cos(pulsation.m*star.phi + 2.*np.pi*sign*puls_phase))

    return norm_puls

def read_inlist(inlist_filename='./inlist.yaml'):
    """
        Read in the required variables to calculate r-mode visibilities,
        following the methodology from Saio et al. (2018).

        Parameters:
            inlist_filename:      string; optional (default: ./inlist.dat)
                                  the path to the inlist

        Returns:
            maindir:              string
                                  main work directory
            binary:               circular binary object
                                  the circular synchronised binary system that will be simulated
            pulsation:            gmode_pulsation object
                                  the g-mode pulsation that will be simulated
            distortion_factor:    float
                                  distortion factor of the simulated pulsation
            N_forb_cycles:        int
                                  number of orbital cycles to be simulated
            Nsample_per_cycle:    int
                                  number of simulated data points per orbital cycle
    """

    with open(inlist_filename,'r') as f:
        cfg = yaml.load(f, Loader=yaml.Loader)
    f.close()

    # collecting the given parameter values within the appropriate objects
    binary = circular_binary(cfg)

    if cfg['pulsation']['mode_type'] == 'p':
        pulsation = pmode_pulsation(cfg, binary.freq_orb)
    elif cfg['pulsation']['mode_type'] == 'g':
        print('g modes are currently not supported')
        sys.exit()
    else:
        print('Mode type {} not recognised. Exiting.'.format(cfg['pulsation']['mode_type']))
        sys.exit()

    if(cfg['pulsation']['pulsating_star'] == 'primary'):
        binary.primary.pulsating = True
        pulsation.calculate_puls_geometry(binary.primary)
    elif(cfg['pulsation']['pulsating_star'] == 'secondary'):
        binary.secondary.pulsating = True
        pulsation.calculate_puls_geometry(binary.secondary)

    return cfg['general']['main_dir'], binary, pulsation, \
           cfg['pulsation']['distortion_factor'], \
           cfg['simulation']['N_forb_cycles'], \
           cfg['simulation']['Nsample_per_cycle']

def save_results(result_filename, time, orb_phases, puls_phases, total_flux, binary_flux, pulsation_flux, eclipse_flags):
    """
        Saving the calculated visibilities of the distorted g-mode pulsations

        Parameters:
            result_filename:    string
                                absolute path to the results output filename
            time:               astropy quantity array
                                the time stamps of the simulate data points
            orb_phases:         numpy array
                                orbital phases corresponding to the different time stamps
            puls_phases:        numpy array
                                pulsation phases at the different time stamps
            total_flux:         numpy array
                                the total observed flux variations at the different time stamps (unit: mmag)
            binary_flux:        numpy array
                                the flux variations from the binary motion at the different time stamps (unit: mmag)
            pulsation_flux:     numpy array
                                the flux variations from the pulsations at the different time stamps (unit: mmag)
            eclipse_flags:      numpy array of integers
                                flags indicating if the data point was taken during an eclipse (yes = 1; no = 0)

    """

    file = open(result_filename, 'w')

    headline = ' '*16 + 'time' + ' '*11 + 'orb_phase' + ' '*10 + 'puls_phase' + ' '*10 + 'total_flux' + ' '*14 + 'binary' + ' '*11 + 'pulsation' + ' '*13 + 'eclipse'
    file.write(f'{headline}\n')

    for itime, iorbphase, ipulsphase, iflux, ibin, ipuls, iflag in zip(time.to(au.d).value, orb_phases, puls_phases, total_flux, binary_flux, pulsation_flux, eclipse_flags):
        data = f'  {itime:18e}  {iorbphase:18e}  {ipulsphase:18e}  {iflux:18e}  {ibin:18e}  {ipuls:18e}                   {iflag}\n'
        file.write(data)
    file.close()

    return

if __name__ == "__main__":

    # Reading the input parameters / variables
    maindir, binary, pulsation, distortion_factor, \
    N_forb_cycles, Nsample_per_cycle = read_inlist(sys.argv[1])
    print('Inlist read')

    # Setting the output directory and copying the used inlist
    mass1 = binary.primary.Mstar.to(au.Msun).value
    mass2 = binary.secondary.Mstar.to(au.Msun).value
    freq_orb = binary.freq_orb.to(1./au.d).value
    outdir = f'{maindir}binary_M{int(round(100.*mass1))}M{int(round(100.*mass2))}_forb{int(round(1000.*freq_orb))}_i{int(binary.incl_deg)}/'

    if not os.path.exists(outdir):
        os.makedirs(outdir)
        print('Directory created')
    print('Directory set')

    computation_nr = f"{len(glob.glob(f'{outdir}inlist*.yaml')) + 1}".zfill(3)
    sp.call(f'cp ./inlist.yaml {outdir}inlist{computation_nr}.yaml', shell=True)   # warning: do not forget to adapt this line if the inlist filename changes!

    # setting the time domain for tidal perturbation simulations
    time = np.linspace(0., float(N_forb_cycles), N_forb_cycles*Nsample_per_cycle+1) * binary.period.to(au.d)
    orb_phases = np.linspace(0.,float(N_forb_cycles),N_forb_cycles*Nsample_per_cycle+1) % 1.
    puls_phases = np.array(pulsation.puls_freq.to(1/au.d)*time, dtype=float) % 1.

    # time = np.loadtxt('tess_sector01_times.dat').T
    # time *= au.day
    # orb_phases = np.array( time * freq_orb, dtype=float) % 1.
    # puls_phases = np.array(pulsation.puls_freq.to(1/au.d)*time, dtype=float) % 1.

    print('Phase arrays constructed.')
    # Calculating the mode visibilities and kinetic energy
    binary_flux = []
    total_flux = []
    pulsation_flux = []
    eclipse_flags = []

    pbar = Bar('Calculating...', max=len(puls_phases))
    for iph,puls_phase,orb_phase in zip(np.arange(len(puls_phases)), puls_phases, orb_phases):

        # calculating the observed fluxes for (1) the binary + pulsation, (2) the binary, and (3) the pulsation, and provide (4) eclipse flags
        bin_iflux, tot_iflux, puls_iflux, ecl_iflag = calculate_flux(binary, pulsation, puls_phase=puls_phase, orb_phase=orb_phase, distortion_factor=distortion_factor)
        binary_flux.append(bin_iflux)
        total_flux.append(tot_iflux)
        pulsation_flux.append(puls_iflux)
        eclipse_flags.append(int(ecl_iflag))
        pbar.next()
    pbar.finish()

    binary_flux = np.array(binary_flux)
    total_flux = np.array(total_flux)
    pulsation_flux = np.array(pulsation_flux)
    eclipse_flags = np.array(eclipse_flags, dtype=int)

    # Saving the results
    save_results(f'{outdir}pmode_f{int(np.round(pulsation.puls_freq.value*1000000))}_perturbed-visibilities_{computation_nr}.dat', time, orb_phases, puls_phases, total_flux, binary_flux, pulsation_flux, eclipse_flags)


import pygame
import sys

#define bird class
class Bird(object):
    def __init__(self):
        self.birdRect = pygame.Rect(65,50,50,50)
        self.birdStatus = [pygame.image.load("flappybirdassets/assets/1.png"),
                           pygame.image.load("flappybirdassets/assets/2.png"),
                           pygame.image.load("flappybirdassets/assets/dead.png")]
        self.status = 0
        self.birdX = 120
        self.birdY = 350
        self.jump = False
        self.jumpSpeed = 10
        self.gravity = 5
        self.dead = False

    def birdUpdate(self):
        #movement
        if self.jump:
           self.jumpSpeed -= 1
           self.birdY -= self.jumpSpeed
        else:
           self.gravity += 0.2
           self.birdY += self.gravity
        self.birdRect[1] = self.birdY

def createMap():
        screen.blit(background,(0,0))
        #display pine
        screen.blit(Pipeline.pineUp,(Pipeline.wallx, -300))
        screen.blit(Pipeline.pineUp,(Pipeline.wallx, 500))
        Pipeline.PipelineUpdate()
        #display bird
        if Bird.dead :
            Bird.status = 2
        elif Bird.jump :
            Bird.status = 1
        screen.blit(Bird.birdStatus[Bird.status], (Bird.birdX,Bird.birdY))
        Bird.birdUpdate()

        screen.blit(font.render('Score:'+ str(score),1,(255,255,255)),(100,50))
        pygame.display.update()

#define pipeline class
class Pipeline(object):
    def __init__(self):
        self.wallx = 400
        self.pineUp = pygame.image.load("flappybirdassets/assets/top.png")
        self.pineDown = pygame.image.load("flappybirdassets/assets/bottom.png")

    def PipelineUpdate(self):
        #movement
        self.wallx -= 5
        if self.wallx < -80:
            global score
            score += 1
            self.wallx = 400

def checkDead():
    upRect = pygame.Rect(Pipeline.wallx,-300,Pipeline.pineUp.get_width(),Pipeline.pineUp.get_height())
    downRect = pygame.Rect(Pipeline.wallx,500,Pipeline.pineDown.get_width(),Pipeline.pineDown.get_height())

    if upRect.colliderect(Bird.birdRect) or downRect.colliderect(Bird.birdRect):
        Bird.dead = True
    if not Bird.birdRect[1] < height:
        Bird.dead = True
        return True
    else:
        return False

def getResult():
    final_text1 = "GAME OVER"
    final_text2 = "Your final score is :" + str(score)
    ft1_font = fit1_font = pygame.font.SysFont("Arial",70)
    ft1_surf = font.render(final_text1,1,(242,3,36))
    ft2_font = fit2_font = pygame.font.SysFont("Arial",50)
    ft2_surf = font.render(final_text2,1,(253,177,6))

    screen.blit(ft1_surf,[screen.get_width()/2-ft1_surf.get_width()/2,100])
    screen.blit(ft2_surf,[screen.get_width()/2-ft2_surf.get_width()/2,200])
    pygame.display.update()

if __name__ == '__main__':
    pygame.init()
    font = pygame.font.SysFont(None,50)

    size = width, height = 400,650
    screen = pygame.display.set_mode(size) #setting windows sieze
    clock = pygame.time.Clock()# setting delay time
    color = (255,255,255)

    Bird = Bird()
    Pipeline = Pipeline()
    score = 0

    while True:
        clock.tick(60)

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
               sys.exit()
            if (event.type == pygame.MOUSEBUTTONDOWN or event.type == pygame.KEYDOWN) and not Bird.dead :
                Bird.jump = True
                Bird.gravity = 5
                Bird.jumpSpeed = 10

        # screen.fill(color)
        background = pygame.image.load("flappybirdassets/assets/background.png")
        if checkDead():
            getResult()
        else:
            createMap()

pygame.quit()


import cv2
import pickle

import numpy as np
import random
import threading
import warnings

from ..utils.image import read_image_bgr

import numpy as np
from PIL import Image
from six import raise_from

import csv
import sys
import os.path

import keras

from ..utils.anchors import (
    anchor_targets_bbox_centers,
    anchor_targets_bbox,
    anchors_for_shape,
    guess_shapes
)
from ..utils.image import (
    TransformParameters,
    adjust_transform_for_image,
    apply_transform,
    preprocess_image,
    resize_image,
)
from ..utils.transform import transform_aabb, random_transform_generator

class Centers_Generator(object):

    def __init__(
            self,
            pairs,
            BCS_path,
            BoxCars_dataset,
            BoxCars_images,
            BCS_sessions=range(4),
            no_centers=False,
            fake_centers=False,
            split_exclusion_function=None,
            batch_size=1,
            group_method='random',  # one of 'none', 'random', 'ratio'
            shuffle_groups=True,
            image_min_side=400,
            image_max_side=600,
            transform_list=None,
            transform_parameters=None,
            compute_anchor_targets=anchor_targets_bbox_centers,
            compute_shapes=guess_shapes,
            preprocess_image=preprocess_image
    ):
        """ Initialize Generator object.

        Args
            transform_generator    : A generator used to randomly transform images and annotations.
            batch_size             : The size of the batches to generate.
            group_method           : Determines how images are grouped together (defaults to 'ratio', one of ('none', 'random', 'ratio')).
            shuffle_groups         : If True, shuffles the groups each epoch.
            image_min_side         : After resizing the minimum side of an image is equal to image_min_side.
            image_max_side         : If after resizing the maximum side is larger than image_max_side, scales down further so that the max side is equal to image_max_side.
            transform_parameters   : The transform parameters used for data augmentation.
            compute_anchor_targets : Function handler for computing the targets of anchors for an image and its annotations.
            compute_shapes         : Function handler for computing the shapes of the pyramid for a given input.
            preprocess_image       : Function handler for preprocessing an image (scaling / normalizing) for passing through a network.
        """

        self.image_names = []
        self.image_data = {}

        self.classes = {'car': 0}

        # Take base_dir from annotations file if not explicitly specified.

        self.labels = {}
        for key, value in self.classes.items():
            self.labels[value] = key

        self.fake_centers = fake_centers
        self.no_centers = no_centers

        self.split_exclusion_function = split_exclusion_function

        self.image_data = {}
        self.transform_indices = []

        for pair in pairs:
            if BoxCars_dataset is not None:
                self.dataset_name = 'BoxCars'
                self.parse_BoxCars(BoxCars_dataset.format(pair), BoxCars_images.format(pair))

            for i in BCS_sessions:
                self.dataset_name = 'BCS'
                ds_path = os.path.join(BCS_path.format(pair), 'dataset_{}.pkl'.format(i))
                im_path = os.path.join(BCS_path.format(pair), 'images_{}'.format(i))
                self.parse_BCS(dataset_path=ds_path, images_path=im_path)

        print("Generator size: {}".format(self.size()))

        # self.transform_generator    = transform_generator
        self.batch_size = int(batch_size)
        self.group_method = group_method
        self.shuffle_groups = shuffle_groups
        self.image_min_side = image_min_side
        self.image_max_side = image_max_side

        if transform_parameters is not None:
            self.transform_list = transform_list
        else:
            self.transform_list = [
                random_transform_generator(
                    min_translation=(-0.4, -0.4),
                    max_translation=(0.4, 0.4),
                    min_scaling=(0.9, 0.9),
                    max_scaling=(2.0, 2.0),
                    flip_x_chance=0.5
                ),
                random_transform_generator(
                    min_translation=(-0.5, -0.5),
                    max_translation=(0.5, 0.5),
                    min_scaling=(0.03, 0.03),
                    max_scaling=(1.0, 1.0),
                    flip_x_chance=0.5
                ),
            ]

        self.transform_parameters = transform_parameters or TransformParameters(fill_mode='constant')
        if self.no_centers:
            self.compute_anchor_targets = anchor_targets_bbox
        else:
            self.compute_anchor_targets = compute_anchor_targets
        self.compute_shapes = compute_shapes
        self.preprocess_image = preprocess_image

        self.group_index = 0
        self.lock = threading.Lock()

        self.group_images()

    def size(self):
        """ Size of the dataset.
        """
        return len(self.image_names)

    def num_classes(self):
        """ Number of classes in the dataset.
        """
        return max(self.classes.values()) + 1

    def name_to_label(self, name):
        """ Map name to label.
        """
        return self.classes[name]

    def label_to_name(self, label):
        """ Map label to name.
        """
        return self.labels[label]

    def image_path(self, image_index):
        """ Returns the image path for image_index.
        """
        return self.image_names[image_index]

    def image_aspect_ratio(self, image_index):
        """ Compute the aspect ratio for an image with image_index.
        """
        # PIL is fast for metadata
        image = Image.open(self.image_path(image_index))
        return float(image.width) / float(image.height)

    def load_image(self, image_index):
        """ Load an image at the image_index.
        """
        return read_image_bgr(self.image_path(image_index))

    def load_annotations(self, image_index):
        """ Load annotations for an image_index.
        """
        path = self.image_names[image_index]
        annots = self.image_data[path]

        if self.no_centers:
            boxes = np.zeros((len(annots), 5))
        else:
            boxes = np.zeros((len(annots), 6))

        for idx, annot in enumerate(annots):
            class_name = annot['class']
            boxes[idx, 0] = float(annot['x1'])
            boxes[idx, 1] = float(annot['y1'])
            boxes[idx, 2] = float(annot['x2'])
            boxes[idx, 3] = float(annot['y2'])
            boxes[idx, 4] = self.name_to_label(class_name)
            if not self.no_centers:
                boxes[idx, 5] = float(annot['c'])

        return boxes

    def load_transform_indices(self, group):
        return [self.transform_indices[index] for index in group]

    def load_annotations_group(self, group):
        """ Load annotations for all images in group.
        """
        return [self.load_annotations(image_index) for image_index in group]

    def filter_annotations(self, image_group, annotations_group, group):
        """ Filter annotations by removing those that are outside of the image bounds or whose width/height < 0.
        """
        # test all annotations
        for index, (image, annotations) in enumerate(zip(image_group, annotations_group)):
            assert (isinstance(annotations,
                               np.ndarray)), '\'load_annotations\' should return a list of numpy arrays, received: {}'.format(
                type(annotations))

            # test x2 < x1 | y2 < y1 | x1 < 0 | y1 < 0 | x2 <= 0 | y2 <= 0 | x2 >= image.shape[1] | y2 >= image.shape[0]
            invalid_indices = np.where(
                (annotations[:, 2] <= annotations[:, 0]) |
                (annotations[:, 3] <= annotations[:, 1]) |
                (annotations[:, 0] < 0) |
                (annotations[:, 1] < 0) |
                (annotations[:, 2] > image.shape[1]) |
                (annotations[:, 3] > image.shape[0])
            )[0]

            # delete invalid indices
            if len(invalid_indices):
                # cv2.imwrite("ID_.png".format(group[index]), image)
                # warnings.warn("Following warning happens in:{}".format(self.dataset_name))
                # warnings.warn('Image with id {} (shape {}) contains the following invalid boxes: {}.'.format(
                #     group[index],
                #     image.shape,
                #     [annotations[invalid_index, :] for invalid_index in invalid_indices]
                # ))
                annotations_group[index] = np.delete(annotations, invalid_indices, axis=0)

        return image_group, annotations_group

    def load_image_group(self, group):
        """ Load images for all images in a group.
        """
        return [self.load_image(image_index) for image_index in group]

    def random_transform_group_entry(self, image, annotations, transform_index):
        """ Randomly transforms image and annotation.
        """
        # randomly transform both image and annotations
        transform_generator = self.transform_list[transform_index]
        if transform_generator:

            transform = adjust_transform_for_image(next(transform_generator), image,
                                                   self.transform_parameters.relative_translation)
            image = apply_transform(transform, image, self.transform_parameters)

            # Transform the bounding boxes in the annotations.
            annotations = annotations.copy()
            for index in range(annotations.shape[0]):
                annotations[index, :4] = transform_aabb(transform, annotations[index, :4])

        return image, annotations

    def resize_image(self, image):
        """ Resize an image using image_min_side and image_max_side.
        """
        return resize_image(image, min_side=self.image_min_side, max_side=self.image_max_side)

    def preprocess_group_entry(self, image, annotations, transform_index):
        """ Preprocess image and its annotations.
        """
        # preprocess the image
        image = self.preprocess_image(image)

        # randomly transform image and annotations
        image, annotations = self.random_transform_group_entry(image, annotations, transform_index)

        # resize image
        image, image_scale = self.resize_image(image)

        # apply resizing to annotations too
        annotations[:, :4] *= image_scale

        return image, annotations

    def preprocess_group(self, image_group, annotations_group, transform_indices):
        """ Preprocess each image and its annotations in its group.
        """
        for index, (image, annotations, transform_index) in enumerate(
                zip(image_group, annotations_group, transform_indices)):
            # preprocess a single group entry
            image, annotations = self.preprocess_group_entry(image, annotations, transform_index)

            # copy processed data back to group
            image_group[index] = image
            annotations_group[index] = annotations

        return image_group, annotations_group

    def group_images(self):
        """ Order the images according to self.order and makes groups of self.batch_size.
        """
        # determine the order of the images
        order = list(range(self.size()))
        if self.group_method == 'random':
            random.shuffle(order)
        elif self.group_method == 'ratio':
            order.sort(key=lambda x: self.image_aspect_ratio(x))

        # divide into groups, one group = one batch
        self.groups = [[order[x % len(order)] for x in range(i, i + self.batch_size)] for i in
                       range(0, len(order), self.batch_size)]

    def compute_inputs(self, image_group):
        """ Compute inputs for the network using an image_group.
        """
        # get the max image shape
        max_shape = tuple(max(image.shape[x] for image in image_group) for x in range(3))

        # construct an image batch object
        image_batch = np.zeros((self.batch_size,) + max_shape, dtype=keras.backend.floatx())

        # copy all images to the upper left part of the image batch object
        for image_index, image in enumerate(image_group):
            image_batch[image_index, :image.shape[0], :image.shape[1], :image.shape[2]] = image

        return image_batch

    def generate_anchors(self, image_shape):
        return anchors_for_shape(image_shape, shapes_callback=self.compute_shapes)

    def compute_targets(self, image_group, annotations_group):
        """ Compute target outputs for the network using images and their annotations.
        """
        # get the max image shape
        max_shape = tuple(max(image.shape[x] for image in image_group) for x in range(3))
        anchors = self.generate_anchors(max_shape)

        if self.no_centers:
            labels_batch, regression_batch, _ = self.compute_anchor_targets(
                anchors,
                image_group,
                annotations_group,
                self.num_classes()
            )
            return [regression_batch, labels_batch]
        else:
            labels_batch, regression_batch, centers_batch, _ = self.compute_anchor_targets(
                anchors,
                image_group,
                annotations_group,
                self.num_classes()
            )

            return [regression_batch, labels_batch, centers_batch]

    def compute_input_output(self, group):
        """ Compute inputs and target outputs for the network.
        """
        # load images and annotations
        image_group = self.load_image_group(group)
        annotations_group = self.load_annotations_group(group)

        # check validity of annotations
        image_group, annotations_group = self.filter_annotations(image_group, annotations_group, group)

        # perform preprocessing steps
        transform_indices = self.load_transform_indices(group)
        image_group, annotations_group = self.preprocess_group(image_group, annotations_group, transform_indices)

        # compute network inputs
        inputs = self.compute_inputs(image_group)

        # compute network targets
        targets = self.compute_targets(image_group, annotations_group)

        return inputs, targets

    def __next__(self):
        return self.next()

    def next(self):
        # advance the group index
        with self.lock:
            if self.group_index == 0 and self.shuffle_groups:
                # shuffle groups at start of epoch
                random.shuffle(self.groups)
            group = self.groups[self.group_index]
            self.group_index = (self.group_index + 1) % len(self.groups)

        return self.compute_input_output(group)

    def parse_BCS(self, dataset_path, images_path):
        with open(dataset_path, "rb") as f:
            ds = pickle.load(f, encoding='latin-1', fix_imports=True)

        for i, entry in enumerate(ds):

            filename = os.path.join(images_path, entry['filename'])
            if self.split_exclusion_function is not None:
                if self.split_exclusion_function(filename):
                    continue

            if filename not in self.image_data:
                self.image_data[filename] = []
                self.image_names.append(filename)
                self.transform_indices.append(0)
            if self.no_centers:
                for label in entry['labels']:
                    dict = {'x1': label['x_min'], 'x2': label['x_max'],
                            'y1': label['y_min'], 'y2': label['y_max'],
                            'class': 'car'}
                    self.image_data[filename].append(dict)
            elif self.fake_centers:
                for label in entry['labels']:
                    dict = {'x1': label['x_min'], 'x2': label['x_max'],
                            'y1': label['y_min'], 'y2': label['y_max'],
                            'c': 0.0, 'class': 'car'}
                    self.image_data[filename].append(dict)
            else:
                for label in entry['labels']:
                    dict = {'x1': label['x_min'], 'x2': label['x_max'],
                            'y1': label['y_min'], 'y2': label['y_max'],
                            'c': label['centery'], 'class': 'car'}
                    self.image_data[filename].append(dict)

    def parse_BoxCars(self, dataset_path, images_path):
        with open(dataset_path, "rb") as f:
            ds = pickle.load(f, encoding='latin-1', fix_imports=True)
        for sample in ds['samples']:
            # to_camera = sample['to_camera']
            for i_id, instance in enumerate(sample['instances']):
                filename = os.path.join(images_path, instance['filename'])
                if filename not in self.image_data:
                    self.image_data[filename] = []
                    self.image_names.append(filename)
                    self.transform_indices.append(1)

                if self.no_centers:
                    dict = {'x1': instance['bb_out']['x_min'], 'x2': instance['bb_out']['x_max'],
                            'y1': instance['bb_out']['y_min'], 'y2': instance['bb_out']['y_max'],
                            'class': 'car'}
                else:
                    if self.fake_centers:
                        centery = 0.0
                    else:
                        centery = (instance['bb_in']['y_min'] - instance['bb_out']['y_min']) / \
                                  (instance['bb_out']['y_max'] - instance['bb_out']['y_min'])

                    dict = {'x1': instance['bb_out']['x_min'], 'x2': instance['bb_out']['x_max'],
                            'y1': instance['bb_out']['y_min'], 'y2': instance['bb_out']['y_max'],
                            'c': centery, 'class': 'car'}
                self.image_data[filename].append(dict)


from pathlib import Path # Pathlib - Working with file paths

p = Path('.')  # Creates a path object in found OS (Windows Path)

test = [x for x in p.iterdir() if x.is_dir()]
print(p.resolve())  # Show file dir in your OS format (D:\Backup\Work\DevOps\Programming\Scripts\Python\fundamentals\Built In Modules\pathlib)
new_p = p / 'Test dir'  # Navigating into Test dir folder
new_p.mkdir()  # Create folder at location p
for file_name in new_p.iterdir():
    if file_name.match('*.txt') or file_name.match('*.py'):  # Check for specific file types when iterating through files in path
        print(file_name)

new_p /= 'test.txt'
print(new_p)
with new_p.open() as f:
    print(f.readline())
    print(f.readline())


# -*- coding: utf-8 -*-
import numpy as np

from deap import algorithms
from deap import base
from deap import creator
from deap import tools
from deap import gp
from deap.algorithms import varAnd
from adan.aiem.genetics.evaluators import *
import array
import random as traditional_random

#import pathos

import pathos
import operator

#from adan import functions
from adan.functions import *
from adan.aidc.feature_selection import *

import time

def eaSimple_island(population,toolbox, cxpb, mutpb, ngen,halloffame=None,
                    verbose=__debug__,allowed_time=np.inf, stats=None,FREQ=None,
                    percentage_migration=0.1):

    """
    ngen is used both for the total generations and for the within island generatins.
    So, the total number of gens will be ngen**2.

    FREQ: How often migration takes place. If FREQ=None, then it is set to ngen/3
    """

    #FREQ is how often migration takes place
    if FREQ is None:
        FREQ=int(ngen/3)
        if FREQ<0:
            FREQ=1

    toolbox.register("algorithm", eaSimple_timed, toolbox=toolbox,
                     cxpb=cxpb, mutpb=mutpb, ngen=ngen,
                     verbose=verbose,stats=stats,halloffame=halloffame)

    islands = population
    #The GA runs each time for ngen, and then it runs for a total number of equal to ngen/FREQ

    for i in range(0, ngen):
        start = time.time()
        results = toolbox.map(toolbox.algorithm, islands)
        islands = [pop for pop, logbook in results]

        if i % FREQ ==0:
            print('******MIGRATION TAKING PLACE******')
            tools.migRing(islands, int(percentage_migration*len(islands[0])), tools.selBest)

        end = time.time()

        if (end-start)>allowed_time:
            if verbose:
                print('Time-out. Maximum allowed time exceeded.')
            break

    return islands

def eaSimple_timed(population, toolbox, cxpb, mutpb, ngen, stats=None,
             halloffame=None, verbose=__debug__,allowed_time=np.inf):
    """This is a copy of the eaSimple() method from DEAP, but adjusted
    to support time-out. In case of timeout, the most recent generation is
    returned.

    """
    logbook = tools.Logbook()
    logbook.header = ['gen', 'nevals'] + (stats.fields if stats else [])

    # Evaluate the individuals with an invalid fitness
    invalid_ind = [ind for ind in population if not ind.fitness.valid]
    fitnesses = toolbox.map(toolbox.evaluate, invalid_ind)
    for ind, fit in zip(invalid_ind, fitnesses):
        #-2 is the 'fail' value (e.g. the fitness function couldn't be computed)
        if fit is None:
            fit=(-2,)
        ind.fitness.values = fit

    if halloffame is not None:
        halloffame.update(population)

    record = stats.compile(population) if stats else {}
    logbook.record(gen=0, nevals=len(invalid_ind), **record)
    if verbose:
        print(logbook.stream)

    start = time.time()
    # Begin the generational process
    for gen in range(1, ngen+1):
        # Select the next generation individuals
        offspring = toolbox.select(population, len(population))

        # Vary the pool of individuals
        offspring = varAnd(offspring, toolbox, cxpb, mutpb)

        # Evaluate the individuals with an invalid fitness
        invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
        fitnesses = toolbox.map(toolbox.evaluate, invalid_ind)
        for ind, fit in zip(invalid_ind, fitnesses):
            if fit is None:
                fit=(-2,)
            ind.fitness.values = fit

        # Update the hall of fame with the generated individuals
        if halloffame is not None:
            halloffame.update(offspring)

        # Replace the current population by the offspring
        population[:] = offspring

        # Append the current generation statistics to the logbook
        record = stats.compile(population) if stats else {}
        logbook.record(gen=gen, nevals=len(invalid_ind), **record)
        if verbose:
            print(logbook.stream)

        end = time.time()

        if (end-start)>allowed_time:
            if verbose:
                print('Time-out. Maximum allowed time exceeded.')
            break

    return population, logbook

def calcNewFeatures(result_set,df,features='best'):
    """
    returns the best features alongside the variables participating in the complex variables
    """
    all_features=[]
    complex_features=[]
    pset=setPset(df)
    toolbox = base.Toolbox()
    toolbox.register("compile", gp.compile, pset=pset)

    complex_columns=[]
    all_columns=[]
    simple_columns=[]

    if features=='best':
        dummy='best_individuals_object'
    elif features=='all':
        dummy='all_features_individuals_object'

    for feat in result_set[dummy]:
        complex_features.append(toolbox.compile(feat))
        all_features.append(toolbox.compile(feat))
        complex_columns.append(str(feat))
        all_columns.append(str(feat))

    simple_features=[]

    for feat in result_set['variables']:
        simple_features.append(df[feat])
        simple_columns.append(str(feat))
        all_features.append(df[feat])
        all_columns.append(str(feat))

    return pd.DataFrame(np.column_stack(all_features),columns=all_columns),pd.DataFrame(np.column_stack(complex_features),columns=complex_columns),pd.DataFrame(np.column_stack(simple_features),columns=simple_columns)

def setPset(df):
    pset = gp.PrimitiveSet("MAIN", 0,prefix="coef")
    pset.addPrimitive(add,2)
    pset.addPrimitive(sub, 2)
    pset.addPrimitive(mul, 2)
    pset.addPrimitive(div, 2)

    for fun in singlefunctions:
        pset.addPrimitive(fun,1)

    for col in df.columns.values:
        #we must use strings for column names otherwise the functions interpret the
    #column names as numbers
        pset.addTerminal(df[col].values,name=col)

    return pset

def findFeaturesGP(df,target,population=300,ngen=50,cxpb=0.9,features=-1,
                   max_tree=3,evaluator=evalPearsonCorNumba,
                   task="regression",n_processes=1,allowed_time=None,target_sampling=0.8):

    """
    This function calculates complex features that correlate with the response variable.
    Output:

    A dictionary with the following fields:

    best_features: a list of lists, where every element is a feature selected by the best n features as defined by the cbf method
    best_features_plus_cols: a list of lists, where every element is a feature selected by the best n features as defined by the cbf method plus
    any original features participating in the creation of the individuals
    best_individuals_equations: the equations used to compute the best_features (this is the string version of best_individuals_object)
    best_individuals_plus_columns: like the previous, plus the column names of the individual features
    best_individuals_object: the programs used to compute the best_features

    scores: the score of each individual produced during the genetic programming
    scores_cbf: the cbf score of each feature (all features not just the best ones)
    variables: the names of the original variables that participate in the creation of the features in the best_features
    all_features: a list of lists with all the features produced by the genetic algorithm
    all_features_individuals: the programs used to compute all_features

    features: if features<1, then the algorithm simply defaults to 1
    target_sampling: When the features are evaluated, we can sample a % of the targets, and evaluate
    the performace on this subset. This should help with overfitting and finding better solutions.

    """

    if features<1:
        features=1

    if task=='regression' and evaluator==None:
        evaluator=evalPearsonCorNumba
    elif task=='classification' and evaluator==None:
        evaluator=evalANOVANumba

    mutpb=1-cxpb

    # for col in df.columns:
    #     df[col]=df[col].astype('float64')

    pset=setPset(df)

    creator.create("FitnessMax", base.Fitness, weights=(1,))
    creator.create("Individual", gp.PrimitiveTree, fitness=creator.FitnessMax)

    toolbox = base.Toolbox()

    toolbox.register("expr", gp.genHalfAndHalf, pset=pset, min_=1, max_=max_tree)
    toolbox.register("individual", tools.initIterate, creator.Individual, toolbox.expr)
    toolbox.register("population", tools.initRepeat, list, toolbox.individual)
    toolbox.register("compile", gp.compile, pset=pset)

    #need to do that because multithreading does not support functions with more than one arguments
    def evaluate(x):
        return evaluator(x,toolbox=toolbox,targets=target,sampling=target_sampling)

    #toolbox.register("evaluate", evaluator,toolbox=toolbox, targets=targets)
    toolbox.register("evaluate", evaluate)

    #toolbox.register("select", tools.selTournament, tournsize=3)
    #toolbox.register("select", tools.selNSGA2)
    toolbox.register("select", tools.selDoubleTournament,fitness_size=3,parsimony_size=1.4,fitness_first=True)
    toolbox.register("mate", gp.cxOnePoint)
    toolbox.register("expr_mut", gp.genFull, min_=0, max_=max_tree)
    toolbox.register("mutate", gp.mutUniform, expr=toolbox.expr_mut, pset=pset)

    toolbox.decorate("mate", gp.staticLimit(key=operator.attrgetter("height"), max_value=max_tree))
    toolbox.decorate("mutate", gp.staticLimit(key=operator.attrgetter("height"), max_value=max_tree))

    if type(population)==type([]):
        toolbox.register("deme", tools.initRepeat, list, toolbox.individual)
        DEME_SIZES = population
        pop = [toolbox.deme(n=i) for i in DEME_SIZES]
        hof = tools.HallOfFame(sum(population))
    else:

        pop = toolbox.population(n=population)
        hof = tools.HallOfFame(population)

    stats_fit = tools.Statistics(lambda ind: ind.fitness.values)
    stats_size = tools.Statistics(len)
    mstats = tools.MultiStatistics(fitness=stats_fit, size=stats_size)
    mstats.register("avg", np.mean)
    mstats.register("std", np.std)
    mstats.register("min", np.min)
    mstats.register("max", np.max)

    if n_processes>1:
        pool = pathos.multiprocessing.ProcessingPool(n_processes)
        toolbox.register("map", pool.map)

#    pop, log = algorithms.eaMuPlusLambda(pop, toolbox, mu,lamb, cxpb,mutpb,ngen=ngen, stats=mstats,
#                                   halloffame=hof, verbose=True)
#    if allowed_time is None:
#        pop, log = algorithms.eaSimple(pop, toolbox, cxpb,mutpb, ngen=ngen, stats=mstats,
#                                       halloffame=hof, verbose=True)
    if type(population)==type([]):
        pop = eaSimple_island(pop, toolbox, cxpb,mutpb, ngen=ngen, stats=mstats,
                                       halloffame=hof, verbose=True, allowed_time=allowed_time)
    else:
        pop, log = eaSimple_timed(pop, toolbox, cxpb,mutpb, ngen=ngen, stats=mstats,
                                       halloffame=hof, verbose=True, allowed_time=allowed_time)

    allfeatures=[]
    allfeatures_individuals_object=[]
    scores=[]
    feature_names=[]
    best_individuals_object=[]
    for i in range(0,len(hof.items)):
        # print(hof.items[i])
        feature=toolbox.compile(hof.items[i])

        if not np.isnan(feature).any():
            #need to guard against zero variance features
            if np.var(feature)>0.0:
                allfeatures.append(feature)
                allfeatures_individuals_object.append(hof.items[i])
                feature_names.append(str(hof.items[i]))
                best_individuals_object.append(hof.items[i])
                #for some reason in DEAP the key in the hall-of-fame is the score

#    if features>0:
#        cbfscores=cbfSelectionNumba(allfeatures,target,task=task)
#        bestindices=sorted(range(len(cbfscores)), key=lambda x: cbfscores[x],reverse=True)
#    else:
#        cbfscores=np.ones(len(allfeatures))
#        bestindices=range(len(allfeatures))

    cbfscores=cbfSelectionNumba(allfeatures,target,task=task)
    bestindices=sorted(range(len(cbfscores)), key=lambda x: cbfscores[x],reverse=True)

    bestfeatures=[]
    bestindividuals=[]
    bestindividuals_plus_cols=[]
    scorescbf=[]
    best_features_plus_cols=[]
    best_individuals_object_final=[]
    for i  in range(0,int(features)):
        index=bestindices[i]
        bestfeatures.append(allfeatures[index])
        best_features_plus_cols.append(allfeatures[index])

        bestindividuals.append(feature_names[index])
        bestindividuals_plus_cols.append(feature_names[index])
        best_individuals_object_final.append(best_individuals_object[i])

        # scores.append(eval(str(hof.keys[index])))
        # scorescbf.append(cbfscores[index])

    #all features includes the best variables, plus any single variables which might participate in the creation of the complex variables
    final_vars=[]
    str_individuals=str(bestindividuals)
    for col in df.columns:
        if str_individuals.find(col)>-1:
            final_vars.append(col)
            #append the original variable to bestfeatures if it exists in a complex feature
            best_features_plus_cols.append(df[col].values)
            bestindividuals_plus_cols.append(col)

    #combine all features (individual and composite) into one df
    best_all_feats_df=pd.DataFrame(np.column_stack(best_features_plus_cols),columns=bestindividuals_plus_cols)

    return {'best_features':bestfeatures,'best_features_plus_cols':best_features_plus_cols,
            'best_individuals_equations':bestindividuals,'best_individuals_object':best_individuals_object_final,
    'scores':scores,'scores_cbf':scorescbf,'variables':final_vars,
    'all_features':allfeatures,'all_features_individuals_object':allfeatures_individuals_object,'best_all_feats_df':best_all_feats_df}

def findEquationFeatures(features_to_be_used,task,target,ngen=10,population=10,crossover_prob=0.5,mut_prob=0.1,individual_mut=0.1,tournsize=3):
    """
    Performs feature selection over the set of features before doing the
    symbolic modelling

    individual_mut: If a mutation occurs, then each item might be flipped according to this probability
    """

    creator.create("FitnessMax", base.Fitness, weights=(1.0,))
    import array
    creator.create("Individual", array.array, typecode='b', fitness=creator.FitnessMax)

    toolbox = base.Toolbox()

    # Attribute generator
    toolbox.register("attr_bool", traditional_random.getrandbits,1)

    # Structure initializers
    toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_bool, features_to_be_used.shape[1])
    toolbox.register("population", tools.initRepeat, list, toolbox.individual)

    #we import here to avoid a cyclical import
    from adan.aiem.symbolic_modelling import findSymbolicExpressionL1_regression_helper, findSymbolicExpressionL1_classification_helper

    def evalOneMax(individual):
        if sum(individual)==0:
            return -100,
        else:
            ind=np.array(individual,bool)
        if task=='regression':
            models=findSymbolicExpressionL1_regression_helper(features_to_be_used.loc[:,ind].values,target)
        elif task=='classification':
            models=findSymbolicExpressionL1_classification_helper(features_to_be_used.loc[:,ind].values,target)

        performances=[perf[1] for perf in models]
        maximum=max(performances)

        return maximum,

    toolbox.register("evaluate", evalOneMax)
    toolbox.register("mate", tools.cxTwoPoint)
    toolbox.register("mutate", tools.mutFlipBit, indpb=individual_mut)
    toolbox.register("select", tools.selTournament, tournsize=tournsize)

    pop = toolbox.population(n=population)
    hof = tools.HallOfFame(1)
    stats = tools.Statistics(lambda ind: ind.fitness.values)
    stats.register("avg", np.mean)
    stats.register("std", np.std)
    stats.register("min", np.min)
    stats.register("max", np.max)

    pop, log = algorithms.eaSimple(pop, toolbox, cxpb=crossover_prob,
                                   mutpb=mut_prob, ngen=ngen,
                                   stats=stats, halloffame=hof, verbose=True)
    final_choice=hof.items[0]
    final_choice=np.array(final_choice,bool)

    #return pop, log, hof
    return final_choice


#!/usr/bin/env python3
'''conda create -n pytorch-env python=3.9 shap pandas optuna=2.10.1 xgboost scikit-learn sklearn-pandas rdkit pytorch torchvision torchaudio pytorch-cuda=11.6 cairosvg dgllife dgl=0.9.1 dgl-cuda11.6 ipython -c pytorch -c nvidia -c dglteam'''
import pandas as pd
import numpy as np
import datetime,time,joblib
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, MinMaxScaler
from sklearn.preprocessing import OrdinalEncoder, OneHotEncoder
from sklearn_pandas import DataFrameMapper

###############  Set required parameters and load data here  ###############
'''basic parameters'''
filename_pkl = 'HistGradientBoosting_Optuna_best'  # load target model from the *.pkl file
split_dataset = False  # whether to split the dataset into training and test sets
model_name = "HistGradientBoostingClassifier"
#  Supported models are as follows:
#    (1) AdaBoostRegressor / AdaBoostClassifier
#    (2) XGBRegressor / XGBClassifier
#    (3) GradientBoostingRegressor / GradientBoostingClassifier
#    (4) HistGradientBoostingRegressor / HistGradientBoostingClassifier
#    (5) RandomForestRegressor / RandomForestClassifier
#    (6) SVR / SVC
#    (7) MLPRegressor / MLPClassifier
#    (8) ElasticNet / LogisticRegression

'''load the dataset'''
selected_features = ['MolWt','NumRotatableBonds','AromaticProportion']
df = pd.read_csv('../../MolLogP_dataset.csv')
data_X = df[selected_features]
data_y = df['MolLogP<2']

# print(data_y)
# exit()

###############  Some user-defined functions  ###############
def total_running_time(end_time, start_time):
    tot_seconds = round(end_time - start_time,2)
    days = tot_seconds // 86400
    hours = (tot_seconds % 86400) // 3600
    minutes = (tot_seconds % 86400 % 3600)// 60
    seconds = tot_seconds % 60
    print(">> Elapsed time: {0:2d} day(s) {1:2d} hour(s) {2:2d} minute(s) {3:5.2f} second(s) <<".format(int(days),int(hours),int(minutes),seconds))

def load_model(model_name, filename_pkl):
    ML_regression_list = ["XGBRegressor", "AdaBoostRegressor", "GradientBoostingRegressor",
                          "HistGradientBoostingRegressor", "MLPRegressor",
                          "RandomForestRegressor", "SVR", "ElasticNet"]
    ML_classification_list = ["XGBClassifier", "AdaBoostClassifier", "GradientBoostingClassifier",
                              "HistGradientBoostingClassifier", "MLPClassifier",
                              "RandomForestClassifier", "SVC", "LogisticRegression"
                              ]

    if model_name in ML_regression_list:
        ML_type = "Regression"
    elif model_name in ML_classification_list:
        ML_type = "Classification"

    if model_name == "XGBRegressor":
        from xgboost import XGBRegressor
    elif model_name == "XGBClassifier":
        from xgboost import XGBClassifier
    elif model_name == "AdaBoostRegressor":
        from sklearn.ensemble import AdaBoostRegressor
    elif model_name == "AdaBoostClassifier":
        from sklearn.ensemble import AdaBoostClassifier
    elif model_name == "GradientBoostingRegressor":
        from sklearn.ensemble import GradientBoostingRegressor
    elif model_name == "GradientBoostingClassifier":
        from sklearn.ensemble import GradientBoostingClassifier
    elif model_name == "HistGradientBoostingRegressor":
        from sklearn.ensemble import HistGradientBoostingRegressor
    elif model_name == "HistGradientBoostingClassifier":
        from sklearn.ensemble import HistGradientBoostingClassifier
    elif model_name == "MLPRegressor":
        from sklearn.neural_network import MLPRegressor
    elif model_name == "MLPClassifier":
        from sklearn.neural_network import MLPClassifier
    elif model_name == "RandomForestRegressor":
        from sklearn.ensemble import RandomForestRegressor
    elif model_name == "RandomForestClassifier":
        from sklearn.ensemble import RandomForestClassifier
    elif model_name == "SVR":
        from sklearn.svm import SVR
    elif model_name == "SVC":
        from sklearn.svm import SVC
    elif model_name == "ElasticNet":
        from sklearn.linear_model import ElasticNet
    elif model_name == "LogisticRegression":
        from sklearn.linear_model import LogisticRegression
    else:
        print('** Please rechoose a model **\n-> Supported models are as follows:')
        print('   (1) AdaBoostRegressor / AdaBoostClassifier\n   (2) XGBRegressor / XGBClassifier')
        print('   (3) GradientBoostingRegressor / GradientBoostingClassifier\n   (4) HistGradientBoostingRegressor / HistGradientBoostingClassifier')
        print('   (5) RandomForestRegressor / RandomForestClassifier\n   (6) SVR / SVC')
        print('   (7) MLPRegressor / MLPClassifier\n   (8) ElasticNet / LogisticRegression')
        exit(1)

    model = joblib.load(filename_pkl + ".pkl")
    print('---------- Results based on the current loaded model ----------')
    print('> Current parameters:\n {}\n'.format(model.get_params()))

    return model, ML_type

def show_metrics(model, ML_type, y_test_pred, y_test_pred_proba, y_test, X_test):
    print("            >>>>  Metrics based on the best model  <<<<\n")
    if ML_type == "Classification":
        from sklearn.metrics import accuracy_score, classification_report, roc_auc_score, average_precision_score
        accuracy_test = accuracy_score(y_test, y_test_pred)
        print('> Accuracy on the test set:  {:.2%}'.format(accuracy_test))
        print('> Score on the test set:  {:.2%}'.format(model.score(X_test, y_test)))
        print('> Classification report on the test set:')
        print(classification_report(y_test, y_test_pred))

        roc_auc_test, average_precision_test = [], []
        for i in range(len(set(y_test))):
            roc_auc_test.append(roc_auc_score(y_test, y_test_pred_proba[:,i], multi_class='ovr'))
            average_precision_test.append(average_precision_score(y_test, y_test_pred_proba[:,i]))
        pd.set_option('display.float_format','{:12.6f}'.format)
        pd.set_option('display.colheader_justify', 'center')
        test_reports = pd.DataFrame(np.vstack((roc_auc_test, average_precision_test)).T, columns=['ROC-AUC','AP(PR-AUC)'])
        print('> Area under the receiver operating characteristic curve (ROC-AUC) and\n  average precision (AP) which summarizes a precision-recall curve as the weighted mean\n  of precisions achieved at each threshold on the test set:\n  {}\n'.format(test_reports))

    elif ML_type == "Regression":
        from sklearn.metrics import mean_squared_error, mean_absolute_error
        mse_test = mean_squared_error(y_test, y_test_pred)
        mae_test = mean_absolute_error(y_test, y_test_pred)
        print('> Mean squared error (MSE) on the test set:  {:.6f}'.format(mse_test))
        print('> Mean absolute error (MAE) on the test set:  {:.6f}'.format(mae_test))
        print('> R-squared (R^2) value on the test set:  {:.6f}\n'.format(model.score(X_test, y_test)))

###############  The ML training script starts from here  ###############
start_time = time.time()
start_date = datetime.datetime.now()
print('***  Scikit-learn evaluation ({0}) started at {1}  ***\n'.format(model_name, start_date.strftime("%Y-%m-%d %H:%M:%S")))

'''split training/test sets'''
if split_dataset:
    print('The dataset is splited into training and test sets, and therefore the target model will be evaluated on the test set...\n')
    X_train, X_test, y_train, y_test = train_test_split(data_X, data_y, test_size=0.2, random_state=0)
else:
    print('The whole dataset will be used to evaluate the target model...\n')
    X_test, y_test = data_X, data_y

target_model, ML_type = load_model(model_name, filename_pkl)
y_test_pred = target_model.predict(X_test)
y_test_pred_proba = target_model.predict_proba(X_test) if ML_type == "Classification" else None
show_metrics(target_model, ML_type, y_test_pred, y_test_pred_proba, y_test, X_test)

end_time = time.time()
end_date = datetime.datetime.now()
print('***  Scikit-learn evaluation ({0}) terminated at {1}  ***\n'.format(model_name, end_date.strftime("%Y-%m-%d %H:%M:%S")))
total_running_time(end_time, start_time)