def calc_time_epc( time ) :

	# Note.  This function assumes that "time" is either a numerical
	#        quantity or a string in the formats described above.

	# If this function has been called on "None" or an empty string, simply
	# return "None".

	if ( ( time is None ) or ( time == '' ) ) :
		return None

	# If "time" is already an "datetime" epoch, return it unmodified.

	if ( type( time ) == datetime ) :
		return time

	# If "time" is a string, standardize it, extract its individual time
	# components, and return the equivalant "datetime" epoch.  Otherwise,
	# assume that it is a value, standardize it, and return the equivalant
	# "datetime" epoch.

	if ( type( time ) == str ) :

		# Try to standardize the string.  If this doesn't work, return
		# "None".

		time_str = calc_time_str( time )

		# Try to Extract/compute the year, month, day, hour, minute,
		# second, and microsecond numbers and to use them to compute the
		# corresponding "datetime" epoch.  If this works, return the
		# "datetime" epoch; otherwise, return "None".

		try :

			t_year = int( time_str[0:4]           )
			t_mon  = int( time_str[5:7]           )
			t_day  = int( time_str[8:10]          )
			t_hour = int( time_str[11:13]         )
			t_min  = int( time_str[14:16]         )
			t_sec  = int( time_str[17:19]         )
			t_mcsc = int( time_str[20:23] + '000' )

			return datetime( t_year, t_mon, t_day,
			                 t_hour, t_min, t_sec, t_mcsc )

		except :

			return None

	else :

		# Assume the "time" is a numerical value and try to construct a
		# corresponding "timedelta" object.  If this doesn't work,
		# return "None".

		try :
			td = timedelta( seconds=time )
		except :
			return None

		# Return the equivalant "datetime" epoch.

		return datetime( 1970, 1, 1, 0, 0, 0, 0 ) + td def calc_time_sec( time ) :

	# If this function has been called on "None" or an empty string, simply
	# return "None".

	if ( ( time is None ) or ( time == '' ) ) :
		return None

	# Try to convert the argument "time" to a value and to round that value
	# to the nearest second.  If this fails, abort (returning "None").

	time_val = calc_time_val( time )

	if ( time_val is None ) :
		return None

	time_val_sec = round( time_val, 0 )

	# Convert the rounded time value to a string and truncate it.

	time_str_sec = ( calc_time_str( time_val_sec ) )[0:19]

	# Return the truncated string.

	return time_str_sec def calc_time_val( time ) :

	# If this function has been called on "None" or an empty string, simply
	# return "None".

	if ( ( time is None ) or ( time == '' ) ) :
		return None

	# If "time" is a "datetime" epoch or a string, compute the elapsed time
	# of "time" since "1970-01-01/00:00:00.000".  Otherwise, assume that
	# "time" is a numerical quantity, and return "time" recast as a "float"
	# rounded to three decimal places (to "standardize" it).

	if ( ( type( time ) == datetime ) or 
	     ( type( time ) == str      )    ) :

		# Attempt to standardize the "datetime" epoch "time".  If this
		# fails, abort (returning "None").

		time_epc = calc_time_epc( time )

		if ( time_epc is None ) :
			return None

		# Create a "datetime" object for "1970-01-01/00:00:00.000".

		unix_epc = datetime( 1970, 1, 1, 0, 0, 0, 0 )

		# Return the number of seconds elased from "unix_epc" to
		# "time_epc" rounded to the thrid decimal place.

		return round( float( 
		                 ( time_epc - unix_epc ).total_seconds( ) ), 3 )

	else :

		# Assume that "time" is a numerical quantity, and attempt to
		# return it recast as a "float" rounded to three decimal places.
		# If this fails, return "None" instead.

		try :
			return round( float( time ), 3 )
		except :
			return None class step( object ) :

	#-----------------------------------------------------------------------
	# DEFINE THE INITIALIZATION FUNCTION.
	#-----------------------------------------------------------------------

	def __init__( self, cen, wid, lev, descending=False ) :

		# Note.  This function presumes that a step function has been
		#        defined in terms of the equal-length, linear arrays
		#        "cen", "wid", and "lev".  The array "cen" indicates the
		#        location of the center of each step, "wid" the width,
		#        and "lev" the height.  It is assumed that the bins are
		#        in assending order (unless it is specified to be
		#        descending) and to have no (or at least negligible)
		#        gaps between them.

		# CAUTION!  No checks are made to ensure that the bins are
		#           ascending/descending and that have no gaps between
		#           them.

		# Determine the number of steps based on the length of the
		# passed arrays.

		self.n = min( len( cen ), len( wid ), len( lev ) )

		# Store the center, width, and level of each steps.  If
		# necessary, truncate the user provided arrays to be the same
		# length.

		self.cen = array( cen[0:self.n] )
		self.wid = array( wid[0:self.n] )
		self.lev = array( lev[0:self.n] )

		# If the bins were passed in descending order, reverse them to
		# ascending order.

		if ( descending ) :

			self.cen = self.cen[::-1]
			self.wid = self.wid[::-1]
			self.lev = self.lev[::-1]

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR COMPUTING THE STEP FUNCTION'S PLOT POINTS.
	#-----------------------------------------------------------------------

	def calc_pnt( self, lev_min=None ) :

		# Note.  This function then returns two, equal-length, linear
		#        arrays, "pnt_x" and "pnt_y", that can be passed to the
		#        user's choice of plotting functions to display the step
		#        function.

		# If undefined, provide a value for "lev_min".

		lev_min = amin( self.lev ) if ( lev_min is None ) else lev_min

		# Initialize the arrays that will contain the computed points of
		# the step function.

		pnt_x = zeros( 2 * ( self.n + 1 ) )
		pnt_y = zeros( 2 * ( self.n + 1 ) )

		# Compute the points of the step function.

		pnt_x[0] = self.cen[0] - ( self.wid[0] / 2. )
		pnt_y[0] = lev_min

		for i in range( self.n ) :
			pnt_x[(2*i)+1] = self.cen[i] - ( self.wid[i] / 2. )
			pnt_x[(2*i)+2] = self.cen[i] + ( self.wid[i] / 2. )
			pnt_y[(2*i)+1] = self.lev[i]
			pnt_y[(2*i)+2] = self.lev[i]

		pnt_x[(2*self.n)+1] = self.cen[self.n-1] + \
		                                     ( self.wid[self.n-1] / 2. )
		pnt_y[(2*self.n)+1] = lev_min

		# Return the computed points of the step function.

		return ( pnt_x, pnt_y ) class fc_arcv( object ) :

	#-----------------------------------------------------------------------
	# DEFINE THE INITIALIZATION FUNCTION.
	#-----------------------------------------------------------------------

	def __init__( self, core=None, buf=None, tol=None,
	                    n_file_max=float('inf'), n_date_max=None,
	                    path=None, verbose=None                   ) :

		# Save the arguments for later use.

		# Note.  The "n_file_max" argument is handled at the end of this
		#        function with a call of "chng_n_file_max".

		self.core       = core

		self.buf        = float( buf )      if ( buf is not None )\
		                                    else 3600.

		self.tol        = float( tol )      if ( tol is not None )\
		                                    else 3600.

		self.n_date_max = int( n_date_max ) if ( n_date_max 
		                                    is not None ) else 40

		self.path       = str( path )       if ( path  is not None )\
		                                    else os.path.join( 
		                                    os.path.dirname( __file__ ), 
		                                    'data', 'fc' )

		self.verbose    = bool( verbose )   if ( verbose is not None )\
		                                    else True

		# Validate the values of the "self.max_*" parameters and, if
		# necessary, provide values for them.

		if ( self.buf < 0 ) :
			raise ValueError( 'Time buffer cannot be negative.'    )

		if ( self.n_date_max < 0 ) :
			raise ValueError( 'Maximum number of dates ' +
			                                 'cannot be negative.' )

		# Initialize arrays of date and times loaded.

		self.arr_cdf  = [ ]
		self.arr_date = [ ]

		self.arr_tag  = [ ]

		# Initialize "n_file_max"

		self.chng_n_file_max( n_file_max )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR LOADING (AND RETURNING) AN ION SPECTRUM.
	#-----------------------------------------------------------------------

	def load_spec( self, time, get_prev=False, get_next=False ) :	

		# If both "get_????" keywords are "True", abort.

		if ( ( get_prev ) and ( get_next ) ) :
			self.mesg_txt( 'none' )
			return None

		# Convert/standardize the requested time.

		time_req_str = calc_time_str( time )
		time_req_val = calc_time_val( time )
		time_req_epc = calc_time_epc( time )

		# Extract requested date (as a string) and the requested time
		# (as a float indicating seconds since midnight).  Likewise,
		# determine the date of the previous day and the date of the
		# next day.

		date_req_str = time_req_str[0:10]
		scnd_req_val = time_req_val - calc_time_val( date_req_str )

		date_pre_str = ( calc_time_str( time_req_val - 86400. ) )[0:10]
		date_nex_str = ( calc_time_str( time_req_val + 86400. ) )[0:10]

		# Load all the spectra from the requested date.  If the
		# requested time is within "self.buf" seconds of either the
		# previous or next day, load all the spectra from that date as
		# well.

		# Note.  There is no need to check here whether a date has
		#        already been loaded as that's the first thing that
		#        "self.load_date( )" does.

		self.load_date( date_req_str )		

		if ( scnd_req_val <= self.buf ) :
			self.load_date( date_pre_str )

		if ( ( 86400. - scnd_req_val ) <= self.buf ) :
			self.load_date( date_nex_str )

		# If no spectra have been loaded, abort.

		if ( len( self.arr_tag ) == 0 ) :
			self.mesg_txt( 'none' )
			return None

		# Locate the spectrum whose timestamp is closest to the
		# one requested.

		adt = [ abs(tag.epoch - time_req_epc) for tag in self.arr_tag ]

		adt_min = min( adt )

		tk = [ a for a in range( len( adt ) ) if adt[a] == adt_min ][0]

		if ( get_prev ) :
			tk -= 1
		if ( get_next ) :
			tk +=1

		if( ( tk <  0                   ) or
		    ( tk >= len( self.arr_tag ) )    ) :
			self.mesg_txt( 'none' )
			return None


		# If the selected spectrum is not within the the request
		# tolerence, abort.

		if ( ( adt[tk] ).total_seconds() > self.tol ) :# In case of long
		                                               # Data gap  
			self.mesg_txt( 'none' )
			return None


		# If the selected spectrum is not within the the request
		# tolerence, abort.
	
		# Extract the spectrum to be returned.

		cdf = self.arr_cdf[self.arr_tag[tk].c]
		s   = self.arr_tag[ tk ].s

		# Find actual no. of voltage bins 

		n_bin_max = 31
		n_dir = 20

		for n_bin_1 in range( n_bin_max ) :
			if ( n_bin_1 == n_bin_max + 1 ) :
				break
			if ( cdf['cup1_EperQ'][s][n_bin_1] >= 
					     cdf['cup1_EperQ'][s][n_bin_1+1] ) :
				break
		n_bin_1 += 1

		for n_bin_2 in range( n_bin_max ) :
			if ( n_bin_2 == n_bin_max + 1 ) :
				break
			if ( cdf['cup2_EperQ'][s][n_bin_2] >= 
					     cdf['cup2_EperQ'][s][n_bin_2+1] ) :
				break
		n_bin_2 += 1

		n_bin = min( [ n_bin_1, n_bin_2 ] )

		# Assigning all retrieved data to parameter values

		time = cdf['Epoch'][s]			

		elev = [ float( cdf['inclination_angle'][0] ),
		         float( cdf['inclination_angle'][1] ) ]

		azim = [ [ float( cdf['cup1_azimuth'][s][d] ) 
		                for d in range( n_dir )       ],
			 [ float( cdf['cup2_azimuth'][s][d] )
		                for d in range( n_dir )       ]  ]

		volt_cen=[ [ float( cdf['cup1_EperQ'][s][b] ) 
		                  for b in range( n_bin )     ],
			   [ float( cdf['cup2_EperQ'][s][b] )
		                  for b in range( n_bin )     ]  ]

		volt_del=[ [ float( cdf['cup1_EperQ_DEL'][s][b] )
		                  for b in range( n_bin )         ],
			   [ float( cdf['cup2_EperQ_DEL'][s][b] )
		                  for b in range( n_bin )         ]  ]

		curr = [ [ [ float( cdf['cup1_qflux'][s][d][b] )
		             for b in range( n_bin )             ] 
		             for d in range( n_dir )               ],
			 [ [ float( cdf['cup2_qflux'][s][d][b] )
		             for b in range( n_bin )             ]
		             for d in range( n_dir )               ]  ]


		spec = fc_spec( n_bin, elev=elev, azim=azim, volt_cen=volt_cen,\
		                       volt_del=volt_del, curr=curr, time=time )

		# Request a cleanup of the data loaded into this archive.

		self.cleanup_date( )	

		return spec

		#fc_arcv().load_spec(1224246301)

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR LOADING ALL SPECTRA FROM DATE-SPECIFIED FILE.
	#-----------------------------------------------------------------------

	def load_date( self, date_str ) :

		# Determine whether or not the requested date has already been
		# loaded.  If it has, abort.

		arr = [ date for date in self.arr_date if date == date_str ]

		if ( len( arr ) > 0 ) :
			return

		# Extract the year, month, and day portions of the "date_str"
		# string.

		str_year = date_str[0:4]
		str_mon  = date_str[5:7]
		str_day  = date_str[8:10]

		# Determine the name of the file that contains data from the
		# requested date.

		fl0 = 'wi_sw-ion-dist_swe-faraday_' + \
		       str_year + str_mon + str_day + '_v??.cdf'

		fl0_path = os.path.join( self.path, fl0 )

		gb = glob( fl0_path )	# returns all files with 
		                        # common names in argument

		# If the file does not exist, attempt to download it.

		if ( len( gb ) > 0 ) :	# Take the last one : gb[-1]
			fl_path = gb[-1]
		else :
			try :
				self.mesg_txt( 'ftp', date_str )
				ftp = FTP_TLS( 'spdf.gsfc.nasa.gov' )
				ftp.login( )
				ftp.cwd( 'pub/data/wind/swe/swe_faraday/' )
				ftp.cwd( str_year )
				ls = ftp.nlst( fl0 )
				fl = ls[-1]
				fl_path = os.path.join( self.path, fl )
				ftp.retrbinary( "RETR " + fl,
					        open( fl_path, 'wb' ).write )
			except :
				self.mesg_txt( 'fail', date_str )
				return

		# If the file now exists, try to load it; otherwise, abort.

		self.mesg_txt( 'load', date_str )

		if ( os.path.isfile( fl_path ) ) :
			try :
				cdf = pycdf.CDF( fl_path )
			except :
				self.mesg_txt( 'fail', date_str )
				return
		else :
			self.mesg_txt( 'fail', date_str )
			return

		# Add the CDF object and tags for each spectrum to the arrays.

		c = len( self.arr_cdf )

		self.arr_cdf  = self.arr_cdf  + [ cdf ]	     # arr_cdf and	
		self.arr_date = self.arr_date + [ date_str ] # arr_date of
		                                             # same size

		n_spec = len( cdf['Epoch'] )
		self.arr_tag = self.arr_tag + [ fc_tag( c=c, s=s,
		                                epoch=cdf['Epoch'][s] )
		                                for s in range( n_spec ) ]

		self.arr_tag = sorted( self.arr_tag, key=attrgetter('epoch') )




		# Request a clean-up of the files in the data directory.

		#self.cleanup_file( )


	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR CLEANING UP THIS ARCHIVE.
	#-----------------------------------------------------------------------

	def cleanup_date( self ) :

		# If the number of dates is less than or equal to the maximum,
		# abort (as nothing needs to be done).

		if ( len(self.arr_date) <= self.n_date_max ) :
			return

		# How to get the entire list of arr_cdf, arr_data, arr_tag for 
		#all downloded data. ...Showing for only the requested one ...

		self.arr_tag = [ tag for tag in self.arr_tag if tag.c != 0 ]

		self.arr_cdf  = self.arr_cdf[1:]
		self.arr_date = self.arr_date[1:]

		for t in range( len( self.arr_tag ) ) :
			self.arr_tag[t].c -= 1

		# In case there are multiple dates to be removed; \
		#special case time 12 a.m.
		
		self.cleanup_date()

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR CLEANING UP THE DATA DIRECTORY.
	#-----------------------------------------------------------------------

	def cleanup_file( self ) :

		# If there is no limit on the number files in the data
		# directory, abort (as there's nothing to be done).

		if ( self.n_file_max >= float( 'infinity' ) ) :
			return

		# Generate a list of the names of all files of the requested
		# type (as specified by the "use_*" keywords) in the data
		# directory.

		file_name = array( glob(
			 os.path.join( self.path, 'wi_sw-ion*'     ) ) )

		n_file = len( file_name )

		# If the number of files is less than or equal to the maximum,
		# abort (as nothing needs to be done).

		if ( n_file <= self.n_file_max ) :
			return

		# Determine the access time of each of the files, and then sort
		# the files in ascending value.

		file_time = array( [ os.path.getatime( fl )
		                     for fl in file_name    ] )

		srt = argsort( file_time )

		file_name = file_name[srt]
		file_time = file_time[srt]

		# Delete files so that the number of files equals the maximum
		# allowed.

		for f in range( n_file - self.n_file_max ) :
			os.remove( file_name[f] )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR SENDING INFORMATIONAL MESSAGES TO THE USER.
	#-----------------------------------------------------------------------

	def mesg_txt( self, mesg_typ, mesg_obj='' ) :

		# If this object is not associated with an instance of the
		# Janus "core", or if messaging has not been requested, abort.

		if ( ( self.core is None ) or ( not self.verbose ) ) :

			return

		# Emit a message signal (on behalf of the core) containing the
		# message parameters.

		self.core.emit( SIGNAL('janus_mesg'),
		                'fc', mesg_typ, mesg_obj )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR CHANGING THE MAXIMUM NUMBER OF FILES.
	#-----------------------------------------------------------------------

	def chng_n_file_max( self, val ) :

		# Check the maximum file number input to ensure it is a postive
		# integer. Change the maximum file number if it is. Otherwise,
		# raise an error.

		if ( ( val != float( 'inf' ) ) and
		     ( type( val ) is not int     )     ) :

			raise ValueError( 'Max file number must be ' +
			                     'infinity or a positive integer.' )

			return

		if ( val < 0 ) :

			raise ValueError( 'Max file number cannot be ' +
			                                           'negative.' )

			return

		self.n_file_max = val

		self.cleanup_file( ) class spin_arcv( object ) :

	#-----------------------------------------------------------------------
	# DEFINE THE INITIALIZATION FUNCTION.
	#-----------------------------------------------------------------------

	def __init__( self, core=None, buf=None, win=None,
	                    n_file_max=float('inf'), n_date_max=None,
	                    path=None, verbose=None                   ) :

		# Save the arguments for later use and, if necessary,
		# provide values

		# Note.  The value of "n_file_max" is set at the end of this
		#        function via the "chng_n_file_max" function.

		self.core = core

		self.buf  = float( buf )            if ( buf is not None )\
		                                    else 3600.

		self.win  = int( win   )            if ( win is not None )\
		                                    else 5

		self.n_date_max = int( n_date_max ) if ( n_date_max 
		                                    is not None ) else 40

		self.path       = str( path )       if ( path  is not None )\
		                                    else os.path.join( 
		                                    os.path.dirname( 
		                                    __file__ ), 'data', 'spin' )

		self.verbose    = bool( verbose )   if ( verbose is not None )\
		                                    else True

		# Validate the values of the parameters.

		if ( self.buf < 0 ) :
			raise ValueError( 'Time buffer cannot be negative.'    )

		if ( self.win <= 0 ) :
			raise ValueError( 'Median window must be at least 1.'  )

		if ( self.n_date_max < 0 ) :
			raise ValueError( 'Maximum number of dates ' +
			                                 'cannot be negative.' )

		# Initialize the list of dates loaded.

		self.arr_date = [ ]

		# Initialize the data lists.

		self.arr_spin_t   = [ ]
		self.arr_spin_w   = [ ]
		self.arr_spin_ind = [ ]

		# Initialize "n_file_max"

		self.chng_n_file_max( n_file_max )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR LOADING (AND RETURNING) A SPIN RATE.
	#-----------------------------------------------------------------------

	def load_spin( self, time ) :

		# Compute the requested time both as a standard-format string
		# and a "datetime" object.

		time_str = calc_time_str( time )
		time_epc = calc_time_epc( time )

		# Construct a list of the dates requested.

		req_date = [ ]

		tm_strt = time_epc - timedelta( seconds=self.buf )
		tm_stop = time_epc + timedelta( seconds=self.buf )

		dt_strt = datetime( tm_strt.year, tm_strt.month, tm_strt.day )
		dt_stop = datetime( tm_stop.year, tm_stop.month, tm_stop.day )

		dt_i = dt_strt

		while ( dt_i <= dt_stop ) :
			date_i = ( calc_time_str( dt_i ) )[0:10]
			req_date.append( date_i )
			dt_i += timedelta( 1 )

		# For each date in "date_i", load the data (if necessary).

		for date in req_date :
			self.load_date( date )

		# Compute the absolute time difference between the requested
		# time and the timestamp of each loaded datum.

		adt = [ abs( ( t - time_epc ).total_seconds( ) )
		                                      for t in self.arr_spin_t ]

		# Determine the ordering of the absolute time differences.

		arg = sorted( range( len( adt ) ), key=adt.__getitem__ )

		# If the smallest time difference is greater than the tolerance,
		# return 'None'.

		if ( adt[arg[0]] > self.buf ) :
			return None

		# Compute and the median spin rate for the data with the
		# smallest time difference.

		w = median( [ self.arr_spin_w[arg[i]]
		              for i in range( self.win ) ] )

		# Request a cleanup of the data loaded into this archive.

		self.cleanup_date( )

		# Return the spin period.

		return ( 2. * pi / w )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR LOADING ALL DATA FROM DATE-SPECIFIED FILE.
	#-----------------------------------------------------------------------

	def load_date( self, date_str ) :

		# Determine whether or not the requested date has already been
		# loaded.  If it has, abort.

		if ( date_str in self.arr_date ) :

			return

		# Extract the year, month, day, and day of year of the requested
		# date.

		year = int( date_str[0:4]  )
		mon  = int( date_str[5:7]  )
		day  = int( date_str[8:10] )

		# Determine the name of the file that contains data from the
		# requested date.

		str_year = date_str[0:4]
		str_mon  = date_str[5:7]
		str_day  = date_str[8:10]

		fl0 = 'wi_k0_spha_' + str_year + str_mon + str_day + '_v??.cdf'

		fl0_path = os.path.join( self.path, fl0 )

		# Search for the file in the local data directory.  If it is not
		# found, attempt to download it.

		gb = glob( fl0_path )

		if ( len( gb ) > 0 ) :

			fl_path = gb[-1]

		else :

			try :

				self.mesg_txt( 'ftp', date_str )

				ftp = FTP_TLS( 'spdf.gsfc.nasa.gov' )

				ftp.login( )

				ftp.cwd( 'pub/data/wind/orbit/spha_k0' )

				ftp.cwd( str_year )

				ls = ftp.nlst( fl0 )

				fl = ls[-1]

				fl_path = os.path.join( self.path, fl )

				ftp.retrbinary( "RETR " + fl,
				           open( fl_path, 'wb' ).write )

			except :

				self.mesg_txt( 'fail', date_str )

				return

		# If the file now exists locally, try to load it; otherwise,
		# abort.

		self.mesg_txt( 'load', date_str )

		if ( os.path.isfile( fl_path ) ) :

			try :

				cdf = pycdf.CDF( fl_path )

			except :

				self.mesg_txt( 'fail', date_str )

				return

		else :

			self.mesg_txt( 'fail', date_str )

			return

		# Append the requested date to the list of dates loaded.

		ind = len( self.arr_date )

		self.arr_date.append( date_str )

		# Extract the data from the loaded file.

		self.arr_spin_t += list( cdf['Epoch'        ][:] )
		self.arr_spin_w += list( cdf['AVG_SPIN_RATE'][:] )

		self.arr_spin_ind += [ ind for ep in cdf['Epoch'] ]

		# Request a clean-up of the files in the data directory.

		self.cleanup_file( )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR CLEANING UP THIS ARCHIVE.
	#-----------------------------------------------------------------------

	def cleanup_date( self ) :

		# For each date over the maximum number, remove that date and
		# all loaded data.

		while ( len( self.arr_date ) > self.n_date_max ) :

			# Remove the least recently loaded date from the list
			# of loaded dates.

			self.arr_date = self.arr_date[1:]

			# Remove the timestamps and spin rates from the removed
			# date.

			rng = range( len( self.arr_spin_ind ) )

			self.arr_spin_t = [ self.arr_spin_t[i]
			                    for i in rng
			                    if  self.arr_spin_ind[i] != 0 ]

			self.arr_spin_w = [ self.arr_spin_w[i]
			                    for i in rng
			                    if  self.arr_spin_ind[i] != 0 ]

			# Remove the indices for the removed data and decrement
			# the remaining indices.

			self.arr_spin_ind = [ ind - 1
			                      for ind in self.arr_spin_ind
			                      if  ind != 0                 ]

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR CLEANING UP THE DATA DIRECTORY.
	#-----------------------------------------------------------------------

	def cleanup_file( self ) :

		# If there is no limit on the number files in the data
		# directory, abort (as there's nothing to be done).

		if ( self.n_file_max >= float( 'infinity' ) ) :
			return

		# Generate a list of the names of all spin data files.

		file_name = list( glob(
				  os.path.join( self.path, 'wi_k0_spha_*' ) ) )

		# If the number of files is less than or equal to the maximum,
		# abort (as nothing needs to be done).

		if ( len( file_name ) <= self.n_file_max ) :
			return

		# Determine the access time of each of the files, and then sort
		# the files in ascending value.

		file_time = [ os.path.getatime( fl ) for fl in file_name ]

		srt = sorted( range( len( file_time ) ),
		              key=file_time.__getitem__  )

		file_name = [ file_name[i] for i in srt ]
		file_time = [ file_time[i] for i in srt ]

		# Delete files so that the number of files equals the maximum
		# allowed.

		for f in range( len( file_name ) - self.n_file_max ) :
			os.remove( file_name[f] )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR SENDING INFORMATIONAL MESSAGES TO THE USER.
	#-----------------------------------------------------------------------

	def mesg_txt( self, mesg_typ, mesg_obj='' ) :

		# If this object is not associated with an instance of the
		# Janus "core", or if messaging has not been requested, abort.

		if ( ( self.core is None ) or ( not self.verbose ) ) :

			return

		# Emit a message signal (on behalf of the core) containing the
		# message parameters.

		self.core.emit( SIGNAL('janus_mesg'),
		                'spin', mesg_typ, mesg_obj )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR CHANGING THE MAXIMUM NUMBER OF FILES.
	#-----------------------------------------------------------------------

	def chng_n_file_max( self, val ) :

		# Check the maximum file number input to ensure it is a postive
		# integer. Change the maximum file number if it is. Otherwise,
		# raise an error.

		if ( ( val != float( 'inf' )  ) and
		     ( type( val ) is not int )     ) :

			raise ValueError( 'Max file number must be ' +
			                     'infinity or a positive integer.' )

			return

		if ( val < 0 ) :

			raise ValueError( 'Max file number cannot be ' +
			                                           'negative.' )

			return

		self.n_file_max = val

		self.cleanup_file( ) class mfi_arcv_lres( object ) :

	#-----------------------------------------------------------------------
	# DEFINE THE INITIALIZATION FUNCTION.
	#-----------------------------------------------------------------------

	def __init__( self, core=None, buf=None, tol=None,
	                    use_h2=None,
	                    n_file_max=float('inf'), n_date_max=None,
	                    path=None, verbose=None                   ) :

		# Save the arguments for later use.

		# Note.  The "n_file_max" argument is handled at the end of this
		#        function with a call of "chng_n_file_max".

		self.core       = core

		self.buf        = float( buf )      if ( buf is not None )\
		                                    else 3600.

		self.tol        = float( tol )      if ( tol is not None )\
		                                    else 0.

		self.n_date_max = int( n_date_max ) if ( n_date_max 
		                                    is not None ) else 40

		self.use_h2     = bool( use_h2 )    if (use_h2 is not None )\
		                                    else False

		self.path       = str( path )       if ( path  is not None )\
		                                    else os.path.join( 
		                                    os.path.dirname( __file__ ), 
		                                    'data', 'mfi', 'lres' )

		self.verbose    = bool( verbose )   if ( verbose is not None )\
		                                    else True

		# Validate the values of the "self.max_*" parameters and, if
		# necessary, provide values for them.

		if ( self.buf < 0 ) :
			raise ValueError( 'Time buffer cannot be negative.'    )


		if ( self.n_date_max < 0 ) :
			raise ValueError( 'Maximum number of dates ' +
			                                 'cannot be negative.' )

		# Initialize the array of dates loaded.

		self.date_str = array( [ ] )
		self.date_ind = array( [ ] )

		self.n_date = 0
		self.t_date = 0

		# Initialize the data arrays.

		self.mfi_t   = array( [ ] )
		self.mfi_b_x = array( [ ] )
		self.mfi_b_y = array( [ ] )
		self.mfi_b_z = array( [ ] )
		self.mfi_ind = array( [ ] )

		# Initialize "n_file_max".

		self.chng_n_file_max( n_file_max )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR LOADING (AND RETURNING) A range OF THE DATA.
	#-----------------------------------------------------------------------

	def load_rang( self, time_strt, dur_sec ) :

		# Compute the requested start and stop times as values, as
		# strings, and as "datetime" epochs.

		time_strt_val = calc_time_val( time_strt               )
		time_stop_val = calc_time_val( time_strt_val + dur_sec )

		time_strt_str = calc_time_str( time_strt_val )
		time_stop_str = calc_time_str( time_stop_val )

		time_strt_epc = calc_time_epc( time_strt_val )
		time_stop_epc = calc_time_epc( time_stop_val )

		# Construct an array of the dates requested.

		date_req = array( [ ] )

		date_i = ( calc_time_str( time_strt_val - self.buf ) )[0:10]
		time_i = calc_time_val( date_i + '/00:00:00.000' ) 

		while ( time_i < ( time_stop_val + self.buf ) ) :

			# Add the current date to the array of dates to be
			# loaded.

			date_req = append( date_req, [ date_i ] )

			# Move on to the next date.

			# Note.  This may look a bit odd, but may be necessary
			#        to avoid issues with leap seconds.  An
			#        additional leap-second concern is the posiblity
			#        of "date_req" containing duplicates, but that
			#        shouldn't be too much of an issue even if it
			#        does occur.

			time_i = time_i + 86400.
			date_i = ( calc_time_str( time_i ) )[0:10]
			time_i = calc_time_val( date_i + '/00:00:00.000' )

		# For each date in "date_i", load the data (if necessary).

		[ self.load_date( dt ) for dt in date_req ]

		# Identify and extract the requested range of Wind/MFI data.

		tk = where( ( self.mfi_t >= ( time_strt_epc -
		                              timedelta( 0, self.tol ) ) ) &
		            ( self.mfi_t <= ( time_stop_epc +
		                              timedelta( 0, self.tol ) ) )   )
		tk = tk[0]

		n_tk = len( tk )

		if ( n_tk <= 0 ) :

			self.mesg_txt( 'none' )

			ret_t   = array( [ ] )
			ret_b_x = array( [ ] )
			ret_b_y = array( [ ] )
			ret_b_z = array( [ ] )

		else :

			ret_t   = self.mfi_t[tk]
			ret_b_x = self.mfi_b_x[tk]
			ret_b_y = self.mfi_b_y[tk]
			ret_b_z = self.mfi_b_z[tk]

			srt = argsort( ret_t )

			ret_t   = ret_t[srt]
			ret_b_x = ret_b_x[srt]
			ret_b_y = ret_b_y[srt]
			ret_b_z = ret_b_z[srt]

		# Request a cleanup of the data loaded into this archive.

		self.cleanup_date( )

		# Return the requested range of Wind/MFI data.

		return ( list( ret_t ), list( ret_b_x ),
		                        list( ret_b_y ), list( ret_b_z ) )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR LOADING ALL DATA FROM DATE-SPECIFIED FILE.
	#-----------------------------------------------------------------------

	def load_date( self, date_str ) :

		# Determine whether or not the requested date has already been
		# loaded.  If it has, abort.

		if ( self.n_date > 0 ) :

			tk = where( self.date_str == date_str )[0]

			if ( len( tk ) > 0 ) :
				return

		# Extract the year, month, day, and day of year of the requested
		# date.

		year = int( date_str[0:4]  )
		mon  = int( date_str[5:7]  )
		day  = int( date_str[8:10] )

		# Determine the name of the file that contains data
		# from the requested date.

		str_year = date_str[0:4]
		str_mon  = date_str[5:7]
		str_day  = date_str[8:10]

		fl0 = 'wi_h0_mfi_' + str_year + str_mon + str_day + '_v??.cdf'

		fl0_path = os.path.join( self.path, fl0 )

		gb = glob( fl0_path )

		# If the file does not exist, attempt to download it.

		if ( len( gb ) > 0 ) :
			fl_path = gb[-1]
		else :
			try :
				self.mesg_txt( 'ftp', date_str )
				ftp = FTP_TLS( 'spdf.gsfc.nasa.gov' )
				ftp.login( )
				ftp.cwd( 'pub/data/wind/mfi/' )
				if ( self.use_h2 ) :
					ftp.cwd( 'mfi_h2/' )
				else :
					ftp.cwd( 'mfi_h0/' )
				ftp.cwd( str_year )
				ls = ftp.nlst( fl0 )
				fl = ls[-1]
				fl_path = os.path.join( self.path, fl )
				ftp.retrbinary( "RETR " + fl,
				           open( fl_path, 'wb' ).write )
			except :
				self.mesg_txt( 'fail', date_str )
				return

		# If the file now exists, try to load it; otherwise,
		# abort.

		self.mesg_txt( 'load', date_str )

		if ( os.path.isfile( fl_path ) ) :
			try :
				cdf = pycdf.CDF( fl_path )
			except :
				self.mesg_txt( 'fail', date_str )
				return
		else :
			self.mesg_txt( 'fail', date_str )
			return

		# Extract the data from the loaded file and select those data
		# which seem to have valid (versus fill) values.

		if ( self.use_h2 ) :

			# Extract the data from the loaded file.

			sub_t   = cdf['Epoch'][:,0]
			sub_b_x = cdf['BGSE'][:,0]
			sub_b_y = cdf['BGSE'][:,1]
			sub_b_z = cdf['BGSE'][:,2]

			sub_ind = tile( self.t_date, len( sub_t ) )

			# Select those data which seem to have valid (versus
			# fill) values.

			# TODO: Establish quality checks.

			n_tk = len( sub_t )
			tk   = arange( n_tk )

		else :

			# Extract the data from the loaded file.

			sub_t   = cdf['Epoch3'][:,0]
			sub_b_x = cdf['B3GSE'][:,0]
			sub_b_y = cdf['B3GSE'][:,1]
			sub_b_z = cdf['B3GSE'][:,2]
			sub_pnt = cdf['NUM3_PTS'][:,0]

			sub_ind = tile( self.t_date, len( sub_t ) )

			# Select those data which seem to have valid (versus
			# fill) values.

			tk   = where( sub_pnt > 0 )[0]
			n_tk = len( tk )

		# Copy the date associated with this file into and
		# array.

		new_date_str = [ date_str ]
		new_date_ind = [ self.t_date ]
		n_new_date = 1

		# Append any valid, newly-loaded data to the saved arrays.

		if ( n_tk > 0 ) :
			self.mfi_t   = append( self.mfi_t  , sub_t[tk]   )
			self.mfi_b_x = append( self.mfi_b_x, sub_b_x[tk] )
			self.mfi_b_y = append( self.mfi_b_y, sub_b_y[tk] )
			self.mfi_b_z = append( self.mfi_b_z, sub_b_z[tk] )
			self.mfi_ind = append( self.mfi_ind, sub_ind[tk] )

		# Append the array of loaded dates with the date(s) loaded in
		# this call of this function.

		self.date_str = append( self.date_str, new_date_str )
		self.date_ind = append( self.date_ind, new_date_ind )

		self.n_date += n_new_date
		self.t_date += n_new_date

		# Request a clean-up of the files in the data directory.

		self.cleanup_file( )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR CLEANING UP THIS ARCHIVE.
	#-----------------------------------------------------------------------

	def cleanup_date( self ) :

		# If the number of dates is less than or equal to the maximum,
		# abort (as nothing needs to be done).

		if ( self.n_date <= self.n_date_max ) :
			return

		# Delete dates (and all associated data) from this archive so
		# that the number of loaded dates equals the maximum allowed.

		n_rmv = self.n_date - self.n_date_max

		ind_min = self.t_date - self.n_date_max

		self.date_str = self.date_str[n_rmv:]
		self.date_ind = self.date_ind[n_rmv:]

		self.n_date -= n_rmv

		tk = where( self.mfi_ind >= ind_min )[0]

		self.mfi_t   = self.mfi_t[tk]
		self.mfi_b_x = self.mfi_b_x[tk]
		self.mfi_b_y = self.mfi_b_y[tk]
		self.mfi_b_z = self.mfi_b_z[tk]
		self.mfi_ind = self.mfi_ind[tk]

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR CLEANING UP THE DATA DIRECTORY.
	#-----------------------------------------------------------------------

	def cleanup_file( self ) :

		# If there is no limit on the number files in the data
		# directory, abort (as there's nothing to be done).

		if ( self.n_file_max >= float( 'inf' ) ) :
			return

		# Generate a list of the names of all files of the requested
		# type (as specified by the "use_*" keywords) in the data
		# directory.

		if ( self.use_h2 ) :
			file_name = list( glob(
			         os.path.join( self.path, 'wi_h2*' ) ) )
		else :
			file_name = list( glob(
			         os.path.join( self.path, 'wi_h0*' ) ) )

		# If the number of files is less than or equal to the maximum,
		# abort (as nothing needs to be done).

		if ( len( file_name )<= self.n_file_max ) :
			return

		# Determine the access time of each of the files, and then sort
		# the files in ascending value.

		file_time = [ os.path.getatime( fl )
		                     for fl in file_name    ]

		srt = sorted( range( len( file_time ) ),
		              key=file_time.__getitem__  )

		file_name = [ file_name[i] for i in srt ]
		file_time = [ file_time[i] for i in srt ]

