1:  // -----------------------------------------------------------
   2:  // --   An Asymptote package for drawing orbitals.
   3:  // --      Free under any GNU public license.
   4:  // --      (C) Copyright: Peter Luschny, 2008
   5:  // --              Version 1 Release 1
   6:  // -----------------------------------------------------------
   7:  // --
   8:  // --  An orbital is here an object of combinatorics, not of
   9:  // --  celestial mechanics. The set of all orbitals of given
  10:  // --  length is a lattice.
  11:  // --
  12:  // -----------------------------------------------------------
  13:  // --  See also the file orbitalgenerator.asy, which provides
  14:  // --  structures and routines to generate orbitals and the
  15:  // --  file orbitalusage.asy, which illustrates the use of
  16:  // --  the structures 'orbital' and 'orbitalgenerator'.
  17:  // --
  18:  // --  For more information see
  19:  // --  http://www.luschny.de/math/swing/orbital/orbitaldoc.pdf
  20:  // -----------------------------------------------------------
  21:  //
  22:  //  The struct Orbital provides 10 application functions.
  23:  //
  24:  //  Two graphical function:
  25:  //
  26:  //     * draw(picture dest=currentpicture, int[] jumps)
  27:  //       Draws an orbital and/or its transformed side by side
  28:  //       or in superposition according to the options set.
  29:  //
  30:  //     * draw(picture dest=currentpicture, int[] j1, int[] j2)
  31:  //       Draws two orbitals /as given/ in one box.
  32:  //       It is required that length(j1) = length(j2).
  33:  //
  34:  //  Jumps may have the values -1 or 0 or 1, at most one
  35:  //  occurrence of 0 is allowed and the sum over all jumps
  36:  //  must be 0.
  37:  //
  38:  //  Eight functions to set the options are provided:
  39:  //
  40:  //     * settransform(string option)
  41:  //       Options: "revers", "invers", "dual", "identity".
  42:  //       Default is "invers".
  43:  //       The second draw routine always uses "identity".
  44:  //
  45:  //     * setdisplay(string separate)
  46:  //       Options: "inone", "separate", "quad".
  47:  //       Default is "inone".
  48:  //       The second draw routine always uses "inone".
  49:  //
  50:  //     * setplot(string option)
  51:  //       Options: "orbonly", "transonly", "orbandtrans".
  52:  //       Default is "orbandtrans".
  53:  //       The second draw routine ignores this option.
  54:  //
  55:  //     * setlabel(string option)
  56:  //       Options: "none", "symbolic", "numeric", "info".
  57:  //       Default is "none".
  58:  //
  59:  //     * sethome(string option)
  60:  //       Options: "nohome", "tinyhome", "bighome".
  61:  //       Default is "bighome".
  62:  //
  63:  //     * setarrow(bool option)
  64:  //       Options: 'true', 'false'.
  65:  //       Default is 'false'.
  66:  //
  67:  //     * setorbpen(pen orbcolor, pen transcolor)
  68:  //       Default colors are 'green' and 'orange'.
  69:  //
  70:  //     * setfillpen(pen innercolor, pen outercolor)
  71:  //       Default colors are 'gray(0.85)' and 'white'.
  72:  //
  73:  //
  74:  //  How to interpret the output:
  75:  //
  76:  //  All orbitals start in the same position, the 'home
  77:  //  position', and return to it (an orbital path is closed.)
  78:  //
  79:  //  Note the following conventions:
  80:  //
  81:  //  The given orbital runs in the counterclockwise sense (is
  82:  //  mathematically positive oriented) and has by default the
  83:  //  color green. Any transformed orbital runs in the clockwise
  84:  //  sense (is mathematically negative oriented) and has the
  85:  //  default color orange.
  86:  //
  87:  //  In the case that both the orbital and the transformed
  88:  //  orbital are displayed in the same picture, the label and
  89:  //  other visual hints always refer to the original orbital.
  90:  //
  91:  // -----------------------------------------------------------
  92:   
  93:  import graph;
  94:   
  95:  struct Orbital
  96:  {
  97:      private picture pic;
  98:   
  99:      private int[] jumps1, jumps2;
 100:   
 101:      private bool labnone, labsymb, labnum, labinfo;
 102:      private bool orbonly, transonly, orbandtrans;
 103:      private bool inone, separate, quad;
 104:      private bool nohome, tinyhome, bighome;
 105:      private bool showarrow;
 106:   
 107:      private pair origin, homepos, labelpos1, labelpos2;
 108:      private int  segnum, circnum, mag;
 109:      private real winkinc;
 110:   
 111:      private pen pospen, negpen, radpen, circpen, labelpen,
 112:                  inpen, outpen, arrowpospen, arrownegpen;
 113:   
 114:      private string currenttrans;
 115:   
 116:      private static string[] labeloptions = {
 117:              "none", "symbolic", "numeric", "info" };
 118:   
 119:      private static string[] plotoptions = {
 120:              "orbonly", "transonly", "orbandtrans" };
 121:   
 122:      private static string[] displayoptions = {
 123:              "inone", "separate", "quad" };
 124:   
 125:      private static string[] orientation = {
 126:              "positive", "negative" };
 127:   
 128:      private static string[] homeoptions = {
 129:              "nohome", "tinyhome", "bighome" };
 130:   
 131:      private static string[] trans = {
 132:              "identity", "revers", "invers", "dual" };
 133:   
 134:      private static int[] primes = {
 135:      2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61};
 136:   
 137:      private int[] transform(int[] jump, string type)
 138:      {
 139:          if(type == "identity") { return jump; }
 140:   
 141:          int[] tra = new int[jump.length];
 142:   
 143:          if(type == "invers")
 144:              for(int i = 0; i < segnum; ++i)
 145:                  tra[i] = -jump[i];
 146:   
 147:          else if(type == "revers")
 148:              for(int i = 0; i < segnum; ++i)
 149:                  tra[i] = jump[segnum-i-1];
 150:   
 151:          else if(type == "dual")
 152:             for(int i = 0; i < segnum; ++i)
 153:                  tra[i] = -jump[segnum-i-1];
 154:   
 155:          else { tra = jump; }
 156:   
 157:          return tra;
 158:      }
 159:   
 160:      private void markhomeposition(bool pre)
 161:      {
 162:          if(nohome) return;
 163:   
 164:          real r = 0; pen p;
 165:   
 166:          if(pre)
 167:          {
 168:              if(bighome) { r = 0.6;  p = gray(0.6);}
 169:   
 170:          } else {
 171:   
 172:              if(bighome) { r = 0.25; p = black; }
 173:              if(tinyhome){ r = 0.22; p = yellow;
 174:                    fill(pic, circle(homepos,r), p);
 175:                    r = 0.1;  p = red; }
 176:          }
 177:   
 178:          fill(pic, circle(homepos,r), p);
 179:      }
 180:   
 181:      private void fillregions()
 182:      {
 183:          fill(pic,scale(circnum)*unitcircle,outpen);
 184:          fill(pic,circle(origin,homepos.x),inpen);
 185:      }
 186:   
 187:      private void marktype(int[] jump)
 188:      {
 189:          bool ele = false, dep = false;
 190:          int sum = 0;
 191:   
 192:          for(int j : jump)
 193:          {
 194:              sum += j;
 195:              ele = (sum > 0) | ele;
 196:              dep = (sum < 0) | dep;
 197:          }
 198:   
 199:          real g = 0;
 200:          if(ele & (!dep)) g = 1.0;
 201:          if(dep &   ele ) g = 0.85;
 202:          if(dep & (!ele)) g = 0.45;
 203:   
 204:          guide center = circle(origin, 1);
 205:          fill(pic, center, gray(g));
 206:          draw(pic, center);
 207:      }
 208:   
 209:      private void drawradials()
 210:      {
 211:          pair b = (1,1);
 212:   
 213:          for(int k = 0; k < segnum; ++k)  // -- roots of unity
 214:          {
 215:              b = scale(circnum)*expi(2*pi*k/segnum);
 216:              draw(pic, origin -- b, radpen);
 217:          }
 218:      }
 219:   
 220:      private void drawcircles()
 221:      {
 222:          for(int i = 1; i <= circnum; ++i)
 223:          {
 224:              draw(pic, scale(i)*unitcircle, circpen);
 225:          }
 226:      }
 227:   
 228:      private void drawarrow(bool pos)
 229:      {
 230:          if(! showarrow) return;
 231:   
 232:          real r = circnum + 1;
 233:          int  or = pos ? 1 : -1;
 234:          path pa = arc(origin, r, 15*or, 45*or);
 235:          pen  pe = pos ? arrowpospen : arrownegpen;
 236:   
 237:          draw(pic,pa,pe,Arrow(SimpleHead),PenMargins);
 238:      }
 239:   
 240:      private void draworbit(int[] jump, string orient)
 241:      {
 242:          real rad = homepos.x;
 243:          real lowink = 0, hiwink = 0;
 244:   
 245:          bool ori  = orient == "positive";
 246:          real winc = ori ? winkinc : -winkinc;
 247:          pen  farb = ori ? pospen  :  negpen;
 248:   
 249:          path P;
 250:   
 251:          for(int j : jump)
 252:          {
 253:              hiwink += winc;
 254:              P = P -- arc(origin, rad, lowink, hiwink);
 255:              lowink = hiwink;
 256:              rad += j;
 257:          }
 258:   
 259:          draw(pic, P -- cycle, farb);
 260:      }
 261:   
 262:      private void drawlabel(int[] jump)
 263:      {
 264:          pair labelpos = labelpos1;
 265:   
 266:          if(labsymb || labinfo)
 267:          {
 268:              string labeltext = "$";
 269:   
 270:              for(int j : jump)
 271:              {
 272:                      if(j >  0) labeltext += "\,+";
 273:                 else if(j <  0) labeltext += "\,-";
 274:                 else if(j == 0) labeltext += "\ *";
 275:              }
 276:   
 277:              label(pic, labeltext + "$", labelpos, labelpen);
 278:              labelpos = labelpos2;
 279:          }
 280:   
 281:          if(labnum || labinfo)
 282:          {
 283:              if(primes.length < segnum) return;
 284:   
 285:              int numer = 1, denom = 1, i = 0;
 286:   
 287:              for(int j : jump)
 288:              {
 289:                      if(j > 0) numer *= primes[i];
 290:                 else if(j < 0) denom *= primes[i];
 291:                 ++i;
 292:              }
 293:   
 294:           // string labeltext = format("$%.4g$",numer/denom);
 295:              string labeltext = "$" + (string)numer +
 296:                             "\ /\ " + (string)denom + "$";
 297:   
 298:              label(pic, labeltext, labelpos, labelpen);
 299:          }
 300:      }
 301:   
 302:      private void init()
 303:      {
 304:          circnum = (segnum % 2) == 1 ? segnum : segnum+1;
 305:          winkinc = 360 / segnum;
 306:   
 307:          real radius = (circnum + 1) / 2;
 308:          homepos     = (radius, 0);
 309:   
 310:          real labeltopmargin  = 1.2;
 311:          real labeltopmargin2 = 1.55;
 312:   
 313:          labelpos1 = (0, -circnum * labeltopmargin);
 314:          labelpos2 = (0, -circnum * labeltopmargin2);
 315:      }
 316:   
 317:      // -- This is the main function
 318:      private void draw1orbsystem(picture newpic)
 319:      {
 320:          picture oldpic = pic;  // -- save
 321:          pic = newpic;
 322:          size(pic, mag, mag);
 323:   
 324:          init();
 325:   
 326:          fillregions();
 327:          markhomeposition(true);
 328:   
 329:          drawcircles();
 330:          drawradials();
 331:   
 332:          if(orbonly | orbandtrans) marktype(jumps1);
 333:   
 334:          if(transonly | orbandtrans)
 335:          {
 336:              int[] traju = transform(jumps1, currenttrans);
 337:              if(transonly) marktype(traju);
 338:              draworbit(traju, "negative");
 339:              if((! labnone) & (! orbandtrans)) drawlabel(traju);
 340:              drawarrow(false);
 341:          }
 342:   
 343:          if(orbonly | orbandtrans)
 344:          {
 345:              draworbit(jumps2, "positive");
 346:              if(! labnone) drawlabel(jumps2);
 347:              drawarrow(true);
 348:          }
 349:   
 350:          markhomeposition(false);
 351:   
 352:          pic = oldpic;  // -- restore
 353:      }
 354:   
 355:      private void draw2orbsystems()
 356:      {
 357:          picture pic1, pic2;
 358:   
 359:          orbandtrans = false; orbonly = true;
 360:          draw1orbsystem(pic1);
 361:   
 362:          orbonly = false; transonly = true;
 363:          draw1orbsystem(pic2);
 364:   
 365:          // -- add to current picture
 366:          size(pic, mag + mag, mag);
 367:   
 368:          add(pic1.fit(),(  0, 0), W);
 369:          add(pic2.fit(),(5mm, 0), E);
 370:      }
 371:   
 372:      private void draw4orbsystems()
 373:      {
 374:          picture pic1, pic2, pic3, pic4;
 375:   
 376:          orbandtrans = false; orbonly = true;
 377:          draw1orbsystem(pic1);
 378:   
 379:          orbonly = false; transonly = true;
 380:          currenttrans = "revers";
 381:          draw1orbsystem(pic2);
 382:   
 383:          currenttrans = "invers";
 384:          draw1orbsystem(pic3);
 385:   
 386:          currenttrans = "dual";
 387:          draw1orbsystem(pic4);
 388:   
 389:          // -- add to current picture
 390:          size(pic, mag + mag, mag + mag);
 391:   
 392:          add(pic1.fit(),(  0, 0), NW);
 393:          add(pic2.fit(),(5mm, 0), NE);
 394:          add(pic3.fit(),(  0, 0), SW);
 395:          add(pic4.fit(),(5mm, 0), SE);
 396:      }
 397:   
 398:      private bool isvalid(int[] jump)
 399:      {
 400:          int sum = 0, z = 0;
 401:          bool err = false;
 402:   
 403:          for(int j : jump)
 404:          {
 405:              sum += j;
 406:                   if(j ==  1) continue;
 407:              else if(j == -1) continue;
 408:              else if(j ==  0) { z += 1; continue; }
 409:              err = true;
 410:          }
 411:   
 412:          err = err | (sum != 0) | (z > 1);
 413:   
 414:          if(err)
 415:          {
 416:               write("Error: Not a valid jump list!");
 417:          }
 418:   
 419:          if(! err)
 420:          {
 421:              jumps1 = jump;
 422:              segnum = jump.length;
 423:          }
 424:   
 425:          return ! err;
 426:      }
 427:   
 428:      restricted static Orbital Orbital(int mag)
 429:      {
 430:          Orbital orb = new Orbital;
 431:   
 432:          orb.mag = mag;
 433:          orb.origin = (0, 0);
 434:          orb.currenttrans = "invers";
 435:   
 436:          orb.inone       = true;
 437:          orb.labnone     = true;
 438:          orb.bighome     = true;
 439:          orb.showarrow   = false;
 440:          orb.orbandtrans = true;
 441:   
 442:          pen nep = linewidth(5*linewidth())+linecap(0);
 443:          orb.pospen   = nep + green;
 444:          orb.negpen   = nep + orange;
 445:          orb.inpen    = gray(0.85);
 446:          orb.outpen   = white;
 447:          orb.arrowpospen = green+linewidth(2*linewidth());
 448:          orb.arrownegpen = orange+linewidth(2*linewidth());
 449:   
 450:          orb.radpen   = currentpen + black;
 451:          orb.circpen  = black + linewidth(1.4*linewidth()) ;
 452:          orb.labelpen = font("cmr12");
 453:   
 454:          return orb;
 455:      }
 456:   
 457:   
 458:  // -------------- public functions start here ----------------
 459:   
 460:      public void settransform(string option)
 461:      {
 462:          bool transident  = trans[0] == option;
 463:          bool transrevers = trans[1] == option;
 464:          bool transinvers = trans[2] == option;
 465:          bool transdual   = trans[3] == option;
 466:   
 467:          if(! (transident | transrevers |
 468:               transinvers | transdual ))
 469:          {
 470:             write("Warning: Option 'settransform' is not valid!");
 471:             currenttrans = "identity";  // -- default
 472:          }
 473:          else
 474:          {
 475:              currenttrans = option;
 476:          }
 477:      }
 478:   
 479:      public void setlabel(string option)
 480:      {
 481:          labnone = labeloptions[0] == option;
 482:          labsymb = labeloptions[1] == option;
 483:          labnum  = labeloptions[2] == option;
 484:          labinfo = labeloptions[3] == option;
 485:   
 486:          if(! (labnone | labsymb | labnum | labinfo))
 487:          {
 488:             write("Warning: Option 'setlabel' is not valid!");
 489:             labnone = true;  // -- default
 490:          }
 491:      }
 492:   
 493:      public void setplot(string option)
 494:      {
 495:          orbonly     = plotoptions[0] == option;
 496:          transonly   = plotoptions[1] == option;
 497:          orbandtrans = plotoptions[2] == option;
 498:   
 499:          if(! (orbonly | transonly | orbandtrans))
 500:          {
 501:             write("Warning: Option 'setplot' is not valid!");
 502:             orbandtrans = true;  // -- default
 503:         }
 504:      }
 505:   
 506:      public void setdisplay(string option)
 507:      {
 508:          inone    = displayoptions[0] == option;
 509:          separate = displayoptions[1] == option;
 510:          quad     = displayoptions[2] == option;
 511:   
 512:          if(! (inone | separate | quad))
 513:          {
 514:             write("Warning: Option 'setdisplay' is not valid!");
 515:             inone = true;  // -- default
 516:          }
 517:      }
 518:   
 519:      public void sethome(string option)
 520:      {
 521:          nohome   = homeoptions[0] == option;
 522:          tinyhome = homeoptions[1] == option;
 523:          bighome  = homeoptions[2] == option;
 524:   
 525:          if(! (nohome | tinyhome | bighome))
 526:          {
 527:             write("Warning: Option 'sethome' is not valid!");
 528:             bighome = true;  // -- default
 529:          }
 530:      }
 531:   
 532:      public void setorbpen(pen orbcolor, pen transcolor)
 533:      {
 534:          pospen = linecap(0) + orbcolor;
 535:          negpen = linecap(0) + transcolor;
 536:          arrowpospen = orbcolor+linewidth(2*linewidth());
 537:          arrownegpen = transcolor+linewidth(2*linewidth());
 538:      }
 539:   
 540:      public void setfillpen(pen innercolor, pen outercolor)
 541:      {
 542:          inpen  = innercolor;
 543:          outpen = outercolor;
 544:      }
 545:   
 546:      public void setarrow(bool option)
 547:      {
 548:          showarrow = option;
 549:      }
 550:   
 551:      public void draw(picture dest=currentpicture, int[] jump)
 552:      {
 553:          if(! isvalid(jump)) return; jumps2 = jumps1;
 554:   
 555:               if(separate) draw2orbsystems();
 556:          else if(quad)     draw4orbsystems();
 557:          else draw1orbsystem(dest);
 558:      }
 559:   
 560:      public void draw(picture dest=currentpicture,
 561:                       int[] j1, int[] j2)
 562:      {
 563:          if(j1.length != j2.length)
 564:          {
 565:              write("Error: Arrays do not have the same length.");
 566:              return;
 567:          }
 568:          if(! isvalid(j1)) return; jumps2 = jumps1;
 569:          if(! isvalid(j2)) return;
 570:   
 571:          setplot("orbandtrans");
 572:          settransform("identity");
 573:          setdisplay("inone");
 574:   
 575:          draw1orbsystem(dest);
 576:      }
 577:   
 578:  }   from Orbital unravel Orbital;
 579:   
 580:   
 581:  // ----------------- End of struct Orbital -------------------