chi and h site logo

{ Practical astronomy | Computing | The computing environment }

The computing environment


Operating system

The choice of computer determines what you can do with it. The problem is that the choices are not obvious and the implications become clear only some way down the line. If you buy a desktop or laptop from Apple, you appear to be narrowed down to using macOS. If the hardware is what you might call a generic PC with an Intel or AMD central processor then you may have the impression that you have to run Microsoft Windows as operating system (OS).

You may also be aware of Linux, an open-source Unix-like operating system that is available for Intel and AMD hardware. You can replace Windows with Linux, or you can make the computer "dual-boot" so that you can run Windows one day and Linux the next day.

Apple computers are also Intel hardware, if rather highly tuned for use with macOS. You can replace macOS with Linux, but the hardware, such as keyboard or graphics card, may not be as well supported as with macOS or as with Linux on non-Apple hardware.

There is perhaps no need to force Linux upon an Apple computer, because macOS is a Unix operating system. It has a very substantial graphical interface hiding the Unix character and making it quite different from the graphical interface on a Linux system.

The choice of operating system (OS) matters when you need to run a particular application, which may not have been built for your operating system and may not be portable to your OS or its graphical interface. Such difficulties can be overcome to some degree. On macOS you can install the X11 graphics system that is common on Unix and Linux. On Windows you can install the Cygwin environment (Vinschen et al. 2017), that allows to work very similarly to Linux.

In this work we assume one of three OS-type environments:

  1. Linux with the Bash command line interface
  2. macOS with the Bash command line interface
  3. Cygwin on Windows with the Bash command line interface;
    Cygwin runs within Windows, you can use proper Windows applications alongside

Software development

The above environment provides only very basic functionalities, such as exploring what files exist. For real use of a computer you need more specific applications. Examples are word processors, spreadsheet applications, image editing suites. A major part of this work is to develop your own software. For this you need a software development environment.

Which environment you need depends on the language in which you write your software. We use Python (van Rossum et al. 2017), as programming language, which has been emerging as scientists' choice in the last few years. The language is, however, almost as old as BASIC and like BASIC can be very simple to use.

To develop software you need a compiler or interpreter, which understands the language you use to code and which translates this into binary machine code that the OS and the central processor understand. You also need some "system libraries" that the compiler/interpreter includes in your application to carry out much of its work. Such libraries are about opening, reading, writing and closing files, or about applying mathematical operators or functions.

Python – like any compiler/interpreter – comes with such system libraries. You can also add very many commonly used libraries from third parties. This is where it can become relevant that Python is popular among astronomers. You might find third party libraries that do a lot of the astronomical work without having to write your own algorithms.

In this work we assume the following software development environments:

  1. Linux with the Python 3.x interpreter from the Linux distribution
  2. macOS with the Python 3.x interpreter from a third party
  3. Cygwin with the Python 3.x interpreter, available as part of Cygwin

When you run Python as line-by-line interpreter, you can do simple maths right-away, but fancy functions require you to pull in a package or module that contains these functions:

$ python3
>>> print(5 * 5)
>>> print(sqrt(25))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'sqrt' is not defined
>>> import math
>>> print(math.sqrt(25))
>>> Ctrl-d

Soon you will want place your code into a script file and thus run a large batch of commands as fast as the processor will allow. The above in script form would look like this:

import math
print(5 * 5)

This has to be an executable file. If the above file is called, make it executable once and type its name as often as you want to run it.

chmod 755

We will be writing such application scripts and invoke them from the Bash command line. But we will also write functions that we collect in modules that in turn form part of a package "Ciel4" that we put together. In the examples, we will assume that you work in a directory called /home/juri/work. You can use any other directory, just replace the directory name consistently from the examples given. In this working directory we create a directory to contain the application scripts and another to contain the package. Each time before we use this, we have to make a few settings so that the applications and functions are found in the file system.

cd /home/juri/work

# Once create the directories
# .py files that are applications go into bin
# .py files that are modules within the package go into lib/Ciel4
mkdir bin
mkdir lib
mkdir lib/Ciel4
touch lib/Ciel4/

# Before a day's work, set up to find applications and modules
export PATH=$PATH:/home/juri/work/bin
export PYTHONPATH=/home/juri/work/lib

You can use the attached Ciel4.tgz file archive to create the directory structure and to fill it with software I have already written. (If you use Windows, be sure to include the tar and gzip utilities in your Cygwin installation.)

cd /home/juri/work

# Once create the directories and unpack the software files
# .py files that are applications go into bin
# .py files that are modules within the package go into lib/Ciel4
tar -zxvf /path/to/Ciel4.tgz

# Before a day's work, set up to find applications and modules
export PATH=$PATH:/home/juri/work/bin
export PYTHONPATH=/home/juri/work/lib

