
#       parametertizedStandard.py
#       
#       
#       Copyright 2010 alex arsenovic <arsenovic@virginia.edu>
#       
#
#       This program is free software; you can redistribute it and/or modify
#       it under the terms of the GNU General Public License as published by
#       the Free Software Foundation; either version 2 of the License, or
#       (at your option) any later versionpy.
#       
#       This program is distributed in the hope that it will be useful,
#       but WITHOUT ANY WARRANTY; without even the implied warranty of
#       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#       GNU General Public License for more details.
#       
#       You should have received a copy of the GNU General Public License
#       along with this program; if not, write to the Free Software
#       Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#       MA 02110-1301, USA.
'''
Provides Parameterized Standard class, and some specific instances. The 
specific instances are named as follows
	StandardType_UnknownQuantity
'''
import numpy as npy
from copy import copy,deepcopy

from discontinuities.variationalMethods import *

class ParameterBoundsError(Exception):
	pass

class ParameterizedStandard(object):
	global INF
	INF=1e12
	'''
	A parameterized standard represents a calibration standard which 
	has uncertainty in its response. This uncertainty is functionally 
	known, and 	represented by a parametric function, where the 
	uknown quantity is the adjustable parameter. 

	This class presents an abstract interface to a general Parametric
	Standard. Its main purpose is to allow the self calibration routine
	to be independent of calibration set. 
	
	See initializer for more 
	details. 
	'''
	def __init__(self, function=None, parameters={}, parameter_bounds={},**kwargs):
		'''
		takes:
			function: a function which will be called to generate
				a Network type, to be used as a ideal response. 
			
			parameters: an dictionary holding an list of parameters,
				which will be the dependent variables to optimize.
				these are passed to the network creating function.
			 
			**kwargs: keyword arguments passed to the function, but 
				not used in parametric optimization.
		'''
		self.kwargs = kwargs
		self.parameters = parameters
		self.function = function
		self.parameter_bounds = parameter_bounds
	
	@property
	def parameter_keys(self):
		'''
		returns a list of parameter dictionary keys in alphabetical order
		'''
		keys = self.parameters.keys()
		keys.sort()
		return keys

	@property	
	def parameter_array(self):
		'''
		This property provides a 1D-array interface to the parameters 
		dictionary. This is needed to intereface teh optimizing function
		because it only takes a 1D-array. Therefore, order must be 
		preserved with accessing and updating the parameters through this
		array. To handle this I make it return and update in alphebetical
		order of the parameters dictionary keys. 
		'''
		return npy.array([self.parameters[k] for  k in self.parameter_keys])

	@parameter_array.setter	
	def parameter_array(self,input_array):
		counter = 0
		for k in self.parameter_keys:
			self.parameters[k]= input_array[counter]
			counter+=1

	@property	
	def parameter_bounds_array(self):
		'''
		This property provides a 1D-array interface to the parameters 
		bounds dictionary. if key doesnt exist, then i presume the
		parameter has no bounds. this then returns a tuple of -INF,INF
		where INF is a global variable in this class. 
		'''
		return ([self.parameter_bounds.get(k,(-INF,INF)) for  k in self.parameter_keys])


	@property
	def number_of_parameters(self):
		'''
		the number of parameters this standard has
		'''
		return len(self.parameter_keys)
	
	@property
	def s(self):
		'''
		a direct access to the calulated networks' s-matrix
		'''
		return self.network.s

	@property
	def network(self):
		'''
		 a Networks instance generated by calling self.function(), for 
		the current set of parameters (and kwargs) 
		'''
		tmp_args = copy(self.kwargs)
		tmp_args.update(self.parameters)

		#for k in range( self.number_of_parameters):
			#if not(self.parameter_bounds_array[k][0] < self.parameter_array[k] \
			#	<self.parameter_bounds_array[k][1]):			
			#	raise ParameterBoundsError('a parameter is out of bounds')

		return self.function(**tmp_args)





## General tline mediums
class Parameterless(ParameterizedStandard):
	'''
		A parameterless standard. 

		note:
		this is needed so that the calibration algorithm doesnt have to
		handle more than one class type for  standards
		'''
	def __init__(self, ideal_network):
		'''
		takes:
			ideal_network: a Network instance of the standard
		'''
		ParameterizedStandard.__init__(self, \
			function  = lambda: ideal_network)
		
class Line_UnknownLength(ParameterizedStandard):
	'''
	A matched delay line of unknown length
	
	initial guess for length should be given to constructor
	'''
	def __init__(self, wb,d,**kwargs):
		'''
		takes:
			wb: a WorkingBand type
			d: initial guess for line length [m]
			**kwargs: passed to self.function
		'''
		ParameterizedStandard.__init__(self, \
			function = wb.line,\
			parameters = {'d':d},\
			**kwargs\
			)
