import numpy as np
import cv2
import os
import datetime

class KNN_FM:

    def __init__(self, f1, kp1, f2, kp2, good):
        self.f1 = f1
        self.f2 = f2
        self.kp1 = kp1
        self.kp2 = kp2
        self.good = good

    def score(self):
        return len(self.good)

    def save_img(self, img_path, img_name):
        i = cv2.drawMatchesKnn(self.f1, self.kp1, \
                self.f2, self.kp2, self.good, None, flags=2)
        save_img(img_path, img_name +"_"+ str(self.score()) + ".png", i)

    def set_info(self, video_1, idx_1, video_2, idx_2):
        self.idx_1 = idx_1
        self.idx_2 = idx_2
        self.video_1 = video_1
        self.video_2 = video_2

    def p(self, s):
        s = s.split("/")
        s = "-".join(s[-2:])
        s = s.replace("/", "-")
        s = s.replace(".mp4", "")
        return s

    def get_title(self):
        return "_".join([
            self.p(self.video_1),
            self.p(self.video_2),
        ])

def save_img(img_path, img_name, image):
    """
    @param image: frame of video
    """
    cv2.imwrite(os.path.join(img_path, img_name), image)

def save_cmp_log(cmp_iter):
    i = 0
    for c in cmp_iter:
        if i == 0:
            o = open("result/"+c.get_title() + ".txt", "w")
        o.write("\t".join([
            str(c.idx_1),
            str(c.idx_2),
            str(c.score()),
        ]) + "\n")
        i += 1

    o.close()

def compare_video(video_1, video_2, cmp_func):
    idx_1 = -1

    cap_1 = cv2.VideoCapture(video_1)
    while(cap_1.isOpened()):
        idx_1 += 1
        idx_2 = 0
        ret_1, frame_1 = cap_1.read()

        if ret_1:
            cap_2 = cv2.VideoCapture(video_2)
            while(cap_2.isOpened()):
                idx_2 += 1
                ret_2, frame_2 = cap_2.read()

                if ret_2:
                    if (idx_1 % 30) == 0 and (idx_2 % 30) == 0:
                        knn_fm = cmp_func(frame_1, frame_2)
                        knn_fm.set_info(video_1, idx_1, video_2, idx_2)
                        yield(knn_fm)
                else:
                    break
        else:
            break

    cap_1.release()
    cap_2.release()

def compare_fm(frame_1, frame_2):
    sift = sift=cv2.xfeatures2d.SIFT_create()
    kp1, des1 = sift.detectAndCompute(frame_1, None)
    kp2, des2 = sift.detectAndCompute(frame_2, None)

    bf = cv2.BFMatcher()
    matches = bf.knnMatch(des1, des2, k=2)

    good = []
    for m,n in matches:
        if m.distance < 0.75 * n.distance:
            good.append([m])

    return KNN_FM(frame_1, kp1, frame_2, kp2, good)

def camp(c, cv):
    return "video/camp/" + c + "/" + cv

def news(n, nv):
    return "video/news/" + n + "/" + nv

if __name__ == "__main__":
    camp_list = ["ssj"]
    news_list = ["mbc", "sbs", "kbs"]

    now = str(datetime.datetime.now())
    err = open("error_{}.txt".format(now), "w")
    acc = open("access_{}.txt".format(now), "w")

    for c in camp_list:
        for cv in os.listdir("video/camp/" + c):
            for n in news_list:
                for nv in os.listdir("video/news/" + n):
                    try:
                        if cv.startswith(".") or nv.startswith("."):
                            continue

                        log = "\t".join([
                            cv,
                            nv,
                        ]) + "\n"
                        acc.write(log)
                        print(log)
                        cmp_iter = compare_video(camp(c, cv), news(n, nv), compare_fm)
                        save_cmp_log(cmp_iter)
                    except:
                        err.write("\t".join([
                            cv,
                            nv,
                        ]) + "\n")
    err.close()
    acc.close()


class Hewan:
    nama_latin = 'Anggora' #class atributes

     #instance atributes
    def __init__(self, name, age):
        self.name = name
        self.age = age

    #instance method
    def bangun(self):
        print("Nama hewan : {} \nUmur : {} tahun ".format(
            self.name,
            self.age,

        ))

    #class method
    def changeNamaLatin(self):

        print ("Nama latin : " +self.nama_latin)

class Kucing(Hewan):
    kecepatan = 5

    #instance attribute
    def __init__(self,name,age, warna, asal):
        super().__init__(name,age)
        self.warna = warna
        self.asal = asal

    #instance method
    def bangun(self):

       print("Nama hewan : {} \nUmur : {} tahun \nWarna : {} \nAsal : {} ".format(
            self.name,
            self.age,
            self.warna,
            self.asal

        ))
    #class method
    def changeNamaLatin(self, nama_latin = 'Felis Catus'):
        print("Nama Latin : ",nama_latin)

    def lari(self):
        kecepatan = getattr(Kucing, 'kecepatan')

        print('Kecepatan lari : {} Km/jam'.format(self.kecepatan))

        if  kecepatan > 10   :
            print('Kategori : cepat sekali')
        else:
            print('Kategori : lambat')

cat1 = Kucing('Ciko', 2, 'Putih', 'Amerika')
cat1.bangun()
cat1.changeNamaLatin()
cat1.lari()


from fastapi import APIRouter, Depends
from core import notification_controller
from core.base import mssg_response
from fastapi.security import HTTPBearer
from fastapi_jwt_auth import AuthJWT

dbn = notification_controller.DBNotificationController()
router = APIRouter()

# need for docs auth button
token_auth_scheme = HTTPBearer()

@router.post('/notify/email', tags=['notification'])
def send_email(
        reply_to: str,
        attach: str,
        subject: str = 'Event between <user> and <user>',
        message: str = '<user> requested you for meeting, details:',
        Authorize: AuthJWT = Depends(), token=Depends(token_auth_scheme)
    ):
    Authorize.jwt_required()
    return mssg_response(dbn.send_email(reply_to, subject, message, attach))


# coding: utf-8
"""
Logic module handling communication with other components in a SampleDB federation
"""
import typing
import datetime
import io

import requests
import flask

from .utils import _get_dict, _get_list, _get_bool
from .users import import_user, parse_user
from .components import import_component_info, parse_component_info
from .location_types import import_location_type, parse_location_type
from .instruments import import_instrument, parse_instrument
from .action_types import import_action_type, parse_action_type
from .locations import import_location, parse_location, locations_check_for_cyclic_dependencies
from .markdown_images import parse_markdown_image, import_markdown_image
from .actions import import_action, parse_action
from .objects import import_object, parse_object
from ..components import Component, set_component_discoverable
from ..component_authentication import get_own_authentication
from .. import errors
from ...models import ComponentAuthenticationType

PROTOCOL_VERSION_MAJOR = 0
PROTOCOL_VERSION_MINOR = 1

FEDERATION_TIMEOUT = 60

def _send_request(
        method: typing.Union[typing.Literal['get'], typing.Literal['post'], typing.Literal['put']],
        endpoint: str,
        component: Component,
        headers: typing.Optional[typing.Dict[str, str]] = None,
        **kwargs: typing.Any
) -> requests.Response:
    if component.address is None:
        raise errors.MissingComponentAddressError()
    if headers is None:
        headers = {}
    auth = get_own_authentication(component.id, ComponentAuthenticationType.TOKEN)

    if auth:
        headers['Authorization'] = 'Bearer ' + auth.login['token']

    url = component.address.rstrip('/') + endpoint

    method_callable = {
        'get': requests.get,
        'post': requests.post,
        'put': requests.put,
    }[method]
    return method_callable(  # type: ignore
        url,
        headers=headers,
        timeout=FEDERATION_TIMEOUT,
        **kwargs
    )

def post(
        endpoint: str,
        component: Component,
        payload: typing.Optional[typing.Dict[str, typing.Any]] = None,
        headers: typing.Optional[typing.Dict[str, str]] = None
) -> None:
    _send_request('post', endpoint, component, headers, data=payload)

def put(
        endpoint: str,
        component: Component,
        headers: typing.Optional[typing.Dict[str, str]] = None,
        **kwargs: typing.Any
) -> None:
    _send_request('put', endpoint, component, headers, **kwargs)

def get_binary(
    endpoint: str,
    component: Component,
    headers: typing.Optional[typing.Dict[str, str]] = None
) -> typing.BinaryIO:
    response = _send_request('get', endpoint, component, headers)
    if response.status_code == 401:
        # 401 Unauthorized
        raise errors.UnauthorizedRequestError()
    if response.status_code in [500, 501, 502, 503, 504]:
        raise errors.RequestServerError()
    if response.status_code != 200:
        raise errors.RequestError()
    return io.BytesIO(response.content)

def get(
        endpoint: str,
        component: Component,
        headers: typing.Optional[typing.Dict[str, str]] = None,
        *,
        ignore_last_sync_time: bool = False
) -> typing.Dict[str, typing.Any]:
    if component.address is None:
        raise errors.MissingComponentAddressError()
    if headers is None:
        headers = {}
    auth = get_own_authentication(component.id, ComponentAuthenticationType.TOKEN)

    if auth:
        headers['Authorization'] = 'Bearer ' + auth.login['token']

    parameters = {}
    if component.last_sync_timestamp is not None and not ignore_last_sync_time:
        parameters['last_sync_timestamp'] = component.last_sync_timestamp.strftime('%Y-%m-%d %H:%M:%S.%f')
    req = requests.get(
        component.address.rstrip('/') + endpoint,
        headers=headers,
        params=parameters,
        timeout=FEDERATION_TIMEOUT
    )
    if req.status_code == 401:
        # 401 Unauthorized
        raise errors.UnauthorizedRequestError()
    if req.status_code in [500, 501, 502, 503, 504]:
        raise errors.RequestServerError()
    if req.status_code != 200:
        raise errors.RequestError()
    try:
        return req.json()  # type: ignore
    except ValueError:
        raise errors.InvalidJSONError()

def update_poke_component(
        component: Component
) -> None:
    post('/federation/v1/hooks/update/', component)

def _validate_header(
        header: typing.Optional[typing.Dict[str, typing.Any]],
        component: Component
) -> None:
    if header is not None:
        if header.get('db_uuid') != component.uuid:
            raise errors.InvalidDataExportError(f'UUID of exporting database ({header.get("db_uuid")}) does not match expected UUID ({component.uuid}).')
        protocol_version = header.get('protocol_version')
        if protocol_version is not None:
            if 'major' not in protocol_version or 'minor' not in protocol_version:
                raise errors.InvalidDataExportError(f'Invalid protocol version \'{header.get("protocol_version")}\'')
            try:
                major, minor = int(protocol_version['major']), int(protocol_version['minor'])
                if major > PROTOCOL_VERSION_MAJOR or (major <= PROTOCOL_VERSION_MAJOR and minor > PROTOCOL_VERSION_MINOR):
                    raise errors.InvalidDataExportError(f'Unsupported protocol version {header.get("protocol_version")}')
            except ValueError:
                raise errors.InvalidDataExportError(f'Invalid protocol version {header.get("protocol_version")}')
        else:
            raise errors.InvalidDataExportError('Missing protocol_version.')

def import_updates(
        component: Component,
        *,
        ignore_last_sync_time: bool = False
) -> None:
    if flask.current_app.config['FEDERATION_UUID'] is None:
        raise errors.ComponentNotConfiguredForFederationError()
    timestamp = datetime.datetime.now(datetime.timezone.utc)
    components = None
    try:
        components = get('/federation/v1/shares/components/', component, ignore_last_sync_time=ignore_last_sync_time)
    except errors.InvalidJSONError:
        raise errors.InvalidDataExportError('Received an invalid JSON string.')
    except errors.RequestServerError:
        pass
    except errors.UnauthorizedRequestError:
        pass
    if components:
        update_components(component, components)
    users = None
    try:
        users = get('/federation/v1/shares/users/', component, ignore_last_sync_time=ignore_last_sync_time)
    except errors.InvalidJSONError:
        raise errors.InvalidDataExportError('Received an invalid JSON string.')
    except errors.RequestServerError:
        pass
    except errors.UnauthorizedRequestError:
        pass
    if users:
        update_users(component, users)
    try:
        updates = get('/federation/v1/shares/objects/', component, ignore_last_sync_time=ignore_last_sync_time)
    except errors.InvalidJSONError:
        raise errors.InvalidDataExportError('Received an invalid JSON string.')
    if updates:
        update_shares(component, updates)
    component.update_last_sync_timestamp(timestamp)

