#!/usr/local/bin/python3
from __future__ import division
import argparse
import os
import numpy as np
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt
import time
from scipy.interpolate import griddata
from scipy.interpolate import CubicSpline, Akima1DInterpolator, PchipInterpolator, CubicHermiteSpline

start = time.time()

plt.rcParams['axes.formatter.useoffset'] = False # Remove axis offsetting in matplotlib - Makes plots look nicer
plt.rc('text', usetex=True)
plt.rc('font', family='serif')

parser=argparse.ArgumentParser()
parser.add_argument('mode',type=str,help="Mode of operation. Specify 'plot_F', 'plot_G', 'plot_G_normalise', 'plot_pressures', or 'plot_pressures_normalise'.")
parser.add_argument('-ylim',nargs='+',type=float,help="Manually supply the lower and upper y-axis limits in units of eV.")
parser.add_argument('-xlim',nargs='+',type=float,help="Manually supply the lower and upper x-axis limits in the relevant pressure units (i.e. GPa or TPa).")
parser.add_argument('-figsize',nargs=2,type=float,default=[6,4.5],help="Matplotlib figure size.")
parser.add_argument('-give_errors',action='store_true',help="If this flag is supplied, the shaded error widths will be removed from all curves.")
parser.add_argument('-guess_params',nargs='+',type=float,help="Initial guess for EoS parameters E_O, V_0, B_0 and B_0_prime (eV, Ang^3, GPa, dimensionless respectively) as space-separated list.")
parser.add_argument('-normalising_structure',default=None,type=str,help="For the 'plot_G_normalise' or 'plot_pressures_normalise' modes , the data will be normalised to this structure.")
parser.add_argument('-eos',default=None,type=str,help="Analytical equation of state to be fitted to. 'Vinet','BM' or 'PT'.")
parser.add_argument('-interpolator',default='CubicSpline',help="Type of scipy interpolator. Choose from 'CubicSpline', 'Akima1DInterpolator', 'PchipInterpolator' or 'CubicHermiteSpline' ")
parser.add_argument('-extrapolation_factor',default=0.05,type=float,help="""Factor by which to extrapolate the volume in the F vs V curve. Setting this
to zero means no extrapolation at all. The volumes will become: (min_volume)*(1-extrapolation_factor) and (max_volume)*(1+extrapolation_factor).""")
parser.add_argument('-functional',help='Optionally pass the name of the functional used for the calculations - This will be used as a label on the plots.')
parser.add_argument('-give_minima',action='store_true',help="""If this flag is supplied when using the 'plot_G_normalise' mode, will print out
the smallest values of ΔG for each curve (relative to the normalising structure).""")
parser.add_argument('-give_maxima',action='store_true',help="""If this flag is supplied when using the 'plot_G_normalise' mode, will print out
the largest values of ΔG for each curve (relative to the normalising structure).""")
parser.add_argument('-use_derived_volume',action='store_true',help="""If this flag is supplied, plotted volumes will be derived from the
(numerical) gradient of G. Default behaviour is to fit the DFT volumes & pressures to a cubic spline.""")
parser.add_argument('-tpa',action='store_true',help="If this flag is supplied, pressure units on plots will be TPa.")
parser.add_argument('-filter_outliers',action='store_true',help="If this flag is supplied, raw datapoints outside the normalised range will be removed.")
parser.add_argument('-legend_loc',type=int,help="Matplotlib legend location (integer, 0-10).")
parser.add_argument('-legend_ncols',type=int,help="Matplotlib number of columns in legend.")
parser.add_argument('-shade_stability',action='store_true',help="""If this flag is supplied, the lowest enthalpy structures will be shaded in their stability region,
	provided that these have been specified in the .pressure_vary files via use of the '#shadelow=' and '#shadehigh=' tags.""")
parser.add_argument('-label_structures',action='store_true',help="If this flag is supplied, the lowest enthalpy structures will be labelled with their structure name.")
parser.add_argument('-num_fine_points',type=int,default=10**6,help="""Number of points in the fine (interpolated) grid. Use 10^5 or 10^6 for publication quality.
Can converge residual fitting errors wrt to num_fine_points (stops changing after like 10^6 or so).""")
parser.add_argument('-no_enthalpy_scatter',action='store_true',help="If this flag is supplied, DFT enthalpies will not be plotted as scatter points.")
args=parser.parse_args()

mode=args.mode
ylim=args.ylim
xlim=args.xlim
figsize=args.figsize
give_errors=args.give_errors
guess_params=args.guess_params
normalising_structure=args.normalising_structure
eos=args.eos
interpolator=args.interpolator
extrapolation_factor=args.extrapolation_factor
functional=args.functional
give_minima=args.give_minima
give_maxima=args.give_maxima
use_derived_volume=args.use_derived_volume
tpa=args.tpa
filter_outliers=args.filter_outliers
legend_loc=args.legend_loc
legend_ncols=args.legend_ncols
label_structures=args.label_structures
shade_stability=args.shade_stability
num_fine_points=args.num_fine_points
no_enthalpy_scatter=args.no_enthalpy_scatter

####################################################################################################
# Globally useful stuff, conversion factors, etc

cwd = os.getcwd()
file_list=os.listdir("%s" % (cwd))

charge_e = 1.602176634e-19 # electronic charge e
volume_conversion_factor = (charge_e * 10**21) # with V = dG/dp in units of eV / GPa, multiply by this to convert the volume to Ang^3
if tpa: volume_conversion_factor = volume_conversion_factor / 1000 # Ditto for TPa

# If no legend location or ncols was specified, use these as defaults
if not legend_loc:
	legend_loc = 1
if not legend_ncols:
	legend_ncols = 3

guess_params_supplied=False
if guess_params is not None:
	guess_params_supplied=True
	guess_params[2] = guess_params[2] / 160.21766208 # User supplies Bulk Modulus in GPa, so convert this to eV / A^3

####################################################################################################
# Parse and set some universal stuff

# Set pressure units
pressure_units = 'GPa'
if tpa:
	pressure_units = 'TPa'

# Check whether user has requested a spline fit, or an equation of state fit
use_spline = True
if eos:
	use_spline = False

# Parse scipy interpolator type (hereafter, 'interpolator' will be a callable)
if interpolator == "CubicSpline":
	interpolator = CubicSpline
elif interpolator == "Akima1DInterpolator":
	interpolator = Akima1DInterpolator
elif interpolator == "PchipInterpolator":
	interpolator = PchipInterpolator
elif interpolator == "CubicHermiteSpline":
	interpolator = CubicHermiteSpline
else:
	print("ERROR: Interpolator type not recognised.")
	print("Should be one of: 'CubicSpline', 'Akima1DInterpolator', 'PchipInterpolator' or 'CubicHermiteSpline' ")
	exit()

####################################################################################################
# Equations of State functions & derivatives (Vinet, Birch-Murnaghan (BM) and Poirier-Tarantola (PT)).
# Have tested all the analytical derivatives as correct using finite differences.

def f_eos(V,E_0,V_0,B_0,B_0_prime):
	V_0=abs(V_0)
	if eos == 'Vinet':
		E = E_0 + (2*B_0*V_0)/(B_0_prime-1)**2 * ( 2 - (5+3*abs(V/V_0)**(1/3)*(B_0_prime-1)-3*B_0_prime)*np.exp(-(3/2)*(B_0_prime-1)*(abs(V/V_0)**(1/3)-1)) )
	elif eos == 'BM':
		E = E_0 + 9*V_0*B_0/16 * ( (abs((V_0/V))**(2/3)-1)**3*B_0_prime + (abs((V_0/V))**(2/3)-1)**2 * (6-4*abs((V_0/V))**(2/3))  )
	elif eos == 'PT':
		E = E_0 + (B_0*V_0)/2 * np.log(V_0/V)**2 * ( 1 + (B_0_prime-2)/3 * np.log(V_0/V) )
	elif eos == 'Tait':
		E = E_0 + (B_0*V_0)/(B_0_prime+1) * ( (V/V_0) - 1 + 1/(B_0_prime+1) * ( np.exp( (B_0_prime+1)*(1-V/V_0) ) -1 ) )
	return E

def df_dV_eos(V,E_0,V_0,B_0,B_0_prime):
	V_0=abs(V_0)
	if eos == 'Vinet':
		df_dV = (2*B_0*V_0)/(B_0_prime-1)**2 * -1/(V**(2/3)*V_0**(1/3))*(B_0_prime-1) * ( 1 - 0.5*( 5 + 3*(V/V_0)**(1/3)*(B_0_prime-1) - 3*B_0_prime ) ) * np.exp(-3/2*(B_0_prime-1)*((V/V_0)**(1/3)-1))
	elif eos == 'BM':
		df_dV = 9*V_0*B_0/16 * (V_0)**(2/3)/V**(5/3) * ( -2*( (V_0/V)**(2/3) -1 )**2 * B_0_prime - (4/3)*( (V_0/V)**(2/3) -1 )*( 6-4*(V_0/V)**(2/3) ) + (8/3)*( (V_0/V)**(2/3) -1 )**2  )
	elif eos == 'PT':
		df_dV = -(B_0*V_0)/(2 * V) * np.log(V_0/V) * ( 2 + (B_0_prime-2) * np.log(V_0/V) )
	elif eos == 'Tait':
		df_dV = B_0/(B_0_prime+1) * ( 1-np.exp( (B_0_prime+1)*(1-V/V_0) ) )
	return df_dV

def d2f_dV2_eos(V,E_0,V_0,B_0,B_0_prime):
	V_0=abs(V_0)
	if eos == 'Vinet':
		x=(V/V_0)**(1/3)
		d2f_dV2 = (B_0/V_0) * (1/x)**(4) * np.exp( (3/2)*(B_0_prime-1)*(1-x) ) * ( 1 + 2*(1-x)/x + (3/2)*(B_0_prime-1)*(1-x) )
	elif eos == 'BM': # THIS IS WRONG, fix at some point
		d2f_dV2 = 9*V_0*B_0/16 * ( ((10/9)*((V_0)**(2/3)/(V)**(8/3))-1)**3*B_0_prime + ((10/9)*((V_0)**(2/3)/(V)**(8/3))-1)**2 * (6-(40/9)*((V_0)**(2/3)/(V)**(8/3)))  )
	elif eos == 'PT':
		d2f_dV2 = (B_0*V_0)/(2 * V**2) * ( np.log(V_0/V) * ( 2 + 2*(B_0_prime-2) + (B_0_prime-2)*np.log(V_0/V) ) + 2 )
	elif eos == 'Tait':
		d2f_dV2 = (B_0/V_0**2) * np.exp( (B_0_prime+1)*(1-V/V_0) )
	return d2f_dV2

def pV_eos(V,V_0,B_0,B_0_prime):
	V_0=abs(V_0)
	if eos == 'Vinet':
		x=(V/V_0)**(1/3)
		p=3*B_0 * ((1-x)/x**2) * np.exp(3/2 * (B_0_prime-1)*(1-x))
	return p

####################################################################################################
# Define a 'trim_data' function that trims supplied data between two pressures.
# This is called if the user specifies either '#trimlow=X' and/or '#trimhigh=Y' in the data file.

def trim_data(data_grid,pressure_grid,p_min,p_max):
	if p_min is None:
		p_min = -1E20
	if p_max is None:
		p_max = 1E20
	zipped_data = zip(data_grid,pressure_grid)
	zipped_data = np.array([ x for x in zipped_data if ( (x[1]>p_min) and (x[1]<p_max) ) ])
	data_grid = zipped_data[:,0]
	pressure_grid = zipped_data[:,1]
	return(data_grid,pressure_grid)

################################################################################################################
# Initialise subplots, define a marker tray, define a function to parse structure names.

fig, ax = plt.subplots(figsize=figsize)

marker_tray = ['o','s','v','^','D','p','P','*','<','>','h','8']