#		srt = argsort( file_time )
#
#		file_name = file_name[srt]
#		file_time = file_time[srt]

		# Delete files so that the number of files equals the maximum
		# allowed.

		for f in range( len( file_name ) - self.n_file_max ) :
			os.remove( file_name[f] )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR SENDING INFORMATIONAL MESSAGES TO THE USER.
	#-----------------------------------------------------------------------

	def mesg_txt( self, mesg_typ, mesg_obj='' ) :

		# If this object is not associated with an instance of the
		# Janus "core", or if messaging has not been requested, abort.

		if ( ( self.core is None ) or ( not self.verbose ) ) :

			return

		# Emit a message signal (on behalf of the core) containing the
		# message parameters.

		self.core.emit( SIGNAL('janus_mesg'),
		                'mfi', mesg_typ, mesg_obj )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR CHANGING THE MAXIMUM NUMBER OF FILES.
	#-----------------------------------------------------------------------

	def chng_n_file_max( self, val ) :

		# Check the maximum file number input to ensure it is a postive
		# integer. Change the maximum file number if it is. Otherwise,
		# raise an error.

		if ( ( val != float( 'inf' ) ) and
		     ( type( val ) is not int     )     ) :

			raise ValueError( 'Max file number must be ' +
			                     'infinity or a positive integer.' )

			return

		if ( val < 0 ) :

			raise ValueError( 'Max file number cannot be ' +
			                                           'negative.' )

			return

		self.n_file_max = val

		self.cleanup_file( ) class mfi_arcv_hres( object ) :

	#-----------------------------------------------------------------------
	# DEFINE THE INITIALIZATION FUNCTION.
	#-----------------------------------------------------------------------

	def __init__( self, core=None, buf=None, tol=None,
	                    use_h2=None,
	                    n_file_max=float('inf'), n_date_max=None,
	                    path=None, verbose=None                   ) :

		# Save the arguments for later use.

		# Note.  The "n_file_max" argument is handled at the end of this
		#        function with a call of "chng_n_file_max".

		self.core       = core

		self.buf        = float( buf )      if ( buf is not None )\
		                                    else 3600.

		self.tol        = float( tol )      if ( tol is not None )\
		                                    else 0.

		self.n_date_max = int( n_date_max ) if ( n_date_max 
		                                    is not None ) else 40

		self.use_h2     = bool( use_h2 )    if (use_h2 is not None )\
		                                    else True

		self.path       = str( path )       if ( path  is not None )\
		                                    else os.path.join( 
		                                    os.path.dirname( __file__ ),
		                                    'data', 'mfi', 'hres' )

		self.verbose    = bool( verbose )   if ( verbose is not None )\
		                                    else True

		# Validate the values of the "self.max_*" parameters and, if
		# necessary, provide values for them.

		if ( self.buf < 0 ) :
			raise ValueError( 'Time buffer cannot be negative.'    )

		if ( self.n_date_max < 0 ) :
			raise ValueError( 'Maximum number of dates ' +
			                                 'cannot be negative.' )

		# Initialize the array of dates loaded.

		self.date_str = array( [ ] )
		self.date_ind = array( [ ] )

		self.n_date = 0
		self.t_date = 0

		# Initialize the data arrays.

		self.mfi_t   = array( [ ] )
		self.mfi_b_x = array( [ ] )
		self.mfi_b_y = array( [ ] )
		self.mfi_b_z = array( [ ] )
		self.mfi_ind = array( [ ] )

		# Initialize "n_file_max".

		self.chng_n_file_max( n_file_max )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR LOADING (AND RETURNING) A range OF THE DATA.
	#-----------------------------------------------------------------------

	def load_rang( self, time_strt, dur_sec ) :

		# Compute the requested start and stop times as values, as
		# strings, and as "datetime" epochs.

		time_strt_val = calc_time_val( time_strt               )
		time_stop_val = calc_time_val( time_strt_val + dur_sec )

		time_strt_str = calc_time_str( time_strt_val )
		time_stop_str = calc_time_str( time_stop_val )

		time_strt_epc = calc_time_epc( time_strt_val )
		time_stop_epc = calc_time_epc( time_stop_val )

		# Construct an array of the dates requested.

		date_req = array( [ ] )

		date_i = ( calc_time_str( time_strt_val - self.buf ) )[0:10]
		time_i = calc_time_val( date_i + '/00:00:00.000' ) 

		while ( time_i < ( time_stop_val + self.buf ) ) :

			# Add the current date to the array of dates to be
			# loaded.

			date_req = append( date_req, [ date_i ] )

			# Move on to the next date.

			# Note.  This may look a bit odd, but may be necessary
			#        to avoid issues with leap seconds.  An
			#        additional leap-second concern is the posiblity
			#        of "date_req" containing duplicates, but that
			#        shouldn't be too much of an issue even if it
			#        does occur.

			time_i = time_i + 86400.
			date_i = ( calc_time_str( time_i ) )[0:10]
			time_i = calc_time_val( date_i + '/00:00:00.000' )

		# For each date in "date_i", load the data (if necessary).

		[ self.load_date( dt ) for dt in date_req ]

		# Identify and extract the requested range of Wind/MFI data.

		tk = where( ( self.mfi_t >= ( time_strt_epc -
		                              timedelta( 0, self.tol ) ) ) &
		            ( self.mfi_t <= ( time_stop_epc +
		                              timedelta( 0, self.tol ) ) )   )
		tk = tk[0]

		n_tk = len( tk )

		if ( n_tk <= 0 ) :

			self.mesg_txt( 'none' )

			ret_t   = array( [ ] )
			ret_b_x = array( [ ] )
			ret_b_y = array( [ ] )
			ret_b_z = array( [ ] )

		else :

			ret_t   = self.mfi_t[tk]
			ret_b_x = self.mfi_b_x[tk]
			ret_b_y = self.mfi_b_y[tk]
			ret_b_z = self.mfi_b_z[tk]

			srt = argsort( ret_t )

			ret_t   = ret_t[srt]
			ret_b_x = ret_b_x[srt]
			ret_b_y = ret_b_y[srt]
			ret_b_z = ret_b_z[srt]

		# Request a cleanup of the data loaded into this archive.

		self.cleanup_date( )

		# Return the requested range of Wind/MFI data.

		return ( list( ret_t ), list( ret_b_x ),
		                        list( ret_b_y ), list( ret_b_z ) )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR LOADING ALL DATA FROM DATE-SPECIFIED FILE.
	#-----------------------------------------------------------------------

	def load_date( self, date_str ) :

		# Determine whether or not the requested date has already been
		# loaded.  If it has, abort.

		if ( self.n_date > 0 ) :

			tk = where( self.date_str == date_str )[0]

			if ( len( tk ) > 0 ) :

				return

		# Extract the year, month, day, and day of year of the requested
		# date.

		year = int( date_str[0:4]  )
		mon  = int( date_str[5:7]  )
		day  = int( date_str[8:10] )

		# Determine the name of the file that contains data
		# from the requested date.

		str_year = date_str[0:4]
		str_mon  = date_str[5:7]
		str_day  = date_str[8:10]

		fl0 = 'wi_h2_mfi_' + str_year + str_mon + str_day + '_v??.cdf'

		fl0_path = os.path.join( self.path, fl0 )

		gb = glob( fl0_path )

		# If the file does not exist, attempt to download it.

		if ( len( gb ) > 0 ) :
			fl_path = gb[-1]
		else :
			try :
				self.mesg_txt( 'ftp', date_str )
				ftp = FTP_TLS( 'spdf.gsfc.nasa.gov' )
				ftp.login( )
				ftp.cwd( 'pub/data/wind/mfi/' )
				if ( self.use_h2 ) :
					ftp.cwd( 'mfi_h2/' )
				else :
					ftp.cwd( 'mfi_h0/' )
				ftp.cwd( str_year )
				ls = ftp.nlst( fl0 )
				fl = ls[-1]
				fl_path = os.path.join( self.path, fl )
				ftp.retrbinary( "RETR " + fl,
				           open( fl_path, 'wb' ).write )
			except :
				self.mesg_txt( 'fail', date_str )
				return

		# If the file now exists, try to load it; otherwise,
		# abort.

		self.mesg_txt( 'load', date_str )

		if ( os.path.isfile( fl_path ) ) :
			try :
				cdf = pycdf.CDF( fl_path )
			except :
				self.mesg_txt( 'fail', date_str )
				return
		else :
			self.mesg_txt( 'fail', date_str )
			return

		# Extract the data from the loaded file and select those data
		# which seem to have valid (versus fill) values.

		if ( self.use_h2 ) :

			# Extract the data from the loaded file.

			sub_t   = cdf['Epoch'][:,0]
			sub_b_x = cdf['BGSE'][:,0]
			sub_b_y = cdf['BGSE'][:,1]
			sub_b_z = cdf['BGSE'][:,2]

			sub_ind = tile( self.t_date, len( sub_t ) )

			# Select those data which seem to have valid (versus
			# fill) values.

			# TODO: Establish quality checks.

			n_tk = len( sub_t )
			tk   = arange( n_tk )

		else :

			# Extract the data from the loaded file.

			sub_t   = cdf['Epoch3'][:,0]
			sub_b_x = cdf['B3GSE'][:,0]
			sub_b_y = cdf['B3GSE'][:,1]
			sub_b_z = cdf['B3GSE'][:,2]
			sub_pnt = cdf['NUM3_PTS'][:,0]

			sub_ind = tile( self.t_date, len( sub_t ) )

			# Select those data which seem to have valid (versus
			# fill) values.

			tk   = where( sub_pnt > 0 )[0]
			n_tk = len( tk )

		# Copy the date associated with this file into and
		# array.

		new_date_str = [ date_str ]
		new_date_ind = [ self.t_date ]
		n_new_date = 1

		# Append any valid, newly-loaded data to the saved arrays.

		if ( n_tk > 0 ) :
			self.mfi_t   = append( self.mfi_t  , sub_t[tk]   )
			self.mfi_b_x = append( self.mfi_b_x, sub_b_x[tk] )
			self.mfi_b_y = append( self.mfi_b_y, sub_b_y[tk] )
			self.mfi_b_z = append( self.mfi_b_z, sub_b_z[tk] )
			self.mfi_ind = append( self.mfi_ind, sub_ind[tk] )

		# Append the array of loaded dates with the date(s) loaded in
		# this call of this function.

		self.date_str = append( self.date_str, new_date_str )
		self.date_ind = append( self.date_ind, new_date_ind )

		self.n_date += n_new_date
		self.t_date += n_new_date

		# Request a clean-up of the files in the data directory.

		self.cleanup_file( )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR CLEANING UP THIS ARCHIVE.
	#-----------------------------------------------------------------------

	def cleanup_date( self ) :

		# If the number of dates is less than or equal to the maximum,
		# abort (as nothing needs to be done).

		if ( self.n_date <= self.n_date_max ) :
			return

		# Delete dates (and all associated data) from this archive so
		# that the number of loaded dates equals the maximum allowed.

		n_rmv = self.n_date - self.n_date_max

		ind_min = self.t_date - self.n_date_max

		self.date_str = self.date_str[n_rmv:]
		self.date_ind = self.date_ind[n_rmv:]

		self.n_date -= n_rmv

		tk = where( self.mfi_ind >= ind_min )[0]

		self.mfi_t   = self.mfi_t[tk]
		self.mfi_b_x = self.mfi_b_x[tk]
		self.mfi_b_y = self.mfi_b_y[tk]
		self.mfi_b_z = self.mfi_b_z[tk]
		self.mfi_ind = self.mfi_ind[tk]

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR CLEANING UP THE DATA DIRECTORY.
	#-----------------------------------------------------------------------

	def cleanup_file( self ) :

		# If there is no limit on the number files in the data
		# directory, abort (as there's nothing to be done).

		if ( self.n_file_max >= float( 'inf' ) ) :
			return

		# Generate a list of the names of all files of the requested
		# type (as specified by the "use_*" keywords) in the data
		# directory.

		if ( self.use_h2 ) :
			file_name = list( glob(
			         os.path.join( self.path, 'wi_h2*' ) ) )
		else :
			file_name = list( glob(
			         os.path.join( self.path, 'wi_h0*' ) ) )

		# If the number of files is less than or equal to the maximum,
		# abort (as nothing needs to be done).

		if ( len( file_name )<= self.n_file_max ) :
			return

		# Determine the access time of each of the files, and then sort
		# the files in ascending value.

		file_time = [ os.path.getatime( fl )
		                     for fl in file_name    ]

		srt = sorted( range( len( file_time ) ),
		              key=file_time.__getitem__  )

		file_name = [ file_name[i] for i in srt ]
		file_time = [ file_time[i] for i in srt ]