def update_components(
        component: Component,
        updates: typing.Dict[str, typing.Any]
) -> None:
    _get_dict(updates, mandatory=True)
    _validate_header(updates.get('header'), component)
    discoverable = _get_bool(updates.get('discoverable'), mandatory=True)
    set_component_discoverable(component.id, discoverable=discoverable)
    if discoverable:
        components = []
        component_data_list = _get_list(updates.get('components'), default=[])
        for component_data in component_data_list:
            components.append(parse_component_info(component_data, component))
        for component_data in components:
            if flask.current_app.config['FEDERATION_UUID'] in (component_data['uuid'], component_data['source_uuid']):
                # skip component info from or about this instance
                continue
            import_component_info(component_data, component)

def update_users(
        component: Component,
        updates: typing.Dict[str, typing.Any]
) -> None:
    _get_dict(updates, mandatory=True)
    _validate_header(updates.get('header'), component)

    users = []
    user_data_list = _get_list(updates.get('users'), default=[])
    for user_data in user_data_list:
        users.append(parse_user(user_data, component))
    for user_data in users:
        import_user(user_data, component)

def update_shares(
        component: Component,
        updates: typing.Dict[str, typing.Any]
) -> None:
    # parse and validate
    _get_dict(updates, mandatory=True)
    _validate_header(updates.get('header'), component)

    markdown_images = []
    markdown_images_data_dict = _get_dict(updates.get('markdown_images'), default={})
    for markdown_image_data in markdown_images_data_dict.items():
        markdown_images.append(parse_markdown_image(markdown_image_data, component))

    users = []
    user_data_list = _get_list(updates.get('users'), default=[])
    for user_data in user_data_list:
        users.append(parse_user(user_data, component))

    instruments = []
    instrument_data_list = _get_list(updates.get('instruments'), default=[])
    for instrument_data in instrument_data_list:
        instruments.append(parse_instrument(instrument_data))

    action_types = []
    action_type_data_list = _get_list(updates.get('action_types'), default=[])
    for action_type_data in action_type_data_list:
        action_types.append(parse_action_type(action_type_data))

    actions = []
    action_data_list = _get_list(updates.get('actions'), default=[])
    for action_data in action_data_list:
        actions.append(parse_action(action_data))

    location_types = []
    location_type_data_list = _get_list(updates.get('location_types'), default=[])
    for location_type_data in location_type_data_list:
        location_types.append(parse_location_type(location_type_data))

    locations = {}
    location_data_list = _get_list(updates.get('locations'), default=[])
    for location_data in location_data_list:
        location = parse_location(location_data)
        locations[(location['fed_id'], location['component_uuid'])] = location

    locations_check_for_cyclic_dependencies(locations)

    objects = []
    object_data_list = _get_list(updates.get('objects'), default=[])
    for object_data in object_data_list:
        objects.append(parse_object(object_data, component))

    # apply
    for markdown_image_data in markdown_images:
        import_markdown_image(markdown_image_data, component)

    for user_data in users:
        import_user(user_data, component)

    for instrument_data in instruments:
        import_instrument(instrument_data, component)

    for action_type_data in action_types:
        import_action_type(action_type_data, component)

    for action_data in actions:
        import_action(action_data, component)

    for location_type_data in location_types:
        import_location_type(location_type_data, component)

    while len(locations) > 0:
        key = list(locations)[0]
        import_location(locations[key], component, locations, users)
        if key in locations:
            locations.pop(key)

    import_status_by_object_id = {}
    for object_data in objects:
        import_status: typing.Dict[str, typing.Any] = {}
        import_object(object_data, component, import_status=import_status)
        import_status_by_object_id[object_data['fed_object_id']] = import_status

    if component.address:
        for object_id, import_status in import_status_by_object_id.items():
            if import_status:
                send_object_share_import_status(
                    object_id=object_id,
                    component=component,
                    import_status=import_status
                )

def send_object_share_import_status(
        object_id: int,
        component: Component,
        import_status: typing.Dict[str, typing.Any]
) -> None:
    put(
        endpoint=f'/federation/v1/shares/objects/{object_id}/import_status',
        component=component,
        json=import_status
    )


#! /usr/bin/env python

import os.path
import numpy as np
import math
import cmath
import spec_pkg.GBOM.franck_condon_response as franck_condon_response
import spec_pkg.GBOM.gbom_ensemble_response as gbom_ensemble_response
import spec_pkg.GBOM.gbom_cumulant_response as gbom_cumulant_response
from spec_pkg.constants import constants as const

# Global variable definition

#--------------------------------------------------------------------------
# Function definitions
def get_sd_skew(freqs_gs,Omega_sq,gamma,kbT,is_cl):
	# compute 2nd order corr func for t=0 and
	# third order corr func for t1=t2=0. These
	# values are related to skew and sd
	skew=gbom_cumulant_response.skew_from_third_order_corr(freqs_gs, Omega_sq, gamma, kbT,is_cl)
	sd=0.0
	if is_cl:
  		sd=gbom_cumulant_response.sd_from_2nd_order_corr_cl(freqs_gs, Omega_sq, gamma, kbT)
	else:
                sd=gbom_cumulant_response.sd_from_2nd_order_corr_qm(freqs_gs, Omega_sq, gamma, kbT)

	return sd,skew/(sd**3.0)

def get_lambda_0(Kmat,Jmat,omega_e_sq):
	Jtrans=np.transpose(Jmat)
	Ktrans=np.transpose(Kmat)
	temp_mat=np.dot(Ktrans,Jmat)
	temp_mat2=np.dot(Jtrans,Kmat)
	temp_mat3=np.dot(omega_e_sq,temp_mat2)
	return np.dot(temp_mat,temp_mat3)*0.5

def get_omega_sq(freqs):
	omega_mat=np.zeros((freqs.shape[0],freqs.shape[0]))
	counter=0
	while counter<freqs.shape[0]:
		omega_mat[counter,counter]=freqs[counter]**2.0
		counter=counter+1
	return omega_mat

def get_gamma(Kmat, Jmat,omega_e_sq):
	Jtrans=np.transpose(Jmat)
	Ktrans=np.transpose(Kmat)
	temp_mat=np.dot(Ktrans,Jmat)
	temp_mat2=np.dot(omega_e_sq,Jtrans)
	return np.dot(temp_mat,temp_mat2)

def get_full_Omega_sq(Jmat,omega_e_sq,freqs_gs):
	Jtrans=np.transpose(Jmat)
	temp_mat=np.dot(omega_e_sq,Jtrans)
	return_mat=0.5*np.dot(Jmat,temp_mat)
	counter=0
	while counter<return_mat.shape[0]:
 		return_mat[counter,counter]=return_mat[counter,counter]-0.5*freqs_gs[counter]**2.0
 		counter=counter+1
	return return_mat

def av_energy_gap_classical(Omega_sq,lambda_0,E_adiabatic,kbT,freqs_gs,is_emission):
	av_gap=E_adiabatic+lambda_0
	counter=0
	while counter<Omega_sq.shape[0]:
		if is_emission:
			av_gap=av_gap-Omega_sq[counter,counter]*kbT/(freqs_gs[counter]**2.0)
		else:
			av_gap=av_gap+Omega_sq[counter,counter]*kbT/(freqs_gs[counter]**2.0)
		counter=counter+1
	return av_gap

def av_energy_gap_exact_qm(Omega_sq,lambda_0,E_adiabatic,kbT,freqs_gs,is_emission):
	av_gap=0.0
	av_gap=lambda_0+E_adiabatic

	counter=0
	while counter<freqs_gs.shape[0]:
		if is_emission:
			av_gap=av_gap-Omega_sq[counter,counter]/(2.0*freqs_gs[counter])*math.cosh(freqs_gs[counter]/(2.0*kbT))/math.sinh(freqs_gs[counter]/(2.0*kbT))
		else:
			av_gap=av_gap+Omega_sq[counter,counter]/(2.0*freqs_gs[counter])*math.cosh(freqs_gs[counter]/(2.0*kbT))/math.sinh(freqs_gs[counter]/(2.0*kbT))
		counter=counter+1

	return av_gap

#--------------------------------------------------------------------------
# Class definition of the Generalized Brownian Oscillator Model.
# Generates the GBOM model parameters from Gaussian output files
# Can construct linear response functions in the ensemble, E-ZTFC
# and pure Franck-Condon approach
# standard way to initialize the GBOM works

