chi and h site logo

{ Practical astronomy | Computing | The computing environment }


The computing environment

Subsections:

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 are 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. I have written detailed instructions (Meyerdierks 2026a) on installing Debian Linux (Debian 2026a) on a Windows machine (or a blank machine).

macOS is a Unix operating system and it should be possible to use it for the software outlined here. The choice of operating system 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 Windows you can install the Cygwin environment (Vinschen et al. 2017), that allows to work very similarly to Linux, while still being able to use Windows as usual.

Python interpreter

The operating system 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 the work here 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 decade or so. 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.

I illustrate here Debian Linux and Cygwin. Other platforms may be similar, but you will have to find out yourself.

Debian Linux

A Linux system will already include a Python interpreter, now usually version 3. This is because part of the operating system is written in Python. The flip side of this coin is that your application may need different versions of Python modules than the Linux system offers and needs. Such conflicts are avoided by using Python environments. The Linux system may in fact force you to use such an environment for your own or for third-party applications. Using a virtual environment is recommended anyway, because you may in the future require different ones for different applications.

A virtual environment is created by invoking the venv module, which is likely also included in your Linux system. If not, you can install it from the Linux release. You might also install pip as a handy tool for installing Python modules. Looking further ahead, you might also install build, which can package your own software into something that you or others can install. For Debian:

sudo apt-get install python3-venv python3-pip build

You should collect all your Python environments somewhere like ~/venv/. The following creates a Ciel4 environment:

cd ~
mkdir venv
python3 -m venv ~/venv/Ciel4

Since you can have multiple environments, you have to enter and leave them explicitly, each time you want to use and stop using one of them:

source ~/venv/Ciel4/bin/activate
  (do your work)
deactivate

If you look inside the environment with a file manager you will find a bin/ directory with commands that you can execute from the Linux command shell. This will include the python3 application, probably as a symbolic link to the application /usr/bin/python3 from the Linux release. There is also a lib/ directory tree, where Python modules are installed or get installed in the future.

Cygwin on Windows 11

To install Cygwin, and with it Python, take your web browser to https://www.cygwin.com and run the Cygwin Installer. This works to install the thing, but also later to update installed packages or to install additional ones. You are downloading a setup.exe and then have to open it (run it). Be sure to install these packages (category in parentheses):

Your normal access to Cygwin and Python is to run a Bash shell, which is probably labelled "Cygwin64 Terminal". Use that to create the environment for Ciel4. While venv is included in Python itself and works on Linux, under Cygwin we seem to have to use the alternative virtualenv:

cd ~
mkdir venv
virtualenv venv/Ciel4
source ~/venv/Ciel4/bin/activate
  pip install build
deactivate

Since you can have multiple environments, you have to enter and leave them explicitly, each time you want to use and stop using one of them:

source ~/venv/Ciel4/bin/activate
  (do your work)
deactivate

First steps with Python

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:

source ~/venv/Ciel4/bin/activate
python3
>>> print(5 * 5)
25
>>> 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))
5.0
>>> Ctrl-d
deactivate

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)
print(math.sqrt(25))

If the above file is called test.py, execute it like this:

source ~/venv/Ciel4/bin/activate
python3 test.py
python3 test.py
python3 test.py
deactivate

Next you will want to pass an argument to your code. Here are the new code

import sys
print(float(sys.argv[1])**2)

and how it is run, provided the virtual environment is active.

python3 test.py 2
python3 test.py 3
python3 test.py 5

Functions, modules, packages

As your code becomes more complex, you will delegate repeat work into functions that you define once and call multiple times. The source code begins with those definitions and ends with the main code:

import sys
def mysquare(number):
    result = number * number
    return result
print(mysquare(float(sys.argv[1])))

The indentation is crucial. The function definition ends when the indentation ends. Such indentation is also required for other language elements, if blocks, while loops, etc. This is perhaps the disadvantage of Python compared to languages like Fortran, C, C++ or Java. Those languages have explicit characters or words to start and end such clauses, they use indentation only for human readability.

