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

import re
import os
import numpy as np
import sqlite3
import pandas as pd

from .plotter import colors
from .plotter import _init_plot
from .plotter import _close_plot
from .dbmgr import get_cursor
from .isotope import Isotope

global ZG_CONNECTIONS_DICT
ZG_CONNECTIONS_DICT = {}

#### TODO: allow new compound definitions as {'El':wt_pct} and accept .json stack

class Ziegler(object):
	"""Method for solving energy loss within stacked-target foil experiment

	...

	Parameters
	----------
	x : type
		Description of parameter `x`.

	Attributes
	----------

	Methods
	-------

	Notes
	-----

	References
	----------

	Examples
	--------

	"""

	def __init__(self, stack, beam=None, **kwargs):
		### stack is list of dicts, which must have 'compound' specified.
		### Areal density specified by either 'ad' (mg/cm^2), both mass (g) and area (cm^2),
		### 'thickness' (mm) or both 'density' (g/cm^3) and 'thickness' (mm)

		zdb = get_cursor('ziegler')

		self.protons = {int(i[0]):list(map(float,i[1:])) for i in zdb.execute('SELECT * FROM protons')}
		self.helium = {int(i[0]):list(map(float,i[1:])) for i in zdb.execute('SELECT * FROM helium')}
		self.ionization = {int(i[0]):list(map(float,i[1:])) for i in zdb.execute('SELECT * FROM ionization')}
		self.weights = {int(i[0]):list(map(float,i[1:3])) for i in zdb.execute('SELECT * FROM weights')}
		self.compounds = {str(i[0]):[[int(h.split(':')[0]), float(h.split(':')[1])] for h in i[2].split(',')] for i in zdb.execute('SELECT * FROM compounds')}
		self.compounds = {cm:[[i[0],i[1]/sum([m[1] for m in self.compounds[cm]])] for i in self.compounds[cm]] for cm in self.compounds}
		self.densities = {str(i[0]):float(i[1]) for i in zdb.execute('SELECT * FROM compounds')}
		self.elements = sorted([[str(i[0]), int(i[2].split(':')[0])] for i in zdb.execute('SELECT * FROM compounds') if len(i[2].split(':'))==2], key=lambda h:len(h[0]), reverse=True)

		self._meta = {}
		self.meta = {'beam_istp':'1H', 'E0':33.0, 'dE0':0.3, 'N':10000, 'dp':1.0,
						'chunk_size':1E7, 'threads':1,
						'solved':False, 'accuracy':0.01, 'min_steps':2, 'max_steps':50}
		self._stack = []
		if beam is not None:
			print('Keyword `beam` deprecated: see documentation for proper input.')
			self.meta = beam
		self.meta = kwargs
		if type(stack)==str:
			stack = pd.read_csv(stack)
			stack = [{i:r[i] for i in stack.columns if not (np.isnan(r[i]) if type(r[i])==float else False)} for n,r in stack.iterrows()]
		self.stack = stack

	def check_db(self, db=None):
		if db is not None:
			path, fnm = os.path.split(db)
			if path in ['',' ']:
				path = os.getcwd()
			if fnm in ['',' ']:
				raise ValueError('Invalid db Filename: {}'.format(db))
			db_fnm = os.path.join(path, fnm)

			global ZG_CONNECTIONS_DICT
			if os.path.exists(db_fnm):
				if db_fnm not in ZG_CONNECTIONS_DICT:
					ZG_CONNECTIONS_DICT[db_fnm] = sqlite3.connect(db_fnm)
				self.db_connection = ZG_CONNECTIONS_DICT[db_fnm]
				self.db = self.db_connection.cursor()
			else:
				print('WARNING: DB {} does not exist, creating new file.'.format(fnm))
				from sqlite3 import Error
				try:
					self.db_connection = sqlite3.connect(db_fnm)
					ZG_CONNECTIONS_DICT[db_fnm] = self.db_connection
					self.db = self.db_connection.cursor()
				except Error as e:
					print(e)

	@property
	def meta(self):
		return self._meta

	@meta.setter
	def meta(self, meta_dict):
		for nm in meta_dict:
			self._meta[nm] = meta_dict[nm]
			if nm in ['beam_istp', 'istp']:
				_ITP = Isotope(meta_dict[nm])
				self._meta['Z'] = _ITP.Z
				self._meta['amu'] = _ITP.mass
		if self._meta['min_steps']>self._meta['max_steps']:
			self._meta['max_steps'] = self._meta['min_steps']+1
			print('WARNING: min_steps > max_steps, setting max_steps to {}'.format(self._meta['max_steps']))
		self._meta['solved'] = False

	def __getitem__(self, key):
		if type(key)==str:
			for s in self.stack:
				if s['name']==key:
					return s
			return None
		else:
			return self.stack[int(key)]

	@property
	def stack(self):
		if not self.meta['solved']:
			self._solve()
		return self._stack

	@stack.setter
	def stack(self, _stack):
		self._stack = list(_stack)
		self._meta['solved'] = False
		for s in self._stack:
			if 'name' not in s:
				s['name'] = None
			if 'compound' not in s:
				raise ValueError('compound must be specified')

			if type(s['compound'])==dict:
				cs = ''
				for c in s['compound']:
					cs = c
					self.compounds[c] = s['compound'][c]
				s['compound'] = cs
				if s['compound']=='':
					raise ValueError('compound must be specified')

			if 'ad' not in s:
				if 'area' in s and 'mass' in s:
					s['ad'] = 1e3*s['mass']/s['area']
				elif 'density' in s and 'thickness' in s:
					s['ad'] = 100.0*s['density']*s['thickness']
				elif s['compound'] in self.densities and 'thickness' in s:
					s['ad'], s['density'] = 100.0*self.densities[s['compound']]*s['thickness'], self.densities[s['compound']]

			if 'density' not in s:
				if s['compound'] in self.densities:
					s['density'] = self.densities[s['compound']]
					if 'thickness' not in s and 'ad' in s:
						s['thickness'] = s['ad']/(100.0*s['density'])

			if 'ad' not in s:
				raise ValueError('Areal density either not specified or not computable: {}'.format(s))

			if s['compound'] not in self.compounds:
				cm = s['compound']
				nums = [str(i) for i in range(10)]
				cm_list = []
				for el,Z in self.elements:
					if len(cm)==0:
						break
					f = cm.split(el)
					if len(f)>1:
						c = f[0]
						for e in f[1:]:
							if len(e):
								if e[0] in nums:
									cm_list.append([Z, float(e[0])])
									if len(e)>1:
										c += e[1:]
								else:
									cm_list.append([Z, 1.0])
									c += e
							else:
								cm_list.append([Z, 1.0])
						cm = c
				if len(cm)==0 and len(cm_list):
					self.compounds[s['compound']] = [[i[0], i[1]/sum([i[1] for i in cm_list])] for i in cm_list]

			if s['compound'] not in self.compounds:
				raise ValueError('compound {} not known.'.format(s['compound']))

	def get_S(self, E, cm):
		"""Description

		...

		Parameters
		----------
		x : type
			Description of parameter `x`.

		Returns
		-------

		Notes
		-----

		References
		----------

		Examples
		--------

		"""

		# energy E in MeV , stopping power in MeV/(mg/cm2)
		E, r0 = np.asarray(E), False
		if not E.shape:
			E, r0 = np.array([E]), True
		S = np.zeros(len(E))
		A_ave = sum([self.weights[z2][0]*w for z2,w in self.compounds[cm]])
		for z2, w in self.compounds[cm]:
			S_nucl = self.get_S_nucl(E, self.meta['Z'], self.meta['amu'], z2, self.weights[z2][0])
			if self.meta['Z']==1:
				S += w*(S_nucl+self.get_S_p(E, z2, self.meta['amu']))
			elif self.meta['Z']==2:
				S += w*(S_nucl+self.get_S_He(E, z2, self.meta['amu']))
			else:
				S += w*(S_nucl+self.get_S_elec(E, z2, self.meta['amu'], self.meta['Z']))
		return S[0]*0.6022140857/A_ave if r0 else S*0.6022140857/A_ave

	def get_S_nucl(self, E, z1, m1, z2, m2):
		RM = (m1+m2)*np.sqrt((z1**(2/3.0)+z2**(2/3.0)))
		ER = 32.53*m2*1E3*E/(z1*z2*RM)
		return (0.5*np.log(1.0+ER)/(ER+0.10718+ER**0.37544))*8.462*z1*z2*m1/RM

	def get_S_p(self, eng, z2, M1=1.00727647):
		S = np.zeros(len(eng))
		E = 1E3*eng/M1
		A = self.protons[z2]
		beta_sq = np.where(E>=1E3,1.0-1.0/(1.0+E/931478.0)**2,0.9)
		B0 = np.where(E>=1E3,np.log(A[6]*beta_sq/(1.0-beta_sq))-beta_sq,0.0)
		Y = np.log(E[(1E3<=E)&(E<=5E4)])
		B0[np.nonzero(np.where((1E3<=E)&(E<=5E4),B0,0))] -= A[7]+A[8]*Y+A[9]*Y**2+A[10]*Y**3+A[11]*Y**4
		S[E>=1E3] = (A[5]/beta_sq[E>=1E3])*B0[E>=1E3]
		S_low = A[1]*E[(10<=E)&(E<1E3)]**0.45
		S_high = (A[2]/E[(10<=E)&(E<1E3)])*np.log(1.0+(A[3]/E[(10<=E)&(E<1E3)])+A[4]*E[(10<=E)&(E<1E3)])
		S[(10<=E)&(E<1E3)] = S_low*S_high/(S_low+S_high)
		S[(0<E)&(E<10)] = A[0]*E[(0<E)&(E<10)]**0.5
		return S

	def get_S_He(self,eng,z2,M1=4.003):
		S = np.zeros(len(eng))
		E = eng*4.0015/M1
		E = np.where(E>=0.001,E,0.001)
		A = self.helium[z2]
		S_low = A[0]*(1E3*E[E<=10])**A[1]
		S_high = (A[2]/E[E<=10])*np.log(1.0+(A[3]/E[E<=10])+A[4]*E[E<=10])
		S[E<=10] = S_low*S_high/(S_low+S_high)
		Y = np.log(1.0/E[E>10])
		S[E>10] = np.exp(A[5]+A[6]*Y+A[7]*Y**2+A[8]*Y**3)
		return S

	def get_S_elec(self, eng, z2, M1, z1):
		S = np.zeros(len(eng))
		E_keV = 1E3*eng
		S[E_keV/M1<1000] = self.get_eff_Z_ratio(E_keV[E_keV/M1<1000],z1,M1)**2*self.get_S_p(eng[E_keV/M1<1000],z2,M1)
		Y = E_keV[E_keV/M1>=1000]/M1
		beta_sq = 1.0-1.0/(1.0+Y/931478.0)**2
		FX = np.log(2E6*0.511003*beta_sq/(1.0-beta_sq))-beta_sq
		ZHY = 1.0-np.exp(-0.2*np.sqrt(Y)-0.0012*Y-0.00001443*Y**2)
		Z1EFF = self.get_eff_Z_ratio(E_keV[E_keV/M1>=1000],z1,M1)*ZHY
		S[E_keV/M1>=1000] = 4E-1*np.pi*(1.9732857/137.03604)**2*Z1EFF**2*z2*(FX-np.log(self.ionization[z2][0]))/(0.511003*beta_sq)
		return S

	def get_eff_Z_ratio(self, E_keV, z1, M1):
		if z1==1:
			return np.ones(len(eng))
		elif z1==2:
			Y = np.log(E_keV/M1)
			return z1*(1.0-np.exp(-0.7446-0.1429*Y-0.01562*Y**2+0.00267*Y**3-0.000001325*Y**8))
		elif z1==3:
			Y = E_keV/M1
			return z1*(1.0-np.exp(-0.7138-0.002797*Y-0.000001348*Y**2))
		BB = -0.886*np.sqrt(0.04*E_keV/M1)/z1**(2/3.0)
		return z1*(1.0-np.exp(BB-0.0378*np.sin(0.5*np.pi*BB))*(1.034-0.1777*np.exp(-0.08114*z1)))

	def _calc_bins(self):
		return np.arange(0.0, self.meta['E0']+10.0*self.meta['dE0'], min([0.1, self.meta['E0']/500.0]))

	def _solve_chunk(self, N):
		E0 = self.meta['E0']+self.meta['dE0']*np.random.normal(size=int(N))
		bins = self._calc_bins()
		hists = []
		dp = self.meta['dp']
		for n, sm in enumerate(self._stack):
			E_bar = [E0]
			if np.average(E0)<=0.0:
				hists.append(np.concatenate([[N],np.zeros(len(bins)-2)]))
			else:
				steps = int((1.0/self.meta['accuracy'])*sm['ad']*dp*self.get_S(np.average(E0), sm['compound'])/np.average(E0))
				steps = min([max([self.meta['min_steps'], steps]), self.meta['max_steps']])
				dr = (1.0/float(steps))
				for i in range(steps):
					S1 = self.get_S(E0, sm['compound'])
					E1 = E0 - dr*dp*sm['ad']*S1
					E1 = np.where(E1>0, E1, 0.0)
					E0 = E0 - dr*0.5*dp*sm['ad']*(S1+self.get_S(E1, sm['compound']))
					E0 = np.where(E0>0, E0, 0.0)
					E_bar.append(E0)
				hists.append(np.histogram(np.concatenate(E_bar), bins=bins)[0])
		return hists

	def _solve(self):
		if self.meta['solved']:
			return
		self.meta['solved'] = True
		dN = np.linspace(0, self.meta['N'], int(np.ceil(self.meta['N']/float(self.meta['chunk_size'])))+1, dtype=int)
		histos = list(map(self._solve_chunk, dN[1:]-dN[:-1]))

		bins = self._calc_bins()
		energy = 0.5*(bins[1:]+bins[:-1])
		for n,sm in enumerate(self._stack):
			sm['flux'] = np.sum([h[n] for h in histos], axis=0)
			sm['flux'] = sm['flux']/np.sum(sm['flux'])
			sm['mu_E'] = np.sum(sm['flux']*energy)
			sm['sig_E'] = np.sqrt(np.sum(sm['flux']*(energy-sm['mu_E'])**2))
			lh = np.where(sm['flux']>0)[0]
			if lh.size:
				if lh[0]==0:
					nm = sm['name'] if sm['name'] is not None else sm['compound']+str(n+1)
					print('WARNING: Beam stopped in foil {}'.format(nm))
			sm['flux'] = sm['flux'][lh[0]:lh[-1]]
			sm['energy'] = energy[lh[0]:lh[-1]]
			sm['bins'] = bins[lh[0]:lh[-1]+1]

	def saveas(self, *fnms):
		"""Description

		...

		Parameters
		----------
		x : type
			Description of parameter `x`.

		Returns
		-------

		Notes
		-----

		References
		----------

		Examples
		--------

		"""

		cols = ['name','compound','thickness','density','ad','mu_E','sig_E']
		stack = pd.DataFrame([{c:(sm[c] if c in sm else None) for c in cols} for sm in self.stack], columns=cols)
		cols = ['name','energy','flux']
		fluxes = pd.concat([pd.DataFrame({c:sm[c] for c in cols}, columns=cols) for sm in self.stack if sm['name'] is not None], ignore_index=True)
		for fl in fnms:
			if any([fl.endswith(e) for e in ['.png','.pdf','.eps','.pgf','.ps','.raw','.rgba','.svg','.svgz']]):
				self.plot(saveas=fl, show=False)
			if fl.endswith('.csv'):
				stack.to_csv(fl.replace('.csv','_stack.csv'), index=False)
				fluxes.to_csv(fl.replace('.csv','_fluxes.csv'), index=False)
			if fl.endswith('.db'):
				self.check_db(fl)
				stack.to_sql('stack', self.db_connection, if_exists='replace', index=False)
				fluxes.to_sql('fluxes', self.db_connection, if_exists='replace', index=False)

	def summarize(self, samples=None):
		"""Description

		...

		Parameters
		----------
		x : type
			Description of parameter `x`.

		Returns
		-------

		Notes
		-----

		References
		----------

		Examples
		--------

		"""

		for n,sm in enumerate(self.stack):
			if sm['name'] is not None:
				if samples is not None:
					if not any([re.match(s, sm['name']) for s in samples]):
						continue
				nm = sm['name'] if sm['name'] is not None else sm['compound']+str(n+1)
				print(nm+': '+str(round(sm['mu_E'], 2))+' +/- '+str(round(sm['sig_E'], 2))+' (MeV)')

	def plot_S(self, compound, energy=None, **kwargs):
		"""Description

		...

		Parameters
		----------
		x : type
			Description of parameter `x`.

		Returns
		-------

		Notes
		-----

		References
		----------

		Examples
		--------

		"""

		if energy is None:
			energy = 10.0**np.arange(0.1,2.8,0.1)
		f,ax = _init_plot(**kwargs)
		ax.plot(energy, self.get_S(energy, compound), label=compound.title())
		ax.set_xlabel('Energy (MeV)')
		ax.set_ylabel(r'Stopping Power (MeV$\cdot$mg$^{-1}\cdot$cm$^{-2}$)')
		ax.set_xscale('log')
		ax.legend(loc=0)
		return _close_plot(f, ax, **kwargs)

	def plot(self, samples=None,  **kwargs):
		"""Description

		...

		Parameters
		----------
		x : type
			Description of parameter `x`.

		Returns
		-------

		Notes
		-----

		References
		----------

		Examples
		--------

		"""

		if type(samples)==str:
			samples = [samples]
		f,ax = _init_plot(**kwargs)
		for sm in self.stack:
			if sm['name'] is not None:
				if samples is not None:
					if not any([re.match(s, sm['name']) for s in samples]):
						continue
				x, y = np.array([sm['bins'][:-1],sm['bins'][1:]]).T.flatten(), np.array([sm['flux'],sm['flux']]).T.flatten()

				ax.plot(x,y,label=sm['name'])

		ax.set_xlabel('Energy (MeV)')
		ax.set_ylabel('Flux (a.u.)')
		ax.legend(loc=0)
		return _close_plot(f, ax, **kwargs)


