10.4.6. Constructing an aggregate cost function

This demo shows how to use the pyopus.evaluator.aggregate.Aggregator class for constructing an aggregate cost function from the results collected by a performance evaluator. Read chapter Using the performance evaluator to learn more about the pyopus.evaluator.performance.PerformanceEvaluator class.

First we leave most parameters to be set by PyOPUS. Therefore we remove their default values from the definition of the amplifier in file opamp.inc in folder demo/evaluation/04-cost/

* Simple opamp, 0.18u, 1.8V, SpiceOpus

* Any include or lib that is set from PyOPUS (specified in problem definition) 
* must not be specified here. 

* Include files - fixed
.include 'mosmm.inc'

* Any parameter that is set from PyOPUS (specified in problem definition) 
* must not be specified here. 

* Operating conditions - fixed
.param ibias=1.000000000000e-004 

* Design parameters - fixed
.param c_out=8.211596855053e-012
.param r_out=1.968986740568e+001

* inp inn out vdd vss bias slp slpx
.subckt amp 3 4 5 1 2 7 11 12
xmp1 9 10 1 1  submodp w={load_w} l={load_l} m=2
xmp2 10 10 1 1 submodp w={load_w} l={load_l} m=2
xmp1s 9 12 1 1 submodp w=1u        l=0.5u
xmp3 5 9 1 1   submodp w={out_w}   l={out_l}   m=16
xmn2 9 3 8 2   submodn w={dif_w}   l={dif_l} 
xmn3 10 4 8 2  submodn w={dif_w}   l={dif_l} 
xmn1s 7 11 2 2 submodn w=1u        l=0.5u
xmn1b 7 7 2 2  submodn w={mirr_w}  l={mirr_l}  m=2
xmn1 8 7 2 2   submodn w={mirr_w}  l={mirr_l}  m=2
xmn4 5 7 2 2   submodn w={mirr_w}  l={mirr_l}  m=16
cout 5a 9 c={c_out}
rout 5 5a r={r_out}
.ends

The problem definition in the main file runme.py in folder demo/evaluation/04-cost/ is identical to the one in chapter Plotting simulation results. Only the differences are given in this document.

First we import the the things we need.

from pyopus.evaluator.performance import PerformanceEvaluator
from pyopus.evaluator.aggregate import *
from pyopus.evaluator.auxfunc import paramList

We define the parameter values.

	params = { 
		'mirr_w': 7.456e-005, 
		'mirr_l': 5.6e-007, 
		'out_w':  4.801e-005, 
		'out_l':  3.8e-007, 
		'load_w': 3.486e-005, 
		'load_l': 2.57e-006, 
		'dif_w':  7.73e-006, 
		'dif_l':  1.08e-006, 
	}

Because the input for an aggregate cost function is a parameter vector we must specify the order in which the parameters are placed in this vector. We simply take the keys of the parameters dictionary and sort them.

	inOrder=list(params.keys())
	inOrder.sort()

