YAML To LMS and LATEX Processor

General purpose

We are often faced with creating quizzes, assigments, and online tests using tools such as canvas or even simply writing LaTeX files for distribution. Platforms such as blackboard make it possible to realize that objective but their format is unforgiving and does not allow for advanced formatting (including the insertion of math symbols). At the same time, we wish to keep those questions in a raw form so that they can be archived, edited, and reused. Here, I introduce the use of a standard yaml file for this purpose. The goal of yaml2lms is to convert a yaml file into a file usable for multiple-choice questions in LMS. The code also creates LaTeX and PDF files as needed.

In addition to creating neat files, this approach is a good way to archive questions in a flexible format and it also provides a spellchecking utility.

Warning

yaml2lms is a hack. No question about it. It was written as a utility that has saved me a lot of time and headache as a certain pandemic forced online teaching. It is not an example of best practice in coding but it works. It really does (I think).

Functionalities

  1. Creation of files needed for integration with LMS or Canvas, including math symbols.

  2. Creation of latex files using the exam class.

  3. Creation of latex files with answer keys.

  4. Spellchecking.

Installation

There is no installation needed as this is a simple Python script. It can be downloaded from Download.

Just make sure to have:

  1. A working LaTeX environment in place

  2. Python installed

Note

If you wish to use specific LaTeX packages in your mathematical formulas, you will need to edit the python script directly. There is work in progress to make this process simpler.

Hint

Feel free to contact me by email with any question!

Basic usage and configuration file

The process is simply to run the python script (see Download) in a directory where the config.yaml file described below is located. A question file must also be findable (see yamlfile: keyword below).

Configuration file

All the necessary configuration information is provided in the config.yaml input file. The file reads as:

yamlfile: quiz20.yaml
spellcheck: no
createLMS: yes
createLMS_text: no
base: "http://homepages.rpi.edu/~meuniv/Images/TSM_F20/"
dir: "THERMO"
title1: 'PHYS {4420}: Thermodynamics and Statistical Mechanics (Quiz 20)'
title2: "Dr. Vincent Meunier, Fall 2020"
solutionKey: no

The various keywords (ending with a :) are mostly self-explanatory.

  1. yamlfile provides the actual file with the list of questions (see here: YAML Format (input))

  2. spellcheck is a yes/no input (more information can be found here: Spellchecking)

  3. createLMS and createLMS_text are yes/no answers (see here: LMS Output)

  4. base is only used if the createLMS: yes is used. It is also described in LMS Output.

  5. dir is only used if the createLMS: yes is used. It is also described in LMS Output.

  6. title1 and title2 are used to assemble the PDF files (both the raw exam and the version with answer keys when requested)

  7. solutionKey is a yes/no input. This provides the answer Key in a PDF file with highlighted answers (see here Portable Data File creation)

Note

This configuration file was used to create the examples shown in Example of multiple questions.

Portable Data File creation

By default, yaml2lms always creates a PDF file, using latex processing and the exam class. The output file name (<prefix>.pdf) is built based on the yaml file name provided using yamlfile: <prefix>.yaml in config.yaml.

The document will have a two-line title, provided by the tags title1 and title2 in the config.yaml file.

An example of PDF file output is shown in Example of multiple questions.

Two additional PDF files can be created if requested in the config.yaml files as described below.

Hint

During PDF file creation, a <prefix>.tex is also created. If the PDF file fails to compile (because of an issue with your latex input), you can directly refer to the latex source. You can also fine-tune that file as necessary.

Answer Key PDF file

If solutionKey: yes is specified, in addition to the questionnaire itself, a file, named <prefix>_solutions.pdf is created where the correct asnwers are highlighted. (See Example of multiple questions for an example).

Spellchecked PDF file

If spellcheck: yes is specified, yaml2lms performs a rudimentary spell checking of the input. This feature is described in details at Spellchecking.

Note

It is recommended to carefully check the PDF files before starting the process of creating LMS files. The createLMS: yes option can take a few minutes or so to process, depending on the number of questions. And it is best to perform this once you have checked the spelling and the correctness of the answer keys. Note, also, that the spellchecking is time consuming and once you are done spellchecking, it is best to turn that option off.

LMS Output

One of the goals of yaml2lms is to create an interface between a list of questions you create locally to a format that can be directly imported into an online teaching platform such as blackboard. It is called Learning Management System (LMS) at Rensselaer Polytechnic Institute. However, the same format is used widely in systems such as canvas, used broadly in many different places.