from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
from builtins import str
from past.utils import old_div
import anuga
import numpy as num
import math
from . import parallel_inlet_enquiry
from anuga.utilities import parallel_abstraction as pypar

from anuga.utilities.system_tools import log_to_file
from anuga.utilities.numerical_tools import ensure_numeric
from anuga.structures.inlet_enquiry import Inlet_enquiry

class Parallel_Structure_operator(anuga.Operator):
    """Parallel Structure Operator - transfer water from one rectangular box to another.
    Sets up the geometry of problem

    This is the base class for structures (culverts, pipes, bridges etc) that exist across multiple
    parallel shallow water domains. Inherit from this class (and overwrite discharge_routine method
    for specific subclasses)

    Input: Two points, pipe_size (either diameter or width, depth),
    mannings_rougness,
    """

    counter = 0

    """
     ===========================================================================================
     PETE: Inputs to this constructor are identical to the serial
     structure operator, except for the following arguments:
     master_proc - the processor that coordinates all processors (with domains) associated with this structure [INT]
     procs - the list of processors associated with thisstructure (List[INT])
     inlet_master_proc - master_proc of the first and second inlet (List[2])
     inlet_procs - list of processors associated with the first and second inlet (LIST[2][INT])
     enquiry_proc - processor associated the first and second enquiry point (List[2])
    """

    def __init__(self,
                 domain,
                 end_points,
                 exchange_lines,
                 enquiry_points,
                 invert_elevations,
                 width,
                 height,
                 diameter,
                 z1,
                 z2,
                 blockage,
                 barrels,
                 apron,
                 manning,
                 enquiry_gap,
                 use_momentum_jet,
                 zero_outflow_momentum,
                 use_old_momentum_method,
                 always_use_Q_wetdry_adjustment,
                 force_constant_inlet_elevations,
                 description,
                 label,
                 structure_type,
                 logging,
                 verbose,
                 master_proc = 0,
                 procs = None,
                 inlet_master_proc = [0,0],
                 inlet_procs = None,
                 enquiry_proc = None):

        self.myid = pypar.rank()
        self.num_procs = pypar.size()

        anuga.Operator.__init__(self,domain)

        # Allocate default processor associations if not specified in arguments
        # although we assume that such associations are provided correctly by the
        # parallel_operator_factory.

        self.master_proc = master_proc
        self.inlet_master_proc = inlet_master_proc

        if procs is None:
            self.procs = [master_proc]
        else:
            self.procs = procs

        if inlet_procs is None:
            self.inlet_procs = [[inlet_master_proc[0]],[inlet_master_proc[0]]]
        else:
            self.inlet_procs = inlet_procs

        if enquiry_proc is None:
            self.enquiry_proc = [[inlet_master_proc[0]],[inlet_master_proc[0]]]
        else:
            self.enquiry_proc = enquiry_proc

        self.end_points = ensure_numeric(end_points)
        self.exchange_lines = ensure_numeric(exchange_lines)
        self.enquiry_points = ensure_numeric(enquiry_points)
        self.invert_elevations = ensure_numeric(invert_elevations)

        assert (width is not None and diameter is None) or (width is None and diameter is not None)

        if width is None:
            width = diameter

        if diameter is None:
            diameter = width

        if height is None:
            height = width

        if apron is None:
            apron = width

        self.width  = width
        self.height = height
        self.diameter = diameter
        self.z1 = z1
        self.z2 = z2
        self.blockage = blockage
        self.barrels = barrels
        self.apron  = apron
        self.manning = manning
        self.enquiry_gap = enquiry_gap
        self.use_momentum_jet = use_momentum_jet
        self.zero_outflow_momentum = zero_outflow_momentum
        if use_momentum_jet and zero_outflow_momentum:
            msg = "Can't have use_momentum_jet and zero_outflow_momentum both True"
            raise Exception(msg)
        self.use_old_momentum_method = use_old_momentum_method
        self.always_use_Q_wetdry_adjustment = always_use_Q_wetdry_adjustment

        if description is None:
            self.description = ' '
        else:
            self.description = description

        if label is None:
            self.label = "structure_%g" % Parallel_Structure_operator.counter + "_P" + str(self.myid)
        else:
            self.label = label + '_%g' % Parallel_Structure_operator.counter + "_P" + str(self.myid)

        if structure_type is None:
            self.structure_type = 'generic structure'
        else:
            self.structure_type = structure_type

        self.verbose = verbose

        # Keep count of structures
        if self.myid == master_proc:
            Parallel_Structure_operator.counter += 1

        # Slots for recording current statistics
        self.accumulated_flow = 0.0
        self.discharge = 0.0
        self.discharge_abs_timemean = 0.0
        self.velocity = 0.0
        self.outlet_depth = 0.0
        self.delta_total_energy = 0.0
        self.driving_energy = 0.0

        if exchange_lines is not None:
            self.__process_skew_culvert()
        elif end_points is not None:
            self.__process_non_skew_culvert()
        else:
            raise Exception('Define either exchange_lines or end_points')

        self.inlets = []

        # Allocate parallel inlet enquiry, assign None if processor is not associated with particular
        # inlet.

        if self.myid in self.inlet_procs[0]:
            line0 = self.exchange_lines[0]
            if self.apron is None:
                poly0 = line0
            else:
                offset = -self.apron*self.outward_vector_0
                poly0 = num.array([ line0[0], line0[1], line0[1]+offset, line0[0]+offset])

            if self.invert_elevations is None:
                invert_elevation0 = None
            else:
                invert_elevation0 = self.invert_elevations[0]

            enquiry_point0 = self.enquiry_points[0]
            outward_vector0 = self.culvert_vector

            self.inlets.append(parallel_inlet_enquiry.Parallel_Inlet_enquiry(
                               self.domain,
                               line0,
                               enquiry_point0,
                               invert_elevation = invert_elevation0,
                               outward_culvert_vector = outward_vector0,
                               master_proc = self.inlet_master_proc[0],
                               procs = self.inlet_procs[0],
                               enquiry_proc = self.enquiry_proc[0],
                               verbose = self.verbose))

            if force_constant_inlet_elevations:
                # Try to enforce a constant inlet elevation
                inlet_global_elevation = self.inlets[-1].get_global_average_elevation()
                self.inlets[-1].set_elevations(inlet_global_elevation)

        else:
            self.inlets.append(None)

        if self.myid in self.inlet_procs[1]:
            line1 = self.exchange_lines[1]
            if self.apron is None:
                poly1 = line1
            else:
                offset = -self.apron*self.outward_vector_1
                poly1 = num.array([ line1[0], line1[1], line1[1]+offset, line1[0]+offset])

            if self.invert_elevations is None:
                invert_elevation1 = None
            else:
                invert_elevation1 = self.invert_elevations[1]

            enquiry_point1 = self.enquiry_points[1]
            outward_vector1  = - self.culvert_vector

            self.inlets.append(parallel_inlet_enquiry.Parallel_Inlet_enquiry(
                               self.domain,
                               line1,
                               enquiry_point1,
                               invert_elevation = invert_elevation1,
                               outward_culvert_vector = outward_vector1,
                               master_proc = self.inlet_master_proc[1],
                               procs = self.inlet_procs[1],
                               enquiry_proc = self.enquiry_proc[1],
                               verbose = self.verbose))

            if force_constant_inlet_elevations:
                # Try to enforce a constant inlet elevation
                inlet_global_elevation = self.inlets[-1].get_global_average_elevation()
                self.inlets[-1].set_elevations(inlet_global_elevation)

        else:
            self.inlets.append(None)

        self.inflow_index = 0
        self.outflow_index = 1

        self.set_parallel_logging(logging)

    def __call__(self):

        timestep = self.domain.get_timestep()

        Q, barrel_speed, outlet_depth = self.discharge_routine()

        # Get attributes of Inflow inlet, all procs associated with inlet must call
        if self.myid in self.inlet_procs[self.inflow_index]:
            old_inflow_depth = self.inlets[self.inflow_index].get_global_average_depth()
            old_inflow_stage = self.inlets[self.inflow_index].get_global_average_stage()
            old_inflow_xmom = self.inlets[self.inflow_index].get_global_average_xmom()
            old_inflow_ymom = self.inlets[self.inflow_index].get_global_average_ymom()
            inflow_area = self.inlets[self.inflow_index].get_global_area()

        # Master proc of inflow inlet sends attributes to master proc of structure
        if self.myid == self.master_proc:
            if self.myid != self.inlet_master_proc[self.inflow_index]:
                old_inflow_depth = pypar.receive(self.inlet_master_proc[self.inflow_index])
                old_inflow_stage = pypar.receive(self.inlet_master_proc[self.inflow_index])
                old_inflow_xmom = pypar.receive(self.inlet_master_proc[self.inflow_index])
                old_inflow_ymom = pypar.receive(self.inlet_master_proc[self.inflow_index])
                inflow_area = pypar.receive(self.inlet_master_proc[self.inflow_index])
        elif self.myid == self.inlet_master_proc[self.inflow_index]:
            pypar.send(old_inflow_depth, self.master_proc)
            pypar.send(old_inflow_stage, self.master_proc)
            pypar.send(old_inflow_xmom, self.master_proc)
            pypar.send(old_inflow_ymom, self.master_proc)
            pypar.send(inflow_area, self.master_proc)

        # Implement the update of flow over a timestep by
        # using a semi-implict update. This ensures that
        # the update does not create a negative depth

        # Master proc of structure only
        if self.myid == self.master_proc:
            if old_inflow_depth > 0.0 :
                dt_Q_on_d = old_div(timestep*Q,old_inflow_depth)
            else:
                dt_Q_on_d = 0.0

            # Check whether we should use the wet-dry Q adjustment (where Q is
            # multiplied by new_inflow_depth/old_inflow_depth)
            always_use_Q_wetdry_adjustment = self.always_use_Q_wetdry_adjustment
            # Always use it if we are near wet-dry
            use_Q_wetdry_adjustment = ((always_use_Q_wetdry_adjustment) |\
                (old_inflow_depth*inflow_area <= Q*timestep))

            factor = 1.0/(1.0 + old_div(dt_Q_on_d,inflow_area))

            if use_Q_wetdry_adjustment:
                new_inflow_depth = old_inflow_depth*factor
                if old_inflow_depth > 0.:
                    timestep_star = old_div(timestep*new_inflow_depth,old_inflow_depth)
                else:
                    timestep_star = 0.
            else:
                new_inflow_depth = old_inflow_depth - old_div(timestep*Q,inflow_area)
                timestep_star = timestep

            #new_inflow_xmom = old_inflow_xmom*factor
            #new_inflow_ymom = old_inflow_ymom*factor
            if(self.use_old_momentum_method):
                # This method is here for consistency with the old version of the
                # routine
                new_inflow_xmom = old_inflow_xmom*factor
                new_inflow_ymom = old_inflow_ymom*factor

            else:
                # For the momentum balance, note that Q also transports the velocity,
                # which has an average value of new_inflow_mom/depth (or old_inflow_mom/depth).
                #
                #     new_inflow_xmom*inflow_area =
                #     old_inflow_xmom*inflow_area -
                #     timestep*Q*(new_inflow_xmom/old_inflow_depth)
                # and:
                #     new_inflow_ymom*inflow_area =
                #     old_inflow_ymom*inflow_area -
                #     timestep*Q*(new_inflow_ymom/old_inflow_depth)
                #
                # The choice of new_inflow_mom in the final term might be
                # replaced with old_inflow_mom.
                #
                # The units balance: (m^2/s)*(m^2) = (m^2/s)*(m^2) - s*(m^3/s)*(m^2/s)*(m^(-1))
                #
                if old_inflow_depth > 0.:
                    if use_Q_wetdry_adjustment:
                        factor2 = 1.0/(1.0 + old_div(dt_Q_on_d*new_inflow_depth,(old_inflow_depth*inflow_area)))
                    else:
                        factor2 = 1.0/(1.0 + old_div(timestep*Q,(old_inflow_depth*inflow_area)))
                else:
                    factor2 = 0.

                new_inflow_xmom = old_inflow_xmom*factor2
                new_inflow_ymom = old_inflow_ymom*factor2

        # Master proc of structure sends new inflow attributes to all inflow inlet processors

        if self.myid == self.master_proc:
            for i in self.inlet_procs[self.inflow_index]:
                if i == self.master_proc: continue
                pypar.send(new_inflow_depth, i)
                pypar.send(new_inflow_xmom, i)
                pypar.send(new_inflow_ymom, i)
        elif self.myid in self.inlet_procs[self.inflow_index]:
            new_inflow_depth = pypar.receive(self.master_proc)
            new_inflow_xmom = pypar.receive(self.master_proc)
            new_inflow_ymom = pypar.receive(self.master_proc)

        # Inflow inlet procs sets new attributes
        if self.myid in self.inlet_procs[self.inflow_index]:
            self.inlets[self.inflow_index].set_depths(new_inflow_depth)
            self.inlets[self.inflow_index].set_xmoms(new_inflow_xmom)
            self.inlets[self.inflow_index].set_ymoms(new_inflow_ymom)

        # Get outflow inlet attributes, all processors associated with outflow inlet must call
        if self.myid in self.inlet_procs[self.outflow_index]:
            outflow_area = self.inlets[self.outflow_index].get_global_area()
            outflow_average_depth = self.inlets[self.outflow_index].get_global_average_depth()
            outflow_outward_culvert_vector = self.inlets[self.outflow_index].outward_culvert_vector
            outflow_average_xmom = self.inlets[self.outflow_index].get_global_average_xmom()
            outflow_average_ymom = self.inlets[self.outflow_index].get_global_average_ymom()

        # Master proc of outflow inlet sends attribute to master proc of structure
        if self.myid == self.master_proc:
            if self.myid != self.inlet_master_proc[self.outflow_index]:
                outflow_area = pypar.receive(self.inlet_master_proc[self.outflow_index])
                outflow_average_depth = pypar.receive(self.inlet_master_proc[self.outflow_index])
                outflow_outward_culvert_vector = pypar.receive(self.inlet_master_proc[self.outflow_index])
                outflow_average_xmom = pypar.receive(self.inlet_master_proc[self.outflow_index])
                outflow_average_ymom = pypar.receive(self.inlet_master_proc[self.outflow_index])
        elif self.myid == self.inlet_master_proc[self.outflow_index]:
            pypar.send(outflow_area, self.master_proc)
            pypar.send(outflow_average_depth, self.master_proc)
            pypar.send(outflow_outward_culvert_vector, self.master_proc)
            pypar.send(outflow_average_xmom, self.master_proc)
            pypar.send(outflow_average_ymom, self.master_proc)

        # Master proc of structure computes new outflow attributes
        if self.myid == self.master_proc:
            loss = (old_inflow_depth - new_inflow_depth)*inflow_area
            xmom_loss = (old_inflow_xmom - new_inflow_xmom)*inflow_area
            ymom_loss = (old_inflow_ymom - new_inflow_ymom)*inflow_area

            # set outflow
            outflow_extra_depth = old_div(Q*timestep_star,outflow_area)
            outflow_direction = - outflow_outward_culvert_vector
            #outflow_extra_momentum = outflow_extra_depth*barrel_speed*outflow_direction

            gain = outflow_extra_depth*outflow_area

            # Update Stats
            self.discharge  = old_div(Q*timestep_star,timestep) #outflow_extra_depth*self.outflow.get_area()/timestep
            self.discharge_abs_timemean += old_div(Q*timestep_star,self.domain.yieldstep)
            self.velocity = barrel_speed #self.discharge/outlet_depth/self.width

            new_outflow_depth = outflow_average_depth + outflow_extra_depth

            self.outlet_depth = new_outflow_depth
            #if self.use_momentum_jet :
            #    # FIXME (SR) Review momentum to account for possible hydraulic jumps at outlet
            #    #new_outflow_xmom = outflow.get_average_xmom() + outflow_extra_momentum[0]
            #    #new_outflow_ymom = outflow.get_average_ymom() + outflow_extra_momentum[1]

            #    new_outflow_xmom = barrel_speed*new_outflow_depth*outflow_direction[0]
            #    new_outflow_ymom = barrel_speed*new_outflow_depth*outflow_direction[1]

            #else:
            #    #new_outflow_xmom = outflow.get_average_xmom()
            #    #new_outflow_ymom = outflow.get_average_ymom()

            #    new_outflow_xmom = 0.0
            #    new_outflow_ymom = 0.0
            if self.use_momentum_jet:
                # FIXME (SR) Review momentum to account for possible hydraulic jumps at outlet
                # FIXME (GD) Depending on barrel speed I think this will be either
                # a source or sink of momentum (considering the momentum losses
                # above). Might not always be reasonable.
                #new_outflow_xmom = self.outflow.get_average_xmom() + outflow_extra_momentum[0]
                #new_outflow_ymom = self.outflow.get_average_ymom() + outflow_extra_momentum[1]
                new_outflow_xmom = barrel_speed*new_outflow_depth*outflow_direction[0]
                new_outflow_ymom = barrel_speed*new_outflow_depth*outflow_direction[1]

            elif self.zero_outflow_momentum:
                new_outflow_xmom = 0.0
                new_outflow_ymom = 0.0
                #new_outflow_xmom = outflow.get_average_xmom()
                #new_outflow_ymom = outflow.get_average_ymom()

            else:
                # Add the momentum lost from the inflow to the outflow. For
                # structures where barrel_speed is unknown + direction doesn't
                # change from inflow to outflow
                new_outflow_xmom = outflow_average_xmom + old_div(xmom_loss,outflow_area)
                new_outflow_ymom = outflow_average_ymom + old_div(ymom_loss,outflow_area)

            # master proc of structure sends outflow attributes to all outflow procs
            for i in self.inlet_procs[self.outflow_index]:
                if i == self.myid: continue
                pypar.send(new_outflow_depth, i)
                pypar.send(new_outflow_xmom, i)
                pypar.send(new_outflow_ymom, i)
        # outflow inlet procs receives new outflow attributes
        elif self.myid in self.inlet_procs[self.outflow_index]:
            new_outflow_depth = pypar.receive(self.master_proc)
            new_outflow_xmom = pypar.receive(self.master_proc)
            new_outflow_ymom = pypar.receive(self.master_proc)

        # outflow inlet procs sets new outflow attributes
        if self.myid in self.inlet_procs[self.outflow_index]:
            self.inlets[self.outflow_index].set_depths(new_outflow_depth)
            self.inlets[self.outflow_index].set_xmoms(new_outflow_xmom)
            self.inlets[self.outflow_index].set_ymoms(new_outflow_ymom)

    def __process_non_skew_culvert(self):
        """Create lines at the end of a culvert inlet and outlet.
        At either end two lines will be created; one for the actual flow to pass through and one a little further away
        for enquiring the total energy at both ends of the culvert and transferring flow.
        """

        self.culvert_vector = self.end_points[1] - self.end_points[0]
        self.culvert_length = math.sqrt(num.sum(self.culvert_vector**2))
        assert self.culvert_length > 0.0, 'The length of culvert is less than 0'

        self.culvert_vector /= self.culvert_length
        self.outward_vector_0 =   self.culvert_vector
        self.outward_vector_1 = - self.culvert_vector

        culvert_normal = num.array([-self.culvert_vector[1], self.culvert_vector[0]])  # Normal vector
        w = 0.5*self.width*culvert_normal # Perpendicular vector of 1/2 width

        self.exchange_lines = []

        # Build exchange polyline and enquiry point
        if self.enquiry_points is None:

            gap = (self.apron + self.enquiry_gap)*self.culvert_vector
            self.enquiry_points = []

            for i in [0, 1]:
                p0 = self.end_points[i] + w
                p1 = self.end_points[i] - w
                self.exchange_lines.append(num.array([p0, p1]))
                ep = self.end_points[i] + (2*i - 1)*gap #(2*i - 1) determines the sign of the points
                self.enquiry_points.append(ep)

        else:
            for i in [0, 1]:
                p0 = self.end_points[i] + w
                p1 = self.end_points[i] - w
                self.exchange_lines.append(num.array([p0, p1]))

    def __process_skew_culvert(self):

        """Compute skew culvert.
        If exchange lines are given, the enquiry points are determined. This is for enquiring
        the total energy at both ends of the culvert and transferring flow.
        """

        centre_point0 = 0.5*(self.exchange_lines[0][0] + self.exchange_lines[0][1])
        centre_point1 = 0.5*(self.exchange_lines[1][0] + self.exchange_lines[1][1])

        n_exchange_0 = len(self.exchange_lines[0])
        n_exchange_1 = len(self.exchange_lines[1])

        assert n_exchange_0 == n_exchange_1, 'There should be the same number of points in both exchange_lines'

        if n_exchange_0 == 2:

            if self.end_points is None:
                self.culvert_vector = centre_point1 - centre_point0
            else:
                self.culvert_vector = self.end_points[1] - self.end_points[0]

            self.outward_vector_0 =   self.culvert_vector
            self.outward_vector_1 = - self.culvert_vector

        elif n_exchange_0 == 4:

            self.outward_vector_0 = self.exchange_lines[0][3] - self.exchange_lines[0][2]
            self.outward_vector_1 = self.exchange_lines[1][3] - self.exchange_lines[1][2]

            self.culvert_vector = centre_point1 - centre_point0

        else:
            raise Exception('n_exchange_0 != 2 or 4')

        self.culvert_length = math.sqrt(num.sum(self.culvert_vector**2))
        assert self.culvert_length > 0.0, 'The length of culvert is less than 0'
        self.culvert_vector /= self.culvert_length

        outward_vector_0_length = math.sqrt(num.sum(self.outward_vector_0**2))
        assert outward_vector_0_length > 0.0, 'The length of outlet_vector_0 is less than 0'
        self.outward_vector_0 /= outward_vector_0_length

        outward_vector_1_length = math.sqrt(num.sum(self.outward_vector_1**2))
        assert outward_vector_1_length > 0.0, 'The length of outlet_vector_1 is less than 0'
        self.outward_vector_1 /= outward_vector_1_length

        if self.enquiry_points is None:

            gap = (self.apron + self.enquiry_gap)*self.culvert_vector

            self.enquiry_points = []

            self.enquiry_points.append(centre_point0 - gap)
            self.enquiry_points.append(centre_point1 + gap)

    def discharge_routine(self):

        msg = 'Need to impelement '
        raise

    def statistics(self):
        # Warning: requires synchronization, must be called by all procs associated
        # with this structure

        message = ' '

        if self.myid == self.master_proc:

            message  = '===============================================\n'
            message += 'Parallel Structure Operator: %s\n' % self.label
            message += '===============================================\n'

            message += 'Structure Type: %s\n' % self.structure_type

            message += 'Description\n'
            message += '%s' % self.description
            message += '\n'

            #add the culvert dimensions, blockage factor here
            if self.structure_type == 'boyd_pipe':
                message += 'Culvert Diameter: %s\n'% self.diameter
                message += 'Culvert Blockage: %s\n'% self.blockage
                message += 'No.  of  barrels: %s\n'% self.barrels
            elif self.structure_type == 'boyd_box':
                message += 'Culvert   Height: %s\n'% self.height
                message += 'Culvert    Width: %s\n'% self.width
                message += 'Culvert Blockage: %s\n'% self.blockage
                message += 'No.  of  barrels: %s\n'% self.barrels
            else:
                message += 'Culvert Height  : %s\n'% self.height
                message += 'Culvert  Width  : %s\n'% self.width
                message += 'Batter Slope 1  : %s\n'% self.z1
                message += 'Batter Slope 2  : %s\n'% self.z2
                message += 'Culvert Blockage: %s\n'% self.blockage
                message += 'No.  of  barrels: %s\n'% self.barrels

        #print "Structure Myids ",self.myid, self.label

        for i, inlet in enumerate(self.inlets):
            if self.myid == self.master_proc:
                message += '-------------------------------------\n'
                message +=  'Inlet %i\n' %(i)
                message += '-------------------------------------\n'

            #print "*****",inlet, i,self.myid
            if inlet is not None:

                stats = inlet.statistics()

            if self.myid == self.master_proc:
                if self.myid != self.inlet_master_proc[i]:
                    stats = pypar.receive(self.inlet_master_proc[i])
            elif self.myid == self.inlet_master_proc[i]:
                pypar.send(stats, self.master_proc)

            if self.myid == self.master_proc: message += stats

        if self.myid == self.master_proc: message += '=====================================\n'

        return message

    def print_statistics(self):
        # Warning: requires synchronization, must be called by all procs associated
        # with this structure

        print(self.statistics())

    def print_timestepping_statistics(self):
        # Warning: must be called by the master proc of this structure to obtain
        # meaningful output

        message = ' '

        if self.myid == self.master_proc:
            message = '--------------------------------------------------\n'
            message += 'Parallel Structure report for %s:\n' % self.label
            message += '-------------------------------------------------\n'
            message += 'Type: %s\n' % self.structure_type
            message += 'Discharge [m^3/s]: %.2f\n' % self.discharge
            message += 'Discharge function value [m^3/s]: %.2f\n' % self.discharge_abs_timemean
            message += 'Velocity  [m/s]: %.2f\n' % self.velocity
            message += 'Inlet Driving Energy %.2f\n' % self.driving_energy
            message += 'Delta Total Energy %.2f\n' % self.delta_total_energy
            message += 'Control at this instant: %s\n' % self.case

        print(message)

    def set_parallel_logging(self, flag=True):
        # Warning: requires synchronization, must be called by all procs associated
        # with this structure

        stats = self.statistics()
        self.logging = flag

        # If flag is true open file with mode = "w" to form a clean file for logging
        if self.logging and self.myid == self.master_proc:
            self.log_filename = self.domain.get_datadir() + '/' + self.label + '.log'
            log_to_file(self.log_filename, stats, mode='w')
            log_to_file(self.log_filename, 'time,discharge_instantaneous,discharge_abs_timemean,velocity_instantaneous,driving_energy_instantaneous,delta_total_energy_instantaneous')

            #log_to_file(self.log_filename, self.culvert_type)

    def set_logging(self, flag=True):
        # Overwrite the sequential procedure with a dummy procedure.
        # Need to call set_parallel_logging which needs to be done later
        # after the calculation of master processors

        pass

    def log_timestepping_statistics(self):

        from anuga.utilities.system_tools import log_to_file
        if self.logging and self.myid == self.master_proc:
            log_to_file(self.log_filename, self.timestepping_statistics())

    def timestepping_statistics(self):

        message  = '%.5f, ' % self.domain.get_time()
        message += '%.5f, ' % self.discharge
        message += '%.5f, ' % self.discharge_abs_timemean
        message += '%.5f, ' % self.velocity
        message += '%.5f, ' % self.driving_energy
        message += '%.5f' % self.delta_total_energy

        # Reset discharge_abs_timemean each time there is reporting (FIXME:
        # This assumes this function is only called after each yieldstep)
        self.discharge_abs_timemean = 0.

        return message

    def get_inlets(self):
        return self.inlets

    def get_culvert_length(self):
        return self.culvert_length

    def get_culvert_width(self):
        return self.width

    def get_culvert_diameter(self):
        return self.diameter

    def get_culvert_height(self):
        return self.height

    def get_culvert_z1(self):
        return self.z1

    def get_culvert_z2(self):
        return self.z2

    def get_culvert_blockage(self):
        return self.blockage

    def get_culvert_barrels(self):
        return self.barrels

    def get_culvert_apron(self):
        return self.apron

    # Get id of master proc of this structure
    def get_master_proc(self):
        return self.master_proc

    # Get id of master proc of first and second inlet
    def get_inlet_master_proc(self):
        return self.inlet_master_proc

    # Get id of processors associated with first and second inlet enquiry points
    def get_enquiry_proc(self, id=None):

        if id is none:
            return self.enquiry_proc
        else:
            return self.enquiry_proc[id]

    def set_culvert_height(self, height):

        self.culvert_height = height

    def set_culvert_width(self, width):

        self.culvert_width = width

    def set_culvert_z1(self, z1):

        self.culvet_z1 = z1

    def set_culvert_z2(self, z2):

        self.culvert_z2 = z2

    def set_culvert_blockage(self, blockage):

        self.culvert_blockage = blockage

    def set_culvert_blockage(self, barrels):

        self.culvert_barrels = barrels

    def parallel_safe(self):
        return True

    def get_enquiry_stages(self):
        # Should be called from all processors associated with operator

        enq0 = None
        enq1 = None

        get0 = 'self.inlets[0].get_enquiry_stage()'
        get1 = 'self.inlets[1].get_enquiry_stage()'

        if self.myid == self.master_proc:

            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
            else:
                enq0 = pypar.receive(self.enquiry_proc[0])

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
            else:
                enq1 = pypar.receive(self.enquiry_proc[1])

        else:
            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
                pypar.send(enq0, self.master_proc)

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
                pypar.send(enq1, self.master_proc)

        return [enq0, enq1]

    def get_enquiry_depths(self):
        # Should be called from all processors associated with operator

        enq0 = None
        enq1 = None

        get0 = 'self.inlets[0].get_enquiry_depth()'
        get1 = 'self.inlets[1].get_enquiry_depth()'

        if self.myid == self.master_proc:

            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
            else:
                enq0 = pypar.receive(self.enquiry_proc[0])

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
            else:
                enq1 = pypar.receive(self.enquiry_proc[1])

        else:
            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
                pypar.send(enq0, self.master_proc)

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
                pypar.send(enq1, self.master_proc)

        return [enq0, enq1]

    def get_enquiry_positions(self):

        enq0 = None
        enq1 = None

        get0 = 'self.inlets[0].get_enquiry_position()'
        get1 = 'self.inlets[1].get_enquiry_position()'

        if self.myid == self.master_proc:

            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
            else:
                enq0 = pypar.receive(self.enquiry_proc[0])

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
            else:
                enq1 = pypar.receive(self.enquiry_proc[1])

        else:
            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
                pypar.send(enq0, self.master_proc)

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
                pypar.send(enq1, self.master_proc)

        return [enq0, enq1]

    def get_enquiry_xmoms(self):

        enq0 = None
        enq1 = None

        get0 = 'self.inlets[0].get_enquiry_xmom()'
        get1 = 'self.inlets[1].get_enquiry_xmom()'

        if self.myid == self.master_proc:

            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
            else:
                enq0 = pypar.receive(self.enquiry_proc[0])

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
            else:
                enq1 = pypar.receive(self.enquiry_proc[1])

        else:
            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
                pypar.send(enq0, self.master_proc)

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
                pypar.send(enq1, self.master_proc)

        return [enq0, enq1]

    def get_enquiry_ymoms(self):

        enq0 = None
        enq1 = None

        get0 = 'self.inlets[0].get_enquiry_ymom()'
        get1 = 'self.inlets[1].get_enquiry_ymom()'

        if self.myid == self.master_proc:

            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
            else:
                enq0 = pypar.receive(self.enquiry_proc[0])

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
            else:
                enq1 = pypar.receive(self.enquiry_proc[1])

        else:
            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
                pypar.send(enq0, self.master_proc)

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
                pypar.send(enq1, self.master_proc)

        return [enq0, enq1]

    def get_enquiry_elevations(self):

        enq0 = None
        enq1 = None

        get0 = 'self.inlets[0].get_enquiry_elevation()'
        get1 = 'self.inlets[1].get_enquiry_elevation()'

        if self.myid == self.master_proc:

            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
            else:
                enq0 = pypar.receive(self.enquiry_proc[0])

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
            else:
                enq1 = pypar.receive(self.enquiry_proc[1])

        else:
            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
                pypar.send(enq0, self.master_proc)

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
                pypar.send(enq1, self.master_proc)

        return [enq0, enq1]

    def get_enquiry_water_depths(self):

        enq0 = None
        enq1 = None

        get0 = 'self.inlets[0].get_enquiry_water_depth()'
        get1 = 'self.inlets[1].get_enquiry_water_depth()'

        if self.myid == self.master_proc:

            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
            else:
                enq0 = pypar.receive(self.enquiry_proc[0])

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
            else:
                enq1 = pypar.receive(self.enquiry_proc[1])

        else:
            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
                pypar.send(enq0, self.master_proc)

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
                pypar.send(enq1, self.master_proc)

        return [enq0, enq1]

    def get_enquiry_invert_elevations(self):

        enq0 = None
        enq1 = None

        get0 = 'self.inlets[0].get_enquiry_invert_elevation()'
        get1 = 'self.inlets[1].get_enquiry_invert_elevation()'

        if self.myid == self.master_proc:

            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
            else:
                enq0 = pypar.receive(self.enquiry_proc[0])

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
            else:
                enq1 = pypar.receive(self.enquiry_proc[1])

        else:
            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
                pypar.send(enq0, self.master_proc)

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
                pypar.send(enq1, self.master_proc)

        return [enq0, enq1]

    def get_enquiry_velocitys(self):

        enq0 = None
        enq1 = None

        get0 = 'self.inlets[0].get_enquiry_velocity()'
        get1 = 'self.inlets[1].get_enquiry_velocity()'

        if self.myid == self.master_proc:

            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
            else:
                enq0 = pypar.receive(self.enquiry_proc[0])

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
            else:
                enq1 = pypar.receive(self.enquiry_proc[1])

        else:
            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
                pypar.send(enq0, self.master_proc)

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
                pypar.send(enq1, self.master_proc)

        return [enq0, enq1]

    def get_enquiry_xvelocitys(self):

        enq0 = None
        enq1 = None

        get0 = 'self.inlets[0].get_enquiry_xvelocity()'
        get1 = 'self.inlets[1].get_enquiry_xvelocity()'

        if self.myid == self.master_proc:

            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
            else:
                enq0 = pypar.receive(self.enquiry_proc[0])

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
            else:
                enq1 = pypar.receive(self.enquiry_proc[1])

        else:
            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
                pypar.send(enq0, self.master_proc)

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
                pypar.send(enq1, self.master_proc)

        return [enq0, enq1]

    def get_enquiry_yvelocitys(self):

        enq0 = None
        enq1 = None

        get0 = 'self.inlets[0].get_enquiry_yvelocity()'
        get1 = 'self.inlets[1].get_enquiry_yvelocity()'

        if self.myid == self.master_proc:

            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
            else:
                enq0 = pypar.receive(self.enquiry_proc[0])

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
            else:
                enq1 = pypar.receive(self.enquiry_proc[1])

        else:
            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
                pypar.send(enq0, self.master_proc)

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
                pypar.send(enq1, self.master_proc)

        return [enq0, enq1]

    def get_enquiry_speeds(self):

        enq0 = None
        enq1 = None

        get0 = 'self.inlets[0].get_enquiry_speed()'
        get1 = 'self.inlets[1].get_enquiry_speed()'

        if self.myid == self.master_proc:

            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
            else:
                enq0 = pypar.receive(self.enquiry_proc[0])

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
            else:
                enq1 = pypar.receive(self.enquiry_proc[1])

        else:
            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
                pypar.send(enq0, self.master_proc)

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
                pypar.send(enq1, self.master_proc)

        return [enq0, enq1]

    def get_enquiry_velocity_heads(self):

        enq0 = None
        enq1 = None

        get0 = 'self.inlets[0].get_enquiry_velocity_head()'
        get1 = 'self.inlets[1].get_enquiry_velocity_head()'

        if self.myid == self.master_proc:

            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
            else:
                enq0 = pypar.receive(self.enquiry_proc[0])

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
            else:
                enq1 = pypar.receive(self.enquiry_proc[1])

        else:
            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
                pypar.send(enq0, self.master_proc)

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
                pypar.send(enq1, self.master_proc)

        return [enq0, enq1]

    def get_enquiry_total_energys(self):

        enq0 = None
        enq1 = None

        get0 = 'self.inlets[0].get_enquiry_total_energy()'
        get1 = 'self.inlets[1].get_enquiry_total_energy()'

        if self.myid == self.master_proc:

            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
            else:
                enq0 = pypar.receive(self.enquiry_proc[0])

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
            else:
                enq1 = pypar.receive(self.enquiry_proc[1])

        else:
            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
                pypar.send(enq0, self.master_proc)

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
                pypar.send(enq1, self.master_proc)

        return [enq0, enq1]

    def get_enquiry_specific_energys(self):

        enq0 = None
        enq1 = None

        get0 = 'self.inlets[0].get_enquiry_specific_energy()'
        get1 = 'self.inlets[1].get_enquiry_specific_energy()'

        if self.myid == self.master_proc:

            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
            else:
                enq0 = pypar.receive(self.enquiry_proc[0])

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
            else:
                enq1 = pypar.receive(self.enquiry_proc[1])

        else:
            if self.myid == self.enquiry_proc[0]:
                enq0 = eval(get0)
                pypar.send(enq0, self.master_proc)

            if self.myid == self.enquiry_proc[1]:
                enq1 = eval(get1)
                pypar.send(enq1, self.master_proc)

        return [enq0, enq1]


