MolyMaker
Program that helps you to create a molecule then predicts its infrared spectrum
Introduction
- 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
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

Ethanoic acid

Ethyl ethanoate

Propanone

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

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.