def parse_structure_name(structure_name):
	if 'sl' in structure_name:
		structure_name = structure_name.replace('sl','/')
	if 'un' in structure_name:
		structure_name = structure_name.replace('un','_')
	if '-' in structure_name:
		to_be_barred=structure_name.split('-')[-1][0] # this character will have a bar over it
		structure_name = structure_name.replace('-'+to_be_barred,'\\overline{%s}'%(to_be_barred)) # 'overline' is overbar for LaTex
	return structure_name

################################################################################################################
# User printouts, errors etc.

if len(file_list) == 0:
	print('No <name>_pressure_vary.results files found. Aborting.')
	exit()

if  ( ( (mode == 'plot_G_normalise') or (mode == 'plot_pressures_normalise') ) and (normalising_structure+'_pressure_vary.results' in file_list) ): # If the normalising structure file is there, bring it to the front of the list. Energies will be normalised to this structure.
	file_list.insert( 0, file_list.pop(file_list.index(normalising_structure+'_pressure_vary.results')) )
elif mode == 'plot_G_normalise':
	print('Specified normalising structure results file (%s_pressure_vary.results) not found in current directory. Aborting.' % (normalising_structure))
	exit()

################################################################################################################

count=0 # Structure index counter

if normalising_structure:
	normalising_structure = parse_structure_name(normalising_structure)