class gbom:
    def __init__(self,freqs_gs,freqs_ex,J,K,E_adiabatic,dipole_mom,stdout):
        # Ground state, excited state and Duschinsky rotations,
        # adiabatic energy gap.
        self.num_modes=freqs_gs.shape[0]
        self.freqs_gs=freqs_gs
        self.freqs_ex=freqs_ex
        self.J=J
        self.K=K
        # emission equivalents of J and K. for now set to zero unless they are needed
        self.J_emission=np.zeros((self.J.shape[0],self.J.shape[0]))
        self.K_emission=np.zeros(self.K.shape[0])
        self.freqs_gs_emission=np.zeros(self.freqs_ex.shape[0])
        self.freqs_ex_emission=np.zeros(self.freqs_gs.shape[0])

		# the E_adiabatic passed into this routine is actually E0_0. To go from E00 to Eadiabatic we have to
		# subtract the zero point energy on the excited state PES and add the zero point energy on the ground-
		# state PES.
        stdout.write('\n'+'Initializing a GBOM object.'+'\n')
        self.E_adiabatic=E_adiabatic+0.5*(-np.sum(self.freqs_ex)+np.sum(self.freqs_gs)) # the energy gap passed in this routine is the energy gap between 0-0 transitions.
		# correct for zero-point energy of ground and excited state
        stdout.write('Adiabatic energy gap: '+str(self.E_adiabatic)+'  Ha'+'\n')
        self.dipole_mom=dipole_mom
        stdout.write('Dipole moment  '+str(self.dipole_mom)+'  Ha'+'\n')

		# by default, this is set to an empty dummy array. It is only needed for HT effects
        self.dipole_deriv=np.zeros((freqs_gs.shape[0],3))
        # emission equivalents of dipole moment and dipole derivative
        self.dipole_mom_emission=np.zeros(3)
        self.dipole_deriv_emission=np.zeros((freqs_gs.shape[0],3))

        # by default, initialize temporary variables for an absorption calculation.
        self.set_absorption_variables()

		# print ground and excited state frequencies
        stdout.write('   MODE'+'\t'+'GS FREQUENCY (cm-1)'+'\t'+'EX FREQUENCY (cm-1)'+'\n')
        for i in range(self.num_modes):
            stdout.write("%5d      %10.4f          %10.4f" % (i+1, self.freqs_gs[i]*const.Ha_to_cm,self.freqs_ex[i]*const.Ha_to_cm)+ '\n')
		# Derived terms beyond the model system parameters
        self.omega_av_cl=0.0
        self.omega_av_qm=0.0

		# response functions
        self.ensemble_response=np.zeros((1,1))
        self.fc_response=np.zeros((1,1))
        self.eztfc_response=np.zeros((1,1))
        self.cumulant_response=np.zeros((1,1))

		# Herzberg teller contribution
        self.HT=np.zeros((1,1))
        # ONLY NEEDED FOR ANDRES version
        self.HT_FCHT=np.zeros((1,1))

		# cumulant terms:
        self.g2_exact=np.zeros((1,1),dtype=complex)
        self.g3_exact=np.zeros((1,1),dtype=complex)
        self.g2_cl=np.zeros((1,1),dtype=complex)
        self.g3_cl=np.zeros((1,1),dtype=complex)

		# 3rd order cumulant 2DES terms. Needed to evaluate the 3rd order
		# cumulant correction to the 3rd order response function
        self.h1_exact=np.zeros((1,1,1),dtype=complex)
        self.h2_exact=np.zeros((1,1,1),dtype=complex)
        self.h1_cl=np.zeros((1,1,1),dtype=complex)
        self.h2_cl=np.zeros((1,1,1),dtype=complex)

        self.h4_exact=np.zeros((1,1,1),dtype=complex)
        self.h5_exact=np.zeros((1,1,1),dtype=complex)
        self.h4_cl=np.zeros((1,1,1),dtype=complex)
        self.h5_cl=np.zeros((1,1,1),dtype=complex)

		# spectral density. Only needed for comparisons
        self.spectral_dens=np.zeros((1,1),dtype=complex)

		# 3rd order correlation functions: Only needed for non-linear response beyond 2nd order cumulant
        self.corr_func_3rd_cl=np.zeros((1,1),dtype=complex)
        self.corr_func_3rd_cl_freq=np.zeros((1,1),dtype=complex)
        self.corr_func_3rd_qm=np.zeros((1,1),dtype=complex)
        self.corr_func_3rd_qm_freq=np.zeros((1,1),dtype=complex)

		# second order divergence factor for the cumulant approach
        self.second_order_divergence=0.0

    def calc_2nd_order_divergence(self,temp,is_qm):
        kbT=const.kb_in_Ha*temp
        divergence_sum=0.0
        print('DIVERGENCE SUM COMPUTED FOR GBOM')
        for i in range(self.Omega_sq.shape[0]):
            if is_qm:
                omega_kbT=self.freqs_gs[i]/kbT
                divergence_sum=divergence_sum+(self.Omega_sq[i,i])**2.0/(2.0*self.freqs_gs[i]**2.0)*math.exp(omega_kbT)/(math.exp(omega_kbT)-1.0)**2.0
            else:
                divergence_sum=divergence_sum+(self.Omega_sq[i,i])**2.0/(2.0*self.freqs_gs[i]**4.0)*kbT**2.0
                print(i,divergence_sum)
            self.second_order_divergence=divergence_sum

	# need an emission equivalent for this, ie where the propagation happens on the excited state PES
    def compute_corr_func_3rd(self,kbT,num_points,max_t,is_qm,four_phonon_term):
        if is_qm:
            self.corr_func_3rd_qm=gbom_cumulant_response.full_third_order_corr_func(self.freqs_gs,self.Omega_sq,self.gamma,kbT,max_t,num_points,False,four_phonon_term)
        else:
            self_corr_func_3rd_cl=gbom_cumulant_response.full_third_order_corr_func(self.freqs_gs,self.Omega_sq,self.gamma,kbT,max_t,num_points,True,four_phonon_term)

    def compute_HT_term(self,temp,num_points,max_t,decay_length,is_qm,is_3rd,dipole_dipole_only,is_emission,stdout):
        kbT=const.kb_in_Ha*temp
        if is_emission:
            self.HT=gbom_cumulant_response.full_HT_term(self.freqs_gs_emission,self.K_emission,self.J_emission,self.Omega_sq,self.gamma,self.dipole_mom_emission,self.dipole_deriv_emission,kbT,max_t,num_points,decay_length,is_qm,is_3rd,dipole_dipole_only,is_emission,stdout)
            # ANDRES TERM
            self.HT_FCHT=gbom_cumulant_response.full_FCHT_term_second_order(self.freqs_gs_emission,self.K_emission,self.J_emission,self.gamma,self.dipole_mom_emission,self.dipole_deriv_emission,kbT,max_t,num_points,decay_length,is_qm,is_emission)
        else:
            self.HT=gbom_cumulant_response.full_HT_term(self.freqs_gs,self.K,self.J,self.Omega_sq,self.gamma,self.dipole_mom,self.dipole_deriv,kbT,max_t,num_points,decay_length,is_qm,is_3rd,dipole_dipole_only,is_emission,stdout)
            # ANDRES TERM
            self.HT_FCHT=gbom_cumulant_response.full_FCHT_term_second_order(self.freqs_gs,self.K,self.J,self.gamma,self.dipole_mom,self.dipole_deriv,kbT,max_t,num_points,decay_length,is_qm,is_emission)

    def set_absorption_variables(self):
		# temporary variable. Not needed to be accessible to the outside
        omega_e_sq=get_omega_sq(self.freqs_ex)

		# These parameters are needed both in the cumulant and in ensemble approaches
        self.gamma=get_gamma(self.K,self.J,omega_e_sq)
        self.Omega_sq=get_full_Omega_sq(self.J,omega_e_sq,self.freqs_gs)
        self.lambda_0=get_lambda_0(self.K,self.J,omega_e_sq)

	# need to swap around initial and final state and reinitialize omega sq and other derived variables
	# also set J_emission and K_emission. J_emission=J^T and K_emission=-J^T*K
    # Finally, for HT effects, we also need to change the definiton of the dipole moment and transition
    # dipole derivative so that they are effectively the ground state transition dipole moment and derivative
    def set_emission_variables(self):
		# swap initial and final state
        self.freqs_gs_emission=self.freqs_ex
        self.freqs_ex_emission=self.freqs_gs
		# same as for absorption but with initial and final state swapped
		# in emisson variables, only omega_e_sq, gamma, Omega_sq and lambda_0 get set correctly
        omega_e_sq=get_omega_sq(self.freqs_ex_emission)
        self.K_emission=-np.dot(np.transpose(self.J),self.K)
        self.J_emission=np.transpose(self.J)
        self.gamma=get_gamma(self.K_emission,self.J_emission, omega_e_sq)
        self.Omega_sq=get_full_Omega_sq(self.J_emission,omega_e_sq,self.freqs_gs_emission)
		# in emission calculation, flip sign of lambda_0
        self.lambda_0=-get_lambda_0(self.K_emission,self.J_emission,omega_e_sq)
		# adjust E_adiabatic:
		#self.E_adiabatic=self.E_adiabatic+0.5*(-np.sum(self.freqs_ex_emission)+np.sum(self.freqs_gs_emission))
		# adjust dipole moment and dipole derivative
        self.dipole_deriv_emission[:,0]=np.dot(self.J_emission,self.dipole_deriv[:,0])
        self.dipole_deriv_emission[:,1]=np.dot(self.J_emission,self.dipole_deriv[:,1])
        self.dipole_deriv_emission[:,2]=np.dot(self.J_emission,self.dipole_deriv[:,2])
        self.dipole_mom_emission[0]=self.dipole_mom[0]+np.dot(self.K_emission,self.dipole_deriv[:,0])
        self.dipole_mom_emission[1]=self.dipole_mom[1]+np.dot(self.K_emission,self.dipole_deriv[:,1])
        self.dipole_mom_emission[2]=self.dipole_mom[2]+np.dot(self.K_emission,self.dipole_deriv[:,2])

    # ADDED FCHT DUMMY TERM. ONLY NEEDED FOR ANDRES VERSION
    def calc_cumulant_response(self,is_3rd_order_cumulant,is_qm,is_emission,is_HT):
        if is_qm:
            self.cumulant_response=gbom_cumulant_response.compute_cumulant_response(self.g2_exact,self.g3_exact,self.dipole_mom,self.HT,self.HT_FCHT,is_3rd_order_cumulant,is_HT,is_emission)
        else:
            self.cumulant_response=gbom_cumulant_response.compute_cumulant_response(self.g2_cl,self.g3_cl,self.dipole_mom,self.HT,self.HT_FCHT,is_3rd_order_cumulant,is_HT,is_emission)

    def calc_spectral_dens(self,temp,max_t,max_steps,decay_length,is_cl,is_emission):
        kbT=const.kb_in_Ha*temp
        if is_emission:
            self.spectral_dens=gbom_cumulant_response.compute_spectral_dens(self.freqs_gs_emission,self.Omega_sq,self.gamma,kbT,max_t,max_steps,decay_length,is_cl)
        else:
            self.spectral_dens=gbom_cumulant_response.compute_spectral_dens(self.freqs_gs,self.Omega_sq,self.gamma,kbT,max_t,max_steps,decay_length,is_cl)

    def calc_g2_cl(self,temp,num_points,max_time,is_emission,stdout):
        kbT=const.kb_in_Ha*temp
        if is_emission:
            # sanity check. Compute sd and skew to see whether cumulant is reliable
            sd,skew=get_sd_skew(self.freqs_gs_emission,self.Omega_sq,self.gamma,kbT,True)
            stdout.write('\n'+'Standard deviation of energy gap fluctuations for GBOM: '+str(sd)+' Ha. Skewness: '+str(skew)+'\n')
            if abs(skew)>0.3:
                stdout.write('WARNING: Large skewness value of '+str(skew)+' detected in energy gap fluctuations.'+'\n')
                stdout.write('This means that the energy gap fluctuations are likely non-Gaussian in nature and low-order cumulant expansions might be unreliable!'+'\n'+'\n')

            self.g2_cl=gbom_cumulant_response.full_2nd_order_lineshape(self.freqs_gs_emission,self.freqs_ex_emission,self.K_emission,self.J_emission,self.gamma,self.Omega_sq,kbT,self.omega_av_cl,max_time,num_points,True,is_emission,self.E_adiabatic,stdout)
        else:
            sd,skew=get_sd_skew(self.freqs_gs,self.Omega_sq,self.gamma,kbT,True)
            stdout.write('\n'+'Standard deviation of energy gap fluctuations for GBOM: '+str(sd)+' Ha. Skewness: '+str(skew)+'\n')
            if abs(skew)>0.3:
                stdout.write('WARNING: Large skewness value of '+str(skew)+' detected in energy gap fluctuations.'+'\n')
                stdout.write('This means that the energy gap fluctuations are likely non-Gaussian in nature and low-order cumulant expansions might be unreliable!'+'\n'+'\n')
            self.g2_cl=gbom_cumulant_response.full_2nd_order_lineshape(self.freqs_gs,self.freqs_ex,self.K,self.J,self.gamma,self.Omega_sq,kbT,self.omega_av_cl,max_time,num_points,True,is_emission,self.E_adiabatic,stdout)

    def calc_g3_cl(self,temp,num_points,max_time,is_emission,four_phonon_term,g3_cutoff,stdout):
        kbT=const.kb_in_Ha*temp
        if is_emission:
            self.g3_cl=gbom_cumulant_response.full_third_order_lineshape(self.freqs_gs_emission,self.Omega_sq,self.gamma,kbT,max_time,num_points,True,four_phonon_term,g3_cutoff,stdout)
        else:
            self.g3_cl=gbom_cumulant_response.full_third_order_lineshape(self.freqs_gs,self.Omega_sq,self.gamma,kbT,max_time,num_points,True,four_phonon_term,g3_cutoff,stdout)

    def calc_g3_qm(self,temp,num_points,max_time,is_emission,four_phonon_term,g3_cutoff,stdout):
        kbT=const.kb_in_Ha*temp
        if is_emission:
            self.g3_exact=gbom_cumulant_response.full_third_order_lineshape(self.freqs_gs_emission,self.Omega_sq,self.gamma,kbT,max_time,num_points,False,four_phonon_term,g3_cutoff,stdout)
        else:
            self.g3_exact=gbom_cumulant_response.full_third_order_lineshape(self.freqs_gs,self.Omega_sq,self.gamma,kbT,max_time,num_points,False,four_phonon_term,g3_cutoff,stdout)

    def calc_h4_qm(self,temp,num_points,max_time,no_dusch,four_phonon_term):
        kbT=const.kb_in_Ha*temp
        self.h4_exact=gbom_cumulant_response.full_h4_func(self.freqs_gs,self.Omega_sq,self.gamma,kbT,max_time,num_points,False,no_dusch,four_phonon_term)

    def calc_h5_qm(self,temp,num_points,max_time,no_dusch,four_phonon_term):
        kbT=const.kb_in_Ha*temp
        self.h5_exact=gbom_cumulant_response.full_h5_func(self.freqs_gs,self.Omega_sq,self.gamma,kbT,max_time,num_points,False,no_dusch,four_phonon_term)

    def calc_h4_cl(self,temp,num_points,max_time,no_dusch,four_phonon_term):
        kbT=const.kb_in_Ha*temp
        self.h4_cl=gbom_cumulant_response.full_h4_func(self.freqs_gs,self.Omega_sq,self.gamma,kbT,max_time,num_points,True,no_dusch,four_phonon_term)

    def calc_h5_cl(self,temp,num_points,max_time,no_dusch,four_phonon_term):
        kbT=const.kb_in_Ha*temp
        self.h5_cl=gbom_cumulant_response.full_h5_func(self.freqs_gs,self.Omega_sq,self.gamma,kbT,max_time,num_points,True,no_dusch,four_phonon_term)

    def calc_h1_qm(self,temp,num_points,max_time,no_dusch,four_phonon_term):
        kbT=const.kb_in_Ha*temp
        self.h1_exact=gbom_cumulant_response.full_h1_func(self.freqs_gs,self.Omega_sq,self.gamma,kbT,max_time,num_points,False,no_dusch,four_phonon_term)

    def calc_h2_qm(self,temp,num_points,max_time,no_dusch,four_phonon_term):
        kbT=const.kb_in_Ha*temp
        self.h2_exact=gbom_cumulant_response.full_h2_func(self.freqs_gs,self.Omega_sq,self.gamma,kbT,max_time,num_points,False,no_dusch,four_phonon_term)

    def calc_h1_cl(self,temp,num_points,max_time,no_dusch,four_phonon_term):
        kbT=const.kb_in_Ha*temp
        self.h1_cl=gbom_cumulant_response.full_h1_func(self.freqs_gs,self.Omega_sq,self.gamma,kbT,max_time,num_points,True,no_dusch,four_phonon_term)

    def calc_h2_cl(self,temp,num_points,max_time,no_dusch,four_phonon_term):
        kbT=const.kb_in_Ha*temp
        self.h2_cl=gbom_cumulant_response.full_h2_func(self.freqs_gs,self.Omega_sq,self.gamma,kbT,max_time,num_points,True,no_dusch,four_phonon_term)

    def calc_g2_qm(self,temp,num_points,max_time,is_emission,stdout):
        kbT=const.kb_in_Ha*temp
        if is_emission:
            sd,skew=get_sd_skew(self.freqs_gs_emission,self.Omega_sq,self.gamma,kbT,False)
            stdout.write('\n'+'Standard deviation of energy gap fluctuations for GBOM: '+str(sd)+' Ha. Skewness: '+str(skew)+'\n')
            if abs(skew)>0.3:
                stdout.write('WARNING: Large skewness value of '+str(skew)+' detected in energy gap fluctuations.'+'\n')
                stdout.write('This means that the energy gap fluctuations are likely non-Gaussian in nature and low-order cumulant expansions might be unreliable!'+'\n'+'\n')
            self.g2_exact=gbom_cumulant_response.full_2nd_order_lineshape(self.freqs_gs_emission,self.freqs_ex_emission,self.K_emission,self.J_emission,self.gamma,self.Omega_sq,kbT,self.omega_av_qm,max_time,num_points,False,is_emission,self.E_adiabatic,stdout)
        else:
            sd,skew=get_sd_skew(self.freqs_gs,self.Omega_sq,self.gamma,kbT,False)
            stdout.write('\n'+'Standard deviation of energy gap fluctuations for GBOM: '+str(sd)+' Ha. Skewness: '+str(skew)+'\n')
            if abs(skew)>0.3:
                stdout.write('WARNING: Large skewness value of '+str(skew)+' detected in energy gap fluctuations.'+'\n')
                stdout.write('This means that the energy gap fluctuations are likely non-Gaussian in nature and low-order cumulant expansions might be unreliable!'+'\n'+'\n')
            self.g2_exact=gbom_cumulant_response.full_2nd_order_lineshape(self.freqs_gs,self.freqs_ex,self.K,self.J,self.gamma,self.Omega_sq,kbT,self.omega_av_qm,max_time,num_points,False,is_emission,self.E_adiabatic,stdout)

    def calc_omega_av_cl(self,temp,is_emission):
        kbT=const.kb_in_Ha*temp
        if is_emission:
            self.omega_av_cl=av_energy_gap_classical(self.Omega_sq,self.lambda_0,self.E_adiabatic,kbT,self.freqs_gs_emission,is_emission)
        else:
            self.omega_av_cl=av_energy_gap_classical(self.Omega_sq,self.lambda_0,self.E_adiabatic,kbT,self.freqs_gs,is_emission)

    def calc_omega_av_qm(self,temp,is_emission):
        kbT=const.kb_in_Ha*temp
        if is_emission:
            self.omega_av_qm=av_energy_gap_exact_qm(self.Omega_sq,self.lambda_0,self.E_adiabatic,kbT,self.freqs_gs_emission,is_emission)
        else:
            self.omega_av_qm=av_energy_gap_exact_qm(self.Omega_sq,self.lambda_0,self.E_adiabatic,kbT,self.freqs_gs,is_emission)

    def calc_ensemble_response(self,temp,num_points,max_time,is_qm,is_emission,stdout):
        kbT=const.kb_in_Ha*temp
        if is_emission:
            omega_e_sq=get_omega_sq(self.freqs_ex_emission)
            omega_g_sq=get_omega_sq(self.freqs_gs_emission)
            self.ensemble_response=gbom_ensemble_response.compute_ensemble_response(self.freqs_gs_emission,self.freqs_ex_emission,self.J_emission,self.K_emission,self.E_adiabatic,self.lambda_0,self.gamma,self.Omega_sq,omega_e_sq,omega_g_sq,kbT,num_points,max_time,is_qm,is_emission,stdout)
        else:
            omega_e_sq=get_omega_sq(self.freqs_ex)
            omega_g_sq=get_omega_sq(self.freqs_gs)
            self.ensemble_response=gbom_ensemble_response.compute_ensemble_response(self.freqs_gs,self.freqs_ex,self.J,self.K,self.E_adiabatic,self.lambda_0,self.gamma,self.Omega_sq,omega_e_sq,omega_g_sq,kbT,num_points,max_time,is_qm,is_emission,stdout)

    def calc_fc_response(self,temp,num_points,max_time,is_emission,is_HT,stdout):
        kbT=const.kb_in_Ha*temp
        if is_emission:
             self.fc_response=franck_condon_response.compute_full_response_func(self.freqs_gs_emission,self.freqs_ex_emission,self.J_emission,self.K_emission,self.E_adiabatic,self.dipole_mom_emission,self.dipole_deriv_emission,kbT,num_points,max_time,is_emission,is_HT,stdout)
        else:
            self.fc_response=franck_condon_response.compute_full_response_func(self.freqs_gs,self.freqs_ex,self.J,self.K,self.E_adiabatic,self.dipole_mom,self.dipole_deriv,kbT,num_points,max_time,is_emission,is_HT,stdout)

	# differentiate between absorption and emission, and whether this is a Quantum Wigner distribution or classical
	# distribution for the ensemble sampling
    def calc_eztfc_response(self,temp,num_points,max_time,is_qm,is_emission,is_HT,stdout):
        low_temp_kbT=10.0*const.kb_in_Ha   # needed for computing low temperature FC limit. Set T to 10 K
        if is_emission:
            ztfc_resp=franck_condon_response.compute_full_response_func(self.freqs_gs_emission,self.freqs_ex_emission,self.J_emission,self.K_emission,self.E_adiabatic,low_temp_kbT,self.dipole_mom_emission,self.dipole_deriv_emission,num_points,max_time,is_emission,is_HT,stdout)
        else:
            ztfc_resp=franck_condon_response.compute_full_response_func(self.freqs_gs,self.freqs_ex,self.J,self.K,self.E_adiabatic,self.dipole_mom,self.dipole_deriv,low_temp_kbT,num_points,max_time,is_emission,is_HT,stdout)
        kbT=temp*const.kb_in_Ha
        # implement this so it works either with quantum wigner or classical distribution of nuclei
        if is_emission:
            omega_e_sq=get_omega_sq(self.freqs_ex_emission)
            omega_g_sq=get_omega_sq(self.freqs_gs_emission)
            if is_qm:
                # compute 'zero temperature' ensemble spectrum
                ensemble_resp_zero_t=gbom_ensemble_response.compute_ensemble_response(self.freqs_gs_emission,self.freqs_ex_emission,self.J_emission,self.K_emission,self.E_adiabatic,self.lambda_0,self.gamma,self.Omega_sq,omega_e_sq,omega_g_sq,low_temp_kbT,num_points,max_time,True,is_emission,stdout)
                ensemble_resp=gbom_ensemble_response.compute_ensemble_response(self.freqs_gs_emission,self.freqs_ex_emission,self.J_emission,self.K_emission,self.E_adiabatic,self.lambda_0,self.gamma,self.Omega_sq,omega_e_sq,omega_g_sq,kbT,num_points,max_time,True,is_emission,stdout)
            else:
                ensemble_resp=gbom_ensemble_response.compute_ensemble_response(self.freqs_gs_emission,self.freqs_ex_emission,self.J_emission,self.K_emission,self.E_adiabatic,self.lambda_0,self.gamma,self.Omega_sq,omega_e_sq,omega_g_sq,kbT,num_points,max_time,False,is_emission,stdout)
        else:
            omega_e_sq=get_omega_sq(self.freqs_ex)
            omega_g_sq=get_omega_sq(self.freqs_gs)
            if is_qm:
                # compute 'zero temperature' ensemble spectrum
                ensemble_resp_zero_t=gbom_ensemble_response.compute_ensemble_response(self.freqs_gs,self.freqs_ex,self.J,self.K,self.E_adiabatic,self.lambda_0,self.gamma,self.Omega_sq,omega_e_sq,omega_g_sq,low_temp_kbT,num_points,max_time,True,is_emission,stdout)
                ensemble_resp=gbom_ensemble_response.compute_ensemble_response(self.freqs_gs,self.freqs_ex,self.J,self.K,self.E_adiabatic,self.lambda_0,self.gamma,self.Omega_sq,omega_e_sq,omega_g_sq,kbT,num_points,max_time,True,is_emission,stdout)
            else:
                ensemble_resp=gbom_ensemble_response.compute_ensemble_response(self.freqs_gs,self.freqs_ex,self.J,self.K,self.E_adiabatic,self.lambda_0,self.gamma,self.Omega_sq,omega_e_sq,omega_g_sq,kbT,num_points,max_time,False,is_emission,stdout)

        total_response=np.zeros((ztfc_resp.shape[0],2),dtype=complex)
        counter=0
		# now combine both the ZTFC and the Ensemble lineshapes. Add the magnitude and the phase
		# individually. We also need to add a term to the phase factor to ensure that the Ensemble
		# spectrum is centered around 0. Also, if this is a calculation using a QM wigner distribution
		# for computing the ensemlbe spectrum, make sure that we add the appropriate correction factor (the zero temperature
		# limit of the QM ensemble spectrum).
        while counter<ztfc_resp.shape[0]:
            total_response[counter,0]=ztfc_resp[counter,0]
            if is_qm:
                total_response[counter,1]=ztfc_resp[counter,1]*ensemble_resp[counter,1]/ensemble_resp_zero_t[counter,1]
            else:
                total_response[counter,1]=ztfc_resp[counter,1]*ensemble_resp[counter,1]*cmath.exp(1j*(self.E_adiabatic)*ztfc_resp[counter,0])
            counter=counter+1

        self.eztfc_response=total_response