To improve the readability, you can insert blank lines, but also comment lines that might explain to the reader what is going on.

We now fast-forward through a couple of further ideas on how to better organise your Python code:

Here is the directory and file structure of a minimal project named "squarepeg":

./
  pyproject.toml
  src/
    squarepeg/
      __init__.py
      func.py

The metadata for the project are contained in pyproject.toml. A minimal such file is:

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "squarepeg"
version = "1.0.0.1"

[project.scripts]
SquarePeg = "squarepeg.func:EntrySquarePeg"

The file __init__.py is just an empty file. It makes the other files in that directory into importable modules. More precisely, it makes squarepeg a module and func a sub-module. The module code func.py looks like this:

# Import modules that contain code used here.
import sys

# Define the entry point listed in pyproject.toml.
def EntrySquarePeg():
    print(mysquare(float(sys.argv[1])))
    return

# Define a function of this module.
def mysquare(number):
    # Calculate the result from the given argument.
    result = number * number
    # Return the result to the caller.
    return result

This is how the source code is built into a package:

python3 -m build
  ...
  Successfully built squarepeg-1.0.0.1.tar.gz
    and squarepeg-1.0.0.1-py2.py3-none-any.whl

and if you check, there now is a sub-directory dist with those two files. The tar ball is a "source distribution" and the other is a "wheel", which is in fact a ZIP archive. Either is installable with pip. The tar ball is at this point platform-independent; the installer will build and install the package from it. The wheel is platform-dependent. The name of the wheel file indicates that the wheel is suitable for Python v2, Python v3, and for any platform (Windows, Linux, macOS, Android, etc.).

We can now install the package in our own, currently active virtual environment by passing the file to the pip command:

pip install dist/squarepeg-1.0.0.1.tar.gz 
  Processing ./dist/squarepeg-1.0.0.1.tar.gz
    Installing build dependencies ... done
    Getting requirements to build wheel ... done
    Preparing metadata (pyproject.toml) ... done
  Building wheels for collected packages: squarepeg
    Building wheel for squarepeg (pyproject.toml) ... done
    Created wheel for squarepeg:
      filename=squarepeg-1.0.0.1-py2.py3-none-any.whl
      size=1541
      sha256=1c685...
    Stored in directory: /home/hme/.cache/pip/wheels/45/4c/1d/5cf...
  Successfully built squarepeg
  Installing collected packages: squarepeg
  Successfully installed squarepeg-1.0.0.1

If we spy on the virtual environment then we can find the func.py and __init__.py in ~/venv/Ciel4/lib/python3.13/site-packages/squarepeg/ (the Python version depends on your installation), but there is also a file SquarePeg in ~/venv/Ciel4/bin/. On Linux that file says:

#!/home/hme/venv/Ciel4/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from squarepeg.func import EntrySquarePeg
if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
    sys.exit(EntrySquarePeg())

There are minor differences that result from the pip installer creating these files for the platform in hand, Linux vs. Cygwin, Windows, macOS, etc.

The entry point now is a command, available while the virtual environment is active:

SquarePeg 25.3
  640.09

The pyproject.toml file should be used to give much more information about the package, author, home page, dependencies, README file, licence file, etc. etc.

The "source distribution" is a full tar ball of the developer directory tree, plus a PKG-INFO file and minus the dist sub-directory itself. If you use a revision control system like RCS or SVN, then their housekeeping files are included in the "source distribution" of the package. You might want to do some cleansing before building for actual distribution to users.

Our own application code

We will be writing commands and invoke them from the command line of the operating system. But we will also write functions that we collect in modules that in turn form part of a package "Ciel4" that we put together. The command scripts are created automatically during installation, but the modules must have the corresponding entry points.

You can use the attached Ciel4.tgz file archive to fill the directory structure with software I have already written. This is ready to build and install.