for filename in file_list:
	if filename.endswith("pressure_vary.results"):
		file=open(cwd+'/'+filename,'r')
		lines=file.readlines()
		file.close()
		# Get the structure name by splitting the filename on '_' (structure name is always first element)
		structure_name=filename.split("_")[0]
		# Dealing with underscores and slashes in spacegroup names...
		structure_name = parse_structure_name(structure_name)
		print(structure_name)
		extrapolation_factor_lower=extrapolation_factor # Will remain as this unless overridden with a user-specified '#extrapolation_factor=X' in the file.
		extrapolation_factor_higher=extrapolation_factor # Ditto.
		# The following list will hold the results as we parse them from the file
		results=[]
		# If any are supplied in the results file, the following list will be filled with tags (lines starting with a '#').
		# Tags indicate special things, i.e. imaginary modes beyond a certain pressure or the type of functional used. See later in code.
		tags=[]
		for line in lines:
			if '#' not in line:
				line.replace(' ','') # Remove all whitespace
				linesplit = line.split(",") # Data is comma-delimited; split each line on a comma
				results.append(linesplit)
			else:
				tag = line.replace(' ','').lower() # Remove all whitespace and cast to lowercase
				tags.append(tag) # If there's a '#' in the line, this is some sort of tag and will be parsed later.
				if '#extrapolation_factor_lower' in tag: # If present, process this particular tag right now
					extrapolation_factor_lower=float(tag.split("=")[-1])
				if '#extrapolation_factor_higher' in tag: # If present, process this particular tag right now
					extrapolation_factor_higher=float(tag.split("=")[-1])
		results=sorted(results,key=lambda x: float(x[0]))
		results_array=np.array(results) # Cast to array
		 # Cast all data to float & sort by pressure
		DFT_pressures=[ float(x) for x  in results_array[:,0] ]
		volume_values=[ float(x) for x  in results_array[:,1] ]
		helmholtz_energy=[ round(float(x),7) for x  in results_array[:,2] ]
		try:
			enthalpies=[ round(float(x),7) for x  in results_array[:,3] ]
		except:
			enthalpies = None
		vol_max, vol_min = volume_values[0], volume_values[-1]
		# If volume is negative, user has probably got energies and volumes wrong way round in results file...
		if any(vol < 0 for vol in volume_values):
			print('Error. Detected a negative volume - Have you got energies and volumes wrong way round in results file?')
			print('  -> Check ordering of columns in %s_pressure_vary.results file, it should be: <pressure>,<volume>,<energy> or <pressure>,<volume>,<energy>,<DFT enthalpy>' % (structure_name))
			exit()
		################################################################################################################
		# Get colour from cycle
		num = len(plt.rcParams['axes.prop_cycle'].by_key()['color'])
		colour = plt.rcParams['axes.prop_cycle'].by_key()['color'][count%num]
		################################################################################################################
		# Fitting...
		volume_values.sort()
		helmholtz_energy.reverse()
		volume_values_fine = np.linspace(vol_min*(1-extrapolation_factor_lower),vol_max*(1+extrapolation_factor_higher),num_fine_points)

		# If user requested a spline fit
		if use_spline:
			F_CS = interpolator(volume_values,helmholtz_energy,extrapolate=True)
			helmholtz_values_fitted = F_CS(volume_values_fine)
			if use_derived_volume: # Then the enthalpy / Gibbs free energy is to be calculated via a derivative
				fine_pressures = F_CS.derivative()
				fine_pressures = -fine_pressures(volume_values_fine)
				gibbs_free_energy = helmholtz_values_fitted + fine_pressures * volume_values_fine
				pressure_values_gpa_fine = [ (x * 160.21766208) for x in fine_pressures ] # Convert to GPa
			else: # Then we'll fit the actual DFT pressures & enthalpies to a (different) spline
				if (enthalpies is None):
					print("ERROR: Cannot fit enthalpies because none were found in %s_pressure_vary.results file" % (structure_name))
					print("To derive the enthalpies via the derivative of the energy, supply the 'use_derived_volume' flag")
					exit()
				H_CS = interpolator(DFT_pressures,enthalpies,extrapolate=True) # Cubic spline for H
				pressure_values_gpa_fine = np.linspace(DFT_pressures[0]*(1-extrapolation_factor_lower),DFT_pressures[-1]*(1+extrapolation_factor_higher),num_fine_points)
				gibbs_free_energy = H_CS(pressure_values_gpa_fine)

		# Otherwise, user requested an eos fit
		else:
			guess_params=[ helmholtz_energy[0],int(volume_values[0]),0.3,4 ] # Use first F & V values as initial guesses for E_0 and V_0, and guess B_0=0.3, B_0_prime=4
			params, covariances = curve_fit(f_eos,volume_values,helmholtz_energy,guess_params,maxfev=10**8)
			E_0_error=np.sqrt(covariances[0][0])
			V_0_error=np.sqrt(covariances[1][1])
			B_0_error=np.sqrt(covariances[2][2])
			B_0_prime_error=np.sqrt(covariances[3][3])
			E_0_fitted=float(params[0])
			V_0_fitted = float(params[1])
			B_0_fitted = float(params[2])
			B_0_prime_fitted = float(params[3])
			helmholtz_values_fitted = [ f_eos(x,E_0_fitted,V_0_fitted,B_0_fitted,B_0_prime_fitted) for x in volume_values_fine ]
			################################################################################################################
			# Calculate pressure and Gibbs free energy
			pressure_values_fine = [ - df_dV_eos(x,E_0_fitted,V_0_fitted,B_0_fitted,B_0_prime_fitted) for x in volume_values_fine ]
			gibbs_free_energy = np.array([ (helmholtz_values_fitted[j] + pressure_values_fine[j]*volume_values_fine[j]) for j in range(len(pressure_values_fine)) ])
			pressure_values_gpa_fine = [ (x * 160.21766208) for x in pressure_values_fine ] # convert to GPa

		################################################################################################################
		# Convert to terapascal, if requested
		if tpa:
			pressure_values_gpa_fine = np.array(pressure_values_gpa_fine) / 1000 # Convert to TPa
			DFT_pressures = np.array(DFT_pressures) / 1000 # Convert to TPa

		################################################################################################################
		# Calculate errors, if requested

		# Error in F
		if give_errors:
			residual_datapoints = [ np.argmin ( np.abs(np.array(volume_values_fine) - volume_values[j] ) ) for j in range (len(volume_values)) ] # Finds the datapoints in volume_values_fine that are closest to the actual volume datapoints (they should be very close)
			if use_spline:
				residuals = [ F_CS(volume_values_fine[residual_datapoints[j]]) - helmholtz_energy[j] for j in range(len(residual_datapoints)) ] # For each one of these volumes, compute the residual.
			else:
				residuals = [ f_eos(volume_values_fine[residual_datapoints[j]],E_0_fitted,V_0_fitted,B_0_fitted,B_0_prime_fitted) - helmholtz_energy[j] for j in range(len(residual_datapoints)) ] # For each one of these volumes, compute the residual.
			f_error = np.std(residuals)
			print('Residual fitting error for %s (eV): F_std = %f' % (structure_name,f_error))

		# Error in F & G (can only do this if the DFT enthalpies are supplied)
		if (give_errors and enthalpies):
			G_residual_datapoints = [ np.argmin ( np.abs(np.array(pressure_values_gpa_fine) - DFT_pressures[j] ) ) for j in range (len(DFT_pressures)) ]
			G_residuals = [ gibbs_free_energy[G_residual_datapoints[j]] - enthalpies[j]  for j in range(len(G_residual_datapoints)) ]
			G_std_error = np.std(G_residuals)
			print('Residual fitting error for %s (eV): F_std = %f, G_std = %f' % (structure_name,f_error,G_std_error))
		################################################################################################################
		#If the user requests a vertical line to be plotted at some pressure
		for tag in tags:
			if '#vline' in tag:
				vline = float(tag.split("=")[-1])
				ax.axvline(x=vline,linestyle='dashed',color='k')
			if '#colour' in tag:
				colour = str(tag.split("=")[-1])
				colour = colour.replace('\n','')
		################################################################################################################
		# Plotting F Vs V - Uncomment as needed
		if mode == 'plot_F':
			print(volume_values)
			print(helmholtz_energy)
			ax.plot(volume_values,helmholtz_energy,color='%s' % (colour), linestyle="none", marker="+")
			ax.plot(volume_values_fine,helmholtz_values_fitted,label=r'$\mathrm{%s}$' % (structure_name))
			plt.xlabel(r'Volume per atom (\AA$^3$)', fontsize=20, usetex=True)
			plt.ylabel(r'Helmholtz Free Energy (eV)', fontsize=20, usetex=True)
			if give_errors:
				ax.fill_between(volume_values_fine, np.array(helmholtz_values_fitted)+f_error, np.array(helmholtz_values_fitted)-f_error, alpha=0.15)
        ################################################################################################################
        # Plotting G Vs p - Uncomment as needed
		elif mode == 'plot_G':
			ax.plot(pressure_values_gpa_fine,gibbs_free_energy,label=r'$\mathrm{%s}$' % (structure_name))
			if (enthalpies and not no_enthalpy_scatter):
				ax.scatter(DFT_pressures,enthalpies,color='%s' % (colour))
			plt.xlabel(r'Pressure (%s)' % (pressure_units), fontsize=20, usetex=True)
			plt.ylabel(r'Gibbs Free Energy (eV)', fontsize=20, usetex=True)
			if give_errors:
				try:
					ax.fill_between(pressure_values_gpa_fine, gibbs_free_energy+G_std_error, gibbs_free_energy-G_std_error, alpha=0.15) # Use stds as error
				except: # This exception is triggered when there are no enthalpies in the file, and thus 'G_std_error' does not even exist
					pass
		################################################################################################################
	    # Plotting G Vs p, normalised to the reference structure - Uncomment as needed
		elif ( (mode == 'plot_G_normalise') or (mode == 'plot_pressures_normalise') ):
			if structure_name == normalising_structure: # Get the normalising structure - By construction, this should be the first structure in the list
				pressure_norm_min=pressure_values_gpa_fine[0]
				pressure_norm_max=pressure_values_gpa_fine[-1]
				pressure_grid=np.linspace(pressure_norm_min,pressure_norm_max,num_fine_points) # All data is projected onto this common pressure grid, which runs between pressure_norm_min and pressure_norm_max
				interpolated_gibbs=griddata(pressure_values_gpa_fine,gibbs_free_energy,pressure_grid)
				gibbs_energy_reference=interpolated_gibbs
			else:
				interpolated_gibbs=griddata(pressure_values_gpa_fine,gibbs_free_energy,pressure_grid)
		################################################################################################################
		if mode == 'plot_G_normalise':
			# Normalise the energy
			normalised_energy = interpolated_gibbs - gibbs_energy_reference
			gibbs_energy_reference_truncated = gibbs_energy_reference
			normalised_energy_truncated = normalised_energy
			pressure_truncated = pressure_grid
			################################################################################################################
			# Normalise the DFT pressure & enthalpy scatter points
			if (filter_outliers and enthalpies):
				press_enth_zipped=zip(DFT_pressures,enthalpies) # zip
				press_enth_zipped=[ x for x in press_enth_zipped if (x[0] > min(pressure_truncated)) and (x[0] < max(pressure_truncated)) ] # filter out datapoints that lie outside pressure_truncated
				DFT_pressures, enthalpies = zip(*press_enth_zipped) # unzip
			DFT_pressures_nearest_idx = [ np.argmin ( abs(pressure_truncated - pressure) ) for pressure in DFT_pressures  ] # finds the closest fine pressure datapoint to the individual scatter DFT pressure datapoint
			if (enthalpies and not no_enthalpy_scatter):
				scatter_enthalpy_normalised = [ (enthalpies[idx]-gibbs_energy_reference_truncated[i]) for idx,i in enumerate(DFT_pressures_nearest_idx) ]
			################################################################################################################
			# Tags
			trimlow,trimhigh=None,None
			low_shade_pressure,high_shade_pressure=None,None
			for tag in tags:
				if '#trimlow' in tag:
					trimlow=float(tag.split("=")[-1])
				if '#trimhigh' in tag:
					trimhigh=float(tag.split("=")[-1])
				if (trimlow or trimhigh):
					normalised_energy_truncated,pressure_truncated = trim_data(normalised_energy_truncated,pressure_truncated,trimlow,trimhigh)
					if (enthalpies and not no_enthalpy_scatter):
						scatter_enthalpy_normalised,DFT_pressures = trim_data(scatter_enthalpy_normalised,DFT_pressures,trimlow,trimhigh)
				if '#shadelow' in tag:
					low_shade_pressure=float(tag.split("=")[-1])
					if tpa and (low_shade_pressure>500):
						print('\'-tpa\' flag specified, so using pressure units of TPa. But #shadelow seems to be in GPa... Aborting.')
						exit()
				if '#shadehigh' in tag:
					high_shade_pressure=float(tag.split("=")[-1])
					if tpa and (high_shade_pressure>500):
						print('\'-tpa\' flag specified, so using pressure units of TPa. But #shadehigh seems to be in GPa... Aborting.')
						exit()
			################################################################################################################
			# Colour shading for lowest-enthalpy structure
			midpoint=None
			if shade_stability:
				if low_shade_pressure and high_shade_pressure:
					ax.axvspan(low_shade_pressure, high_shade_pressure, alpha=0.125, color=colour)
					midpoint = low_shade_pressure + (high_shade_pressure-low_shade_pressure) / 2
					low_shade_pressure,high_shade_pressure=None,None
				elif low_shade_pressure:
					ax.axvspan(low_shade_pressure, max(pressure_truncated), alpha=0.125, color=colour)
					midpoint = low_shade_pressure + (max(pressure_truncated)-low_shade_pressure) / 2
					low_shade_pressure=None
				elif high_shade_pressure:
					ax.axvspan(min(pressure_truncated), high_shade_pressure, alpha=0.125, color=colour)
					midpoint = min(pressure_truncated) + (high_shade_pressure-min(pressure_truncated)) / 2
					high_shade_pressure=None
			# SPACEGROUP LABELLING
			if (midpoint and label_structures):
				plt.text(midpoint, 0, r'$\mathrm{%s}$' % (structure_name),fontsize=10,horizontalalignment="center", color='k',
					bbox=dict(facecolor='white', edgecolor=colour, boxstyle='round', alpha=0.75))
			################################################################################################################
			# 'NRM' is a special reserved structure name - If a structure
			# is called 'NRM', it will not have its data plotted
			if (structure_name != 'NRM'):
				marker = marker_tray[ count%len(marker_tray) ]
				if (enthalpies and not no_enthalpy_scatter):
					ax.scatter(DFT_pressures,scatter_enthalpy_normalised,marker=marker,s=18,color=colour)
				ax.plot(pressure_truncated,normalised_energy_truncated,linewidth=1.25,label=r'$\mathrm{%s}$' % (structure_name),color=colour,marker=marker,markevery=[])
			closest_energy_idx = np.nanargmin([x for x in normalised_energy_truncated])
			closest_energy = normalised_energy_truncated[closest_energy_idx]
			pressure_at_closest_energy = pressure_truncated[closest_energy_idx]
			furthest_energy_idx = np.nanargmax([x for x in normalised_energy_truncated])
			furthest_energy = normalised_energy_truncated[closest_energy_idx]
			pressure_at_furthest_energy = pressure_truncated[closest_energy_idx]
			if (give_minima and structure_name != normalising_structure):
				print('Smallest ΔG for %s was %f eV at %f GPa' % (structure_name,closest_energy,pressure_at_closest_energy))
			if (give_maxima and structure_name != normalising_structure):
				print('Largest ΔG for %s was %f eV at %f GPa' % (structure_name,furthest_energy,pressure_at_furthest_energy))
			if (functional is not None): # Then user requested the functional used to be labelled.
				plt.text(0.5, 0.95, functional,fontsize=15,horizontalalignment="center",transform=plt.gca().transAxes, color='blue',bbox=dict(facecolor='none', edgecolor='blue', boxstyle='round',fc='gray', alpha=0.05))
			plt.xlabel(r'Pressure (%s)' % (pressure_units), fontsize=19, usetex=True)
			#plt.ylabel(r'Gibbs Free Energy (eV)', fontsize=19, usetex=True)
			plt.ylabel('Enthalpy (eV/atom)', fontsize=19, usetex=True)
			if give_errors:
				try:
					ax.fill_between(pressure_truncated, normalised_energy_truncated+G_std_error, normalised_energy_truncated-G_std_error, alpha=0.15)
				except: # This exception is triggered when there are no enthalpies in the file, and thus 'G_std_error' does not even exist
					pass
		################################################################################################################
		# Plotting V vs p
		elif (mode == 'plot_pressures_normalise' or mode == 'plot_pressures'):
			volume_values.reverse() # Else volumes are ordered backwards
			if use_derived_volume: # derive volume from gradient of Gibbs curve
				if (count == 0):
					print('Volumes are being derived from the derivative of G...')
				volumes_fine=np.gradient(gibbs_free_energy,pressure_values_gpa_fine) * volume_conversion_factor # V = dG/dp, with conversion to Ang^3
			else: # fit DFT volumes & pressures to a cubic spline (default)
				if count == 0: print('Volumes are being fitted to a cubic spline using the raw data...')
				V_CS=interpolator(DFT_pressures,volume_values,extrapolate=True) # no conversion factor needed - raw volumes already supplied in Ang^3
				volumes_fine=V_CS(pressure_values_gpa_fine)

			if mode == 'plot_pressures':
				# Calculate residuals...
				min_idxs = [np.argmin ( np.abs( np.array(pressure_values_gpa_fine) - p ) ) for p in DFT_pressures]
				V_residuals = [volumes_fine[idx] - volume_values[i] for i,idx in enumerate(min_idxs)]
				trimlow,trimhigh=None,None
				for tag in tags:
					if '#trimlow' in tag:
						trimlow=float(tag.split("=")[-1])
					if '#trimhigh' in tag:
						trimhigh=float(tag.split("=")[-1])
					if trimlow or trimhigh:
						volumes_fine,pressure_values_gpa_fine = trim_data(volumes_fine,pressure_values_gpa_fine,trimlow,trimhigh)
						volume_values,DFT_pressures = trim_data(volume_values,DFT_pressures,trimlow,trimhigh)
				ax.plot(pressure_values_gpa_fine,volumes_fine,label=r'$\mathrm{%s}$' % (structure_name),linewidth=2)
				ax.scatter(DFT_pressures,volume_values,marker='x')
				plt.xlabel(r'Pressure (%s)' % (pressure_units), fontsize=19, usetex=True)
				plt.ylabel(r'Volume per atom (\AA$^3$)', fontsize=19, usetex=True)

			else:
				if structure_name == normalising_structure: # Get the normalising structure - By construction, this should be the first structure in the list
					pressure_norm_min=pressure_values_gpa_fine[0]
					pressure_norm_max=pressure_values_gpa_fine[-1]
					pressure_grid=np.linspace(pressure_norm_min,pressure_norm_max,num_fine_points) # All data is projected onto this common pressure grid, which runs between 'pressure_norm_min' and 'pressure_norm_max'
					interpolated_volumes=griddata(pressure_values_gpa_fine,volumes_fine,pressure_grid)
					volume_reference=interpolated_volumes
				else:
					interpolated_volumes=griddata(pressure_values_gpa_fine,volumes_fine,pressure_grid)
				# Normalise the fine grid volumes
				normalised_volumes = interpolated_volumes-volume_reference
				plotting_pressure_min_index = np.argmin ( np.abs( pressure_grid - pressure_values_gpa_fine[0] ) )
				plotting_pressure_max_index = np.argmin ( np.abs( pressure_grid - pressure_values_gpa_fine[-1] ) )
				normalised_volumes_truncated = normalised_volumes[plotting_pressure_min_index:plotting_pressure_max_index]
				volume_reference_truncated = volume_reference[plotting_pressure_min_index:plotting_pressure_max_index]
				pressure_truncated = pressure_grid[plotting_pressure_min_index:plotting_pressure_max_index]
				# Normalise the DFT scatter raw volumes
				DFT_pressures_nearest_idx = [ np.argmin ( abs(pressure_grid - pressure) ) for pressure in DFT_pressures  ] # Finds the closest fine pressure datapoint to the individual scatter DFT pressure datapoint
				scatter_volumes_normalised = [ (volume_values[idx]-volume_reference[i]) for idx,i in enumerate(DFT_pressures_nearest_idx) ]
				###########################
				trimlow,trimhigh=None,None
				for tag in tags:
					if '#trimlow' in tag:
						trimlow=float(tag.split("=")[-1])
					if '#trimhigh' in tag:
						trimhigh=float(tag.split("=")[-1])
					if trimlow or trimhigh:
						normalised_volumes_truncated,pressure_truncated = trim_data(normalised_volumes_truncated,pressure_truncated,trimlow,trimhigh)
						scatter_volumes_normalised,DFT_pressures = trim_data(scatter_volumes_normalised,DFT_pressures,trimlow,trimhigh)
				ax.plot(pressure_truncated,normalised_volumes_truncated,label=r'$\mathrm{%s}$' % (structure_name),linewidth=2)
				ax.scatter(DFT_pressures,scatter_volumes_normalised)
				plt.xlabel(r'Pressure (%s)' % (pressure_units), fontsize=19, usetex=True)
				plt.ylabel(r'Volume per atom (\AA$^3$)', fontsize=19, usetex=True)
		################################################################################################################
		count+=1

end = time.time()
total_script_time = end - start
print('Total Script Time: %f s' % (total_script_time))

ax.legend(loc=legend_loc,ncol=legend_ncols,fontsize=12)
plt.xticks(fontsize=15)
plt.yticks(fontsize=15)
if (xlim and ylim):
	plt.xlim(xlim[0],xlim[1])
	plt.ylim(ylim[0],ylim[1])
elif (xlim):
	plt.xlim(xlim[0],xlim[1])
elif (ylim):
	plt.ylim(ylim[0],ylim[1])
plt.minorticks_on()
plt.tight_layout()
plt.show()


"""`ExportCableInstallation` and related processes."""

__author__ = "Jake Nunemaker"
__copyright__ = "Copyright 2020, National Renewable Energy Laboratory"
__maintainer__ = "Jake Nunemaker"
__email__ = "jake.nunemaker@nrel.gov"

from copy import deepcopy
from math import ceil

from marmot import process

