Main Page   Class Hierarchy   Alphabetical List   Compound List   File List   Compound Members   File Members  

lna_opt.annotated.cc

Go to the documentation of this file.
00001 // Build a two-stage low noise amplifier, and optimize the component values.
00002 // Shows how to use the optimizer and its associated classes
00003 
00004 // Look at lna.cc first to get details on building and simulating the amplifier
00005 // circuit.
00006 
00007 #include "supermix.h"
00008  
00009 int main()
00010 {
00011   // Set the global temperature and normalization impedance.
00012   device::T = 4.2 * Kelvin;
00013   device::Z0 = 50. * Ohm;
00014 
00015   // ==========================================================================
00016   // Building the circuit:
00017 
00018   // Declare the components to be used to make the amplifier. Same as in lna.cc
00019   fhx13x t1, t2;            // Field-effect transistors made by Fujitsu.
00020   t1.Ls.L = 0.05 * nHenry;  // adjusting the source inductance values a bit...
00021   t2.Ls.L = 0.05 * nHenry;
00022 
00023   inductor lg1, lg2;        // Gate tuning inductors.
00024   resistor rd1, rd2;        // Drain bias resistors.
00025   lg1.series();             // series is the default, but who can remember?
00026   lg1.series();
00027   rd1.parallel();
00028   rd2.parallel();
00029   // The inductance and resistance values will be found by the optimizer
00030   
00031   // Connect the components into a circuit.
00032   circuit amp;
00033   int input = amp.add_port(lg1, 1);
00034   amp.connect(lg1, 2, t1, 1);
00035   amp.connect(t1, 2, rd1, 1);
00036   amp.connect(rd1, 2, lg2, 1);
00037   amp.connect(lg2, 2, t2, 1);
00038   amp.connect(t2, 2, rd2, 1);
00039   int output = amp.add_port(rd2, 2);
00040 
00041   // ==========================================================================
00042   // Setting up the error function for the optimization:
00043 
00044   // error functions perform two tasks:
00045   //
00046   //  (1) provide the interface between an optimization algorithm and the
00047   //      actual variables to be optimized in your program
00048   //
00049   //  (2) calculate an error function value for the optimization algorithm
00050   //      by summing the values of one or more error terms
00051   //
00052   // The interface to the optimizer algorithms which all error functions
00053   // must provide is defined in optimizer.h, using class abstract_error_func.
00054 
00055   // We'll be using class error_func, defined in error_func.h. It is derived
00056   // from class error_func_parameters, defined in simple_error_func.h. The
00057   // program variables it optimizes must be of class real_parameter, defined
00058   // in parameter/real_parameter.h. Essentially all of the real-valued variable
00059   // parameters in the various devices are of this type, so they can be
00060   // controlled by class error_func. The vary() member function gives control
00061   // of the parameters to the error function. vary() needs minimum, initial,
00062   // and maximum values for the parameter, along with units:
00063 
00064   // we'll be doing a 4-variable optimization:
00065   error_func ef;
00066   lg1.L = ef.vary(  1.0,    3.0,    20.0,  nHenry );
00067   lg2.L = ef.vary(  0.2,    1.0,    10.0,  nHenry );
00068   rd1.R = ef.vary( 30.0,  100.0,   500.0,  Ohm    );
00069   rd2.R = ef.vary( 30.0,  100.0,   150.0,  Ohm    );
00070 
00071 
00072   // Sweepers are used to define ranges (in this case, frequency bands) for
00073   // sweeping some parameter under the control of an error function object.
00074   // The error function is calculated from the sum of the error function
00075   // values at each of the sweeper points. See sweeper.h for details. 
00076 
00077   // In this case, both of the sweepers will control the global frequency
00078   // variable, device::f, which determines the frequency for circuit response
00079   // calculations. The stability_band sweeper sweeps over a wider frequency
00080   // range. The last argument in the call to sweep() gives the step size.
00081   // Note the need for units!
00082 
00083   sweeper gain_band, stability_band;
00084 
00085        gain_band.sweep( device::f, 4.0*GHz,  8.0*GHz, 0.25*GHz );
00086   stability_band.sweep( device::f, 0.1*GHz, 20.1*GHz, 0.50*GHz );
00087 
00088 
00089   // Error terms are individual components in an error function. Their design
00090   // allows for the accumulation of an error value through the range of a
00091   // parameter sweep by a sweeper object. Several generic error term forms
00092   // are defined in error_func.h, all derived from class error_term. Error
00093   // terms based on these generic objects are easy to write from scratch, but
00094   // several concrete and useful error terms are already defined in
00095   // error_terms.h. We'll be using these objects, which take a circuit, and
00096   // maybe port numbers and a target mode and value. The target mode and
00097   // target value can be provided as additional constructor arguments, but
00098   // here we'll call member functions to do that job.
00099 
00100   // The classes gain_dB, input_tn, amp_k, and amp_mag_delta are all defined
00101   // in error_terms.h; they are derived from error_term_mode defined in
00102   // error_func.h, which also describes the member functions we are calling
00103   // to set the mode and target for each error term. Error terms derived from
00104   // error_term_mode generally return a value proportional to the square of the
00105   // deviation from a target value. error_func averages the error term values
00106   // across the points of an associated sweeper (if any).
00107 
00108   // Optimize for stability.
00109   amp_k         stb1_term(amp);
00110   amp_mag_delta stb2_term(amp);
00111 
00112   // Keep the gain high.
00113   gain_dB  gain_term(amp, input, output);
00114   gain_term.above(20.0);  // an error value results if gain is not above 20 dB
00115 
00116   // Optimize for flat gain.
00117   gain_dB  flatness_term(amp, input, output);
00118   flatness_term.flat();   // proportional to overall deviation from flatness
00119 
00120   // Optimize the output match.
00121   gain_dB  out_match_term(amp, output, output);
00122   out_match_term.below(-15.0); // want to be below the target of -15 dB
00123 
00124   // Optimize the input match.
00125   gain_dB  in_match_term(amp, input, input);
00126   in_match_term.below(-10.0);  // want to be below the target of -10 dB
00127 
00128   // Optimize for low noise.
00129   input_tn noise_term(amp, input, output);
00130   noise_term.match(0.0); // want the noise to match 0 Kelvin
00131 
00132 
00133   // Here's where we add the various error terms to the error function in
00134   // order to fully define it. add_term() takes a relative weighting factor
00135   // as its first argument, along with the error term and an optional sweeper.
00136   // add_term() is defined in error_func.h
00137 
00138   ef.add_term( 10.0,       stb1_term,  stability_band );
00139   ef.add_term( 10.0,       stb2_term,  stability_band );
00140   ef.add_term(  1.5,       gain_term,       gain_band );
00141   ef.add_term( 20.0,   flatness_term,       gain_band );
00142   ef.add_term(  1.0,  out_match_term,       gain_band );
00143   ef.add_term(  1.0,   in_match_term,       gain_band );
00144   ef.add_term( 10.0,      noise_term,       gain_band );
00145 
00146   // ==========================================================================
00147   // Setting up the optimization algorithm:
00148 
00149   // Optimization algorithms are derived from class minimizer, found in
00150   // optimizer.h; we'll be using Powell's algorithm, defined in powell.h
00151   // Minimizers must be given an error function at construction. They also
00152   // have member functions which control verbosity. Class powell has some
00153   // variables which can be adjusted to tweak the performance of the
00154   // algorithm; we adjust one of them from its default value. See powell.h
00155   // for details.
00156 
00157   powell opt(ef);
00158   opt.verbose();      // see the iterations as they occur
00159   opt.FTOL = 0.00001;
00160 
00161   // ==========================================================================
00162   // Perform the optimization, displaying the resulting optimized values:
00163 
00164   cout << endl << "2 stage cryogenic low-noise amplifier" << endl << endl;
00165   cout << "Starting optimization:" << endl << endl;
00166 
00167   // Calling the minimize() member function of the optimizer object causes it
00168   // to "do its thing", finding parameter values which minimize the error
00169   // function. minimize() returns the final error function value (a double).
00170   //
00171   // In the next lines we call minimize() and output the returned error
00172   // function value. Because we called opt.verbose() earlier, we'll get several
00173   // lines of output generated internally by the optimizer as it searches for a
00174   // solution.
00175 
00176   double error_val = opt.minimize();
00177   cout << endl << "Final error function value: " << error_val  << endl ;
00178 
00179   // We can get a breakdown of the contribution of each error term to the
00180   // final error function value by calling error_func's member function
00181   // get_func_breakdown(). It returns a real_vector object (see vector.h
00182   // -- the one that comes with supermix, NOT the one that comes with the
00183   // standard C++ library, known as the STL). The stream operator << can be
00184   // used to output the contents of a real_vector object, by default all on
00185   // one line. The contributions of the terms are displayed in the same
00186   // order as the order of the calls to ef.add_term() made previously, in this
00187   // case: stb1_term, stb2_term, gain_term, flatness_term, out_match_term,
00188   // in_match_term, noise_term.
00189 
00190   cout << "Error function breakdown by term:" << endl;
00191   cout << ef.get_func_breakdown() << endl;
00192 
00193   // Now that the optimization is complete, we can conveniently ask the error
00194   // function for the final values of the parameter assignments. By calling the
00195   // member function get_parms_user(), we get a real_vector object. The show()
00196   // member function of the real_vector class can also be used to display the
00197   // results, by default all on one line preceded and followed by a newline.
00198   // The parameters are displayed in the same order as the order of the calls
00199   // to ef.vary() made previously. In this case the order is lg1.L, lg2.L,
00200   // rd1.R, and rd2.R. They are displayed in terms of the units provided in
00201   // the vary() calls; in our case the inductors are in nHenry, the resistors
00202   // in Ohms.
00203 
00204   cout << "Final parameters are:" << endl;
00205   cout << "lg1(nH)" << " " << "lg2(nH)" << " " << "rd1(O) " << " " << "rd2(O)";
00206 
00207   ef.get_parms_user().show() ;
00208 
00209 
00210   // ==========================================================================
00211   // Now display the response of our amplifier using the optimized values:
00212 
00213   // Here you also see what you need to do to set a specific output field width
00214   cout << endl << "Response:" << endl << endl;
00215   cout << setw(8) << "Freq" << "  "  
00216        << setw(8) << "S21(dB)" << "  "  
00217        << setw(8) << "S22(dB)" << "  "  
00218        << setw(8) << "Tn(K)" << "  "  
00219        << setw(8) << "NF(dB)" << endl << endl;
00220 
00221   // The scattering and noise matrices will be held in a special form
00222   // of sdata object, called ampdata. It is defined in ampdata.h and has
00223   // some additional member functions appropriate for displaying
00224   // amplifier characteristics.
00225   ampdata response;
00226 
00227   for(double freq = 1.0; freq <=12.0; freq += 0.5)
00228   {
00229     // Set the frequency.
00230     device::f = freq * GHz;
00231 
00232     // Calcuate the scattering and noise matrices, and hold them in response.
00233     response = amp.get_data();
00234 
00235     // Print out the frequency, gain, output match, noise temp, noise figure.
00236     cout << fixed << setprecision(4) 
00237          << setw(8) << freq << "  "
00238          << setw(8) << response.SdB(output,input) << "  " 
00239          << setw(8) << response.SdB(output,output) << "  "
00240          << setw(8) << response.tn(output,input)/Kelvin << "  " 
00241          << setw(8) << response.NF(output,input) << "  ";
00242 
00243     // Also let user know if amp is unconditionally stable at this frequency
00244     if(response.unconditionally_stable()) cout << "Unconditionally Stable";
00245 
00246     cout << endl;
00247   }
00248 
00249   cout << endl;
00250 }

Please direct comments and corrections to supermix@submm.caltech.edu
Go to the supermix home page
Generated by doxygen1.2.7