#		srt = argsort( file_time )
#
#		file_name = file_name[srt]
#		file_time = file_time[srt]

		# Delete files so that the number of files equals the maximum
		# allowed.

		for f in range( len( file_name ) - self.n_file_max ) :
			os.remove( file_name[f] )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR SENDING INFORMATIONAL MESSAGES TO THE USER.
	#-----------------------------------------------------------------------

	def mesg_txt( self, mesg_typ, mesg_obj='' ) :

		# If this object is not associated with an instance of the
		# Janus "core", or if messaging has not been requested, abort.

		if ( ( self.core is None ) or ( not self.verbose ) ) :

			return

		# Emit a message signal (on behalf of the core) containing the
		# message parameters.

		self.core.emit( SIGNAL('janus_mesg'),
		                'mfi', mesg_typ, mesg_obj )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR CHANGING THE MAXIMUM NUMBER OF FILES.
	#-----------------------------------------------------------------------

	def chng_n_file_max( self, val ) :

		# Check the maximum file number input to ensure it is a postive
		# integer. Change the maximum file number if it is. Otherwise,
		# raise an error.

		if ( ( val != float( 'inf' ) ) and
		     ( type( val ) is not int     )     ) :

			raise ValueError( 'Max file number must be ' +
			                     'infinity or a positive integer.' )

			return

		if ( val < 0 ) :

			raise ValueError( 'Max file number cannot be ' +
			                                           'negative.' )

			return

		self.n_file_max = val

		self.cleanup_file( ) def round_sig( val, sig ) :

	if ( val is None ) :

		return None

	elif ( val == 0. ) :

		return 0.

	else :

		return round( val,
		              sig - int( floor( log10( abs( val ) ) ) ) - 1 ) class fc_spec( ) :

	#-----------------------------------------------------------------------
	# DEFINE THE INITIALIZATION FUNCTION.
	#-----------------------------------------------------------------------

	def __init__( self, n_bin,
	              elev=None, azim=None, volt_cen=None, volt_del=None,
	              time=None, curr=None, rot=3., curr_jump=100.,
	              curr_min=1.                                            ) :

		self._n_cup     = 2
		self._n_dir     = 20
		self._n_bin     = n_bin
		self._time      = time
		self._curr_jump = curr_jump
		self._curr_min  = curr_min

		if ( elev == None ) :
			elev = [ None for c in range( self._n_cup ) ]

		if ( azim == None ) :
			azim = [ [ None for d in range( self._n_dir )       ]
						for c in range(self._n_cup) ]

		if ( volt_cen == None ) :
			volt_cen = [ [ None for b in range( self._n_bin )   ]
						for c in range(self._n_cup) ]

		if ( volt_del == None ) :
			volt_del = [ [ None for b in range( self._n_bin )   ]
						for c in range(self._n_cup) ]		

		if ( curr == None ) :
			curr = [ [ [ None for b in range( self._n_bin ) ]
			                  for d in range( self._n_dir ) ]
			                  for c in range( self._n_cup ) ]

		#if ( len( elev ) != self._n_cup ) :
		#	raise TypeError( 'List elev must be length n_cup.' )

		self.arr = [[[ fc_dat( spec=self,
		                       elev=elev[c],
		                       azim=azim[c][d],
		                       volt_cen=volt_cen[c][b], 
		                       volt_del=volt_del[c][b], 
		                       curr=curr[c][d][b]) 
		               for b in range(self._n_bin) ]
		               for d in range(self._n_dir) ]
		               for c in range(self._n_cup) ]

	        # Define the time offsets for the individual data in the
                # spectrum. 

                # Note.  These calibration parameters have been taken from the
                #        "calfile_mode_params" file in the old IDL code.  The
                #        meaning of the "integ" values is unknown, but has been
                #        assumed to represent integration times (possibly
                #        expressed in terms of the number of modulator-voltage
                #        oscillations).

                self._offset = [
                     [ 0.105350, 0.175650, 0.245850, 0.316150, 0.386350,
                       0.456600, 0.526850, 0.597050, 0.667350, 0.737550,
                       0.807850, 0.878050, 0.948350, 1.01855 , 1.08885 ,
                       1.15905 , 1.22930 , 1.29955 , 1.91670 , 2.54390   ],
                     [ 0.366300, 0.993500, 1.61065 , 1.68085 , 1.75115 ,
                       1.82135 , 1.89165 , 1.96185 , 2.03215 , 2.10235 ,
                       2.17265 , 2.24285 , 2.31305 , 2.38335 , 2.45355 ,
                       2.52385 , 2.59405 , 2.66435 , 2.73455 , 2.80485   ]  ]

                self._integ = [
                     [  6,  6, 6, 6, 6, 6, 6, 6,  6,  6,
                        6,  6, 6, 6, 6, 6, 6, 6, 24, 24  ],
                     [ 24, 24, 6, 6, 6, 6, 6, 6,  6,  6,
                        6,  6, 6, 6, 6, 6, 6, 6,  6,  6  ]  ]



		self.set_rot( rot )

		self._eff_deg  = [ float(i) for i in range(91) ]
		self._eff_area = [
		      33.820000, 33.830000, 33.830000, 33.820000, 33.810000,
		      33.800000, 33.780000, 33.770000, 33.760000, 33.740000,
		      33.720000, 33.690000, 33.680000, 33.640000, 33.620000,
		      33.590000, 33.550000, 33.510000, 33.470000, 33.430000,
		      33.387000, 33.341000, 33.293000, 33.243000, 33.182000,
		      33.128000, 33.063000, 32.996000, 32.928000, 32.859000,
		      32.778000, 32.707000, 32.616000, 32.535000, 32.445000,
		      32.346000, 32.249000, 32.001000, 31.615000, 31.140000,
		      30.588200, 29.971700, 29.300000, 28.570000, 27.790000,
		      26.940000, 25.869997, 24.650000, 23.299996, 21.830000,
		      20.259996, 18.590001, 16.829996, 14.970001, 13.019996,
		      10.990001, 8.8779956, 6.6850016, 4.5209962, 2.5750016,
		      0.96799784, 0.0053996863, 0.0000000, 0.0000000, 0.0000000,
		      0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,
		      0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,
		      0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,
		      0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,
		      0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,
		      0.0000000                                              ]

		# Validate the data in the spectrum.

		self.validate( )

		#List of shape [[[n_bin] n_dir] n_cup]

	def __getitem__(self, key ) :

		if ( key == 'curr_min' ) :
			return self._curr_min
		elif ( key == 'n_cup' ) :
			return self._n_cup
		elif ( key == 'n_dir' ) :
			return self._n_dir
		elif ( key == 'n_bin') :
			return self._n_bin
		elif ( key == 'time') :
			return self._time
		elif ( key == 'eff_deg') :
			return self._eff_deg
		elif ( key == 'eff_area') :
			return self._eff_area
		elif ( key == 'elev' ) :
			return [  self.arr[c][0][0]['elev'] 
					for c in range(self._n_cup)]
		elif ( key == 'azim' ) :
			return [[ self.arr[c][d][0]['azim'] 
					for d in range(self._n_dir) ]
					for c in range(self._n_cup) ]
		elif ( key == 'volt_strt' ) :
			return  [[ self.arr[c][0][b]['volt_strt'] 
					for b in range(self._n_bin)] 
					for c in range(self._n_cup)]
		elif ( key == 'volt_stop' ) :
			return  [[ self.arr[c][0][b]['volt_stop'] 
					for b in range(self._n_bin)] 
					for c in range(self._n_cup)]
		elif ( key == 'volt_cen' ) :
			return  [[ self.arr[c][0][b]['volt_cen'] 
					for b in range(self._n_bin)] 
					for c in range(self._n_cup)]
		elif ( key == 'volt_del' ) :
			return  [[ self.arr[c][0][b]['volt_del'] 
					for b in range(self._n_bin)] 
					for c in range(self._n_cup)]
		elif ( key == 'vel_strt' ) :
			return  [[ self.arr[c][0][b]['vel_strt'] 
					for b in range(self._n_bin)] 
					for c in range(self._n_cup)]
		elif ( key == 'vel_stop' ) :
			return  [[ self.arr[c][0][b]['vel_stop'] 
					for b in range(self._n_bin)] 
					for c in range(self._n_cup)]
		elif ( key == 'vel_cen' ) :
			return  [[ self.arr[c][0][b]['vel_cen'] 
					for b in range(self._n_bin)] 
					for c in range(self._n_cup)]
		elif ( key == 'vel_del' ) :
			return  [[ self.arr[c][0][b]['vel_del'] 
					for b in range(self._n_bin)] 
					for c in range(self._n_cup)]
		elif ( key == 'curr' ) :
			return [ [ [ self.arr[c][d][b]['curr']
			             for b in range( self._n_bin ) ]
			             for d in range( self._n_dir ) ]
			             for c in range( self._n_cup ) ]
		elif ( key == 'curr_valid' ) :
			return [ [ [ self.arr[c][d][b]['curr_valid']
			             for b in range( self._n_bin ) ]
			             for d in range( self._n_dir ) ]
			             for c in range( self._n_cup ) ]
		elif ( key == 'curr_flat' ) :
			return [ self.arr[c][d][b]['curr']
			         for b in range( self._n_bin )
			         for d in range( self._n_dir )
			         for c in range( self._n_cup ) ]

		elif ( key == 'curr_jump' ) :
			return self._curr_jump

		elif ( key == 'curr_min' ) :
			return self._curr_min
		elif ( key == 'rot' ) :
			return self._rot
		elif ( key == 'dur' ) :
			return self._dur
		else :
			raise KeyError( 'Invalid key for "fc_spec".' )

	def __setitem__( self, key, val ) :		

		raise KeyError( 'Reassignment not permitted except through'
		                                    + ' "set_*" functions.' )

	def validate( self ) :

		# Validate each datum in the spectrum.

		# Note.  Each datum should already contain a Boolean parameter,
		#        "valid", that is pre-valued to indicate whether all
		#        necessary values have been provided.

		for c in range( self._n_cup ) :

			for d in range( self._n_dir ) :

				for b in range( self._n_bin - 1 ) :

					# If the datum is not valid on its own
					# (e.g., a parameter value is missing),
					# move on to the next datum (since
					# nothing more needs to be done).

					if ( not self.arr[c][d][b]['valid'] ) :
						continue

					# If the current is too low, mark it as
					# invalid.

					if ( self.arr[c][d][b]['curr'] <
					                    self['curr_min'] ) :
						self.arr[c][d][b]._valid = False
						continue

					# If the bin appears to be an isolated
					# jump, mark it as invalid.

					if ( ( b == 0                     ) and
					     ( self.arr[c][d][1]['curr']
                                                               is not None ) and
					     ( self.arr[c][d][0]['curr']
					        > self['curr_jump']
					        * self.arr[c][d][1]['curr']) ) :
                                                self.arr[c][d][0]._valid = False

					elif ( ( b == ( self._n_bin - 1 )  ) and
					       ( self.arr[c][d][-2]['curr']
                                                               is not None ) and
					       ( self.arr[c][d][-1]['curr']
					          > self['curr_jump']
					       * self.arr[c][d][-2]['curr']) ) :
                                                self.arr[c][d][-1]._valid = False

					elif ( ( self.arr[c][d][b-1]['curr']
					                       is not None ) and
					       ( self.arr[c][d][b+1]['curr']
					                       is not None ) and
					       ( self.arr[c][d][b]['curr']
					           > self['curr_jump']
					     * self.arr[c][d][b-1]['curr'] ) and 
					       ( self.arr[c][d][b]['curr']
					           > self['curr_jump']
					     * self.arr[c][d][b+1]['curr'] ) ) :
						self.arr[c][d][b]._valid = False


        #-----------------------------------------------------------------------
        # DEFINE THE FUNCTION FOR CALCULATING THE TIMESTAMP OF A SINGLE DATUM.
        #-----------------------------------------------------------------------

        def calc_time( self, c, d, b ) :

                # Compute and return the timestamp of the specified datum.  If
                # this cannot be done (due to missing information), return
                # "None".

		if ( ( self._time is None ) or ( self._rot  is None ) ) :
			return

		else :

			return ( self._time
                                     + timedelta( seconds=(self._rot*b) )
                                     + timedelta( seconds=self._offset[c][d] ) )

        #-----------------------------------------------------------------------
        # DEFINE THE FUNCTION FOR MAKING A TIMESTAMP FOR EACH DATUM.
        #-----------------------------------------------------------------------

        def make_time( self ) :

                # Compute and apply each datum's timestamp.

                for c in range ( self._n_cup ) :

                        for d in range ( self._n_dir ) :

                                for b in range ( self._n_bin ) :

                                        self.arr[c][d][b]._time \
                                                     = self.calc_time( c, d, b )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION SETTING THE ROTATION PERIOD.
	#-----------------------------------------------------------------------

	def set_rot( self, rot ) :

		# Update the rotation period and the duration of this spectrum.

		self._rot = rot
		self._dur = rot * self['n_bin']

                # Update the timestamps of the individual data in this spectrum.

                self.make_time( )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR CALC'ING EXPECTED CURRENT FROM A POPULATION.
	#-----------------------------------------------------------------------

	def calc_curr( self, m, q, v0, n, dv, w) :

		# Return a 3-D list with the calculated current for each bin in
		# the spectrum.

		return [ [ [ self.arr[c][d][b].calc_curr( 
		                    m, q, v0, n, dv, w      )
		                    for b in range( self['n_bin'] ) ]
		                    for d in range( self['n_dir'] ) ]
		                    for c in range( self['n_cup'] ) ]

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR CALCULATING TOTAL CURRENT IN A GIVEN WINDOW.
	#-----------------------------------------------------------------------

	def calc_tot_curr( self, c, d, b, win=1 ) :

		# Validate the cup, direction, and bin indices.

		if ( ( c < 0 ) or ( c >= self['n_cup'] ) ) :
			raise ValueError( 'Cup index out of range.' )

		if ( ( d < 0 ) or ( d >= self['n_dir'] ) ) :
			raise ValueError( 'Direction index out of range.' )

		if ( ( b < 0 ) or ( b >= self['n_bin'] ) ) :
			raise ValueError( 'Direction index out of range.' )

		# Validate the window size.

		if ( ( win < 1 ) or ( b + win > self['n_bin'] ) ) :
			raise ValueError( 'Window out of range.' ) 

		# Return the total (valid) current in the specified window.

		return sum( [ self.arr[c][d][b+w]['curr']
		                                       for w in range( win ) ] )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION TO FIND THE INDEX OF WINDOW WITH MAXIMUM CURRENT
	#-----------------------------------------------------------------------

	def find_max_curr( self, c, d, win=1 ) :

		# Validate the cup and direction indices.

		if ( ( c < 0 ) or ( c >= self['n_cup'] ) ) :
			raise ValueError( 'Cup index out of range.' )

		if ( ( d < 0 ) or ( d >= self['n_dir'] ) ) :
			raise ValueError( 'Direction index out of range.' )

		# Validate the window size.

		if ( ( win < 1 ) or ( win > self['n_bin'] ) ) :
			raise ValueError( 'Window out of range.' ) 

		# Search the specified direction for the "win"-bin range that
		# contains the maximum total current.

		b_max    = 0
		curr_max = 0.

		for b in range( 0, self['n_bin'] - win + 1 ) :

			curr = sum( [ self.arr[c][d][b+w]['curr']
			                               for w in range( win ) ] )

			if ( curr > curr_max ) :
				b_max    = b
				curr_max = curr

		# Return the location of the window with the maximum current.

		return b_max

        #-----------------------------------------------------------------------
	# DEFINE THE FUNCTION TO ASSIGN THE MAGNETIC FIELD TO EACH DATUM. 
	#-----------------------------------------------------------------------

	def set_mag( self, mfi_t, mfi_b_x, mfi_b_y, mfi_b_z ) :

		mfi_s = [ ( t - mfi_t[0] ).total_seconds( ) for t in mfi_t ]

		fnc_b_x = interp1d( mfi_s, mfi_b_x )
		fnc_b_y = interp1d( mfi_s, mfi_b_y )
		fnc_b_z = interp1d( mfi_s, mfi_b_z )

		try :

			for c in range( self['n_cup'] ) :

				for d in range( self['n_dir'] ) :

					for b in range( self['n_bin'] ) :

						s = ( self.arr[c][d][b]['time']
                                                    - mfi_t[0] ).total_seconds( )

						b_x = fnc_b_x( s )
						b_y = fnc_b_y( s )
						b_z = fnc_b_z( s )

						self.arr[c][d][b].set_mag( ( b_x
                                                                           , b_y
                                                                           , b_z
                                                                             ) )

		except :

			avg_b_x = sum( mfi_b_x ) / float( len( mfi_b_x ) )
			avg_b_y = sum( mfi_b_y ) / float( len( mfi_b_y ) )
			avg_b_z = sum( mfi_b_z ) / float( len( mfi_b_z ) )

			for c in range( self['n_cup'] ) :

                                for d in range( self['n_dir'] ) :

                                        for b in range( self['n_bin'] ) :

                                                self.arr[c][d][b].set_mag( ( avg_b_x,
                                                                             avg_b_y,
                                                                             avg_b_z ) ) class plas( object ) :

	#-----------------------------------------------------------------------
	# DEFINE THE INITIALIZATION FUNCTION.
	#-----------------------------------------------------------------------

	def __init__( self, time=None, enforce=False ) :

		self.time = time

		self.arr_spec = [ ]
		self.arr_pop  = [ ]

		self.covar = None

		self.v0_x = None
		self.v0_y = None
		self.v0_z = None

		self.sig_v0_x = None
		self.sig_v0_y = None
		self.sig_v0_z = None

		self.b0_x = None
		self.b0_y = None
		self.b0_z = None

		self.enforce = bool( enforce )

	#-----------------------------------------------------------------------
	# DEFINE THE COMPARISON FUNCTION.
	#-----------------------------------------------------------------------

	def __cmp__( self, other ) :

		if ( type( self ) != type( other ) ) :
			raise TypeError( 'Cannot compare dissimilar types.' )

		if ( self.time is None ) :
			if ( other.time is None ) :
				return 0
			else :
				return -1
		else :
			if ( other.time is None ) :
				return 1
			else :
				if   ( self.time <  other.time ) :
					return -1
				elif ( self.time == other.time ) :
					return  0
				else :
					return  1

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR PARSING A KEY.
	#-----------------------------------------------------------------------

	def parse( self, key ) :

		# Initialize the onary of return variables.

		elem = { 'param':None,
		         'sigma':None,
		          'comp':None,
		          'spec':None,
		           'pop':None  }

		# Attempting to split the "key" string into substrings based on
		# the "_" token.  If this fails, abort.

		try :
