kent logo

CO538 Anonymous Questions and Answers

This page lists the various questions and answers. To submit a question, use the anonymous questions page. You may find the keyword index and/or top-level index useful for locating past questions and answers.

We have taken the liberty of making some minor typographical corrections to some of the questions as originally put. Although most of the questions here will have been submitted anonymously, this page also serves to answer some questions of general interest to those on the course.

When asking questions, please do not simply paste in your code and ask what is wrong with it, as these types of question are hard to answer in a public forum (resulting in either no answer, or an answer which will only be of use to the person asking the question). In many cases, a question about code can be formulated in general terms. For example, if you have an `IF' block that results in an `incorrect indentation' error, you might ask, ``what is the correct syntax for an occam `if' statement ?''. Questions of this form are easier to answer, and are meaningful to all.

Questions that are not suitable for these public pages (i.e. those that contain assignment-specific code), should be mailed to your seminar-leader.

Question 1:

Submission reference: IN1477

How many swallows does it take to change a parallel process?

Answer 1:


Question 2:

Submission reference: IN2031

For Assessment 1, I can link together SO, S1, print.stream and alternate using channels. I'm sure the code does what its supposed to do. However I'm unsure as to what exactly goes in to PROC q1 (code-wise). Does it take an input from print.stream that then outputs to the screen channel? Or does it output to print.stream? I'm not sure what it's exactly meant to do. I've had a look at several of the example programs and still haven't managed to work it out.

Any hints here would be appreciated :)

Answer 2:

If you have done what you say in your first sentence, you are almost done! But where have you done it? This is (or should be) the network shown in the middle of page 2 of the exercise sheet. This network is inside a box labelled q1 (i.e. this network is the implementation of the q1 process). Therefore, the code that builds this network should be the (sole) contents of the body of the PROC q1, replacing the "SKIP" line in your starter file.

The "it" in your fourth sentence makes no sense, as it seems to refer to the code discussed in your first two sentences?! The network diagram (middle, page 2) shows the output from alternate fed (via a channel whose name you have to choose) to the input of print.stream, whose output is fed to the external screen channel. The network your code implements cannot go between print.stream and the screen channel – that makes no sense! The network your code implements ("linking together SO, S1, print.stream and alternate using channels") should represent the diagram (middle, page 2) and its output should connect to the external screen channel.

All the code in the body of q1 has to do is:

The other external channels (keyboard and error) can be safely ignored (even though the Transterpreter compiler will issue a warning about their not being used).

Keywords: q1

Question 3:

Submission reference: IN2032

