In this tutorial, we will learn how to customize the text output of theia (not the CAD output). There are two approaches which we choose according to whether we want the information output to be the same in each simulation (method 1 and 2), or we want each run to be customized by a separate configuration file (method 3).

Virtually, any information that is accessible to the objects (beams, optics, simulations, etc.) can be output, and it suffices to change the output functions at the right place in the code.

Method 1: Permanent changes using the lines method

For all beams and optics, there exists a lines method, which returns a list of strings which are meant to be output in a curly-braces-structured paragraph, as explained in the very last paragraph of the User Guide. Each string in the list returned by the lines method will appear on one line in the text output, and they will be organized according to the braces (‘{‘, ‘}’) contained in the lines.

Note that in the current version of theia (v0.1.3), these methods are implemented but never used for output. They’re just waiting there to be used in a outputting function.

For example, here is an extract from the lines method of the optics.beam.GaussianBeam class:

def lines(self):
        '''Returns the list of lines necessary to print the object.

        '''
        sph = geometry.rectToSph(self.Dir)
        sphx = geometry.rectToSph(self.U[0])
        sphy = geometry.rectToSph(self.U[1])

        return ["Beam: %s {" %self.Ref,
        "Power: %sW/Index: %s/Wavelength: %snm/Length: %sm" \
                % (str(self.P), str(self.N), str(self.Wl/nm), str(self.Length)),
        "Order: %s" %str(self.StrayOrder),
        "Origin: %s" %str(self.Pos),
        #...
        "Rayleigh: %sm" % str(self.rayleigh()),
        "ROC: " + str(self.ROC()),
        "}"]

In the text output, this produces a paragraph like so:

Beam: LAS-tt {
    Power: 2.0W/Index: 1.0/Wavelength: 1064nm/Length: 1.0m
    Order: 2
	Origin: (0.2, 0.1, 0.0)
	...
	Rayleigh: (2.0, 1.0)m
	ROC: (3.0, 2.0)m
}

How to use this method? When passed to the helpers.tools.formatter function, a single string is returned, ready to be printed (or written to a file). Thus you may use the following to print the info on a optic (mirror1 for example):

print tools.formatter(mirror1.lines())

Thus one has the liberty to reimplement these functions (keeping the curly braces structure to integrate well) and use them to print text info or write it to a file.

Method 2: Permanent changes using the beam tree and simulation printing methods

As explained in the User Guide, the text optical output is divided into 2 sections: “SIMULATION DATA” and “BEAM LISTING”

1. Simulation data section

This output is defined in the beginning of the running.simulation.Simulation.writeOut method. The info of this section can be changed there. Again, a list of strings to write is built (with the curly braces format).

2. Beam listing section

Here, the information on the beams generated by the simulation can be found. The function of interest is tree.beamtree.BeamTree.outputLines. This returns a list of strings for each beam tree, calling some methods of the beams of this tree to obtain the values of the beam characteristics.

Essentially it all happens in the following lines:

if beam.Length == 0.:
    sList = ["(%s, %s) [open] %s {" % (beam.Optic, beam.Face, Ref)]
else:
    if self.R is None and self.T is None:
        sList = ["(%s, %s) %sm [end] (%s, %s) %s {" \
                % (beam.Optic, beam.Face, str(beam.Length),
                            beam.TargetOptic, beam.TargetFace, Ref)]
    else:
        sList = ["(%s, %s) %sm (%s, %s) %s {" \
                % (beam.Optic, beam.Face, str(beam.Length),
                            beam.TargetOptic, beam.TargetFace, Ref)]

    sList = sList + ["Waist Pos: (%s, %s)m" \
                                %(str(beam.DWx), str(beam.DWy)),
                    "Waist Size: (%s, %s)mm" \
                                %(str(beam.Wx/mm), str(beam.Wy/mm)),
                    "Direction: (%s, %s)deg" \
                                %(str(sph[0]/deg), str(sph[1]/deg)),
                    "}"]

All this does is check if the beams are end beams or open beams, and describe what the output lines contain. In this example, each beam section will contain the optic the beam departs from, the length, the target beam, the waist positions, sizes, and the direction of the beam.

You can put in any code here to change the output.

Method 3: Reading output format from a file

In order to customize the output of each simulation, it is recommended to have a separate “output format” file, say the same file as the config file, but with the “.ui” extension (e.g. “virgo_simulation.ui”).

In order to read this file only once during the run, here is an implementation suggestion:

  1. Have a new attribute (say OutputFormat) for the running.simulation.Simulation class, which is parsed from the file in the Simulation class constructor. By the way, the name of the configuration file (with ‘/’ but no extension, e.g. “virgo_simulations/august2017/virgo_core”) is the setting.fname global variable, which is accessible if the settings module is imported.

     # in the Simulation class constructor (__init__)
    
     # the readOutputFormat parses the file to find the output format
     self.OutputFormat = helpers.tools.readOutputFormat(settings.fname + '.ui')
    
  2. To print the beams characteristics, use the outputLines method (like in the previous paragraph), but passing the OutputFormat attribute along.

     # in the writeOut method of the Simulation class, in the "beam listing" section
      outList = tree.outputLines(self.OutputFormat) # instead of just tree.outputLines()
    
  3. Reimplement the outputLines using the new argument to customize the output.

For example, imagine that we want the beam length, power and Rayleigh ranges to be output, and that the output format file thus has this content:

Length
P
rayleigh()

These are the tokens to call the Length, P attributes and the rayleigh method of the beam.

Then, let us say that the output format parser (in the Simulation class constructor) returned the string “Length Power rayleigh()”.

Finally, we may call these attributes and method in the outputLines method using python’s eval function, and reimplement like so:

def outputLines(self, formatString):
    '''Return the list of lines to write the output of simulation.'''
    # list of methods and attribute to call on beams:
    thingsToCall = formatString.split() # ['Length', 'P', 'rayleigh()']

    sList = list()
    if self.Root is not None and (self.Root.N == 1 or not settings.short):
        beam = self.Root
        Ref = beam.Ref if not settings.short else shortRef(beam.Ref)
        sph = rectToSph(beam.Dir)
        if beam.Length == 0.:
            sList = ["(%s, %s) [open] %s {" % (beam.Optic, beam.Face, Ref)]
        else:
            if self.R is None and self.T is None:
                sList = ["(%s, %s) %sm [end] (%s, %s) %s {" \
                    % (beam.Optic, beam.Face, str(beam.Length),
                        beam.TargetOptic, beam.TargetFace, Ref)]
            else:
                sList = ["(%s, %s) %sm (%s, %s) %s {" \
                    % (beam.Optic, beam.Face, str(beam.Length),
                        beam.TargetOptic, beam.TargetFace, Ref)]

        # here is the change, call the attributes and method
        # listed in thingsToCall
        sList = sList + [token + ": " + str(eval("beam." + token)) for token in thingsToCall]\
                        + ["}"]
        if self.R is not None:
            sList = sList + self.R.outputLines()
        if self.T is not None:
            sList = sList + self.T.outputLines()

    return sList

Basically, here we evaluated “beam.Length”, “beam.P”, “beam.rayleigh()”, converted the results to strings, and made a list of these strings.

The output here would be like so:

(Laser, Out) 1.0m [end] (F1, HR) La400- {
		Length: 1.0
		Power: 12.
		rayleigh(): (1.0, 3.4)
	}

The units are missing, but we can put them in the output format file and handle that in the parser. There is no limit to what we can pass all the way down to the outputLines method.