MolyMaker

Program that helps you to create a molecule then predicts its infrared spectrum

Introduction

We can recommend MolyMaker as a teaching aid to be used by teachers and students of Chemistry. It allows you to make many "legitimate" molecules containing the elements carbon, nitrogen, oxygen and hydrogen. Each atom of these forms the following number of bonds:
  • Carbon - four (four single, two double or a triple and a single).
  • Nitrogen - three (three single, one triple or a double and a single).
  • Oxygen - two (two single or one double).
  • Hydrogen - one single.

As you drag an atom towards another, a bond shows automatically if both atoms have spare bonding capacity. The bond multiplicity is the maximum possible, so a lone nitrogen dragged to a lone carbon will generate a triple bond. We put any hydrogen atoms in place first to obtain the desired bond multiplicity. In order to obtain the correct bonding in limonene, shown in the first screenshot, we added some temporary hydrogen atoms. Can you use MolyMaker to draw limonene?

Limonene

Limonene

As you can see from the top of the screenshot, the program draws a predicted infrared spectrum for the molecule. This is hugely ambitious because of the potential complexity; professional attempts to predict spectra rely on published spectra for molecules that are closely related to the target. Lecture notes containing the actual spectrum of limonene (on page 7) give the important advice that the region from 1500-400cm-1 is the fingerprint region, which is individual to each molecule and does not have much useful information for product identification. A document entitled Interpreting IR spectra provides a useful, entertaining overview of the "interpretive power" of different regions of the spectrum.

The following screenshots show the predicted spectra of ethanol, ethanoic acid, ethyl ethanoate, propanone and 1-aminobutane. The actual spectra appear in this guide. You can compare the region from 4000 to 1500 cm-1 in the predicted and observed spectra to judge the success of each prediction.

Ethanol

Ethanol

Ethanoic acid

Ethanoic acid

Ethyl ethanoate

Ethyl ethanoate

Propanone

Propanone

1-Aminobutane

1-Aminobutane

Similarly, you can compare the predicted spectrum for acetonitrile with the published spectrum.

Acetonitrile

Acetonitrile

