00001 // matchivfts.cc 00002 // Optimize parameters to simultaneously match laboratory pumped IV and 00003 // Fourier Transform Spectrometer (FTS) response data, to evaluate the 00004 // deviation between design and production receiver characteristics. 00005 // 00006 // Familiarize yourself with hotcold.cc before studying this example, 00007 // which uses the same mixer model (specs.h and build_mixer.inc). 00008 // 00009 // Review the descriptions of the fts_match and iv_match error term 00010 // classes found in the SuperMix header file error_terms.h 00011 00012 #include "specs.h" 00013 00014 00015 int main(int argc, char** argv) 00016 { 00017 00018 # include "build_mixer.inc" 00019 00020 // ========================================================================== 00021 // Information regarding the FTS and IV match 00022 00023 // The FTS measured data info: 00024 char *ftsname = "fts.data"; // file with the FTS data set 00025 double Vbias = 2.4*mVolt; // bias voltage for the data set 00026 double power = 1*Nano*Watt; // LO power to simulate FTS 00027 00028 // The FTS match RF frequency range: 00029 double LO_min = 500*GHz; 00030 double LO_max = 1300*GHz; 00031 double LO_step = 20*GHz; 00032 00033 00034 // The Pumped IV measured data info: 00035 char *ivname = "pumpediv.data"; // file with the IV data set 00036 double freq = 750*GHz; // LO freq for the data 00037 00038 // The Pumped IV match bias voltage range: 00039 double V_min = -VGAP * 1.2; 00040 double V_max = VGAP * 1.2; 00041 double V_step = 0.2*mVolt; 00042 00043 00044 // ========================================================================== 00045 // Error function declarations and Parameters to Optimize 00046 00047 error_func ef; 00048 00049 // Here are the physical SIS parameters which we figure might be different 00050 // from their design values and which could significantly affect the 00051 // measured behavior of the circuit. We'll use the optimizer to get an 00052 // estimate of their actual values by matching the model to the measured 00053 // data. We set the initial value of each parameter to its current value, 00054 // assigned in specs.h 00055 00056 AREA = ef.vary(AREA*0.7, AREA, AREA*1.3); 00057 RNA = ef.vary( RNA*0.8, RNA, RNA*1.2); 00058 00059 00060 // Since our lab setup for the pumped IV measurement had no capability 00061 // to determine the LO power, we must include it as a parameter to be 00062 // determined during the optimization: 00063 00064 LO_POWER = ef.vary(50, 100, 200, Nano*Watt); 00065 00066 00067 // Lastly, the measured IV data has unknown current and voltage offset 00068 // errors. We include these as additional parameters controlled by the 00069 // optimizer: 00070 00071 parameter I_off = 0.0, V_off = 0.0; 00072 I_off = ef.vary( -10, 0, 10, Micro*Amp), 00073 V_off = ef.vary( -.1, 0, .1, mVolt); 00074 00075 00076 // ========================================================================== 00077 // The FTS match sweeper and error term 00078 00079 00080 // The FTS match sweeper needs to control two parameters: 00081 // (1) Set the SIS bias voltage to the value used in the data set 00082 // (2) Sweep the LO frequency over the RF range of the FTS data 00083 // 00084 // We need to set the bias voltage because the sweeper for the pumped 00085 // IV term will be changing it. We need to keep setting it back to 00086 // the FTS value with each iteration of the FTS sweep. The initialize() 00087 // member function was included for just such a contingency. As many 00088 // parameters as you need may be reset at each sweeper iteration by 00089 // adding more initialize() calls for the sweeper setup. 00090 00091 sweeper fts_sweep; 00092 fts_sweep.initialize(V_BIAS, Vbias); 00093 fts_sweep.sweep(LO_FREQ, LO_min, LO_max, LO_step); 00094 00095 00096 // The FTS match error term is a special one designed to be used 00097 // specifically to match measured FTS data (see error_terms.h). Given 00098 // an FTS data file with an arbitrary scale factor in its response, 00099 // it compares the calculated mixer DC bias current response from the 00100 // model to the FTS file data. Note that in the code below we directly 00101 // access the interpolator of the FTS data to change it to linear 00102 // interpolation mode. The purpose of the f_correct() member function 00103 // is described in error_terms.h 00104 00105 fts_match fts_term(mix, LO_FREQ, LO_POWER, ftsname, GHz, power); 00106 fts_term.f_correct(); 00107 fts_term.measured.linear().build(); // since data is noisy, don't spline 00108 00109 // Add the term to the error function. Since class fts_match always 00110 // returns an error value for the curve match between 0 and 1, we 00111 // use the error term weight (100 in this case) to scale its return value. 00112 ef.add_term(100.0, fts_term, fts_sweep); 00113 00114 00115 // ========================================================================== 00116 // The IV match sweeper and error term 00117 00118 // The IV match sweeper needs to control two parameters: 00119 // (1) Set the LO frequency to the value used in the data set 00120 // (2) Sweep the SIS bias voltage over the bias range of the data 00121 // 00122 // We need to set the LO frequency because the sweeper for the FTS term 00123 // will be changing it. We need to keep setting it back to the proper 00124 // value with each iteration of the IV sweep. Again, the initialize() 00125 // member function was included for just such a contingency. 00126 00127 sweeper iv_sweep; 00128 iv_sweep.sweep(V_BIAS, V_min, V_max, V_step); 00129 iv_sweep.initialize(LO_FREQ, freq); 00130 00131 00132 // The IV match error term is another special one whose class is 00133 // described in error_terms.h Again we directly access the data 00134 // interpolator to use linear interpolation. Note that we have to 00135 // call build() following the call to linear() in order to actually 00136 // affect the behavior of the interpolator. We also set the offsets to 00137 // shadow the parameters the optimizer will be controlling. 00138 00139 iv_match iv_term(mix, V_BIAS, ivname, mVolt, Micro*Amp); 00140 iv_term.measured.linear().build(); 00141 iv_term.i_offset(&I_off); 00142 iv_term.v_offset(&V_off); 00143 00144 // Add the iv term to the error function. The weight should result in an 00145 // error term value of ~1.0 for an average error of 1 uA. 00146 ef.add_term(1.0/(Micro*Amp*Micro*Amp), iv_term, iv_sweep); 00147 00148 00149 // ========================================================================== 00150 // Optimize: 00151 00152 // We use the powell optimizer since we expect the design values to be 00153 // pretty close to the actual values. A local optimizer should be just what 00154 // we need, rather than a global optimizer like montecarlo. See the example 00155 // file lna/lna_opt.annotated.cc for more details about using powell. 00156 00157 powell opt(ef); 00158 opt.verbose(); 00159 opt.FTOL = 0.0001; 00160 00161 // Initial error function stats: 00162 cerr << "Initial error function and parameter stats:" << endl; 00163 cerr << ef() << " : "; 00164 cerr << ef.get_func_breakdown() << endl 00165 << "Parameter values: " << ef.get_parms_user() << endl; 00166 00167 cerr << "Starting optimization. This may take a while..." << endl; 00168 double final_error = opt.minimize(); 00169 00170 // final error function stats: 00171 cerr << "Final error function and parameter stats:" << endl; 00172 cerr << final_error << " : " << ef.get_func_breakdown() << endl 00173 << "Parameter values: " << ef.get_parms_user() << endl; 00174 00175 // ========================================================================== 00176 // Here we generate and output the optimized response 00177 00178 Vector currents; // Used to temporarily hold SIS bias currents 00179 00180 // Output a nice header giving details and the final parameter values 00181 // found by the optimizer: 00182 00183 cout << fixed << setprecision(3); 00184 cout << "# " << argv[0] << ":" << endl 00185 << "# Match Pumped IV and FTS response" << endl 00186 << "# IV data file: " << ivname << endl 00187 << "# FTS data file: " << ftsname << endl 00188 << "# Control Parameter Values:" << endl 00189 << "# LO freq = " << LO_FREQ/GHz << " GHz" << endl 00190 << "# Min Bias = " << V_min/mVolt << " mV" << endl 00191 << "# Max Bias = " << V_max/mVolt << " mV" << endl 00192 << "# FTS Bias = " << Vbias/mVolt << " mV" << endl 00193 << "# FTS LO pwr = " << power/(Nano*Watt) << " nW" << endl 00194 << "# Optimized Parameters:" << endl 00195 << "# SUB1_T = " << SUB1_T/Angstrom << " Angstrom" << endl 00196 << "# AREA = " << AREA/(Micron*Micron) << " sq uM" << endl 00197 << "# RNA = " << RNA/(Ohm*Micron*Micron) << " Ohm sq uM" 00198 << endl 00199 << "# IV LO pwr = " << LO_POWER/(Nano*Watt) << " nW" << endl 00200 << "# I offset = " << I_off/(Micro*Amp) << " uA" << endl 00201 << "# V offset = " << V_off/(mVolt) << " mV" << endl; 00202 00203 00204 // Now output the pumped IV response using the optimized values: 00205 cout << "# V(mV)" << "\t" << "I(uA)" << endl; 00206 00207 LO_FREQ = freq; 00208 00209 // We sweep over the Bias Voltage range, generating the response 00210 for(V_BIAS = -1.5*VGAP; V_BIAS <= 1.5*VGAP; V_BIAS += 0.02*mVolt) { 00211 00212 // Here's where we calculate the total bias current 00213 mix.balance(); // calculate the SIS operating state 00214 currents = mix.I_junc(0); // vector of DC bias currents 00215 int min = currents.minindex(); 00216 int max = currents.maxindex(); 00217 00218 // Sum the junction currents 00219 double resp = 0.0; 00220 for(int i=min; i<=max; i++) resp += currents[i].real; 00221 00222 // Include offsets in the output, so it can be compared directly 00223 // with the measured data 00224 cout << fixed << setprecision(3) 00225 << (V_BIAS + V_off)/mVolt 00226 << "\t" 00227 << scientific << setprecision(3) << (resp + I_off)/(Micro*Amp) 00228 << endl; 00229 } 00230 00231 00232 // Now for the FTS response 00233 cout << "#" << endl; 00234 cout << "# f(GHz)" << "\t" << "response" << endl; 00235 00236 V_BIAS = Vbias; 00237 00238 // First we need the dark current, which is independent of input RF (LO) 00239 // frequency, since the RF power is zero: 00240 LO_POWER = 0; 00241 mix.balance(); 00242 currents = mix.I_junc(0); 00243 int min = currents.minindex(); 00244 int max = currents.maxindex(); 00245 double dark_current = 0.0; 00246 for(int i=min; i<=max; i++) dark_current += currents[i].real; 00247 00248 // Now we sweep over the LO frequency range, generating the response 00249 LO_POWER = power; 00250 for(LO_FREQ = LO_min; LO_FREQ <= LO_max; LO_FREQ += LO_step) { 00251 mix.balance(); 00252 currents = mix.I_junc(0); 00253 double resp = 0.0; 00254 for(int i=min; i<=max; i++) resp += currents[i].real; 00255 resp -= dark_current; 00256 00257 // Scale the response by a freq "correction" factor, since the data 00258 // set included such a factor: 00259 resp *= LO_FREQ; 00260 00261 // Scale the response so that it matches the FTS data. Use the scale 00262 // value found by fts_term during the final error function calculation. 00263 resp *= fts_term.scale(); 00264 00265 // Output the generated response 00266 cout << fixed << setprecision(0) << LO_FREQ/GHz 00267 << "\t" 00268 << scientific << setprecision(2) << resp 00269 << endl; 00270 } 00271 00272 }
Please direct comments and corrections to
supermix@submm.caltech.edu
Go to the supermix home page
Generated by
1.2.7