Ejemplo n.º 1
0
    def _calculate_matrix(self):

        self.matrix_2_values = self._matrix_2_values_default() # clear matrix data
        my_reset_values=self.return_reset_values() # store the values to reset them at the end
        try:
            startTime             = timing()
            self.state            = 'calculating matrix data'
            self.x_data           = self.generate_frequency()
            myfield               = np.arange(self.begin, self.end, self.delta)
            self.matrix_dimension = len(myfield) # adjust matrix lines here
    
    
    
    
            for i,value in enumerate(myfield):
                self.state = 'calculating'
                if i==0:
                    self.old_data = [self.return_hstacked(self.return_spec_data())]#save all old calculated data here
                if self.bfield_bool  :
                  self.bfield         = value
                  self.matrix_2_label = 'B-Field[Gauss]'
                if self.temp1_bool   :
                  self.temp1          = value
                  self.matrix_2_label = 'Temperature 1 ['+self.degree+'C]'
                if self.temp2_bool   :
                  self.temp2          = value
                  self.matrix_2_label = 'Temperature 2 ['+self.degree+'C]'
                if self.length_bool  :
                  self.length         = value
                  self.matrix_2_label = 'Cell Length [mm]'
                if self.inputpol_bool:
                  self.inputpol       = value
                  self.matrix_2_label = 'Input polarization ['+self.degree+']'
                if self.detpol_bool  :
                  self.detpol         = value
                  self.matrix_2_label = 'Output polarization [%]'
                self.new_data = [self.return_hstacked(self.return_spec_data())]
                self.old_data = np.vstack((self.new_data,self.old_data))
                self.matrix_2_values  = np.vstack( (self.y_data, self.matrix_2_values[:-1,:]) )
    
            cutted_data = self.old_data[:-1,:,:]#one entry has to be removed
            self.matrix_dictionary = self.create_2D_spectra_dict(cutted_data)#for plotting
            self.matrix_dictionary2= self.create_2D_spectra_dict(cutted_data[::-1,:,:])#reverse it for saving
    
            self.state                = 'done'
            self.last_calc_time       = 1000*(timing()-startTime)
            file_prefix               = self.return_file_prefix()
            self.filename             = file_prefix+str(self.element).lower()+'_'+str(self.line).lower()+'_'+str(self.spectype).lower()+'_'+str(self.bfield)+'_'+str(self.temp1)+'_matrix.txt'
            self.matrix_image_filename= file_prefix+str(self.element).lower()+'_'+str(self.line).lower()+'_'+str(self.spectype).lower()+'_'+self.matrix_2_label+'_'+str(self.begin)+'_'+str(self.end)
            self.matrix_data_filename = file_prefix+str(self.element).lower()+'_'+str(self.line).lower()+'_'+self.matrix_2_label+'_'+str(self.begin)+'_'+str(self.end)
        except:
            self.state = 'error'
            print 'uups....something went wrong in _calculate_matrix()'
            self.reset_values(my_reset_values) # reset iterated values to start value
        finally:
            self.state = 'done'
            self.reset_values(my_reset_values)
Ejemplo n.º 2
0
def test1():
    ### 1. Fig 3 of Generalised treatment ... Rotondaro JOSAB 2015 paper
    ### Normal Faraday spectrum
    import os
    import time
    if hasattr(time, 'process_time'):
        from time import process_time as timing  # Python 3.3
    elif os.name == 'posix':
        from time import time as timing  #Timing for linux or apple
    else:
        from time import clock as timing  #Timing for windows
    d = np.arange(-10000, 10000, 10)
    #Voigt
    p_dict = {
        'Bfield': 300,
        'rb85frac': 1,
        'Btheta': 0,
        'lcell': 75e-3,
        'T': 58,
        'Dline': 'D2',
        'Elem': 'Cs'
    }

    #timing:
    st = timing()
    TF = get_spectra2(d, [1, 0, 0], p_dict, outputs=['Iy'])
    et = timing() - st
    print(('E-field - Elapsed time (s):', et))

    #check vs old elecsus
    from elecsus.libs import spectra as old_spec

    st = timing()
    TF_old = old_spec.get_spectra(d, p_dict, outputs=['Iy'])
    et = timing() - st
    print(('Old elecsus - Elapsed time (s):', et))

    index = 0  # Iy

    fig = plt.figure("Faraday comparison")
    ax1 = fig.add_subplot(111)
    ax1.plot(d, TF[index], 'r', lw=2, label='Faraday')
    ax1.plot(d, TF_old[0], 'k--', lw=2, label='Vanilla ElecSus')

    ax1.legend(loc=0)

    ax1.set_xlabel('Detuning (MHz)')
    ax1.set_ylabel('Transmission')

    plt.show()