It is possible to import questions on LMS but the format is somewhat difficult to handle. The format can be found at various places online, such as as on the Blackboard website.

The primary goal of this script is to translate the yaml-formatted questions into a file readable by Blackboard. It has now been extended to provide nice latex and rst outputs. The current version is versatile with options such as: randomization, use of multiple files, various output formats, etc.

Text only

If you only have unformatted text in your questions, this is the option you need to use in the config.yaml file (createLMS_text: yes), as described in Basic usage and configuration file. In this case, all the information is stored in a file that can be directly uploaded onto lms.

Note

the name of the output file is built from the <prefix> of the input file name provided in config.yaml under yamlfile: <prefix>.yaml to which “_LMS_text.txt” is appended.

Example:

If the input is:

- Type: MC
  Text: >-
    Consider the exchange operator P12 whose effect is to swap two
    particles. What is true among the assertions below?
  Answers:
  - Choice: The eigenvalues of the operator are complex.
  - Choice: The eigenvalues of the operator are positive.
  - Choice: >-
      The eigenvalues of the operator can only take two values for any
      wavefunction describing a pair of Bosons or a pair of Fermions.
    Validity: correct
  - Choice: 'The operator is unitary but not Hermitian.'
  - Choice: 'None of the other claims are correct.  '
  Note: lecture 29

The output becomes:

MC Consider the exchange operator P12 whose effect is
 to swap two particles. What is true among the assertions below?
 The eigenvalues of the operator are complex.  incorrect The
 eigenvalues of the operator are positive.  incorrect The
 eigenvalues of the operator can only take two values for any
 wavefunction describing a pair of Bosons or a pair of Fermions.
 correct The operator is unitary but not Hermitian.  incorrect
 None of the other claims are correct.  incorrect

You can upload the file <prefix>_lms_text.txt on LMS when creating a new test (or adding a question to an existing test) and you obtain a result like this:

Alternative text

Note

In this example, we only used one question but if the yaml file contains multiple questions, they will be included into the test on LMS.

LMS using images

The method described above works very well and is very fast in terms of processing time. However, it does not allow for fancy formatting and, while possible, the inclusion of math symbols is neither straitghforward nor totally satisfactory. Here, I describe the second method to create an LMS test using more advanced formatting. In this case you need to use this option: createLMS: yes.

It is important to note that this mode uses a collection of small images to assemble the questions (each image is created by LaTeX). So, one caveat with this method is that you need a place where you will copy the images and that place has to be accessible on the web.

Important

The LMS using images mode is only possible if you have a place where you can copy the images to be accessible on a browser.

The place where the images will be copied is provided in the keyword base: <httpsite> provided in file config.yaml, as described in Basic usage and configuration file.

Note

The name of the output file is built from the <prefix> of the input file name provided in config.yaml under yamlfile: <prefix>.yaml to which “_LMS_png.txt” is appended.

Let’s describe the process using an example. The example is similar to the one we used for the text-only option but in this case, we added some LaTeX and some formatting code.

If the input is:

- Type: MC
  Text: >-
    Consider the exchange operator $\hat{P}_{12}$  whose effect is to swap two
    particles. What is \textbf{true} among the assertions below?
  Answers:
  - Choice: The eigenvalues of the operator are \textit{complex}.
  - Choice: The eigenvalues of the operator are positive.
  - Choice: >-
      The eigenvalues of the operator can only take two values for any
      wavefunction describing a pair of Bosons or a pair of Fermions.
    Validity: correct
  - Choice: 'The operator is unitary but not Hermitian.'
  - Choice: 'None of the other claims are correct.  '
  Note: lecture 29

When using the option createLMS: yes, yaml2lms creates a file <prefix>_lms_png.txt along with a directory with a collection of images. The output stored in <prefix>_lms_png.tx looks something like this (though you will probably never have to look at it, as the idea of the script is to avoid it!)