Next we define the aggregate cost function components. The function value is obtained by summing all of the components. Every component is based on some performance measure collected by the performance evaluator. The aggregate cost function reflects the quality of the circuit. Lower values should correspond to better circuits.

	definition = [
		{
			# Name of the performance measure
			'measure': 'isup', 
			# How to normalize it 
			#   anything below 1e-3 is negative, while everything above is positive
			#   a change of 0.1e-3 corresponds to contribution of size 1 to the aggregate cost function
			#   a failed measurement results in contribution 10000.0 (default value)
			'norm': Nbelow(1e-3, 0.1e-3, 10000.0),	
			# Shape of the contribution is piecewise linear. It is obtaine by multiplying 
			#   negative normalized values with 0 and 
			#   positive normalized values with 1
			'shape': Slinear2(1.0,0.0),
			# The contribution will be computed, but not added to the aggregate cost function
			# This is good for monitoring a performance
			'reduce': Rexcluded()
		},
		{
			'measure': 'out_op',
			# Output operating point must be below 10 with 0.1e-3 norm
			# This means that violating this requirement results in a positive normalized value
			'norm': Nbelow(10, 0.1e-3),	
			'shape': Slinear2(1.0,0.0), 
			# This one also does not contribute to the aggregate cost function. 
			'reduce': Rexcluded()
		},
		{
			'measure': 'vgs_drv', 
			# This one should be above 1e-3. Failure to achieve the goal is penalized with a 
			# positive normalized value. If norm is not specified (like here) it is equal 
			# to the goal (1e-3 in this case)
			'norm': Nabove(1e-3), 			
			# Take the worst contribution across all corners where vgs_drv is computed and 
			# add it to the aggregate cost function
			# Because we specified no shape the default is used (Slinear2(1.0,0.0))
			'reduce': Rworst()
		},
		{
			# This one should be above 1e-3
			# Because this performance measure is a vector the requirement is enforced on 
			# all of its components. Every component results in one contribution to the 
			# aggregate cost function. 
			'measure': 'vds_drv', 
			'norm': Nabove(1e-3)
		},
		{
			'measure': 'swing', 
			'norm': Nabove(1.6), 
			# If the goal is violated (swing<1.6) we get a positive contribution to the 
			# aggregate cost function obtained by multiplying the normalized value with 1.0. 
			# If, however, swing>1.6 we get a negative contribution obtained by multiplying 
			# the normalized contribution (which is negative) with 0.001. 
			'shape': Slinear2(1.0,0.001), 
		},
		{
			'measure': 'mirr_area', 
			# Mirror area should be below 800e-12 with norm 100e-12
			'norm': Nbelow(800e-12, 100e-12), 
			# Again negative normalized values are multiplied with 0.001 while positive 
			# ones are multiplied with 1. 
			'shape': Slinear2(1.0,0.001)
		}
	]

Finally we put it all together in the main program.

	# Performance evaluator
	pe=PerformanceEvaluator(heads, analyses, measures, corners, variables=variables, debug=0)

	# Aggregate cost function
	ce=Aggregator(pe, definition, inOrder, debug=1)

	# Vectorize parameters
	x=paramList(params, inOrder)
	
	# Evaluate aggregate function at vector x 
	cf=ce(x)
	
	# Print the results
	print("")
	print("cost=%e" % cf)
	print(ce.formatParameters())
	print(ce.formatResults(nMeasureName=10, nCornerName=15))
	print("")
	
	# Print analysis count 
	print("Analysis count: "+str(pe.analysisCount))
	
	# Cleanup intemediate files
	pe.finalize()

This is the output we get (debug output is omitted)

cost=1.190359e+01
          dif_l:    1.080000e-06
          dif_w:    7.730000e-06
         load_l:    2.570000e-06
         load_w:    3.486000e-05
         mirr_l:    5.600000e-07
         mirr_w:    7.456000e-05
          out_l:    3.800000e-07
          out_w:    4.801000e-05
      isup  <  1.000e-03    | .    1.112e-03     worst_power : 0
    out_op  <  1.000e+01    |      1.004e+00     worst_power : 0
   vgs_drv  >  1.000e-03    | o   -1.046e-02         nominal : 11.5
   vds_drv  >  1.000e-03    |      1.026e-01     worst_speed : 0
     swing  >  1.600e+00    | o    1.449e+00     worst_speed : 0.0944
 mirr_area  <  8.000e-10    | o    8.351e-10         nominal : 0.351

Analysis count: {'dccom': 5, 'dc': 5, 'op': 5}

The aggregate cost if printed along with the parameter values.

For every aggregate cost component the corresponding requirement is printed. Dots denote components that fail to satisfy requirements but are not included in the aggregate cost. Symbol ‘o’ denotes components that are included and fail to satisfy the requirement. For every component the value of the performance is printed along with the corner where the worst performance is observed and the contribution to the aggregate cost. Note how performances that satisfy the requirements produce contributions that are not greater than 0. Finally, the analysis count is printed.

Note that debug output can be disabled by setting the debug parameter to 0.