In order to develop this program, you will need to have downloaded Stefan Berinde`s wingraph.zip file as described in our Graphics tutorial. You should copy the unzipped wincrt.pas, wingraph.pas and winmouse.pas (from the src folder) into your program folder.

You can download a zip file (only 92KB) containing the source code, project information and MolyMaker.exe.

The Code

program MolyMaker;
{
    Copyright (c) 2012 Peter Hearnshaw

Licensed under the Apache License, Version 2.0 (the "License"); you may not
 use this file except in compliance with the License, as described at http://www.apache.org/licenses/
 and http://www.pp4s.co.uk/licenses/
}

{$APPTYPE GUI}

uses
  Ct, SysUtils, WinCrt, WinMouse, WinGraph;

type
  TAtom = record
    Z, X, Y : integer;
    linksTo : array[1 .. 20] of integer;
    linksNum : integer;
  end;

const
  AtomRadii : array[1 .. 8] of integer = (12, 0, 0, 0, 0, 17, 16, 15);

var
  Gd, Gm : smallint;
  AllAtoms : array[1 .. 30] of TAtom;
  i, a, u, v, CurrentAtom, BondingAtom, TotalAtoms, bonded,
    atomLinkedToThis, CASpareValency, iSpareValency : integer;
  me : MouseEventType;
  Spectrum : array[1 .. 4000] of integer;
  mouseIsDown, doubleCCbond, tripleCCbond, singleCCbond, CHBond, carbonylBond,
    CNBond, singleCOBond, OHBond, NHBond, tripleCNbond, imineBond : Boolean;

procedure addPeak(wavenumber : integer; width : integer; height : integer);
begin
  for a := 1 to width do
    Spectrum[wavenumber + a - round(0.5 * width)] :=
      round(Spectrum[wavenumber + a - round(0.5 * width)] + height * sin(a * (3.142 / width)));
  //Spectrum contains peak height for every wavenumber integer from 1 to 4000cm-1.
  //Here it adds a peak around the entered wavenumber value in the spectrum array.
  //The peaks are made by a sine wave.
end;

procedure checkPrincipalGroup;
var
  i : integer;

begin
  for i := 1 to 4000 do
    begin
      spectrum[i] := 0;
    end;
  //Create spectrum
  if (CHBond = True) then        //single C-H
    begin
      addPeak(2918, 22, 75); //stretch
      addPeak(2956, 47, 85); //stretch
      addPeak(1410, 35, 35); //bending in between CH2 and CH3
    end;
  if (singleCCbond = True) then //single C-C
    addPeak(1300, 25, 17);
  if (doubleCCbond = True) then
    begin
      addPeak(1630, 60, 20); //double bond C=C
      addPeak(1630, 15, 15); //double bond C=C
    end;
  if (tripleCCbond = True) then
    begin
      addPeak(2250, 25, 38);  //triple bond CC stretch
      addPeak(650, 90, 60);   //bend
    end;
  if (carbonylBond = True) then
    begin
      addPeak(1710, 100, 50);
      addPeak(3410, 20, 8); //overtone
    end;
  if (singleCOBond = True) then
    begin
      addPeak(1100, 60, 40);
      addPeak(1130, 50, 43);
      addPeak(2270, 140, 2); //overtone
    end;
  if (imineBond = True) then
    addPeak(1660, 38, 45);

  if (CNBond = True) then
    addPeak(1220, 42, 37);
  if (OHBond = True) then
    begin
      addPeak(1000, 29, 6); //out-of-plane (oop)
      addPeak(700, 500, 6); //oop

      addPeak(3300, 300, 50); //stretch
      addPeak(1300, 80, 18);  //C-O-H bending
    end;
  if (tripleCNbond = True) then
    addPeak(2250, 22, 65);
  if (NHBond = True) then
    begin
      //hard to program to prove it, but we will assume user has chosen a primary amine
      addPeak(3400, 320, 25); //main base of absorption
      addPeak(3200, 200, 8);  //diffuse right side of base
      addPeak(3500, 35, 25);  //left peak
      addPeak(3420, 60, 10);  //right peak

      addPeak(800, 500, 15);  //N-H oop bending
      addPeak(700, 150, 22);  //N-H oop bending

      addPeak(1600, 130, 22); //N-H in-plane (ip) bending
    end;
end;

procedure displaySpectrum;
var
  i : integer;
begin
  setTextStyle(DefaultFont, HorizDir, 1);
  outTextXY(420, 6, '0');
  outTextXY(320, 6, '1000');
  outTextXY(220, 6, '2000');
  outTextXY(120, 6, '3000');
  outTextXY(20, 6, '4000');
  moveTo(420, 20);
  for i := 1 to 400 do
    begin
      lineTo(420 - i, 20 + Spectrum[i * 10]);
    end;
end;

procedure setUpDesign;
var
  i, a : integer;
begin
  setTextStyle(DefaultFont, HorizDir, 2);

  clearDevice;

  outTextXY(100, 260, 'Drag Atoms');
  outTextXY(100, 270, 'Out of Boxes');

  rectangle(100, 300, 150, 350);
  outTextXY(120, 320, 'C');
  rectangle(100, 350, 150, 400);
  outTextXY(120, 370, 'H');
  rectangle(100, 400, 150, 450);
  outTextXY(120, 420, 'N');
  rectangle(100, 450, 150, 500);
  outTextXY(120, 470, 'O');

  rectangle(300, 550, 550, 650); //bin
  outTextXY(305, 555, 'The Bin');

  //SPECTRUM
  singleCCbond := False;
  doubleCCbond := False;
  tripleCCbond := False;
  CHBond := False;
  carbonylBond := False;
  CNBond := False;
  singleCOBond := False;
  OHBond := False;
  NHBond := False;
  tripleCNbond := False;
  imineBond := False;

  for i := 1 to TotalAtoms do //draw all bonding links
    begin
      a := 1;
      while (a <= AllAtoms[i].linksNum) do
        begin
          atomLinkedToThis := AllAtoms[i].linksTo[a];

          if (atomLinkedToThis = AllAtoms[i].linksTo[a + 2]) then
            begin      //triple bond
              SetLineStyle(SolidLn, 0, 10);
              line(AllAtoms[i].X, AllAtoms[i].Y - 2,
                AllAtoms[atomLinkedToThis].X, AllAtoms[atomLinkedToThis].Y - 2);
              setcolor(black);
              SetLineStyle(SolidLn, 0, 6);
              line(AllAtoms[i].X, AllAtoms[i].Y - 2,
                AllAtoms[atomLinkedToThis].X, AllAtoms[atomLinkedToThis].Y - 2);
              setcolor(white);
              SetLineStyle(SolidLn, 0, 2);
              line(AllAtoms[i].X, AllAtoms[i].Y - 2,
                AllAtoms[atomLinkedToThis].X, AllAtoms[atomLinkedToThis].Y - 2);

              inc(a);
              //a incs twice to avoid placing the next linksTo item as a single bond
              inc(a);
              if (AllAtoms[i].z = 6) and (AllAtoms[atomLinkedToThis].z = 6) then
                //SPECTRUM
                tripleCCbond := True;
              if (AllAtoms[i].z = 6) and (AllAtoms[atomLinkedToThis].z = 7) then
                //SPECTRUM
                tripleCNbond := True;
            end
          else if (atomLinkedToThis = AllAtoms[i].linksTo[a + 1]) then
            begin      //double bond
              SetLineStyle(SolidLn, 0, 6);
              line(AllAtoms[i].X, AllAtoms[i].Y - 1,
                AllAtoms[atomLinkedToThis].X, AllAtoms[atomLinkedToThis].Y - 1);
              setcolor(black);
              SetLineStyle(SolidLn, 0, 2);
              line(AllAtoms[i].X, AllAtoms[i].Y - 1,
                AllAtoms[atomLinkedToThis].X, AllAtoms[atomLinkedToThis].Y - 1);
              setcolor(white);
              inc(a);
              //a incs twice to avoid placing the next linksTo item as a single bond
              if (AllAtoms[i].z = 6) and (AllAtoms[atomLinkedToThis].z = 6) then
                //SPECTRUM
                doubleCCbond := True;
              if (AllAtoms[i].z = 6) and (AllAtoms[atomLinkedToThis].z = 8) then
                //SPECTRUM
                carbonylBond := True;
              if (AllAtoms[i].z = 6) and (AllAtoms[atomLinkedToThis].z = 7) then
                //SPECTRUM
                imineBond := True;
            end
          else
            begin
              line(AllAtoms[i].X, AllAtoms[i].Y,
                AllAtoms[atomLinkedToThis].X, AllAtoms[atomLinkedToThis].Y);
              if (AllAtoms[i].z = 6) and (AllAtoms[atomLinkedToThis].z = 6) then
                //SPECTRUM
                singleCCbond := True;
              if (AllAtoms[i].z = 6) and (AllAtoms[atomLinkedToThis].z = 1) then
                //SPECTRUM
                CHBond := True;
              if (AllAtoms[i].z = 6) and (AllAtoms[atomLinkedToThis].z = 7) then
                //SPECTRUM
                CNBond := True;
              if (AllAtoms[i].z = 6) and (AllAtoms[atomLinkedToThis].z = 8) then
                //SPECTRUM
                singleCOBond := True;
              if (AllAtoms[i].z = 7) and (AllAtoms[atomLinkedToThis].z = 1) then
                //SPECTRUM
                NHBond := True;
              if (AllAtoms[i].z = 8) and (AllAtoms[atomLinkedToThis].z = 1) then
                //SPECTRUM
                OHBond := True;
            end;
          inc(a);
        end;
    end;
  for i := 1 to TotalAtoms do  //draw all atoms
    begin
      setcolor(black);
      SetTextStyle(ArialFont, 0, 2);
      case AllAtoms[i].Z of
        1 : begin
              SetFillStyle(SolidFill, red);
              FillEllipse(AllAtoms[i].X, AllAtoms[i].Y,
                          AtomRadii[AllAtoms[i].Z], AtomRadii[AllAtoms[i].Z]);
              SetTextStyle(ArialFont, 0, 3);
              outTextXY((AllAtoms[i].X - 7), (AllAtoms[i].Y - 10), 'H');
            end;
        6 : begin
              SetFillStyle(SolidFill, yellow);
              FillEllipse(AllAtoms[i].X, AllAtoms[i].Y,
                          AtomRadii[AllAtoms[i].Z], AtomRadii[AllAtoms[i].Z]);
              SetTextStyle(ArialFont, 0, 3);
              outTextXY(AllAtoms[i].X - 7, AllAtoms[i].Y - 10, 'C');
            end;
        7 : begin
              SetFillStyle(SolidFill, blue);
              FillEllipse(AllAtoms[i].X, AllAtoms[i].Y,
                          AtomRadii[AllAtoms[i].Z], AtomRadii[AllAtoms[i].Z]);
              SetTextStyle(ArialFont, 0, 3);
              outTextXY((AllAtoms[i].X - 6), (AllAtoms[i].Y - 10), 'N');
            end;
        8 : begin
              SetFillStyle(SolidFill, white);
              FillEllipse(AllAtoms[i].X, AllAtoms[i].Y,
                          AtomRadii[AllAtoms[i].Z], AtomRadii[AllAtoms[i].Z]);
              SetTextStyle(ArialFont, 0, 3);
              outTextXY((AllAtoms[i].X - 7), (AllAtoms[i].Y - 11), 'O');
            end;
      end;
      SetTextStyle(ArialFont, 0, 16);
      setcolor(white);
    end;
  checkPrincipalGroup;
  displaySpectrum;
  updateGraph(updateNow);
end;

begin
  Gd := 9;  //Set graphics driver to 9.
  Gm := 13; //Graphics mode will be set automatically.
  SetWindowSize(800, 700);
  InitGraph(Gd, Gm, 'MolyMaker'); //Open the graph window
  updateGraph(updateOff);
  CurrentAtom := 0;
  SetLineStyle(SolidLn, 0, 2);
  SetTextStyle(ArialFont, 0, 2);
  repeat
    sleep(1);
    PollMouseEvent(me);
    //Mouse movement
    for i := 1 to TotalAtoms do
      begin
        if (sqrt(sqr(AllAtoms[CurrentAtom].X - AllAtoms[i].X) +
            sqr(AllAtoms[CurrentAtom].Y - AllAtoms[i].Y)) < 150) then
          begin
            bonded := 0;
            //Go through all the bonds for this atom.
            for a := 1 to AllAtoms[i].linksNum do
              begin
                if (AllAtoms[i].linksTo[a] = CurrentAtom) then
                  bonded := CurrentAtom;
              end;
            if (CurrentAtom = i) then
              bonded := 1;   //prevents bonding to itself

            if (AllAtoms[i].Z = 1) then
              iSpareValency := 1 - AllAtoms[i].linksNum;
            if (AllAtoms[i].Z = 6) then
              iSpareValency := 4 - AllAtoms[i].linksNum;
            if (AllAtoms[i].Z = 7) then
              iSpareValency := 3 - AllAtoms[i].linksNum;
            if (AllAtoms[i].Z = 8) then
              iSpareValency := 2 - AllAtoms[i].linksNum;
            if (AllAtoms[CurrentAtom].Z = 1) then
              CASpareValency := 1 - AllAtoms[CurrentAtom].linksNum;
            if (AllAtoms[CurrentAtom].Z = 6) then
              CASpareValency := 4 - AllAtoms[CurrentAtom].linksNum;
            if (AllAtoms[CurrentAtom].Z = 7) then
              CASpareValency := 3 - AllAtoms[CurrentAtom].linksNum;
            if (AllAtoms[CurrentAtom].Z = 8) then
              CASpareValency := 2 - AllAtoms[CurrentAtom].linksNum;

            //If either atom is fully bonded don't allow another.
            if (CASpareValency = 0) or (iSpareValency = 0) then
              bonded := 1;

            if (bonded = 0) then //if it isn't already bonded to the current atom
              begin
                if (CASpareValency > 2) and (iSpareValency > 2) then
                  begin   //add a triple bond
                    inc(AllAtoms[i].linksNum);
                    AllAtoms[i].linksTo[AllAtoms[i].linksNum] := CurrentAtom;
                    inc(AllAtoms[CurrentAtom].linksNum);
                    AllAtoms[CurrentAtom].linksTo[AllAtoms[CurrentAtom].linksNum] := i;
                  end;
                if (CASpareValency > 1) and (iSpareValency > 1) then
                  begin   //add a double bond
                    inc(AllAtoms[i].linksNum);
                    AllAtoms[i].linksTo[AllAtoms[i].linksNum] := CurrentAtom;
                    inc(AllAtoms[CurrentAtom].linksNum);
                    AllAtoms[CurrentAtom].linksTo[AllAtoms[CurrentAtom].linksNum] := i;
                  end;

                inc(AllAtoms[i].linksNum);
                AllAtoms[i].linksTo[AllAtoms[i].linksNum] := CurrentAtom;

                inc(AllAtoms[CurrentAtom].linksNum);
                AllAtoms[CurrentAtom].linksTo[AllAtoms[CurrentAtom].linksNum] := i;
              end;
          end
        else
          begin  //Check if bonded and if so then remove bond
            //Go through all the bonds for this atom.
            for a := 1 to AllAtoms[i].linksNum do
              begin
                //if the two atoms are bonded
                if (AllAtoms[i].linksTo[a] = CurrentAtom) then
                  begin
                    repeat
                      for v := a to (AllAtoms[i].linksNum) do
                        AllAtoms[i].linksTo[v] := AllAtoms[i].linksTo[v + 1];
                      dec(AllAtoms[i].linksNum);
                    until AllAtoms[i].linksTo[a] <> CurrentAtom;

                    for u := 1 to AllAtoms[CurrentAtom].linksNum do
                      //Go through all the bonds of the other atom.
                      begin
                        while (AllAtoms[CurrentAtom].linksTo[u] = i) do
                          begin
                            for v := u to (AllAtoms[CurrentAtom].linksNum) do
                              AllAtoms[CurrentAtom].linksTo[v] := AllAtoms[CurrentAtom].linksTo[v + 1];
                            dec(AllAtoms[CurrentAtom].linksNum);
                          end;
                      end;
                  end;
              end;
          end;
      end;
    if (mouseIsDown = True) then
      begin
        AllAtoms[CurrentAtom].X := getMouseX;
        AllAtoms[CurrentAtom].Y := getMouseY;
      end;

    GetMouseEvent(me);
    with me do
      case action of
        MouseActionDown :
          begin //mouse button pressed
            if (mouseIsDown = False) then
              begin
                //User clicked on box to add new atom.
                if (getMouseX < 150) and (getMouseY < 500) and
                   (getMouseX > 100) and (getMouseY > 300) then
                  begin
                    mouseIsDown := True;
                    inc(TotalAtoms);
                    CurrentAtom := TotalAtoms;
                    AllAtoms[CurrentAtom].X := getMouseX;
                    AllAtoms[CurrentAtom].Y := getMouseY;
                    if (getMouseY < 350) and (getMouseY > 300) then
                      AllAtoms[CurrentAtom].Z := 6; //Atomic number of C
                    if (getMouseY < 400) and (getMouseY > 350) then
                      AllAtoms[CurrentAtom].Z := 1;
                    if (getMouseY < 450) and (getMouseY > 400) then
                      AllAtoms[CurrentAtom].Z := 7;
                    if (getMouseY < 500) and (getMouseY > 450) then
                      AllAtoms[CurrentAtom].Z := 8;
                    AllAtoms[CurrentAtom].linksNum := 0;
                    for a := 1 to 10 do
                      AllAtoms[CurrentAtom].linksTo[a] := 0;
                  end;
                for i := 1 to TotalAtoms do //click on an atom
                  begin
                    if (AllAtoms[i].X < getMouseX + 10) and
                       (AllAtoms[i].Y < getMouseY + 10) and
                       (AllAtoms[i].X > getMouseX - 10) and
                       (AllAtoms[i].Y > getMouseY - 10) then
                      begin
                        CurrentAtom := i;
                        mouseIsDown := True;
                      end;
                  end;
              end;
          end;
        MouseActionUp :
          begin
            mouseIsDown := False;
            if (getMouseX < 150) and (getMouseY < 500) and
               (getMouseX > 100) and (getMouseY > 300) then
              mouseIsDown := True;
            if (getMouseX < 550) and (getMouseY < 650) and
               (getMouseX > 300) and (getMouseY > 550) then
              begin
                //Go through all its linking atoms.
                for a := 1 to AllAtoms[CurrentAtom].linksNum do
                  begin
                    BondingAtom := AllAtoms[CurrentAtom].linksTo[a];
                    //this is an atom it links to
                    u := 1;
                    while u <= AllAtoms[BondingAtom].linksNum do
                      //Go through all its links.
                      begin
                        if (AllAtoms[BondingAtom].linksTo[u] = CurrentAtom) then
                          begin
                            //If it has found the link then shift all above it down over it.
                            for i := u to AllAtoms[BondingAtom].linksNum do
                              begin
                                AllAtoms[BondingAtom].linksTo[i] := AllAtoms[BondingAtom].linksTo[i + 1];
                              end;
                            dec(AllAtoms[BondingAtom].linksNum);
                          end;
                        inc(u);
                      end;
                  end;
                for a := CurrentAtom to totalAtoms do
                  begin
                    //Shift other atoms in its place to avoid empty spaces.
                    AllAtoms[a] := AllAtoms[a + 1];

                    for u := 1 to totalAtoms do
                      //Now make every link to this atom one less because its id is one less.
                      begin
                        for i := 1 to AllAtoms[u].linksNum do
                          begin
                            //if it has found a link to the atom we're shifting
                            if (AllAtoms[u].linksTo[i] = a + 1) then
                              AllAtoms[u].linksTo[i] := a;
                          end;
                      end;
                  end;
                dec(totalAtoms);
              end;
          end;
      end;
    //End of mouse movement
    setUpDesign;
  until closeGraphRequest;
end.
Programming - a skill for life!

Fourteen programs (with five web versions) including 3D-Driving, GASP and Knowledge by Peter Hearnshaw