from wisdem.orbit.core.logic import position_onsite
from wisdem.orbit.phases.install import InstallPhase
from wisdem.orbit.core.exceptions import InsufficientCable

from .common import SimpleCable as Cable
from .common import (
    lay_cable,
    bury_cable,
    dig_trench,
    pull_in_cable,
    landfall_tasks,
    lay_bury_cable,
    splice_process,
    terminate_cable,
    load_cable_on_vessel,
)

class ExportCableInstallation(InstallPhase):
    """Export Cable Installation Phase"""

    phase = "Export Cable Installation"
    capex_category = "Export System"

    #:
    expected_config = {
        "landfall": {"trench_length": "km (optional)"},
        "export_cable_install_vessel": "str | dict",
        "export_cable_bury_vessel": "str | dict (optional)",
        "export_cable_trench_vessel": "str (optional)",
        "site": {"distance": "km"},
        "plant": {"capacity": "MW"},
        "export_system": {
            "cable": {
                "linear_density": "t/km",
                "sections": [("length, km", "speed, km/h (optional)")],
                "number": "int (optional)",
            },
            "interconnection_distance": "km (optional); default: 3km",
            "interconnection_voltage": "kV (optional); default: 345kV",
        },
    }

    def __init__(self, config, weather=None, **kwargs):
        """
        Creates an instance of ExportCableInstallation.

        Parameters
        ----------
        config : dict
            Simulation specific configuration.
        weather : np.ndarray
            Weather profile at site.
        """

        super().__init__(weather, **kwargs)

        config = self.initialize_library(config, **kwargs)
        self.config = self.validate_config(config)

        self.initialize_port()
        self.extract_distances()
        self.setup_simulation(**kwargs)

    def setup_simulation(self, **kwargs):
        """
        Setup method for the ExportCableInstallation phase.
        - Extracts key inputs
        - Performs onshore infrastructure construction
        - Routes to specific setup scripts based on configured strategy.
        """

        depth = self.config["site"]["depth"]
        system = self.config["export_system"]
        self.free_cable_length = system.get("free_cable_length", depth / 1000)

        self.cable = Cable(system["cable"]["linear_density"])
        self.sections = system["cable"]["sections"]
        self.number = system["cable"].get("number", 1)

        self.initialize_installation_vessel()
        self.initialize_burial_vessel()
        self.initialize_trench_vessel()

        # Perform onshore construction
        onshore = kwargs.get("include_onshore_construction", True)
        if onshore:
            self.onshore_construction(**kwargs)

        # Perform cable installation
        install_export_cables(
            self.install_vessel,
            sections=self.sections,
            cable=self.cable,
            number=self.number,
            distances=self.distances,
            burial_vessel=self.bury_vessel,
            trench_vessel=self.trench_vessel,
            free_cable_length=self.free_cable_length,
            **kwargs,
        )

    @property
    def system_capex(self):
        """Returns total procurement cost of the array system."""

        return self.config["export_system"]["system_cost"]

    def extract_distances(self):
        """Extracts distances from input configuration or default values."""

        site = self.config["site"]["distance"]
        try:
            trench = self.config["landfall"]["trench_length"]

        except KeyError:
            trench = 1

        self.distances = {"site": site, "trench": trench}

    def onshore_construction(self, **kwargs):
        """
        Performs onshore construction prior to the installation of the export
        cable system.

        Parameters
        ----------
        construction_time : int | float
            Amount of time onshore construction takes.
            Default: 48h
        construction_rate : int | float
            Day rate of onshore construction.
            Default: 50000 USD/day
        """

        construction_time = kwargs.get("onshore_construction_time", 0.0)
        construction_cost = self.calculate_onshore_transmission_cost(**kwargs)

        if construction_time:
            _ = self.env.timeout(construction_time)
            self.env.run()

        self.env._submit_log(
            {
                "action": "Onshore Construction",
                "agent": "Onshore Construction",
                "duration": construction_time,
                "cost": construction_cost,
                "location": "Landfall",
            },
            level="ACTION",
        )

    def calculate_onshore_transmission_cost(self, **kwargs):
        """
        Calculates the cost of onshore transmission costs. From legacy
        OffshoreBOS model.
        """

        capacity = self.config["plant"]["capacity"]

        voltage = self.config["export_system"].get("interconnection_voltage", 345)
        distance = self.config["export_system"].get("interconnection_distance", 3)

        switchyard_cost = 18115 * voltage + 165944
        onshore_substation_cost = (0.165 * 1e6) * capacity  # From BNEF Tomorrow's Cost of Offshore Wind
        onshore_misc_cost = 11795 * capacity**0.3549 + 350000
        transmission_line_cost = (1176 * voltage + 218257) * (distance ** (1 - 0.1063))

        onshore_transmission_cost = (
            switchyard_cost + onshore_substation_cost + onshore_misc_cost + transmission_line_cost
        )

        return onshore_transmission_cost

    def initialize_installation_vessel(self):
        """Creates the export cable installation vessel."""

        # Vessel name and costs
        vessel_specs = self.config.get("export_cable_install_vessel", None)
        name = vessel_specs.get("name", "Export Cable Installation Vessel")

        vessel = self.initialize_vessel(name, vessel_specs)
        self.env.register(vessel)

        vessel.initialize()
        self.install_vessel = vessel

    def initialize_burial_vessel(self):
        """Creates the export cable burial vessel."""

        # Vessel name and costs
        vessel_specs = self.config.get("export_cable_bury_vessel", None)
        if vessel_specs is None:
            self.bury_vessel = None
            return

        name = vessel_specs.get("name", "Export Cable Burial Vessel")

        vessel = self.initialize_vessel(name, vessel_specs)
        self.env.register(vessel)

        vessel.initialize()
        self.bury_vessel = vessel

    def initialize_trench_vessel(self):
        """Creates the export cable trenching vessel."""

        # Vessel name and costs
        vessel_specs = self.config.get("export_cable_trench_vessel", None)
        if vessel_specs is None:
            self.trench_vessel = None
            return
        name = vessel_specs.get("name", "Export Cable Trench Vessel")

        vessel = self.initialize_vessel(name, vessel_specs)
        self.env.register(vessel)

        vessel.initialize()
        vessel.at_port = True
        vessel.at_site = False
        self.trench_vessel = vessel

    @property
    def detailed_output(self):
        """Detailed outputs of the export system installation."""

        outputs = {self.phase: {**self.agent_efficiencies}}

        return outputs

@process
def install_export_cables(
    vessel,
    sections,
    cable,
    number,
    distances,
    burial_vessel=None,
    trench_vessel=None,
    free_cable_length=None,
    **kwargs,
):
    """
    Simulation of the installation of export cables.

    Parameters
    ----------
    vessel : Vessel
        Cable installation vessel.
    sections : float
        Section lengths of the export cable.
    cable : SimpleCable | Cable
        Cable type to use.
    number : int
        Number of export cables.
    distances : dict
        Distances required for export cable installation simulation:
        site : int | float
            Distance between from the offshore substation and port. For
            simplicity, the cable landfall point is assumed to be at port.
        trench : int | float
            Trench length at landfall. Determines time required to tow the plow
            and pull-in cable (km).
    burial_vessel : Vessel
        Optional configuration for burial vessel. If configured, the
        installation vessel only lays the cable on the seafloor and this
        vessel will bury them at the end of the simulation.
    trench_vessel: Vessel
        Optional configuration for trenching vessel.  If configured, the
        trenching vessel travels along the cable route prior to arrival of
        the cable lay vessel and digs a trench.
    """

    ground_distance = -free_cable_length
    for s in sections:
        try:
            length, speed = s

        except TypeError:
            length = s

        ground_distance += length

    # Conduct trenching operations
    if trench_vessel is None:
        pass

    else:
        for _ in range(number):
            # Trenching vessel can dig a trench during inbound or outbound journey
            if trench_vessel.at_port:
                trench_vessel.at_port = False
                yield dig_export_cables_trench(trench_vessel, ground_distance, **kwargs)
                trench_vessel.at_site = True
            elif trench_vessel.at_site:
                trench_vessel.at_site = False
                yield dig_export_cables_trench(trench_vessel, ground_distance, **kwargs)
                trench_vessel.at_port = True

        # If the vessel finishes trenching at site, return to shore
        # TODO: replace with demobilization method
        if trench_vessel.at_site:
            trench_vessel.at_site = False
            yield trench_vessel.transit(ground_distance, **kwargs)
        trench_vessel.at_port = True

    for _ in range(number):
        vessel.cable_storage.reset()
        yield load_cable_on_vessel(vessel, cable, **kwargs)

        # At Landfall
        yield landfall_tasks(vessel, distances["trench"], **kwargs)

        for s in sections:
            splice_required = False
            try:
                length, speed = s
                if burial_vessel is None:
                    specs = {**kwargs, "cable_lay_bury_speed": speed}

                else:
                    specs = {**kwargs, "cable_lay_speed": speed}

            except TypeError:
                length = s
                specs = deepcopy(kwargs)

            remaining = length
            while remaining > 0:
                if splice_required:
                    yield splice_process(vessel, **kwargs)

                try:
                    section = vessel.cable_storage.get_cable(remaining)

                except InsufficientCable as e:
                    section = vessel.cable_storage.get_cable(e.current)

                if burial_vessel is None:
                    yield lay_bury_cable(vessel, section, **specs)

                else:
                    yield lay_cable(vessel, section, **specs)

                remaining -= ceil(section)
                if remaining > 0:
                    splice_required = True

                    yield vessel.transit(distances["site"])
                    vessel.cable_storage.reset()
                    yield load_cable_on_vessel(vessel, cable, **kwargs)
                    yield vessel.transit(distances["site"])

        # At Site
        yield position_onsite(vessel, **kwargs)
        yield pull_in_cable(vessel, **kwargs)
        yield terminate_cable(vessel, **kwargs)

        # Transit back to port
        yield vessel.transit(distances["site"])

    if burial_vessel is None:
        vessel.submit_debug_log(
            message="Export cable lay/burial process completed!",
            progress="Export System",
        )

    else:
        vessel.submit_debug_log(message="Export cable lay process completed!")
        bury_export_cables(burial_vessel, ground_distance, number, **kwargs)

@process
def bury_export_cables(vessel, length, number, **kwargs):
    """
    Simulation for the burial of export cables if configured.

    Parameters
    ----------
    vessel : Vessel
        Performing vessel.
    length : float
        Full length of an export cable.
    number : int
        Number of export cables.
    """

    for _ in range(number):
        yield bury_cable(vessel, length, **kwargs)

    vessel.submit_debug_log(
        message="Export cable burial process completed!",
        progress="Export System",
    )

@process
def dig_export_cables_trench(vessel, distance, **kwargs):
    """
    Simulation for digging a trench for the export cables (if configured).

    Parameters
    ----------
    vessel : Vessel
        Performing vessel.
    distance : int | float
        Distance along export cable route to dig trench for cable
    """

    yield position_onsite(vessel, site_position_time=2)
    yield dig_trench(vessel, distance, **kwargs)

    vessel.submit_debug_log(message="Export cable trench digging process completed!")


from absl.testing import parameterized
import numpy as np

from tensorflow.core.framework import full_type_pb2
from tensorflow.python.eager import context
from tensorflow.python.eager import def_function
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import errors
from tensorflow.python.framework import ops
from tensorflow.python.framework import sparse_tensor
from tensorflow.python.framework import tensor as tensor_lib
from tensorflow.python.framework import tensor_shape
from tensorflow.python.framework import test_util
from tensorflow.python.framework.type_utils import fulltypes_for_flat_tensors
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.ops import sparse_ops
from tensorflow.python.platform import googletest