# class definition of a list of GBOM models. This is relevant for the E-ZTFC approach as applied to condensed phase systems, but also more accurate
# approaches like E-avFTFC and E-FTFC.
# FIX emission approach for GBOM list, similar to the correction done in GBOM
class gbom_list:
	def __init__(self,freqs_gs_batch,freqs_ex_batch,J_batch,K_batch,E_adiabatic_batch,dipole_mom_batch,num_gboms,stdout):
		self.gboms = []
		# gboms is a list of GBOM objects
		for i in range(num_gboms):
			stdout.write('\n'+'\n'+'ADDING GBOM '+str(i+1)+' OUT OF '+str(num_gboms)+' TO BATCH.'+'\n')
			self.gboms.append(gbom(freqs_gs_batch[i,:],freqs_ex_batch[i,:],J_batch[i,:,:],K_batch[i,:],E_adiabatic_batch[i],dipole_mom_batch[i],stdout))

		self.num_gboms=num_gboms

		# response functions
		self.avZTFC_response=np.zeros((1,1))  # average ZTFC response function, calculated by setting E_adiabatic to the same value for all of them
		self.avFTFC_response=np.zeros((1,1))  # average FTFC response function, calculated by setting E_adiabatic to the same value for all of them
		self.full_FTFC_response=np.zeros((1,1)) # Just a sum of all FTFC response functions for each GBOM, divided by num_gboms
		self.avEnsemble_response=np.zeros((1,1)) # sum of all ensemble response functions
		self.avCumulant_response=np.zeros((1,1)) # sum of all cumulant response functions

	# Routines for calculating the response functions
	def calc_avEnsemble_response(self,temp,num_points,max_time,is_qm,is_emission,stdout):
		kbT=const.kb_in_Ha*temp
		counter=0
		while counter<self.num_gboms:
			omega_e_sq=get_omega_sq(self[counter].freqs_ex)
			omega_g_sq=get_omega_sq(self[counter].freqs_gs)

			if is_emission:
				eff_K=-np.dot(np.transpose(self[counter].J),self[counter].K)
				ensemble_resp=gbom_ensemble_response.compute_ensemble_response(self[counter].freqs_ex,self[counter].freqs_gs,np.transpose(self[counter].J),eff_K,self[counter].E_adiabatic,self[counter].lambda_0,self[counter].gamma,self[counter].Omega_sq,omega_e_sq,omega_g_sq,kbT,num_points,max_time,is_qm,is_emission,stdout)
			else:
				ensemble_resp=gbom_ensemble_response.compute_ensemble_response(self[counter].freqs_gs,self[counter].freqs_ex,self[counter].J,self[counter].K,self[counter].E_adiabatic,self[counter].lambda_0,self[counter].gamma,self[counter].Omega_sq,omega_e_sq,omega_g_sq,kbT,num_points,max_time,is_qm,is_emission,stdout)
			if counter==0:
				self.avEnsemble_response=ensemble_resp
			else:
				self.avEnsemble_response[:,1]=self.avEnsemble_response[:,1]+ensemble_resp[:,1]
			counter=counter+1
		self.avEnsemble_response[:,1]=self.avEnsemble_response[:,1]/(1.0*self.num_gboms)

	def calc_avZTFC_response(self,num_points,max_time,is_emission,is_HT,stdout):
		low_temp_kbT=10.0*const.kb_in_Ha
		counter=0
		while counter<self.num_gboms:
			ztfc_resp=franck_condon_response.compute_full_response_func(self[counter].freqs_gs,self[counter].freqs_ex,self[counter].J,self[counter].K,0.0,self.dipole_mom,self.dipole_deriv,low_temp_kbT,num_points,max_time,is_emission,stdout)
			if counter==0:
				self.avZTFC_response=ztfc_resp
			else:
				self.avZTFC_response[:,1]=self.avZTFC_response[:,1]+ztfc_rsponse[:,1]

			counter=counter+1
		self.avZTFC_response[:,1]=self.avZTFC_response[:,1]/(1.0*self.num_gboms)

	def calc_avFTFC_response(self,temp,num_points,max_time,is_emission,is_HT,stdout):
		kbT=temp*const.kb_in_Ha
		counter=0
		print('Compute FTFC response for GBOM batch.')
		while counter<self.num_gboms:
			stdout.write('PROCESSING BATCH	'+str(counter))
			ftfc_resp=franck_condon_response.compute_full_response_func(self[counter].freqs_gs,self[counter].freqs_ex,self[counter].J,self[couinter].K,0.0,self.dipole_mom,self.dipole_deriv,kbT,num_points,max_time,is_emission,is_HT,stdout)
			if counter==0:
				self.avFTFC_response=ftfc_resp
			else:
				self.avFTFC_response[:,1]=self.avFTFC_response[:,1]+ftfc_rsponse[:,1]

			counter=counter+1
		self.avFTFC_response[:,1]=self.avFTFC_response[:,1]/(1.0*self.num_gboms)

	def calc_full_FTFC_response(self,temp,num_points,max_time,is_emission,is_HT,stdout):
		kbT=temp*const.kb_in_Ha
		counter=0
		while counter<self.num_gboms:
			stdout.write('PROCESSING BATCH  '+str(counter))
			ftfc_resp=franck_condon_response.compute_full_response_func(self[counter].freqs_gs,self[counter].freqs_ex,self[counter].J,self[counter].K,self[counter].E_adiabatic,self.dipole_mom,self.dipole_deriv,kbT,num_points,max_time,is_emission,is_HT,stdout)
			if counter==0:
				self.full_FTFC_response=ftfc_resp
			else:
				self.full_FTFC_response[:,1]=self.full_FTFC_response[:,1]+ftfc_rsponse[:,1]

			counter=counter+1
		self.full_FTFC_response[:,1]=self.full_FTFC_response[:,1]/(1.0*self.num_gboms)