class DelayedShort_UnknownLength(ParameterizedStandard):
	'''
	A delay short of unknown length
	
	initial guess for length should be given to constructor
	'''
	def __init__(self, wb,d,**kwargs):
		'''
		takes:
			wb: a WorkingBand type
			d: initial guess for delay short physical length [m]
			**kwargs: passed to self.function
		'''
		ParameterizedStandard.__init__(self, \
			function = wb.delay_short,\
			parameters = {'d':d},\
			**kwargs\
			)

class DelayedTermination_UnknownLength(ParameterizedStandard):
	'''
	A  Delayed Termination of unknown length, but known termination
	'''
	def __init__(self, wb,d,Gamma0,**kwargs):
		'''
		takes:
			wb: a WorkingBand type, with a RectangularWaveguide object
				for its tline property.
			d: distance to termination
			Gamma0: reflection coefficient off termination at termination
			**kwargs: passed to self.function
		'''
		kwargs.update({'Gamma0':Gamma0,})
		
		ParameterizedStandard.__init__(self, \
			function = wb.delay_load,\
			parameters = {'d':d},\
			**kwargs\
			)

class DelayedTermination_UnknownTermination(ParameterizedStandard):
	'''
	A  Delayed Termination of unknown length or termination
	'''
	def __init__(self, wb,d,Gamma0,**kwargs):
		'''
		takes:
			wb: a WorkingBand type, with a RectangularWaveguide object
				for its tline property.
			d: distance to termination
			Gamma0: reflection coefficient off termination at termination
			**kwargs: passed to self.function
		'''
		kwargs.update({'d':d})
		ParameterizedStandard.__init__(self, \
			function = wb.delay_load,\
			parameters = {'Gamma0':Gamma0},\
			**kwargs\
			)
class DelayedTermination_UnknownLength_UnknownTermination(ParameterizedStandard):
	'''
	A  Delayed Termination of unknown length or termination
	'''
	def __init__(self, wb,d,Gamma0,**kwargs):
		'''
		takes:
			wb: a WorkingBand type, with a RectangularWaveguide object
				for its tline property.
			d: distance to termination
			Gamma0: reflection coefficient off termination at termination
			**kwargs: passed to self.function
		'''
		
		ParameterizedStandard.__init__(self, \
			function = wb.delay_load,\
			parameters = {'d':d,'Gamma0':Gamma0},\
			**kwargs\
			)
class DelayShort_Mulipath(ParameterizedStandard):
	'''
	A delay short of unknown length
	
	initial guess for length should be given to constructor
	'''
	def __init__(self, wb,d1,d2,d1_to_d2_power, **kwargs):
		'''
		takes:
			wb: a WorkingBand type
			d: initial guess for delay short physical length [m]
			**kwargs: passed to self.function
		'''
		def multipath(d1,d2,d1_to_d2_power):
			d2_power = 1./(d1_to_d2_power +1)
			d1_power = 1-d2_power
			ds1 = wb.delay_short(d1)
			ds2 = wb.delay_short(d2)
			ds1.s = ds1.s * d1_power
			ds2.s = ds2.s * d2_power
			return ds1+ds2
		kwargs.update({'d1':d1})
		ParameterizedStandard.__init__(self, \
			function = multipath,\
			parameters = {\
				'd2':d2,\
				'd1_to_d2_power':d1_to_d2_power\
				},\
			**kwargs\
			)
class DelayLoad_Mulipath(ParameterizedStandard):
	'''
	A delay short of unknown length
	
	initial guess for length should be given to constructor
	'''
	def __init__(self, wb,d1,Gamma0, d2,d1_to_d2_power, **kwargs):
		'''
		takes:
			wb: a WorkingBand type
			d: initial guess for delay short physical length [m]
			**kwargs: passed to self.function
		'''
		def multipath(d1,d2,Gamma0, d1_to_d2_power):
			d2_power = 1./(d1_to_d2_power +1)
			d1_power = 1-d2_power
			ds1 = wb.delay_load(d1,Gamma0)
			ds2 = wb.delay_short(d2)
			ds1.s = ds1.s * d1_power
			ds2.s = ds2.s * d2_power
			return ds1+ds2
		kwargs.update({'d1':d1,'Gamma0':Gamma0})
		ParameterizedStandard.__init__(self, \
			function = multipath,\
			parameters = {\
				'd2':d2,\
				'd1_to_d2_power':d1_to_d2_power\
				},\
			**kwargs\
			)
				


