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

balance.cc

Go to the documentation of this file.
00001 // balance.cc
00002 // SuperMix version 1.0  C++ source file
00003 //
00004 // Copyright (c) 1999 California Institute of Technology.
00005 // All rights reserved.
00006 //
00007 // Redistribution and use in source and binary forms for noncommercial
00008 // purposes are permitted provided that the above copyright notice and
00009 // this paragraph are duplicated in all such forms and that any
00010 // documentation and other materials related to such distribution and
00011 // use acknowledge that the software was developed by California
00012 // Institute of Technology. Redistribution and/or use in source or
00013 // binary forms is not permitted for any commercial purpose. Use of
00014 // this software does not include a permitted use of the Institute's
00015 // name or trademark for any purpose.
00016 //
00017 // DISCLAIMER:
00018 // THIS SOFTWARE AND/OR RELATED MATERIALS ARE PROVIDED "AS-IS" WITHOUT
00019 // WARRANTY OF ANY KIND INCLUDING ANY WARRANTIES OF PERFORMANCE OR
00020 // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE OR PURPOSE (AS SET
00021 // FORTH IN UCC 23212-2313) OR FOR ANY PURPOSE WHATSOEVER, FOR THE
00022 // LICENSED PRODUCT, HOWEVER USED.  IN NO EVENT SHALL CALTECH/JPL BE
00023 // LIABLE FOR ANY DAMAGES AND/OR COSTS, INCLUDING BUT NOT LIMITED TO
00024 // INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING ECONOMIC
00025 // DAMAGE OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF
00026 // WHETHER CALTECH/JPL SHALL BE ADVISED, HAVE REASON TO KNOW, OR IN
00027 // FACT SHALL KNOW OF THE POSSIBILITY.  THE USER BEARS ALL RISK
00028 // RELATING TO QUALITY AND PERFORMANCE OF THE SOFTWARE AND/OR RELATED
00029 // MATERIALS.
00030 //
00031 // Change history:
00032 // 7/26/99:  Changed to use functions from newton.h vice newt_raph.h
00033 // 12/29/98: Added iteration counter; balance parameters
00034 // 12/18/98: Some reorganization to make more modular
00035 // 11/11/98: Changed table access to new syntax
00036 // 10/5/98:  Adding code for smart rebuilding of data structures
00037 // 9/14/98:  Added balance_init_flag checks to _balance().
00038 // 9/9/98:   added LO freq checks to mixer::balancer::init()
00039 // 8/28/98:  fixed calculations so device::f can safely shadow another
00040 // 7/24/98:  fully debugged; debug code removed.
00041 // 7/22/98:  code completed.
00042 
00043 #include "mixer.h"
00044 #include "units.h"
00045 #include "error.h"
00046 #include "sources.h"
00047 #include <math.h>
00048 
00049 // ********************************************************************
00050 // balancer public functions:
00051 
00052 mixer::balancer::balancer(mixer & m) :
00053   mix(m),
00054   must_rebuild(1),
00055   ival(0,Index_C),
00056   iter(0),
00057   pY(0),
00058   linear(0)
00059 { max_iter = 100; }
00060 
00061 
00062 // operator(): saves the current junction operating states, then attempts
00063 // to perform a harmonic balance. If it succeeds, it returns 0 and clears
00064 // mixer::balance_not_ok_flag; if it fails, it restores the saved states
00065 // and returns 1, also setting mixer::balance_not_ok_flag.
00066 
00067 int mixer::balancer::operator()()
00068 {
00069   // need a complete mixer to do this
00070   if (mix.flag_mixer_incomplete())
00071     error::fatal("Must correctly add all elements before\
00072  calling mixer::balance().");
00073 
00074   if((mix.num_junctions != 0) && (mix.LO <= 0))
00075     error::fatal("Must have a positive LO frequency during\
00076  mixer::balance().");
00077 
00078   // if there are no junctions, no work need be done
00079   if (mix.num_junctions == 0) {
00080     mix.balance_not_ok_flag = 0;
00081     mix.LO_saved = mix.LO;
00082     return 0;
00083   }
00084 
00085   Matrix Vsave; mix.save_operating_state(Vsave);  // in case of failure
00086 
00087   if (mix.balance_init_flag) i_state();
00088 
00089   init();
00090   solve();
00091 
00092   if (no_solution()&& !mix.balance_init_flag) {
00093     // solver failed, try again from a basic operating state:
00094     i_state();
00095     init();
00096     solve();
00097   }
00098   if (no_solution()) {
00099     // balance still fails, out of ideas. Restore original state
00100     mix.initialize_operating_state(Vsave);
00101     mix.balance_not_ok_flag = 1;
00102     return 1;
00103   }
00104 
00105   // balance worked if we get here.
00106   mix.balance_not_ok_flag = 0;
00107   return 0;
00108 }
00109 
00110 
00111 void mixer::balancer::i_state()
00112 {
00113   // if there are no junctions, no work need be done
00114   if (mix.num_junctions == 0) return;
00115 
00116   // save away LO frequency used for operating state calculations
00117   mix.LO_saved = mix.LO;
00118 
00119   // rebuild data structures if needed and then fetch linear data
00120   rebuild();
00121   fill_data();
00122   
00123   // Build an array of state voltages (rows:junctions; columns:harmonics).
00124   // we call the global solve() (in matmath.h), not newton::solve():
00125   Matrix Vs(mix.num_junctions, mix.max_harmonics + 1, Index_1, Index_C);
00126   Matrix I = identity_matrix(mix.num_junctions, Index_1);
00127   
00128   Vs.fillcol(0, ::solve((I - linear[0].S), linear[0].B));  // DC voltages
00129 
00130   for(int n = 1; n <= mix.max_harmonics; ++n)
00131     Vs.fillcol(n, ::solve((I - linear[n].S), linear[n].B));
00132 
00133   // Finally call each junction's large_signal() code:
00134   for(int n = 0; n < mix.num_junctions; ++n)
00135     mix.junc[n]->large_signal(row(n+1,Vs), mix.LO_saved, mix.max_harmonics);
00136 }
00137 
00138 
00139 mixer::balancer & mixer::balancer::parameters
00140 (int m, double tf, double tm, double tx, double a)
00141 {
00142   max_iter = m;    // maximum number of iterations
00143   f_tol = tf;      // single voltage balance error tolerance
00144   F_tol = tm;      // variance of all voltages from balance tolerance 
00145   dx_tol = tx;     // change in a single voltage step tolerance
00146   rate_factor = a; // if variance of all voltages changes by less than
00147                    //   this, assume at a local minimum of the error function
00148 
00149   return *this;
00150 }
00151 
00152 
00153 // ********************************************************************
00154 // balancer private helper functions (matrix element access):
00155 
00156 // index(): given junction n and harmonic m, find the index of this
00157 // entry in a representation vector like xlast
00158 
00159 inline int mixer::balancer::index(int n, int m)
00160 {
00161   return rep.junc_inc * n + rep.harm_inc * m;
00162 }
00163 
00164 
00165 // to_rep(): enter the harmonic state data for junction n, contained in
00166 // state, into the representation vector V at the correct locations
00167 
00168 void mixer::balancer::to_rep
00169 (real_vector & V, const Vector & state, int n)
00170 {
00171   register int real_index = index(n, 0);
00172   register int imag_index = real_index + rep.imag_inc;
00173   for (register int m = 0; m <= mix.max_harmonics; ++m) {
00174     complex v = state.read(m);
00175     V[real_index] = v.real;
00176     V[imag_index] = v.imaginary;
00177     real_index += rep.harm_inc;
00178     imag_index += rep.harm_inc;
00179   }
00180 }
00181 
00182 
00183 // fm_rep(): construct the harmonic state data for junction n into
00184 // state, by reading the representation vector V at the correct locations
00185 
00186 void mixer::balancer::fm_rep
00187 (Vector & state, const real_vector & V, int n)
00188 {
00189   register int real_index = index(n, 0);
00190   register int imag_index = real_index + rep.imag_inc;
00191   for (register int m = 0; m <= mix.max_harmonics; ++m) {
00192     state.get(m) = complex(V[real_index], V[imag_index]);
00193     real_index += rep.harm_inc;
00194     imag_index += rep.harm_inc;
00195   }
00196 }
00197 
00198 
00199 // B(): fetch the source vector element for junction n at harmonic m
00200 // from the array of linear device sdatas
00201 
00202 inline complex mixer::balancer::B(int n, int m)
00203 { return linear[m].B.read(n+1); }
00204 
00205 
00206 // S(): fetch the S matrix element S(n1,n2) connecting junctions n1
00207 // and n2 at harmonic m from the array of linear device sdatas
00208 
00209 inline complex mixer::balancer::S(int n1, int n2, int m)
00210 { return linear[m].S.read(n1+1, n2+1); }
00211 
00212 
00213 // Y(): fetch the Y matrix element Y(m1,m2) connecting harmonicss m1
00214 // and m2 at junction n from the array of junction Y(m1,m2) pointers
00215 
00216 inline complex mixer::balancer::Y(int n, int m1, int m2)
00217 { return pY[n]->read(m1, m2); }
00218 
00219 
00220 // SV(): calculate the (n,m)th element of the product S*V, ie: at harmonic
00221 // m, calculate Sum( S(n,n')*V(n') ), where the S matrix comes from the
00222 // array of linear sdatas, and the vector V is a representation vector
00223 // such as xlast; only those elements of V associated with harmonic m
00224 // are used; n and n' are junction indexes
00225 
00226 complex mixer::balancer::SV(int n, int m, const real_vector & V)
00227 {
00228   complex result = 0;
00229   complex * S = linear[m].S[n+1];
00230 
00231   int index_r = index(0,m), index_i = index_r + rep.imag_inc;
00232   for (register int i = 1; i <= mix.num_junctions; ++i) {
00233     result += S[i]*complex(V[index_r], V[index_i]);
00234     index_r += rep.junc_inc; index_i += rep.junc_inc;
00235   }
00236 
00237   return result;
00238 }
00239 
00240 
00241 // ********************************************************************
00242 // helper functions limited to this source file:
00243 
00244 
00245 // Kronecker Delta function:
00246 static inline int Delta(int i, int j)
00247 { return (i == j); }
00248 
00249 // various linear combinations of Ynm and Yn-m, scaled by Z0
00250 static inline complex YpY(int n, int m, const Matrix & Y)
00251 { return device::Z0 * (Y[n][m] + Y[n][-m]); }
00252 
00253 static inline complex YmY(int n, int m, const Matrix & Y)
00254 { return device::Z0 * (Y[n][m] - Y[n][-m]); }
00255 
00256 // scaled Yom
00257 static inline complex Yom(int m, const Matrix & Y)
00258 { return device::Z0 * Y[0][m]; }
00259 
00260 
00261 // ********************************************************************
00262 // init(): performs the following tasks in preparation for calling
00263 //         the nonlinear solver routine:
00264 // 
00265 // 1. establishes the data representation used in xlast and fills
00266 //    the members of rep
00267 //
00268 // 2. declares and builds the RF balance circuit by terminating RF ports
00269 //    not connected to junctions using balance terminators or default Z0
00270 //    terminations, which it also creates
00271 //
00272 // 3. Properly size the balancer data members used by the nonlinear solver,
00273 //    using the representation established in 1.
00274 //
00275 // 4. performs a get_data() on the bias circuit, then get_data() on the
00276 //    circuit created in 2 at each LO harmonic frequency. The sdata values
00277 //    are copied into the mixer linear device data arrays. The sdata::B
00278 //    vectors are converted to voltages by appropriate scaling
00279 //
00280 // 5. Fill xlast with the initial junction operating state data
00281 //
00282 // 6. Set nonlinear solver control variable values
00283 //
00284 // Uses rebuild() for tasks 1, 2, 3; fill_data() for task 4. 
00285 
00286 void mixer::balancer::init()
00287 {
00288   int n;  // a loop index used variously below
00289 
00290   // if necessary, rebuild data structures:
00291   rebuild();
00292 
00293   // Calculate the linear circuit sdata's:
00294   fill_data();
00295 
00296   // save away LO frequency used for operating state calculations
00297   mix.LO_saved = mix.LO;
00298 
00299   // now build xlast from the current junction state vectors
00300   for (n = 0; n < mix.num_junctions; ++n) {
00301     to_rep(xlast, mix.junc[n]->V(), n);
00302   }
00303 
00304   // now set the maximum allowable step size and reset iteration count
00305   maxstep = 10;
00306   iter = 0;
00307 
00308 } // mixer::balancer::init()
00309 
00310 
00311 // ********************************************************************
00312 // rebuild() and fill_data():
00313 
00314 // rebuild(): performs the following data allocation tasks, if
00315 //            must_rebuild flag is set, then resets that flag.
00316 // 
00317 // 1. establishes the data representation used in xlast and fills
00318 //    the members of rep
00319 //
00320 // 2. declares and builds the RF balance circuit by terminating RF ports
00321 //    not connected to junctions using balance terminators or default Z0
00322 //    terminations, which it also creates
00323 //
00324 // 3. Properly size the balancer data members used by the nonlinear solver,
00325 //    using the representation established in 1.
00326 
00327 void mixer::balancer::rebuild()
00328 {
00329   if (must_rebuild) {
00330     int n;
00331     
00332     // establish the internal vector representation scheme
00333     rep.length   = 2 * mix.num_junctions * (mix.max_harmonics + 1);
00334     rep.harm_inc = 2 * mix.num_junctions;
00335     rep.junc_inc = 2;
00336     rep.imag_inc = 1;
00337     
00338     // properly size the balancer data members:
00339     xlast.reallocate(rep.length,Index_C);
00340     fval.reallocate(rep.length,Index_C);
00341     ival.reallocate(rep.length, Index_C);
00342     Jacobian.reallocate(rep.length, rep.length, Index_C, Index_C);
00343     pY.resize(mix.num_junctions);
00344     linear.resize(mix.max_harmonics + 1);
00345     int num_ports = mix.rf_circuit->size() - mix.num_junctions;
00346     //    default_term.resize(num_ports);  // our Z0 terminations
00347 
00348     // Build the RF balance circuit into temp, using terminators.
00349     temp = circuit();  // clear out old construction
00350     for (n = 0; n < num_ports; ++n) {
00351       int p = mix.num_junctions + n + 1;
00352       temp.connect(*(mix.rf_circuit), p, 
00353                    ((mix.term[p-1])? *(mix.term[p-1]) : mix.default_term[n]), 1);
00354     }
00355     for (n = 1; n <= mix.num_junctions; ++n)
00356       temp.add_port(*(mix.rf_circuit), n);
00357 
00358     must_rebuild = 0;
00359   } // if(must_rebuild)
00360 }
00361 
00362 
00363 // fill_data():
00364 //    performs a get_data_S() on the bias circuit, then get_data_S() on the
00365 //    circuit created in 2 at each LO harmonic frequency. The sdata values
00366 //    are copied into the mixer linear device data arrays. The sdata::B
00367 //    vectors are converted to voltages by appropriate scaling
00368 
00369 void mixer::balancer::fill_data()
00370 {
00371   parameter IF_saved = device::f;
00372   double LO = mix.LO;
00373   double B_factor = 2 * sqrt(device::Z0);  // convert sdata::B units to voltages
00374 
00375   const sdata * ps;   // will point to various get_data() results
00376 
00377   device::f = 0;      // DC bias
00378 
00379   ps = & mix.bias_circuit->get_data_S();  // don't need noise data
00380   linear[0].S = ps->S;
00381   linear[0].B = ps->B;
00382   linear[0].B *= B_factor;
00383 
00384   device::f = LO;
00385   for (int n = 1; n <= mix.max_harmonics; ++n, device::f += LO ) {
00386     ps = & temp.get_data_S();  // holds harmonic n sdata
00387     linear[n].S = ps->S;
00388     linear[n].B = ps->B;
00389     linear[n].B *= B_factor;
00390   }
00391   device::f = IF_saved;
00392 }
00393 
00394 
00395 // ********************************************************************
00396 // calc(): called by the nonlinear solver during each iteration, it
00397 // uses the junction voltages in xlast to set the junction operating
00398 // states, then calls junction::large_signal() and small_siganl(). It
00399 // uses the results of these calls to fill fval and Jacobean.
00400 
00401 void mixer::balancer::calc()
00402 {
00403   int n, m;    // common loop indices
00404   ++iter;      // increment iteration counter
00405 
00406   // fill ival and a vector of pointers to small signal admittance matrices,
00407   // one per junction.
00408 
00409   Vector V(mix.max_harmonics+1, Index_C);  // holds the voltages for a junction
00410   for (n = 0; n < mix.num_junctions; ++n) {
00411     // fill V with junction n's state voltages:
00412     fm_rep(V, xlast, n);
00413     // then use V to get state currents into ival
00414     to_rep(ival, 
00415            mix.junc[n]->large_signal(V, mix.LO_saved, mix.max_harmonics), n);
00416 
00417     // next get the small signal admittance matrix
00418     pY[n] = & mix.junc[n]->small_signal(0, mix.max_harmonics);
00419   }
00420   //convert units of ival to voltage from current:
00421   ival *= device::Z0;
00422 
00423 
00424   // use the currents, voltages, and linear sdata objects to calculate
00425   // the error vector fval: f(V) = I + V + S(I - V) - B
00426 
00427   fval = ival + xlast;  // f(V) = I + V
00428   ival -= xlast;           // now use ival to hold I - V
00429 
00430   // loop over junctions and harmonics:
00431   int index_r, index_i;
00432   for (n = 0; n < mix.num_junctions; ++n) {
00433     index_r = index(n,0), index_i = index_r + rep.imag_inc;
00434     for (m = 0; m <= mix.max_harmonics; ++m) {
00435       complex sum = SV(n,m,ival) - B(n,m);  // S(I - V) - B
00436       fval[index_r] += sum.real;
00437       fval[index_i] += sum.imaginary;
00438       index_r += rep.harm_inc; index_i += rep.harm_inc;
00439     }
00440   }
00441   // finished: fval = f(V) = I + V + S(I - V) - B
00442 
00443 
00444   // use the admittance matrices and sdata objects to calculate Jacobian:
00445 
00446   int n2, m2;  // two more loop indices
00447   for (n = 0; n < mix.num_junctions; ++n) {
00448     for (n2 = 0; n2 < mix.num_junctions; ++n2) {
00449       int dn = Delta(n,n2);
00450       const Matrix & Y = *(pY[n2]);
00451       for (m = 0; m <= mix.max_harmonics; ++m) {
00452         int Lindex_r = index(n,m), Lindex_i = Lindex_r + rep.imag_inc;
00453         for (m2 = 0; m2 <= mix.max_harmonics; ++m2) {
00454           int dm = Delta(m,m2);
00455           int Rindex_r = index(n2,m2), Rindex_i = Rindex_r + rep.imag_inc;
00456           complex Iprime, A;
00457 
00458           if (m == 0) {
00459             Iprime = Yom(m2,Y);
00460             A = dn*(Iprime + dm) + S(n,n2,m)*(Iprime - dm);  // A(n,n2,0,m2)
00461             Jacobian[Lindex_r][Rindex_r] = A.real;
00462             Jacobian[Lindex_i][Rindex_r] = 0;
00463             Jacobian[Lindex_r][Rindex_i] = -A.imaginary;
00464             Jacobian[Lindex_i][Rindex_i] = 0;
00465           }
00466           else {
00467             Iprime = YpY(m, m2, Y);
00468             A = dn*(Iprime + dm) + S(n,n2,m)*(Iprime - dm);  // A(n,n2,m,m2);r
00469             Jacobian[Lindex_r][Rindex_r] = A.real;
00470             Jacobian[Lindex_i][Rindex_r] = A.imaginary;
00471 
00472             Iprime = YmY(m, m2, Y);
00473             A = dn*(Iprime + dm) + S(n,n2,m)*(Iprime - dm);  // A(n,n2,m,m2);i
00474             Jacobian[Lindex_r][Rindex_i] = -A.imaginary;
00475             Jacobian[Lindex_i][Rindex_i] = A.real;
00476           }
00477         }  // m2 loop
00478       }    // m  loop
00479     }      // n2 loop
00480   }        // n  loop
00481 
00482   // fix up the singularities in Jacobian for the imaginary parts of the
00483   // junctions' DC currents (just putting 1's in the diagonal elements):
00484 
00485   index_i = index(0,0) + rep.imag_inc;
00486   for (n = 0; n < mix.num_junctions; ++n, index_i += rep.junc_inc)
00487     Jacobian[index_i][index_i] = 1;
00488 
00489 } // mixer::balancer::calc()

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