from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import os

import numpy as np
import tensorflow as tf
import pandas as pds

PATH = os.path.abspath('./data')

tf.logging.set_verbosity(tf.logging.ERROR)

def main(_):
    data_set = pds.read_csv(
        filepath_or_buffer=PATH + os.path.sep + 'temp_with_label2.csv',
        sep='\t'
    ).as_matrix()

    X = data_set[:, 1:-1]
    Y = data_set[:, -1]

    # print(X[0], Y[0])
    # print(X.shape, Y.shape)

    # max_value = X.max(axis=0)
    # min_value = X.min(axis=0)

    # for i in range(X.shape[1]):
    #     X[:, i] = (X[:, i] - min_value[i]) / (max_value[i] - min_value[i])

    feature_columns = [tf.feature_column.numeric_column("x", shape=[11])]

    regressor = tf.contrib.learn.DNNRegressor(
        feature_columns=feature_columns,
        hidden_units=[20, 10],
        optimizer=tf.train.AdamOptimizer(
            learning_rate=1,
        ),
        model_dir='./model/temp-model-2'
    )

    train_input_fn = tf.estimator.inputs.numpy_input_fn(
        x={"x": np.array(X, dtype=np.float32)},
        y=np.array(np.reshape(Y, (-1, 1)), dtype=np.float32),
        batch_size=50,
        num_epochs=None,
        shuffle=True
    )

    regressor.fit(input_fn=train_input_fn, steps=2000)
    # regressor.fit(
    #     x=np.array(X, dtype=np.float32),
    #     y=np.array(np.reshape(Y, (-1, 1)), dtype=np.float32),
    #     batch_size=50,
    #     steps=2000
    # )

if __name__ == "__main__":
    tf.app.run(main)


from __future__ import annotations
from pathpy.models.temporal_network import TemporalNetwork
from typing import Any, List, Union, Optional, Tuple
from functools import singledispatch
from collections import defaultdict, deque
import itertools as it
import functools as ft
from collections import Counter

from pathpy import logger, tqdm

from pathpy.core.api import NodeCollection, Node
from pathpy.core.api import EdgeCollection
from pathpy.core.api import PathCollection
from pathpy.models.classes import BaseTemporalNetwork
from pathpy.models.models import ABCDirectedAcyclicGraph

# create logger
LOG = logger(__name__)

def _remove_repetitions(path):
    """
    Remove repeated nodes in the path

    Parameters
    ----------
    path

    Returns
    -------

    Examples
    -------
    >>> remove_repetitions((1, 2, 2, 3, 4, 1))
    (1, 2, 3, 4, 1)
    >>> remove_repetitions((1, 2, 2, 2, 3)) == remove_repetitions((1, 2, 2, 3, 3))
    True
    """
    return tuple(p[0] for p in it.groupby(path))

def _expand_set_paths(set_path):
    """returns all possible paths which are consistent with the sequence of sets

    Parameters
    ----------
    set_path: list
        a list of sets or other iterable

    Examples
    -------
    >>> node_path = [{1, 2}, {2, 5}, {1, 2}]
    >>> list(expand_set_paths(node_path))
    [(1, 2, 1), (2, 2, 1), (1, 5, 1), (2, 5, 1), (1, 2, 2), (2, 2, 2), (1, 5, 2), (2, 5, 2)]
    >>> node_path = [{1, 2}, {5}, {2, 5}]
    >>> list(expand_set_paths(node_path))
    [(1, 5, 2), (2, 5, 2), (1, 5, 5), (2, 5, 5)]

    Yields
    ------
    tuple
        a possible path
    """
    # how many possible combinations are there
    node_sizes = [len(n) for n in set_path]
    num_possibilities = ft.reduce(lambda x, y: x * y, node_sizes, 1)

    # create a list of lists such that each iterator is repeated the number of times
    # his predecessors have completed their cycle
    all_periodics = []
    current_length = 1
    for node_set in set_path:
        periodic_num = []
        for num in node_set:
            periodic_num.extend([num] * current_length)
        current_length *= len(node_set)
        all_periodics.append(periodic_num)

    iterator = [it.cycle(periodic) for periodic in all_periodics]
    for i, elements in enumerate(zip(*iterator)):
        if i >= num_possibilities:
            break
        yield elements

def all_paths_from_dag(dag: ABCDirectedAcyclicGraph, node_mapping=None, max_subpath_length=None, repetitions=True, unique=False) -> PathCollection:
    """
    Calculates path statistics in a directed acyclic graph.
    All paths between all roots (nodes with zero indegree)
    and all leafs (nodes with zero outdegree) are generated.

    Parameters
    ----------
    dag: DAG
        the directed acyclic graph instance for which paths are calculated
    node_mapping: dict
        can be a simple mapping (1-to-1) or a 1-to-many (a dict with sets as values)
    max_subpath_length: int
        This can be used to limit the calculation of sub path statistics to a given
        maximum length. This is useful, as the statistics of sub paths of length k
        are only needed to fit a higher-order model with order k. Hence, if we know
        that the model selection is limited to a given maximum order K, we can safely
        set the maximum sub path length to K. By default, sub paths of any length
        will be calculated. Note that, independent of the sub path calculation
        longest path of any length will be considered in the likelihood calculation!
    repetitions: bool
        whether or not to remove repeated nodes in paths. Repeated nodes can occur
        if a non-injective node_mapping is applied. If set to True, a path a,a,b,b,c,c,d is
        returned as a,b,c,d.
    unique: bool
        whether or not multiple identical mapped paths should be counted separately. For
        DAG representations of temporal networks with delta > 1, where nodes are temporal copies,
        we do not want to count multiple paths from the same root that pass through different
        temporal copies of the same physical node. For instance with delta=2, time-stamped edges
        (a,b;1), (b,c;3) are transformed into a DAG a1->b2, a1->b3, b3->c4. With the mapping to
        physical nodes we would find two different paths a->b->c of length two, which only differ
        in terms of WHEN they arrive in node c

    Returns
    -------
    Paths

    """
    # Check whether we are doing a one-to-many mapping
    if node_mapping is None:
        node_mapping = { v.uid: v.uid for v in dag.nodes }

    if node_mapping:
        first_key = list(node_mapping.keys())[0]
        ONE_TO_MANY = isinstance(node_mapping[first_key], set)
    else:
        ONE_TO_MANY = False

    # Try to topologically sort the graph if not already sorted
    if dag.acyclic is None:
        dag.topological_sorting()
    if not dag.acyclic:
        LOG.error('Cannot extract statistics from a cyclic graph')
        raise ValueError
    else:
        # PathCollection that stores detected (projected) paths
        paths = PathCollection()

        LOG.info('Creating paths from directed acyclic graph')

        # construct all paths originating from root nodes for 1 to 1
        if not ONE_TO_MANY:
            for root in dag.roots:
                print('Processing root node ', root.uid)
                extracted_paths = dag.routes_from(root.uid, node_mapping)

                # if unique:
                #     extracted_paths = set(tuple(x) for x in extracted_paths)
                # for path in extracted_paths:   # add detected paths to paths object
                #     if repetitions:
                #         p = path
                #     else:
                #         p = _remove_repetitions(path)
                for p in extracted_paths:
                    if repetitions:
                        path = tuple([x for x in p])
                    else:
                        path = tuple(set([x for x in p]))
                    if unique:
                        paths.add(list(path), count=1, uid='-'.join(x.uid for x in path))
                    else:
                        paths.add(list(path), count=extracted_paths.counter[path], uid='-'.join([x.uid for x in path]))
        else:
            for root in dag.roots:
                for set_path in dag.routes_from_node(root.uid, node_mapping):
                    for blown_up_path in _expand_set_paths(set_path):
                        paths[blown_up_path] += 1

            # for path, count in path_counter.items():
            #     if repetitions:
            #         p = path
            #     else:
            #         p = _remove_repetitions(path)
            #     paths.add(p, count=count, uid='-'.join(p))

        #LOG.info('Expanding Subpaths')
        # p.expand_subpaths()
        #LOG.info('finished.')
        return paths