class SparseTensorTest(test_util.TensorFlowTestCase):

  def testPythonConstruction(self):
    indices = [[1, 2], [2, 0], [3, 4]]
    values = [b"a", b"b", b"c"]
    shape = [4, 5]
    sp_value = sparse_tensor.SparseTensorValue(indices, values, shape)
    for sp in [
        sparse_tensor.SparseTensor(indices, values, shape),
        sparse_tensor.SparseTensor.from_value(sp_value),
        sparse_tensor.SparseTensor.from_value(
            sparse_tensor.SparseTensor(indices, values, shape))]:
      self.assertEqual(sp.indices.dtype, dtypes.int64)
      self.assertEqual(sp.values.dtype, dtypes.string)
      self.assertEqual(sp.dense_shape.dtype, dtypes.int64)
      self.assertEqual(sp.get_shape(), (4, 5))

      value = self.evaluate(sp)
      self.assertAllEqual(indices, value.indices)
      self.assertAllEqual(values, value.values)
      self.assertAllEqual(shape, value.dense_shape)
      sp_value = self.evaluate(sp)
      self.assertAllEqual(sp_value.indices, value.indices)
      self.assertAllEqual(sp_value.values, value.values)
      self.assertAllEqual(sp_value.dense_shape, value.dense_shape)

  def testShape(self):

    @def_function.function
    def test_fn(tensor):
      tensor = sparse_ops.sparse_transpose(tensor)
      self.assertEqual(tensor.shape.rank, 2)
      return tensor

    tensor = sparse_tensor.SparseTensor(
        indices=[[0, 0], [1, 2]], values=[1., 2], dense_shape=[3, 4])
    test_fn(tensor)

  def testIsSparse(self):
    self.assertFalse(sparse_tensor.is_sparse(3))
    self.assertFalse(sparse_tensor.is_sparse("foo"))
    self.assertFalse(sparse_tensor.is_sparse(np.array(3)))
    self.assertTrue(
        sparse_tensor.is_sparse(sparse_tensor.SparseTensor([[0]], [0], [1])))
    self.assertTrue(
        sparse_tensor.is_sparse(
            sparse_tensor.SparseTensorValue([[0]], [0], [1])))

  def testConsumers(self):
    with context.graph_mode():
      sp = sparse_tensor.SparseTensor([[0, 0], [1, 2]], [1.0, 3.0], [3, 4])
      w = ops.convert_to_tensor(np.ones([4, 1], np.float32))
      out = sparse_ops.sparse_tensor_dense_matmul(sp, w)
      self.assertEqual(len(sp.consumers()), 1)
      self.assertEqual(sp.consumers()[0], out.op)

      dense = sparse_ops.sparse_tensor_to_dense(sp)
      self.assertEqual(len(sp.consumers()), 2)
      self.assertIn(dense.op, sp.consumers())
      self.assertIn(out.op, sp.consumers())

  def testWithValues(self):
    source = sparse_tensor.SparseTensor(
        indices=[[0, 0], [1, 2]], values=[1., 2], dense_shape=[3, 4])
    new_tensor = source.with_values([5.0, 1.0])
    self.assertAllEqual(new_tensor.indices, source.indices)
    self.assertAllEqual(new_tensor.values, [5.0, 1.0])
    self.assertAllEqual(new_tensor.dense_shape, source.dense_shape)

    # ensure new value's shape is checked
    with self.assertRaises((errors.InvalidArgumentError, ValueError)):
      source.with_values([[5.0, 1.0]])

  @test_util.run_in_graph_and_eager_modes
  def testIsEager(self):
    st = sparse_tensor.SparseTensor([[0, 0], [0, 1]], [1.0, 3.0], [2, 2])
    if context.executing_eagerly():
      self.assertTrue(st._is_eager())

  @test_util.run_in_graph_and_eager_modes
  def testNumpy(self):
    st1 = sparse_tensor.SparseTensor([[0, 0], [0, 1]], [1.0, 3.0], [2, 2])
    if not context.executing_eagerly():
      with self.assertRaises(ValueError):
        st1._numpy()
    else:
      self.assertAllEqual(st1._numpy(), [[1.0, 3.0], [0.0, 0.0]])

class ConvertToTensorOrSparseTensorTest(test_util.TensorFlowTestCase):

  def test_convert_dense(self):
    value = [42, 43]
    from_value = sparse_tensor.convert_to_tensor_or_sparse_tensor(
        value)
    self.assertAllEqual(value, self.evaluate(from_value))

  def test_convert_sparse(self):
    indices = [[0, 1], [1, 0]]
    values = [42, 43]
    shape = [2, 2]
    sparse_tensor_value = sparse_tensor.SparseTensorValue(
        indices, values, shape)
    st = sparse_tensor.SparseTensor.from_value(sparse_tensor_value)
    from_value = self.evaluate(
        sparse_tensor.convert_to_tensor_or_sparse_tensor(sparse_tensor_value))
    from_tensor = self.evaluate(
        sparse_tensor.convert_to_tensor_or_sparse_tensor(st))
    for convertee in [from_value, from_tensor]:
      self.assertAllEqual(sparse_tensor_value.indices, convertee.indices)
      self.assertAllEqual(sparse_tensor_value.values, convertee.values)
      self.assertAllEqual(
          sparse_tensor_value.dense_shape, convertee.dense_shape)

class SparseTensorShapeTest(test_util.TensorFlowTestCase):

  def test_simple(self):
    indices = [[0, 2]]
    values = [1]
    dense_shape = [5, 5]
    sp = sparse_tensor.SparseTensor(indices, values, dense_shape)

    self.assertIsInstance(sp.shape, tensor_shape.TensorShape)
    self.assertIsInstance(sp.dense_shape, tensor_lib.Tensor)
    self.assertEqual(sp.shape.as_list(), [5, 5])

  def test_unknown_shape(self):

    @def_function.function
    def my_func(dense_shape):
      indices = [[0, 2]]
      values = [1]
      sp = sparse_tensor.SparseTensor(indices, values, dense_shape)
      self.assertEqual(sp.shape.as_list(), [None, None])
      return sp

    my_func.get_concrete_function(
        dense_shape=tensor_lib.TensorSpec(
            dtype=dtypes.int64, shape=[2,]))

  def test_partial_shape(self):

    @def_function.function
    def my_func(x):
      indices = [[0, 2]]
      values = [1]
      y = ops.convert_to_tensor(3, dtype=dtypes.int64)
      dense_shape = [x, y]
      sp = sparse_tensor.SparseTensor(indices, values, dense_shape)
      self.assertEqual(sp.shape.as_list(), [None, 3])
      return sp

    my_func.get_concrete_function(
        x=tensor_lib.TensorSpec(dtype=dtypes.int64, shape=[]))

  def test_neg_shape(self):
    indices = [[0, 2]]
    values = [1]
    dense_shape = [-1, 5]
    sp = sparse_tensor.SparseTensor(indices, values, dense_shape)
    self.assertEqual(sp.shape.as_list(), [None, 5])

  def test_unknown_tensor_shape(self):

    @def_function.function
    def my_func(x):
      indices = [[0, 0]]
      values = [1]
      dense_shape = array_ops.shape(x)
      dense_shape = math_ops.cast(dense_shape, dtypes.int64)

      sp = sparse_tensor.SparseTensor(indices, values, dense_shape)
      self.assertEqual(sp.shape.as_list(), [None, None])
      return sp

    my_func.get_concrete_function(
        x=tensor_lib.TensorSpec(dtype=dtypes.int64, shape=[None, None]))

  def test_unknown_rank(self):

    @def_function.function
    def my_func(dense_shape):
      indices = [[0, 0]]
      values = [1]
      sp = sparse_tensor.SparseTensor(indices, values, dense_shape)
      self.assertEqual(sp.shape.rank, None)
      return sp

    my_func.get_concrete_function(
        dense_shape=tensor_lib.TensorSpec(dtype=dtypes.int64, shape=[None]))

@test_util.run_all_in_graph_and_eager_modes
class SparseTensorSpecTest(test_util.TensorFlowTestCase,
                           parameterized.TestCase):

  def assertAllTensorsEqual(self, list1, list2):
    self.assertLen(list1, len(list2))
    for (t1, t2) in zip(list1, list2):
      self.assertAllEqual(t1, t2)

  def testConstruction(self):
    spec1 = sparse_tensor.SparseTensorSpec()
    self.assertEqual(spec1.shape.rank, None)
    self.assertEqual(spec1.dtype, dtypes.float32)

    spec2 = sparse_tensor.SparseTensorSpec([None, None], dtypes.string)
    self.assertEqual(spec2.shape.as_list(), [None, None])
    self.assertEqual(spec2.dtype, dtypes.string)

  def testValueType(self):
    spec1 = sparse_tensor.SparseTensorSpec()
    self.assertEqual(spec1.value_type, sparse_tensor.SparseTensor)

  @parameterized.parameters([
      (sparse_tensor.SparseTensorSpec(),
       (tensor_shape.TensorShape(None), dtypes.float32)),
      (sparse_tensor.SparseTensorSpec(shape=[5, None, None]),
       (tensor_shape.TensorShape([5, None, None]), dtypes.float32)),
      (sparse_tensor.SparseTensorSpec(dtype=dtypes.int32),
       (tensor_shape.TensorShape(None), dtypes.int32)),
  ])  # pyformat: disable
  def testSerialize(self, st_spec, expected):
    serialization = st_spec._serialize()
    # TensorShape has an unconventional definition of equality, so we can't use
    # assertEqual directly here.  But repr() is deterministic and lossless for
    # the expected values, so we can use that instead.
    self.assertEqual(repr(serialization), repr(expected))

  @parameterized.parameters([
      (sparse_tensor.SparseTensorSpec(dtype=dtypes.string), [
          tensor_lib.TensorSpec([None, None], dtypes.int64),
          tensor_lib.TensorSpec([None], dtypes.string),
          tensor_lib.TensorSpec([None], dtypes.int64)
      ]),
      (sparse_tensor.SparseTensorSpec(shape=[5, None, None]), [
          tensor_lib.TensorSpec([None, 3], dtypes.int64),
          tensor_lib.TensorSpec([None], dtypes.float32),
          tensor_lib.TensorSpec([3], dtypes.int64)
      ]),
  ])
  def testComponentSpecs(self, st_spec, expected):
    self.assertEqual(st_spec._component_specs, expected)

  @parameterized.parameters([
      {
          "st_spec": sparse_tensor.SparseTensorSpec(),
          "indices": [[0, 1], [10, 8]],
          "values": [3.0, 5.0],
          "dense_shape": [100, 100]
      },
      {
          "st_spec": sparse_tensor.SparseTensorSpec([100, None, None]),
          "indices": [[0, 1, 3], [10, 8, 2]],
          "values": [3.0, 5.0],
          "dense_shape": [100, 20, 20]
      },
  ])
  def testToFromComponents(self, st_spec, indices, values, dense_shape):
    st = sparse_tensor.SparseTensor(indices, values, dense_shape)
    actual_components = st_spec._to_components(st)
    self.assertAllTensorsEqual(actual_components,
                               [indices, values, dense_shape])
    st_reconstructed = st_spec._from_components(actual_components)
    self.assertAllEqual(st.indices, st_reconstructed.indices)
    self.assertAllEqual(st.values, st_reconstructed.values)
    self.assertAllEqual(st.dense_shape, st_reconstructed.dense_shape)

  def testFromComponentsDynamicDenseShapeTensor(self):
    @def_function.function(input_signature=[
        sparse_tensor.SparseTensorSpec([None, 10, 100])])
    def sparse_fun(st):
      self.assertEqual(st.get_shape().as_list(), [None, 10, 100])
      return st.dense_shape

    # Force tracing the TF function.
    _ = sparse_fun.get_concrete_function()

  @test_util.run_v1_only("SparseTensorValue is deprecated in v2")
  def testFromNumpyComponents(self):
    indices = np.array([[0], [8]])
    values = np.array([1.0, 9.0])
    dense_shape = np.array([100])
    spec = sparse_tensor.SparseTensorSpec()
    st = spec._from_components([indices, values, dense_shape])
    self.assertIsInstance(st, sparse_tensor.SparseTensorValue)
    self.assertAllEqual(st.indices, indices)
    self.assertAllEqual(st.values, values)
    self.assertAllEqual(st.dense_shape, dense_shape)

  @parameterized.parameters([
      sparse_tensor.SparseTensorSpec(dtype=dtypes.string),
      sparse_tensor.SparseTensorSpec(shape=[5, None, None]),
  ])
  def testFlatTensorSpecs(self, st_spec):
    self.assertEqual(st_spec._flat_tensor_specs,
                     [tensor_lib.TensorSpec(None, dtypes.variant)])

  @parameterized.parameters([
      dtypes.float32,
      dtypes.string,
  ])
  def testFullTypesForFlatTensors(self, dt):
    st_spec = sparse_tensor.SparseTensorSpec(dtype=dt)
    full_type_list = fulltypes_for_flat_tensors(st_spec)
    expect = [full_type_pb2.FullTypeDef(type_id=full_type_pb2.TFT_UNSET)]
    self.assertEqual(len(st_spec._flat_tensor_specs), len(full_type_list))
    self.assertEqual(expect, full_type_list)

  @parameterized.parameters([
      {
          "st_spec": sparse_tensor.SparseTensorSpec(),
          "indices": [[0, 1], [10, 8]],
          "values": [3.0, 5.0],
          "dense_shape": [100, 100]
      },
      {
          "st_spec": sparse_tensor.SparseTensorSpec([100, None, None]),
          "indices": [[0, 1, 3], [10, 8, 2]],
          "values": [3.0, 5.0],
          "dense_shape": [100, 20, 20]
      },
  ])
  def testToFromTensorList(self, st_spec, indices, values, dense_shape):
    st = sparse_tensor.SparseTensor(indices, values, dense_shape)
    tensor_list = st_spec._to_tensor_list(st)
    st_reconstructed = st_spec._from_tensor_list(tensor_list)
    self.assertAllEqual(st.indices, st_reconstructed.indices)
    self.assertAllEqual(st.values, st_reconstructed.values)
    self.assertAllEqual(st.dense_shape, st_reconstructed.dense_shape)

  @parameterized.parameters([
      (sparse_tensor.SparseTensorSpec([2, None], dtypes.float32), 32,
       sparse_tensor.SparseTensorSpec([32, 2, None], dtypes.float32)),
      (sparse_tensor.SparseTensorSpec([4, None], dtypes.float32), None,
       sparse_tensor.SparseTensorSpec([None, 4, None], dtypes.float32)),
      (sparse_tensor.SparseTensorSpec([2], dtypes.float32), 32,
       sparse_tensor.SparseTensorSpec([32, 2], dtypes.float32)),
  ])
  def testBatch(self, spec, batch_size, expected):
    self.assertEqual(spec._batch(batch_size), expected)

  @parameterized.parameters([
      (sparse_tensor.SparseTensorSpec([32, None, None], dtypes.float32),
       sparse_tensor.SparseTensorSpec([None, None], dtypes.float32)),
      (sparse_tensor.SparseTensorSpec([None, None, None], dtypes.float32),
       sparse_tensor.SparseTensorSpec([None, None], dtypes.float32)),
      (sparse_tensor.SparseTensorSpec([32, 2], dtypes.float32),
       sparse_tensor.SparseTensorSpec([2], dtypes.float32)),
  ])
  def testUnbatch(self, spec, expected):
    self.assertEqual(spec._unbatch(), expected)