Ejemplo n.º 3
0
    def _calculate(self):


        try:
            startTime           = timing()
            self.state          = 'calculating'
            self.x_data         = self.generate_frequency()
            self.y_data         = self.return_spectrum(self.x_data)
            self.state          = 'done'
            self.last_calc_time = 1000*(timing()-startTime)
            file_prefix         = self.return_file_prefix()            
        except:
            self.state = 'error'
        finally:
            self.state = 'done'
Ejemplo n.º 4
0
    def _calculate_all(self):
        """calculates all spetra and sets their values"""

        try:
            startTime = timing()
            self.state='calculating'
            self.x_data                        = self.generate_frequency()
            S0,S1,S2,S3,Ix,Iy,RIp,RIm,GIm,GIp,Tm,Tp,Rot  = self.return_spectrum_all(self.x_data)
            self.S0_spec,self.S1_spec,self.S2_spec,self.S3_spec,self.Ix_spec,self.Iy_spec,self.RIp_spec,self.RIm_spec,self.GIm_spec,self.GIp_spec,self.Tm_spec,self.Tp_spec,self.Rot_spec  = S0,S1,S2,S3,Ix,Iy,RIp,RIm,GIm,GIp,Tm,Tp,Rot
            self.set_y_data() # thats for the matrix plot data setting
            self.State                         = 'done'
            self.last_calc_time                = 1000*(timing()-startTime)
            self.estimate_calc_time()
            file_prefix                        = self.return_file_prefix()
            self.filename                      = file_prefix+str(self.element).lower()+'_'+str(self.line).lower()+'_'+str(self.bfield)+'_'+str(self.temp1)+'.fits'
        except:
            self.state = 'error'
            print 'some error occured in calculation, check: _calculate_all()'
        finally:

            self.state = 'done'
Ejemplo n.º 5
0
def fit_data(data,
             parameters,
             paramBoolList,
             experimental_datatype='S0',
             fit_algorithm='ML',
             **kw):
    """ 
	Blurb about this:
	
	data is a 2-element list containing x and y data 1-d arrays.
	parameters (and parameter order) is same as for the calculate() method.
	paramBoolList is ...
	experimental_datatype is ...
	fit_algorith is ...
	keywords are
	"""

    ## alter the parameter order. again.
    ## Really need to rewrite the fitting modules for a more sensible parameter order
    ## but it's a pain and doesn't add any functionality, so
    ## it is therefore on the 'to do' list!

    ## Order as given in the 'parameters' argument
    ## Element, Dline, B, T, L, Rb85, DoppT, Theta0, Pol, shift,
    ## GammaBuf, Constrain, K40, K41

    ## Order required by Fitting routines:
    ## Element, OutputType, B, T, L, Rb85%, DoppT, Theta0, Pol, Shift,
    ## GammaBuf, Constrain, Dline, Precision, K40%, K41%

    parameters_new = [None] * 16
    parameters_new[0] = parameters[0]
    parameters_new[1] = experimental_datatype
    parameters_new[2:12] = parameters[2:12]
    parameters_new[12] = parameters[1]
    parameters_new[13] = 10
    parameters_new[14:] = parameters[12:]

    #print parameters_new

    startTime = timing()
    xdata, ydata = data
    #print xdata, ydata
    #print type(xdata), type(ydata)

    # Call different fitting routines
    if fit_algorithm == 'Marquardt-Levenberg':
        print '\nPerfoming Marquardt-Levenberg fitting routine.'
        optParams, Spec = ML.MLfit(xdata, ydata, parameters_new, paramBoolList,
                                   **kw)
    elif fit_algorithm == 'Simulated Annealing':
        print '\nPerforming fitting by simulated annealing.'
        optParams, Spec = SA.SAFit(xdata, ydata, parameters_new, paramBoolList,
                                   **kw)
    else:
        print '\nPerforming fitting by Random-Restart hill climbing method.'
        #The more parameters to fit, the more evaluations we need to do.
        factor = sum(paramBoolList)
        evaluationNumber = factor**2 + 5  #integer
        optParams, Spec = RR.RRFit(xdata, ydata, parameters_new, paramBoolList,
                                   evaluationNumber, **kw)

    RMS = sqrt(((ydata - Spec)**2).sum() / float(len(ydata)))

    # Write the fit parameters to a file
    parameterLabels = [
        'Magnetic field in Gauss =', 'Reservoir temperature in Celsius =',
        'Cell Length in mm =', 'Rb85 percentage =',
        'Doppler temperature in Celsius =', 'Theta0 in degrees =',
        'Initial sigma minus polarisation percentage = ', 'Shift in MHz =',
        'Extra Lorentzian width broadening (MHz) =',
        'Potassium-40 percentage =', 'Potassium-41 percentage ='
    ]
    #f_Parameters = open(os.path.join(outputDirectory,DAT+'_Parameters.txt')
    #					,'w')
    #optParams[2] = optParams[2]
    #optParams[3] = optParams[3]
    #optParams[6] = optParams[6]
    #optParams[5] = optParams[5]

    ## re-order the optimal params back to the way they were given in the 'parameters' argument....
    ## this is getting pretty tedious...
    optParams_out = [0] * len(parameters)
    optParams_out[0] = optParams[0]
    optParams_out[1] = optParams[12]
    optParams_out[2:12] = optParams[2:12]
    optParams_out[12:] = optParams[14:]

    print 'Optimum parameters found !'

    return optParams_out, RMS