def all_paths_from_temporal_network(tempnet: TemporalNetwork, delta: int=1, max_subpath_length: int=-1) -> PathCollection:
    """
    Calculates the frequency of causal paths in a temporal network assuming a
    maximum temporal distance of delta between consecutive
    time-stamped links on a path. This method first creates a directed and acyclic
    time-unfolded graph based on the given parameter delta. This directed acyclic
    graph is used to calculate all time-respecting paths for a given delta.
    I.e., for time-stamped links (a,b,1), (b,c,5), (b,d,7) and delta = 5 the
    time-respecting path (a,b,c) will be found.

    Parameters
    ----------
    tempnet : pathpy.TemporalNetwork
        TemporalNetwork to extract the time-respecting paths from
    delta : int
        Indicates the maximum temporal distance up to which time-stamped
        links will be considered to contribute to a causal path.
        For (u,v;3) and (v,w;7) a causal path (u,v,w) is generated
        for 0 < delta <= 4, while no causal path is generated for
        delta > 4. Every time-stamped edge is a causal path of
        length one. Default value is 1.
    max_subpath_length : int
        Can be used to limit the calculation of sub path statistics to a given
        maximum length. This is useful as statistics of sub paths of length k
        are only needed to fit higher-order model with order k and larger. If model
        selection is limited to a maximum order K, we can set the maximum sub path length
        to K. Default is None, which means all subpaths are calculated.

    Returns
    -------
    Paths
        An instance of the class Paths, which can be used to generate higher- and multi-order
        models of causal paths in temporal networks.

    Examples
    ---------
    >>> t = pp.TemporalNetwork()
    >>> t.add_edge('a', 'b', 1)
    >>> t.add_edge('b', 'a', 3)
    >>> t.add_edge('b', 'c', 3)
    >>> t.add_edge('d', 'c', 4)
    >>> t.add_edge('c', 'd', 5)
    >>> t.add_edge('c', 'b', 6)

    >>> >>>causal_paths = pp.path_extraction.paths_from_temporal_network_dag(t, delta=2)
    >>> [Severity.INFO]	Constructing time-unfolded DAG ...
    >>> [Severity.INFO]	finished.
    >>> [Severity.INFO]	Generating causal trees for 2 root nodes ...
    >>> [Severity.INFO]	finished.
    >>> print(causal_paths)
    >>> Total path count: 		4.0
    >>> [Unique / Sub paths / Total]: 	[4.0 / 24.0 / 28.0]
    >>> Nodes:				    4
    >>> Edges:				    6
    >>> Max. path length:		3
    >>> Avg path length:		2.25
    >>> Paths of length k = 0		0.0 [ 0.0 / 13.0 / 13.0 ]
    >>> Paths of length k = 1		0.0 [ 0.0 / 9.0 / 9.0 ]
    >>> Paths of length k = 2		3.0 [ 3.0 / 2.0 / 5.0 ]
    >>> Paths of length k = 3		1.0 [ 1.0 / 0.0 / 1.0 ]

    >>> The calculated (longest) causal paths in this example are:
    >>> (a, b, c, d), (d, c, b), (d, c, d), (a, b, a)
    """
    from pathpy.models.directed_acyclic_graph import DirectedAcyclicGraph

    # generate a single time-unfolded DAG
    LOG.info('Constructing time-unfolded DAG ...')
    dag = DirectedAcyclicGraph.from_temporal_network(tempnet, delta)
    node_map = { v.uid: v['original'].uid for v in dag.nodes }
    LOG.info('finished.')

    # path statistics
    causal_paths = PathCollection()

    # For each root in the time-unfolded DAG, we generate a
    # causal tree and use it to count all causal paths
    # that originate at this root
    num_roots = len(dag.roots)
    current_root = 1

    LOG.info('Generating causal trees for {0} root nodes ...'.format(num_roots))
    for root in tqdm(dag.roots):

        # generate the causal tree from this root node
        causal_tree, causal_mapping = generate_causal_tree(dag, root, node_map)

        # output
        if num_roots > 10:
            step = num_roots/10
            if current_root % step == 0:
                LOG.info('Analyzing tree {0}/{1} ...'.format(current_root, num_roots))

        # calculate all unique longest path in the causal tree
        # TODO: replace by add operator
        paths = all_paths_from_dag(causal_tree, causal_mapping, repetitions=False, max_subpath_length=max_subpath_length)
        for p in paths:
            causal_paths.add(p, count=paths.counter[p.uid], uid=p.uid)
        current_root += 1

    LOG.info('finished.')

    return causal_paths

def generate_causal_tree(dag, root, node_map) -> Tuple(ABCDirectedAcyclicGraph, defaultdict):
    """
    For a directed acyclic graph and a non-injective mapping of nodes,
    this method creates a *causal tree* for a given root node.
    This is useful for the extraction of causal paths in time-unfolded DAG
    representations of temporal networks. The nodes "{v}_{d}" in the resulting
    causal tree capture that - starting from the root node at step 0 - there is
    a causal path to node v at distance d from the root. Note that the same node
    can be represented by multiple nodes in the causal tree (at different distances d).
    """
    from pathpy.models.directed_acyclic_graph import DirectedAcyclicGraph
    causal_tree = DirectedAcyclicGraph()

    causal_mapping = {}
    visited = defaultdict(lambda: False)
    queue = deque()

    # launch breadth-first-search at root of tree
    # root nodes are necessarily at depth 0
    queue.append((root.uid, 0))
    edges = []
    while queue:
        # take out left-most element from FIFO queue
        v, depth = queue.popleft()

        # x is the node ID of the node in the causal tree
        # the second component captures the distance from
        # the root of the causal tree. These IDs ensure
        # that the same physical nodes can occur at different
        # distances from the root
        x = '{0}_{1}'.format(node_map[v], depth)
        causal_mapping[x] = node_map[v]

        # process nodes at next level
        for w in dag.successors[v]:
            if (w.uid, depth+1) not in queue:
                queue.append((w.uid, depth+1))
                # only consider nodes that have not already
                # been added to this level
                if not visited[node_map[w.uid], depth+1]:
                    # add edge to causal tree
                    y = '{0}_{1}'.format(node_map[w.uid], depth+1)
                    edges.append((x, y))

                    visited[node_map[w.uid], depth+1] = True
                    causal_mapping[y] = node_map[w.uid]

    # Adding all edges at once is more efficient!
    for e in edges:
        causal_tree.add_edges(e)

    return causal_tree, causal_mapping

def PaCo(
    tn: BaseTemporalNetwork,
    delta: float,
    skip_first: int = 0,
    up_to_k: int = 10 ) -> PathCollection:
    """
    Path counting algorithm PaCo.
    Published at TempWeb 2021 workshop.
    in:
        tn : BaseTemporalNetwork,
            temporal network in which we count paths.

        delta : float
            maximal time difference that between two links that can form a path.

        skip_first = 0 : int,
            paths computed in the first `skip_first' temporal links are not counted towards the total path count. This feature is for parallel computing.

        up_to_k = 10 : int
            maximal lengt of paths that we count.
    """
    # all the entries that are at max distance delta away from the current entry
    delta_window = []

    path_collection = PathCollection()

    # current_path_stack[i][p] is the number of paths p that go from
    # current_edge of index i.
    current_path_stack = defaultdict(lambda: defaultdict(int))

    # for e, current_edge in enumerate(D):
    for e, edge in enumerate(tn.edges[:]):
        current_edge = (edge.v.uid, edge.w.uid, edge.start)
        # since we go in forward direction, delta window is back in time.
        # not every entry from delta window is important for the current
        # considered current_edge some are happening at the same time.

        # check if there is anything to update about delta window
        # and current stack
        if len(delta_window) > 0:
            # I throw away the ones in delta_window that are too far away
            # (in temporal sense)
            i = 0
            didnt_find_one_yet = True
            # since the data is sorted, I just need to find the one that
            # is inside the delta time window.
            # This while loop identifies the first entry saved in delta window,
            # that is inside the new delta window so it only loops through the
            # ones that are outside the new delta window.
            while i < len(delta_window) and didnt_find_one_yet:
                # i'th entry [i], then I need the entry,
                # and not the ennumerator [1],
                # and then I need time, which is the third column[2]
                if delta_window[i][1][2] >= current_edge[2]-delta:
                    didnt_find_one_yet = False
                else:
                    # i didn't find yet one which is inside the new delta window.
                    i += 1

            # keep all starting from the first that is inside the
            # new delta window.
            if i < len(delta_window):
                first_index_in_delta_window = delta_window[i][0]
                old_inx = [
                    e for (e, current_edge) in delta_window if e < first_index_in_delta_window]
                delta_window = delta_window[i:]
            else:
                # all from delta window should be removed.
                # asign current index
                first_index_in_delta_window = e
                old_inx = [
                    e for (e, current_edge) in delta_window if e < first_index_in_delta_window]
                delta_window = []
            # delta_window is now corresponding to the current_edge that I am
            # considering.
            # go through outdated indices
            for j in old_inx:
                # remove them from the current stack.
                del current_path_stack[j]
        # all stacks updated now.

        # compute for the next
        for enu, past_edge in delta_window:
            # if target of the link in delta_window is the same as the
            # source in considered entry
            if past_edge[1] == current_edge[0]:
                # the interactions should not happen at the same time!
                # the considered one should happen AFTER the one from the
                # delta window.
                if current_edge[2] > past_edge[2]:
                    # i know that the link happened in the appropriate time,
                    # and that the link continues the link I consider.
                    # so I can add the paths to the current_path_stack
                    for path in current_path_stack[enu]:
                        p = (*path, current_edge[1])
                        if len(p)-1 <= up_to_k:
                            current_path_stack[e][p] += current_path_stack[enu][path]

                            if e >= skip_first:
                                # path_collection[p] += current_path_stack[enu][path]
                                path_collection.add(p, uid='-'.join(p), count=current_path_stack[enu][path])
        current_path_stack[e][(current_edge[0], current_edge[1])] += 1

        if e >= skip_first:
            p = (current_edge[0], current_edge[1])
            path_collection.add(p[0], p[1], uid='-'.join(p), count=1)
            # path_collection[p] += 1

        # add this current_edge at the end of delta_window,
        # so that the next current_edge can have all the entries it needs.
        delta_window.append((e, current_edge))

    return path_collection


import math
import random
import copy
import ast
from functools import reduce

import numpy as np
import cv2

def get_rotation_matrix(rotation_angles):

    rotation_angles = list(map(lambda x : np.deg2rad(x), rotation_angles))

    phi = rotation_angles[0] # around x
    gamma = rotation_angles[1] # around y
    theta = rotation_angles[2] # around z

    # X rotation matrix
    rot_matrix_phi = np.eye(4,4)
    sp = np.sin(phi)
    cp = np.cos(phi)
    rot_matrix_phi[1,1] = cp
    rot_matrix_phi[2,2] = rot_matrix_phi[1,1]
    rot_matrix_phi[1,2] = -sp
    rot_matrix_phi[2,1] = sp

    # Y rotation matrix
    rot_matrix_gamma = np.eye(4,4)
    sg = np.sin(gamma)
    cg = np.cos(gamma)
    rot_matrix_gamma[0,0] = cg
    rot_matrix_gamma[2,2] = rot_matrix_gamma[0,0]
    rot_matrix_gamma[0,2] = sg
    rot_matrix_gamma[2,0] = -sg

    # Z rotation matrix (in-image-plane)
    rot_matrix_theta = np.eye(4,4)
    st = np.sin(theta)
    ct = np.cos(theta)
    rot_matrix_theta[0,0] = ct
    rot_matrix_theta[1,1] = rot_matrix_theta[0,0]
    rot_matrix_theta[0,1] = -st
    rot_matrix_theta[1,0] = st

    rotation_matrix = reduce(lambda x,y : np.matmul(x,y), [rot_matrix_phi, rot_matrix_gamma, rot_matrix_theta])

    return rotation_matrix

def get_perspective_transform_estimation(pts_in, pts_out, width, height, side_length):
    pts_in_2D = pts_in[0,:]
    pts_out_2D = pts_out[0,:]

    pts_out_2D_list = []
    pts_in_2D_list = []
    for i in range(0,4): # TODO: use len
        pts_out_2D_list.append([pts_out_2D[i,0], pts_out_2D[i,1]])
        pts_in_2D_list.append([pts_in_2D[i,0], pts_in_2D[i,1]])

    pin  =  np.array(pts_in_2D_list) + [width/2., height/2.]
    pout = (np.array(pts_out_2D_list) + [1., 1.]) * (0.5*side_length)
    pin  = pin.astype(np.float32)
    pout = pout.astype(np.float32)

    return pin, pout

def get_warp_matrix(width, height, theta, phi, gamma, scale, field_vision):

    fv_half = np.deg2rad(field_vision / 2.)
    d = np.sqrt(width*width + height*height)
    side_length = scale * (d / np.cos(fv_half))
    h = d / (2.0*np.sin(fv_half))
    n = h - (d/2.0)
    f = h + (d/2.0)

    # Translation along Z-axis by -h
    T = np.eye(4,4)
    T[2,3] = -h

    # Rotation matrices around x,y,z
    rotation_matrix = get_rotation_matrix([phi, gamma, theta])

    # Projection Matrix
    P = np.eye(4,4)
    P[0,0] = 1.0/np.tan(fv_half)
    P[1,1] = P[0,0]
    P[2,2] = -(f+n)/(f-n)
    P[2,3] = -(2.0*f*n)/(f-n)
    P[3,2] = -1.0

    # pythonic matrix multiplication
    F = reduce(lambda x,y : np.matmul(x,y), [P, T, rotation_matrix])

    # shape should be 1,4,3 for ptsIn and ptsOut since perspectiveTransform() expects data in this way.
    # In C++, this can be achieved by Mat ptsIn(1,4,CV_64FC3);
    pts_in = np.array([[
                 [-width/2., height/2., 0.],[ width/2., height/2., 0.],[ width/2.,-height/2., 0.],[-width/2.,-height/2., 0.]
                 ]])
    pts_out  = np.array(np.zeros((pts_in.shape), dtype=pts_in.dtype))
    pts_out  = cv2.perspectiveTransform(pts_in, F)

    pts_in_pt2f, pts_out_pt2f = get_perspective_transform_estimation(pts_in, pts_out, width, height, side_length)

    # check float32 otherwise OpenCV throws an error
    assert(pts_in_pt2f.dtype  == np.float32)
    assert(pts_out_pt2f.dtype == np.float32)
    M33 = cv2.getPerspectiveTransform(pts_in_pt2f, pts_out_pt2f)

    return M33, int(side_length)