#!/opt/local/bin/python
# -*- Encoding: UTF-8 -*-

"""
=========
plotting
=========

Plotting routines for blobtrail objects

* plot_trail_simple -> Simple plotting without special geometry
* plot_trail_geom -> Use outboard midplane geometry of CMOD. Needs geometry and separatrix data

"""

import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import griddata, interp1d
import matplotlib.patches as mpatches
from geometry import velocity_max, velocity_com

def plot_trail_simple(blobtrail, frames, plot_com='o', plot_max='^', plot_shape=True, save_frames=False):
    """
    Plot the motion of the blob. The GPI frames are to
    be supplied externally

    Input:
        frames:         GPI data
        plot_com:       Mark the center of mass of the blob
        plot_max:       Mark the maximum of the blob
        plot_shape:     If available, plot the radial extend of the blob
        save_frames:    Save the frames

    """

    # find the frames needed to track the object
    frame_idx = blobtrail.get_event_frames()

    # Length of the blobtrail object
    tau = np.arange(blobtrail.get_tau().size)

    # Get the coordinates of the blob
    xymax = blobtrail.get_xymax()
    xycom = blobtrail.get_xycom()

    ell_rad = blobtrail.get_ell_rad()
    ell_pol = blobtrail.get_ell_pol()

    # Get minimum and maximum value for color scales
    minval = frames[frame_idx, :, :].min()
    maxval = frames[frame_idx, :, :].max()

    num_levels = 64
    color_levels = np.linspace(minval, maxval, num_levels)

    for fidx, tau_idx in zip(frame_idx, tau):
        plt.figure()
        plt.title('frame %05d' % (fidx))
        plt.xlabel('x / px')
        plt.ylabel('y / px')

        plt.contourf(frames[fidx], cmap=plt.cm.hot, levels=color_levels)

        if plot_com is not None:
            plt.plot(xycom[:tau_idx, 1], xycom[:tau_idx, 0], plot_com)

        # Plot maximum with errorbars
        if plot_max is not None:
            if plot_shape:
                plt.errorbar(xymax[:tau_idx, 1], xymax[:tau_idx, 0],
                             xerr=ell_rad[:tau_idx], yerr=ell_pol[:tau_idx],
                             ecolor='w', linestyle='None',
                             mfc='white', mec='green', marker=plot_max)
            else:
                plt.plot(xymax[:tau_idx, 1], xymax[:tau_idx, 0], plot_max)

        plt.colorbar()

    if save_frames:
        F = plt.gcf()
        F.savefig('%d/frames/frame_%05d.eps' % (self.shotnr,
                                                self.event[1] +
                                                self.frame0 + tau))
        plt.close()

    plt.show()