MC <p><img
src="http://homepages.rpi.edu/~meuniv/Images/TSM_F20/Questions_THERMO_test3/Q0/Q0.png"
height="33" /></p> <p><img
src="http://homepages.rpi.edu/~meuniv/Images/TSM_F20/Questions_THERMO_test3/Q0/Q0_0.png"
height="33" /></p> incorrect <p><img
src="http://homepages.rpi.edu/~meuniv/Images/TSM_F20/Questions_THERMO_test3/Q0/Q0_1.png"
height="33" /></p> incorrect <p><img
src="http://homepages.rpi.edu/~meuniv/Images/TSM_F20/Questions_THERMO_test3/Q0/Q0_2.png"
height="33" /></p> correct <p><img
src="http://homepages.rpi.edu/~meuniv/Images/TSM_F20/Questions_THERMO_test3/Q0/Q0_3.png"
height="33" /></p> incorrect <p><img
src="http://homepages.rpi.edu/~meuniv/Images/TSM_F20/Questions_THERMO_test3/Q0/Q0_4.png"
height="33" /></p> incorrect

You can see from the example that the script tells LMS that the various image files are stored, in this example, at “http://homepages.rpi.edu/~meuniv/Images/TSM_F20/Questions_THERMO_test3”.This address was created using the base: "http://homepages.rpi.edu/~meuniv/Images/TSM_F20/" provided in config.yaml and the directory is built from dir: "THERMO".

Now that you have completed this, you only need two more steps.

  1. Copy the image directory to the place where you want to move the file. This can be done easily with a method such as scp -r Questions_THERMO_test3  meuniv@rcs.rpi.edu:~/public_html/Images/TSM_F20/. This line is provided for your convenience at the end of the script. Of course you need to change the username and the address of the server where you place the file. Here I use the space provided by my university but using a different repository may not be a bad idea (github or even dropbox).

  2. Go to LMS and upload the questions, using the file <prefix>_lms_png.txt as described above.

After uploading the question, the exam looks like this:

Alternative text

The content is the same as in the example using text only. I personally prefer this approach as it makes for much nicer looking exam, even when no math is required. However, note that the other method is somewhat more straighforward.

Important

Do not turn on the creation of LMS files (using either method) until you have carefully checked the PDF created with the default options. It is also important to check that the asnwers you selected as correct are indeed correct (check the PDF with the answer keys). It is always possible to change that on LMS itself but it is not as easy.

YAML Format (input)

Each question is provided in yaml format. The format is somewhat unforgiving as spacings and alignments need to be correct for the file to be readable. Below is an example of a question using this format. Each line will be described in details.

Question example

 1   - Type: MC
 2     Text: >-
 3      In the screencast we derived an expression for the Fermi-Dirac distribution
 4      using the grand canonical ensemble. What is the grand canonical ensemble?
 5     Size: Auto
 6     Points: 2
 7     Answers:
 8      - Choice:  >-
 9          The ensemble of systems with fixed energy and entropy.
10        Validity: incorrect
11      - Choice: >-
12          The ensemble of systems with fixed energy and chemical potential.
13        Validity: incorrect
14      - Choice: 'The ensemble of systems with fixed temperature and chemical potential.'
15        Validity: correct
16      Skip: 'no'
17      Note: question regarding lecture 29

Anatomy of a question

We will now review each line. Basically, all names that are followed by “:” is a key in a dictionary. If you wish to use a “:” in your questions or answers, you need to use quotation marks (or the >- sign – it is usually used for text with multiple lines).

  1. New question starts with the type.

- Type: MC

Each new question starts with a - Type: keyword. The options are: MC (Multiple Choice), MA (Multiple Answers),…

  1. Description of the question itself.

- Text: 'This is my question'

or

- Text: >-
   This is a multiline question with various parts in it.
   Try it if you want to.

Tip

The advantage of this approach is that you can use LaTeX commands in both text and math modes in all the text used in the yaml file.

  1. For multiple-choice questions, you then have the list of possible answers:

Answers:
     - Choice:  >-
         The ensemble of systems with fixed energy and entropy.
       Validity: incorrect

Each Choice can be entered in a single-line or multiline format. There is a second keyword called Validity (case sensitive) to assign an incorrect or correct attribute to the choice. Note that you only need to provide the information for a correct asnwer as the script will assign an incorrect attribute by default.

  • Note 1: You can list as many Choice lines as you need.

  • Note 2: For some types of questions, only one answer can have the Validity: correct attribute.

  1. You can skip the question from the file without deleting it by using: Skip: 'no' (this is an optional keyword)

  2. To keep things tidy, you can add a note for each question, using the Note: keyword.

Note

The easiest way to create a question is to use an existing one as a template!

Hint