Because the installation is into your Python virtual environment, you can then do your daily work somewhere entirely different, say ~/today. Once you have activated the environment, 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 module (strictly) is a directory with an __init__.py file, a module (strictly a sub-module) like "Optics" is a file named Optics.py in such a directory.

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. With the interactive Python interpreter:

source ~/venv/Ciel4/bin/activate
python3
>>> import math
>>> import Ciel4.Optics as Optics
>>> print((3600. * 180. / math.pi) * Optics.Airy(200.))
  0.6920184249590083
deactivate

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

Third party modules

The world already has a plethora of Python modules to do all kinds of useful things. For example, math has functions like sine and cosine, the constant π, etc., and is already included in Python itself. To find suitable, additional packages, search https://pypi.org (PyPi 2026a). You can install packages with the pip command. Remember to do this while in the environment, i.e. after source'inc the activate:

source ~/venv/Ciel4/bin/activate
pip install matplotlib
deactivate

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. The following have guided me in putting together Ciel4. They will make little sense to you now, but if you dig deep into the code, you may recognise these points and then understand them.

  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 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. This is a function layering in addition to the script created automatically during installation. For example:
    1. TimSetUT is an automatically created script found on the command $PATH while the virtual environment is active.
    2. Ciel4.Times.EntryTimSetUT is the module function called by the above. This function merely obtains the command line arguments sys.argv[] and calls the next function:
    3. Ciel4.Times.TimSetUT does the actual job of converting the given ISO date and time into the internally used Julian Date, which is stored to the file .CL4_TIM.
    The first item is used from the OS command line. The third item can be called from an interactive Python interpreter, if the user wished to work in that manner. The third item can also be called by any Python function: Instead of concocting a Shell script to call multiple items of the first type, one should write a Python script to call the corresponding third items.
  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 are created automatically during installation and merely call an entry point in the module.
    2. These entry points convert options and parameters from the Bash command line to Python variables.
    3. Python functions that convert and format information returned from the algorithm and put this out into a file or to the terminal.
    4. Python functions that implement complex algorithms by plugging together simple algorithms.
    5. Python functions that implement simple algorithms.
    The fourth and fifth items cannot be clearly distinguished, but the first to third have strong implications for how we go about writing our software.

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 that we anticipate:

Consider the following code, where the main part of the code calls a function twice with different values. The first call succeeds, the second goes wrong:

#!/usr/bin/env python
import math

def worker(n):
    result = math.sqrt(n)
    return result

number = 25.
print(worker(number))
number = -25.
print(worker(number))

This code takes no precautions of its own and it is up to the Python interpreter to report the problem and kill the process.

./test.py 
5.0
Traceback (most recent call last):
  File "/home/hme/duo/docs/2017/03/12001/comp/environ/./test.py",
    line 16, in <module>
    print(worker(number))
          ~~~~~~^^^^^^^^
  File "/home/hme/duo/docs/2017/03/12001/comp/environ/./test.py",
    line 8, in worker
    result = math.sqrt(n)
ValueError: math domain error

If we anticipate the problem, we can place it within a try/except clause and code evasive action. In this simple case it makes little difference, but note that the application does not stumble and crash; it can continue to do useful things. In this case, however, it uses an inappropriate result as if nothing happened.

#!/usr/bin/env python
import math

def worker(n):
    try:
        result = math.sqrt(n)
    except:
        return 0.
    return result

number = 25.
print(worker(number))
number = -25.
print(worker(number))


./test.py 
5.0
0.0

Often the problem occurs in the function, but the calling function sees the wider picture and can take evasive action.

#!/usr/bin/env python
import math

def worker(n):
    result = math.sqrt(n)
    return result

number = 25.
try:
    print(worker(number))
except:
    print(number, "does not compute")
number = -25.
try:
    print(worker(number))
except:
    print(number, "does not compute")


./test.py 
5.0
-25.0 does not compute

If there is a genuine reason to abort, we can use the following code to abort after putting out the message on the standard error channel.

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

Alas, the Ciel4 code has only recently started to use try/except clauses. With hindsight I wish I had looked more closely at this before starting to code.