## How do I change the simulation tolerances in Tellurium?

For very complicated and large models it may be necessary to adjust the simulator tolerances in order to get the correct simulation results. Sometimes the simulator will terminate a simulation because it was unable to proceed due to numerical errors. In many cases this is due to a bad model and the user must investgate the model to determine what the issue might be. If the model is assumed to be correct then the other option is to change the simulator tolerances. The current option state of the simulator is obtained using the getInfo call, for example:

r = roadrunner.RoadRunner ('mymodel.xml')
r.getInfo()
'this' : 22F59350
'modelName' : __main
'libSBMLVersion' : LibSBML Version: 5.13.0
'jacobianStepSize' : 1e-005
'conservedMoietyAnalysis' : false
'simulateOptions' :
{
'this' : 1B89AF28,
'reset' : 0,
'structuredResult' : 0,
'copyResult' : 1,
'steps' : 50,
'start' : 0,
'duration' : 20
},
'integrator' :
name: cvode
settings:
relative_tolerance: 0.00001
absolute_tolerance: 0.0000000001
stiff: true
maximum_bdf_order: 5
maximum_num_steps: 20000
maximum_time_step: 0
minimum_time_step: 0
initial_time_step: 0
multiple_steps: false
variable_step_size: false

}>


There are a variety of tuning parameters that can be changed in the simulator. Of interest are the relative and absolute tolerances, the maximium number of steps and the initial time step.

The smaller the relative tolerance the more accuate the solution, however too small a value will result in either excessive runtimes or more likely rounadoff errors. A relative tolerance of 1E-4 means that errors are controlled to 0.01%. An optimal value is roughly 1E-6. The absoute tolerance is used when a variable gets so small that the relative tolerance don’t make much sense to apply. In these situations the absolute error tolerance is used to control the error. A small value for the absolute tolerance is often desirable, such as 1E-12, we do not recommend going below 1E-15 for either tolerances.

To set the tolerances use the statements:

r.integrator.absolute_tolerance = 5e-10
r.integrator.relative_tolerance = 1e-3


Another parameter worth changing if the simulations are not working well is to change the initial time step. This is often set by the integrator to be a relatively large value which means that the integrator will try to reduce this value if there are problems. Sometimes it is better to provide a small initial step size to help the integrator get started, for example, 1E-5.

r.integrator.initial_time_step = 0.00001


The reader if refered to the CVODE documentation for more details.

## How do I plot phase plots using Tellurium?

Phase plots are a common way to visualize the dynamics of models where time courses are generated and one variable is plotted against the other. For example consider the following model that can show oscillations:

  v1: $Xo -> S1; k1*Xo; v2: S1 -> S2; k2*S1*S2^h/(10 + S2^h) + k3*S1; v3: S2 -> ; k4*S2;  In this model S2 positively activates reaction v2 thus forming a positive feedback loop. The rate equation for v2 include a Hill like coefficient term, S2^h, this determines the strength of the positive feedback. The oscillations originate from an interaction between the positive feedback and a non-obvious negative feedback loop at S1 beteen v1 and v2. Let us assign suitable parameter values to this model, run a simulation and plot S1 versus S2. import tellurium as te # Import pylab to access subplot plotting feature. import pylab as plt r = te.loada (''' v1:$Xo -> S1; k1*Xo;