def plot_trail_geom(blobtrail, frames, rz_array=None, xyi=None, trigger_box=None,
                    sep_data=None, plot_com=False, plot_max=False,
                    plot_shape=False, plot_geom=False, save_frames=False):
    """
    Plot the motion of the blob using CMOD GPI geometry
    The GPI frames are to be supplied externally.

    Input:
        frames:         ndarray, GPI data. axis0: time, axis1: poloidal, axis2: radial
        rz_array:       ndarray, Where the GPI data is known
        xyi:            ndarray, Array on which we interpolate GPI data for output
        trigger_box:    ndarray, Array where blobs have been detected
        sep_data:       IDL .sav structure, see /home/rkube/IDL/separatrix.pro
        plot_com:       Mark the center of mass of the blob
        plot_max:       Mark the maximum of the blob
        plot_shape:     If available, mark the FWHM of the blob
        plot_geom:      Overplot triggering blox, limiter shadow and
                        separatrix
        save_frames:    Save the frames
    """

    # find the frames needed to track the object
    frame_idx = blobtrail.get_event_frames()
    tau = np.arange(blobtrail.get_tau().size)
    xymax = blobtrail.get_xymax()
    xycom = blobtrail.get_xycom()
    ell_rad = blobtrail.get_ell_rad()
    ell_pol = blobtrail.get_ell_pol()

    # Get minimum and maximum value for color scales
    minval = frames[frame_idx, :, :].min()
    maxval = frames[frame_idx, :, :].max()
    print 'min = %f, max = %f' % (minval, maxval)
    print 'plotting from %d - %d' % (tau[0], tau[-1])

    # Number of levels in contour plots
    num_levels = 64
    color_levels = np.linspace(minval, maxval, num_levels)

    # Positiion of velocity label
    text_x, text_y = 86.2, -6.
    # Font of velocity label
    vlabel_font = dict(size=16., color='white', weight='bold')

    # Blob velocity
    vmax = velocity_max(blobtrail, rz_array)
    vcom = velocity_com(blobtrail, rz_array)

    print 'mean(vmax) rad=%f, pol=%f' % (vmax.mean(axis=0)[0], vmax.mean(axis=0)[1])
    print 'mean(vcom) rad=%f, pol=%f' % (vcom.mean(axis=0)[0], vcom.mean(axis=0)[1])

    for fidx, tau_idx in zip(frame_idx, tau):
        plt.figure()
        plt.title('frame %05d' % (fidx))
        plt.xlabel('R / cm')
        plt.ylabel('Z / cm')

        # Try plotting everythin in machine coordinates. If it fails draw in pixels
        zi = griddata(rz_array.reshape(64 * 64, 2),
                frames[fidx, :, :].reshape(64 * 64),
                xyi.reshape(64 * 64, 2), method='linear')
        #plt.contour(xyi[:, :, 0], xyi[:, :, 1], zi.reshape(64, 64),
        #            32, linewidths=0.5, colors='k')
        plt.contourf(xyi[:, :, 0], xyi[:, :, 1], zi.reshape(64, 64),
                     num_levels, cmap=plt.cm.hot, levels=color_levels)

        #except:
        #    print 'Failed to grid data on rz_array... Drawing in pixels'
        #    #plt.contour(frames[self.event[1] + self.frame0 + tau, :, :],
        #    #            32, linewidths=0.5, colors='k')
        #    plt.contourf(frames[fidx, :, :], num_levels, cmap=plt.cm.hot, levels=color_levels)
        plt.colorbar(ticks=np.arange(minval, maxval, (maxval - minval) / 5.), format='%3.1f')

        if plot_com:
            # Plot the leading up blob trail without error bars
            plt.plot(xyi[xycom[:tau_idx + 1, 0].astype('int'),
                         xycom[:tau_idx + 1, 1].astype('int'),
                         0],
                     xyi[xycom[:tau_idx + 1, 0].astype('int'),
                         xycom[:tau_idx + 1, 1].astype('int'),
                     1], '-ws')

            # Interpolate width from pixel to physical units
            ip_rad = interp1d(np.arange(64), xyi[xycom[tau_idx, 0].astype('int'), :, 0], kind='quadratic')
            ip_pol = interp1d(np.arange(64), xyi[:, xycom[tau_idx, 1].astype('int'), 1], kind='quadratic')
            # lower and upper 1-sigma intervall of com position in pixel coordinates
            # Extend these intervals not out of bounds in pixel coordinates, otherwise interp1d throws ValueError
            sigma_x_px = np.array([max(xycom[tau_idx, 0] - ell_rad[tau_idx], 0), min(xycom[tau_idx, 0] + ell_rad[tau_idx], 63)])
            sigma_x = ip_rad(sigma_x_px)
            sigma_y_px = np.array([max(xycom[tau_idx, 0] - ell_pol[tau_idx], 0), min(xycom[tau_idx, 0] + ell_pol[tau_idx], 63)])
            sigma_y = ip_rad(sigma_y_px)
            #frame_xerr = ip_rad(np.array([xycom[tau_idx, 0] - ell_rad[tau_idx], xycom[tau_idx, 0] + ell_rad[tau_idx]]))
            #frame_yerr = ip_pol(np.array([xycom[tau_idx, 0] - ell_pol[tau_idx], xycom[tau_idx, 0] + ell_pol[tau_idx]]))

            sigma_x = np.abs(sigma_x[1] - sigma_x[0])
            sigma_y = np.abs(sigma_y[1] - sigma_y[0])
            # Plot current blob position with error bars
            plt.errorbar(xyi[xycom[tau_idx, 0].astype('int'),
                             xycom[tau_idx, 1].astype('int'), 0],
                         xyi[xycom[tau_idx, 0].astype('int'),
                             xycom[tau_idx, 1].astype('int'), 1],
                         xerr=sigma_x, yerr=sigma_y, ecolor='w', linestyle='None', mfc='white', mec='green', marker='s')

            # Set the coordinates for plotting the text field

            if (tau_idx > 0):
                str_vcom = r"$V_\mathrm{COM} = (%4.1f, %4.1f)$" % (vcom[tau_idx - 1, 0], vcom[tau_idx - 1, 1])
                plt.text(text_x, text_y, str_vcom, fontdict=vlabel_font)

        if plot_max:
            plt.plot(xyi[xymax[:tau_idx + 1, 0].astype('int'),
                         xymax[:tau_idx + 1, 1].astype('int'), 0],
                     xyi[xymax[:tau_idx + 1, 0].astype('int'),
                         xymax[:tau_idx + 1, 1].astype('int'), 1], '-.wo')

            if (tau_idx > 0):
                str_vmax = r"$V_{max} = (%4.1f, %4.1f)$" % (vmax[tau_idx - 1, 0], vmax[tau_idx - 1, 1])
                plt.text(text_x, text_y, str_vmax, fontdict=vlabel_font)

        if plot_geom:
            # Get the position of the pixels for the separatrix
            # and limiter
            separatrix_pxs = surface_line(sep_data['rmid'].
                                          reshape(64, 64) >
                                          sep_data['rmid_sepx'],
                                          mode='max')
            limiter_pxs = surface_line(sep_data['rmid'].
                                       reshape(64, 64) <
                                       sep_data['rmid_lim'],
                                       mode='min')

            # Compute position, width and height of the triggering box
            tb_lower_left = (xyi[trigger_box[2], trigger_box[0], 0],
                             xyi[trigger_box[2], trigger_box[0], 1])
            tb_width = (xyi[trigger_box[2], trigger_box[1], 0] -
                        xyi[trigger_box[2], trigger_box[0], 0])
            tb_height = (xyi[trigger_box[3], trigger_box[0], 1] -
                         xyi[trigger_box[2], trigger_box[0], 1])

            # Plot the triggering domain. Position, height and width
            # are not automatically determined but static values.

            triggering_box = mpatches.Rectangle(tb_lower_left,
                                                width=tb_width, height=tb_height,
                                                fill=False,
                                                ls='dashdot',
                                                ec='w', lw=3)
            fig = plt.gcf()
            ax = fig.gca()
            ax.add_patch(triggering_box)

            # Plot the separatrix
            sep_x = [xyi[i, separatrix_pxs[i], 0] for i in
                     np.arange(64)]
            sep_y = [xyi[i, separatrix_pxs[i], 1] for i in
                     np.arange(64)]
            plt.plot(sep_x, sep_y, 'w--', linewidth=4)

            lim_x = [xyi[i, limiter_pxs[i], 0] for i in np.arange(64)]
            lim_y = [xyi[i, limiter_pxs[i], 1] for i in np.arange(64)]
            plt.plot(lim_x, lim_y, 'w-.', linewidth=4)

        if save_frames:
            F = plt.gcf()
            F.savefig('%d/frames/frame_%05d.eps' % (self.shotnr,
                                                    self.event[1] +
                                                    self.frame0 + tau))
            plt.close()

    plt.show()