#			arr=arr.lower()
			arr = key.split( '_' )
			for i in range(len(arr)):
				arr[i] = arr[i].lower()
		except :
			return None

		# For each element from the key, attempt to identify and record
		# what it represents.  If this fails for any given element or if
		# elements are in conflict, abort. The parameters, components
		# and sigma are all changed to lower case independent of the
		# type of case used for input.

		# Note.  Additional verification is required for "ret['pop']" at
		#        the end of this loop.  It's value cannot be confirmed
		#        to be a valid population until the species has been
		#        established (and the key need not have the species
		#        identifier precede that for the population).

		for e in arr :

			if ( e.lower() in PARAM ) :

				if ( elem['param'] is None ) :
					elem['param'] = e.lower()
				else :
					return None

			elif ( e.lower() in COMP ) :

				if ( elem['comp'] is None ) :
					elem['comp'] = e.lower()
				else :
					return None

			elif ( e.lower() in SIGMA ) :

				if ( elem['sigma'] is None ) :
					elem['sigma'] = e.lower()
				else :
					return None

			elif ( self.get_spec( e ) is not None ) :

				if ( elem['spec'] is None ) :
					elem['spec'] = e
				else :
					return None

			else :

				if ( elem['pop'] is None ) :
					elem['pop'] = e
				else :
					return None

		if ( elem['pop'] is not None ) :

			if ( elem['spec'] is None ) :
				return None

			if ( self.get_pop(
			                 elem['spec'], elem['pop'] ) is None ) :
				return None

		return elem

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR RETRIEVING THE VALUE OF A GIVEN KEY.
	#-----------------------------------------------------------------------

	def __getitem__( self, key ) :
		
		# Attempt to parse the key.  If this fails, abort.

		elem = self.parse( key )

		if ( elem is None ) :
			return None

		# If no parameter has been specified but a species (and possibly
		# a population) has, attempt to return the corresponding object;
		# otherwise, abort.

		if ( elem['param'] is None ) :

			if ( elem['spec'] is None ) :

				return None

			else :

				if ( elem['pop'] is None ) :
					return self.get_spec( elem['spec'] )
				else :
					return self.get_pop( elem['spec'],
					                     elem['pop']   )

		# If the specified parameter is one stored by the "plas" class,
		# attempt to return its value.  If the component is invalid for
		# the specified parameter, abort (returning "None").

		if   ( elem['param'] == 'time' ) :

			return self.time

		elif ( elem['param'] == 'b0' ) :

			if ( elem['sigma'] is None ) :

				if ( ( elem['comp'] is None  ) or
				     ( elem['comp'] == 'mag' )    ) :
					return self.get_b0_mag( )
				elif ( elem['comp'] == 'vec' ) :
					return self.get_b0_vec( )
				elif ( elem['comp'] == 'hat' ) :
					return self.get_b0_hat( )
				elif ( elem['comp'] == 'x' ) :
					return self.b0_x
				elif ( elem['comp'] == 'y' ) :
					return self.b0_y
				elif ( elem['comp'] == 'z' ) :
					return self.b0_z
				else :
					return None

			else :

				return None

		elif ( elem['param'] == 'v0' ) :

			if ( elem['sigma'] is None ) :

				if ( ( elem['comp'] is None  ) or
				     ( elem['comp'] == 'mag' )    ) :
					return self.get_v0_mag( )
				elif ( elem['comp'] == 'vec' ) :
					return self.get_v0_vec( )
				elif ( elem['comp'] == 'x' ) :
					return self.v0_x
				elif ( elem['comp'] == 'y' ) :
					return self.v0_y
				elif ( elem['comp'] == 'z' ) :
					return self.v0_z
				else :
					return None

			else :

				if   ( elem['comp'] == 'x' ) :
					return self.sig_v0_x
				elif ( elem['comp'] == 'y' ) :
					return self.sig_v0_y
				elif ( elem['comp'] == 'z' ) :
					return self.sig_v0_z
				else :
					return None

		# Note.  If this point is reached, the parameter is one to be
		#        handled by the species or population.

		# If no species has been specified, abort.

		if ( elem['spec'] is None ) :
			return None

		# Convert "elem" into a string of standard form for the "spec"
		# and "pop" classes.

		arg = elem['param']

		if ( elem['comp'] is not None ) :
			arg = arg + '_' + elem['comp']

		if ( elem['sigma'] is not None ) :
			arg = 'sig_' + arg

		# Pass the string to the either the "spec" or "pop" class for
		# processing.  If an error is raised, catch it and abort quietly
		# (i.e., return "None").

		if ( elem['pop'] is None ) :

			try :
				return self.get_spec( elem['spec'] )[arg]
			except :
				return None

		else :

			try :
				return self.get_pop(
				                elem['spec'], elem['pop'] )[arg]
			except :
				return None

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR ASSIGNING A GIVEN VALUE TO A GIVEN KEY.
	#-----------------------------------------------------------------------

	def __setitem__( self, key, value ) :

		# Based on the "key" in question, validate the "value".  If it
		# is valid, store the new value (and make any appropriate
		# changes to other parameters).
		# Change all keys to lower case independent of the input type
		# used.
		
		key=key.lower()

		if ( key == 'time' ) :

			if ( value is None ) :
				self.time = None
			elif ( type( value ) == datetime ) :
				self.time = value
			else :
				raise TypeError(
				     'Type "datetime" required for timestamp.' )

		elif ( key == 'v0_x' ) :

			self.v0_x = None

			if ( value is not None ) :
				self.v0_x = float( value )

		elif ( key == 'v0_y' ) :

			self.v0_y = None

			if ( value is not None ) :

				self.v0_y = float( value )

		elif ( key == 'v0_z' ) :

			self.v0_z = None

			if ( value is not None ) :

				self.v0_z = float( value )

		elif ( key == 'v0_vec' ) :

			try :
				l = len( value )
			except :
				l = 0

			if ( l != 3 ) :
				raise TypeError( 'Array of length 3 required.' )
			else :
				if ( value[0] is not None ) :
					self.v0_x = float( value[0] )
				if ( value[1] is not None ) :
					self.v0_y = float( value[1] )
				if ( value[2] is not None ) :
					self.v0_z = float( value[2] )

		elif ( key == 'sig_v0_x' ) :

			self.sig_v0_x = None

			if ( value is not None ) :

				self.sig_v0_x = float( value )

		elif ( key == 'sig_v0_y' ) :

			self.sig_v0_y = None

			if ( value is not None ) :

				self.sig_v0_y = float( value )

		elif ( key == 'sig_v0_z' ) :

			self.sig_v0_z = None

			if ( value is not None ) :

				self.sig_v0_z = float( value )

		elif   ( key == 'b0_x' ) :

			self.b0_x = None

			if ( value is not None ) :

				self.b0_x = float( value )

		elif ( key == 'b0_y' ) :

			self.b0_y = None

			if ( value is not None ) :

				self.b0_y = float( value )

		elif ( key == 'b0_z' ) :

			self.b0_z = None

			if ( value is not None ) :

				self.b0_z = float( value )

		elif ( key == 'b0_vec' ) :

			try :
				l = len( value )
			except :
				l = 0

			if ( l != 3 ) :
				raise TypeError( 'Array of length 3 required.' )
			else :
				if ( value[0] is not None ) :
					self.b0_x = float( value[0] )
				if ( value[1] is not None ) :
					self.b0_y = float( value[1] )
				if ( value[2] is not None ) :
					self.b0_z = float( value[2] )

		else :

			raise KeyError( 'Invalid key.' )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR GETTING THE MAGNITUDE OF THE MAGNETIC FIELD.
	#-----------------------------------------------------------------------

	def get_b0_mag( self ) :

		# If any of the components of the magnetic field are undefined,
		# abort (returning "None").

		if ( ( self.b0_x is None ) or ( self.b0_y is None ) or
		     ( self.b0_z is None )                             ) :

			return None

		# Calculate and return the magnitude.

		return sqrt( self.b0_x**2 + self.b0_y**2 + self.b0_z**2 )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR GETTING THE VECTOR OF THE MAGNETIC FIELD.
	#-----------------------------------------------------------------------

	def get_b0_vec( self ) :

		# If any of the components of the magnetic field are undefined,
		# abort (returning "None").

		if ( ( self.b0_x is None ) or ( self.b0_y is None ) or
		     ( self.b0_z is None )                             ) :

			return None

		# Return the vector.

		return ( self.b0_x, self.b0_y, self.b0_z )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR GETTING THE UNIT VECTOR OF THE MAGNETIC FIELD.
	#-----------------------------------------------------------------------

	def get_b0_hat( self ) :

		# If any of the components of the magnetic field are undefined,
		# abort (returning "None").

		if ( ( self.b0_x is None ) or ( self.b0_y is None ) or
		     ( self.b0_z is None )                             ) :

			return None

		# Return the unit vector.

		b0 =  sqrt( self.b0_x**2 + self.b0_y**2 + self.b0_z**2 )

		return ( self.b0_x / b0, self.b0_y / b0, self.b0_z / b0 )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR GETTING THE MAGNITUDE OF THE VELOCITY.
	#-----------------------------------------------------------------------

	def get_v0_mag( self ) :

		# If any of the components of the velocity are undefined, abort
		# (returning "None").

		if ( ( self.v0_x is None ) or ( self.v0_y is None ) or
		     ( self.v0_z is None )                             ) :

			return None

		# Calculate and return the magnitude.

		return sqrt( self.v0_x**2 + self.v0_y**2 + self.v0_z**2 )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR GETTING THE VECTOR OF THE VELOCITY.
	#-----------------------------------------------------------------------

	def get_v0_vec( self ) :

		# If any of the components of the velocity are undefined, abort
		# (returning "None").

		if ( ( self.v0_x is None ) or ( self.v0_y is None ) or
		     ( self.v0_z is None )                             ) :

			return None

		# Return the vector.

		return ( self.v0_x, self.v0_y, self.v0_z )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR ADDING A NEW SPECIES.
	#-----------------------------------------------------------------------

	def add_spec( self,
	              name=None, sym=None,
	              m=None, q=None       ) :

		self.arr_spec.append(
		                    spec( self, name=name, sym=sym, m=m, q=q ) )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR ADDING A NEW POPULATION.
	#-----------------------------------------------------------------------

	def add_pop( self, spc,
	             drift=False, aniso=False,
	             name=None, sym=None, n=None, dv=None,
	             w=None, w_per=None, w_par=None,
	             sig_n=None, sig_dv=None, sig_w=None,
	             sig_w_per=None, sig_w_par=None       ) :

		self.arr_pop.append( pop( self,
		                          self.get_spec( spc ),
		                          drift=drift, aniso=aniso,
		                          name=name, sym=sym,
		                          n=n, dv=dv, w=w,
		                          w_per=w_per, w_par=w_par,
		                          sig_n=sig_n, sig_dv=sig_dv, sig_w=sig_w,
		                          sig_w_per=sig_w_per, sig_w_par=sig_w_par ) )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR RETREIVING A SPECIES.
	#-----------------------------------------------------------------------

	def get_spec( self, key ) :

		if ( key is None ) :
			return None

		for s in self.arr_spec :

			if ( ( s['name'] == key ) or
			     ( s['sym']  == key )    ) :

				return s

		return None

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR RETREIVING A POPULATION.
	#-----------------------------------------------------------------------

	def get_pop( self, s_key, p_key ) :

		# Attempt to retrieve the species.  If this fails, abort.

		s = self.get_spec( s_key )

		if ( s is None ) :

			return None

		# Attempt to retrieve the requested population of this species
		# and return it to the user.

		for p in self.arr_pop :

			if ( ( p['spec'] == s            ) and
			     ( ( p['name'] == p_key ) or
			       ( p['sym']  == p_key )    )     ) :

				return p

		return None

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR RETREIVING A LIST OF POPULATIONS.
	#-----------------------------------------------------------------------

	def lst_pop( self, key ) :

		lst = [ ]

		if ( key is None ) :

			return

		for p in self.arr_pop :

			if ( ( p['spec'] == key ) or
			     ( p['name'] == key ) or
			     ( p['sym']  == key )    ) :

				lst.append( p )

		return lst

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR REMOVING A SPECIES.
	#-----------------------------------------------------------------------

	def del_spec( self, key ) :



		# WARNING!

		# TODO: Special handling of populations that still reference
		#       this species.





		# Attempt to retrieve the species.  If this fails, abort.

		s = self.get_spec( key )

		if ( s is None ) :
			return

		# Identify the index of the species in the array of species.

		i = self.arr_spec.index( s )

		# Delete the requested species.

		del self.arr_spec[i]

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR REMOVING A POPULATION.
	#-----------------------------------------------------------------------

	def del_pop( self, key ) :

		# Attempt to retrieve the population.  If this fails, abort.

		p = self.get_pop( key )

		if ( p is None ) :
			return

		# Identify the index of the population in the array of
		# populations.

		i = self.arr_pop.index( p )

		# Delete the requested population.

		del self.arr_pop[i] class series( object ) :

	#-----------------------------------------------------------------------
	# DEFINE THE INITIALIZATION FUNCTION.
	#-----------------------------------------------------------------------

	def __init__( self,
	              replace=True, sort=True ) :

		self.replace = bool( replace )
		self.sort    = bool( sort    )

		self.arr = [ ]

		self.n = 0

	#-----------------------------------------------------------------------
	# DEFINE THE LENGTH FUNCTION.
	#-----------------------------------------------------------------------

	def __len__( self ) :

		# Return the number of "plas" objects stored in this object's
		# array.

		return self.n

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION TO FIND A "plas" OBJECT BASED ON ITS TIMESTAMP.
	#-----------------------------------------------------------------------

	def fnd_spec( self, time ) :

		# If a "None" timestamp has been provided, abort.

		if ( time == None ) :
			return None

		# Locate all occurances of the target timestamp among the
		# timestamps in the array.

		ret = [ i for ( i, j ) in enumerate( self.arr )
		                                             if j.time == time ]

		# If at least one match has been found, return in; otherwise,
		# return "None".

		if ( len( ret ) > 0 ) :
			return ret[0]
		else :
			return None

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR ADDING A SPECTRUM TO THE SERIES.
	#-----------------------------------------------------------------------

	def add_spec( self, spec ) :

		# Insert the new spectrum into the array (replacing an old
		# spectrum, if that behavior has been requested and one is
		# found).

		if ( self.replace ) :
			ind = self.fnd_spec( spec.time )
		else :
			ind = None

		if ( ind == None ) :
			self.arr.append( spec )
			self.n += 1
		else :
			self.arr[ind] = spec

		# If requested, sort the array.

		if ( self.sort ) :
			self.arr.sort( )

	#-----------------------------------------------------------------------
	# DEFINE THE FUNCTION FOR RETURNING A KEY.
	#-----------------------------------------------------------------------

	def __getitem__( self, key ) :

		return [ s[key] for s in self.arr ]