## multi-standards
class SlidingLoad_UnknownTermination(ParameterizedStandard):
	'''
	A set of parametersized standards representing a set of Delayed
	 Terminations of known length, but unknown termination
	'''
	
	def __init__(self, wb,d_list,Gamma0,**kwargs):
		'''
		takes:
			wb: a WorkingBand type, with a RectangularWaveguide object
				for its tline property.
			d_list: list of distances to termination [m]
			Gamma0: guess for reflection coefficient off termination at
				termination
			**kwargs: passed to self.function
		'''
		raise (NotImplementedError())
		kwargs.update({'Gamma0':Gamma0,})

		#def tempfunc(Gamma0,**kwargs):
		#	
			#return [wb.delay_load(


		parameters = {}
		#[parameters['d'+str(k)] = d_list[k] for k in range(len(d_list))]
		ParameterizedStandard.__init__(self, \
			function = wb.delay_load,\
			parameters = parameters,\
			**kwargs\
			)



## rectangular waveguide specific
class DelayedTermination_TranslationMissalignment(ParameterizedStandard):
	'''
	A delayed rectangular waveguide termination with unknown flange
	translation missalignment.

	'''
	def __init__(self, wb,d,Gamma0,initial_offset= 1./10, **kwargs):
		'''
		takes:
			wb: a WorkingBand type, with a RectangularWaveguide object
				for its tline property.
			d: distance to termination
			Gamma0: reflection coefficient off termination at termination
			initial_offset: initial offset guess, as a fraction of a, 
				(the waveguide width dimension)
			**kwargs: passed to self.function
		'''
		wg = wb.tline
		kwargs.update({\
			'wg_I':wg,\
			'wg_II':wg,\
			'freq':wb.frequency,\
			'd':d,\
			'Gamma0':Gamma0})
		
		ParameterizedStandard.__init__(self, \
			function = rectangular_junction_centered,\
			parameters = {\
				'da':wg.a*initial_offset, \
				'db':wg.a*initial_offset},\
			**kwargs\
			)
class DelayedShort_TranslationMissalignment(ParameterizedStandard):
	'''
	A delayed rectangular waveguide termination with unknown flange
	translation missalignment.

	'''
	def __init__(self, wb,d,initial_offset= 1./10, **kwargs):
		'''
		takes:
			wb: a WorkingBand type, with a RectangularWaveguide object
				for its tline property.
			d: distance to termination
			Gamma0: reflection coefficient off termination at termination
			initial_offset: initial offset guess, as a fraction of a, 
				(the waveguide width dimension)
			**kwargs: passed to self.function
		'''
		wg = wb.tline
		kwargs.update({\
			'wg_I':wg,\
			'wg_II':wg,\
			'freq':wb.frequency,\
			'd':d,\
			'Gamma0':-1})
		
		ParameterizedStandard.__init__(self, \
			function = rectangular_junction_centered,\
			parameters = {\
				'da':wg.a*initial_offset, \
				'db':wg.a*initial_offset},\
			**kwargs\
			)
class Line_TranslationMissalignment(ParameterizedStandard):
	'''
	A  rectangular waveaguide matched line standard with unknown flange
	translation missalignment.

	'''
	def __init__(self, wb,d,initial_offset= 1./10, **kwargs):
		'''
		takes:
			wb: a WorkingBand type, with a RectangularWaveguide object
				for its tline property.
			d: length of line [m]
			Gamma0: reflection coefficient off termination at termination
			initial_offset: initial offset guess, as a fraction of a, 
				(the waveguide width dimension)
			**kwargs: passed to self.function
		
		'''
		wg = wb.tline
		kwargs.update({\
			'wg_I':wg,\
			'wg_II':wg,\
			'freq':wb.frequency,\
			'd':d,\
			'Gamma0':0,\
			'nports':2,\
			})
		
		ParameterizedStandard.__init__(self, \
			function = rectangular_junction_centered,\
			parameters = {\
				'da':wg.a*initial_offset, \
				'db':wg.a*initial_offset},\
			**kwargs\
			)