def surface_line(sep_pixels, mode='max'):
    """
    Given the pixels which are mapped to the closed field line region,
    return the pixel with the largest radial coordinate for each
    poloidal coordinate.
    """

    # Index all pixels radially
    lin_array = np.repeat(np.arange(64), 64).reshape(64, 64).T

    # Apply the mask
    la_masked = np.ma.array(lin_array, mask=sep_pixels)

    # Return the maximum radial indices for each poloidal position
    if (mode == 'max'):
        return la_masked.argmax(axis=1)
    elif (mode == 'min'):
        return la_masked.argmin(axis=1)

# End of file plotting.py


#!/usr/bin/env python3
# Christopher Vollmers
# Roger Volden

import sys
import numpy as np

content_file  =  sys.argv[1]
out_path = sys.argv[2]
cutoff = float(sys.argv[3])
genome_file = sys.argv[4]
refine = sys.argv[5]

minimum_read_count = 3

splice_site_width = 10

def scan_for_best_bin(entry, distance_range, iterator_shift, density_dict,
                      base_cutoff_min, base_cutoff_max,
                      peak_areas, chromosome, side):
    '''
    Stuff about the inputs, outputs, and how it works
    '''

    best_extra_list, peak_center, bases = [], 0, []
    coverage_area, best_direction_l, best_direction_r = [], [], []
    for x in distance_range:
        extra_list_bases, extra_list_expression = [], []
        direction_l, direction_r = {}, {}
        coverage_set = []
        called = False
        for y in distance_range:
            try:
               called = peak_areas[chromosome][side][entry+x+y]
            except KeyError:
               pass
        if not called:
             highest_y = 0
             highest_y_pos = 0
             for y in distance_range:
                try:
                    for item in density_dict[entry+x+y]:
                        extra_list_bases.append(item[0])
                        extra_list_expression.append(1)
                        if not direction_l.get(item[4]):
                            direction_l[item[4]] = 1
                        else:
                            direction_l[item[4]] += 1
                        if not direction_r.get(item[5]):
                            direction_r[item[5]] = 1
                        else:
                            direction_r[item[5]] += 1
                        for covered_position in item[3]:
                            coverage_set.append(covered_position)
                except:
                    pass

        if base_cutoff_min <= np.median(extra_list_bases) <= base_cutoff_max:
            if sum(extra_list_expression) > sum(best_extra_list):
                best_extra_list = extra_list_expression
                peak_center = entry+x
                bases = extra_list_bases
                coverage_area = coverage_set
                best_direction_l = direction_l
                best_direction_r = direction_r

    return best_extra_list, peak_center, bases, coverage_area, \
           best_direction_l, best_direction_r