from PyQt4.QtCore import QObject, SIGNAL, QThread
from time import sleep
from datetime import datetime, timedelta
from janus_time import calc_time_epc, calc_time_sec, calc_time_val
from janus_step import step
from janus_const import const
from janus_fc_arcv   import fc_arcv
from janus_spin_arcv import spin_arcv
from janus_mfi_arcv_lres  import mfi_arcv_lres
from janus_mfi_arcv_hres import mfi_arcv_hres
from numpy import amax, amin, append, arccos, arctan2, arange, argsort, array, \
                    average, cos, deg2rad, diag, dot, exp, indices, interp, \
                    mean, pi, polyfit, rad2deg, reshape, sign, sin, sum, sqrt, \
                    std, tile, transpose, where, zeros
from numpy.linalg import lstsq
from scipy.special     import erf
from scipy.interpolate import interp1d
from scipy.optimize    import curve_fit
from scipy.stats       import pearsonr, spearmanr
from janus_helper import round_sig
from janus_fc_spec import fc_spec
from janus_pyon import plas, series
from copy import deepcopy
import os.path
import time
import pickle
################################################################################

# Load the modules necessary for signaling the graphical interface.


# Load the modules necessary for file operations.


# Load the modules necessary for handling dates and times.


# Load the module necessary handling step functions.