class Line_UnknownLength_TranslationMissalignment(ParameterizedStandard):
	'''
	A  rectangular waveaguide matched line standard with unknown flange
	translation missalignment and unknown length. 

	'''
	def __init__(self, wb,d,initial_offset= 1./10, **kwargs):
		'''
		takes:
			wb: a WorkingBand type, with a RectangularWaveguide object
				for its tline property.
			d: guess for length of line [m]
			Gamma0: reflection coefficient off termination at termination
			initial_offset: initial offset guess, as a fraction of a, 
				(the waveguide width dimension)
			**kwargs: passed to self.function
		
		'''
		wg = wb.tline
		kwargs.update({\
			'wg_I':wg,\
			'wg_II':wg,\
			'freq':wb.frequency,\
			'Gamma0':0,\
			'nports':2,\
			})
		
		ParameterizedStandard.__init__(self, \
			function = rectangular_junction_centered,\
			parameters = {\
				'da':wg.a*initial_offset, \
				'db':wg.a*initial_offset,
				'd':d
				},\
			**kwargs\
			)
class Thru_TranslationMissalignment(ParameterizedStandard):
	'''
	A  rectangular waveaguide thru standard with unknown flange
	translation missalignment.

	'''
	def __init__(self, wb,initial_offset= 1./10, **kwargs):
		'''
		takes:
			wb: a WorkingBand type, with a RectangularWaveguide object
				for its tline property.
			d: distance to termination
			Gamma0: reflection coefficient off termination at termination
			initial_offset: initial offset guess, as a fraction of a, 
				(the waveguide width dimension)
			**kwargs: passed to self.function
		
		'''
		wg = wb.tline
		kwargs.update({\
			'wg_I':wg,\
			'wg_II':wg,\
			'freq':wb.frequency,\
			'd':0,\
			'Gamma0':0,\
			'nports':2,\
			})
		
		ParameterizedStandard.__init__(self, \
			function = rectangular_junction_centered,\
			parameters = {\
				'da':wg.a*initial_offset, \
				'db':wg.a*initial_offset},\
			**kwargs\
			)
class Match_TranslationMissalignment(ParameterizedStandard):
	'''
	A match with unknown translation missalignment.
	the initial guess for missalignment is [a/10,a/10], where a is the 
	waveguide width
	'''
	def __init__(self, wb, initial_offset= 1./10 , **kwargs):
		'''
		takes:
			wb: a WorkingBand type, with a RectangularWaveguide object
				for its tline property.
			initial_offset: initial offset guess, as a fraction of a, 
				(the waveguide width dimension)
			**kwargs: passed to self.function
		'''
		wg = wb.tline
		kwargs.update({'wg':wg,'freq':wb.frequency})
		
		ParameterizedStandard.__init__(self, \
			function = translation_offset,\
			parameters = {'delta_a':wg.a*initial_offset, \
				'delta_b':wg.a*initial_offset},\
			**kwargs\
			)




class DelayedTermination_UnknownLength_TranslationMissalignment(ParameterizedStandard):
	'''
	A known Delayed Termination with unknown translation missalignment.
	the initial guess for missalignment defaults to [1/10,1/10]*a,
	where a is the 	waveguide width
	'''
	def __init__(self, wb,d,Gamma0,initial_offset= 1./10, **kwargs):
		'''
		takes:
			wb: a WorkingBand type, with a RectangularWaveguide object
				for its tline property.
			d: distance to termination
			Gamma0: reflection coefficient off termination at termination
			initial_offset: initial offset guess, as a fraction of a, 
				(the waveguide width dimension)
			**kwargs: passed to self.function
		'''
		wg = wb.tline
		kwargs.update({\
			'wg_I':wg,\
			'wg_II':wg,\
			'freq':wb.frequency,\
			'Gamma0':Gamma0})
		
		ParameterizedStandard.__init__(self, \
			function = rectangular_junction_centered,\
			parameters = {'da':wg.a*initial_offset, \
						'db':wg.a*initial_offset,\
						'd':d},\
			**kwargs\
			)

class RotatedWaveguide_UnknownLength(ParameterizedStandard):
	'''
	A rotated waveguide of unkown delay length.
	'''
	def __init__(self, wb,d,Gamma0, **kwargs):
		'''
		takes:
			wb: a WorkingBand type, with a RectangularWaveguide object
				for its tline property.
			d: distance to termination
			Gamma0: reflection coefficient off termination at termination
			initial_offset: initial offset guess, as a fraction of a, 
				(the waveguide width dimension)
			**kwargs: passed to self.function
		'''
		wg_I = wb.tline
		wg_II = deepcopy(wb.tline)
		wg_II.a , wg_II.b = wg_I.b,wg_I.a
		
		kwargs.update({\
			'wg_I':wg,\
			'wg_II':wg,\
			'freq':wb.frequency,\
			'da':0,\
			'db':0,\
			'Gamma0':Gamma0})
		
		ParameterizedStandard.__init__(self, \
			function = rectangular_junction_centered,\
			parameters = {'d':d},\
			**kwargs\
			)



