from pyopus.evaluator.performance import PerformanceEvaluator, updateAnalysisCount
from pyopus.evaluator.aggregate import *
from pyopus.optimizer import optimizerClass
from pyopus.optimizer.base import Reporter
from pyopus.parallel.cooperative import cOS
import numpy as np

heads = {
	'opus': {
		'simulator': 'SpiceOpus', 
		'settings': {
			'debug': 0
		},
		'moddefs': {
			'def':  { 'file': 'bjtamp.inc' }, 
		}, 
		'options': {
		}, 
		'params': {
		}
	}
}

analyses = {
	'dc': {
		'head': 'opus', 
		'modules': [ 'def' ], 
		'command': "dc(-100e-6, 100e-6, 'lin', 100, 'i1', 'dc')"
	}, 
}

# Define performance measures, dependencies, and design requirements (lower and upper bounds)
# Gain should be above 20kV/A, nonlinearity should be below 80mV
measures = {
	'gain': {
		'analysis': 'dc', 
		'expression': """
# Get response
x, y = scale(), v('out')

# Construct line from first to last point, extract gain
gain=(y[-1]-y[0])/(x[-1]-x[0])
""", 
	}, 
	'nonlinearity': {
		'analysis': 'dc', 
		'expression': """
# Get response
x, y = scale(), v('out')

# Construct line from first to last point
ylin=y[0]+(x-x[0])*(y[-1]-y[0])/(x[-1]-x[0])

# Maximal deviation from line
nonlinearity=(np.abs(y-ylin)).max()
""", 
	}, 
}
	
class CostFunction:
	def __init__(self, evaluator):
		self.evaluator=evaluator
		self.dcAnalyses=0
	
	def __call__(self, x):
		# Get parameter values
		r1, r2 = x[0], x[1]
		
		# Call evaluator
		result, anCount = self.evaluator({'r1': r1, 'r2': r2})
		
		# Update analysis counter
		self.dcAnalyses+=anCount['dc']
		
		# Get performance measures
		gain=result['gain']['nominal']
		nonlinearity=result['nonlinearity']['nominal']
		
		# Compute cost function
		cost=0
		if gain is not None:
			if gain<20e3:
				cost+=(20e3-gain)/1e3
		else:
			# Penalize failed measurement
			cost+=10000
		
		if nonlinearity is not None:
			if nonlinearity>80e-3:
				cost+=(nonlinearity-80e-3)/10e-3
		else:
			# Penalize failed measurement
			cost+=10000
			
		return cost
			
# Nominal corner
corners={
	'nominal': {
		'heads': ['opus'], 
		'params': {
			'vcc': 12, 
			'temperature': 25
		}, 
		'modules': []
	}
}
		
if __name__=='__main__':
	# Performance evaluator
	pe=PerformanceEvaluator(heads, analyses, measures, corners, debug=0)
	
	# Design parameter settings
	xnames=np.array(['r1', 'r2'])
	xlow=  np.array([5e3,  20e3])
	xhi=   np.array([50e3, 200e3])
	xinit= np.array([45e3, 195e3])
	
	# Aggregate cost function
	ce=CostFunction(pe)
	
	# Optimizer
	opt=optimizerClass("ParallelSADE")(ce, xlo=xlow, xhi=xhi, fstop=0, maxiter=1000)
	
	# Set initial point. Must be a numpy array. 
	opt.reset(xinit)
	
	# Install reporter plugin for printing the cost function value
	opt.installPlugin(Reporter())
	
	# Run
	opt.run()

	# Optimization result
	xresult=opt.x
	iterresult=opt.bestIter
		
	# Final evaluation at xresult. 
	cf=ce(xresult)
	
	# Print results. 
	print("\n\nFinal cost: "+str(cf)+", found in iter "+str(iterresult)+", total "+str(opt.niter)+" iteration(s)")
	print("r1=", xresult[0])
	print("r2=", xresult[1])
	# ce was used by pe for evaluation, so all the performance measure values are there
	print("Performance in corners")
	print(pe.formatResults(['gain', 'nonlinearity'], nMeasureName=12, nCornerName=15))
	print("")
	
	print("Analysis count: ", ce.dcAnalyses)
	
	# Cleanup intemediate files
	pe.finalize()
	
	# Finalize cOS parallel environment
	cOS.finalize()
	