# Load the dictionary of physical constants.


# Load the modules necessary for loading Wind/FC and Wind/MFI data.


# Load the necessary array modules and mathematical functions.






# Load the "pyon" module.


# Load the modules necessary for saving results to a data file.


# Load the modules necessary for copying.



################################################################################
## DEFINE THE "core" CLASS: THE ANLYSIS CORE OF JANUS.
################################################################################

class core( QObject ) :

	# +-------------------+------------------------------+
	# | SIGNAL('janus_*') | Arguments                    |
	# +-------------------+------------------------------+
	# | busy_beg          |                              |
	# | busy_end          |                              |
	# | mesg              | mesg_src, mesg_typ, mesg_obj |
	# | rset              |                              |
	# | chng_spc          |                              |
	# | chng_mfi          |                              |
	# | chng_spin         |                              |
	# | chng_mom_win      |                              |
	# | chng_mom_sel_bin  | c, d, b                      |
	# | chng_mom_sel_dir  | c, d                         |
	# | chng_mom_sel_all  |                              |
	# | chng_mom_res      |                              |
	# | chng_nln_pop      | i                            |
	# | chng_nln_ion      |                              |
	# | chng_nln_set      |                              |
	# | chng_nln_gss      |                              |
	# | chng_nln_sel_bin  | c, d, b                      |
	# | chng_nln_sel_all  |                              |
	# | chng_nln_res      |                              |
	# | chng_dsp          |                              |
	# | chng_dyn          |                              |
	# | chng_opt          |                              |
	# | rstr_opt          |                              |
	# | done_auto_run     |                              |
	# | exit              |                              |
	# +-------------------+------------------------------+

	#-----------------------------------------------------------------------
	# DEFINE THE INITIALIZATION FUNCTION.
	#-----------------------------------------------------------------------

	def __init__( self, app=None, time=None ) :

		# Inherit all attributes of the "QObject" class.

		# Note.  This class does not directly provide any graphical
		#        interface.  Rather, the functions of the "QObject"
		#        class are used principally for signal the classes that
		#        do.

		super( core, self ).__init__( )

		self.app = app

		# Read and store the version information.

		# Note.  The "[:-1]" array operation used below trims the final
		#        character (which should be an end-line) from the line
		#        read from the file.

		fl = open( os.path.join( os.path.dirname( __file__ ),
		                         'janus.dat'                  ) )

		self.version = fl.readline( )[:-1]

		fl.close( )

		# Initialially deactiveate the debugging mode.

		self.debug = False

		# Initialize the variables that will contain the Wind/FC ion
		# spectrum's data, the associated Wind/MFI magnetic field data,
		# the ion spectrum's point selection, and the results of the
		# moments analysis of the ion spectrum.

		self.init_var( )

		# Initialize and store the archives for downloading, loading,
		# and managing data files.

		self.fc_arcv = fc_arcv( core=self,
		                            n_file_max=self.opt['fls_n_fc'  ]  )


		self.spin_arcv = spin_arcv( core=self,