v2:  S1 -> S2; k2*S1*S2^h/(10 + S2^h) + k3*S1;
v3:  S2 -> $w; k4*S2; # Initialize h = 2; # Hill coefficient k1 = 1; k2 = 2; Xo = 1; k3 = 0.02; k4 = 1; S1 = 6; S2 = 2 ''') m = r.simulate (0, 80, 500, ['S1', 'S2']) r.plot()  Running this script by clicking the green button in the toolbar yields the following plot: What if we’d like to investigate how the oscillations are affected by the parmaeters of the model. For example how does the model bahave when we change k1? One way to do this is to plot simulations at different k1 values onto the same plot. In this case however this will create a difficult to read graph. Instead let us create a grid of subplots where each subplot represents a different simulation. import tellurium as te import pylab as plt r = te.loada (''' v1:$Xo -> S1; k1*Xo;
v2:  S1 -> S2; k2*S1*S2^h/(10 + S2^h) + k3*S1;
v3:  S2 -> $w; k4*S2; # Initialize h = 2; # Hill coefficient k1 = 1; k2 = 2; Xo = 1; k3 = 0.02; k4 = 1; S1 = 6; S2 = 2 ''') plt.subplots(3,3) plt.figure(figsize=(12,6)) for i in range (9): r.reset() m = r.simulate (0, 80, 500, ['S1', 'S2']) plt.subplot (3,3,i+1) plt.plot (m[:,0], m[:,1], "k1=" + str (r.k1)) plt.legend() r.k1 = r.k1 + 0.2  Here we create a 3 by 3 subplot grid, start a loop that changes k1 and each time round the loop it plots the simulation onto one of the subplots. Running this script results in the following output Posted in Modeling, Pathways, Programming, Software, Systems Theory | Leave a comment ## How to get the Stoichiometry Matrix using Tellurium Here is a simple need, given a reaction model how do we get hold of the stoichiometry matrix? Consider the following simple model: import tellurium as te import roadrunner r = te.loada("""$Xo -> S1; k1*Xo;
S1 -> S2; k2*S1;
S2 -> S3; k3*S2;
S3 -> $X1; k4*S3; k1 = 0.1; k2 = 0.4; k3 = 0.5; k4 = 0.6; Xo = 1; """) print r.getFullStoichiometryMatrix()  Running this script by clicking on the green arrow in the tool bar will yield:  _J0, _J1, _J2, _J3 S1 [[ 1, -1, 0, 0], S2 [ 0, 1, -1, 0], S3 [ 0, 0, 1, -1]]  The nice thing about this output is that the columns and rows are labeled so you know exact what is what. What about much larger models? For example the iAF1260.xml model from the Bigg database (http://bigg.ucsd.edu:8888/models/iAF1260). This is a model of E. Coli that includes 1668 metabolites and 2382 reactions. We can download the iAF1260.xml file and load it into libRoadRunner using:  r = roadrunner.RoadRunner ('iAF1260.xml')  This might take up to a minute to load depending on how fast your computer is. We are assuming here that the file is located in the current directory (os.getcwd()). If not, move the file, change the current directory (using os.chdir), or use the appropriate path in the call. Rather than print out the stoichiometry matrix (don’t even try) to the screen we’ll save it to a file. Because the stoichiometry matrix is so large we will use numpy to write the matrix out as a text file: import numpy as np r = roadrunner.RoadRunner ('iAF1260.xml') st = r.getFullStoichiometryMatrix() print "Number of metabolites = ", r.getNumFloatingSpecies() print "Number of reactions = ", r.getNumReactions() np.savetxt ('stoich.txt', st) Number of metabolites = 1668 Number of reactions = 2382  One can change the formating of the output using savetxt, for example the following will output the individual stoichiometry coefficeint using 3 decimal places, 5 characters minimium, and separated by a comma. np.savetxt ('c:\\tmp\\st.txt', st, delimiter=',', fmt='%5.3f',)  You can get the labels for the rows and columns by calling r.getFloatingSpeciesIds() and r.getReactionIds() respectively. Posted in Modeling, Pathways, SBML, Systems Theory | Leave a comment ## How to do a simple simulation using Telluirum The most common requirement is the ability to carry out a simple time course simulation of a model. Consider the model: Two reactions and three metabolites, S1, S2 and S3. We can describe this system using an Antimony string: import tellurium as te import roadrunner r = te.loada (''' S1 -> S2; k1*S1; S2 -> S3; k2*S2; k1= 0.4; k2 = 0.45 S1 = 5; S2 = 0; S3 = 0 ''') m = r.simulate (0, 20, 100) r.plot()  Run the script by clicking on the green arrow in the tool bar to yield: Posted in Modeling, Pathways, SBML, Systems Theory | Leave a comment ## How do I run a stochastic simulation using Tellurium/libRoadRunner? In this post I will show you how to run a stochastic simulation using our Tellurium application. Tellurium is a set of libraries that can be used via Python. One of those libraries is libRoadRunner which is our very fast simulator. It can simulate both stochastic and detemrinistic models. Let’s illustrate a stochastic simulation using the following simple model: import tellurium as te import numpy as np r = te.loada(''' J1: S1 -> S2; k1*S1; J2: S2 -> S3; k2*S2 J3: S3 -> S4; k3*S3; k1 = 0.1; k2 = 0.5; k3 = 0.5; S1 = 100; ''')  We’ve set up the number of initial molecules of S1 to be 100 molecules. The easiest way to run a stochastic simulation is to call the gillespie method on roadrunner. This is shown in the code below: m = r.gillespie (0, 40, steps=100) r.plot()  Running this by clicking on the green button in the tool bar will give you the plot: What if you wanted to run a lot of gillespie simulations to get an idea of the distribution of trajectories? TO do that just just need ot repeat the simulation many times and plot all the results on the same graph: import tellurium as te import numpy as np r = te.loada(''' J1: S1 -> S2; k1*S1; J2: S2 -> S3; k2*S2 J3: S3 -> S4; k3*S3; k1 = 0.1; k2 = 0.5; k3 = 0.5; S1 = 100; ''') # run repeated simulation numSimulations = 50 points = 101 for k in range(numSimulations ): r.resetToOrigin() s = r.gillespie(0, 50) # No legend, do not show r.plot(s, show=False, loc=None, alpha=0.4, linewidth=1.0)  This script will yield the plot: We can do one other thing and compute the average trajectory and overlay the plot with the average line. The one thing we have to watch out for is that we must set the integrator property variable_step_size = False to false. This will ensure that time points are equally spaced and that all trajectories end at the same point in time. import tellurium as te import numpy as np r = te.loada(''' J1: S1 -> S2; k1*S1; J2: S2 -> S3; k2*S2 J3: S3 -> S4; k3*S3; k1 = 0.1; k2 = 0.5; k3 = 0.5; S1 = 100; ''') # run repeated simulation numSimulations = 50 #points = 101 # Set the physical size of the plot (units are in inches) plt.figure(figsize=(10,5)) r.setIntegrator ('gillespie') # Make sure we do this so that all trajectories # are the same length and spacings r.getIntegrator().variable_step_size = False s_sum = np.array(0.) for k in range(numSimulations): r.resetToOrigin() s = r.simulate(0, 100, steps=50) s_sum = np.add (s_sum, s) # no legend, do not show r.plot(s, show=False, loc=None, alpha=0.4, linewidth=1.0) # add mean curve, legend, show everything and set labels, titels, ... s_mean = s_sum/numSimulations r.plot(s_mean, loc='upper right', show=True, linewidth=3.0, title="Stochastic simulation", xlabel="time", ylabel="concentration", grid=True);  This will give us the following plot: Posted in Modeling, Pathways, Programming, SBML, Software | Leave a comment ## Computing the steady state using Tellurium If you’re building a model and you want to quickly find the model’s steady state, you can call the command steadyState. Let’s illustrate this with an example: import tellurium as te r = te.loada (''' # Define a simple linear chain of reactions$Xo -> S1; k1*Xo;
S1 -> S2; k2*S1;
S2 -> S3; k3*S2;
R1 -> $w; k3*R1; EP -> E; Vm1*EP/(Km + EP); E -> EP; ((Vm2+R1)*E)/(Km + E); Vm1 = 12; Vm2 = 6; Km = 0.6; k1 = 1.6; k2 = 4; E = 5; EP = 15; k3 = 3; Signal = 0.1; ''')  We’ve imported three packages, tellurium to load the model, teplugins to access AUTO2000 and pylab to gain acess to matplotlib. Once we have the model loaded we can get a handle on AUTO2000 by calling teplugins.Plugin(“tel_auto2000”) and set a number of properties in AUTO2000. This includes loading the model into AUTO200, identifying the parameter we wish to modify for the bifurcation diagram (in this case signal), following by some options to carry out a pre simulation to help with the initial location of the steady state and finally the limits for x axis for the plot, in this case -2 to 3. Details of other propoerties to change can be found by typing auto.viewManual(), make sure you have a pdf reader available. The alternative is to go to the intro page. auto = teplugins.Plugin("tel_auto2000") auto.setProperty("SBML", r.getCurrentSBML()) auto.setProperty("ScanDirection", "Negative") auto.setProperty("PrincipalContinuationParameter", "Signal") auto.setProperty("PreSimulation", "True") auto.setProperty("PreSimulationDuration", 1.0) auto.setProperty("RL0", -2.0) auto.setProperty("RL1", 3.0)  To run the bifurcation analysis we use the Python code: auto.execute()  If all was successful we can next plot the results. It is possible to plot the results using your own code (see below) but it might be more convenient to use the builtin facilties, for example: pts = auto.BifurcationPoints lbls = auto.BifurcationLabels biData = auto.BifurcationData biData.plotBifurcationDiagram(pts, lbls)  The pts vector contains the point coordinates where the bifurcation points are located. lbls give the labels that correspond to the pts vector and indicate what type of bifurcation point it represented. Finally a special object, here called biData contains the data together with a number of useful utilties. The import important of these is biData.plotBifurcationDiagram(pts, lbls) which takes pts and lbls as arguments. Running this code will generate the bifurcation plot shown below. We can also print out a text summary of the computation using the command, auto.BifurcationSummary, which returns a summary of tjhe fidnnngs. Summary : BR PT TY LAB PAR(0) L2-NORM U(1) U(2) 1 1 EP 1 3.00000E+000 2.38908E+001 1.42336E+001 1.91879E+001 1 50 2 5.48632E-001 2.14502E+001 1.06592E+001 1.86144E+001 1 95 LP 3 -9.31897E-001 1.79088E+001 7.44448E+000 1.62881E+001 1 100 4 -9.13592E-001 1.74094E+001 7.22867E+000 1.58377E+001 1 150 5 1.74114E-001 1.26908E+001 6.15211E+000 1.10999E+001 1 200 6 1.54579E+000 8.36095E+000 5.44499E+000 6.34488E+000 1 234 LP 7 2.06965E+000 5.51178E+000 4.47540E+000 3.21723E+000 1 250 8 1.84381E+000 4.03137E+000 3.51306E+000 1.97747E+000 1 300 9 -5.77433E-001 7.02653E-001 -5.14930E-001 4.78089E-001 1 325 EP 10 -2.00625E+000 2.56175E+000 -2.55121E+000 2.32100E-001  We can manually plot the data by gaining access to the numpy version of the data. To do this we use: pltData = biData.toNumpy  pltData is a numpy array where the first column is the bifurcation parameter and the remaning columns contain the species. For example to plot the bifurcation diagram for the first species in the model, R1 we would use: plt.plot(x[:,0], x[:,1], linewidth=2) plt.axvline(0, color='black') plt.xlabel ("Signal") plt.ylabel ("R1")  I added a axvline command to draw a vertical line from the zero axis. I also added some axis labeling statements. These commands will result in: What is interesting about this model is that the upper branch reaches the zero parameter value before the turning point. This means it is difficult to switch to the lower steady state by just lowering the signal. Viewing thee Network One other things we can do is view the model as a network. Telluirum comes witha simple network viewer in the package nwed. import the viewer using import nwed  at the ipython console. To view the network make sure the network viewer panel is visible, do this by going to the View menu, find panes and select, then look down the menu items and near the bottom you’ll find Network Viewer, select this option. To view the network, type the following at trhe ipython console. nwed.setsbml (r.getSBML())  The viewer should now display something like: Note that every view will be different and depends on the layout algorithm. Posted in General Science Interest, Modeling, Pathways, Software | Leave a comment ## Tikz Code for Drawing Metabolic Feedback Loops I needed some figures that displayed a variety of different negative feedback loops so I created these using Tikz. Nothing particularly special. There are some absolute distances in the code which perhaps could be removed to make it more generic. \documentclass{article} \usepackage{amsmath} \usepackage{tikz} \usetikzlibrary{arrows} \usetikzlibrary{calc} \begin{document} \begin{tikzpicture}[>=latex', node distance=2cm] \node (Xo) {}; \node [right of = Xo] (S1) {\Large$x$}; \node [right of = S1] (S2) {}; \draw [->,ultra thick,blue] (Xo) -- node[above, black] {$v_1$} (S1); \draw [->,ultra thick,blue] (S1) -- node[above, black] {$v_2$} (S2); % Lets draw a line with a blunt end, -| \draw [-|,ultra thick,blue] % start in the middle of S2, and move down 2.75 mm % ($ ... $) notation is used to add the coordinates ($ (S1) + (0mm,-2.75mm) $) % Now draw the line down by 3mm % -- means draw to, + means move by -- +(0,-3mm) % Now move back to the left of S2 % The symbol -| means draw horizontal then vertical. % If we used -- instead the line would be drawn % diagonally to the reaction edge. % (S1) is the center of the node. But we want the % blunt end to end below the S1 line and % half way to the left. The 10mm is half the node % distance of 2cm, and 1mm is slightly below % the reaction line. -| ($ (S1) - (10mm,1mm) $); \end{tikzpicture} \vspace{1cm} \begin{tikzpicture}[>=latex', node distance=2cm] \node (Xo) {}; \node [right of = Xo] (x1) {\Large$x_1$}; \node [right of = x1] (x2) {\Large$x_2$}; \node [right of = x2] (x3) {}; \draw [->,ultra thick,blue] (Xo) -- node[above, black] {$v_1$} (x1); \draw [->,ultra thick,blue] (x1) -- node[above, black] {$v_2$} (x2); \draw [->,ultra thick,blue] (x2) -- node[above, black] {$v_3$} (x3); % Lets draw a line with a blunt end, -|, using the following coords \draw [-|,ultra thick,blue] % start in the middle of x2, and move down 2.75 mm % ($ ... $) notation is used to add the coordinates ($ (x2) + (0mm,-2.75mm) $) % Now draw the line down by an additional 3mm % -- means draw to, + means move by -- +(0,-3mm) % Now move back to the left of x2 % The symbol -| means draw horizontal then vertical. % If we used -- instead the line would be drawn % diagonally to the reaction edge. % (S1) is the center of the node. But we want the % blunt end to end below the S1 line and % half way to the left. The 10mm is half the node % distance of 2cm, and 1mm is slightly below % the reaction line. -| ($ (x1) - (10mm,1mm) $); \end{tikzpicture} \vspace{1cm} \begin{tikzpicture}[>=latex', node distance=2cm] \node (Xo) {}; \node [right of = Xo] (x1) {\Large$x_1$}; \node [right of = x1] (x2) {\Large$x_2$}; \node [right of = x2] (x3) {\Large$x_3$}; \node [right of = x3] (x4) {}; \draw [->,ultra thick,blue] (Xo) -- node[above, black] {$v_1$} (x1); \draw [->,ultra thick,blue] (x1) -- node[above, black] {$v_2$} (x2); \draw [->,ultra thick,blue] (x2) -- node[above, black] {$v_3$} (x3); \draw [->,ultra thick,blue] (x3) -- node[above, black] {$v_4$} (x4); % Lets draw a line with a blunt end, -| \draw [-|,ultra thick,blue] ($ (x3) + (0mm,-2.75mm) $) -- +(0,-3mm) -| ($ (x1) - (10mm,1mm) $); \end{tikzpicture} \vspace{1cm} \begin{tikzpicture}[>=latex', node distance=2cm] \node (Xo) {}; \node [right of = Xo] (x1) {\Large$x_1$}; \node [right of = x1] (x2) {\Large$x_2$}; \node [right of = x2] (x3) {\Large$x_3$}; \node [right of = x3] (x4) {\Large$x_4$}; \node [right of = x4] (x5) {}; \draw [->,ultra thick,blue] (Xo) -- node[above, black] {$v_1$} (x1); \draw [->,ultra thick,blue] (x1) -- node[above, black] {$v_2$} (x2); \draw [->,ultra thick,blue] (x2) -- node[above, black] {$v_3$} (x3); \draw [->,ultra thick,blue] (x3) -- node[above, black] {$v_4$} (x4); \draw [->,ultra thick,blue] (x4) -- node[above, black] {$v_5$} (x5); % Lets draw a line with a blunt end, -| \draw [-|,ultra thick,blue] ($ (x4) + (0mm,-2.75mm) $) -- +(0,-3mm) -| ($ (x1) - (10mm,1mm) $); \end{tikzpicture} \vspace{1cm} \begin{tikzpicture}[>=latex', node distance=2cm] \node (Xo) {}; \node [right of = Xo] (x1) {\Large$x_1$}; \node [right of = x1] (x2) {\Large$x_2$}; \node [right of = x2] (x3) {\Large$x_3$}; \node [right of = x3] (x4) {}; \draw [->,ultra thick,blue] (Xo) -- node[above, black] {$v_1$} (x1); \draw [->,ultra thick,blue] (x1) -- node[above, black] {$v_2$} (x2); \draw [->,ultra thick,blue] (x2) -- node[above, black] {$v_3$} (x3); \draw [->,ultra thick,blue] (x3) -- node[above, black] {$v_4$} (x4); % Lets draw a line with a blunt end, -| \draw [-|,ultra thick,blue] (10mm, -8mm) -- +(0,6mm); \node (x) at (10mm,-11mm) {\Large$x\$};
\end{tikzpicture}

\end{document}


## The Confusion of Modern Textbooks – Stylistic Sugar

I’ve been looking for a textbook on statistics for a class I’ll teach in the autumn term. While the content of many textbooks might be ok the way the information is presented makes them difficult to read – at least it does for me. This applies to most undergraduate textbooks published today whether they be about statistics or other topics. There seems to be a need by publishers to embellish textbooks with so much stylistic sugar that the content is buried.

I scanned two typical pages from a second-hand stats textbook I bought from Goodwill to illustrate what I mean.

I’ve marked using a red star the many different styles used in the text book on two random pages, these include:

1. Section heading with a vertical dark purple line
2. Highlighted area using three colors (black, blue and purple)
3. A notes section with blue heading
4. An Illustration section using blue font but spaced out letters and thin left-bar in dark purple
4. Up and down purple arrows in illustration section indicating question and answer
6. Paragraph of text using back font (finally something normal)
7. Figure caption in dark purple text with green vertical line and horizonal line in black
8. Exercise box in margin with heading in white with black background
9. Exercise box where the question in black with yellow background
10. Typewriter font for computer code with black and blue horizontal lines delimiting code

Not included in these pages are also other stylistic sugar:

1. Case study section that uses five colors and six stylistic features
2. Exercises with six stylistic features including at least eleven different symbols
3. Call-out in exercises using a script font with blue background.
3. Chapter practice tests in black font with blue background and thick blue horizontal line
4. Chapter Objective in black font, red bullet point in off yellow background with vertical dotted line.
5. Chapter heading page, eight stylistic features with multiple fonts

And this is before a student has even started to read the content I counted at least 15 different fonts used in the text.

## Transistor Based Flip-Flop or a (not)RS NAND Latch

Been a while since I did a posting, too much time spent writing grants, and I mean a lot of time. In this blog I thought I’d describe a small project I did a month or two ago to build a sinple RS NAND latch using transitors only.The RS stands for Reset/Set. The latch itself is based on two NAND gates connected to each other in a cycle. The following diagram (borrowed from Wikipedia) shows the latch in terms of two NAND gates:

The inputs are designated bar S and bar R (bar meaning not). The outputs are Q and bar Q. Let us assume bar S is set to digital input zero and bar R to one. If you follow the logic through the two NAND gates you’ll realise that this means that Q will have a value of zero and bar Q one. This is the reset state and relative to Q, the circut stores a value of zero. If we now set bar S to one and bar R to zero, the circuit will flip to a new state where Q is now at one and bar Q at zero. Relative to Q the circuit stores a value of one. The point of the circuit is that even if bar S (or bar R) now goes back to zero the circuit remains in its last state. The only way to change the circuit is to apply a one to the reset or set input lines. The two states that the circuit exhibits are stable.

One can buy flip-flops ready made and one can also buy NAND gates ready made (7400). What I wanted to do was build a flip-flop using discrete transistors. I found the following site that describes how to build a 4 bit adder using transitors and from their circuit diagrams I came up with the following NAND circuit using transitors:

In the orgiinal circuit the input resistors were 10K but I wasn’t able to get enough forward bias with 10K so I dropped the input resistors to 4.7K. As with the adder circuit I used common NPN BC 547 tranistors. They are pretty cheap on ebay, you can get 50 for a dollar (!). I used 10 volts for the voltage supply but 9 volts willl work too. I used the following PCB boards, cut in half, to make a single NAND circuit:

Again you can find these cheaply on ebay for about 7 dollars for 10 boards. The image below shows a single NAND gate made using the circuit above.

I made two of the above NAND gates andf connected them together. I also made two LED driver circuits that would cature the outputs Q and bar Q. The final circuit looks like:

There are two push buttons on the broad board next to the Set and Reset labels which momentarily take the inputs high (one), turning on the transitors. The circuit on the right that uses the white PC board is a simple LED driver, this also uses a BC547, with a 1K resistor on the output and a 4.7K resistor on the input. When the output from the flipflop goes to one current flows into the LED driver transistor base which in turns switches on the transitor allowing current to flow through LED which lights up. The LED circuit is shown below:

The following video shows the circuit in operation. For those who might be interested in taking this idea to the extreme, I suggest you check out the megaprocessor.