You can then do your daily work somewhere entirely different, say /home/juri/today. /home/juri/work is just where you place the software. Once you have run the two export commands, that software will be found even if you are in a different working directory. This can be useful, because when running the software in today files may be created, overwritten, re-read to help the software do its job. Today's job may be different from tomorrow's, and not having to do all work where the software is located adds flexibility to our working.

The package "Ciel4" contains modules with names like "Optics" or "Times". A package is a directory with an file, a module like "Optics" is a file named in such a directory. When used for the first time, a file Optics.pyc is also created; it is re-created whenever is used for the first time after a change has been made to it.

A module contains functions, which have names, take parameters, and return values. Between taking parameters and returning values, the functions perform arithmetic and other stuff, mathematical or otherwise. A small example is

def Airy(aAper):
    return(1.22 * 0.000550 / aAper)

which converts a given aperture in mm into an Airy disc radius in radian, assuming a wavelength of 550 μm. To use this function, we have to import the module and can then call the function:

$ python3
>>> import math
>>> import Ciel4.Optics as Optics
>>> print((3600. * 180. / math.pi) * Optics.Airy(200.))

meaning that a 200 mm aperture Schmidt-Cassegrain telescope has an angular resolution of 0.7".

Design principles

Coding software without a plan is what leads to re-designs, re-writes and software being thrown away later. It helps to write down a few rules first and the software second.

  1. We want simple utilities, each doing one small job well. This philosophy is borrowed from Unix, where the core commands serve only one limited purpose, need only simple input and make only simple output.
  2. There is a limited requirement to maintain information over longer periods of time. We use a small number of "hidden" files in the /home/juri/today working directory to keep such information. We hence need utilities to set and store such information and perhaps utilities to show such information. Also, most utilities are likely to retrieve information from these files to carry out their task.
  3. We want a command line interface with command history recall and editing. The Bash shell provides this. The Python interpreter also offers the feature of command recall and editing. We want to maintain the choice of using one or the other. From this follows that the Python scripts that we run from the Bash shell are only thin wrappers to a Python function that does the actual job. That way the Python function can be called either from Bash via the Python script or from the Python shell.
  4. We want to write simple algorithms, but we want the option to plug them together to make larger algorithms. From this follows that the bulk of the software will not make input or output from or to files or the terminal. Rather, the algorithms are in functions that make input and output only via the function parameters and return arrays.
  5. Input and output involving files and the terminal are then in thin wrappers around the algorithms. Overall we arrive at the following onion shells for our software:
    1. Python scripts to be invoked from the Bash shell. These convert options and parameters from the Bash command line to Python variables.
    2. Python functions that convert and format information returned from the algorithm and put this out into a file or to the terminal.
    3. Python functions that implement complex algorithms by plugging together simple algorithms.
    4. Python functions that implement simple algorithms.
    The third and fourth items cannot be clearly distinguished, but the first and second have strong implications for how we go about writing our software.

As an example of these principles, we begin with a utility to store the parameters of the lens we use for imaging. The Bash-callable utility is

import sys
import Ciel4.Optics as Optics
theAper   = float(sys.argv[1])
theFocLen = float(sys.argv[2])
Optics.OTASet(theAper, theFocLen)

As you see, this only converts two command line parameters from the Bash command line into numbers to transfer and use within Python software. The actual work of storing the information is done in the following function that can be called from within Python, which makes it more useful.

def OTASet(aAper, aFocLen):
    theFile = open(".CL4_OTA", "w")

This merely writes the two given numbers to a file. The opposite utility is even simpler at the outer wrapper level.

import Ciel4.Optics as Optics

The inner Python function to do the job is more complex, though. In the simplest case it could just read the numbers from the file written previously and put them out on the terminal. But it also makes some trivial calculations itself and uses some simple algorithms that are functions within the Optics module:

def OTAShow():
# Look up the stored aperture and focal length.
    theAper, theFocLen = OTAGet()
# Calculate f ratio f/D.
    theFocRatio = theFocLen / theAper
# Calculate Airy disc radius in arcsec.
    theAiry     = Airy(theAper)
    theAiry     = theAiry * 3600. * 180. / math.pi
    print("              Aperture [mm] ", theAper)
    print("          Focal length [mm] ", theFocLen)
    print("            Focal ratio f/D ", theFocRatio)
    print("        Resolution [arcsec] ", theAiry)

As an exercise, unpack the Ciel4.tgz file archive and find the files and the lines of code shown here.

Error handling

Things can go wrong. Some errors can be anticipated while coding the software. Errors we have not prepared for will normally be detected by the Python interpreter and reported in a reasonably useful manner. For error conditions we anticipate:

In practice, once an error is detected and a suitable message decided on, we can use the code

sys.exit("CodeLocation: Something went wrong.")

to abort after putting the message out on the standard error channel. With a non-trivial message, the return value will be set to +1 to signal an error condition; normal good exit would be signalled by a zero return value.