def get_random_angles(theta_range, phi_range, gamma_range, step):
    theta = random.randrange(theta_range[0], theta_range[1], step)
    phi = random.randrange(phi_range[0], phi_range[1], step)
    gamma = random.randrange(gamma_range[0], gamma_range[1], step)

    return (theta, phi, gamma)

def cut_warped_image(warped_image, source_width, source_height, matrix):
    """Returns a cropped version of the image, containing the minimum size required to contain the image"""
    # Find coordinates position of warped image
    image_coords = np.array([[
                    [0,0],
                    [source_width, 0],
                    [0, source_height],
                    [source_width, source_height]]], dtype='float32')

    warped_coords = cv2.perspectiveTransform(image_coords, matrix)[0]

    # Find the minimum rect bouding box that fits the warped image. p = [x, y]
    p1 = np.min(warped_coords, axis=0).astype(int)
    p2 = np.max(warped_coords, axis=0).astype(int)

    # Crop image
    result = warped_image[p1[1]:p2[1], p1[0]:p2[0]]

    return result, [p1, p2]

def warp_bboxes(bboxes, matrix, crop_points=None, rotate_bboxes=False):
    """Re-calculates new bounding boxes based on transformation matrix used for an image"""
    warped_bboxes = []
    for bbox in bboxes:
        bbox_coords = bbox_to_coords(bbox)
        bbox_points = np.array([[
            [bbox_coords[0], bbox_coords[1]],
            [bbox_coords[2], bbox_coords[1]],
            [bbox_coords[0], bbox_coords[3]],
            [bbox_coords[2], bbox_coords[3]]
        ]], dtype='float32')

        # Find the new rectangle bbox that fits the warped bbox
        warped_coords = cv2.perspectiveTransform(bbox_points, matrix)[0]
        new_bbox = copy.copy(bbox)
        if rotate_bboxes:
            # minAreaRect = (cx,cy),(w,h),angle
            bbox_rect = cv2.minAreaRect(warped_coords)
            new_bbox['cx'], new_bbox['cy'] = bbox_rect[0][0], bbox_rect[0][1]
            new_bbox['w'], new_bbox['h'] = bbox_rect[1][0], bbox_rect[1][1]
            new_bbox['angle'] = bbox_rect[2]
        else:
            # boundingRect = (x1,y1),(w,h)
            bbox_rect = cv2.boundingRect(warped_coords)
            new_bbox['w'], new_bbox['h'] = bbox_rect[2], bbox_rect[3]
            # cx = x1 + w/2, cy = x2 + h/2
            new_bbox['cx'], new_bbox['cy'] = bbox_rect[0] + (bbox_rect[2]/2), bbox_rect[1] + (bbox_rect[3]/2)
            new_bbox['angle'] = 0

        # Apply cropping to bbox if points provided
        if crop_points:
            new_bbox['cx'] -= crop_points[0][0]
            new_bbox['cy'] -= crop_points[0][1]

        warped_bboxes.append(new_bbox)

    return warped_bboxes

def warp_image(image, theta, phi, gamma, scale, fovy, bboxes=None, rotate_bboxes=False):
    """Changes the perspective of an image according to x,y,z angles"""
    height, width, _ = image.shape
    matrix, side_length = get_warp_matrix(width, height, theta, phi, gamma, scale, fovy) # Compute warp matrix
    transparent_bg = (0,0,0,0)
    result_image = image
    if image.shape[2] == 3:
        result_image = cv2.cvtColor(image, cv2.COLOR_RGB2RGBA)
    result_image = cv2.warpPerspective(result_image, matrix, (side_length, side_length), borderValue=transparent_bg) # Do actual image warp
    result_image, crop_points = cut_warped_image(result_image, image.shape[1], image.shape[0], matrix)
    result_bboxes = None
    if bboxes:
        result_bboxes = warp_bboxes(bboxes, matrix, crop_points=crop_points, rotate_bboxes=rotate_bboxes)

    return result_image, result_bboxes

def warp_image_random(image, bboxes, context):
    """Changes the perspective viewing angles of an image by a random number"""
    theta_range = ast.literal_eval(context.getConfig("Perspective", "theta_range"))
    phi_range   = ast.literal_eval(context.getConfig("Perspective", "phi_range"))
    gamma_range = ast.literal_eval(context.getConfig("Perspective", "gamma_range"))
    step = int(context.getConfig("Perspective", "rotation_step"))
    fov = int(context.getConfig("Perspective", "field_of_view"))
    scale = float(context.getConfig("Perspective", "scale"))
    rotate_bboxes = context.getBoolean("Image", "rotate_bboxes")
    theta, phi, gamma = get_random_angles(theta_range, phi_range, gamma_range, step)

    result_image, result_bboxes = warp_image(image, theta, phi, gamma, scale, fov, bboxes, rotate_bboxes)
    return result_image, result_bboxes

#region bounding box operations
# TODO: Move bbox functions to utility?

def coords_to_bbox(coords):
    """Converts (x1,y1,x2,y2) rectangular coordinates to bounding box representation (cx,xy,w,h)"""
    width = (coords[2] - coords[0]) # w = x2 - x1
    height = (coords[3] - coords[1]) # h = y2 - y1
    center_x = coords[0] + (width / 2)
    center_y = coords[1] + (height / 2)
    return (center_x, center_y, width, height)

def bbox_to_coords(bbox):
    """Converts bounding box representation (cx,xy,w,h) to (x1,y1,x2,y2) coordinates"""
    cx, cy, w, h = bbox['cx'], bbox['cy'], bbox['w'], bbox['h']
    x1 = cx - w/2
    y1 = cy - h/2
    x2 = x1 + w
    y2 = y1 + h

    return (int(x1), int(y1), int(x2), int(y2))

def get_bbox_vertices(bbox):
    """Returns a clock-wise ordered list of vertices of a bbox, accounting for rotation"""
    bbox_coords = bbox_to_coords(bbox)
    bbox_vertices = [
        [bbox_coords[0], bbox_coords[1]],
        [bbox_coords[2], bbox_coords[1]],
        [bbox_coords[2], bbox_coords[3]],
        [bbox_coords[0], bbox_coords[3]]
    ]
    cx, cy, w, h = bbox['cx'], bbox['cy'], bbox['w'], bbox['h']
    angle = np.deg2rad(bbox['angle']) # Calculations need to be done in radians
    if angle != 0: # Calculate new coordinates if bbox is rotated
        for vertex in bbox_vertices:
            temp_x = vertex[0] - cx
            temp_y = vertex[1] - cy
            rot_x = (temp_x * math.cos(angle)) - (temp_y * math.sin(angle)) + cx
            rot_y = (temp_x * math.sin(angle)) + (temp_y * math.cos(angle)) + cy
            vertex[0] = rot_x
            vertex[1] = rot_y

    return bbox_vertices

def get_centroid(vertices):
    """Calculates polygon centroid from a list of vertices"""
    verts_x = [v[0] for v in vertices]
    verts_y = [v[1] for v in vertices]
    centroid_x = sum(verts_x) / len(vertices)
    centroid_y = sum(verts_y) / len(vertices)

    return [centroid_x, centroid_y]

#endregion


# -*- coding: utf-8 -*-
"""
Created on Fri Nov 16 12:01:07 2018

@author: Alexandre
"""

###############################################################################
import numpy as np
import matplotlib.pyplot as plt
###############################################################################
from pyro.dynamic import vehicle
###############################################################################
import advanced_vehicles
import test_vehicle_controllers
###############################################################################

# "Fake" controller - Varying inputs (delta, T_f, T_r) throughout time (change in linear.py)
ctl = test_vehicle_controllers.kinematicInputs()
# Vehicule dynamical system
sys = vehicle.KinematicBicyleModel()

# Set default wheel velocity and steering angle
cl_sys = ctl+sys

# Plot open-loop behavior (ex: np.array[intial_conditions], time_of_simulation)
cl_sys.x0 = np.array([0,0,0])
cl_sys.plot_trajectory()

# Rebuild x,u and t from simulation
x = cl_sys.traj.x
u = cl_sys.traj.u
t = cl_sys.traj.t

# Plot the vehicle's trajectory
figsize   = (7, 4)
dpi       = 100
plt.figure(2,figsize=figsize, dpi=dpi)
plt.axes(xlim=(-20,150), ylim=(-5,200))
plt.plot(x[:,0],x[:,1],'--g', label = 'Kinematic Model')
plt.legend(fontsize ='15')
plt.legend(fontsize ='15')
plt.title("Vehicle's cartesian position", fontsize=20)
plt.xlabel('X position (m)')
plt.ylabel('Y position (m)')
plt.show()

# Animate the simulation
cl_sys.animate_simulation()


#file where urls will be mapped to View functions
from django.urls import path
from . import views

# URLConfiguration module (every app in your project needs this)
# This needs to be imported into the main URLConfig of the project
urlpatterns = [
    path('', views.loadNsearchFunc, name="loadNsearchFunc"),
    path('save', views.saveFunc, name="saveFunc"),
    path('delete', views.deleteFunc, name="deleteFunc"),
    path('regauthor', views.regAuthorFunc, name="regAuthorFunc"),

]


# Import packages
import numpy as np
import pandas as pd

#######################################################################################################################
df_data = pd.read_pickle('data_process/conclusion/sample/hedonic_full_data.pkl')

# normalization 이 필요한 변수 select
indep_var = ['per_Pr', 'old', 'old_sq', 'car_per', 'area', 'room', 'toilet', 'floor', 'floor_sq',
             'FAR', 'BC', 'Efficiency', 'H1', 'H2', 'T1', 'T2', 'C1', 'first', 'log_num']

distance_var = ['dist_high', 'dist_sub', 'dist_park']

gu = []
len_gu = 25  # i
len_time = 24  # j
for i in range(len_gu):
    a = 'GU' + str(i+1)
    gu.append(a)

time = []
for i in range(len_time):
    b = 'Half' + str(i+1)
    time.append(b)

variables = indep_var + distance_var +gu[1:] + time[1:]

df_without = df_data[variables]

df_without.to_pickle('data_process/conclusion/NN/normalization_without_interaction.pkl')

#######################################################################################################################
# with interaction term
len_gu = 25  # i
len_time = 24  # j

inter = []
for i in range(len_gu):
    for j in range(len_time):
        c = 'i' + str(i+1) + ',' + str(j+1)
        inter.append(c)

variables2 = indep_var + distance_var + inter[1:]

df_with = df_data[variables2]

df_with.to_pickle('data_process/conclusion/NN/normalization_with_interaction.pkl')


"""
https://leetcode.com/problems/splitting-a-string-into-descending-consecutive-values/
"""

class Solution:
    def splitString(self, s: str) -> bool:
        def dfs(start: int, currSize: int, prevNum: int, currSplits: int):
            if start == N and currSplits >= 2:
                return True

            while start + currSize <= N:
                currNum = int(s[start: start + currSize])
                currSize += 1

                if prevNum != -1 and currNum != prevNum - 1:
                    continue

                if dfs(start + currSize - 1, 1, currNum, currSplits + 1):
                    return True

            return False

        N = len(s)
        return dfs(0, 1, -1, 0)


def grade_cal(n):
    if n < 0 or n > 100:
        print('Invalid score')
    else:
        if n >= 80:
            print('A')
        elif n >= 75:
            print('B+')
        elif n >= 70:
            print('B')
        elif n >= 65:
            print('C+')
        elif n >= 60:
            print('C')
        elif n >= 55:
            print('D+')
        elif n >= 50:
            print('D')
        else:
            print('F')

i = 0
n = int(input())
while i < n:
    grade_cal(int(input()))
    i += 1


import json
from typing import Any, Dict, Iterable, Iterator, List, Optional, Tuple