A good habit is to check your yaml file using a free online tool such as those provided by onlineyamltools.com (see, here: https://onlineyamltools.com/validate-yaml). After a while you won’t make a mistake anymore but early on, this could be frustrating.

Spellchecking

Note

This features remains in development but can be useful to identify basic typos.

Usage

When you use the spellcheck: yes in the config.yaml file, yam2lms will perform a spellcheck of the questions. Currently, the script uses the python library provided in the spellchecker module.

The script will check all words in the document and provide a corrected version called XXX_SPELLCHECKED.pdf file. Using the example provided in YAML Format (input), we get the result shown below.

Notes

  1. The process is slow. Once you have run this and corrected your mistake(s), turn this option off to avoid lenghty processing.

  2. yaml2lms does not make any correction; instead it makes suggestions as shown in the example below. In this example, most of the mistakes found are actually not mistakes and it is expected each user will look into this separately. Only one word was correctly flagged as incorrect.

  3. Clearly this utility needs work but it at least avoids making too many obvious mistkaes (oops).

Example

Alternative text

Example of multiple questions

Yaml input

- Type: MC
  Text: 'Among the following claims, which one is \textbf{not} true?'
  Size: Auto
  Points: 2
  Answers:
  - Choice: Bosons have an integer spin value and Fermions have an half integer spin value.
  - Choice: Bosons and Fermions are treated as indistiguishable particles.
  - Choice: Multiple Fermions can occupy the same quantum state.
    Validity: correct
  - Choice: Multiple Bosons can occupy the same quantum state.
  - Choice: 'All the other claims are correct.  '
  Note: lecture 29
- Type: MC
  Text: >-
     Consider the exchange operator $\hat{P}_{12}$ whose effect is to swap two
     particles. What is true among the assertions below?
  Size: Auto
  Points: 2
  Answers:
  - Choice: The eigenvalues of the operator are complex.
  - Choice: The eigenvalues of the operator are positive.
  - Choice: >-
      The eigenvalues of the operator can only take two values for any
      wavefunction describing a pair of Bosons or a pair of Fermions.
    Validity: correct
  - Choice: 'The operator is unitary but not Hermitian.'
  - Choice: 'None of the other claims are correct.  '
  Note: lecture 29
- Type: MC
  Text: 'At low energy, Fermions and Bosons follow the same statistical distribution.'
  Size: Auto
  Points: 2
  Answers:
  - Choice: This is always true.
  - Choice: This is sometimes true.
  - Choice: This is never true.
    Validity: correct
  Note: lecture 29
- Type: MC
  Text: What can you say about the chemical potential ($\mu$)?
  Size: Auto
  Points: 2
  Answers:
  - Choice: It is small when the density of matter ($n$) is small.
    Validity: correct
  - Choice: It is small when the density of matter ($n$) is large.
  - Choice: 'It does not depend on the density of matter ($n$).  '
  Note: lecture 29
- Type: MC
  Text: >-
   At high energy (large $E-\mu$ values), we can use the Botzmann,
   Fermi-Dirac, or Bose-Einstein disributrions to study any gas of
   particles (e.g., photons, electrons,\ldots)
  Size: Auto
  Points: 2
  Answers:
  - Choice: 'This is true because this corresponds to the low density limit. '
    Validity: correct
  - Choice: >-
      This is false because the three distributions do not converge to  one
      another at high energy.
  - Choice: >-
      This is a very good question. I wil make sure to read page 598 of  the
      slides posted on SLACK to understand the answer.
  Note: lecture 29
- Type: MC
  Text: >-
   Consider a bosonic particle extracted from a large distribution of the  same
   particles. A specific measurement shows that its energy is 1~eV  below the
   chemical potential of the distribution.  What can you conclude?
  Size: Auto
  Points: 2
  Answers:
  - Choice: 'The particle is very stable. '
    Validity: incorrect
  - Choice: 'The entropy of the particle is very small. '
    Validity: incorrect
  - Choice: >-
     Researchers who performed the measurements should sign up for  PHYS-2350
     at RPI: their measurement is clearly wrong.
    Validity: correct
  Note: lecture 29

Latex Output

The latex output for this entry is thus:

Alternative text

Latex Output with keys

If you used the SolutionKey: yes option, you would get:

Alternative text

Work in progress and future utilities

Yaml2LMS is a hack. It is not perfect but it gets the job done in many cases.

There are many things that can be improved (send your suggestions to me).

Here is a list of things I’m working on:

  1. Improved spellchecker

  2. Better management of latex packages

  3. Possibility of reading multiple question files

  4. Create readthedocs questionnaires for learning

Download

yam2lms is just a python script. It can be obtained below:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sat Aug 29 17:49:38 2020

@author: vmeunier
"""
#38add: scp -r Questions_THERMO_quiz14 meuniv@rcs.rpi.edu:~/public_html/Images/TSM_F20/.

import yaml
from textblob import TextBlob
from distutils.util import strtobool
from spellchecker import SpellChecker

with open("config.yaml") as conf:
    configuration = yaml.load(conf, Loader=yaml.FullLoader)

yamlfile=configuration['yamlfile']
spellcheck=configuration['spellcheck']
create_LMS=configuration['createLMS']
create_LMS_text=configuration['createLMS_text']
base=configuration['base']
dir=configuration['dir']
title1=configuration['title1']
title2=configuration['title2']
solutions=configuration['solutionKey']

print(yamlfile)

with open(yamlfile) as f:
    myset = yaml.load(f, Loader=yaml.FullLoader)

print(myset)

class Question:
    def __init__(self, Type, Text, Size, Choice, Valid):
        self.Type = Type
        self.Text = Text
        self.Size = 'Auto'
        self.Points = '0'
        self.Skip = 'False'
        self.TextString = Text
        self.Choice = []
        self.ChoiceString = []
        self.Valid = []
        self.ValidString = []

    def out(self):
        string="%s\t%s" % (self.Type,self.Text)
        for i in range(len(self.Choice)):
            string+='\t%s \t%s' %  (self.Choice[i], self.Valid[i])
        string+='\n'
        return string

    def outpng(self):
        string="%s\t%s" % (self.Type,self.TextString)
        for i in range(len(self.ChoiceString)):
            string+='\t%s \t%s' %  (self.ChoiceString[i], self.ValidString[i])
        string+='\n'
        return string

#here we want to translate input into the question class and populate it
MyQuestions=[]
l=len(myset)
print(l)
for q, question in enumerate(myset):
    ThisQuestion=Question('MC','','','','')
    if 'Type' in question.keys():
        ThisQuestion=Question(question['Type'],'','','','')
    ThisQuestion.Text=question['Text']
    if 'Size' in question.keys():
        ThisQuestion.Size=question['Size']
    else:
        ThisQuestion.Size='Auto'
    nanswers=len(question['Answers'])
    for i in range(nanswers):
        ThisQuestion.Choice.append(question['Answers'][i]['Choice'])
        if 'Validity' in question['Answers'][i].keys():
            ThisQuestion.Valid.append(question['Answers'][i]['Validity'])
        else:
            ThisQuestion.Valid.append('incorrect')
    if 'Skip' in question.keys():
        ThisQuestion.Skip=strtobool(question['Skip'])
    else:
        ThisQuestion.Skip=strtobool('no')

    if not ThisQuestion.Skip:
        MyQuestions.append(ThisQuestion)



#spell checking

def correctstring(string):
    splits=string.split()
    for i, word in enumerate(splits):
        if "$" not in word and "\\" not in word:
            #print(word)
            wordy=TextBlob(word)
            if wordy != wordy.correct():
                splits[i]="\\sout{\\textcolor{red}{" + word + "}} \\textcolor{blue}{" + str(wordy.correct())+"}"
    newstring=' '.join(splits)
    return newstring


def correctstring2(string):
    spell=SpellChecker()
    splits=string.split()
    misspelled=spell.unknown(splits)

    for i, word in enumerate(misspelled):
    # Get the one `most likely` answer
        splits[i]=spell.correction(word)

    newstring=' '.join(splits)
    return newstring


import os
os.environ["PATH"] += os.pathsep + '/Library/TeX/texbin/'
os.environ["PATH"] += os.pathsep +'/usr/local/bin/'
def latexsize(string,filename):
    size='auto'
    with open("temporary.tex","w") as myfile:
       myfile.write("\\documentclass[preview]{standalone}\n")
       myfile.write("\\usepackage[fleqn]{amsmath}\n")
       myfile.write("\\usepackage{physics}\n")
       myfile.write("\\usepackage[T1]{fontenc}")
       myfile.write("\\newcommand{\\dbar}{\\text{\\dj}}")
       myfile.write("\\DeclareUnicodeCharacter{2212}{-}")
       myfile.write("\\begin{document}\n")
       myfile.write("\\setlength{\\mathindent}{3pt}\n")
       #myfile.write("\\setlength{\\abovedisplayskip}{3pt}\n")
       myfile.write("\\newcommand{\\makenonemptybox}[2]{%\n")
       myfile.write("\\par\\nobreak\\vspace{\\ht\\strutbox}\\noindent\n")
    #FIXME: make this part of option
    #put back if you want a box around the box
    #   myfile.write("\\fbox{%\n")
       myfile.write("\\parbox[c][\\dimexpr#1-2\\fboxsep][c]{\\dimexpr\\linewidth-6\\fboxsep}{\n")
       myfile.write("\\hrule width \\hsize height 0pt\n")
       myfile.write("  #2\n")
    #put back if you want a box around the box
    #   myfile.write(" }%\n")
       myfile.write("}%\n")
       myfile.write("\\par\\vspace{\\ht\\strutbox}\n")
       myfile.write("}\n")
       myfile.write("\\makeatother\n")
       string=string.replace("$$","\\begin{equation*}",1)
       string=string.replace("$$","\\end{equation*}",1)
       size2=size.strip()

       if size2.lower()=="auto": #automatic height is size is negative
           #print(string)
           myfile.write("\\parbox[c]{\\textwidth}{\\begin{flushleft}\n%s\n\\end{flushleft}} \n" % string)
       else: #fixed height if size is provided (Auto is default)
           myfile.write("\\makenonemptybox{%s}{%s} \n" % (size, string))

       myfile.write("\\end{document}\n")
       myfile.close()

       x = os.system("pdflatex temporary.tex > latex.log 2>&1 && gs -sDEVICE=pnggray -sBATCH -sOutputFile=%s -dNOPAUSE -r1200 temporary.pdf > latex.log 2>&1" % filename)

       if x !=0:
           print ('Exit code not 0, check result!')
       else:
           #os.system('open %s' % filename)
           os.system('rm temporary.tex')


def latexquestionnaire(Questions,solutions,yamlfile):
    if solutions:
        answers='answers'
        filepdf=yamlfile.rsplit(".",1)[0]+"_solutions.pdf"
        filelatex=filepdf.rsplit(".",1)[0]+".tex"
    else:
        answers=''
        filepdf=yamlfile.rsplit(".",1)[0]+".pdf"
        filelatex=filepdf.rsplit(".",1)[0]+".tex"

    with open(filelatex,"w") as flatex:
        flatex.write("\\documentclass[%s]{exam}\n" % answers)
        flatex.write("\\usepackage{physics}\n")
        flatex.write("\\usepackage[T1]{fontenc}\n")
        flatex.write("\\usepackage[normalem]{ulem}\n")
        #below is special macro for Thermo!
        flatex.write("\\newcommand{\\dbar}{\\text{\\dj}}\n")
        flatex.write("\\usepackage[margin=.75in]{geometry}\n")
        flatex.write("\\usepackage{lastpage}\n")
        flatex.write("\\usepackage{color}\n")
        flatex.write("\\usepackage[T1]{fontenc}\n")
        flatex.write("\\DeclareUnicodeCharacter{2212}{-}")
        flatex.write("\\firstpageheader{% left\n")
        flatex.write("%s\\\\\n" % title1)
        flatex.write("%s\n" % title2)
        flatex.write("}{% center\n")
        flatex.write("}{% right\n")
        flatex.write("\ifprintanswers \\textbf{Answer Key}\n")
        flatex.write("      \\fi}\n")
        #flatex.write("\\runningheader{}{}{}\n")
        #flatex.write("\\firstpagefooter{}{\\thepage/\pageref{LastPage}}{}\n")
        #flatex.write("\\runningfooter{}{\\thepage/\pageref{LastPage}}{}\n")
        #flatex.write("\\frenchspacing\n")
        flatex.write("\\unframedsolutions\n")
        flatex.write("\\SolutionEmphasis{\\sffamily}\n")
        flatex.write("\\renewcommand{\\solutiontitle}{Answer:~}\n")
        flatex.write("\\makeatother\n")
        flatex.write("\\extraheadheight{.35in}\n")
        flatex.write("\\extrafootheight{.15in}\n")
        flatex.write("\\setlength{\\marginparwidth}{1.5in}\n")
        flatex.write("\\nopointsinmargin\n")
        flatex.write("\\pointformat{}\n")
        flatex.write("\\CorrectChoiceEmphasis{\\color{red}\\bfseries}\n")

        flatex.write("\\begin{document}\n")
        flatex.write("\\begin{questions}\n")
        for q, question in enumerate(Questions):
            flatex.write("\\question %s\n" % question.Text)
            flatex.write("\\begin{choices}\n")
            for i in range(len(question.Choice)):
                if(solutions and question.Valid[i].lower() == "correct"):
                    flatex.write("\\CorrectChoice %s\n" % question.Choice[i])
                else:
                    flatex.write("\\choice %s\n" % question.Choice[i])
            flatex.write("\\end{choices}\n")
            flatex.write("\n")
        flatex.write("\\end{questions}\n")
        flatex.write("\\end{document}\n")
        flatex.close()
    x = os.system("/Library/TeX/texbin/pdflatex %s > latex.log2 2>&1" % filelatex)
    #need to run twice to get total number of points correctly added
    x = os.system("/Library/TeX/texbin/pdflatex %s > latex.log2 2>&1" % filelatex)
    if x !=0:
        print ('Exit code not 0, check result! %s ' % filepdf)
    os.system("open %s" % filepdf)

if spellcheck:
    #spell checking and provide latex file with proposed corrections
    #we do not touch the original questions
    #the users must make the changes themselves
    import copy
    MyNewQuestions=copy.deepcopy(MyQuestions)

    for question in MyNewQuestions:
        question.Text=correctstring(question.Text)
        for i, choice in enumerate(question.Choice):
            question.Choice[i]=correctstring(question.Choice[i])

    filepdf=yamlfile.rsplit(".",1)[0]+"SPELLCHECKED.pdf"
    latexquestionnaire(MyNewQuestions,False,filepdf)

#now the original file



latexquestionnaire(MyQuestions,solutions,yamlfile)

if create_LMS_text:
 #This is for un-latexized version of the questions
    filename=yamlfile.rsplit(".",1)[0]+"_LMS_text.txt"
    f = open(filename, "w")
    for q in MyQuestions: #run over all questions
        f.write(q.out().replace("$","$$"))
    f.close()

#ID = "THERMO_"+yamlfile.rsplit(".",1)[0] #we remove suffix but only last one
ID = dir+"_"+yamlfile.rsplit(".",1)[0] #we remove suffix but only last one
if create_LMS:
    myfolder='Questions_%s' % ID
    scp_string='scp -r '+myfolder+'  meuniv@rcs.rpi.edu:~/public_html/Images/TSM_F20/.'

    print("PNG files will be stored at %s " % myfolder)
    if not os.path.exists(myfolder):
        print("Directory does not exist, creating it\n")
        os.makedirs(myfolder)
    else:
        print("Directory already exists. Files will be replaced.\n")

    #this is for latexized version of the questions
    filename=yamlfile.rsplit(".",1)[0]+"_LMS_png.txt"

    fpng = open(filename, "w")
    j=0
    for q in MyQuestions: #run over all questions
        p=j+1
    # one directory per question
        myfolder2=myfolder+"/Q%s" % j
        if not os.path.exists(myfolder2):
            os.makedirs(myfolder2)

        print("\t Question type: %s" % q.Type)
        filename=myfolder2+"/Q%s.png" % j
        latexsize(q.Text,filename)
        print(q.Text)
        filename=base+filename
        q.TextString="<p><img src=\"%s\" height=\"33\" /></p>" % filename


        for i in range(len(q.Choice)):
            filename=myfolder2+"/Q%s_%s.png" % (j, i)
            latexsize(q.Choice[i],filename)
            filename=base+filename
            q.ChoiceString.append("<p><img src=\"%s\" height=\"33\" /></p>" % filename)

            if q.Type.lower() == "mat" :
                filename=myfolder2+"/S%s.png" % i
                latexsize(q.Valid[i],filename)
                filename=base+filename
                q.ValidString.append("<p><img src=\"%s\" height=\"33\" /></p>" % filename)
            else :
                q.ValidString.append(q.Valid[i])


        fpng.write(q.outpng())
        j=j+1

    fpng.close()
    print("To copy for LMS use, executive: %s" % scp_string)