Enhancing theia -- Customizing the Output
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:
-
Have a new attribute (say
OutputFormat
) for therunning.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 thesetting.fname
global variable, which is accessible if thesettings
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')
-
To print the beams characteristics, use the
outputLines
method (like in the previous paragraph), but passing theOutputFormat
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()
-
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.