import requests

def query_sheet(
    document_id: str,
    sheet_name: str,
    tq: str = "SELECT *",
    cell_range: Optional[str] = None,
) -> Tuple[List[str], Iterable[Iterable[Any]]]:

    headers = {"X-DataSource-Auth": ""}
    params = {"tqx": "out:json", "tq": tq, "sheet": sheet_name}
    if cell_range is not None:
        params["range"] = cell_range
    data_resp = requests.get(
        f"https://docs.google.com/spreadsheets/d/{document_id}/gviz/tq",
        params=params,
        headers=headers,
    )

    sheet_data = json.loads(data_resp.text.split("\n", maxsplit=1)[1])
    cols = [c["label"] for c in sheet_data["table"]["cols"]]
    sheet_rows = sheet_data["table"]["rows"]

    return cols, ((v["v"] if v else None for v in record["c"]) for record in sheet_rows)

def query_sheet_tuples(*args: Any, **kwargs: Any) -> Iterator[Tuple[Any, ...]]:

    _, sheet_rows = query_sheet(*args, **kwargs)

    return (tuple(r) for r in sheet_rows)

def query_sheet_dicts(*args: Any, **kwargs: Any) -> Iterator[Dict[str, Any]]:

    cols, sheet_rows = query_sheet(*args, **kwargs)
    for record in sheet_rows:
        r_dict = dict(
            zip(
                cols,
                record,
            )
        )
        try:
            del r_dict[""]
        except KeyError:
            pass
        yield r_dict


from geometry_msgs.msg import Pose, PoseArray, Quaternion
from pf_base import PFLocaliserBase
import math
import rospy
import numpy as np
import random

from util import rotateQuaternion, getHeading
from random import random

from time import time

class PFLocaliser(PFLocaliserBase):

    def __init__(self):
        # Call the superclass constructor
        super(PFLocaliser, self).__init__()

        # Set motion model parameters
        self.ODOM_ROTATION_NOISE = 0.05 # Odometry model rotation noise
        self.ODOM_TRANSLATION_NOISE = 0.5 # Odometry model x axis (forward) noise
        self.ODOM_DRIFT_NOISE = 0.5 # Odometry model y axis (side-to-side) noise
        self.RESAMPING_NOISE = 0.005 # Noise added to resampled particle cloud

        # Sensor model parameters
        self.NUMBER_PREDICTED_READINGS = 20 	# Number of readings to predict
        self.NUMBER_PARTICLE = 2000 #Number of particle
        self.INDEX_MAX_WEIGHT = 0  #The max weight pose
        self.MEAN_MAX_WEIGHT_POSE_X = 0
        self.MEAN_MAX_WEIGHT_POSE_Y = 0
        self.VARIANCE_MAX_WEIGHT_POSE_X = 0
        self.VARIANCE_MAX_WEIGHT_POSE_X = 0
        self.NUMBER_HIGE_WEIGHT = 0
        self.ESTIMATE_MAX_WEIGHT = 0

    def initialise_particle_cloud(self, initialpose):
        # Set particle cloud to initialpose plus noise
        """
        Called whenever an initialpose message is received (to change the
        starting location of the robot), or a new occupancy_map is received.

        :Args:
            | initialpose: the initial pose estimate
        :Return:
            | (geometry_msgs.msg.PoseArray) poses of the particles
        """
        pose_array = PoseArray()

        for i in range(0,self.NUMBER_PARTICLE):
            temp = Pose()
            rnd = []
            for j in range(0,3):
                rnd.append(np.random.normal(0,1))

            temp.position.x = initialpose.pose.pose.position.x + self.ODOM_TRANSLATION_NOISE*rnd[0]
            temp.position.y = initialpose.pose.pose.position.y + self.ODOM_DRIFT_NOISE*rnd[1]
            temp.position.z = 0
            temp.orientation = rotateQuaternion(initialpose.pose.pose.orientation, rnd[2]*self.ODOM_ROTATION_NOISE*2*math.pi)
            pose_array.poses.extend([temp])
        return pose_array

    def update_particle_cloud(self, scan):
        # Update particlecloud, given map and laser scan
        # through Zt -> prior bel -> posterior bel
        """
        This should use the supplied laser scan to update the current
        particle cloud. I.e. self.particlecloud should be updated.

        :Args:
            | scan (sensor_msgs.msg.LaserScan): laser scan to use for update

         """
        resample_particlecloud = PoseArray()
        weight_particle = []
        cumulative_weight_particle = []
        high_weight_number = 0
        for i in range(0,self.NUMBER_PARTICLE):
            #for get weight
            weight_temp = self.sensor_model.get_weight(scan,self.particlecloud.poses[i])
            weight_particle.append(weight_temp)
            #resample delete low weight particle

        sum_weight = sum(weight_particle)
        for i in xrange(0,len(weight_particle)):
            cumulative_weight_particle.extend([weight_particle[i]/sum_weight])

        for i in xrange(0,self.NUMBER_PARTICLE):
            if cumulative_weight_particle[i] > 1/self.NUMBER_PARTICLE:
                resample_particlecloud.poses.extend([self.particlecloud.poses[i]])
                high_weight_number = high_weight_number+1

        # max_weight = np.where(cumulative_weight_particle == np.argmax(cumulative_weight_particle))
        # index_max_weight = max_weight[0][0]
        index_max_weight = np.argmax(cumulative_weight_particle)
        sum_pose_x =0
        sum_pose_y =0
        # sum_pose_x = sum(resample_particlecloud.poses.position.x)
        for i in range(0,high_weight_number):
            sum_pose_x = sum_pose_x + resample_particlecloud.poses[i].position.x
        for i in range(0,high_weight_number):
            sum_pose_y += resample_particlecloud.poses[i].position.y

        mean_x = sum_pose_x/high_weight_number
        mean_y = sum_pose_y/high_weight_number

        variance_x = abs(mean_x - self.particlecloud.poses[index_max_weight].position.x) / 8
        variance_y = abs(mean_y - self.particlecloud.poses[index_max_weight].position.y) / 8
        #generate new particles
        for num_particle in range(0,self.NUMBER_PARTICLE - high_weight_number):
            temp_pose = self.particlecloud.poses[index_max_weight]
            rnd_position_x = random.gauss(temp_pose.poses.position.x , variance_x)
            rnd_position_y = random.gauss(temp_pose.poses.position.y , variance_y)
            #noise
            rnd = []
            for j in range(0,3):
                rnd.append(np.random.normal(0,1))
            temp_pose.position.x = rnd_position_x + rnd[0]*self.RESAMPING_NOISE
            temp_pose.position.y = rnd_position_y + rnd[1]*self.RESAMPING_NOISE
            temp_pose.orientation = rotateQuaternion(temp_pose.orientation, rnd[2]*0.001*2*math.pi)
            resample_particlecloud.poses.extend([temp_pose])
        #transfer the max index of particle
        self.ESTIMATE_MAX_WEIGHT = self.particlecloud.poses[index_max_weight]

        #return the particlecloud
        self.particlecloud = resample_particlecloud
        #transfer parameter to
        self.INDEX_MAX_WEIGHT = index_max_weight
        self.MEAN_MAX_WEIGHT_POSE_X = mean_x
        self.MEAN_MAX_WEIGHT_POSE_Y = mean_y
        self.VARIANCE_MAX_WEIGHT_POSE_X = variance_x
        self.VARIANCE_MAX_WEIGHT_POSE_X = variance_y
        self.NUMBER_HIGE_WEIGHT = high_weight_number
        '''#get 500 particle that have high w
        for m in xrange(0,500):
            max_weight=np.where(weight_particle == np.max(weight_particle))
                for i in xrange(len(max_weight[0])):
                    temp = max_weight[0][i] - i
                    particlecloud_temp.poses.append(particlecloud.poses[temp])
                    del weight_particle[temp]`
                m = m + len(max_weight[0])
        '''

    def estimate_pose(self):
        # Create new estimated pose, given particle cloud
        # E.g. just average the location and orientation values of each of
        # the particles and return this.

        # Better approximations could be made by doing some simple clustering,
        # e.g. taking the average location of half the particles after
        # throwing away any which are outliers
        """
        This should calculate and return an updated robot pose estimate based
        on the particle cloud (self.particlecloud).

        :Return:
            | (geometry_msgs.msg.Pose) robot's estimated pose.
         """

        #function 1 : weight_resample particlecloud
        pose = Pose()

        for i in xrange(0,self.NUMBER_PARTICLE - self.NUMBER_HIGE_WEIGHT):
            temp_pose = self.ESTIMATE_MAX_WEIGHT.poses
            temp_pose.position.x = np.random.normal(temp_pose.position.x , self.VARIANCE_MAX_WEIGHT_POSE_X)
            temp_pose.position.y = np.random.normal(temp_pose.position.y , self.VARIANCE_MAX_WEIGHT_POSE_Y)
            temp_pose.orientation = temp_pose.orientation
            pose.extend([temp_pose])
        #rospy.loginfo(pose)
        return pose

        #function 2 :max_weight
        # pose = self.ESTIMATE_MAX_WEIGHT.poses
        # return pose

        #function3 : Average positon
        # pose = self.ESTIMATE_MAX_WEIGHT.poses
        # mean_x = np.average(self.particlecloud.poses.position.x)
        # mean_y = np.average(self.particlecloud.poses.position.y)
        # pose.position.x = mean_x
        # pose.position.y = mean_y
        # return pose


#Battleship game

from random import randint

#empty board
board = []
#set up a board to display the answer
answer_board = []
#set column and row size (square)
grid_size = 8
#set number of battleships
battleships = 3
#set up list of co-ordinates for ships
ships_list = []

#create a grid
def create_grid(board):
    for i in range(0, grid_size):
        board.append(["O"] * grid_size)

#print the grid
def print_board(board):
    for row in board:
        print(" ".join(row))

#select random row and column for ship to be in
def random_row(board):
    return randint(0, len(board) - 1)

def random_col(board):
    return randint(0, len(board[0]) - 1)

create_grid(board)
create_grid(answer_board)

#Create the battleships and set location of ships
for i in range(battleships):
    #how many ships have been created
    number_of_ships = len(ships_list)
    #while the number of ships hasn't been reached, create new ships
    while not number_of_ships == battleships:
        ship_row = random_row(board)
        ship_col = random_col(board)
        if (ship_row, ship_col) not in ships_list:
            ships_list.append((ship_row, ship_col))
            answer_board[ship_row][ship_col] = "S"
            break

#print ships_list for debugging
#print(ships_list)

print("Let's play Battleship")
print_board(board)

#playing the game - input row and column, set number of guesses allowed
guesses = 4
for i in range(guesses):
    #make a guess
    print("Turn", i + 1)
    #the -1 is to take account of the user input starting at 1 not 0
    #ensure that only integers are input
    while True:
        try:
            guess_row = int(input("Guess Row:")) - 1
            guess_col = int(input("Guess Col:")) - 1
        except ValueError:
            print("That was not a number.")
            continue
        break

    if (guess_row, guess_col) in ships_list: #correct guess
        if board[guess_row][guess_col] == "*": #have we guessed this correctly already
            print("Already sunk.")
        else:
            print("Congratulations! You sank a battleship!")
            board[guess_row][guess_col] = "*"
            print_board(board)
            if sum(i.count("*") for i in board) == battleships:
                print("You Win!!")
                break
    else:
        if guess_row not in range(grid_size) or guess_col not in range(grid_size): #guess not on the board
            print("Oops, that\'s not even in the ocean.")
        elif board[guess_row][guess_col] == "X": #have we guessed this already
            print("You guessed that one already.")
        else:
            print("You missed my battleship!") #incorrect guess
            board[guess_row][guess_col] = "X"
            print_board(board)

    #checks if user out of guesses and if so displays the answer
    if i == guesses - 1:
        print("Game Over")
        print("The answer is:")
        print_board(answer_board)

'''You can also add on to your Battleship! program to make it more complex and fun to play. Here are some ideas for enhancements—maybe you can think of some more!

    Make multiple battleships: you'll need to be careful because you need to make sure that you don’t place battleships on top of each other on the game board. You'll also want to make sure that you balance the size of the board with the number of ships so the game is still challenging and fun to play.
    IDEA - Add user input to determine the size of the board and the number of battleships??
    Make battleships of different sizes: this is trickier than it sounds. All the parts of the battleship need to be vertically or horizontally touching and you’ll need to make sure you don’t accidentally place part of a ship off the side of the board.

    Make your game a two-player game.

    Use functions to allow your game to have more features like rematches, statistics and more!
'''