def determine_coverage(coverage_area, chromosome, reverse,
                       peak_center, histo_coverage):
    '''
    Insert docstring
    '''
    coverage = [0]
    coverage_area2 = []
    for covered_position in set(coverage_area):
        if coverage_area.count(covered_position) > 1:
            coverage_area2.append(covered_position)

    coverage_area = sorted(coverage_area2, reverse=reverse)
    counter = 0
    for base_f in coverage_area:
        count = 0
        if not reverse:
            if base_f > peak_center:
                count = 1
        elif reverse:
            if base_f < peak_center:
                count = 1
        if count == 1:
            if counter <= 3:
                counter += 1
                base_f = myround(base_f)
                try:
                    coverage.append(histo_coverage[chromosome][base_f])
                except KeyError:
                    pass
            else:
                break
    coverage = max(coverage)
    return coverage, coverage_area

def read_seq_file(seq_file):
    read_seq = {}
    length = 0
    for line2 in open(seq_file):
        length += 1
    seq_file_open = open(seq_file, 'r')
    counter = 0
    while counter < length:
        fasta_name = seq_file_open.readline().strip()
        fasta_seq = seq_file_open.readline().strip()
        fasta_name = fasta_name[1:]
        read_seq[fasta_name] = fasta_seq
        counter += 2
    return read_seq