Ejemplo n.º 6
0
def main(argv):
	""" 
	Main method. Imports specified runcard.py file from the second system argument.
	Program behaviour is then determined by contents of the runcard.py file. See
	runcard documentation for more information on program options.
	
	Usage examples:
	
		From command line / terminal:
			>> python elecsus_runcard.py path_to/runcard_file.py

		Within python interpreter:
		
			> python
			>>> import elecsus_runcard as er
			>>> er.main([None,'path_to/runcard_file.py'])
			
	!!! NOTE:: Use forward slashes to separate directories, even on Windows !!! 
	
	"""
	
	initialFiles = os.listdir('.') #List files existing before program started


	# Try to import runcard 
	if (len(argv) > 1): #If user specifies a run card name.
		runcardPath = argv[1]
		# if user has entered filename in another directory, check if any backslashes are used in string
		# try to convert string to raw string - replace backslashes (directories) with forward slashes
		# this doesn't work when there are escape sequences in the string, 
		# so warn user of errors and exit if found.
		runcardPath = runcardPath.replace("\\", "/")
		if any(es in runcardPath for es in esc_seq):
			print 'There are escape characters in the runcard file name string.\
					\nUse Forward slashes (/) to separate directories instead of back slashes.'
			sys.exit(1)
			
		# if file is in other directory, add to sys.path
		runcardDir = os.path.dirname(runcardPath)
		sys.path.append(runcardDir)
		
		# get only the file name
		runcardFilename = os.path.split(runcardPath)[1]
		RCFNstripped = runcardFilename.rstrip('.py')
		code1 = 'import ' + RCFNstripped + ' as G'
		try:
			exec code1 in globals()#Import user specified run card
		except:
			print 'Run card file not recognised.'
			sys.exit(1)
	else:
		print 'Run card file name must be specified. Exiting Python...' 
		sys.exit(1)

	print '\n'
	banner = open(os.path.join(cmd_subfolder,'NOTICE'),'r')
	for line in banner: #This loop prints the banner to the terminal
		print line,
	print '\n\n'
	banner.close()

	
	#run user inputs through the run card checker
	ELE,NDT,NDTB,CB,DT,DTB,BF,BFB,R85,R85B,K40,K41,T0,T0B,CL,CLB,POL,POLB,SHI,\
	SHIB,GAM,GAMB,PRE,TRA,SPE,DAT,SMB,SMF,FT,DStart,DStop,PB,SB,PFT =\
	runcardCheck.sanity_check(
		G.element,G.NdenTemp,G.NdenTempBool,G.ConstrainBool,G.DoppTemp,
		G.DoppTempBool,G.Bfield,G.BfieldBool,G.Rb85,G.Rb85Bool,G.K40,G.K41,
		G.Theta0,G.Theta0Bool,G.CellLength,G.CellLenBool,G.Polar,G.PolarBool,
		G.Shift,G.ShiftBool,G.Gamma,G.GammaBool,G.Precision,G.Transition,
		G.Spectrum,G.DataFilename,G.SmoothBool,G.SmoothFactor,G.FitType,
		G.DetStart,G.DetStop,G.PlotBool,G.SavePlotBool,G.PlotFileType
		)

	if TRA == 'D1': #If specified D-line is D1.
		print 'Calculating D1 spectrum...\n' #Tell user program is running.
	elif TRA == 'D2':					   
		print 'Calculating D2 spectrum...\n'

	#Stop warnings about casting complex numbers
	warnings.simplefilter("ignore")

	#Create results output directory
	tryDir = os.path.join(os.getcwd(),DAT+"_output")
	while True:
		outputDirectory = tryDir
		try:
			os.makedirs(outputDirectory)
			break
		except:
			tryDir = tryDir + "1"

	#Copy runcard to output directory for user reference
	try:
		copyfile(runcardPath,os.path.join(
									outputDirectory,runcardFilename))
	except:
		copyfile('runcard.py',os.path.join(outputDirectory,'runcard.py'))

	if FT == 'No fit, just theory':
		startTime = timing()
		GHzPrecision = PRE/1000.0
		#Detuning in GHz
		X	= arange(DStart,DStop+GHzPrecision,GHzPrecision)
		XMHz = X*1.0e3 #Detuning in MHz
		Spec = spectra.spectrum(XMHz,ELE,SPE,BF,NDT,CL,R85,DT,T0,POL,SHI,GAM,
								CB,TRA,PRE,K40,K41)
		print 'Calculation complete.'
	else: #If a fit to experimental data was requested
		try:
			xdata, ydata = read_in_twoColumn(DAT)
			file_read_success = True
		except:
			try:
				xdata, ydata = read_in_twoColumn(os.path.join(runcardDir,DAT))
				file_read_success = True
			except:
				print 'Data file not found. Check file exists in specified location with name: ', runcardFilename
				sys.exit(1)
			
		startTime = timing()
		if SMB:
			print '\nData binned for faster fitting.\n'
			#Take average of local points (low pass filter).
			xdata, ydata = smoother(xdata,ydata,SMF)
		
		X = xdata*1e-3 #Convert back to GHz for plotting
		
		#Generate parameter lists
		paramBoolList = [BFB,NDTB,CLB,R85B,DTB,T0B,POLB,SHIB,GAMB]
		parameters = [ELE,SPE,BF,NDT,CL,R85,DT,T0,POL,SHI,GAM,CB,\
					  TRA,PRE,K40,K41]
		
		# Call different fitting routines		
		if FT == 'Marquardt-Levenberg':
			import MLFittingRoutine as FR
			print '\nPerfoming Marquardt-Levenberg fitting routine.'
			optParams, Spec = FR.MLfit(xdata,ydata,parameters,
													 paramBoolList)
		elif FT == 'Simulated annealing':
			import SAFittingRoutine as FR
			print '\nPerforming fitting by simulated annealing.'
			optParams, Spec = FR.SAFit(xdata,ydata,parameters,
													 paramBoolList)
		else:
			import RRFittingRoutine as FR
			print '\nPerforming fitting by random-restart hill climbing method.'
			#The more parameters to fit, the more evaluations we need to do.
			factor = sum(paramBoolList) 
			evaluationNumber = factor**2 + 5 #integer
			optParams, Spec = FR.RRFit(xdata,ydata,parameters,paramBoolList,
									   evaluationNumber)
		
		optParams = optParams[2:] # compatibility with newer fitting routine (elecsus >V2.0.0)
		
		# Write the fit parameters to a file
		parameterLabels = ['Magnetic field in Gauss =',
						   'Reservoir temperature in Celsius =',
						   'Cell Length in mm =','Rb85 percentage =',
						   'Doppler temperature in Celsius =',
						   'Theta0 in degrees =',
						   'Initial sigma minus polarisation percentage = ',
						   'Shift in MHz =', 
						   'Extra Lorentzian width broadening (MHz) =',
						   'Potassium-40 percentage =',
						   'Potassium-41 percentage =']
		f_Parameters = open(os.path.join(outputDirectory,os.path.split(DAT)[1]+'_Parameters.txt')
							,'w')
		i = 0
		for par in parameterLabels:
			if i == 2:
				optParams[i] = optParams[i]*1000.0
				print >> f_Parameters, parameterLabels[i], optParams[i]
			elif (i==3 and ELE=='Rb') or i==6:
				optParams[i] = optParams[i]*100.0
				print >> f_Parameters, parameterLabels[i], optParams[i]
			elif i == 4 and CB:
				print >> f_Parameters, parameterLabels[i],\
						 "Reservoir temperature (constrained)"
			elif i == 5 and (SPE in ['Ix','Iy','S1','S2']):
				optParams[i] = optParams[i]*180.0
				print >> f_Parameters, parameterLabels[i], optParams[i]
			elif (i in [0,1,4,7,8]):
				print >> f_Parameters, parameterLabels[i], optParams[i]
			elif (i in [9,10]) and ELE == 'K':
				print >> f_Parameters, parameterLabels[i], parameters[i+5]
			i += 1
		numDenVal = numDenRb(optParams[1]+273.15)/1.0e18
		#Find root mean square deviation
		RMS = sqrt(((ydata - Spec)**2).sum()/float(len(ydata)))
		print >> f_Parameters, 'Number density of vapour =', numDenVal,\
				 'x 10^12 cm^-3'
		print >> f_Parameters, "\n\n"
		print >> f_Parameters, "RMS deviation of experimental data and theory =", RMS
		f_Parameters.close()
		#Print parameters to screen
		os.system('cls' if os.name == 'nt' else 'clear') #Clear the terminal
		print ''
		i=0
		for Boolian in paramBoolList:
			if Boolian:
				print parameterLabels[i], optParams[i]
			i+=1

	# Output theory to csv file
	fileDirectory = os.path.join(outputDirectory,os.path.split(DAT)[1]+'_theory.csv')
	fileOutput(fileDirectory,X,Spec)

	# Calculate and output residuals
	if FT != 'No fit, just theory':
		residuals = ydata - Spec
		fileOutput(os.path.join(outputDirectory,os.path.split(DAT)[1]+'_residuals.csv'),
				   X,residuals)
	
	print '\n\nTime taken:', timing() - startTime

	if SB:
		PlotName = os.path.join(outputDirectory,os.path.split(DAT)[1]+'_output'+PFT)
	else:
		PlotName = False

	# Plot results
	if FT == 'No fit, just theory':
		plotOutput(X,Spec,SPE,0,PBool=PB,path=PlotName)
	else:
		plotOutput(X,Spec,SPE,ydata,PB,True,residuals,PlotName)

	#Clean up pyc files
	if os.path.isfile("elecsus.pyc"):
		os.remove("elecsus.pyc")
	if os.path.isfile("runcard.pyc"):
		os.remove("runcard.pyc")
	if (len(argv) > 1):
		NewruncardFilename = runcardFilename + "c"
		BOOL1 = os.path.isfile(NewruncardFilename)
		BOOL2 = (NewruncardFilename in initialFiles)
		if BOOL1 and not BOOL2:
			os.remove(argv[1]+"c")