if __name__ == "__main__":
  googletest.main()


import os
import xml.etree.ElementTree as ET

class XmlGenerator:
    """Responsible for generating an invoice in xml format"""

    TEMPLATE_XML = """\
    <Invoice SALES_ID="{sales_id}" ORDER_ID ="{order_id}">
        <Status>{status}</Status>
        <Comment>{comment}</Comment>
        <Product>
            <Name>{name}</Name>
            <Price>{price}</Price>
            <Supplier>{supplier}</Supplier>
            <Category>{category}</Category>
            <Description>{description}</Description>
        </Product>
        <Sale>
            <Quantity>{quantity}</Quantity>
            <Value>{value}</Value>
            <Date>{sale_date}</Date>
            <AddressInformation>
                <Country>{country}</Country>
                <State>{state}</State>
                <City>{city}</City>
                <ZipCode>{zip_code}</ZipCode>
                <Street>{street}</Street>
                <Neighborhood>{neighborhood}</Neighborhood>
            </AddressInformation>
        </Sale>
    </Invoice>
    """

    def __init__(self, data: dict, file_name: str):
        self._data = data
        self._file_name = file_name

    def _format_template(self) -> str:
        """Method to format template data"""
        formatted_template = self.TEMPLATE_XML.format(**self._data)

        return formatted_template

    def generate_xml(self) -> None:
        """Method to generate the xml file"""
        formatted_template = self._format_template()
        invoice_directory = "./invoices"

        if not os.path.exists(invoice_directory):
            os.makedirs(invoice_directory)

        root = ET.fromstring(formatted_template)
        tree = ET.ElementTree(root)
        path = f"./invoices/{self._file_name}"
        tree.write(path, encoding="utf-8", xml_declaration=True)


from jax import numpy as np
import jax.numpy as jnp
from jax import jit, device_put
from bench.core.executer import Executer

def bboxes2tblr(priors, gts, normalizer=4.0, normalize_by_wh=True):
    # dist b/t match center and prior's center
    prior_centers = (priors[:, 0:2] + priors[:, 2:4]) / 2
    xmin, ymin, xmax, ymax = jnp.split(gts, 4, axis=1)

    top = jnp.expand_dims(prior_centers[:, 1], 1) - ymin
    bottom = ymax - jnp.expand_dims(prior_centers[:, 1], 1)
    left = jnp.expand_dims(prior_centers[:, 0], 1) - xmin
    right = xmax - jnp.expand_dims(prior_centers[:, 0], 1)
    loc = jnp.concatenate((top, bottom, left, right), axis=1)
    if normalize_by_wh:
        # Normalize tblr by anchor width and height
        wh = priors[:, 2:4] - priors[:, 0:2]
        w, h = jnp.split(wh, 2, axis=1)
        loc[:, :2] /= h  # tb is normalized by h
        loc[:, 2:] /= w  # lr is normalized by w
    # Normalize tblr by the given normalization factor
    return loc / normalizer

def args_adaptor(np_args):
    priors = device_put(np_args[0])
    gts = device_put(np_args[1])
    return [priors, gts]

def executer_creator():
    return Executer(bboxes2tblr, args_adaptor)


"""module: helper_functions
Author: Bill Duncan

This module contains a number of functions to assist with the querying and processing of tabular data.
See `init_graph` for details on functions that get attached to rdflib.Graph.
* `init_graph`: attaches custom functions an rdflib Graph and returns a new graph
* `add_spo`: shortcut for adding subject, predicate, object URIRefs to graph
* `add_spo`: shortcut for adding subject, predicate URIRefs and literal value to graph
* `sparql_results_to_df`: converts sparql results into a dataframe
* `sparql_query_to_df`: queries the graph and returns the results as a dataframe
* `add_table_metadata`: adds instances of tables and fields to graph
* `add_enums`: adds instances of enumerated values to graph
* `df_to_sql`: creates an sql query based on values in a dataframe (with specific columns)
* `add_df_to_graph`: adds a simple rdf transformation of a dataframe into the graph

"""
import pandas as pds
import itertools
from typing import Optional, List, Any
from rdflib import URIRef, BNode, Literal, Graph, Namespace, RDF, RDFS, OWL
from rdflib.plugins.sparql.processor import SPARQLResult

def init_graph(ontology:str =None, format='ttl') -> Graph:
    """attaches custom functions an rdflib Graph and returns a new graph"""

    # attach helper functions
    Graph.add_spo = add_spo
    Graph.add_spv = add_spv
    Graph.sparql_query_to_df = sparql_query_to_df
    Graph.add_table_metadata = add_table_metadata
    Graph.add_enums = add_enums
    Graph.add_df = add_df

    # intstantiate Graph
    graph = Graph()

    # parse ontology
    if ontology:
        return graph.parse(ontology, format=format)
    else:
        return graph

def add_spo(self: Graph, subj: Any, predicate: Any, obj: Any) -> Graph:
    """shortcut for adding subject, predicate, object URIRefs to graph"""

    self.add((URIRef(subj), URIRef(predicate), URIRef(obj)))
    return self

def add_spv(self: Graph, subj: Any, predicate: Any, val: Any) -> Graph:
    """shortcut for adding subject, predicate URIRefs and literal value to graph"""

    self.add((URIRef(subj), URIRef(predicate), Literal(val)))
    return self

def sparql_results_to_df(results: SPARQLResult, graph=Optional[Graph]) -> pds.DataFrame:
    """converts sparql results into a dataframe"""

    def set_value(x):
        if x is None:
            return None
        elif graph is not None:
            for n in graph.namespaces():
                # each is a tuple of form (<prefix>, URIRef(...))
                # e.g., ('dc', rdflib.term.URIRef('http://purl.org/dc/elements/1.1/'))
                if str(x).startswith(str(n[1])):
                    # replace uri with prefix
                    return str(x).replace(n[1], n[0])

            # if it makes it here, no replacements occurred
            return x.toPython()
        else:
            return x.toPython()

    return \
        pds.DataFrame(
            data=([set_value(x) for x in row] for row in results),
            columns=[str(x) for x in results.vars]
        )

def sparql_query_to_df(self: Graph, query: str, use_ns=True) -> pds.DataFrame:
    """queries the graph and returns the results as a dataframe"""

    results = self.query(query)
    if use_ns:
        return sparql_results_to_df(results, self)
    else:
        return sparql_results_to_df(results, None)

def add_table_metadata(self: Graph,
                        table: pds.DataFrame,
                        table_name: str,
                        table_ns: Namespace,
                        field_ns: Namespace,
                        property_ns: Namespace) -> Graph:
    """adds instances of tables and fields to graph"""

    # add table instance to graph
    table_uri = table_ns[f'/{table_name}']
    self.add_spo(table_uri, RDF.type, table_ns)
    self.add_spv(table_uri, RDFS.label, table_name)

    # add each of the tables fields to graph as instances and subclass of fields
    for field_name in table.columns:
        field_name = f'{table_name}.{field_name}' # prepend table name to field name
        uri = URIRef(field_ns[f'/{field_name}'])
        self.add_spo(uri, RDF.type, field_ns)
        self.add_spo(uri, property_ns.member_of, table_uri)
        self.add_spv(uri, RDFS.label, field_name)

        # *pun* the field as an owl class
        # note: field_ns[:-1] removes the last "/" from the uri
        self.add_spo(uri, RDF.type, OWL.Class)
        self.add_spo(uri, RDFS.subClassOf, field_ns[:-1])

    return self

def add_enums(self: Graph,
             enums: List,
             table_name: str,
             field_name: str,
             enum_ns: Namespace,
             base_ns: Namespace) -> Graph:
    """adds instances of enumerated values to graph"""

    field_name = f'{table_name}.{field_name}' # prepend table name to field name
    for enum in enums:
        # build uri
        uri = URIRef(enum_ns[f'/{field_name}#{enum}'])

        # add instances
        # Note: the literal value is added to the graph as well
        self.add_spo(uri, RDF.type, enum_ns)
        self.add_spv(uri, base_ns.has_value, enum)
        self.add_spv(uri, RDFS.label, f'{field_name} {enum}')

        # enums constrain values in fields, so add this informaton the graph
        field = base_ns[f'field/{field_name}']
        self.add_spo(uri, base_ns.defines_values_in, field)

    return self