def myround(x, base=10):
    '''Rounds to the nearest base'''
    return int(base * round(float(x)/base))

def find_peaks(density_dict, out, peaks, reverse, cutoff, base_cutoff_min,
               base_cutoff_max, histo_coverage, side, peak_areas,chromosome):
    '''
    Insert docstring
    '''

    if not reverse:
        distance_range = range(-splice_site_width, splice_site_width)
        iterator_shift = 1
    if reverse:
        distance_range = range(splice_site_width, -splice_site_width, -1)
        iterator_shift =- 1

    entry_list = []
    for entry in density_dict:
      entry_list.append([entry, density_dict[entry]])

    for entry, density in sorted(entry_list,
                                 key=lambda x: sum(np.array(x[1])[:,2]),
                                 reverse=True):

        if len(density) >= minimum_read_count:
          if not peak_areas[chromosome][side].get(entry):

            best_extra_list, peak_center, bases, \
            coverage_area, best_direction_l, best_direction_r \
            = scan_for_best_bin(entry, distance_range, iterator_shift,
                                density_dict, base_cutoff_min,
                                base_cutoff_max, peak_areas,
                                chromosome, side)

            coverage, coverage_area \
            = determine_coverage(coverage_area, chromosome, reverse,
                                 peak_center, histo_coverage)

            if coverage > 0:
                proportion = round(sum(best_extra_list)/coverage, 3)
                print(chromosome + '\t' + str(peak_center - 1) + '\t'
                      + str(peak_center + 1) + '\t' + str(proportion))
                if proportion > cutoff:
                    try:
                        Left_TSS = best_direction_l['TSS']
                    except:
                        Left_TSS = 0
                    try:
                        Left_TES = best_direction_l['TES']
                    except:
                        Left_TES = 0
                    try:
                        Right_TSS = best_direction_r['TSS']
                    except:
                        Right_TSS = 0
                    try:
                        Right_TES = best_direction_r['TES']
                    except:
                        Right_TES = 0
                    Left_to_Right = Left_TSS + Right_TES
                    Right_to_Left = Left_TES + Right_TSS
                    Type = '-'

                    if Left_to_Right < Right_to_Left and reverse:
                        Type = '3'
                    elif Left_to_Right < Right_to_Left and not reverse:
                        Type = '5'
                    elif Left_to_Right > Right_to_Left and reverse:
                        Type = '5'
                    elif Left_to_Right > Right_to_Left and not reverse:
                        Type = '3'

                    if Type != '-':
                        peaks += 1
                        out.write(chromosome + '\t'
                                  + str(peak_center-splice_site_width)
                                  + '\t' + str(peak_center+splice_site_width)
                                  + '\t' + str(Type) + side + str(peaks) + '_'
                                  + str(peak_center-splice_site_width) + '_'
                                  + str(peak_center+splice_site_width) + '_'
                                  + str(proportion) + '\t' + str(peaks) + '\n')
                        for base in range(peak_center - splice_site_width,
                                          peak_center + splice_site_width):
                            peak_areas[chromosome][side][base] = 1

        else:
            break

    return peaks, peak_areas