Ejemplo n.º 7
0
def fit_data(data,parameters,paramBoolList,experimental_datatype='S0',fit_algorithm='ML',**kw):
	""" 
	Blurb about this:
	
	data is a 2-element list containing x and y data 1-d arrays.
	parameters (and parameter order) is same as for the calculate() method.
	paramBoolList is ...
	experimental_datatype is ...
	fit_algorith is ...
	keywords are
	"""
	
	
	## alter the parameter order. again.
	## Really need to rewrite the fitting modules for a more sensible parameter order
	## but it's a pain and doesn't add any functionality, so
	## it is therefore on the 'to do' list!
	
	## Order as given in the 'parameters' argument
	## Element, Dline, B, T, L, Rb85, DoppT, Theta0, Pol, shift,
	## GammaBuf, Constrain, K40, K41
	
	## Order required by Fitting routines:
	## Element, OutputType, B, T, L, Rb85%, DoppT, Theta0, Pol, Shift, 
	## GammaBuf, Constrain, Dline, Precision, K40%, K41%

	parameters_new = [None]*16
	parameters_new[0] = parameters[0]
	parameters_new[1] = experimental_datatype
	parameters_new[2:12] = parameters[2:12]
	parameters_new[12] = parameters[1]
	parameters_new[13] = 10
	parameters_new[14:] = parameters[12:]
	
	#print parameters_new
	
	
	startTime = timing()
	xdata, ydata = data
	#print xdata, ydata
	#print type(xdata), type(ydata)
	
	
	# Call different fitting routines        
	if fit_algorithm == 'Marquardt-Levenberg':
		print '\nPerfoming Marquardt-Levenberg fitting routine.'
		optParams, Spec = ML.MLfit(xdata,ydata,parameters_new,
												 paramBoolList,**kw)
	elif fit_algorithm == 'Simulated Annealing':
		print '\nPerforming fitting by simulated annealing.'
		optParams, Spec = SA.SAFit(xdata,ydata,parameters_new,
												 paramBoolList,**kw)
	else:
		print '\nPerforming fitting by Random-Restart hill climbing method.'
		#The more parameters to fit, the more evaluations we need to do.
		factor = sum(paramBoolList) 
		evaluationNumber = factor**2 + 5 #integer
		optParams, Spec = RR.RRFit(xdata,ydata,parameters_new,paramBoolList,
								   evaluationNumber,**kw)
	
	
	RMS = sqrt(((ydata - Spec)**2).sum()/float(len(ydata)))
	
	# Write the fit parameters to a file
	parameterLabels = ['Magnetic field in Gauss =',
					   'Reservoir temperature in Celsius =',
					   'Cell Length in mm =',
					   'Rb85 percentage =',
					   'Doppler temperature in Celsius =',
					   'Theta0 in degrees =',
					   'Initial sigma minus polarisation percentage = ',
					   'Shift in MHz =', 
					   'Extra Lorentzian width broadening (MHz) =',
					   'Potassium-40 percentage =',
					   'Potassium-41 percentage =']
	#f_Parameters = open(os.path.join(outputDirectory,DAT+'_Parameters.txt')
	#					,'w')
	#optParams[2] = optParams[2]
	#optParams[3] = optParams[3]
	#optParams[6] = optParams[6]
	#optParams[5] = optParams[5]

	## re-order the optimal params back to the way they were given in the 'parameters' argument....
	## this is getting pretty tedious...
	optParams_out = [0]*len(parameters)
	optParams_out[0] = optParams[0]
	optParams_out[1] = optParams[12]
	optParams_out[2:12] = optParams[2:12]
	optParams_out[12:] = optParams[14:]
	
	print 'Optimum parameters found !'
	
	return optParams_out, RMS
 def fct(*args, **kwargs):
     before = timing()
     rv = func(*args, **kwargs)
     after = timing()
     print('elapsed', after - before)
     return rv