def df_to_sql(df: pds.DataFrame) -> str:
    """creates an sql query based on values in a dataframe (with specific columns)"""
    # N.B.: This need to be refactored!!!

    # dict to hold info about the table and class the field represents
    # note: check for enum_value column
    if 'enum_value' in df.columns:
        columns = ['field_name', 'table_name', 'cls_names', 'enum_value']
    else:
        columns = ['field_name', 'table_name', 'cls_names']

    field_info_dict = \
        df[columns].drop_duplicates().set_index('field_name').to_dict(orient='index')

    # gather all table and field names in dataframe
    all_tables = set(df['table_name'])
    all_fields = set(df['field_name'])

    # dict to hold whether the table occurs in a 'from' clause or 'join' clause
    table_clause_dict = {'from': '', 'joins': []}
    for i, tbl in enumerate(all_tables):
        if i == 0:
            table_clause_dict['from'] = tbl
        else:
            table_clause_dict['joins'].append(tbl)

    # dict to hold just those tables that occur in 'join' clause
    # this dict just help build the sql query
    table_join_dict = \
        {tbl:{'joins': []} for tbl in all_tables if tbl != table_clause_dict['from']}

    # iterate over each class in the dataframe
    # b/c each field is part of the same group we know the reprenst the same type of thing
    # so, we can relate the fields in the join clause
    for group_name, group_df in df.groupby('cls_names'):
        # all fields in the same group represent the same type of thing
        # find the pairwise combinations of how each field relates to one another
        field_pairs = \
            list(itertools.combinations(group_df['field_name'], 2))

        # now for each pair add it to the appropriate join table
        for left_field, right_field in field_pairs:
            # fetch table names of fields
            left_table = field_info_dict[left_field]['table_name']
            right_table = field_info_dict[right_field]['table_name']

            # add pair as a join condition (e.g. patients.patient_id = procedures.patient_id)
            if left_table != table_clause_dict['from']:
                table_join_dict[left_table]['joins'].append((left_field, right_field))
            elif right_table != table_clause_dict['from']:
                table_join_dict[right_table]['joins'].append((left_field, right_field))

    # finally, build sql query
    sql = ""

    # SELECT ...
    # form the select list for the sql
    select_fields = \
        [f"  {field} as [{field} ({value['cls_names']})]\n"
        for field, value in field_info_dict.items()]

    select_fields = []
    for field, value in field_info_dict.items():
        # the "as [{field}" is needed to guarantee that the whole
        # field name (i.e., <table>.<field name>) is used in the results
        if 'cls_names' in value.keys():
            select_fields.append(f"  {field} as [{field} ({value['cls_names']})] \n")
        else:
            select_fields.append(f"  {field} as [{field}]\n)")

    sql = sql + f"select \n{'  ,'.join(select_fields)} \n"

    # FROM ... JOIN ...
    # add tables that data is retrieved from (note: there must be a 'from' clause)
    if len(table_clause_dict['from']) > 0:
        sql = sql + f"from {table_clause_dict['from']} \n"

        #  collect joined tables
        for tbl in table_clause_dict['joins']:
            join_fields = table_join_dict[tbl]['joins']
            if len(join_fields) > 0:
                sql = sql + f"inner join {tbl} on \n"

                # put an '=' between each pair of fields
                field_pairs = [f"{' = '.join(field_pair)}\n" for field_pair in join_fields]
                sql = sql + f"  {'  and '.join(field_pairs)}"

    # WHERE ...
    # check if enum value used for filter
    if 'enum_value' in df.columns:
        #
        print_where = True
        for idx, field_name, enum_value in df[['field_name', 'enum_value']].itertuples():
            if print_where and enum_value is not None and len(enum_value) > 0:
                sql = sql + f"where {field_name} = '{enum_value}' \n"
                print_where = False
            elif enum_value is not None and len(enum_value) > 0:
                sql = sql + f"and {field_name} = '{enum_value}' \n"

    # return sql query
    return sql

def add_df(self: Graph, df: pds.DataFrame, table_name, field_ns: Namespace, base_ns: Namespace) -> Graph:
    """adds a simple rdf transformation of a dataframe into the graph"""

    # add table to graph
    table_uri = base_ns[f'table/{table_name}']
    self.add_spo(table_name, RDF.type, base_ns.table)

    for row in df.itertuples(index=None):
        # add row as blank node to table
        row_uri = BNode()
        self.add_spo(row_uri, RDF.type, base_ns.row)
        self.add_spo(row_uri, base_ns.member_of, table_uri)

        # for each field in row
        for field_name, value in row._asdict().items():
            # add instance of field value using blank node
            field_value_uri = BNode()
            self.add_spo(field_value_uri, RDF.type, base_ns.field_value)
            self.add_spv(field_value_uri, base_ns.has_value, value)

            # relate row to field value using *punned* field name
            field_uri = field_ns[f'/{table_name}.{field_name}']
            self.add_spo(field_uri, RDF.type, OWL.ObjectProperty)
            self.add_spo(row_uri, field_uri, field_value_uri)

            # relate field value to field and field to row via 'member of'
            field_instance_uri = BNode()
            self.add_spo(field_instance_uri, RDF.type, field_uri)
            self.add_spo(field_instance_uri, base_ns.member_of, row_uri)
            self.add_spo(field_value_uri, base_ns.member_of, field_instance_uri)

    return self


from django.db import models
from django.utils.encoding import python_2_unicode_compatible

@python_2_unicode_compatible
class SupportedResourceType(models.Model):
    resource_name           = models.CharField(max_length=256, unique=True,
                                    db_index=True)
    json_schema             = models.TextField(max_length=5120, default="{}",
                                                help_text="{} indicates no schema.")
    get                = models.BooleanField(default=False, verbose_name="get",
                                                    help_text="FHIR Interaction Type")
    put                = models.BooleanField(default=False, verbose_name="put",
                                                  help_text="FHIR Interaction Type")
    create             = models.BooleanField(default=False, verbose_name="create",
                                                  help_text="FHIR Interaction Type")
    read               = models.BooleanField(default=False, verbose_name="read",
                                                  help_text="FHIR Interaction Type")
    vread              = models.BooleanField(default=False, verbose_name="vread",
                                                  help_text="FHIR Interaction Type")
    update             = models.BooleanField(default=False, verbose_name="update",
                                                  help_text="FHIR Interaction Type")
    delete             = models.BooleanField(default=False, verbose_name="delete",
                                                  help_text="FHIR Interaction Type")
    search             = models.BooleanField(default=False, verbose_name="search",
                                                  help_text="FHIR Interaction Type")
    history            = models.BooleanField(default=False, verbose_name="_history",
                                                  help_text="FHIR Interaction Type")

    # Python2 uses __unicode__(self):

    def __str__(self):
        return self.resource_name

    def get_supported_interaction_types(self):
        sit = []
        if self.get:
            sit.append(self._meta.get_field("get").verbose_name)
        if self.put:
            sit.append(self._meta.get_field("put").verbose_name)
        if self.create:
            sit.append(self._meta.get_field("create").verbose_name)
        if self.read:
            sit.append(self._meta.get_field("read").verbose_name)
        if self.vread:
            sit.append(self._meta.get_field("vread").verbose_name)
        if self.update:
            sit.append(self._meta.get_field("update").verbose_name)
        if self.delete:
            sit.append(self._meta.get_field("delete").verbose_name)
        if self.search:
            sit.append(self._meta.get_field("search").verbose_name)
        if self.history:
            sit.append(self._meta.get_field("history").verbose_name)
        return sit

    def access_denied(self, access_to_check):
        return True
        # if access_to_check.lower() == "fhir_get":
        #     return not self.fhir_get
        # elif access_to_check.lower() == "fhir_put":
        #     return not self.fhir_put
        # elif access_to_check.lower() == "fhir_create":
        #     return not self.fhir_create
        # elif access_to_check.lower() == "fhir_read":
        #     return not self.fhir_read
        # elif access_to_check.lower() == "fhir_update":
        #     return not self.fhir_update
        # elif access_to_check.lower() == "fhir_delete":
        #     return not self.fhir_delete
        # elif access_to_check.lower() == "fhir_search":
        #     return not self.fhir_search
        # elif access_to_check.lower() == "fhir_history":
        #     return not self.fhir_history
        # else:
        #     return True

    def access_permitted(self, access_to_check):
        return True
        # if access_to_check.lower() == "fhir_get":
        #     return self.fhir_get
        # elif access_to_check.lower() == "fhir_put":
        #     return self.fhir_put
        # elif access_to_check.lower() == "fhir_create":
        #     return self.fhir_create
        # elif access_to_check.lower() == "fhir_read":
        #     return self.fhir_read
        # elif access_to_check.lower() == "fhir_update":
        #     return self.fhir_update
        # elif access_to_check.lower() == "fhir_delete":
        #     return self.fhir_delete
        # elif access_to_check.lower() == "fhir_search":
        #     return self.fhir_search
        # elif access_to_check.lower() == "fhir_history":
        #     return self.fhir_history
        # else:
        #     return False


# coding: utf-8
"""
api_utils.auth
~~~~~~~~~~~~~~

This module provides API authentication based on Hawk scheme and
Flask-Login extension.

"""
from functools import wraps

from flask import request, session, current_app
from werkzeug.exceptions import BadRequest, Unauthorized
try:
    import mohawk
    from flask.ext.login import current_user
except ImportError:
    pass

from . import compat

__all__ = ('Hawk',)

class Hawk(object):
    """HTTP authentication scheme using a message authentication code
    (MAC) algorithm.

    Instances are *not* bound to specific apps.

    """
    def __init__(self, app=None):
        self._client_key_loader_func = None

        if app is not None:
            self.init_app(app)

    def init_app(self, app):
        app.config.setdefault('HAWK_ENABLED', True)
        app.config.setdefault('HAWK_SIGN_RESPONSE', False)
        app.config.setdefault('HAWK_ALLOW_COOKIE_AUTH', False)
        app.config.setdefault('HAWK_ALGORITHM', 'sha256')
        app.config.setdefault('HAWK_ACCEPT_UNTRUSTED_CONTENT', False)
        app.config.setdefault('HAWK_LOCALTIME_OFFSET_IN_SECONDS', 0)
        app.config.setdefault('HAWK_TIMESTAMP_SKEW_IN_SECONDS', 60)

        if app.config['HAWK_SIGN_RESPONSE']:
            app.after_request(self._sign_response)

    def client_key_loader(self, f):
        """Registers a function to be called to find a client key.

        Function you set has to take a client id and return a client key::

            @hawk.client_key_loader
            def get_client_key(client_id):
                if client_id == 'Alice':
                    return 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn'
                else:
                    raise LookupError()

        :param f: The callback for retrieving a client key.

        """
        @wraps(f)
        def wrapped_f(client_id):
            client_key = f(client_id)
            return {
                'id': client_id,
                'key': client_key,
                'algorithm': current_app.config['HAWK_ALGORITHM']
            }

        self._client_key_loader_func = wrapped_f
        return wrapped_f

    def auth_required(self, view_func):
        """Decorator that provides an access to view function for
        authenticated users only.

        Note that we don't run authentication when `HAWK_ENABLED` is `False`.

        """
        @wraps(view_func)
        def wrapped_view_func(*args, **kwargs):
            if current_app.config['HAWK_ENABLED']:
                if current_app.config['HAWK_ALLOW_COOKIE_AUTH'] and session:
                    self._auth_by_cookie()
                else:
                    self._auth_by_signature()
            return view_func(*args, **kwargs)

        return wrapped_view_func

    def _auth_by_cookie(self):
        if not compat.is_user_authenticated(current_user):
            raise Unauthorized()

    def _auth_by_signature(self):
        if self._client_key_loader_func is None:
            raise RuntimeError('Client key loader function was not defined')
        if 'Authorization' not in request.headers:
            raise Unauthorized()

        try:
            mohawk.Receiver(
                credentials_map=self._client_key_loader_func,
                request_header=request.headers['Authorization'],
                url=request.url,
                method=request.method,
                content=request.get_data(),
                content_type=request.mimetype,
                accept_untrusted_content=current_app.config['HAWK_ACCEPT_UNTRUSTED_CONTENT'],
                localtime_offset_in_seconds=current_app.config['HAWK_LOCALTIME_OFFSET_IN_SECONDS'],
                timestamp_skew_in_seconds=current_app.config['HAWK_TIMESTAMP_SKEW_IN_SECONDS']
            )
        except mohawk.exc.MacMismatch:
            # mohawk exception contains computed MAC.
            # We should not expose it in response.
            raise Unauthorized()
        except (
            mohawk.exc.CredentialsLookupError,
            mohawk.exc.AlreadyProcessed,
            mohawk.exc.MisComputedContentHash,
            mohawk.exc.TokenExpired
        ) as e:
            raise Unauthorized(str(e))
        except mohawk.exc.HawkFail as e:
            raise BadRequest(str(e))
        except KeyError:
            raise BadRequest()

    def _sign_response(self, response):
        """Signs a response if it's possible."""
        if 'Authorization' not in request.headers:
            return response

        try:
            mohawk_receiver = mohawk.Receiver(
                credentials_map=self._client_key_loader_func,
                request_header=request.headers['Authorization'],
                url=request.url,
                method=request.method,
                content=request.get_data(),
                content_type=request.mimetype,
                accept_untrusted_content=current_app.config['HAWK_ACCEPT_UNTRUSTED_CONTENT'],
                localtime_offset_in_seconds=current_app.config['HAWK_LOCALTIME_OFFSET_IN_SECONDS'],
                timestamp_skew_in_seconds=current_app.config['HAWK_TIMESTAMP_SKEW_IN_SECONDS']
            )
        except mohawk.exc.HawkFail:
            return response

        response.headers['Server-Authorization'] = mohawk_receiver.respond(
            content=response.data,
            content_type=response.mimetype
        )
        return response


