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
1.2.7