PageRenderTime 62ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/aoutil.i

http://github.com/frigaut/yao
Swig | 2096 lines | 1604 code | 237 blank | 255 comment | 0 complexity | 988f086c7ede4b81a2d9e86fb00c9681 MD5 | raw file
Possible License(s): GPL-2.0
  1. /* AOUTIL.I
  2. *
  3. * A collection of utility routines to go with yao.i
  4. *
  5. * This file is part of the yao package, an adaptive optics simulation tool.
  6. *
  7. * Copyright (c) 2002-2017, Francois Rigaut
  8. *
  9. * This program is free software; you can redistribute it and/or modify it
  10. * under the terms of the GNU General Public License as published by the
  11. * Free Software Foundation; either version 2 of the License, or (at your
  12. * option) any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful, but
  15. * WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  17. * General Public License for more details (to receive a copy of the GNU
  18. * General Public License, write to the Free Software Foundation, Inc., 675
  19. * Mass Ave, Cambridge, MA 02139, USA).
  20. *
  21. */
  22. func yao_tricks(void)
  23. {
  24. write,format="%-78s\n","FLAGS:";
  25. txt = "dispImImav: Image (PSF) display: (0) inst. PSF (1): avg PSF (2) phases";
  26. grow,txt,"display_phase_not_wfs: (bool) Display phases instead of WFS images";
  27. write,format="%-78s\n",txt;
  28. write,"";
  29. write,format="%-78s\n","HOOKS: (see code in yao.i, go() function)";
  30. txt = "go_user_func: called at the start of go(), before entering the loop is go has been called with all=1";
  31. grow,txt,"go2_user_func: called at the start of go(), after entering the loop is go has been called with all=1";
  32. grow,txt,"user_loop_err: called after calculation of err from measurements";
  33. grow,txt,"user_loop_command: called after calculation of command vector";
  34. grow,txt,"";
  35. write,format="%-78s\n",txt;
  36. write,"";
  37. }
  38. func init_image_display(void)
  39. {
  40. if (target._ntarget == 1) {
  41. // if only one target, display correct plate scale for image display
  42. *target.dispzoom = [(*target.lambda)(0)*1e-6/4.848e-6/
  43. tel.diam*sim.pupildiam/sim._size/2*sim._size];
  44. // last 2 terms to accomodate internal dispzoom scaling
  45. }
  46. txpos = *target.xposition;
  47. typos = *target.yposition;
  48. disp2d,im,txpos,typos,1,zoom=*target.dispzoom,init=1;
  49. for (j=1;j<=nwfs;j++) {
  50. // plg,wfs(j).gspos(2),wfs(j).gspos(1),marker='\2',
  51. // type="none",marks=1,color="red";
  52. if (wfs(j).gsalt==0) plp,wfs(j).gspos(2),wfs(j).gspos(1),symbol=8,color="fg",width=2,size=0.6;
  53. else if (wfs(j).gsalt>50000) plp,wfs(j).gspos(2),wfs(j).gspos(1),symbol=8,color="orange",width=2,size=0.6;
  54. else plp,wfs(j).gspos(2),wfs(j).gspos(1),symbol=8,color="green",width=2,size=0.6;
  55. }
  56. myxytitles,"","arcsec",[0.005,0.01],height=12;
  57. plmargin;
  58. if (wfs_display_mode=="spatial") {
  59. disp2d,wfs._dispimage,wfs.pupoffset(1,),wfs.pupoffset(2,),2,\
  60. zoom=wfs.dispzoom,init=1;
  61. } else {
  62. disp2d,wfs._dispimage,wfs.gspos(1,),wfs.gspos(2,),2,zoom=wfs.dispzoom,\
  63. init=1;
  64. }
  65. return 0;
  66. }
  67. func create_yao_window(dpi)
  68. /* DOCUMENT create_yao_window(dpi)
  69. Open or re-open (e.g. after a fork() ) the main and only yao graphical
  70. window.
  71. SEE ALSO:
  72. */
  73. {
  74. // dpi is in fact already stored in extern (sigh)
  75. if (!dpi) dpi=default_dpi;
  76. if (window_exists(2)) winkill,2;
  77. if (yao_pyk_parent_id) {
  78. // there's a GUI, re-parent within GUI drawingarea:
  79. yao_win_init,yao_pyk_parent_id;
  80. } else {
  81. // there's no GUI, re-open normal graphical window:width=635,height=650
  82. // check if there's one existing of the right size. I'm fed up to see
  83. // yao window pop up everywhere ;-(
  84. need2open = 0;
  85. // is there a window 0?
  86. if (!window_exists(0)) {
  87. need2open=1;
  88. // write,"There is no window 0, need to create";
  89. } else {
  90. // is the size allright?
  91. wg = long(window_geometry(0));
  92. // write,wg(-1:0);
  93. // write,long([635*(dpi/75.),650*(dpi/75.)]);
  94. if (nallof(wg(-1:0)==long([635*(dpi/75.),650*(dpi/75.)]))) {
  95. need2open=1;
  96. } else {
  97. // is there 5 plsys, in which case it's probably a yao window?
  98. window,0;
  99. get_style,l,sys;
  100. if (numberof(sys)!=5) need2open=1;
  101. }
  102. }
  103. if (need2open) {
  104. if (window_exists(0)) winkill,0;
  105. window,0,style="yao.gs",dpi=dpi,width=long(635*(dpi/75.)), \
  106. height=long(650*(dpi/75.)),wait=1;
  107. if ( (xft!=[]) && (xft()) ) {
  108. get_style, landscape, systems, legends, clegends;
  109. systems.ticks.vert.textStyle.height(4)*=1.5;
  110. systems.ticks.horiz.textStyle.height(4)*=1.5;
  111. set_style, landscape, systems, legends, clegends;
  112. }
  113. }
  114. }
  115. plsys,1; limits; limits,square=1;
  116. plsys,2; limits; limits,square=1;
  117. }
  118. //----------------------------------------------------
  119. func xyxy2xxyy(void)
  120. {
  121. offset=0;
  122. for (n=1;n<=nwfs;n++) {
  123. tmp = indgen(wfs(n)._nsub)*2;
  124. tmp = _(tmp,tmp+1);
  125. grow,reordered,tmp+offset;
  126. offset+=wfs(n)._nmes;
  127. }
  128. return reordered;
  129. }
  130. func xxyy2xyxy(void)
  131. {
  132. offset=0;
  133. for (n=1;n<=nwfs;n++) {
  134. grow,reordered,transpose(reform(indgen(wfs(n)._nmes),
  135. [2,wfs(n)._nsub,2]))(*)+offset;
  136. offset+=wfs(n)._nmes;
  137. }
  138. return reordered;
  139. }
  140. //----------------------------------------------------
  141. func get_turb_phase_initCheckOverflow(void)
  142. /* DOCUMENT func get_turb_phase_initCheckOverflow
  143. This routine has the sole purpose of checking the possible "overflow"
  144. of the Y index in the future calls to get_turb_phase. In any of the
  145. Y index at which the interpolation is to be done is larger than the
  146. phase screen(s) Y dimension, then an error is flagged.
  147. SEE ALSO: get_turb_phase_init, get_turb_phase
  148. */
  149. {
  150. extern screendim;
  151. dimy = screendim(2);
  152. if (max(wfsyposcub(max,,)+yposvec(max,)(,-)) > dimy) {
  153. write,"\nSome of the phase screens are too small (Y dimension) "+
  154. "for the specified system.";
  155. write,format="The following WFS/screens will cause a "+
  156. "index overflow (current Ydim is %d):\n",dimy;
  157. maxind = wfsyposcub(max,,)+yposvec(max,)(,-);
  158. w2 = where2(maxind > dimy);
  159. for (i=1;i<=dimsof(w2)(3);i++) {
  160. write,format="WFS#%d, screen#%d, Ymax=%f\n",w2(2,i),w2(1,i),
  161. maxind(w2(1,i),w2(2,i));
  162. }
  163. write,"To remedy this situation, you can either:";
  164. write," - use larger phase screens (Y axis) using 'create_phase_screens'";
  165. write," - Modify the extremum Y offsets of your WFSs";
  166. write," - Lower the altitude of the offending atmospheric layer";
  167. exit;
  168. }
  169. if (max(gsyposcub(max,,)+yposvec(max,)(,-)) > dimy) {
  170. write,"\nSome of the phase screens are too small (Y dimension) "+
  171. "for the specified system.";
  172. write,format="The following Perf Star/screens will cause a "+
  173. "index overflow (current Ydim is %d):\n",dimy;
  174. maxind = gsyposcub(max,,)+yposvec(max,)(,-);
  175. w2 = where2(maxind > dimy);
  176. for (i=1;i<=dimsof(w2)(3);i++) {
  177. write,format="Perf.Star#%d, screen#%d, Ymax=%f\n",w2(2,i),w2(1,i),
  178. maxind(w2(1,i),w2(2,i));
  179. }
  180. write,"To remedy this situation, you can either:";
  181. write," - use larger phase screens (Y axis) using 'create_phase_screens'";
  182. write," - Modify the Y position of your Perf. Star";
  183. write," - Lower the altitude of the offending atmospheric layer";
  184. exit;
  185. }
  186. }
  187. //----------------------------------------------------
  188. func graphic_config(subsystemnum,dmnum)
  189. /* DOCUMENT func configGraphic(void)
  190. Plots a graphical representation of the system config,
  191. per subsystem and per level (altitude)
  192. subsystemnum and dmnum optional. If not set all subsystems
  193. and dms are displayed
  194. SEE ALSO:
  195. */
  196. {
  197. t = span(0.,2*pi,100);
  198. col = ["red","blue","green","cyan","magenta","yellow"];
  199. markers = ['1','2','3','4','5','6','7','8','9'];
  200. markers = char(indgen(36)+48); // 1,2,...A,B...
  201. maxcol = numberof(col);
  202. plsys,1;
  203. limits,square=1;
  204. psize = tel.diam/sim.pupildiam;
  205. for (nss=1;nss<=max(dm.subsystem);nss++) {
  206. if ( (subsystemnum != [] ) && (subsystemnum != nss) ) continue;
  207. fma;
  208. // one level by one level
  209. for (i=1;i<=ndm;i++) {
  210. if ( (dmnum != [] ) && (dmnum != i) ) continue;
  211. if ( dm(i).subsystem != nss ) continue;
  212. if ( dm(i).type == "aniso" ) continue;
  213. for (j=1;j<=nwfs;j++) {
  214. if ( wfs(j).subsystem != nss ) continue;
  215. // overplot subaperture position and size for first wfs in this subsystem
  216. first_wfs_plotted = 0;
  217. if ( (first_wfs_plotted==0) && (dm(i).alt==0.) && (dm(i).type!="tiptilt") ) {
  218. for (jj=1;jj<=wfs(j)._nsub4disp;jj++) {
  219. if ((*wfs(j)._validsubs)(jj)==0) continue;
  220. x1 = (*wfs(j)._istart)(jj);
  221. y1 = (*wfs(j)._jstart)(jj);
  222. subsize = sim.pupildiam/wfs(j).shnxsub;
  223. if (wfs(j).npixpersub) subsize = wfs(j).npixpersub;
  224. l1 = subsize;
  225. // convert pixels in meters
  226. x1 = (x1-sim._cent)*tel.diam/sim.pupildiam;
  227. y1 = (y1-sim._cent)*tel.diam/sim.pupildiam;
  228. l1 = l1*tel.diam/sim.pupildiam;
  229. plg,_(y1,y1,y1+l1,y1+l1,y1),_(x1,x1+l1,x1+l1,x1,x1),color=[50,50,50];
  230. }
  231. first_wfs_plotted = 1;
  232. }
  233. }
  234. // radius of outmost actuator in meters:
  235. // rad = (dm(i).nxact-1)/2.*dm(i).pitch*psize;
  236. // plots circle containing all actuators
  237. // plg,rad*sin(t),rad*cos(t),type=2;
  238. // radius of outmost object beam in meter:
  239. tmp = sqrt((*target.xposition)^2.+(*target.yposition)^2.);
  240. maxobjrad = max(tmp);
  241. rad = max(tmp)*4.848e-6*dm(i).alt+tel.diam/2.;
  242. // plots circle containing all rays from objects:
  243. plg,rad*sin(t),rad*cos(t),type=3;
  244. if ( dm(i).type == "stackarray" ) {
  245. // build array containing (x,y) location of valid/controlled actuators:
  246. loc = ([*(dm(i)._x),*(dm(i)._y)]-sim._cent)*psize;
  247. // and plot:
  248. plg,loc(,2),loc(,1),type=0,marker=markers(i),color=col(i%maxcol);
  249. if (numberof(*dm(i)._ex) != 0) {
  250. // build array containing (x,y) location of extrapolated actuators:
  251. loc = ([*(dm(i)._ex),*(dm(i)._ey)]-sim._cent)*psize;
  252. // and plot:
  253. plg,loc(,2),loc(,1),type=0,marker=markers(i);
  254. }
  255. }
  256. // loop on sensor:
  257. for (j=1;j<=nwfs;j++) {
  258. if ( wfs(j).subsystem != nss ) continue;
  259. // computes scaling factor due to limited altitude:
  260. if (wfs(j)._gsalt != 0) {
  261. fact = (wfs(j)._gsalt-dm(i).alt)/wfs(j)._gsalt;
  262. } else {
  263. fact=1;
  264. }
  265. offsets = wfs(j).gspos*4.848e-6*dm(i).alt;
  266. rad=tel.diam/2.*fact;
  267. // plotting beam shape for this wfs at this dm altitude:
  268. plg,offsets(2)+rad*sin(t),offsets(1)+rad*cos(t),color=col(i%maxcol),width=3;
  269. }
  270. myxytitles,"Beam outline [m]","Beam outline [m]",[0.0,0.0];
  271. mypltitle,swrite(format="Patches of guide star beams on DM#%d, Subsystem %d",i,nss);
  272. comment = swrite(format="Configuration actuator/beams in a plane at altitude %.0f\n"+
  273. "Solid lines: Outline of this subsystem GS beams\n"+
  274. "Dotted line: Circle including outermost ray to specified science \n"+
  275. " objects (at %.1f arcsec off-center)\n"+
  276. "Numbers for stackarrays actuators: colored:controlled, BW: extrapolated\n"+
  277. "# of active actuators= %d, # of extrapolated actuators=%d",
  278. dm(i).alt,maxobjrad,dm(i)._nact,dm(i)._enact);
  279. plt,comment,0.06,0.22,tosys=0,justify="LT";
  280. plt,sim.name,0.01,0.227,tosys=0;
  281. limits; lim=limits(); limits,lim(1)*1.1,lim(2)*1.1,lim(3)*1.1,lim(4)*1.1;
  282. hcp;
  283. if (sim.debug >=1) typeReturn;
  284. fma;
  285. }
  286. if ( subsystemnum != [] ) continue;
  287. // all levels
  288. for (i=1;i<=ndm;i++) {
  289. if ( dm(i).subsystem != nss ) continue;
  290. if ( dm(i).type == "aniso" ) continue;
  291. // radius of outmost actuator in meters:
  292. rad = (dm(i).nxact-1)/2.*dm(i).pitch*psize;
  293. // plots circle containing all actuators
  294. plg,rad*sin(t),rad*cos(t),type=i;
  295. if ( dm(i).type == "stackarray" ) {
  296. // build array containing (x,y) location of valid actuators:
  297. loc = ([*(dm(i)._x),*(dm(i)._y)]-sim._cent)*psize;
  298. // and plot:
  299. plg,loc(,2),loc(,1),type=0,marker=markers(i),color=col(i%maxcol);
  300. }
  301. // loop on sensor:
  302. for (j=1;j<=nwfs;j++) {
  303. if ( wfs(j).subsystem != nss ) continue;
  304. // computes scaling factor due to limited altitude:
  305. if (wfs(j)._gsalt != 0) {
  306. fact = (wfs(j)._gsalt-dm(i).alt)/wfs(j)._gsalt;
  307. } else {
  308. fact=1;
  309. }
  310. offsets = wfs(j).gspos*4.848e-6*dm(i).alt;
  311. rad=tel.diam/2.*fact;
  312. // plotting beam shape for this wfs at this dm altitude:
  313. plg,offsets(2)+rad*sin(t),offsets(1)+rad*cos(t),color=col(i%maxcol),width=3;
  314. }
  315. }
  316. myxytitles,"Beam outline [m]","Beam outline [m]",[0.0,0.0];
  317. mypltitle,swrite(format="Patches of guide star beams on DMs, Subsystem %d",nss);
  318. plt,sim.name,0.01,0.227,tosys=0;
  319. limits; lim=limits(); limits,lim(1)*1.1,lim(2)*1.1,lim(3)*1.1,lim(4)*1.1;
  320. hcp;
  321. if (sim.debug >=1) typeReturn;
  322. }
  323. }
  324. //----------------------------------------------------
  325. func check_parameters(void)
  326. /* DOCUMENT func check_parameters(void)
  327. Check the parameters in yao parfile
  328. - set defaults
  329. - value valid?
  330. - compatibility with other parameters
  331. SEE ALSO:
  332. */
  333. {
  334. extern sim,atm,wfs,dm,mat,tel,target,gs,loop;
  335. if (sim.verbose) write,format="Checking parameters ... %s\n","";
  336. //==================================================================
  337. // BASIC EXISTENCE AND CONSISTENCY CHECKINGS AND DEFAULT ASSIGNMENTS
  338. //==================================================================
  339. extern nwfs,ndm,ntarget,noptics;
  340. nwfs = numberof(wfs);
  341. ndm = numberof(dm);
  342. ntarget = numberof(*target.lambda);
  343. noptics = numberof(opt);
  344. // SIM STRUCTURE
  345. if (sim.pupildiam == 0) {exit,"sim.pupildiam has not been set";}
  346. if ( ((sim.svipc>>2)&1) && (!((sim.svipc>>0)&1)) ) {
  347. write,"If you have (sim.svipc>>2)&1, you should have (sim.svipc>>0)&1";
  348. write,"Setting sim.svipc first bit to 1"
  349. sim.svipc++;
  350. pause,2000;
  351. }
  352. if (sim.svipc_wfs_nfork>nwfs) {
  353. write,"sim.svipc_wfs_nfork > nwfs, setting sim.svipc_wfs_nfork = nwfs"
  354. sim.svipc_wfs_nfork = nwfs;
  355. pause,2000;
  356. }
  357. // ATM STRUCTURE
  358. if ((*atm.screen) == []) {exit,"atm.screen has not been set";}
  359. if (typeof(*atm.screen) != "string") {exit,"*atm.screen is not a string";}
  360. // Check that all phase screens are different. If the same file is repeated, this makes the turbulence much worse than what is specified by r0!
  361. ftmp = *atm.screen;
  362. for (i=1;i<=numberof(ftmp);i++){
  363. if (sum(ftmp == (ftmp)(i)) > 1){
  364. write, "atm.screen must not have repeated values";
  365. write, "doing so changes the statistics of the atmospheric turbulence";
  366. error,"exiting";
  367. }
  368. }
  369. for (i=1;i<=numberof(ftmp);i++) {
  370. if (!open(ftmp(i),"r",1)) { // file does not exist
  371. msg = swrite(format="Phase screen %s not found!\\n"+
  372. "You need to generate phase screens for yao.\\n"+
  373. "Go to \"Phase Screen -> Create phase Screen\"\\n"+
  374. "If you already have phase screens, you can modify\\n"+
  375. "the path in the parfile atm.screen definition",ftmp(i));
  376. if (_pyk_proc) pyk,"set_cursor_busy(0)";
  377. pyk_warning,msg;
  378. msg = swrite(format="\nWARNING: %s not found!\nEdit the par file and change the "+
  379. "path,\nand/or run \"Phase Screen -> Create phase Screen\"\n",ftmp(i));
  380. write,msg;
  381. break;
  382. }
  383. }
  384. if ((*atm.layerfrac) == []) {exit,"atm.layerfrac has not been set";}
  385. if ((*atm.layerspeed) == []) {exit,"atm.layerspeed has not been set";}
  386. if ((*atm.layeralt) == []) {exit,"atm.layeralt has not been set";}
  387. if ((*atm.winddir) == []) {exit,"atm.winddir has not been set";}
  388. if (nallof(_(numberof(*atm.screen),numberof(*atm.layerfrac),numberof(*atm.layerspeed), \
  389. numberof(*atm.layeralt)) == numberof(*atm.winddir))) {
  390. exit,"Some elements within atm.screen, layerfrac, layerspeed, layeralt \n"+\
  391. "or winddir do not have the same number of elements.";
  392. }
  393. // WFS STRUCTURE
  394. for (ns=1;ns<=nwfs;ns++) {
  395. if (wfs(ns).type == string()) {
  396. exit,swrite(format="wfs(%d).type has not been set",ns);
  397. }
  398. // if (wfs(ns).subsystem == 0) {wfs(ns).subsystem = 1;}
  399. if (wfs(ns).lambda == 0) {
  400. exit,swrite(format="wfs(%d).lambda has not been set",ns);
  401. }
  402. //~ if (wfs(ns).dispzoom == 0) {wfs(ns).dispzoom = 1;}
  403. if ((wfs(ns).type == "curvature") && ((*wfs(ns).nsubperring) == [])) {
  404. write,format="wfs(%d).nsubperring has not been set.\n",ns;
  405. write,"Valid values are, for example:";
  406. exit,"wfs(n).nsubperring = &([6,12,18]);";
  407. }
  408. if ((wfs(ns).type == "curvature") && (wfs(ns).l == 0)) {
  409. exit,swrite(format="wfs(%d).l has not been set",ns);
  410. }
  411. if ((wfs(ns).type == "curvature") && (wfs(ns).gsdepth > 0)) {
  412. exit,swrite(format="wfs(%d): gsdepth not implemented for CWFS, sorry",ns);
  413. }
  414. if ((wfs(ns).type == "curvature") && (wfs(ns).rayleighflag == 1)) {
  415. exit,swrite(format="wfs(%d): Rayleigh not implemented for CWFS, sorry",ns);
  416. }
  417. if ((wfs(ns).gsalt == 0) && (wfs(ns).rayleighflag == 1)) {
  418. write,swrite(format="wfs(%d): gsalt = 0 and Rayleigh flag is set !",ns);
  419. exit,"Rayleigh is not computed for NGS sensors, sorry.";
  420. }
  421. if ((wfs(ns).gsalt > 0) && (wfs(ns).laserpower == 0)) {
  422. exit,swrite(format="wfs(%d): this is a LGS and you haven't set wfs.laserpower",ns);
  423. }
  424. if ((wfs(ns).type == "curvature") && (wfs(ns).fieldstopdiam == 0)) {
  425. wfs(ns).fieldstopdiam = 1.f;
  426. }
  427. if ((wfs(ns).type == "hartmann") && (wfs(ns).shmethod == 0)) {
  428. exit,swrite(format="wfs(%d).shmethod has not been set",ns);
  429. }
  430. if ((wfs(ns).type == "hartmann") && (wfs(ns).shnxsub == 0)) {
  431. exit,swrite(format="wfs(%d).shnxsub has not been set",ns);
  432. }
  433. if ((wfs(ns).type == "hartmann") && (wfs(ns).shmethod == 2)) {
  434. if ((wfs(ns).type == "hartmann") && (wfs(ns).pixsize == 0)) {
  435. exit,swrite(format="wfs(%d).pixsize has not been set",ns);
  436. }
  437. if ((wfs(ns).type == "hartmann") && (wfs(ns).npixels == 0)) {
  438. exit,swrite(format="wfs(%d).npixels has not been set",ns);
  439. }
  440. if ((wfs(ns).type == "hartmann") && (wfs(ns).shthmethod == 0)) {
  441. wfs(ns).shthmethod = 1;
  442. }
  443. if ((wfs(ns).type == "hartmann") && (wfs(ns).shthmethod == 3) &&
  444. ((wfs(ns).shthreshold > (wfs(ns).npixels)^2) || (wfs(ns).shthreshold <= 0))) {
  445. exit,swrite(format="Wrong wfs(%d).shthreshold value for wfs(%d).shthmethod = %d",ns,ns,wfs(ns).shthmethod);
  446. }
  447. }
  448. if ((wfs(ns).type == "hartmann") && (wfs(ns).shmethod == 2)) {
  449. if ((strlen(wfs(ns).fsname)>0) && (strlen(wfs(ns).fstop)>0)) {
  450. write,format="You have set both wfs(%d).fsname and wfs(%d).fstop\n",ns,ns;
  451. write,format="I will ignore wfs(%d).fstop and use wfs(%d).fsname !\n",ns,ns;
  452. }
  453. if ((strlen(wfs(ns).fsname)==0) && (strlen(wfs(ns).fstop)==0)) {
  454. write,format="No field stop defined for wfs %d. Setting to 'square'\n",ns;
  455. wfs(ns).fstop = "square";
  456. }
  457. if (wfs(ns).fssize==0) {
  458. write,format="wfs(%d).fssize has not been set, will be forced to subap FoV\n",ns;
  459. }
  460. }
  461. if ((wfs(ns).type != "hartmann")&&(wfs(ns).LLT_uplink_turb)) {
  462. write,format="WARNING, wfs#%d: LLT_uplink_turb only implemented for SHWFS, ignoring.\n",ns;
  463. }
  464. if (wfs(ns).LLT_uplink_turb) {
  465. if ((wfs(ns).LLTr0==0)) {
  466. write,format="WARNING, wfs(%d).LLTr0 undefined, Using atm-defined r0\n",ns;
  467. wfs(ns).LLTr0 = tel.diam/atm.dr0at05mic * (wfs(ns).lambda/0.5)^1.2;
  468. }
  469. if ((wfs(ns).LLTdiam==0)) \
  470. error,swrite(format="wfs#%d: LLT_uplink_turb set but LLTdiam not defined",ns);
  471. if ((wfs(ns).LLT1overe2diam==0)) \
  472. error,swrite(format="wfs#%d: LLT_uplink_turb set but LLT1overe2diam not defined",ns);
  473. }
  474. if (wfs(ns).nintegcycles == 0) {wfs(ns).nintegcycles = 1;}
  475. if (wfs(ns).fracIllum == 0) {wfs(ns).fracIllum = 0.5;}
  476. if (wfs(ns).optthroughput == 0) {wfs(ns).optthroughput = 1.0;}
  477. if (wfs(ns).svipc>1) {
  478. if (wfs(ns).type!="hartmann") {
  479. write,format="wfs(%d).svipc >1 only for SHWFS, will have no effect\n",ns;
  480. wfs(ns).svipc = 0;
  481. } else if (wfs(ns).shnxsub < 2) {
  482. write,format="wfs(%d).svipc >1 but SH 1x1 sub. Disabling ||.\n",ns;
  483. wfs(ns).svipc = 0;
  484. }
  485. }
  486. if (wfs(ns).type == "pyramid") {
  487. if ((wfs(ns).pyr_mod_loc!="after") && (wfs(ns).pyr_mod_loc!="before")) {
  488. write,format="wfs(%d).pyr_mod_loc is not set, setting to \"after\"\n",ns;
  489. }
  490. }
  491. // DMs in this WFS path. Default is for all DM to be in path:
  492. wfs(ns)._dmnotinpath = &array(0n,ndm);
  493. if (wfs(ns).dmnotinpath) {
  494. if (anyof(*wfs(ns).dmnotinpath>ndm)||anyof(*wfs(ns).dmnotinpath<1)) \
  495. error,swrite(format="Issue with WFS#%d dmnotinpath definition (indices)",ns);
  496. (*wfs(ns)._dmnotinpath)(int(*wfs(ns).dmnotinpath)) = 1n;
  497. }
  498. wfs(ns).ron = float(wfs(ns).ron);
  499. if ((wfs(ns).type == "hartmann") && (wfs(ns).shmethod == 1) && (wfs(ns).noise == 1) && (wfs(ns).ron > 0.)){
  500. write, format = "WARNING: wfs(%d).shmethod = 1 and wfs(%d).noise = 1\n",ns,ns;
  501. write, format = "This feature produces a measurement error in wfs(%d) of wfs(%d).ron arcsec\n",ns,ns;
  502. }
  503. if (!wfs(ns).lgs_prof_amp) wfs(ns).lgs_prof_amp = &float([0.]);
  504. if (!wfs(ns).lgs_prof_alt) wfs(ns).lgs_prof_alt = &float([0.]);
  505. if (numberof(*wfs(ns).lgs_prof_amp)!=numberof(*wfs(ns).lgs_prof_alt)) {
  506. error,"numberof(*wfs(ns).lgs_prof_amp)!=numberof(*wfs(ns).lgs_prof_alt)";
  507. }
  508. if (wfs(ns).minzer == 0) {
  509. wfs(ns).minzer = 1;
  510. }
  511. }
  512. // DM STRUCTURE
  513. for (nm=1;nm<=ndm;nm++) {
  514. if (dm(nm).type == string()) {
  515. exit,swrite(format="dm(%d).type has not been set",nm);
  516. }
  517. // disk harmonic type string has changed. for compatibility
  518. // with old API:
  519. if (dm(nm).type == "diskharmonic") dm(nm).type = "dh";
  520. if (dm(nm).subsystem == 0) {dm(nm).subsystem = 1;}
  521. if (dm(nm).iffile == string()) {dm(nm).iffile = "";}
  522. if (dm(nm).ecmatfile == string()) {dm(nm).ecmatfile = "";}
  523. if (dm(nm).push4imat == 0) {dm(nm).push4imat = 20;}
  524. if (dm(nm).filterpiston == 0) {dm(nm).filterpiston = 1;}
  525. if (dm(nm).gain == 0) {write,format=" WARNING: dm(%d).gain set to 0\n",nm;}
  526. if (dm(nm).unitpervolt == 0) {
  527. write,format=" WARNING: dm(%d).unitpervolt set to 0\n",nm;
  528. // below: this is stupid. but I don't dare to change it now (2011mar16)
  529. if ( (dm(nm).type == "tiptilt") || (dm(nm).type == "zernike")) {
  530. dm(nm).unitpervolt = 0.0005;
  531. } else if ( (dm(nm).type == "bimorph") || (dm(nm).type == "dh")) {
  532. dm(nm).unitpervolt = 1.0;
  533. } else {
  534. dm(nm).unitpervolt = 0.01;
  535. }
  536. write,format=" WARNING: dm(%d).unitpervolt overwritten to %f\n",nm,dm(nm).unitpervolt;
  537. }
  538. if ( (dm(nm).type == "bimorph") && ((*dm(nm).nelperring) == []) ) {
  539. write,format="dm(%d).nelperring has not been set.\n",nm;
  540. write,"Valid values are, for example:";
  541. exit,"dm(n).nelperring = &([6,12,18]);";
  542. }
  543. if ( (dm(nm).type == "stackarray") && (dm(nm).nxact == 0) ) {
  544. exit,swrite(format="dm(%d).nxact has not been set",nm);
  545. }
  546. if ( (dm(nm).type == "stackarray") && (dm(nm).pitch == 0) ) {
  547. exit,swrite(format="dm(%d).pitch has not been set",nm);
  548. }
  549. if (dm(nm).type=="stackarray") {
  550. if ((dm(nm).coupling<0.0) || (dm(nm).coupling>0.8)) {
  551. write,format="Invalid value for dm(%d).coupling -> %f\n",nm,dm(nm).coupling;
  552. exit,"Valid values from 0 to 0.80";
  553. }
  554. }
  555. if ( (dm(nm).type == "zernike") && (dm(nm).nzer == 0) ) {
  556. exit,swrite(format="dm(%d).nzer has not been set",nm);
  557. }
  558. if ( (dm(nm).type == "dh") && (dm(nm).ndh == 0) ) {
  559. exit,swrite(format="dm(%d).ndh has not been set",nm);
  560. }
  561. if (dm(nm).minzer == 0) {
  562. dm(nm).minzer = 1;
  563. }
  564. if ( (dm(nm).filtertilt) && (noneof(dm(nm).type == ["zernike","stackarray"]) )) {write, "WARNING: filtertilt only defined for zernike and stackarray DMs";}
  565. if ( (dm(nm).type == "kl") && (dm(nm).nkl == 0) ) {
  566. exit,swrite(format="dm(%d).nkl has not been set",nm);
  567. }
  568. if (dm(nm).irexp==1) {
  569. if (dm(nm).irfact == 0.) {
  570. dm(nm).irfact = 1.;
  571. write,format="dm(%d).irfact set to %f\n",nm,dm(nm).irfact;
  572. }
  573. }
  574. // check that laplacian only applies to stackarrays
  575. if ((dm(nm).regtype == "laplacian") && (dm(nm).type != "stackarray")){
  576. exit, "regtype can only be set to laplacian for stackarray DMs";
  577. }
  578. // set the regparam for stack arrays to something other than 0
  579. if ((dm(nm).type == "stackarray") && (dm(nm).regparam == 0)){
  580. write,"Setting regparam to 1e-5";
  581. dm(nm).regparam = 1e-5;
  582. }
  583. // check that any virtual DMs have a lower DM number than a DM that uses it
  584. if (*dm(nm).dmfit_which != []){
  585. if (max(*dm(nm).dmfit_which) > nm){
  586. write,format="Virtual DMs (%d) must have a lower numbering than the DM (%d) that fits to them\n",max(*dm(nm).dmfit_which),nm;
  587. exit, "Exiting";
  588. }
  589. }
  590. if ((dm(nm).hyst < 0.)+(dm(nm).hyst > 0.25)){exit,"DM hysteresis must be between 0 and 0.25";}
  591. if (dm(nm).hyst > 0.){write,"WARNING: DM hysteresis implementation assumes that voltages are of the order of 0.1 to 10. If not, do not use hysteresis.";}
  592. }
  593. nmaniso = where(dm.type == "aniso");
  594. if (numberof(nmaniso) > 1){
  595. exit, "The number of DMs with dm.type == aniso must be 0 or 1";
  596. }
  597. if (numberof(nmaniso) == 1){
  598. nmaniso = nmaniso(1);
  599. // use dm.dmfit_which to specify which DMs these modes are projected onto
  600. dmfit = dm(nmaniso).anisodmfit;
  601. if (numberof(dmfit) != 2){
  602. // the user needs to specify two DMs at different altitudes
  603. exit,"Exactly 2 DMs must be specified in anisodmfit for aniso DM";
  604. }
  605. }
  606. // MAT STRUCTURE
  607. if (mat.method == string()) {mat.method = "svd";};
  608. if (mat.method == "svd"){
  609. if ((*mat.condition) == []) {exit,"mat.condition has not been set";}
  610. if (numberof(*mat.condition) != max(_(wfs.subsystem,dm.subsystem)) ) {
  611. exit,"dimension of *mat.condition is not equal to the number of subsystems";
  612. }
  613. }
  614. if (mat.file == string()) {mat.file = "";}
  615. if (mat.sparse_MR == long()){mat.sparse_MR = 10000;}
  616. if (mat.sparse_MN == long()){mat.sparse_MN = 200000;}
  617. if (mat.sparse_thresh == float()){mat.sparse_thresh = 1e-8;}
  618. if (mat.sparse_pcgtol == float()){mat.sparse_pcgtol = 1e-6;}
  619. if (mat.fit_subsamp == long()){mat.fit_subsamp = 1;}
  620. if (mat.fit_minval == float()){mat.fit_minval = 1e-2;}
  621. // TEL STRUCTURE
  622. if (tel.diam == 0) exit,"tel.diam has not been set";
  623. // TARGET STRUCTURE
  624. if ((*target.lambda) == []) exit,"target.lambda has not been set";
  625. target.lambda = &(float(*target.lambda)); // *target.lambda must be floats
  626. if ((*target.xposition) == []) exit,"target.xposition has not been set";
  627. if ((*target.yposition) == []) exit,"target.yposition has not been set";
  628. if ((*target.dispzoom) == []) {
  629. target.dispzoom = &(array(1.,numberof(*target.xposition)));
  630. }
  631. if (nallof(_(numberof(*target.xposition), \
  632. numberof(*target.yposition)) == numberof(*target.dispzoom) )) {
  633. exit,"Some elements within target.xposition, yposition, dispzoom "+\
  634. "do not have the same number of elements.";
  635. }
  636. // GS STRUCTURE
  637. if (gs.zenithangle > 0 && anyof(wfs.gsalt > 0)){write,"WARNING: The return from the LGS is assumed to vary as cos(gs.zenithangle).";}
  638. if (anyof(wfs.gsalt != 0) && (gs.lgsreturnperwatt == 0)) {
  639. gs.lgsreturnperwatt = 22.;
  640. write,format="gs.lgsreturnperwatt set to %f\n",gs.lgsreturnperwatt;
  641. }
  642. if (anyof((wfs.gsalt == 0)*(wfs.zeropoint == 0)) && (gs.zeropoint == 0)) {
  643. exit,"You have some NGS and gs.zeropoint has not been set";
  644. }
  645. if (anyof((wfs.gsalt != 0)*(wfs.zeropoint == 0)*(wfs.skymag != 0)) && (gs.zeropoint == 0)) {
  646. exit,"You have some LGS with sky background and gs.zeropoint has not been set";
  647. }
  648. // LOOP STRUCTURE
  649. if (loop.method == string()) loop.method = "closed-loop";
  650. loop_method_options = ["closed-loop","open-loop","pseudo open-loop","none"];
  651. if (noneof(loop_method_options == loop.method)) exit,"ERROR: loop.method should be set to closed-loop, open-loop or pseudo open-loop";
  652. if ((loop.method == "open-loop") && (loop.gain != 1 || loop.leak != 1)){
  653. write, "Warning: For open-loop simulations the recommended settings are";
  654. write, "loop.gain = 1. and loop.leak = 1.";
  655. }
  656. if (loop.gain == 0) write,format="%s\n","Warning: loop.gain = 0";
  657. if ( (numberof(*loop.gainho)) != (numberof(*loop.leakho)) ) \
  658. exit,"*loop.gainho should have same number of elements as *loop.leakho";
  659. if (loop.niter == 0) exit,"loop.niter has not been set";
  660. if (loop.ittime == 0) exit,"loop.ittime has not been set";
  661. if (loop.startskip == 0) loop.startskip = 10;
  662. if (loop.skipby == 0) loop.skipby = 10000;
  663. if (loop.modalgainfile == string()) loop.modalgainfile = "";
  664. if (loop.stats_every==0) loop.stats_every=4;
  665. //============================================================================
  666. // DONE WITH BASIC EXISTENCE AND CONSISTENCY CHECKINGS AND DEFAULT ASSIGNMENTS
  667. //============================================================================
  668. // NOW GOING INTO MORE ELABORATE CHECKINGS
  669. // WFSs:
  670. for (ns=1;ns<=nwfs;ns++) {
  671. if (wfs(ns).zeropoint == 0){
  672. wfs(ns)._zeropoint = gs.zeropoint;
  673. } else {
  674. wfs(ns)._zeropoint = wfs(ns).zeropoint;
  675. }
  676. if (wfs(ns).framedelay == -1){
  677. wfs(ns)._framedelay = loop.framedelay;
  678. } else {
  679. wfs(ns)._framedelay = wfs(ns).framedelay;
  680. if ((sim.verbose) && (loop.framedelay != wfs(ns).framedelay)){
  681. write, format="Using a loop delay of %d frames for wfs(%d)\n",wfs(ns).framedelay, ns;
  682. }
  683. }
  684. if (wfs(ns).lambda < 0.1) {
  685. write,format="WFS#%d: wfs.lambda < 0.1. That seems weird.\n",ns;
  686. write,"Remember: lambda should be in microns";
  687. exit;
  688. }
  689. if (wfs(ns).shthreshold < 0.) {
  690. write,format="WFS#%d: wfs.shthreshold < 0 does not make sense\n",ns;
  691. exit;
  692. }
  693. if ((wfs(ns).filtertilt == 1) && (wfs(ns).correctUpTT == 0)) {
  694. write,format="WARNING! WFS#%d: wfs.correctUpTT = 0 with wfs.filtertilt = 1\n",ns;
  695. }
  696. if ((wfs(ns).correctUpTT == 1) && (wfs(ns).uplinkgain == 0)) {
  697. write,format="WARNING! WFS#%d: wfs.correctUpTT = 1 but wfs.uplinkgain = 0\n",ns;
  698. }
  699. // for shmethod = 1, we still need to set npixels and pixsize to avoid crashing the
  700. // WFS initialization routine that is still used to set other parameters used with
  701. // method 1. So we put DUMMY VALUES.
  702. if (wfs(ns).shmethod == 1) {
  703. if (wfs(ns).pixsize == 0){wfs(ns).pixsize = 0.1;}
  704. if (wfs(ns).npixels == 0){wfs(ns).npixels = 2;}
  705. }
  706. if ( (wfs(ns).type=="curvature") && (wfs(ns).nintegcycles != 1) ) {
  707. write,"\n I have not implemented yet nintegcycle > 1 for curvature WFS.";
  708. write,"I can see no reason to do it, as curvature sensors are usually";
  709. write,"read-out noise free, therefore reducing the gain should be";
  710. write,"prefered. If you have a compelling reason and want it, drop";
  711. write,"me an email.";
  712. exit;
  713. }
  714. // Are we using a WFS we know?
  715. wfs_type = strtolower(wfs(ns).type);
  716. if ( (wfs_type != "curvature") && (wfs_type != "hartmann") &&
  717. (wfs_type != "zernike") && (wfs_type !="kl") &&
  718. (wfs_type != "pyramid") && (wfs_type !="dh")) {
  719. // check if this is a user supplied function
  720. cmd = swrite(format="totype = typeof(%s)",wfs(ns).type);
  721. include,[cmd],1;
  722. if ( totype != "function") {
  723. error,swrite(format="wfs(%d).type : Unknown value or non-existing function \"%s\"",
  724. ns,wfs(ns).type)
  725. }
  726. } else wfs(ns).type = wfs_type;
  727. }
  728. // Sets the Influence function file name if not set:
  729. for (nm=1;nm<=ndm;nm++) {
  730. if (dm(nm).iffile == "") {
  731. dm(nm).iffile = parprefix+swrite(format="-if%d",nm)+".fits";
  732. write,format="dm(%d).iffile set to %s\n",nm,dm(nm).iffile;
  733. }
  734. if (dm(nm).ecmatfile == "") {
  735. dm(nm).ecmatfile = parprefix+swrite(format="-ecmat%d",nm)+".fits";
  736. }
  737. if ((dm(nm).type != "stackarray") && (dm(nm).elt == 1)) {
  738. exit,swrite(format="DM %d: parameter dm.elt only used with stackarray mirrors\n",nm);
  739. }
  740. // Are we using a DM we know?
  741. dm_type = strtolower(dm(nm).type);
  742. if ( (dm_type != "bimorph") && (dm_type != "stackarray") &&
  743. (dm_type != "zernike") && (dm_type != "kl") &&
  744. (dm_type != "segmented") && (dm_type != "dh") &&
  745. (dm_type != "tiptilt") && (dm_type != "aniso") ) {
  746. // check if this is a user supplied function
  747. cmd = swrite(format="totype = typeof(%s)",dm(nm).type);
  748. include,[cmd],1;
  749. if ( totype != "function") {
  750. error,swrite(format="dm(%d).type : Unknown value or non-existing function \"%s\"",
  751. nm,dm(nm).type)
  752. }
  753. } else dm(nm).type = dm_type;
  754. dm(nm)._eiffile = parprefix+swrite(format="-if%d",nm)+"-ext.fits";
  755. }
  756. for (i=1;i<=max(_(wfs.subsystem,dm.subsystem));i++) {
  757. if (noneof(wfs.subsystem == i)) {
  758. exit,swrite(format="There is no WFS in subsystem %d\n",i);
  759. }
  760. if (noneof(dm.subsystem == i)) {
  761. exit,swrite(format="There is no DM in subsystem %d\n",i);
  762. }
  763. }
  764. // Sets the interaction/command matrix file name if not set:
  765. if (mat.file == "") {mat.file = parprefix+"-mat.fits";}
  766. // # of targets (stars at which the performance are evaluated):
  767. target.xposition = &(float(*target.xposition));
  768. target.yposition = &(float(*target.yposition));
  769. target._ntarget = numberof(*target.xposition);
  770. target._nlambda = numberof(*target.lambda);
  771. if (anyof((*target.lambda) < 0.1)) {
  772. write,"Some or all target.lambda < 0.1. That seems weird.";
  773. write,"Remember: lambda should be in microns";
  774. exit;
  775. }
  776. if (opt!=[]) {
  777. for (no=1;no<=noptics;no++) {
  778. if (opt(no).misreg==[]) {
  779. noptics = numberof(opt(no).phasemaps);
  780. for (i=1;i<=noptics;i++) opt(no).misreg = [0.,0.];
  781. }
  782. opt(no).misreg= float(opt(no).misreg);
  783. if ((opt(no).path_type=="wfs")&&(opt.path_which==[])) opt.path_which = &indgen(nwfs);
  784. if ((opt(no).path_type=="target")&&(opt.path_which==[])) opt.path_which = &indgen(ntarget);
  785. if ((opt(no).path_type)&&(opt(no).path_type=="")) opt(no).path_type="common";
  786. if ((opt(no).path_type)&&(opt(no).path_type!="wfs")&&\
  787. (opt(no).path_type!="target")&&(opt(no).path_type!="common")) \
  788. error,swrite(format="Unknown optics path \"%s\".\n It should be \"common\", \"wfs\" or \"target\" (default=\"common\")",opt(no).path_type);
  789. }
  790. }
  791. if (sim.verbose) write,format="%s\n","Check parameters: OK";
  792. }
  793. //----------------------------------------------------
  794. func disp2d(ar,xpos,ypos,area,zoom=,power=,init=,nolimits=)
  795. /* DOCUMENT func disp2d(arrayptr,xpos,ypos,area,zoom=,power=,init=)
  796. display several images in the same plsys, at position given
  797. by xpos and ypos.
  798. ar: ar can be either an array of pointers or an image cube
  799. xpos,ypos: the (X,Y) positions, in arbitrary coordinates
  800. area: plsys number
  801. zoom: keyword (vector, dim nim), additional zooming factor (on top of default)
  802. power: *arrayptr^power is displayed
  803. init: initialize, precompute stuff
  804. SEE ALSO:
  805. */
  806. {
  807. extern basezoomptr;
  808. plsys,area;
  809. if (typeof(ar) == "pointer") {
  810. cas = "ptr";
  811. nim = numberof(ar);
  812. earlyExit = (*ar(1) == []); // in case we call init with ar undefined (legitimate)
  813. } else {
  814. cas = "cube";
  815. tmp = dimsof(ar);
  816. if (tmp(1) == 2) {nim=1;} else {nim=tmp(4);}
  817. }
  818. if (is_set(init)) {
  819. if (basezoomptr == []) {
  820. basezoomptr=array(pointer,10);
  821. }
  822. if ((zoom != []) && (numberof(zoom) != nim)) {zoom = array(zoom,nim);}
  823. if (zoom == []) {zoom = array(1.,nim);}
  824. xd = abs(xpos-xpos(-,));
  825. yd = abs(ypos-ypos(-,));
  826. di = sqrt(xd^2.+yd^2.);
  827. di = di+unit(nim)*max(di);
  828. w = where(zoom!=0);
  829. basezoom = (1.+0.9*min(di(w))/2.)*zoom;
  830. basezoomptr(area) = &basezoom;
  831. if (!is_set(nolimits)) {
  832. limits,min(xpos(w)-basezoom(w)),max(xpos(w)+basezoom(w)),
  833. min(ypos(w)-basezoom(w)),max(ypos(w)+basezoom(w)),square=1;
  834. }
  835. if (earlyExit) return;
  836. }
  837. basezoom = *basezoomptr(area);
  838. if ( cas=="ptr") {
  839. if (!is_set(power)) {
  840. for (i=1;i<=nim;i++) {
  841. if (basezoom(i)==0) continue;
  842. off = basezoom(i)/(dimsof(*ar(1))(2));
  843. pli,bytscl(*ar(i),cmin=0),xpos(i)-basezoom(i)-off,ypos(i)-basezoom(i)-off,
  844. xpos(i)+basezoom(i)-off,ypos(i)+basezoom(i)-off;
  845. }
  846. } else {
  847. for (i=1;i<=nim;i++) {
  848. if (basezoom(i)==0) continue;
  849. off = basezoom(i)/(dimsof(*ar(1))(2));
  850. pli,bytscl(*ar(i)^power,cmin=0),xpos(i)-basezoom(i)-off,ypos(i)-basezoom(i)-off,
  851. xpos(i)+basezoom(i)-off,ypos(i)+basezoom(i)-off;
  852. }
  853. }
  854. } else {
  855. if (!is_set(power)) {
  856. for (i=1;i<=nim;i++) {
  857. if (basezoom(i)==0) continue;
  858. off = basezoom(i)/(dimsof(ar(,,1))(2));
  859. pli,bytscl(ar(,,i),cmin=0),xpos(i)-basezoom(i)-off,ypos(i)-basezoom(i)-off,
  860. xpos(i)+basezoom(i)-off,ypos(i)+basezoom(i)-off;
  861. }
  862. } else {
  863. for (i=1;i<=nim;i++) {
  864. if (basezoom(i)==0) continue;
  865. off = basezoom(i)/(dimsof(ar(,,1))(2));
  866. pli,bytscl(ar(,,i)^power,cmin=0),xpos(i)-basezoom(i)-off,ypos(i)-basezoom(i)-off,
  867. xpos(i)+basezoom(i)-off,ypos(i)+basezoom(i)-off;
  868. }
  869. }
  870. }
  871. if ((is_set(init)) && (!is_set(nolimits))) {
  872. limits,square=1;
  873. limits;
  874. }
  875. }
  876. //--------------------------------------------------------------------------
  877. func modal_gain_optimization(disp=,update=)
  878. /* DOCUMENT func modal_gain_optimization(disp=,update=)
  879. NOT UPGRADED TO VERSION 2.
  880. DO NOT USE.
  881. This routine optimizes the modal gains.
  882. Keywords:
  883. disp: set to display stuff
  884. update: set if this is a gain update (opposite to the first time run)
  885. This routine uses:
  886. - the saved error circular buffer (cberr.fits)
  887. This routine calls:
  888. ...
  889. This routine sets:
  890. ...
  891. SEE ALSO:
  892. */
  893. {
  894. cberr = yao_fitsread("cberr.fits"); // CB of actuator error
  895. nGoodSamples = long(2^floor(log(ao.LoopNIter-ao.LoopStartSkip)/log(2)));
  896. cbmoderr = atm(,+)*cberr(+,1-nGoodSamples:);// CB of mode coef errors
  897. modgains = modalgain*ao.LoopGain; // overall mode gains
  898. bestGains = modgains*0.;
  899. length = 2048;
  900. gainNpt = 30;
  901. toGains = spanl(1e-2,0.9,gainNpt);
  902. errvect = array(float,gainNpt);
  903. errTF = array(float,length/2-1,gainNpt);
  904. fudgeFactor = 0.85;
  905. // initialize Error Transfer functions:
  906. for (n=1;n<=gainNpt;n++)
  907. {errTF(,n) = ft_cb_ao_simul(ao.LoopFrameDelay,toGains(n),length/2)(,1);}
  908. // Loop over controlled modes:
  909. for (i=1;i<=NModesControlled;i++)
  910. {
  911. // Build PSD of the error
  912. psderr = psd(cbmoderr(i,),length,samp=ao.LoopItTime,filter=6,silent=1)(2:,2);
  913. pause,10;
  914. // ^^ psderr is in fact length/2 long
  915. // Build current gain transfer function
  916. curErrTF = ft_cb_ao_simul(ao.LoopFrameDelay,modgains(i),length/2)(,1);
  917. // Reconstruct open loop PSD
  918. OLmodPsd = psderr/curErrTF;
  919. // Loop over possible gains
  920. for (n=1;n<=gainNpt;n++) {errvect(n) = sum(OLmodPsd*errTF(,n));}
  921. if (is_set(disp)) {logxy,0,0; plot,errvect,toGains; pause,500;}
  922. // Determine best gain
  923. bestGains(i) = fudgeFactor*toGains(errvect(mnx));
  924. if (ao.verbose>=1) {write,format="Mode %i, gain to be updated from %f to %f\n",
  925. i,modgains(i),bestGains(i);}
  926. }
  927. // Update modalgain
  928. if (is_set(update))
  929. {
  930. modalgain = bestGains/ao.LoopGain;
  931. yao_fitswrite,YAO_SAVEPATH+ao.LoopModalGainFile,modalgain;
  932. if (ao.verbose>=1) {write,format="Gains updated and saved in !",ao.LoopModalGainFile;}
  933. }
  934. }
  935. //----------------------------------------------------
  936. func ft_cb_ao_simul(FrameDelay,gain,dim)
  937. /* DOCUMENT func ft_cb_ao_simul(FrameDelay,gain,dim)
  938. NOT UPGRADED TO VERSION 2.
  939. DO NOT USE UNTIL UPGRADED.
  940. This routine simulates the time aspect of the numerical loop
  941. and compute the associated transfer functions (error, closeloop)
  942. for further use in modal_gain_optimization().
  943. Inputs:
  944. FrameDelay: frame delay in close loop.
  945. gain: AO loop gain
  946. dim: desired linear size of the output transfer functions
  947. SEE ALSO: modal_gain_optimization
  948. */
  949. {
  950. input = array(float,2*dim);
  951. mirvect = array(float,2*dim);
  952. output = array(float,2*dim);
  953. input(2) = 1.;
  954. mir = 0.;
  955. command = 0.;
  956. wfsMesHistory = array(float,FrameDelay+1);
  957. for (i=1;i<=2*dim;i++)
  958. {
  959. in = input(i);
  960. // Subtract the mirror shape:
  961. out = in - mir;
  962. // Do the WFS:
  963. mes = out;
  964. // Handling frame delay (0 -> no frame delay at all, 1 -> regular
  965. // one frame delay of integrator, 2 -> integrator + one frame
  966. // computation, etc...)
  967. wfsMesHistory = roll(wfsMesHistory,-1);
  968. wfsMesHistory(FrameDelay+1) = mes;
  969. usedMes = wfsMesHistory(1);
  970. // computes the command vector (integrator with gain):
  971. err = usedMes;
  972. command += gain*err;
  973. // Computes the mirror shape using influence functions:
  974. mir = command;
  975. // Subtract the mirror shape:
  976. out = in - mir;
  977. mirvect(i) = command;
  978. output(i) = err;
  979. // write,input(i),command,err,out; pause,500;
  980. }
  981. errTF = abs((fft(output+1e-12,1))^2.)/abs((fft(input,1))^2.);
  982. errTF = errTF(2:dim);
  983. clTF = abs((fft(mirvect,1))^2.)/abs((fft(input,1))^2.);
  984. clTF = clTF(2:dim);
  985. return [errTF,clTF];
  986. }
  987. //----------------------------------------------------
  988. func ss_noise(nss)
  989. {
  990. write,format="Computing propagated noise for subsystem %d\n",nss;
  991. write,"Extracting cmat";
  992. sswfs = ssdm = [];
  993. for (ns=1;ns<=nwfs;ns++) {grow,sswfs,array(wfs(ns).subsystem,wfs(ns)._nmes);}
  994. for (nm=1;nm<=ndm;nm++) {grow,ssdm,array(dm(nm).subsystem,dm(nm)._nact);}
  995. wsswfs = where(sswfs == nss);
  996. wssdm = where(ssdm == nss);
  997. cmat = cMat(wssdm,wsswfs);
  998. write,"Computing subsystem modes";
  999. nmv = where(dm.subsystem == nss);
  1000. if (numberof(nmv)==0) error,"No DM found in subsystems";
  1001. modes = build_dm_modes(nmv,actmodes,modvar);
  1002. tmp = actmodes(+,)*cmat(+,);
  1003. modcov = trace(tmp(,+)*tmp(,+));
  1004. // now the measurements are in arcsec, so we got to convert to rd of
  1005. // phase difference at the edge of the subaperture, which is what
  1006. // the SH measure:
  1007. nsv = where(wfs.subsystem == nss);
  1008. if (numberof(nsv) != 1) error,"zero or more than one wfs in subsystem";
  1009. ns = nsv(1);
  1010. // subsize in pixel:
  1011. subsize = sim.pupildiam/wfs(ns).shnxsub(0);
  1012. if (wfs(ns).npixpersub) subsize = wfs(ns).npixpersub;
  1013. // subsize in meters:
  1014. subsize_m = subsize * tel.diam/sim.pupildiam;
  1015. //subsize = tel.diam/wfs(ns).shnxsub;
  1016. arcsectord = 4.848e-6*subsize_m*2*pi/wfs(ns).lambda/1e-6;
  1017. noise = modcov*modvar/arcsectord^2.;
  1018. write,format="Total noise on phase for 1rd2 per subaperture = %f rd2\n",sum(noise);
  1019. fma;plh,noise; limits,square=0; limits;
  1020. return noise;
  1021. }
  1022. //----------------------------------------------------
  1023. func build_dm_modes(nm,&u,&modvar,&eigenvalues,disp=)
  1024. /* DOCUMENT unfinished, I think.
  1025. NOT FINISHED, NOT UPGRADED TO VERSION 2.
  1026. DO NOT USE.
  1027. */
  1028. {
  1029. if (anyof(dm(nm).elt)) exit,"Not implemented for dm.elt=1";
  1030. def = [];
  1031. for (i=1;i<=numberof(nm);i++) {
  1032. grow,def,*dm(nm(i))._def;
  1033. }
  1034. defpup = ipupil(dm(nm(1))._n1:dm(nm(1))._n2,dm(nm(1))._n1:dm(nm(1))._n2);
  1035. wpup = where(defpup);
  1036. tmp = def(*,)(wpup,); // not really saving RAM, but...
  1037. tpup = sum(defpup);
  1038. /*
  1039. p = (def*ipupil)(sum,sum,)/tpup;
  1040. def = def-p(-,-,);
  1041. def = reform(def,long(ao._size)*ao._size,ao._DmNAct);
  1042. def = def(where(ipupil),);
  1043. */
  1044. dd = tmp(+,)*tmp(+,);
  1045. eigenvalues = SVdec(dd,u,vt);
  1046. modes = def(,,+)*u(+,);
  1047. // mask with ipupil
  1048. modes = modes*defpup(,,-);
  1049. // compute mode variance:
  1050. modvar = (modes(*,)(wpup,)(rms,))^2.;
  1051. return modes;
  1052. if (disp == 1)
  1053. {for (i=1;i<=ao._DmNAct;i++) {fma; pli,modes(,,i)*ipupil;pause,100;}}
  1054. //normalize the modes:
  1055. norm = sqrt((modes^2.*ipupil)(avg,avg,));
  1056. modes = modes/norm(-,-,);
  1057. ActIF = modes(,,1:-1) //*(1./indgen(ao._DmNAct-1))(-,-,);
  1058. ao._DmNAct = ao._DmNAct-1;
  1059. do_imat,disp=1;
  1060. build_cmat,all=1,nomodalgain=1;
  1061. mn = cMat(,+)*cMat(,+);
  1062. mn = diag(mn)*indgen(ao._DmNAct-1)^2.
  1063. if (disp == 1) {fma;plg,mn;}
  1064. return modes;
  1065. }
  1066. //----------------------------------------------------
  1067. func make_curv_wfs_subs(ns,dim,pupd,disp=,cobs=)
  1068. /* DOCUMENT func make_curv_wfs_subs(ns,dim,pupd,disp=)
  1069. */
  1070. {
  1071. local WhichRing,SubThetaIn,SubThetaOut,SubRadiusIn,SubRadiusOut;
  1072. NRing = sum(*(wfs(ns).nsubperring) != 0); // Number of Rings
  1073. NSubPerRing = (*(wfs(ns).nsubperring))(1:NRing);
  1074. NSub = sum(NSubPerRing);
  1075. // Compute the internal and external radius of each rings
  1076. // given the number of subapertures per rings:
  1077. SurfOneSub = pi/sum(NSubPerRing); // Surface of one actuator
  1078. RInRing = array(float,NRing); // Internal Radius
  1079. ROutRing = array(float,NRing); // External Radius
  1080. if (is_set(cobs)) {RInRing(1) = cobs;}
  1081. // loop on ring number
  1082. for (i=1;i<=NRing;i++) {
  1083. ROutRing(i) = sqrt(NSubPerRing(i)*SurfOneSub/pi+RInRing(i)^2.);
  1084. if (i != NRing) RInRing(i+1) = ROutRing(i);
  1085. }
  1086. if (is_set(cobs)) {RInRing(1) = 0.;}
  1087. ROutRing(NRing) = 1.6;
  1088. // now we got to determine the inner and outer radius and angle for
  1089. // each actuators:
  1090. WhichRing = array(1,NSubPerRing(1)); // Ring index per actuator
  1091. for (i=2;i<=NRing;i++) {grow,WhichRing,array(i,NSubPerRing(i));}
  1092. // offset angle of first subaperture in rings:
  1093. if (*wfs(ns).angleoffset==[]) angleoffset=array(0.,NRing);
  1094. else angleoffset=(*wfs(ns).angleoffset)*pi/180.;
  1095. // if rint and rout are specified, use it instead:
  1096. if ((*wfs(ns).rint)!=[]) RInRing=*wfs(ns).rint;
  1097. if ((*wfs(ns).rout)!=[]) ROutRing=*wfs(ns).rout;
  1098. // loop to determine radiuses and angle:
  1099. for (i=1;i<=NRing;i++) {
  1100. dtheta = 2*pi/NSubPerRing(i);
  1101. for (j=1;j<=NSubPerRing(i);j++) {
  1102. t1 = (j-1.)*dtheta + angleoffset(i);
  1103. t2 = t1+dtheta;
  1104. grow,SubThetaIn,t1 ;
  1105. grow,SubThetaOut,t2 ;
  1106. grow,SubRadiusIn,RInRing(i) ;
  1107. grow,SubRadiusOut,ROutRing(i) ;
  1108. }
  1109. }
  1110. // Now build the subapertures images:
  1111. x = span(1,dim,dim)(,-:1:dim)-dim/2.-1;
  1112. y = transpose(x);
  1113. ang = atan(y,x);
  1114. ang1 = ang + (ang < 0)*2*pi;
  1115. ang2 = (ang1+pi)%(2*pi)+pi;
  1116. rad = dist(dim)/(pupd/2.);
  1117. d2 = eclat(dist(dim)^2.);
  1118. Subs = array(short,dim,dim,NSub);
  1119. for (i=1;i<=NSub;i++) {
  1120. // the following to avoid issues due to discontinuity of ang array at 0=2pi
  1121. if (SubThetaOut(i)>(2*pi)) ang=ang2; else ang=ang1;
  1122. tmp = (rad >= SubRadiusIn(i)) * (rad < SubRadiusOut(i)) * \
  1123. (ang >= SubThetaIn(i)) * (ang < SubThetaOut(i));
  1124. if (i>1) tmp = tmp-Subs(,,i-1); // avoid double points
  1125. Subs(,,i) = (tmp==1);
  1126. if (disp == 1) {fma; pli,tmp;}
  1127. }
  1128. // ok, in the following, I have changed the way one sums
  1129. // the signal over the subaperture area in CurvWFS:
  1130. // now I am passing a vector of indices for each subaperture
  1131. // and using them for the sum.
  1132. // sind is the ensemble of index vectors
  1133. // nsind is the number of actual indices for a given subaperture
  1134. tmp = Subs(sum,sum,);
  1135. sind = array(long,max(tmp),NSub)*0+1;
  1136. nsind = array(long,NSub);
  1137. for (i=1;i<=NSub;i++)
  1138. {
  1139. sind(1:tmp(i),i) = where(Subs(,,i) == 1);
  1140. nsind(i) = sum(Subs(,,i));
  1141. }
  1142. wfs(ns)._sind = &(int(sind));
  1143. wfs(ns)._nsind = &(int(nsind));
  1144. return Subs;
  1145. }
  1146. //----------------------------------------------------
  1147. func _map2d(t,dim,cent)
  1148. {
  1149. // assumes XY square array as input
  1150. curdim = dimsof(t);
  1151. if (curdim(2) != curdim(3)) {exit,"_map2d only takes square arrays";}
  1152. cdim = curdim(2);
  1153. if (cdim == dim) {return t;}
  1154. if (cdim > dim) {
  1155. if (is_void(cent)) {cent = cdim/2+1.;}
  1156. _p1 = long(ceil(cent-dim/2.));
  1157. _p2 = _p1+dim-1;
  1158. return t(_p1:_p2,_p1:_p2,..);
  1159. }
  1160. if (cdim < dim) {
  1161. if (is_void(cent)) {cent = dim/2+1.;}
  1162. curdim(2:3) = [dim,dim];
  1163. tmp = array(structof(t),curdim);
  1164. _p1 = long(ceil(cent-cdim/2.));
  1165. _p2 = _p1+cdim-1;
  1166. if (_p2 != long(floor(cent+cdim/2.-1))) {exit,"Problem in _map2d";}
  1167. tmp(_p1:_p2,_p1:_p2,..) = t;
  1168. return tmp;
  1169. }
  1170. }
  1171. //----------------------------------------------------
  1172. func noll(ord)
  1173. {
  1174. /* DOCUMENT func noll(ord)
  1175. Noll variance of zernike for D/r0 = 1
  1176. SEE ALSO:
  1177. */
  1178. innp = 0.;
  1179. cov = array(double,ord);
  1180. for (j=2;j<=ord+1;j++) {
  1181. tmp = zernumero(j); n = tmp(1); m = tmp(2);
  1182. innp = gamma(14./3.)*gamma((2*n-14./3.+3.)/2.)/
  1183. (2.^(14./3.)*gamma((14./3.+1.)/2.)*gamma((14./3.+1.)/2.)*
  1184. gamma((2*n+14./3.+3.)/2.));
  1185. cov(j-1) = (0.046/pi*(n+1.)*innp*(-1.)^((2*n-2*m)/2.));
  1186. }
  1187. return cov*(2.*pi)^(11./3.)/pi;
  1188. }
  1189. //----------------------------------------------------
  1190. func nollmat(ord)
  1191. {
  1192. /* DOCUMENT func nollmat(ord)
  1193. Noll covariance matrix for D/r0 = 1
  1194. SEE ALSO:
  1195. */
  1196. innp = 0.;
  1197. cov = array(double,[2,ord,ord]);
  1198. for (j=2;j<=ord+1;j++) {
  1199. for (jp=2;jp<=ord+1;jp++) {
  1200. tmp = zernumero(j);
  1201. tmpp = zernumero(jp);
  1202. n = tmp(1); m = tmp(2);
  1203. np = tmpp(1); mp = tmpp(2);
  1204. //print,(n+np-14./3.+3.)/2.,(np-n+14./3.+1.)/2.,
  1205. // (n-np+14./3.+1.)/2.,(n+np+14./3.+3.)/2.;
  1206. innp = gamma(14./3.)*gamma((n+np-14./3.+3.)/2.)/
  1207. (2.^(14./3.)*gamma((np-n+14./3.+1.)/2.)*gamma((n-np+14./3.+1.)/2.)*
  1208. gamma((n+np+14./3.+3.)/2.));
  1209. cov(j-1,jp-1) = (0.046/pi*sqrt((n+1.)*(np+1.))*innp*float(m == mp)*
  1210. (-1.)^(float(m == mp)*(n+np-2*m)/2.))*long((abs(j-jp)%2) == 0);
  1211. }
  1212. }
  1213. cov = (cov+transpose(cov))/2.;
  1214. return cov*(2.*pi)^(11./3.)/pi;
  1215. }
  1216. //----------------------------------------------------
  1217. func rotby90(image,rot)
  1218. /* DOCUMENT rotby90(image,rot)
  1219. with:
  1220. rot = 0 -> no rotation
  1221. rot = 1 -> 90 deg anticlockwise
  1222. rot = 2 -> 180 deg anticlockwise
  1223. rot = 3 -> 270 deg anticlockwise
  1224. AUTHOR: F.Rigaut, June 11, 2002.
  1225. SEE ALSO: rotate
  1226. */
  1227. {
  1228. if (rot == 0) {return image;}
  1229. if (rot == 1) {return transpose(image)(::-1,);}
  1230. if (rot == 2) {return image(::-1,::-1);}
  1231. if (rot == 3) {return transpose(image)(,::-1);}
  1232. }
  1233. //----------------------------------------------------
  1234. func make_pupil(dim,pupd,xc=,yc=,real=,cobs=)
  1235. /* DOCUMENT func make_pupil(dim,pupd,xc=,yc=,real=)
  1236. */
  1237. {
  1238. if (real == 1) {
  1239. pup = exp(-clip(dist(dim,xc=xc,yc=yc)/(pupd/2.),0.,2.)^60.)^0.69314;
  1240. } else {
  1241. // xc;yc;info,xc;
  1242. // tv,dist(dim,xc=xc,yc=yc);pause,2000;
  1243. pup = dist(dim,xc=xc,yc=yc) < (pupd+1.)/2.;
  1244. }
  1245. if (is_set(cobs)) {
  1246. if (real == 1) {
  1247. pup -= exp(-clip(dist(dim,xc=xc,yc=yc)/(pupd*cobs/2.),0.,2.)^60.)^0.69314;
  1248. } else {
  1249. pup -= dist(dim,xc=xc,yc=yc) < (pupd*cobs+1.)/2.;
  1250. }
  1251. }
  1252. return pup;
  1253. }
  1254. //----------------------------------------------------
  1255. func fwhmStrehl(image,ps,lambda,teldiam,cobs,&strehl,&fwhm,&strehlab,&airy,&psf0,
  1256. fibre=,source=,rmask=,dlambda=,psfcomp=,autoback=,silent=)
  1257. {
  1258. /* DOCUMENT function fwhmStrehl
  1259. Syntax: fwhmStrehl(image,ps,lambda,teldiam,cobs,strehl,fwhm,strehlab,&airy,
  1260. fibre=,source=,rmask=,dlambda=,psf0=,psfcomp=,autoback=)
  1261. Cette routine calcule le strehl et la fwhm de l'image. Le strehl
  1262. est calcule de la facon suivante :
  1263. 1) on calcule la fto et on filtre les frequences > coupure du telescope
  1264. 2) On corrige de l'effet de filtrage par la taille finie du pixel
  1265. 3) Le cas echeant ("source" is set), on divise par la fto de la source
  1266. 4) On synthetise une tache d'airy pour la longueur d'onde en question
  1267. 5) On rebinne par 4, normalise, et on compare les maxima -> Strehl
  1268. La FWHM est simplement calcule a partir du nombre de pixel de valeur > au
  1269. max de l'image/2.
  1270. ps = pixel size en arcsec
  1271. lambda = en um
  1272. teldiam = telescope diameter en m
  1273. cobs = fraction obstruction central/telescope diameter
  1274. fibre = cf ci-dessus
  1275. source = source dimension in arcsec.
  1276. autoback = automatic normalization of background by interpolation of
  1277. zero point in MTF
  1278. By convention, real plane coordinate (0,0) -> [dim/2,dim/2]
  1279. fourier plane coordinates (0,0) -> [0,0]
  1280. */
  1281. if (!is_set(dlambda)) {dlambda=0.;}
  1282. if (!is_set(fibre)) {fibre = "disk";}
  1283. // valeur autorisee pour fibre = 'disk' ou 'gaussian'
  1284. dim = dimsof(image)(2);
  1285. // CALCUL DU STREHL :
  1286. // Image theorique :
  1287. tfto = telfto(lambda,dlambda,teldiam,cobs,ps,dim,silent=1);
  1288. tfto = roll(tfto,[1,1]); // bug fixed 2007jul28 (no impact)
  1289. mask = (tfto > 1e-4);
  1290. airy = roll(abs(fft(tfto,1)));
  1291. // was fftrebin, caused ripples in ima below, 2007jul28
  1292. airy = spline2(airy,4);
  1293. // Image experimentale :
  1294. fcoup = dim/2/(lambda*1e-6/2./teldiam/4.848e-6/ps);
  1295. ifto = fft(roll(image),1);
  1296. mask = roll((dist(dim) < fcoup));
  1297. // on soustrait le bruit moyen : what's that? 2007jul28
  1298. /*
  1299. if (sum(1.-mask) != 0) {
  1300. mfl = sum(ifto.re*(1.-mask))/sum(1.-mask);
  1301. mim = sum(ifto.im*(1.-mask))/sum(1.-mask);
  1302. // print,max(ifto.re)/mfl,max(ifto.im)/mim;
  1303. ifto.re = (ifto.re-mfl)*mask;
  1304. ifto.im = (ifto.im-mim)*mask;
  1305. }
  1306. */
  1307. // calcul de l'attenuation de la FTM par le moyennage du pixel :
  1308. tmp = roll(dist(dim))/(dim/2.)*pi/2.;
  1309. ifto = ifto/sinc(tmp);
  1310. if (is_set(source)) {
  1311. // Calcul de la FTM de la source (etoile artificielle ) :
  1312. if (fibre == "gaussian") {
  1313. // fibre = gaussian
  1314. imfib = exp(-(clip((dist(dim)/(source/ps/1.66))^2.,,20)));
  1315. fibfto = abs(fft(imfib,-1));
  1316. } else {
  1317. // fibre = camember
  1318. tmp = roll(dist(dim))/(dim/2.)*pi/2.*source/ps;
  1319. fibfto = sinc(tmp);
  1320. }
  1321. } else {
  1322. fibfto = ifto.re*0.+1.;
  1323. }
  1324. ima = roll(float(fft((ifto/fibfto)*(tfto > 1e-8),-1)));
  1325. psf0 = ima/sum(ima);
  1326. // was fftrebin, caused ripples, 2007jul28
  1327. ima = spline2(ima,4);
  1328. if (is_set(rmask)) // diaphragme les deux images
  1329. {
  1330. diap = roll(dist(4*dim)) <= 4*rmask;
  1331. wm = (where2(ima == max(ima)))(,1);
  1332. ima = ima*roll(diap,wm);
  1333. airy = airy*roll(diap);
  1334. }
  1335. airy = airy/sum(airy);
  1336. ima = ima/sum(ima);
  1337. strehl = max(ima)/max(airy);
  1338. // Calcul de la largeur a mi-hauteur :
  1339. fwhm = sqrt(4./pi*sum(ima >= max(ima)/2.))*ps/4.;
  1340. if (!is_set(silent)) write,format="fwhm = %f, Strehl = %f\n",fwhm,strehl;
  1341. if (is_set(autoback))
  1342. {
  1343. tmp = ifto.re;
  1344. t1 = (tmp(2,1)+tmp(1,2)+tmp(1,0)+tmp(0,1))/4.;
  1345. t12 = (tmp(2,1)-tmp(3,1)+tmp(1,2)-tmp(1,3)+
  1346. tmp(1,0)-tmp(1,-1)+tmp(0,1)-tmp(-1,1))/4.;
  1347. ifto.re(1,1) = t1+t12;
  1348. ifto = ifto/(t1+t12);
  1349. ima = roll(float(fft((ifto/fibfto)*(tfto > 1e-8),-1)));
  1350. psf0 = ima/sum(ima);
  1351. ima = fftrebin(ima,4);
  1352. if (is_set(rmask)) // diaphragme les deux images
  1353. {
  1354. diap = dist(4*dim) <= 4*rmask;
  1355. ima = ima*roll(diap,[ima(mxx,),ima(,mxx)]);
  1356. }
  1357. airy = airy/sum(airy);
  1358. ima = ima/sum(ima);
  1359. strehlab = max(ima)/max(airy);
  1360. // Calcul de la largeur a mi-hauteur :
  1361. fwhm = sqrt(4./pi*sum(ima >= max(ima)/2.))*ps/4.;
  1362. write,format="%s\n","With automatic determination of background from points 0,1,2 of MTF :";
  1363. write,format="fwhm = %f, Strehl = %f\n",fwhm,strehlab;
  1364. }
  1365. }
  1366. //------------------------------------------------
  1367. func telftoh1(f,u,v)
  1368. {
  1369. e = -1;
  1370. if (abs(1-v) < 1e-12) {e = 1.;}
  1371. tmp= (v^2/pi)*acos((f/v)*(1.+e*(1-u^2.)/(4.*f^2.)));
  1372. return tmp;
  1373. }
  1374. //------------------------------------------------
  1375. func telftoh2(f,u)
  1376. {return -1.*(f/pi)*(1.+u)*sqrt((1.-(2*f/(1+u))^2.)* (1-((1-u)/(2*f))^2));}
  1377. //------------------------------------------------
  1378. func telftog(f,u)
  1379. {
  1380. tmp = f*0.;
  1381. z1 = where(f <= (1-u)/2.);
  1382. z2 = where(f >= (1+u)/2.);
  1383. z3 = where( f > (1-u)/2. & f < (1+u)/2. );
  1384. if (exist(z3))
  1385. {tmp(z3) = telftoh1(f(z3),u,1.) + telftoh1(f(z3),u,u) + telftoh2(f(z3),u);}
  1386. if (exist(z1)) {tmp(z1) = u^2.;}
  1387. if (exist(z2)) {tmp(z2) = 0.;}
  1388. return tmp;
  1389. }
  1390. //------------------------------------------------
  1391. func telftot0(f,cobs)
  1392. {return (telftog(f,1.)+cobs^2*telftog(f/cobs,1.)-2.*telftog(f,cobs))/(1.-cobs^2.);}
  1393. //------------------------------------------------
  1394. func telfto(lambda,dlambda,teldiam,cobs,pixsize,dim,freqc=,npt=,silent=,returnpsf=)
  1395. {
  1396. /* DOCUMENT func telfto
  1397. Syntax: telfto(lambda,dlambda,teldiam,cobs,pixsize,dim,freqc=,
  1398. npt=,silent=,returnpsf=)
  1399. Computes and returns the Modulation transfer function (FTO is
  1400. the french equivalent...) of a telescope with central obstruction
  1401. and perfect optics.
  1402. Parameters and keywords:
  1403. lambda in microns
  1404. dlambda in microns
  1405. teldiam in meters
  1406. cobs in fraction of teldiam
  1407. pixsize in arcsec
  1408. dim is output array dimension
  1409. freqc is the cut-off frequency ?
  1410. npt the number of point in dlambda (for calculations in this routine)
  1411. silent has to be set to one for suppressing printout comments
  1412. setting returnpsf has the effect of returning the PSF, not the MTF
  1413. */
  1414. if (is_void(dim))
  1415. {exit,"function telfto,lambda,dlambda,teldiam,cobs,pixsize,dim,freqc=,npt=,silent=";}
  1416. if (is_set(freqc)) {
  1417. dlamb = 0.;
  1418. lamb = 1.;
  1419. teld = 1.;
  1420. pixs = freqc*lamb/teld/4.848/dim;
  1421. } else {
  1422. dlamb = dlambda;
  1423. lamb = lambda;
  1424. teld = teldiam;
  1425. pixs = pixsize;
  1426. }
  1427. if (!is_set(npt)) npt = 5;
  1428. if (dlambda == 0.) npt = 1;
  1429. if (!is_set(silent)) {
  1430. write,format="Cut-off frequency in pixels : %7.4f\n",teld*4.848/lamb*dim*pixs;
  1431. }
  1432. mtf = array(float,dim,dim);
  1433. dd = clip(roll(dist(dim)),1e-10,);
  1434. for (i=0;i<=npt-1;i++) {
  1435. if (npt > 1) {l = lamb - dlamb*(i-(npt-1)/2.)/(npt-1);} else {l=lamb;}
  1436. fc = teld*4.848/l;
  1437. fc = fc*dim*pixs;
  1438. f = dd/fc;
  1439. mask = (f <= 1.);
  1440. f = f * mask + f(dim/2+1,dim/2+1) * (1-mask);
  1441. mtf = mtf + telftot0(f,cobs)*mask/npt;
  1442. }
  1443. mtf = mtf*sin(pi*dd/2./dim)/(pi*dd/2./dim);
  1444. // big bug detected on 2007feb13: was returning fft(mtf)^2. for PSF
  1445. // fortunately, I was not using the flag in any other routine, so no harm done
  1446. if (is_set(returnpsf)) return abs(fft(mtf,1));
  1447. return mtf;
  1448. }
  1449. //-------------------------------------------------------
  1450. func ftcb(te,tcal,tmir,gain,dim,x=)
  1451. {
  1452. /* DOCUMENT ftcb(te,tcal,tmir,gain,dim,x=)
  1453. returns [f,hbo,hcor,hbf]
  1454. AUTHOR: F.Rigaut, way back in 1996?
  1455. SEE ALSO:
  1456. */
  1457. f = indgen(dim)/te/2./dim;
  1458. if (!is_void(x)) { f = x;}
  1459. p = 2i*pi*f;
  1460. hzoh = (1.-exp(-te*p))/(te*p);
  1461. //hzoh = 1.;
  1462. hmir = 1./(1.+tmir*p);
  1463. hwfs = (1.-exp(-te*p))/(te*p);
  1464. hcal = gain*exp(-tcal*p);
  1465. hbo = hzoh*hmir*hwfs*hcal/(1-exp(-p*te));
  1466. hcor = float((1./(1.+hbo))*conj(1./(1.+hbo)));
  1467. hbf = float((hbo/(1.+hbo))*conj(hbo/(1.+hbo)));
  1468. hbo = float(hbo*conj(hbo));
  1469. return ([f,hbo,hcor,hbf]);
  1470. }
  1471. //---------------------------------------------------------
  1472. func encircled_energy(image,&ee50,xc=,yc=)
  1473. /* DOCUMENT encircled_energy(image,ee50,xc=,yc=)
  1474. * Computes encircled energy function for a 2D array.
  1475. * Keywords xc and yc optionaly specify center about which the encircled
  1476. * energy profile is to be computed. Returns optionally the value of the
  1477. * 50% encircled energy *diameter* (output).
  1478. * F.Rigaut, Nov 2001.
  1479. * SEE ALSO: findfwhm
  1480. */
  1481. {
  1482. im = float(image);
  1483. dim = (dimsof(image))(2);
  1484. if (xc == []) {xc = dim/2+1;}
  1485. if (yc == []) {yc = dim/2+1;}
  1486. e = 1.9;
  1487. npt = 20;
  1488. rv = span(1.,(dim/2.)^(1./e),npt)^e;
  1489. ee = rv*0.;
  1490. for (i=1;i<=npt;i++)
  1491. {
  1492. fil = make_pupil(dim,rv(i),xc=xc,yc=yc,real=0);
  1493. rv(i) = sqrt(sum(fil)*4/pi); // that's a diameter
  1494. ee(i) = sum(fil*im);
  1495. }
  1496. rv = grow(0.,rv);
  1497. ee = grow(0.,ee);
  1498. ee = ee/sum(im);
  1499. //fma;plg,ee,rv;
  1500. xp = span(0.,dim/2.,2*dim);
  1501. yp = interp(ee,rv,xp);
  1502. ayp = abs(yp-0.5);
  1503. ee50 = xp(ayp(mnx));
  1504. return yp;
  1505. }
  1506. //---------------------------------------------------------
  1507. func findfwhm(image,psize,nreb=,saveram=)
  1508. /* DOCUMENT findfwhm(image,psize)
  1509. * Determine the FWHM of an image. It simply rebins the image by
  1510. * a factor of 4 and counts the number of pixels above the maximum
  1511. * of the image divided by 2. Not the best accurate method but it's
  1512. * robust
  1513. * image = image for which to compute FWHM
  1514. * psize = pixel size (in whatever units FWHM has to be computed, e.g. arcsec)
  1515. * nreb = rebin factor to compute FWHM (default 4). not used when
  1516. * saveram is set
  1517. * saveram = in that case, we don't use the brutal rebin + number of
  1518. * pixels above 1/2 max method. Instead, we iteratively determined
  1519. * if the FWHM is above 8 pixels, and zoom on the peak if not.
  1520. * when this condition is verified, we use the above rebin method.
  1521. * This saves RAM when dealing with very large images and small cores.
  1522. * Modified 2010Oct05 to save RAM for ELT applications. Do a first pass
  1523. * at lower rebin factor to avoid generating too large images. Isolate
  1524. * a subimage for rebinning if necessary.
  1525. * F.Rigaut, 2001/11/10.
  1526. * SEE ALSO:
  1527. */
  1528. {
  1529. if (psize == []) psize=1;
  1530. if (nreb==[]) nreb=4;
  1531. if (!saveram) {
  1532. if (nreb==1) eq_nocopy,imreb,image;
  1533. else imreb = fftrebin(image,nreb);
  1534. fwhm = sum(imreb >= (max(imreb)/2.))/nreb^2.;
  1535. fwhm = sqrt(4.*fwhm/pi)*psize;
  1536. return fwhm;
  1537. }
  1538. // saveram:
  1539. nreb = 1;
  1540. fwhm = compfwhm(image);
  1541. while (fwhm < 8.) {
  1542. nreb *= 2;
  1543. dim = dimsof(image)(2);
  1544. hdim = dimsof(image)(2)/4;
  1545. // identify max
  1546. wmax = where2(image==max(image))(,1);
  1547. // compute sub image (1/2 the initial size):
  1548. i1 = clip(wmax(1)-hdim+1,1,);
  1549. i2 = clip(wmax(1)+hdim,,dim);
  1550. j1 = clip(wmax(2)-hdim+1,1,);
  1551. j2 = clip(wmax(2)+hdim,,dim);
  1552. image = image(i1:i2,j1:j2);
  1553. // rebin image:
  1554. imreb = fftrebin(image,nreb);
  1555. fwhm = compfwhm(imreb);
  1556. }
  1557. fwhm = fwhm*psize/nreb;
  1558. return fwhm;
  1559. }
  1560. func compfwhm(image)
  1561. {
  1562. fwhm = sum(image >= (max(image)/2.));
  1563. fwhm = sqrt(4.*fwhm/pi);
  1564. return fwhm;
  1565. }
  1566. func phi2zer(i_num, phase,pup, nzer=, kl=)
  1567. /* DOCUMENT phi2zer(phase,nzer=,&zcoeff)
  1568. * Do the decomposition of a phase screen on N zernike (or KL)
  1569. * coefficients. To be used in yao_mcao.i, to save a circular buffer
  1570. * with the Z (or KL) coefficients of the residual phase.
  1571. * B.Neichel, 2009/05/13.
  1572. * SEE ALSO:
  1573. */
  1574. {
  1575. //require,mcao_i_dir+"lib/libkl.i";
  1576. //no need anymore, they are include in myst_init.i
  1577. extern conv, w_def;
  1578. //first, find the dimension of the phase
  1579. dim_phi = dimsof(phase)(2); //in x and y
  1580. if (dimsof(phase)(1) == 3) {ndir = dimsof(phase)(4);};
  1581. //we are dealing with multiple directions
  1582. //This should be computed only once (for the first iteration)
  1583. //then, we only need "conv"
  1584. if (i_num == 1) {
  1585. if (kl) {
  1586. kl_tab = array(float,[3,dim_phi,dim_phi,nzer]);
  1587. kl_tab = make_kl(nzer,dim_phi,var,outbas,pup);
  1588. w_def = where(kl_tab(,,1));
  1589. def_kl =kl_tab(*,)(w_def,);
  1590. conv = generalized_inverse(def_kl);
  1591. } else {
  1592. prepzernike,dim_phi,sim.pupildiam;
  1593. tmp = zernike(1);
  1594. w_def = where(tmp);
  1595. zer_tab = array(float,[3,dimsof(tmp)(2),dimsof(tmp)(2),nzer]);
  1596. for (z=1;z<=nzer;z++) {
  1597. zer_tab(,,z) = zernike(z); };
  1598. def_zer =zer_tab(*,)(w_def,);///norm;
  1599. conv = generalized_inverse(def_zer);
  1600. };
  1601. };
  1602. //do the loop on each directions here.
  1603. if (ndir) ztmp = array(float,[2,ndir,nzer]);
  1604. else {
  1605. ztmp = array(float,[2,1,nzer]);
  1606. ndir = 1;
  1607. }
  1608. for (nnn=1;nnn<=ndir;nnn++){
  1609. ztmp(nnn,) = conv(,+)*(phase(,,nnn)(w_def))(+);
  1610. }
  1611. return ztmp;
  1612. }
  1613. func make_fieldstop(ns)
  1614. {
  1615. extern wfs;
  1616. // build field stop for WFS ns
  1617. subsize = sim.pupildiam/wfs(ns).shnxsub(0);
  1618. if (wfs(ns).npixpersub) subsize = wfs(ns).npixpersub;
  1619. sdim = long(2^ceil(log(subsize)/log(2)+wfs(ns).pad_simage));
  1620. fftpixsize = wfs(ns).pixsize/wfs(ns)._rebinfactor; // fft pixel size in arcsec
  1621. // if field stop size has not been set, set it to the subap fov (which seems
  1622. // the best thing to do)
  1623. if (wfs(ns).fssize==0) wfs(ns).fssize = wfs(ns)._npixels * wfs(ns).pixsize;
  1624. if (sim.verbose>=1) {
  1625. write,format="WFS#%d Field stop size = %f\n",ns,wfs(ns).fssize;
  1626. }
  1627. fs_size_fftpix = wfs(ns).fssize/fftpixsize;
  1628. if (wfs(ns).fstop=="none") {
  1629. fs = array(1n,[2,wfs(ns)._nx4fft,wfs(ns)._nx4fft]);
  1630. } else if (wfs(ns).fstop=="round") {
  1631. fs = dist(wfs(ns)._nx4fft,xc=wfs(ns)._nx/2+0.5, \
  1632. yc=wfs(ns)._nx/2+0.5)<=(fs_size_fftpix/2.);
  1633. } else { // anything else -> square FS
  1634. fs = array(0n,[2,wfs(ns)._nx4fft,wfs(ns)._nx4fft]);
  1635. hsize = long(round(fs_size_fftpix/2.));
  1636. hsize = clip(hsize,,wfs(ns)._nx/2);
  1637. fs(wfs(ns)._nx/2-hsize+1:wfs(ns)._nx/2+hsize, \
  1638. wfs(ns)._nx/2-hsize+1:wfs(ns)._nx/2+hsize) = 1n;
  1639. }
  1640. if (wfs(ns).fsoffset!=[]) {
  1641. // only roll by an integer # of fft pixel:
  1642. fsoffset = long(round(wfs(ns).fsoffset/fftpixsize));
  1643. fs = roll(fs,fsoffset);
  1644. // refresh value of fsoffset to actual value:
  1645. wfs(ns).fsoffset = fsoffset * fftpixsize;
  1646. if (sim.verbose>=2) {
  1647. write,format="WFS#%d Field Stop offsets actual values: (%f,%f) arcsec\n",
  1648. ns,(wfs(ns).fsoffset)(1),(wfs(ns).fsoffset)(2);
  1649. }
  1650. }
  1651. wfs(ns)._submask = &(float(fs));
  1652. wfs(ns)._domask = 1l;
  1653. }
  1654. func generate_vib_time_serie(sampling_time,length,white_rms,one_over_f_rms,peak,peak_rms,peak_width=)
  1655. /* DOCUMENT generate_vib_time_serie
  1656. sampling_time = loop sampling time [seconds]
  1657. length = number of point desired in generated time serie
  1658. white_rms = rms of white noise [arcsec]
  1659. one_over_f_rms = rms of 1/f noise (from 1 Hz included up to cutoff
  1660. frequency)
  1661. peak = vector containing the frequency [Hz] at which vibration
  1662. peaks are to be generated
  1663. peak_rms = vector (same number of elements as peak) containing the
  1664. rms of each peaks (arcsec)
  1665. peak_width = keyword optionaly containing a vector (same number of
  1666. elements as peak) of width of each peaks [FWHM in Hz,
  1667. default one Hz].
  1668. SEE ALSO:
  1669. */
  1670. {
  1671. local psd;
  1672. // construct power spectrum:
  1673. npt = long(ceil(length/2.))+1; //+1 to include both 0 and freqmax
  1674. freq = span(0.,1./sampling_time/2.,npt);
  1675. psdall = array(0.,npt);
  1676. // white noise:
  1677. psd = array(1.,npt);
  1678. // normalize (power theorem)
  1679. psd = psd/sum(psd)*white_rms^2.;
  1680. psdall = psd;
  1681. // one over f noise:
  1682. psd = 1./clip(freq,freq(2),);
  1683. onehz = long(ceil(1./freq(2)));
  1684. psd = psd/sum(psd(onehz:))*one_over_f_rms^2.;
  1685. psdall += psd;
  1686. // peaks:
  1687. if (peak!=[]) {
  1688. if (peak_width==[]) peak_width=array(freq(2)/10.,numberof(peak));
  1689. if ( (numberof(peak_rms) != numberof(peak)) ) \
  1690. error,"numberof(peak_rms) != numberof(peak)";
  1691. if ( (numberof(peak_width) != numberof(peak)) ) \
  1692. error,"numberof(peak_width) != numberof(peak)";
  1693. for (i=1;i<=numberof(peak);i++) {
  1694. if ( (peak(i)<0) || (peak(i)>max(freq)) ) continue;
  1695. peak_width(i) = clip(peak_width(i),freq(2)/10.,);
  1696. sigma = peak_width(i)/2.35;
  1697. psd = exp( - (freq-peak(i))^2./(2*sigma^2.));
  1698. psd = psd/sum(psd)*peak_rms(i)^2.;
  1699. psdall += psd;
  1700. }
  1701. }
  1702. psdall(1) = 0.; // null zero freq component
  1703. // add negative part:
  1704. psdtot = _(psdall,psdall(2:-1)(::-1));
  1705. norm = sum(psdtot);
  1706. // amplitude
  1707. amp = sqrt(psdtot);
  1708. // generate random phase:
  1709. pha = random(numberof(amp))*2*pi;
  1710. // do the fourier transform:
  1711. tmp = array(complex,numberof(amp));
  1712. tmp.re = amp*cos(pha);
  1713. tmp.im = amp*sin(pha);
  1714. ts = float(fft(tmp,1));
  1715. // post normalize:
  1716. if ((normts=sum(ts^2))!=0.) ts = ts * sqrt(norm/normts);
  1717. ts = ts*sqrt(length)/sqrt(2.); // don't ask, but it works and kinda make sense
  1718. write,format="rms of time series = %.3f milliarcsec\n",ts(rms)*1000.;
  1719. if (sim.debug>=2) {
  1720. fma;
  1721. plsys,2;
  1722. logxy,0,0;
  1723. plh,sqrt(psdall(2:)),freq(2:);
  1724. xytitles,"Frequency [Hz]","sqrt(PSD) (one axis)";
  1725. pltitle,swrite(format="Vibrations PSD (rms=%.1fmas)",ts(rms)*1000);
  1726. limits,square=0;
  1727. limits;
  1728. limits,-10,freq(0)+10;
  1729. hitReturn;
  1730. logxy,0,0;
  1731. limits;
  1732. }
  1733. return ts;
  1734. }
  1735. func find_examples_path(void)
  1736. {
  1737. parpath="./:"+pathform(_(Y_USER,Y_SITES,Y_SITE));
  1738. tmp = find_in_path("sh6x6.par",takefirst=1,path=parpath);
  1739. if (tmp==[]) tmp=find_in_path("data/sh6x6.par",takefirst=1,path=parpath);
  1740. if (tmp==[]) tmp=find_in_path("share/yao/examples/sh6x6.par",takefirst=1,path=parpath);
  1741. if (tmp==[]) {
  1742. parpath="/usr/share/doc/yorick-yao/examples/";
  1743. tmp = find_in_path("sh6x6.par",takefirst=1,path=parpath);
  1744. }
  1745. if (tmp==[]) {
  1746. write,"Couldn't find the examples directory";
  1747. return [];
  1748. } else write,format="Found examples directory: %s\n",dirname(tmp);
  1749. return dirname(tmp);
  1750. }
  1751. func find_doc_path(silent=)
  1752. {
  1753. parpath="./:"+pathform(_(Y_USER,Y_SITES,Y_SITE));
  1754. tmp = find_in_path("aosimul.html",takefirst=1,path=parpath);
  1755. if (tmp==[]) tmp=find_in_path("doc/aosimul.html",takefirst=1,path=parpath);
  1756. if (tmp==[]) tmp=find_in_path("share/yao/doc/aosimul.html",takefirst=1,path=parpath);
  1757. if (tmp==[]) {
  1758. parpath="/usr/share/doc/yorick-yao/doc/";
  1759. tmp = find_in_path("aosimul.html",takefirst=1,path=parpath);
  1760. }
  1761. if (tmp==[]) {
  1762. if (!silent) write,"Couldn't find the doc directory";
  1763. return [];
  1764. } else {
  1765. if (!silent) write,format="Found doc directory: %s\n",dirname(tmp);
  1766. return dirname(tmp);
  1767. }
  1768. }
  1769. func yaodoc(void)
  1770. /* DOCUMENT
  1771. will find the location of the documentation directory and open the
  1772. manual in a browser (default browser for osx, firefox for linux).
  1773. SEE ALSO:
  1774. */
  1775. {
  1776. docpath = find_doc_path(silent=1);
  1777. if (os_env=="darwin") {
  1778. system,"open "+docpath+"/manual.html";
  1779. } else if (os_env=="linux") {
  1780. system,"firefox "+docpath+"/manual.html";
  1781. }
  1782. }
  1783. func expand_path(_path)
  1784. /* DOCUMENT expand_file_name(path)
  1785. Expand leading "~" in file name PATH which must be an array of
  1786. strings (or a scalar string).
  1787. SEE ALSO: strpart, cd, get_cwd, get_file_name, protect_file_name. */
  1788. {
  1789. result = array(string, dimsof(_path));
  1790. n = numberof(_path);
  1791. cwd = get_cwd(); /* memorize current working directory */
  1792. head = string(0);
  1793. local path;
  1794. for (i=1 ; i<=n ; ++i) {
  1795. /* Expand leading "~" in file name(s). */
  1796. eq_nocopy, path, _path(i);
  1797. if (strpart(path, 1:1) == "~") {
  1798. sread, path, format="%[^/]", head;
  1799. home = cd(head);
  1800. if (home) {
  1801. cd, cwd; /* restore working directory */
  1802. tail = strpart(path, strlen(head)+1:0);
  1803. if (strlen(tail)) {
  1804. /* remove trailing '/' in HOME to avoid two '/' in result */
  1805. path = strpart(home, 1:-1) + tail;
  1806. } else {
  1807. path = strpart(home, 1:-1);
  1808. }
  1809. //~ else {
  1810. //~ eq_nocopy, path, home;
  1811. //~ }
  1812. }
  1813. }
  1814. result(i) = path;
  1815. }
  1816. return result;
  1817. }
  1818. func prime_factors(n,upper_limit=)
  1819. {
  1820. if (!upper_limit) upper_limit=7; // up to or equal
  1821. // prime up to 1000:
  1822. prime = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,\
  1823. 97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,\
  1824. 181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,\
  1825. 277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,\
  1826. 383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,\
  1827. 487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,\
  1828. 601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,\
  1829. 709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,\
  1830. 827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,\
  1831. 947,953,967,971,977,983,991,997];
  1832. prime = prime(where(prime<=upper_limit));
  1833. factors = [];
  1834. while (n>upper_limit) {
  1835. m = float(n)/prime-n/prime;
  1836. w = where(m==0);
  1837. if (numberof(w)==0) {
  1838. grow,factors,n;
  1839. return factors;
  1840. }
  1841. grow,factors,prime(w(1));
  1842. n /= prime(w(1));
  1843. }
  1844. grow,factors,n;
  1845. return factors;
  1846. }