Ejemplo n.º 9
0
def solve_diel(chiL,
               chiR,
               chiZ,
               THETA,
               Bfield,
               verbose=False,
               force_numeric=False):
    ''' 
	Solves the wave equation to find the two propagating normal modes of the system, 
	for a given magnetic field angle THETA. For the general case, use symbolic python to 
	solve for the roots of n-squared.
	(Escapes this slow approach for the two analytic cases for the Voigt and Faraday geometries)
	
	Returns the rotation matrix to transform the coordinate system into the normal mode basis,
	and returns the two refractive index arrays.
	
	Inputs:
	
		chiL, chiR, chiZ	:	1D lists or numpy arrays, of length N, that are the frequency-dependent electric susceptibilities
		THETA				:	Float, Magnetic field angle in radians
		Bfield				:	Float, Magnitude of applied magnetic field (skips slow approach if magnetic field is very close to zero)
		
	Options:
		
		verbose			:	Boolean to output more print statements (timing reports mostly)
		force_numeric	:	If True, forces all angles to go through the numeric approach, rather than escaping for the analytic cases (THETA=0, THETA=pi/2...)
	
	Outputs:
		RotMat	:	Rotation matrix to transform coordinate system, dimensions (3, 3, N)
		n1		:	First solution for refractive index, dimensions (N)
		n2		:	Second solution for refractive index, dimensions (N)
	
	'''

    if verbose:
        print(('B-field angle (rad, pi rad): ', THETA, THETA / np.pi))

    stt = timing()

    # make chiL,R,Z arrays if not already
    chiL = np.array(chiL)
    chiR = np.array(chiR)
    chiZ = np.array(chiZ)

    #### Escape the slow loop for analytic (Faraday and Voigt) cases
    ## For these analytic cases we can use array operations and it is therefore
    ## much faster to compute
    if (abs(THETA % (2 * np.pi) - np.pi / 2) < 1e-4) and (not force_numeric):
        # ANALYTIC SOLNS FOR VOIGT
        if verbose: print('Voigt - analytic')

        # solutions for elements of the dielectric tensor:
        ex = 0.5 * (2. + chiL + chiR)
        exy = 0.5j * (chiR - chiL)
        ez = 1.0 + chiZ

        # refractive indices to propagate
        n1 = np.sqrt(ex + exy**2 / ex)
        n2 = np.sqrt(ez)

        ev1 = [np.zeros(len(ex)), ex / exy, np.ones(len(ex))]
        ev2 = [np.ones(len(ex)), np.zeros(len(ex)), np.zeros(len(ex))]
        ev3 = [np.zeros(len(ex)), np.zeros(len(ex)), np.ones(len(ex))]

        RotMat = np.array([ev1, ev2, ev3])

        if verbose:
            print('Shortcut:')
            print((RotMat.shape))
            print((n1.shape))
            print((n2.shape))

    elif ((abs(THETA) < 1e-4) or
          ((abs(THETA - np.pi)) < 1e-4) or abs(Bfield) < 1e-2) and (
              not force_numeric
          ):  ## Use Faraday geometry if Bfield is very close to zero
        # ANALYTIC SOLNS FOR FARADAY
        #if verbose:
        if verbose: print('Faraday - analytic TT')

        ex = 0.5 * (2. + chiL + chiR)
        exy = 0.5j * (chiR - chiL)
        e_z = 1.0 + chiZ

        n1 = np.sqrt(ex + 1.j * exy)
        n2 = np.sqrt(ex - 1.j * exy)

        ev1 = np.array(
            [-1.j * np.ones(len(ex)),
             np.ones(len(ex)),
             np.zeros(len(ex))])
        ev2 = np.array(
            [1.j * np.ones(len(ex)),
             np.ones(len(ex)),
             np.zeros(len(ex))])
        ev3 = [np.zeros(len(ex)), np.zeros(len(ex)), np.ones(len(ex))]

        if (abs(THETA) < 1e-4):
            RotMat = np.array([ev1, ev2, ev3])
        else:
            #if anti-aligned, swap the two eigenvectors
            RotMat = np.array([ev2, ev1, ev3])

        if verbose:
            print('Shortcut:')
            print((RotMat.shape))
            print((n1.shape))
            print((n2.shape))

    else:
        if verbose:
            print('Non-analytic angle.. This will take a while...'
                  )  ##### THIS IS THE ONE THAT's WRONG....
        # set up sympy symbols
        theta = Symbol('theta', real=True)
        n_sq = Symbol('n_sq')
        e_x = Symbol('e_x')
        e_xy = Symbol('e_xy')
        e_z = Symbol('e_z')

        # General form of the dielectric tensor
        DielMat = Matrix(([(e_x - n_sq) * cos(theta), e_xy, e_x * sin(theta)],
                          [-e_xy * cos(theta), e_x - n_sq,
                           -e_xy * sin(theta)], [(n_sq - e_z) * sin(theta), 0,
                                                 e_z * cos(theta)]))

        et1 = timing() - stt

        # Substitute in angle
        DielMat_sub = DielMat.subs(theta, pi * THETA / np.pi)

        et2 = timing() - stt

        # Find solutions for complex indices for a given angle
        solns = solve(det(DielMat_sub), n_sq)

        et3a = timing() - stt
        #print et3a

        # Find first refractive index
        DielMat_sub1 = DielMat_sub.subs(n_sq, solns[0])
        n1 = np.zeros(len(chiL), dtype='complex')
        n1old = np.zeros(len(chiL), dtype='complex')
        # Find second refractive index
        DielMat_sub2 = DielMat_sub.subs(n_sq, solns[1])
        n2 = np.zeros(len(chiL), dtype='complex')
        n2old = np.zeros(len(chiL), dtype='complex')

        et3b = timing() - stt

        Dsub1 = lambdify((e_x, e_xy, e_z), DielMat_sub1, 'numpy')
        Dsub2 = lambdify((e_x, e_xy, e_z), DielMat_sub2, 'numpy')

        nsub1 = lambdify((e_x, e_xy, e_z), solns[0], 'numpy')
        nsub2 = lambdify((e_x, e_xy, e_z), solns[1], 'numpy')

        # Initialise rotation matrix
        RotMat = np.zeros((3, 3, len(chiL)), dtype='complex')

        et3c = timing() - stt

        # populate refractive index arrays
        n1 = np.sqrt(
            nsub1(0.5 * (2. + chiL + chiR), 0.5j * (chiR - chiL),
                  (1.0 + chiZ)))
        n2 = np.sqrt(
            nsub2(0.5 * (2. + chiL + chiR), 0.5j * (chiR - chiL),
                  (1.0 + chiZ)))

        et3 = timing() - stt

        if verbose:
            print(('setup time:', et1, et1))
            print(('solve nsq: (total/solve/sub in) ', et3a, et3a - et2,
                   et2 - et1))
            print((
                'get nsq arrays (tot time / populate ref. index / gen. lambdify / sub in): ',
                et3, et3 - et3c, et3c - et3b, et3b - et3a))

        # loop over all elements of chiL,R,Z to populate eigenvectors
        # time-limiting step for arrays of length >~ 5000
        for i, (cL, cR, cZ) in enumerate(zip(chiL, chiR, chiZ)):
            #if verbose: print 'Detuning point i: ',i

            #time diagnostics
            st = timing()
            '''	
		## OLD and slow method::
			# Sub in values of susceptibility
			DielMat_sub1a = DielMat_sub1.subs(e_x, 0.5*(2.+cL+cR))
			DielMat_sub1a = DielMat_sub1a.subs(e_xy, 0.5j*(cR-cL))
			DielMat_sub1a = DielMat_sub1a.subs(e_z, (1.0+cZ))
			
			et1 = timing() - st
			
			# Evaluate and convert to numpy array
			DM = np.array(DielMat_sub1a.evalf())
			DMa = np.zeros((3,3),dtype='complex')
			for ii in range(3):
				for jj in range(3):
					DMa[ii,jj] = np.complex128(DM[ii,jj])
			
			et2 = timing() - st
		
			# use scipy to find eigenvector
			#ev1 = Matrix(DMa).nullspace()
			#print 'Sympy: ', ev1
			
			ev1old = nullOld(DMa).T[0]
			#ev1 = null(DMaNP).T
			
			# sub in for ref. index
			n1soln = solns[0].subs(e_x, 0.5*(2.+cL+cR))
			n1soln = n1soln.subs(e_xy, 0.5j*(cR-cL))
			n1soln = n1soln.subs(e_z, (1.0+cZ))
			
			# Populate the refractive index array
			n1old[i] = np.sqrt(np.complex128(n1soln.evalf()))
		## /OLD method
			'''

            # NEW method

            # Sub in values of susceptibility
            DMaNP = Dsub1(0.5 * (2. + cL + cR), 0.5j * (cR - cL), (1.0 + cZ))
            #print DMa
            ev1 = null(DMaNP).T
            # Populate the refractive index array
            #n1[i] = np.sqrt(nsub1(0.5*(2.+cL+cR), 0.5j*(cR-cL), (1.0+cZ)))
            '''
			## METHOD COMPARISON
			
			print 'SymPy:'
			print DMa
			print DMa.shape, type(DMa)
			print 'Numpy'
			print DMaNP
			print DMaNP.shape, type(DMaNP)
			
			print 'Eigenvectors ...'
			print 'Old: ', ev1old			
			print 'New: ',ev1
			'''

            #print '\n\n\n'

            #print 'scipy: ', ev1

            et3 = timing() - st

            et4 = timing() - st

            #
            ## Now repeat the above for second eigenvector
            #

            ## NEW
            # Sub in values of susceptibility
            DMaNP = Dsub2(0.5 * (2. + cL + cR), 0.5j * (cR - cL), (1.0 + cZ))
            # Find null eigenvector
            ev2 = null(DMaNP).T
            # Populate the refractive index array
            #n2[i] = np.sqrt(nsub2(0.5*(2.+cL+cR), 0.5j*(cR-cL), (1.0+cZ)))

            et5 = timing() - st
            '''
		## OLD
			# Evaluate and convert to numpy array
			DielMat_sub2a = DielMat_sub2.subs(e_x, 0.5*(2.+cL+cR))
			DielMat_sub2a = DielMat_sub2a.subs(e_xy, 0.5j*(cR-cL))
			DielMat_sub2a = DielMat_sub2a.subs(e_z, (1.0+cZ))
			
			DM = np.array(DielMat_sub2a.evalf())
			DMa = np.zeros((3,3),dtype='complex')
			for ii in range(3):
				for jj in range(3):
					DMa[ii,jj] = np.complex128(DM[ii,jj])
					
			et6 = timing() - st
			
			# use scipy to find eigenvector
			ev2old = nullOld(DMa).T[0]
			
			et7 = timing() - st
			
			# sub in for ref. index
			n2soln = solns[1].subs(e_x, 0.5*(2.+cL+cR))
			n2soln = n2soln.subs(e_xy, 0.5j*(cR-cL))
			n2soln = n2soln.subs(e_z, (1.0+cZ))
			
			# Populate the refractive index array
			n2old[i] = np.sqrt(np.complex128(n2soln.evalf()))
			'''

            # Populate the rotation matrix
            RotMat[:, :, i] = [ev1, ev2, [0, 0, 1]]

        et_tot = timing() - stt
        if verbose:
            print(('Time elapsed (non-analytic angle):', et_tot))

    if verbose: print('SD done')
    return RotMat, n1, n2