Following the style conventions for folds, it seems duplicate effort to use the PROC/FUNCTION header as the name. Can we include the fold marker ({{{) on the occamdoc line itself? e.g.:

  --* Some declaration. {{{

Answer 3:

Please note that folding is a property of the editor you are using, not the occam-pi programming language. Editor folding – i.e. hiding from view some lines of code, leaving a single row of (usually three) dots optionally followed by a short comment – can be done with any text document that has some formal structure (like a programming language, LaTeX, HTML, etc.).

For occam-pi, we often fold up entire PROC declarations – e.g.

  --{{{  PROC print.stream (VAL INT delay, CHAN INT in?, CHAN BYTE out!)
  --* This inputs a number and tabulates it on a single line of output.
  -- Then, it pauses for delay microseconds.  It repeats this for ever.
  -- @param delay The length of the pause (in microseconds)
  -- @param in Numbers coming in
  -- @param out Characters going out
  PROC print.stream (VAL INT delay, CHAN INT in?, CHAN BYTE out!)
      INT n:
        in ? n
        out.int (n, 10, out!)             -- out.int is from "course.module"
        out.string ("*c*n", 0, out!)      -- out.string is from "course.module"
        pause (delay)

which, as you observe, repeats a line of text twice. However, when folded, all that's seen is:

  ...  PROC print.stream (VAL INT delay, CHAN INT in?, CHAN BYTE out!)

For most editors supporting folding, the opening "{{{" and closing "}}}" fold markers must be aligned vertically – so your suggestion won't work. However, your could avoid the redundant (but hidden) text by having:

  --* This inputs a number and tabulates it on a single line of output.
  -- Then, it pauses for delay microseconds.  It repeats this for ever.
  -- @param delay The length of the pause (in microseconds)
  -- @param in Numbers coming in
  -- @param out Characters going out
  PROC print.stream (VAL INT delay, CHAN INT in?, CHAN BYTE out!)
      INT n:
        in ? n
        out.int (n, 10, out!)             -- out.int is from "course.module"
        out.string ("*c*n", 0, out!)      -- out.string is from "course.module"
        pause (delay)

Now, the body of the process (which may be very long) gets folded away and we are left seeing only the process header, preceded by its documentation:

  --* This inputs a number and tabulates it on a single line of output.
  -- Then, it pauses for delay microseconds.  It repeats this for ever.
  -- @param delay The length of the pause (in microseconds)
  -- @param in Numbers coming in
  -- @param out Characters going out
  PROC print.stream (VAL INT delay, CHAN INT in?, CHAN BYTE out!)

which I think is close to what you are wanting?

It might be nice to go a step further and fold away the documentation as well:

  --{{{  documentation
  --* This inputs a number and tabulates it on a single line of output.
  -- Then, it pauses for delay microseconds.  It repeats this for ever.
  -- @param delay The length of the pause (in microseconds)
  -- @param in Numbers coming in
  -- @param out Characters going out
  PROC print.stream (VAL INT delay, CHAN INT in?, CHAN BYTE out!)
      INT n:
        in ? n
        out.int (n, 10, out!)             -- out.int is from "course.module"
        out.string ("*c*n", 0, out!)      -- out.string is from "course.module"
        pause (delay)

which yields the view:

  ...  documentation
  PROC print.stream (VAL INT delay, CHAN INT in?, CHAN BYTE out!)

so that you can open up the documentation and code separately and browse collections of such things more compactly.

Claim: folding is a far more useful property to have in an editor than, for instance, syntax directed colouring.

Keywords: folding

Question 4:

Submission reference: IN2033

Any advice on how to go about doing the last part of q1 with regards to the print screen section, as I'm not sure of the best way to tackle this section of the problem?

Answer 4:

By "print screen section", I'm assuming you mean the second-last paragraph ... about modifying 'print.stream' to take an extra parameter (say 'nCols') that specifies how many numbers to tabulate across each line of output?

Tabulation is already handled by the line that's already there:

    out.int (n, 10, out!)

which formats the digital representation of the value of 'n', right-justified in a field-width of 10 characters (i.e. padded with spaces on the left to make up 10 characters), and outputs those 10 characters (actually, their ASCII BYTE codes) to the 'out' channel.

So, all that's needed is to write some code that ensures that the new-line code:

    out.string ("*c*n", 0, out!)

only happens every 'nCols' times around the loop.

If you really did mean the last line of the question (?), all that's needed is to modify your previous code for the body of the 'q1' process to invoke your modified 'print.stream' with a suitable value for its extra parameter.

Keywords: q1

Question 5:

Submission reference: IN2034

I'm trying to put the 'alternate' process in the network for q1, and as far as I can tell things should be programmed about right, but when I run the program, it prints 0 then 1 then it deadlocks and I'm not sure what to do about it.

What should I look for in the code, or what sort of code would likely cause a problem like this so I can change it?

Answer 5:

Make sure your 'alternate' has a loop ... that's the most common accident!

If there's no "WHILE TRUE" at the start, the code body will execute just once – enough to get the first two numbers through (one from each input) – and then terminate, leaving the approaching number stream with nowhere to go. The process before 'alternate' blocks trying to output to a terminated process and the ones before that block trying to output to a blocked process. Result: one process terminated, three processes blocked, no other processes to do anything – deadlock!

Check also that your 'S0' and 'S1' have loops! Otherwise, each will only output one number and then terminate ... which also accounts for what you are seeing.

Welcome to the wonderful world of parallel processing, :). Deadlock is a natural hazard ... but (mostly) easy to avoid as soon as you realise what's happening. There are much darker hazards ... like data races, where variables change without your code (apparently) doing anything ... and from which occam-pi protects.

Keywords: q1

Question 6:

Submission reference: IN2035

Hi, While attempting to test the output from the integrate process for q2, I keep getting this error message whenever I try to run the program:

    Running: q2.tbc
    Error at ../../../../../../../modules/course/libsrc/demo_cycles.occ:50
    Program failed, state = e, eflags = 00000000
    q2.tbc exited with error code: 1

I can't figure out what I'm doing wrong to cause this error :(. Thanks.

Editor: the following question has been rolled into this, since the answer is the same.

Hello, I'm doing Q1 and am on the bit about filtering to remove multiples of 5. I've got some code that I think would work, but my program compiles with no warnings (except that keyboard and error aren't used), but when I run it, it displays:

    Error at [file location]:88
    Program failed, state E =, eflags = 00000001
    q1.tbc exited with error code: 1

If the 88 refers to line 88, I don't understand why as line 88 is simply:

    INT input, remainder:

Can you give any reasons why this might occur (I expect the error message means far more to you than it does to me)?

Answer 6:

These look like possible problems with the Transterpreter? Do you get the same problem when running from a University PC in a terminal room?

Please send me, <p.h.welch@kent.ac.uk>, your source code and say what operating system (and version) you are running. Also, what Java (JDK) version do you have (since the jEdit part of the Transterpreter runs on Java)?

Sorry, doing this will identify yourself to me ... but finding bugs that are our fault will do you no harm! Same also, if the bug turns out to be yours!! :)

Added later: solution to the second question, at least, is below.

Thanks for sending your code – it's your fault!   ;)   But also our fault for giving such a terrible run-time error message and pointing to the wrong line number ...

Check out slides 63-64 of Basics – on the IF process. Also, look here in the occam-pi reference wiki.

Unlike in most programming languages, there is no default do-nothing if an IF-condition fails ... so:

    INT x:
      n := 42
        n = 43
	  out.string ("All is well", 0, out!)
      n := 99

causes a crash, since none of the IF-conditions (there is only one above) is true.

There is a proper engineering reason for this language decision. If you want a default do-nothing, you must say so:

    INT n:
      n := 42
        n = 43
	  out.string ("All is well", 0, out!)
      n := 99

Saying so declares that you have programmed to manage all conditions that are necessary for that IF and that no action need be taken if none of them apply. Such an explicit declaration begs to be defended in any code review.

The INMOS team who first developed occam did not have this rule in the language originally. Their first project was writing the occam compiler in occam and, then, an operating system / IDE for a micro-computer (this was 1984). Their code had plenty of implicit do-nothings at the end of IFs. Someone noticed that many bugs were down to omitted tests (that should not have been omitted). Execution passed through such IFs without failing and crashes occured later that were difficult to trace back to the real source.

They changed the language to the present rule for IF-semantics. Now, explicit do-nothings (TRUE-SKIPs) had to be defended and missing tests were found. IFs without explicit do-nothings were either correct or crashed the first time they executed in a state when the missing test should have been present. Because the crashes were at the actual causes of failure, the bugs were easy to detect and fix.

See also Question 2 (2006), Question 70 (2000) and Question 46 (2000).

It may, or may not, be consoling to know that if you had a Linux or Mac OS and could install KRoC successfully, you could compile with the debug flag:

    kroc -d q1.occ

so that when your execution fails, you are told:

    KRoC: error in PROC "filter" in file "q1.occ" line 96
    KRoC: application error, stopped.

which takes you to the line with the offending IF ...

Keywords: if , stop , transterpreter

Referrers: Question 31 (2012)

Question 7:

Submission reference: IN2037

How will the assessments be marked? Will we be assed purely on whether our code has the required funcionality, or will things such as style and efficiency be looked at as well?

Answer 7:

Correct code is not enough – it must be obvious that it is correct as well! Obscure coding, therefore, will lose marks ... style matters.

Efficiency, at this stage, is not top priority – though code that is gratuitously inefficient will lose marks.

There are, for example, some strangely complicated ways of coding S0. Keep it simple and clear, which is easy to say but one of the trickiest engineering skills to master. Absolutely essential to master though, :).

Keywords: q1

Question 8:

Submission reference: IN2038

Why can you not use id() in a SEQ? For example:

      id (in0?, out!) 
      id (in1?, out!) --This line is ignored or never outputted

but you can use another solution which involves copying between chan and int and that works fine? (Essentially rewriting id.)

Answer 8:

The id() process has a never-ending loop, so never terminates. Asking for something to be done after an id() process has finished is, therefore, pointless – the asked-for something will never be reached.

Generally, we use an id() process in PAR with other processes, connected to them via channels on which it receives and sends data – its function in such circuits being a one-place buffer.

I don't understand what you mean by: "involves copying between chan and int"? What chan (channel?) and what int (integer?)?

Keywords: q2

Question 9:

Submission reference: IN2039

Regarding the integrate process, I was confused on how the network was initiated. Is the following explanation correct?

Thinking sequentially, from the network diagram, it appears to begin with a number read in by the plus component. plus requires two inputs, so my thought was that the network would deadlock since only a single number could possibly be read in.

However, since all components are ran in parallel, the network is in-fact initiated by the *prefix* component. prefix outputs a 0, which is read in by plus (as well as the input number), passed to delta, with prefix copying the stream from delta from then on.

Answer 9:

Thank you for this question. Apologies for this lengthy reply ...

Thinking sequentially about a parallel network is not a good idea. The network does not begin with, nor is it initiated by, any of its parallel components. All its components start independently and in whatever order they happen to get going.

The platform on which the components run does not matter. They could be physically separate pieces of silicon real-estate, part of a larger chip, in which case the order in which they power up is indeterminate (though separated by nanoseconds). They could be on different computers in the cloud (in separate parts of the planet), in which case the order in which they start is also indeterminate (and may be hours apart). They could be software scheduled on a single core (as is the case with the Transterpreter) – in which case the order they are scheduled is still indeterminate! On some magic platform of the future, they might even start up simultaneously (though the Special Theory of Relativity pours scorn on the idea of simultaneity).

The point is that PAR components start in any order and that order does not matter! So, stop worrying about which component physically starts first.

In the case of the integrate process, its components' first actions are to communicate either between each other or with devices external to integrate. Indeed, apart from the addition in the plus component, there is nothing else that they do!

Other components (of other PARs) may get on with lots of computation (e.g. calculate pi to a billion decimal places, invert some very large matrices) before any communication. If those components are placed on separate computational engines (e.g. separate cores on a microprocessor or separate computers in the cloud), then their computations can proceed in parallel (i.e. at the same time). Clearly, the order in which they actually start is not relevant.

Back with integrate, because they communicate with each other, we do need to consider the patterns (i.e. sequences) of communication that are possible. In general (and in this case), there can be several.

The last paragraph in your question pretty much says it right.

The above bullets, however, do not describe all the possible initial patterns of communication!

For example, in the fourth bullet above, the second input value could be taken (it it is being offered by some external device) by plus (actually, by one if its freshly spawned sub-processes). So, that second external input could happen inteleaved anywhere between the two outputs from delta described in this bullet.

Do not be alarmed by all these different possible sequences in which the communications may happen. Apart from getting a feel for the rules of synchronised communication (fundemental to the occam-pi/CSP concurrency model), we don't need to think at this low-level about what is actually happening! A crucial property of the occam-pi/CSP concurrency algebra is that the function performed by a network is deterministic (i.e. independent of the order in which events actually happen). For integrate, its outputs are the running sums of its inputs, regardless of the order of its internal actions.

At least, that's the story so far ...

In lecture 6, we will introduce Choice mechanisms that are provided so that we can design processes whose functions are determined by the order in which events happen. However, the nice thing about occam-pi is that we only get non-determinism by explicitly programming it. For almost all other concurrency models, non-determinacy is the default mode of operation (which means that the understanding we had about serial coding no longer applies) and we have to become skillful in eliminating it ... and that turns out to be very hard ...

With occam-pi/CSP concurrency, our intuition about serial programming remains trustworthy. For example, a variable only changes its value if the code working on it changes its value. There's no guarantee of that with, for example, threads-and-locks concurrency – i.e. what you see is not what you get.

[Aside: in most Object Oriented languages (including Java), the position is even worse ... that guarantee does not even apply to purely serial programming ... but that's another story ...]

Keywords: q2 , non-determinism , choice , alt

Question 10:

Submission reference: IN2040

The following will compile:

    PROC test (CHAN INT out!)
	INT x:
	out ! x

and will output "-1431655766". What's significant about that number?

Answer 10:

Your code declares a variable, x, and outputs its value down a channel. That value has not been set and, therefore, may be anything, :(.

In occam-pi, there is no automatic initialisation of variables to some default value. From the point of view of engineering, that's a good thing ... because what that default value should be depends on the application.

In Java, object fields are always initialised to something (number types to zero, object types to null) ... unless, of course, you assign it yourself to some initial value in the declaration (which I always do, even when that value is zero or null). But do you know to what value Java char fields are auto-initialised?

Java has a different rule for local variables (declared within methods or constructors). For these, there is no automatic initialisation of values (same as occam-pi) and, if your code tries to use those values, the compiler declares an error and refuses to compile. That is a good thing ... and, IMHO, Java should have this rule for object fields as well.

The occam-pi compiler only issues warnings about uninitialised variables, but lets them compile anyway. This is a temporary state ... because something has broken in the uninitialised tracking logic of the compiler and, when things get more complex, it gets this wrong, :(. When this gets fixed, these compiler warnings will be replaced with errors and the compilation refused. Meanwhile, take seriously all compiler warnings and ask yourself if you need to respond!

So, what happens if you use the value of an uninitialised occam-pi variable and have ignored the warning from the compiler? Well, the variable gets the bit-pattern left in whatever piece of memory it's been allocated ... which could be anything (especially for pieces of memory re-allocated during runtime).

However, for security reasons, many runtimes clear all memory to zero before running a program.

The Transterpreter takes the same care, but (I need to check this!) clears all memory to the bit-pattern "10101010101010101010101010101010" (which is -1431655766 in decimal ... I think!), just because that is a value that is most unlikely to be the default value you want ... and you will notice this ... and do something about it!

For the same reason, the KRoC runtime also clears its memory, but less excitingly to the smallest integer (-2147483648).

Clearing memory to zero is not safe, for the same engineering reason that auto-initialising number variables to zero is not safe: for many cases, initialising to zero is what's wanted ... but not for all cases. If we are lazy when coding and rely on auto-initialisation when we shouldn't, the bug from an incorrect variable initialisation to zero (which manifests itself some time later) may be difficult to find. If we are lazy when coding and rely on auto-initialisation at any time, getting an initial value of -1431655766 (or -2147483648) will probably be more noticeable!

Keywords: initial , q2

Question 11:

Submission reference: IN2042

This question relates to the pairs2 implementation of assessment 3 (q4.occ).

In order to implement this process, I must have a component that will 'flip' the addition to a subtraction. Quoting from the text: "A neater way is to leave the existing sub-components alone, but introduce an extra component into the circuit to achieve the required effect. [A process suitable for this new component has been described in the course.]"

What process is this? I have not seen such a process?

Answer 11:

Answering your questions in reverse order: yes you have ... and you have to find (or re-invent) it yourself! ;)

Keywords: q4

Question 12:

Submission reference: IN2043

In exercise 4, I have a freeze.control proc that takes the output of print.streams. The behaviour is controlled by monitor.

If I do not forward the input to screen AND do not read the input, the program seems to lock (no deadlock error).

However, if I just read the input without fowarding it, I can freeze than restart to output to screen without problems.

I can't figure out why.

Answer 12:

Sorry – cannot understand your question. In paragraphs 2 and 3, to which process does the "I" refer and to which channel does "input" refer? In para 3, to what channel is the input being "forwarded"? Thanks.

Keywords: q4

Referrers: Question 15 (2011)

Question 13:

Submission reference: IN2044

For assessment 3 (q4), it is required for pairs to be flipped to differentiate on the push of a button and reverse if pushed again. How would I implement this e.g: what is the best structure to use: IF or CASE?

Answer 13:

If there is a 2-valued decision to make, then a boolean expression managed by an IF is appropriate. If there is an n-way decision and (n > 2) and the decision is to choose between a known set of options (represented by constant integer or character values), then a CASE is appropriate.

We have not yet presented the CASE structure in the lectures, but there is a forward reference to it at the end of the the question (Exercise 4) on the more-exercises sheet. The note there recommends its use for processing character input from the keyboard channel (i.e. in your monitor process).

Keywords: q4

Question 14:

Submission reference: IN2041

Hello, I am trying to implement what is on Slide 51 of Mobiles. I don't know how to actually send the mobile record of my recursive channel down my custom channel.

Editor: rest of question omitted.

Answer 14:

Sorry – you are asking questions about mobile channels that are not in the examinable material for this module!

Now, we do welcome such questions ... but you haven't given enough information for us to understand your particular problem. Please contact me <phw@kent.ac.uk> or Fred <frmb@kent.ac.uk> and fix a time to see one of us. Thanks.

Keywords: mobiles

Question 15:

Submission reference: IN2045

This is a follow-up to Question 12 (2011) ...

    PROC freeze.control (...)

Editor: rest of question omitted.

Answer 15:

Thank you for giving more information on the area of your problem. However, without seeing your code, I can't help. The description you give is still not enough to diagnose your error.

If you send me (<phw@kent.ac.uk>) your code, I will point out any misunderstanding of system behaviour that's apparent (but, of course, I cannot correct your code).

Keywords: q4

Question 16:

Submission reference: IN2046

In q4, I'm building the first network diagram where you have to connect all the deltas into the printstream, but it says there are too many actual parameters. Is there any way to resolve this please?

Answer 16:

There is no printstream process. There is a print.stream, but that takes only one input stream and is only used for the first part of this exercise. For the first modification (with the deltas), there are three streams that need printing and the data-flow diagram shows print.streams (plural) to be used.

Note also that print.streams (in your q4.occ starter code) takes four parameters – the third being an array of channels (the three streams, in this case, for printing). See Basics slide 100 for an outline of its use: outline because the example in that slide prints from four channels and, to avoid distraction, does not have the first two parameters (column width and delay) that are defined for the print.streams given in the starter code.

Keywords: q4

Question 17:

Submission reference: IN2048

Hi, I'm just doing the speed control part, and it has come to my attention that there are two different versions of more-exercises.pdf floating around. I personally used the one on raptor, but I have found out from a friend the moodle one says the upper limit should be 256 lines/second, where as the raptor one says it is 1024 lines/second.

Which is it, and can the old versions of files be removed to avoid confusion please?

Answer 17:

I've just checked. There are two ways to find more-exercises.pdf from the Co538 Moodle page: from the Assessment 3 box and from the "Exercise sheets ..." link in the Teaching Resources box. These link to different, but identical, files. The same-named file on raptor (in courses\co538\exercises\) is also identical (and says limit the rate of output to 256 lines/second). Your q4.occ starter file (unless we have made a mistake there) also says 256. The q4.occ on raptor (in courses\co538\exercises\) says 256.

There used to be a version where the limit was 1024. We lowered it because your terminal emulator probably can't go that fast and, if it could, you wouldn't be able to read it! We must have an old version somewhere you can access ... please mail Peter, <phw@kent.ac.uk>, where - thanks!

PS: nobody would lose any marks for setting a limit of 1024 instead of 256.

Keywords: q4

Question 18:

Submission reference: IN2047

When I 1 000 000 of delay for the pause method (which should be 1 second when I run it it seems to be delaying for about 70 seconds is this purely down to the TVM or is it something else if so what am I missing?

Answer 18:

The grammar in your question does not parse. So, it's a little difficult to understand.

However, the code:

    pause (1000000)

will pause the executing process one second (= 1,000,000 microseconds) on either the KRoC compiled system (for Linux and Macs) or the Transterpreter (so long as you have not switched it to the runtime for the Lego Mindstorms ... in which case, the pause would be 1,000 seconds (= 1,000,000 milliseconds)). Of course, you must not have changed the implementation of the pause process in your starter file.

There is no way it will result in a delay of around 70 seconds. If still a problem, please mail Fred, <frmb@kent.ac.uk>, your code and ask nicely, :).

Keywords: q4

Referrers: Question 21 (2011)

Question 19:

Submission reference: IN2050

Hi, I am very confused with how to go about implementing the freeze part of q4. Unfortunately, I was not able to do this in q3 either! Help!

Answer 19:

Sorry – can't just tell you the answer ... it's too simple ...

Think this way: a data stream through a channel only flows if the processes through which it is routed are programmed to execute appropriate input and output instructions. If those instructions are not being executed (e.g. the code is doing something else ... like waiting for some signal to resume the flow), the stream cannot flow. The default action is no action. Data flows only if it is programmed to flow. What you see (in your code) is what you get.

Hope this helps!

Keywords: q3 , q4

Question 20:

Submission reference: IN2049

When I output BELL to error! in q4 it does not BELL error message the console. However, it just exits the program with error code 1.

Is this meant to happen or is there something wrong with my code?

Answer 20:

That should not happen! Please send Fred <frmb@kent.ac.uk> your code.

Keywords: q4

Valid CSS!

Valid XHTML 1.0!

This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License.
Last modified Mon May 20 13:50:22 2013
This document is maintained by Fred Barnes, to whom any comments and corrections should be addressed.