import maya.cmds as cmds
import pymel.core as pm
import pymel.core.datatypes as dt
from tqdm import tqdm, trange

import attachedLocalSpace as als

import importlib
importlib.reload(als)

def connectMeshToTree(meshNode, treeNode, index):
    meshNode.outMesh.connect(treeNode.inMeshes[index].inMesh)
    meshNode.parent(0).worldMatrix.connect(treeNode.inMeshes[index].inWorldMatrix)

def connectMeshToDeform(treeNode,chainId,meshId,subIds):
    deformIdx = treeNode.deformedMeshes.get(s=True)
    curDeformMesh = treeNode.deformedMeshes[deformIdx]
    curDeformMesh.deformedChainId.set(chainId)
    curDeformMesh.deformedMeshId.set(meshId)
    curDeformMesh.deformedPointIds.set(subIds)

def connectRootFrame(rootJoint, treeNode, idx, rootMatrix):
    rootFrameAttr = treeNode.chains[idx].rootFrame
    rootP = dt.Point(rootJoint.getTranslation("world"))
    locP = rootP * rootMatrix
    rootFrameAttr.offset.set([locP.x, locP.y, locP.z])
    q = rootJoint.getRotation(quaternion=True) * rootJoint.getOrientation()
    q *= dt.Quaternion(rootMatrix)
    rootFrameAttr.rootQuat.set([q.x, q.y, q.z, q.w])

def connectFrame(joint, treeNode, idx, idx2):
    frame = treeNode.chains[idx].frames[idx2]
    frame.xOffset.set(joint.tx.get())
    q = joint.getRotation(quaternion=True) * joint.getOrientation()
    frame.frameQuat.set([q.x, q.y, q.z, q.w])

def connectJointHierarchyToTree(joint, treeNode, chainId, idx2):
    connectFrame(joint, treeNode, chainId, idx2)
    for subJoint in joint.getChildren():
        connectJointHierarchyToTree(subJoint, treeNode, chainId, idx2+1)

def inputChain(treeNode, rootJoint, chainId, rootMatrix):
    # input joints
    rootJointNode = pm.PyNode(rootJoint)
    rootMatrix = rootMatrix.inverse()
    connectRootFrame(rootJointNode, treeNode, chainId, rootMatrix)

    for subJoint in rootJointNode.getChildren():
        connectJointHierarchyToTree(subJoint, treeNode, chainId, 0)

def parentChainFrame(parentJoint, rootP):
    jointNodes = pm.ls(parentJoint,dag=True,type="joint")
    nearestDist = 1000000
    curDist = 0
    nearestId = 0
    idx = 0
    nearestJointNode = None
    for joint in jointNodes:
        p = pm.joint(joint,q=True,p=True)
        curDist = (p - rootP).length()
        if nearestDist > curDist:
            nearestDist = curDist
            nearestId = idx
            nearestJointNode = joint

        idx += 1

    return nearestJointNode, nearestId

def setTreeInputForDeformedMesh(treeNode, meshId, subPIds, rootJoint, chainId, parentJoint=None):
    rootMatrix = dt.Matrix()
    rootMatrix.setToIdentity()
    # input rootMeshes
    if parentJoint:
        rootP = cmds.xform(rootJoint, q=True, ws=True, t=True)
        rootP = dt.Point(rootP)
        parentFrameJoint, rootChainFrameId = parentChainFrame(parentJoint, rootP)
        treeNode.chains[chainId].trunkFrameId.set(rootChainFrameId)
        rootMatrix = pm.xform(parentFrameJoint, q=True, ws=True, m=True)
        rootMatrix = dt.Matrix(rootMatrix)

    # input chain
    inputChain(treeNode, rootJoint, chainId, rootMatrix)

    connectMeshToDeform(treeNode, chainId, meshId, subPIds)

def inputFoliages(treeNode, foliageData, rootMesh, meshId, rootMeshId):
    foliageNum = len(foliageData["pnt"])
    vIdList = [0] * foliageNum * 3
    uvList = [0] * foliageNum * 2
    axisList = [0] * foliageNum * 3
    radianList = [0] * foliageNum
    scaleList = [1] * foliageNum

    itersector = als.createMeshIntersector(rootMesh)

    print("Foliage process is beginning")

    # pbar = tqdm(range(foliageNum))
    for i in range(foliageNum):
        # pbar.set_description('Processing {}'.format(i))

        if i % 10000 == 1:
            print("Processing {:.2f}%".format(i/foliageNum*100))

        rootP = foliageData["pnt"][i]
        rootP = dt.Point(rootP)
        vIds, u, v = als.closestPolygonAndWeights(rootP, rootMesh, itersector)

        vId3 = i*3
        vIdList[vId3  ]=vIds[0]
        vIdList[vId3+1]=vIds[1]
        vIdList[vId3+2]=vIds[2]

        uvId2 = i*2
        uvList[uvId2  ]=u
        uvList[uvId2+1]=v

        qId3 = i*3
        axis = foliageData["rotAxis"][i]

        axisList[qId3  ] = axis[0]
        axisList[qId3+1] = axis[1]
        axisList[qId3+2] = axis[2]

        radianList[i] = foliageData["rotRadian"][i]

        scaleList[i] = foliageData["scale"][i]

    print("Foliage process is done")

    attachedPointAttr = treeNode.foliages
    attachedPointAttr.foliageMeshId.set(meshId)
    attachedPointAttr.foliageAttachedMeshId.set(rootMeshId)
    attachedPointAttr.foliageAttachedPointId.set(vIdList)
    attachedPointAttr.foliageAttachedWeight.set(uvList)
    attachedPointAttr.foliageScale.set(scaleList)
    attachedPointAttr.foliageRotAxis.set(axisList)
    attachedPointAttr.foliageRotRadian.set(radianList)

def skelTreeCreator(meshDataDict):
    if not cmds.pluginInfo("skelTree",q=True,l=True):
        cmds.loadPlugin("skelTree")

    treeNode = pm.PyNode(cmds.createNode('skelTreeCreator'))
    visNode = pm.PyNode(cmds.createNode('skelTreeVisualization'))

    visNode.noiseTrunkValueFreqSpeed.set([0.2,.1,0.05,0.1])
    visNode.noiseBranchValueFreqSpeed.set([0.2,.2,1])
    visNode.noiseLeafValueFreqSpeed.set([1,.2,1,2])
    visNode.windDirection.set([1,0,0])
    visNode.dispChainScale.set(0.1)

    visNode.dispEnableDeformedPoints.set(False)
    visNode.dispEnableDeformedMeshes.set(False)
    visNode.dispEnableChainBoxes.set(False)
    visNode.dispEnableChainLine.set(True)
    visNode.dispEnableAttachedPoint.set(False)
    visNode.dispEnableFoliages.set(False)

    cmds.expression(s="{}.time=frame*0.01;".format(visNode.name()),o=visNode.name(), ae=1, uc="all")

    # input meshes
    meshList = meshDataDict["mesh"]
    idx = 0
    for mesh in meshList:
        meshNode = pm.PyNode(mesh)
        connectMeshToTree(meshNode, treeNode, idx)
        idx += 1

    deformDataList = meshDataDict["deform"]

    # input deformed meshes and chains
    for meshData in deformDataList:
        if meshData[3]:
            rootMeshId = meshList.index(meshData[3])
            setTreeInputForDeformedMesh(treeNode, meshList.index(meshData[0]), meshData[1], meshData[2], meshDataDict["chain"].index(meshData[2]), meshDataDict["chain"][0])
        else:
            setTreeInputForDeformedMesh(treeNode, meshList.index(meshData[0]), meshData[1], meshData[2], meshDataDict["chain"].index(meshData[2]))

    treeNode.outSkelTreeData.connect(visNode.inSkelTreeData)

    if meshDataDict["foliage"]:
        # input foliages
        rootMeshId = 0
        foliageMeshId = 1
        rootMesh = meshList[rootMeshId]
        inputFoliages(treeNode, meshDataDict["foliage"], rootMesh, foliageMeshId, rootMeshId)

# skelTreeCreator({
#     "mesh":["pPlaneShape1", "pCylinderShape1", "pCylinderShape2","pCylinderShape3"],
#     "chain":["joint1","joint10","joint16"],
#     "deform":[
#         ["pCylinderShape1", "joint1", "pPlaneShape1"],
#         ["pCylinderShape2", "joint10", "pCylinderShape1"],
#         ["pCylinderShape3", "joint16", "pCylinderShape1"]
#     ]
# })

# skelTreeCreator({
#     "mesh":["pCylinderShape1"],
#     "chain":["joint1"],
#     "deform":[["pCylinderShape1","joint1",None]]
# })


import tkinter as tk
import random
from datetime import *
random_metin = ["ben klavye hız testi yapıyorum", "klavye parmak hızımı öğrenmeye çalışıyorum","kalvyede hızlı olup olmadığımı öğrenmeye çalışıyorum", "on parmak hızımı bulmaya çalışıyorum","hızlı bir şekilde yazıp yazamadığımı öğreniyorum", "klavyede ne kadar hızlıyım bilmek istiyorum","bu test bana ne kadar hızlı yazdığımı gösterecek","verilen paragrafı ne kadar hızlı yazabilirim"]
metin =(random.choice(random_metin))
before = datetime.now()

# Create the main window
window = tk.Tk()
window.title("Klavye Hız Testi")

# Create a label to display the instructions
instructions_label = tk.Label(text="Aşağıdaki metni olabildiğince hızlı yazın:")
instructions_label.pack()

# Display the random text
random_text_label = tk.Label(text=metin)
random_text_label.pack()

# Create an input field for the user to type in
input_field = tk.Entry()
input_field.pack()

# Create a function to check the user's input
def check_input():
  user_input = input_field.get()
  if user_input == metin:
    # Calculate the speed
    after = datetime.now()
    speed = after - before
    seconds = speed.total_seconds()
    letter_per_second = round(len(metin) / seconds, 1)

    # Display the results
    result_label = tk.Label(text="Tebrikler! {} karakterlik metni {} saniyede yazdınız ({} saniyedeki harf sayısı)".format(len(metin), seconds, letter_per_second))
    result_label.pack()
  else:
    result_label = tk.Label(text="Yanlış! Lütfen tekrar deneyin.")
    result_label.pack()

# Create a button to check the user's input
check_button = tk.Button(text="Kontrol Et", command=check_input)
check_button.pack()

# Run the main loop
window.mainloop()