def collect_reads(content_file,chromosome_list):
    '''
    Insert docstring
    '''

    histo_left_bases, histo_right_bases = {}, {}
    chromosome_list_left, chromosome_list_right = chromosome_list, chromosome_list
    histo_coverage = {}
    base_cutoff_min, base_cutoff_max = 0, 5

    for line in open(content_file):
        total = 0
        b = line.strip().split('\t')
        infile = b[0]
        sam_file = b[4]
        length = 0
        direction_dict=get_alignment_direction(sam_file)
        for line in open(infile):
            total += 1
            a = line.strip().split('\t')
            chromosome = a[13]
            name=a[9].split('_')[0]
            if direction_dict.get(name):
                direction=direction_dict[name]
                if not histo_coverage.get(chromosome):
                    histo_coverage[chromosome] = {}

                score, direction, name,length = int(a[0]), a[8], a[9], int(a[10])
    #            coverage = int(name.split('_')[3])
    #            if coverage >= minimum_read_coverage:

                begin, span = int(a[15]), int(a[16])
                blocksizes = a[18].split(',')[:-1]
                blockstarts = a[20].split(',')[:-1]
                readstarts = a[19].split(',')[:-1]

                if direction == '+':
                    start_seq, end_seq = 'S', 'E'
                    left_match, right_match = 'TSS', 'TES'

                else:
                    start_seq, end_seq= 'E', 'S'
                    left_match, right_match = 'TES', 'TSS'

                coverage_set = set()
                previous_blocksize, previous_start = -1, -1
                previous_blockend = np.inf
                intron, indel, indel1 = 0, 0, 0
                low_bounds, up_bounds = [], []
                aligned_bases=0
                for x in range(0, len(blocksizes)):
                    blockstart = int(blockstarts[x])
                    blocksize = int(blocksizes[x])
                    readstart = int(readstarts[x])
                    aligned_bases += blocksize
                    blockend = blockstart + blocksize
                    if blocksize > 10:
                        for y in range(0, blocksize, 10):
                            rounded = myround(blockstart + y)
                            coverage_set.add(rounded)
                        for yy in range(y, blocksize):
                            rounded = myround(blockstart + yy)
                            coverage_set.add(rounded)
                        if previous_start == -1:
                            previous_start = blockstart
                            min_length = 10
                        else:
                            min_length = 10

                        if blockstart - previous_blockend > 20:
                            previous_start = blockstart

                        if blockend - previous_start > min_length:
                            if intron == 1:
                                up_bounds.append([previous_start, indel1, blockend])
                                low_bounds.append([remember_blockend,
                                                   remember_indel1, remember_start])
                                intron = 0
                            else:
                                try:
                                    next_blockstart = int(blockstarts[x+1])
                                    next_blocksize = int(blocksizes[x+1])
                                    next_readstart = int(readstarts[x+1])

                                    insert = next_blockstart - blockend
                                    if insert > 50:
                                        indel1 = next_readstart \
                                                 - (readstart + blocksize)
                                        remember_blockend = blockend
                                        remember_indel1 = indel1
                                        remember_start = previous_start
                                        intron = 1
                                        previous_start = next_blockstart
                                        blockend = next_blockstart
                                except:
                                    pass
                        previous_blockend = blockend

                for rounded in coverage_set:
                    try:
                        histo_coverage[chromosome][rounded] += 1
                    except:
                        histo_coverage[chromosome][rounded] = 1

                if aligned_bases/length > 0.70:
                    for low_bound, indel1, blockend in low_bounds:
                        chromosome_list_left.add(chromosome)
                        if not histo_left_bases.get(chromosome):
                            histo_left_bases[chromosome] = {}
                        if not histo_left_bases[chromosome].get(low_bound):
                            histo_left_bases[chromosome][low_bound] = []
                        histo_left_bases[chromosome][low_bound].append([indel1, begin,
                                                                        span, coverage_set,
                                                                        left_match,
                                                                        right_match])
                    for up_bound, indel1, blockend in up_bounds:
                        chromosome_list_right.add(chromosome)
                        if not histo_right_bases.get(chromosome):
                            histo_right_bases[chromosome] = {}
                        if not histo_right_bases[chromosome].get(up_bound):
                            histo_right_bases[chromosome][up_bound] = []
                        histo_right_bases[chromosome][up_bound].append([indel1, begin,
                                                                        span, coverage_set,
                                                                        left_match,
                                                                        right_match])

    chromosome_list = chromosome_list_left & chromosome_list_right
    return histo_left_bases, histo_right_bases, chromosome_list, histo_coverage

def parse_genome(input_file, left_bounds, right_bounds):
    '''
    Insert docstring
    '''
    chromosome_list=set()
    gene_dict = {}
    for line in open(input_file):
        a = line.strip().split('\t')
        if len(a) > 7:
             if a[2] == 'exon':
                 testKey = a[8].split('; transcript_id "')[1].split('"')[0]
                 if not gene_dict.get(testKey):
                     gene_dict[testKey] = []
                 gene_dict[testKey].append((a[0], a[3], a[4], a[6]))

    read_list = []
    for transcript_id in gene_dict:
        transcript_data = gene_dict[transcript_id]

        chromosome = transcript_data[0][0]
        if not right_bounds.get(chromosome):
            left_bounds[chromosome], right_bounds[chromosome]= {}, {}
            left_bounds[chromosome]['5'], right_bounds[chromosome]['5'] = [], []
            left_bounds[chromosome]['3'], right_bounds[chromosome]['3'] = [], []

        start = sorted(transcript_data, key=lambda x: int(x[1]))[0][1]
        end = sorted(transcript_data, key=lambda x: int(x[2]), reverse=True)[0][2]

        for entry in transcript_data:
            if entry[1] != start:
                if entry[3] == '+':
                    right_bounds[chromosome]['3'].append(int(entry[1])-1)
                elif entry[3] == '-':
                    right_bounds[chromosome]['5'].append(int(entry[1])-1)
            if entry[2] != end:
                if entry[3] == '+':
                    left_bounds[chromosome]['5'].append(int(entry[2]))
                if entry[3] == '-':
                    left_bounds[chromosome]['3'].append(int(entry[2]))
    return chromosome_list, left_bounds, right_bounds

def make_genome_bins(bounds, side, peaks, chromosome, peak_areas,out):
    '''
    Insert docstring
    '''

    for type1 in ['5', '3']:
        covered = {}
        position_list = sorted(bounds[type1], key=int)
        for index1 in range(0, len(position_list)):
            if not covered.get(index1):
                sub_list=[]
                sub_list.append(position_list[index1])
                for index2 in range(index1, len(position_list)):
                    if position_list[index2] - max(sub_list) <= splice_site_width:
                        sub_list.append(position_list[index2])
                        covered[index2] = 1
                    else:
                        break
                single = 0
                if len(sub_list) > 1:
                    splice_distances = []
                    for splice_pos in range(0, len(sub_list)-1):
                        splice_distances.append(int(sub_list[splice_pos+1])
                                                -int(sub_list[splice_pos]))
                    if min(splice_distances) > 3:
                        for x in range(0,len(sub_list),1):
                            if x != 0:
                                start = int(sub_list[x]
                                            - ((sub_list[x] - sub_list[x-1])/2))
                            else:
                                start = int(sub_list[x]) - 1
                            if x != len(sub_list) - 1:
                                end = int(sub_list[x]
                                          + ((sub_list[x+1] - sub_list[x])/2))
                            else:
                                end = int(sub_list[x]) + 1

                            out.write(chromosome + '\t' + str(start) + '\t'
                                      + str(end) + '\t' + type1 + side
                                      + str(peaks) + '_' + str(start) + '_'
                                      + str(end) + '_A' + '\t' + str(peaks) + '\n')
                            for base in range(start, end):
                                peak_areas[chromosome][side][base] = 1
                            peaks += 1
                    else:
                         single = 1
                else:
                    single = 1
                if single == 1:
                    start = min(sub_list) - splice_site_width
                    end = max(sub_list) + splice_site_width
                    out.write(chromosome + '\t' + str(start) + '\t' + str(end)
                              + '\t' + type1 + side + str(peaks) + '_'
                              + str(start) + '_' + str(end) + '_A'
                              + '\t' + str(peaks) + '\n')
                    for base in range(start-1, end+1):
                        peak_areas[chromosome][side][base] = 1
                    peaks += 1

    return peaks, peak_areas

def get_alignment_direction(sam_file):
    direction_dict={}
    for line in open(sam_file):
        if line[0]!='@':
            a=line.strip().split('\t')
            read_name=a[0]
            for entry in a[10:]:
                if 'ts:A:' in entry:
                    direction=entry.split('ts:A:')[1]
                    direction_dict[read_name]=direction
    return direction_dict

def main():
    left_bounds = {}
    right_bounds = {}

    chromosome_list,left_bounds, right_bounds = parse_genome(genome_file, left_bounds, right_bounds)

    Left_Peaks = 0
    Right_Peaks = 0

    histo_left_bases, histo_right_bases, \
    chromosome_list, histo_coverage = collect_reads(content_file,chromosome_list)
    out = open(out_path + '/SS.bed', 'w')

    peak_areas = {}
    print(chromosome_list)
    for chromosome in chromosome_list:
        peak_areas[chromosome] = {}
        peak_areas[chromosome]['l'] = {}
        peak_areas[chromosome]['r'] = {}
        if not left_bounds.get(chromosome):
            left_bounds[chromosome]={}
            left_bounds[chromosome]['5'],left_bounds[chromosome]['3']=[],[]
        if not right_bounds.get(chromosome):
            right_bounds[chromosome]={}
            right_bounds[chromosome]['5'],right_bounds[chromosome]['3']=[],[]
        print(chromosome)

        if 'g' in refine:
            Left_Peaks_old = Left_Peaks
            Right_Peaks_old = Right_Peaks
            Left_Peaks, peak_areas = make_genome_bins(left_bounds[chromosome], 'l',
                                                      Left_Peaks, chromosome, peak_areas,out)
            Right_Peaks, peak_areas = make_genome_bins(right_bounds[chromosome], 'r',
                                                       Right_Peaks, chromosome, peak_areas,out)
            print('Annotation-Based',
                  Left_Peaks - Left_Peaks_old,
                  Right_Peaks - Right_Peaks_old)
        Left_Peaks_old = Left_Peaks
        Right_Peaks_old = Right_Peaks
        Left_Peaks, peak_areas = find_peaks(histo_left_bases[chromosome],
                                            out, Left_Peaks, True, cutoff,
                                            0, 5, histo_coverage, 'l',
                                            peak_areas, chromosome)
        Right_Peaks, peak_areas = find_peaks(histo_right_bases[chromosome],
                                             out, Right_Peaks, False, cutoff,
                                             0, 5, histo_coverage, 'r',
                                             peak_areas, chromosome)
        print('Read-Based',
              Left_Peaks - Left_Peaks_old,
              Right_Peaks - Right_Peaks_old)
    print(Left_Peaks)
    print(Right_Peaks)

main()