
{ Practical astronomy | Computing | The computing environment }
The computing environment
- Python interpreter
- Debian Linux
- Cygwin on Windows 11
- First steps with Python
- Functions, modules, packages
- Our own application code
- Third party modules
- Design principles
- Error handling
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):
- bash (Base)
- tar (Base)
- gzip (Base)
- python3 (Python)
- python3-pip (Python)
- python3-virtualenv (Python)
- The build module seems unavailable in Cygwin, install it later
with
pip.
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:
- The functions can be moved out of the main script into a separate file, a module. The module can collect all your functions of similar purpose and the module can be imported by multiple main scripts to do different jobs with support from the same functions.
- A collection of one or more modules can become a package. This can also include zero or more main scripts.
- The package can then be something the developer develops, then builds into something that one or more users can copy and install in their virtual environment.
- With the develop, distribute, install paradigm, the zero or more main scripts become automatically created scripts that make simple reference to entry points in the module. As far as the developer is concerned, the main scripts are then particular functions in the module and not separate files.
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.
- 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.
- 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.
- 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:
TimSetUTis an automatically created script found on the command $PATH while the virtual environment is active.Ciel4.Times.EntryTimSetUTis the module function called by the above. This function merely obtains the command line argumentssys.argv[]and calls the next function:Ciel4.Times.TimSetUTdoes 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.
- 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.
- 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:
- Python scripts to be invoked from the Bash shell. These are created automatically during installation and merely call an entry point in the module.
- These entry points convert options and parameters from the Bash command line to Python variables.
- Python functions that convert and format information returned from the algorithm and put this out into a file or to the terminal.
- Python functions that implement complex algorithms by plugging together simple algorithms.
- Python functions that implement simple algorithms.
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:
- We do not simply continue without message.
- We should not simply continue, even with message.
- We should not simply exit, even with a message to the standard output or standard error channel.
- We should detect and avoid the error or we should trigger behaviour similar to the interpreter detecting an error.
- This can be split into detection at a low level and avoidance
or abort at a higher level. That involves explicit return of a
status code, not the abuse of a regular return variable.
The
try/exceptclause helps enormously with this.
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.
